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;
024import org.apache.logging.log4j.util.StringBuilderFormattable;
025
026/**
027 * Applications create Markers by using the Marker Manager. All Markers created by this Manager are immutable.
028 */
029public final class MarkerManager {
030
031    private static final ConcurrentMap<String, Marker> MARKERS = new ConcurrentHashMap<>();
032
033    private MarkerManager() {
034        // do nothing
035    }
036
037    /**
038     * Clears all markers.
039     */
040    public static void clear() {
041        MARKERS.clear();
042    }
043
044    /**
045     * Tests existence of the given marker.
046     *
047     * @param key the marker name
048     * @return true if the marker exists.
049     * @since 2.4
050     */
051    public static boolean exists(final String key) {
052        return MARKERS.containsKey(key);
053    }
054
055    /**
056     * Retrieves a Marker or create a Marker that has no parent.
057     *
058     * @param name The name of the Marker.
059     * @return The Marker with the specified name.
060     * @throws IllegalArgumentException if the argument is {@code null}
061     */
062    public static Marker getMarker(final String name) {
063        Marker result = MARKERS.get(name);
064        if (result == null) {
065            MARKERS.putIfAbsent(name, new Log4jMarker(name));
066            result = MARKERS.get(name);
067        }
068        return result;
069    }
070
071    /**
072     * Retrieves or creates a Marker with the specified parent. The parent must have been previously created.
073     *
074     * @param name The name of the Marker.
075     * @param parent The name of the parent Marker.
076     * @return The Marker with the specified name.
077     * @throws IllegalArgumentException if the parent Marker does not exist.
078     * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
079     */
080    @Deprecated
081    public static Marker getMarker(final String name, final String parent) {
082        final Marker parentMarker = MARKERS.get(parent);
083        if (parentMarker == null) {
084            throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined");
085        }
086        return getMarker(name, parentMarker);
087    }
088
089    /**
090     * Retrieves or creates a Marker with the specified parent.
091     *
092     * @param name The name of the Marker.
093     * @param parent The parent Marker.
094     * @return The Marker with the specified name.
095     * @throws IllegalArgumentException if any argument is {@code null}
096     * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
097     */
098    @Deprecated
099    public static Marker getMarker(final String name, final Marker parent) {
100        return getMarker(name).addParents(parent);
101    }
102
103    /**
104     * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em>
105     * <p>
106     * The actual Marker implementation.
107     * </p>
108     * <p>
109     * <em>Internal note: We could make this class package private instead of public if the class
110     * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn}
111     * is moved to this package and would of course stay in its current module.</em>
112     * </p>
113     */
114    public static class Log4jMarker implements Marker, StringBuilderFormattable {
115
116        private static final long serialVersionUID = 100L;
117
118        private final String name;
119
120        private volatile Marker[] parents;
121
122        /**
123         * Required by JAXB and Jackson for XML and JSON IO.
124         */
125        @SuppressWarnings("unused")
126        private Log4jMarker() {
127            this.name = null;
128            this.parents = null;
129        }
130
131        /**
132         * Constructs a new Marker.
133         *
134         * @param name the name of the Marker.
135         * @throws IllegalArgumentException if the argument is {@code null}
136         */
137        public Log4jMarker(final String name) {
138            // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker
139            // name seems rather pointless. To get an "anonymous" Marker, just use an empty string.
140            requireNonNull(name, "Marker name cannot be null.");
141            this.name = name;
142            this.parents = null;
143        }
144
145        // TODO: use java.util.concurrent
146
147        @Override
148        public synchronized Marker addParents(final Marker... parentMarkers) {
149            requireNonNull(parentMarkers, "A parent marker must be specified");
150            // It is not strictly necessary to copy the variable here but it should perform better than
151            // Accessing a volatile variable multiple times.
152            final Marker[] localParents = this.parents;
153            // Don't add a parent that is already in the hierarchy.
154            int count = 0;
155            int size = parentMarkers.length;
156            if (localParents != null) {
157                for (final Marker parent : parentMarkers) {
158                    if (!(contains(parent, localParents) || parent.isInstanceOf(this))) {
159                        ++count;
160                    }
161                }
162                if (count == 0) {
163                    return this;
164                }
165                size = localParents.length + count;
166            }
167            final Marker[] markers = new Marker[size];
168            if (localParents != null) {
169                // It's perfectly OK to call arraycopy in a synchronized context; it's still faster
170                // noinspection CallToNativeMethodWhileLocked
171                System.arraycopy(localParents, 0, markers, 0, localParents.length);
172            }
173            int index = localParents == null ? 0 : localParents.length;
174            for (final Marker parent : parentMarkers) {
175                if (localParents == null || !(contains(parent, localParents) || parent.isInstanceOf(this))) {
176                    markers[index++] = parent;
177                }
178            }
179            this.parents = markers;
180            return this;
181        }
182
183        @Override
184        public synchronized boolean remove(final Marker parent) {
185            requireNonNull(parent, "A parent marker must be specified");
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        @PerformanceSensitive({"allocation", "unrolled"})
247        public boolean isInstanceOf(final Marker marker) {
248            requireNonNull(marker, "A marker parameter is required");
249            if (this == marker) {
250                return true;
251            }
252            final Marker[] localParents = parents;
253            if (localParents != null) {
254                // With only one or two parents the for loop is slower.
255                final int localParentsLength = localParents.length;
256                if (localParentsLength == 1) {
257                    return checkParent(localParents[0], marker);
258                }
259                if (localParentsLength == 2) {
260                    return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
261                }
262                // noinspection ForLoopReplaceableByForEach
263                for (int i = 0; i < localParentsLength; i++) {
264                    final Marker localParent = localParents[i];
265                    if (checkParent(localParent, marker)) {
266                        return true;
267                    }
268                }
269            }
270            return false;
271        }
272
273        @Override
274        @PerformanceSensitive({"allocation", "unrolled"})
275        public boolean isInstanceOf(final String markerName) {
276            requireNonNull(markerName, "A marker name is required");
277            if (markerName.equals(this.getName())) {
278                return true;
279            }
280            // Use a real marker for child comparisons. It is faster than comparing the names.
281            final Marker marker = MARKERS.get(markerName);
282            if (marker == null) {
283                return false;
284            }
285            final Marker[] localParents = parents;
286            if (localParents != null) {
287                final int localParentsLength = localParents.length;
288                if (localParentsLength == 1) {
289                    return checkParent(localParents[0], marker);
290                }
291                if (localParentsLength == 2) {
292                    return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
293                }
294                // noinspection ForLoopReplaceableByForEach
295                for (int i = 0; i < localParentsLength; i++) {
296                    final Marker localParent = localParents[i];
297                    if (checkParent(localParent, marker)) {
298                        return true;
299                    }
300                }
301            }
302
303            return false;
304        }
305
306        @PerformanceSensitive({"allocation", "unrolled"})
307        private static boolean checkParent(final Marker parent, final Marker marker) {
308            if (parent == marker) {
309                return true;
310            }
311            final Marker[] localParents = parent instanceof Log4jMarker ? ((Log4jMarker) parent).parents : parent
312                    .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        @PerformanceSensitive("allocation")
336        private static boolean contains(final Marker parent, final Marker... localParents) {
337            // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms
338            // noinspection ForLoopReplaceableByForEach
339            for (int i = 0, localParentsLength = localParents.length; i < localParentsLength; i++) {
340                final Marker marker = localParents[i];
341                if (marker == parent) {
342                    return true;
343                }
344            }
345            return false;
346        }
347
348        @Override
349        public boolean equals(final Object o) {
350            if (this == o) {
351                return true;
352            }
353            if (o == null || !(o instanceof Marker)) {
354                return false;
355            }
356            final Marker marker = (Marker) o;
357            return name.equals(marker.getName());
358        }
359
360        @Override
361        public int hashCode() {
362            return name.hashCode();
363        }
364
365        @Override
366        public String toString() {
367            // FIXME: might want to use an initial capacity; the default is 16 (or str.length() + 16)
368            final StringBuilder sb = new StringBuilder();
369            formatTo(sb);
370            return sb.toString();
371        }
372
373        @Override
374        public void formatTo(final StringBuilder sb) {
375            sb.append(name);
376            final Marker[] localParents = parents;
377            if (localParents != null) {
378                addParentInfo(sb, localParents);
379            }
380        }
381
382        @PerformanceSensitive("allocation")
383        private static void addParentInfo(final StringBuilder sb, final Marker... parents) {
384            sb.append("[ ");
385            boolean first = true;
386            // noinspection ForLoopReplaceableByForEach
387            for (int i = 0, parentsLength = parents.length; i < parentsLength; i++) {
388                final Marker marker = parents[i];
389                if (!first) {
390                    sb.append(", ");
391                }
392                first = false;
393                sb.append(marker.getName());
394                final Marker[] p = marker instanceof Log4jMarker ? ((Log4jMarker) marker).parents : marker.getParents();
395                if (p != null) {
396                    addParentInfo(sb, p);
397                }
398            }
399            sb.append(" ]");
400        }
401    }
402
403    // this method wouldn't be necessary if Marker methods threw an NPE instead of an IAE for null values ;)
404    private static void requireNonNull(final Object obj, final String message) {
405        if (obj == null) {
406            throw new IllegalArgumentException(message);
407        }
408    }
409}