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        return getMarker(name, parentMarker);
086    }
087
088    /**
089     * Retrieves or creates a Marker with the specified parent.
090     *
091     * @param name The name of the Marker.
092     * @param parent The parent Marker.
093     * @return The Marker with the specified name.
094     * @throws IllegalArgumentException if any argument is {@code null}
095     * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
096     */
097    @Deprecated
098    public static Marker getMarker(final String name, final Marker parent) {
099        return getMarker(name).addParents(parent);
100    }
101
102    /**
103     * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em>
104     * <p>
105     * The actual Marker implementation.
106     * </p>
107     * <p>
108     * <em>Internal note: We could make this class package private instead of public if the class
109     * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn}
110     * is moved to this package and would of course stay in its current module.</em>
111     * </p>
112     */
113    public static class Log4jMarker implements Marker {
114
115        private static final long serialVersionUID = 100L;
116
117        private final String name;
118
119        private volatile Marker[] parents;
120
121        /**
122         * Required by JAXB and Jackson for XML and JSON IO.
123         */
124        @SuppressWarnings("unused")
125        private Log4jMarker() {
126            this.name = null;
127            this.parents = null;
128        }
129
130        /**
131         * Constructs a new Marker.
132         *
133         * @param name the name of the Marker.
134         * @throws IllegalArgumentException if the argument is {@code null}
135         */
136        public Log4jMarker(final String name) {
137            // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker
138            // name seems rather pointless. To get an "anonymous" Marker, just use an empty string.
139            requireNonNull(name, "Marker name cannot be null.");
140            this.name = name;
141            this.parents = null;
142        }
143
144        // TODO: use java.util.concurrent
145
146        @Override
147        public synchronized Marker addParents(final Marker... parentMarkers) {
148            requireNonNull(parentMarkers, "A parent marker must be specified");
149            // It is not strictly necessary to copy the variable here but it should perform better than
150            // Accessing a volatile variable multiple times.
151            final Marker[] localParents = this.parents;
152            // Don't add a parent that is already in the hierarchy.
153            int count = 0;
154            int size = parentMarkers.length;
155            if (localParents != null) {
156                for (final Marker parent : parentMarkers) {
157                    if (!(contains(parent, localParents) || parent.isInstanceOf(this))) {
158                        ++count;
159                    }
160                }
161                if (count == 0) {
162                    return this;
163                }
164                size = localParents.length + count;
165            }
166            final Marker[] markers = new Marker[size];
167            if (localParents != null) {
168                // It's perfectly OK to call arraycopy in a synchronized context; it's still faster
169                // noinspection CallToNativeMethodWhileLocked
170                System.arraycopy(localParents, 0, markers, 0, localParents.length);
171            }
172            int index = localParents == null ? 0 : localParents.length;
173            for (final Marker parent : parentMarkers) {
174                if (localParents == null || !(contains(parent, localParents) || parent.isInstanceOf(this))) {
175                    markers[index++] = parent;
176                }
177            }
178            this.parents = markers;
179            return this;
180        }
181
182        @Override
183        public synchronized boolean remove(final Marker parent) {
184            requireNonNull(parent, "A parent marker must be specified");
185            final Marker[] localParents = this.parents;
186            if (localParents == null) {
187                return false;
188            }
189            final int localParentsLength = localParents.length;
190            if (localParentsLength == 1) {
191                if (localParents[0].equals(parent)) {
192                    parents = null;
193                    return true;
194                }
195                return false;
196            }
197            int index = 0;
198            final Marker[] markers = new Marker[localParentsLength - 1];
199            // noinspection ForLoopReplaceableByForEach
200            for (int i = 0; i < localParentsLength; i++) {
201                final Marker marker = localParents[i];
202                if (!marker.equals(parent)) {
203                    if (index == localParentsLength - 1) {
204                        // no need to swap array
205                        return false;
206                    }
207                    markers[index++] = marker;
208                }
209            }
210            parents = markers;
211            return true;
212        }
213
214        @Override
215        public Marker setParents(final Marker... markers) {
216            if (markers == null || markers.length == 0) {
217                this.parents = null;
218            } else {
219                final Marker[] array = new Marker[markers.length];
220                System.arraycopy(markers, 0, array, 0, markers.length);
221                this.parents = array;
222            }
223            return this;
224        }
225
226        @Override
227        public String getName() {
228            return this.name;
229        }
230
231        @Override
232        public Marker[] getParents() {
233            if (this.parents == null) {
234                return null;
235            }
236            return Arrays.copyOf(this.parents, this.parents.length);
237        }
238
239        @Override
240        public boolean hasParents() {
241            return this.parents != null;
242        }
243
244        @Override
245        @PerformanceSensitive({"allocation", "unrolled"})
246        public boolean isInstanceOf(final Marker marker) {
247            requireNonNull(marker, "A marker parameter is required");
248            if (this == marker) {
249                return true;
250            }
251            final Marker[] localParents = parents;
252            if (localParents != null) {
253                // With only one or two parents the for loop is slower.
254                final int localParentsLength = localParents.length;
255                if (localParentsLength == 1) {
256                    return checkParent(localParents[0], marker);
257                }
258                if (localParentsLength == 2) {
259                    return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
260                }
261                // noinspection ForLoopReplaceableByForEach
262                for (int i = 0; i < localParentsLength; i++) {
263                    final Marker localParent = localParents[i];
264                    if (checkParent(localParent, marker)) {
265                        return true;
266                    }
267                }
268            }
269            return false;
270        }
271
272        @Override
273        @PerformanceSensitive({"allocation", "unrolled"})
274        public boolean isInstanceOf(final String markerName) {
275            requireNonNull(markerName, "A marker name is required");
276            if (markerName.equals(this.getName())) {
277                return true;
278            }
279            // Use a real marker for child comparisons. It is faster than comparing the names.
280            final Marker marker = MARKERS.get(markerName);
281            if (marker == null) {
282                return false;
283            }
284            final Marker[] localParents = parents;
285            if (localParents != null) {
286                final int localParentsLength = localParents.length;
287                if (localParentsLength == 1) {
288                    return checkParent(localParents[0], marker);
289                }
290                if (localParentsLength == 2) {
291                    return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
292                }
293                // noinspection ForLoopReplaceableByForEach
294                for (int i = 0; i < localParentsLength; i++) {
295                    final Marker localParent = localParents[i];
296                    if (checkParent(localParent, marker)) {
297                        return true;
298                    }
299                }
300            }
301
302            return false;
303        }
304
305        @PerformanceSensitive({"allocation", "unrolled"})
306        private static boolean checkParent(final Marker parent, final Marker marker) {
307            if (parent == marker) {
308                return true;
309            }
310            final Marker[] localParents = parent instanceof Log4jMarker ? ((Log4jMarker) parent).parents : parent
311                    .getParents();
312            if (localParents != null) {
313                final int localParentsLength = localParents.length;
314                if (localParentsLength == 1) {
315                    return checkParent(localParents[0], marker);
316                }
317                if (localParentsLength == 2) {
318                    return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
319                }
320                // noinspection ForLoopReplaceableByForEach
321                for (int i = 0; i < localParentsLength; i++) {
322                    final Marker localParent = localParents[i];
323                    if (checkParent(localParent, marker)) {
324                        return true;
325                    }
326                }
327            }
328            return false;
329        }
330
331        /*
332         * Called from add while synchronized.
333         */
334        @PerformanceSensitive("allocation")
335        private static boolean contains(final Marker parent, final Marker... localParents) {
336            // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms
337            // noinspection ForLoopReplaceableByForEach
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        @PerformanceSensitive("allocation")
376        private static void addParentInfo(final StringBuilder sb, final Marker... parents) {
377            sb.append("[ ");
378            boolean first = true;
379            // noinspection ForLoopReplaceableByForEach
380            for (int i = 0, parentsLength = parents.length; i < parentsLength; i++) {
381                final Marker marker = parents[i];
382                if (!first) {
383                    sb.append(", ");
384                }
385                first = false;
386                sb.append(marker.getName());
387                final Marker[] p = marker instanceof Log4jMarker ? ((Log4jMarker) marker).parents : marker.getParents();
388                if (p != null) {
389                    addParentInfo(sb, p);
390                }
391            }
392            sb.append(" ]");
393        }
394    }
395
396    // this method wouldn't be necessary if Marker methods threw an NPE instead of an IAE for null values ;)
397    private static void requireNonNull(final Object obj, final String message) {
398        if (obj == null) {
399            throw new IllegalArgumentException(message);
400        }
401    }
402}