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
023import org.apache.logging.log4j.util.PerformanceSensitive;
024
025/**
026 * Applications create Markers by using the Marker Manager. All Markers created by this Manager are 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     *
046     * @param key the marker name
047     * @return true if the marker exists.
048     * @since 2.4
049     */
050    public static boolean exists(final String key) {
051        return MARKERS.containsKey(key);
052    }
053
054    /**
055     * Retrieves a Marker or create a Marker that has no parent.
056     *
057     * @param name The name of the Marker.
058     * @return The Marker with the specified name.
059     * @throws IllegalArgumentException if the argument is {@code null}
060     */
061    public static Marker getMarker(final String name) {
062        Marker result = MARKERS.get(name);
063        if (result == null) {
064            MARKERS.putIfAbsent(name, new Log4jMarker(name));
065            result = MARKERS.get(name);
066        }
067        return result;
068    }
069
070    /**
071     * Retrieves or creates a Marker with the specified parent. The parent must have been previously created.
072     *
073     * @param name The name of the Marker.
074     * @param parent The name of the parent Marker.
075     * @return The Marker with the specified name.
076     * @throws IllegalArgumentException if the parent Marker does not exist.
077     * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
078     */
079    @Deprecated
080    public static Marker getMarker(final String name, final String parent) {
081        final Marker parentMarker = MARKERS.get(parent);
082        if (parentMarker == null) {
083            throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined");
084        }
085        @SuppressWarnings("deprecation")
086        final Marker marker = getMarker(name, parentMarker);
087        return marker;
088    }
089
090    /**
091     * Retrieves or creates a Marker with the specified parent.
092     *
093     * @param name The name of the Marker.
094     * @param parent The parent Marker.
095     * @return The Marker with the specified name.
096     * @throws IllegalArgumentException if any argument is {@code null}
097     * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
098     */
099    @Deprecated
100    public static Marker getMarker(final String name, final Marker parent) {
101        return getMarker(name).addParents(parent);
102    }
103
104    /**
105     * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em>
106     * <p>
107     * The actual Marker implementation.
108     * </p>
109     * <p>
110     * <em>Internal note: We could make this class package private instead of public if the class
111     * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn}
112     * is moved to this package and would of course stay in its current module.</em>
113     * </p>
114     */
115    public static class Log4jMarker implements Marker {
116
117        private static final long serialVersionUID = 100L;
118
119        private final String name;
120
121        private volatile Marker[] parents;
122
123        /**
124         * Required by JAXB and Jackson for XML and JSON IO.
125         */
126        @SuppressWarnings("unused")
127        private Log4jMarker() {
128            this.name = null;
129            this.parents = null;
130        }
131
132        /**
133         * Constructs a new Marker.
134         *
135         * @param name the name of the Marker.
136         * @throws IllegalArgumentException if the argument is {@code null}
137         */
138        public Log4jMarker(final String name) {
139            // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker
140            // name seems rather pointless. To get an "anonymous" Marker, just use an empty string.
141            requireNonNull(name, "Marker name cannot be null.");
142            this.name = name;
143            this.parents = null;
144        }
145
146        // TODO: use java.util.concurrent
147
148        @Override
149        public synchronized Marker addParents(final Marker... parentMarkers) {
150            requireNonNull(parentMarkers, "A parent marker must be specified");
151            // It is not strictly necessary to copy the variable here but it should perform better than
152            // Accessing a volatile variable multiple times.
153            final Marker[] localParents = this.parents;
154            // Don't add a parent that is already in the hierarchy.
155            int count = 0;
156            int size = parentMarkers.length;
157            if (localParents != null) {
158                for (final Marker parent : parentMarkers) {
159                    if (!(contains(parent, localParents) || parent.isInstanceOf(this))) {
160                        ++count;
161                    }
162                }
163                if (count == 0) {
164                    return this;
165                }
166                size = localParents.length + count;
167            }
168            final Marker[] markers = new Marker[size];
169            if (localParents != null) {
170                // It's perfectly OK to call arraycopy in a synchronized context; it's still faster
171                // noinspection CallToNativeMethodWhileLocked
172                System.arraycopy(localParents, 0, markers, 0, localParents.length);
173            }
174            int index = localParents == null ? 0 : localParents.length;
175            for (final Marker parent : parentMarkers) {
176                if (localParents == null || !(contains(parent, localParents) || parent.isInstanceOf(this))) {
177                    markers[index++] = parent;
178                }
179            }
180            this.parents = markers;
181            return this;
182        }
183
184        @Override
185        public synchronized boolean remove(final Marker parent) {
186            requireNonNull(parent, "A parent marker must be specified");
187            final Marker[] localParents = this.parents;
188            if (localParents == null) {
189                return false;
190            }
191            final int localParentsLength = localParents.length;
192            if (localParentsLength == 1) {
193                if (localParents[0].equals(parent)) {
194                    parents = null;
195                    return true;
196                }
197                return false;
198            }
199            int index = 0;
200            final Marker[] markers = new Marker[localParentsLength - 1];
201            // noinspection ForLoopReplaceableByForEach
202            for (int i = 0; i < localParentsLength; i++) {
203                final Marker marker = localParents[i];
204                if (!marker.equals(parent)) {
205                    if (index == localParentsLength - 1) {
206                        // no need to swap array
207                        return false;
208                    }
209                    markers[index++] = marker;
210                }
211            }
212            parents = markers;
213            return true;
214        }
215
216        @Override
217        public Marker setParents(final Marker... markers) {
218            if (markers == null || markers.length == 0) {
219                this.parents = null;
220            } else {
221                final Marker[] array = new Marker[markers.length];
222                System.arraycopy(markers, 0, array, 0, markers.length);
223                this.parents = array;
224            }
225            return this;
226        }
227
228        @Override
229        public String getName() {
230            return this.name;
231        }
232
233        @Override
234        public Marker[] getParents() {
235            if (this.parents == null) {
236                return null;
237            }
238            return Arrays.copyOf(this.parents, this.parents.length);
239        }
240
241        @Override
242        public boolean hasParents() {
243            return this.parents != null;
244        }
245
246        @Override
247        @PerformanceSensitive({"allocation", "unrolled"})
248        public boolean isInstanceOf(final Marker marker) {
249            requireNonNull(marker, "A marker parameter is required");
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        @PerformanceSensitive({"allocation", "unrolled"})
276        public boolean isInstanceOf(final String markerName) {
277            requireNonNull(markerName, "A marker name is required");
278            if (markerName.equals(this.getName())) {
279                return true;
280            }
281            // Use a real marker for child comparisons. It is faster than comparing the names.
282            final Marker marker = MARKERS.get(markerName);
283            if (marker == null) {
284                return false;
285            }
286            final Marker[] localParents = parents;
287            if (localParents != null) {
288                final int localParentsLength = localParents.length;
289                if (localParentsLength == 1) {
290                    return checkParent(localParents[0], marker);
291                }
292                if (localParentsLength == 2) {
293                    return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
294                }
295                // noinspection ForLoopReplaceableByForEach
296                for (int i = 0; i < localParentsLength; i++) {
297                    final Marker localParent = localParents[i];
298                    if (checkParent(localParent, marker)) {
299                        return true;
300                    }
301                }
302            }
303
304            return false;
305        }
306
307        @PerformanceSensitive({"allocation", "unrolled"})
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
313                    .getParents();
314            if (localParents != null) {
315                final int localParentsLength = localParents.length;
316                if (localParentsLength == 1) {
317                    return checkParent(localParents[0], marker);
318                }
319                if (localParentsLength == 2) {
320                    return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
321                }
322                // noinspection ForLoopReplaceableByForEach
323                for (int i = 0; i < localParentsLength; i++) {
324                    final Marker localParent = localParents[i];
325                    if (checkParent(localParent, marker)) {
326                        return true;
327                    }
328                }
329            }
330            return false;
331        }
332
333        /*
334         * Called from add while synchronized.
335         */
336        @PerformanceSensitive("allocation")
337        private static boolean contains(final Marker parent, final Marker... localParents) {
338            // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms
339            // noinspection ForLoopReplaceableByForEach
340            for (int i = 0, localParentsLength = localParents.length; i < localParentsLength; i++) {
341                final Marker marker = localParents[i];
342                if (marker == parent) {
343                    return true;
344                }
345            }
346            return false;
347        }
348
349        @Override
350        public boolean equals(final Object o) {
351            if (this == o) {
352                return true;
353            }
354            if (o == null || !(o instanceof Marker)) {
355                return false;
356            }
357            final Marker marker = (Marker) o;
358            return name.equals(marker.getName());
359        }
360
361        @Override
362        public int hashCode() {
363            return name.hashCode();
364        }
365
366        @Override
367        public String toString() {
368            // FIXME: might want to use an initial capacity; the default is 16 (or str.length() + 16)
369            final StringBuilder sb = new StringBuilder(name);
370            final Marker[] localParents = parents;
371            if (localParents != null) {
372                addParentInfo(sb, localParents);
373            }
374            return sb.toString();
375        }
376
377        @PerformanceSensitive("allocation")
378        private static void addParentInfo(final StringBuilder sb, final Marker... parents) {
379            sb.append("[ ");
380            boolean first = true;
381            // noinspection ForLoopReplaceableByForEach
382            for (int i = 0, parentsLength = parents.length; i < parentsLength; i++) {
383                final Marker marker = parents[i];
384                if (!first) {
385                    sb.append(", ");
386                }
387                first = false;
388                sb.append(marker.getName());
389                final Marker[] p = marker instanceof Log4jMarker ? ((Log4jMarker) marker).parents : marker.getParents();
390                if (p != null) {
391                    addParentInfo(sb, p);
392                }
393            }
394            sb.append(" ]");
395        }
396    }
397
398    // this method wouldn't be necessary if Marker methods threw an NPE instead of an IAE for null values ;)
399    private static void requireNonNull(final Object obj, final String message) {
400        if (obj == null) {
401            throw new IllegalArgumentException(message);
402        }
403    }
404}