001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j;
018
019import java.util.Arrays;
020import java.util.concurrent.ConcurrentHashMap;
021import java.util.concurrent.ConcurrentMap;
022
023
024/**
025 * Applications create Markers by using the Marker Manager. All Markers created by this Manager are
026 * immutable.
027 */
028public final class MarkerManager {
029
030    private static final ConcurrentMap<String, Marker> MARKERS = new ConcurrentHashMap<>();
031
032    private MarkerManager() {
033        // do nothing
034    }
035
036    /**
037     * Clears all markers.
038     */
039    public static void clear() {
040        MARKERS.clear();
041    }
042
043    /**
044     * Tests existence of the given marker.
045     * @param key the marker name
046     * @return true if the marker exists.
047     * @since 2.4
048     */
049    public static boolean exists(final String key) {
050        return MARKERS.containsKey(key);
051    }
052
053
054    /**
055     * Retrieves a Marker or create a Marker that has no parent.
056     * @param name The name of the Marker.
057     * @return The Marker with the specified name.
058     * @throws IllegalArgumentException if the argument is {@code null}
059     */
060    public static Marker getMarker(final String name) {
061        MARKERS.putIfAbsent(name, new Log4jMarker(name));
062        return MARKERS.get(name);
063    }
064
065    /**
066     * Retrieves or creates a Marker with the specified parent. The parent must have been previously created.
067     * @param name The name of the Marker.
068     * @param parent The name of the parent Marker.
069     * @return The Marker with the specified name.
070     * @throws IllegalArgumentException if the parent Marker does not exist.
071     * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
072     */
073    @Deprecated
074    public static Marker getMarker(final String name, final String parent) {
075        final Marker parentMarker = MARKERS.get(parent);
076        if (parentMarker == null) {
077            throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined");
078        }
079        @SuppressWarnings("deprecation")
080        final Marker marker = getMarker(name, parentMarker);
081        return marker;
082    }
083
084    /**
085     * Retrieves or creates a Marker with the specified parent.
086     * @param name The name of the Marker.
087     * @param parent The parent Marker.
088     * @return The Marker with the specified name.
089     * @throws IllegalArgumentException if any argument is {@code null}
090     * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
091     */
092    @Deprecated
093    public static Marker getMarker(final String name, final Marker parent) {
094        MARKERS.putIfAbsent(name, new Log4jMarker(name));
095        return MARKERS.get(name).addParents(parent);
096    }
097
098    /**
099     * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em>
100     * <p>
101     * The actual Marker implementation.
102     * </p>
103     * <p>
104     * <em>Internal note: We could make this class package private instead of public if the class
105     * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn}
106     * is moved to this package and would of course stay in its current module.</em>
107     * </p>
108     */
109    public static class Log4jMarker implements Marker {
110
111        private static final long serialVersionUID = 100L;
112
113        private final String name;
114
115        private volatile Marker[] parents;
116
117        /**
118         * Required by JAXB and Jackson for XML and JSON IO.
119         */
120        @SuppressWarnings("unused")
121        private Log4jMarker() {
122            this.name = null;
123            this.parents = null;
124        }
125
126        /**
127         * Constructs a new Marker.
128         * @param name the name of the Marker.
129         * @throws IllegalArgumentException if the argument is {@code null}
130         */
131        public Log4jMarker(final String name) {
132            if (name == null) {
133                // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker
134                // name seems rather pointless. To get an "anonymous" Marker, just use an empty string.
135                throw new IllegalArgumentException("Marker name cannot be null.");
136            }
137            this.name = name;
138            this.parents = null;
139        }
140
141        // TODO: use java.util.concurrent
142
143        @Override
144        public synchronized Marker addParents(final Marker... parents) {
145            if (parents == null) {
146                throw new IllegalArgumentException("A parent marker must be specified");
147            }
148            // It is not strictly necessary to copy the variable here but it should perform better than
149            // Accessing a volatile variable multiple times.
150            final Marker[] localParents = this.parents;
151            // Don't add a parent that is already in the hierarchy.
152            int count = 0;
153            int size = parents.length;
154            if (localParents != null) {
155                for (final Marker parent : parents) {
156                    if (!(contains(parent, localParents) || parent.isInstanceOf(this))) {
157                        ++count;
158                    }
159                }
160                if (count == 0) {
161                    return this;
162                }
163                size = localParents.length + count;
164            }
165            final Marker[] markers = new Marker[size];
166            if (localParents != null) {
167                // It's perfectly OK to call arraycopy in a synchronized context; it's still faster
168                //noinspection CallToNativeMethodWhileLocked
169                System.arraycopy(localParents, 0, markers, 0, localParents.length);
170            }
171            int index = localParents == null ? 0 : localParents.length;
172            for (final Marker parent : parents) {
173                if (localParents == null || !(contains(parent, localParents) || parent.isInstanceOf(this))) {
174                    markers[index++] = parent;
175                }
176            }
177            this.parents = markers;
178            return this;
179        }
180
181        @Override
182        public synchronized boolean remove(final Marker parent) {
183            if (parent == null) {
184                throw new IllegalArgumentException("A parent marker must be specified");
185            }
186            final Marker[] localParents = this.parents;
187            if (localParents == null) {
188                return false;
189            }
190            final int localParentsLength = localParents.length;
191            if (localParentsLength == 1) {
192                if (localParents[0].equals(parent)) {
193                    parents = null;
194                    return true;
195                }
196                return false;
197            }
198            int index = 0;
199            final Marker[] markers = new Marker[localParentsLength - 1];
200            //noinspection ForLoopReplaceableByForEach
201            for (int i = 0; i < localParentsLength; i++) {
202                final Marker marker = localParents[i];
203                if (!marker.equals(parent)) {
204                    if (index == localParentsLength - 1) {
205                        // no need to swap array
206                        return false;
207                    }
208                    markers[index++] = marker;
209                }
210            }
211            parents = markers;
212            return true;
213        }
214
215        @Override
216        public Marker setParents(final Marker... markers) {
217            if (markers == null || markers.length == 0) {
218                this.parents = null;
219            } else {
220                final Marker[] array = new Marker[markers.length];
221                System.arraycopy(markers, 0, array, 0, markers.length);
222                this.parents = array;
223            }
224            return this;
225        }
226
227        @Override
228        public String getName() {
229            return this.name;
230        }
231
232        @Override
233        public Marker[] getParents() {
234            if (this.parents == null) {
235                return null;
236            }
237            return Arrays.copyOf(this.parents, this.parents.length);
238        }
239
240        @Override
241        public boolean hasParents() {
242            return this.parents != null;
243        }
244
245        @Override
246        public boolean isInstanceOf(final Marker marker) {
247            if (marker == null) {
248                throw new IllegalArgumentException("A marker parameter is required");
249            }
250            if (this == marker) {
251                return true;
252            }
253            final Marker[] localParents = parents;
254            if (localParents != null) {
255                // With only one or two parents the for loop is slower.
256                final int localParentsLength = localParents.length;
257                if (localParentsLength == 1) {
258                    return checkParent(localParents[0], marker);
259                }
260                if (localParentsLength == 2) {
261                    return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
262                }
263                //noinspection ForLoopReplaceableByForEach
264                for (int i = 0; i < localParentsLength; i++) {
265                    final Marker localParent = localParents[i];
266                    if (checkParent(localParent, marker)) {
267                        return true;
268                    }
269                }
270            }
271            return false;
272        }
273
274        @Override
275        public boolean isInstanceOf(final String markerName) {
276            if (markerName == null) {
277                throw new IllegalArgumentException("A marker name is required");
278            }
279            if (markerName.equals(this.getName())) {
280                return true;
281            }
282            // Use a real marker for child comparisons. It is faster than comparing the names.
283            final Marker marker = MARKERS.get(markerName);
284            if (marker == null) {
285                return false;
286            }
287            final Marker[] localParents = parents;
288            if (localParents != null) {
289                final int localParentsLength = localParents.length;
290                if (localParentsLength == 1) {
291                    return checkParent(localParents[0], marker);
292                }
293                if (localParentsLength == 2) {
294                    return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
295                }
296                //noinspection ForLoopReplaceableByForEach
297                for (int i = 0; i < localParentsLength; i++) {
298                    final Marker localParent = localParents[i];
299                    if (checkParent(localParent, marker)) {
300                        return true;
301                    }
302                }
303            }
304
305            return false;
306        }
307
308        private static boolean checkParent(final Marker parent, final Marker marker) {
309            if (parent == marker) {
310                return true;
311            }
312            final Marker[] localParents = parent instanceof Log4jMarker ? ((Log4jMarker)parent).parents : parent.getParents();
313            if (localParents != null) {
314                final int localParentsLength = localParents.length;
315                if (localParentsLength == 1) {
316                    return checkParent(localParents[0], marker);
317                }
318                if (localParentsLength == 2) {
319                    return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
320                }
321                //noinspection ForLoopReplaceableByForEach
322                for (int i = 0; i < localParentsLength; i++) {
323                    final Marker localParent = localParents[i];
324                    if (checkParent(localParent, marker)) {
325                        return true;
326                    }
327                }
328            }
329            return false;
330        }
331
332        /*
333         * Called from add while synchronized.
334         */
335        private static boolean contains(final Marker parent, final Marker... localParents) {
336            //noinspection ForLoopReplaceableByForEach
337            // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms
338            for (int i = 0, localParentsLength = localParents.length; i < localParentsLength; i++) {
339                final Marker marker = localParents[i];
340                if (marker == parent) {
341                    return true;
342                }
343            }
344            return false;
345        }
346
347        @Override
348        public boolean equals(final Object o) {
349            if (this == o) {
350                return true;
351            }
352            if (o == null || !(o instanceof Marker)) {
353                return false;
354            }
355            final Marker marker = (Marker) o;
356            return name.equals(marker.getName());
357        }
358
359        @Override
360        public int hashCode() {
361            return name.hashCode();
362        }
363
364        @Override
365        public String toString() {
366            // FIXME: might want to use an initial capacity; the default is 16 (or str.length() + 16)
367            final StringBuilder sb = new StringBuilder(name);
368            final Marker[] localParents = parents;
369            if (localParents != null) {
370                addParentInfo(sb, localParents);
371            }
372            return sb.toString();
373        }
374
375        private static void addParentInfo(final StringBuilder sb, final Marker... parents) {
376            sb.append("[ ");
377            boolean first = true;
378            //noinspection ForLoopReplaceableByForEach
379            for (int i = 0, parentsLength = parents.length; i < parentsLength; i++) {
380                final Marker marker = parents[i];
381                if (!first) {
382                    sb.append(", ");
383                }
384                first = false;
385                sb.append(marker.getName());
386                final Marker[] p = marker instanceof Log4jMarker ? ((Log4jMarker) marker).parents : marker.getParents();
387                if (p != null) {
388                    addParentInfo(sb, p);
389                }
390            }
391            sb.append(" ]");
392        }
393    }
394
395}