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 * Applications create Markers by using the Marker Manager. All Markers created by this Manager are immutable.
025 */
026public final class MarkerManager {
027
028    private static final ConcurrentMap<String, Marker> MARKERS = new ConcurrentHashMap<>();
029
030    private MarkerManager() {
031        // do nothing
032    }
033
034    /**
035     * Clears all markers.
036     */
037    public static void clear() {
038        MARKERS.clear();
039    }
040
041    /**
042     * Tests existence of the given marker.
043     * 
044     * @param key the marker name
045     * @return true if the marker exists.
046     * @since 2.4
047     */
048    public static boolean exists(final String key) {
049        return MARKERS.containsKey(key);
050    }
051
052    /**
053     * Retrieves a Marker or create a Marker that has no parent.
054     * 
055     * @param name The name of the Marker.
056     * @return The Marker with the specified name.
057     * @throws IllegalArgumentException if the argument is {@code null}
058     */
059    public static Marker getMarker(final String name) {
060        MARKERS.putIfAbsent(name, new Log4jMarker(name));
061        return MARKERS.get(name);
062    }
063
064    /**
065     * Retrieves or creates a Marker with the specified parent. The parent must have been previously created.
066     * 
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     * 
087     * @param name The name of the Marker.
088     * @param parent The parent Marker.
089     * @return The Marker with the specified name.
090     * @throws IllegalArgumentException if any argument is {@code null}
091     * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
092     */
093    @Deprecated
094    public static Marker getMarker(final String name, final Marker parent) {
095        MARKERS.putIfAbsent(name, new Log4jMarker(name));
096        return MARKERS.get(name).addParents(parent);
097    }
098
099    /**
100     * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em>
101     * <p>
102     * The actual Marker implementation.
103     * </p>
104     * <p>
105     * <em>Internal note: We could make this class package private instead of public if the class
106     * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn}
107     * is moved to this package and would of course stay in its current module.</em>
108     * </p>
109     */
110    public static class Log4jMarker implements Marker {
111
112        private static final long serialVersionUID = 100L;
113
114        private final String name;
115
116        private volatile Marker[] parents;
117
118        /**
119         * Required by JAXB and Jackson for XML and JSON IO.
120         */
121        @SuppressWarnings("unused")
122        private Log4jMarker() {
123            this.name = null;
124            this.parents = null;
125        }
126
127        /**
128         * Constructs a new Marker.
129         * 
130         * @param name the name of the Marker.
131         * @throws IllegalArgumentException if the argument is {@code null}
132         */
133        public Log4jMarker(final String name) {
134            if (name == null) {
135                // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker
136                // name seems rather pointless. To get an "anonymous" Marker, just use an empty string.
137                throw new IllegalArgumentException("Marker name cannot be null.");
138            }
139            this.name = name;
140            this.parents = null;
141        }
142
143        // TODO: use java.util.concurrent
144
145        @Override
146        public synchronized Marker addParents(final Marker... parentMarkers) {
147            if (parentMarkers == null) {
148                throw new IllegalArgumentException("A parent marker must be specified");
149            }
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            if (parent == null) {
186                throw new IllegalArgumentException("A parent marker must be specified");
187            }
188            final Marker[] localParents = this.parents;
189            if (localParents == null) {
190                return false;
191            }
192            final int localParentsLength = localParents.length;
193            if (localParentsLength == 1) {
194                if (localParents[0].equals(parent)) {
195                    parents = null;
196                    return true;
197                }
198                return false;
199            }
200            int index = 0;
201            final Marker[] markers = new Marker[localParentsLength - 1];
202            // noinspection ForLoopReplaceableByForEach
203            for (int i = 0; i < localParentsLength; i++) {
204                final Marker marker = localParents[i];
205                if (!marker.equals(parent)) {
206                    if (index == localParentsLength - 1) {
207                        // no need to swap array
208                        return false;
209                    }
210                    markers[index++] = marker;
211                }
212            }
213            parents = markers;
214            return true;
215        }
216
217        @Override
218        public Marker setParents(final Marker... markers) {
219            if (markers == null || markers.length == 0) {
220                this.parents = null;
221            } else {
222                final Marker[] array = new Marker[markers.length];
223                System.arraycopy(markers, 0, array, 0, markers.length);
224                this.parents = array;
225            }
226            return this;
227        }
228
229        @Override
230        public String getName() {
231            return this.name;
232        }
233
234        @Override
235        public Marker[] getParents() {
236            if (this.parents == null) {
237                return null;
238            }
239            return Arrays.copyOf(this.parents, this.parents.length);
240        }
241
242        @Override
243        public boolean hasParents() {
244            return this.parents != null;
245        }
246
247        @Override
248        public boolean isInstanceOf(final Marker marker) {
249            if (marker == null) {
250                throw new IllegalArgumentException("A marker parameter is required");
251            }
252            if (this == marker) {
253                return true;
254            }
255            final Marker[] localParents = parents;
256            if (localParents != null) {
257                // With only one or two parents the for loop is slower.
258                final int localParentsLength = localParents.length;
259                if (localParentsLength == 1) {
260                    return checkParent(localParents[0], marker);
261                }
262                if (localParentsLength == 2) {
263                    return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
264                }
265                // noinspection ForLoopReplaceableByForEach
266                for (int i = 0; i < localParentsLength; i++) {
267                    final Marker localParent = localParents[i];
268                    if (checkParent(localParent, marker)) {
269                        return true;
270                    }
271                }
272            }
273            return false;
274        }
275
276        @Override
277        public boolean isInstanceOf(final String markerName) {
278            if (markerName == null) {
279                throw new IllegalArgumentException("A marker name is required");
280            }
281            if (markerName.equals(this.getName())) {
282                return true;
283            }
284            // Use a real marker for child comparisons. It is faster than comparing the names.
285            final Marker marker = MARKERS.get(markerName);
286            if (marker == null) {
287                return false;
288            }
289            final Marker[] localParents = parents;
290            if (localParents != null) {
291                final int localParentsLength = localParents.length;
292                if (localParentsLength == 1) {
293                    return checkParent(localParents[0], marker);
294                }
295                if (localParentsLength == 2) {
296                    return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
297                }
298                // noinspection ForLoopReplaceableByForEach
299                for (int i = 0; i < localParentsLength; i++) {
300                    final Marker localParent = localParents[i];
301                    if (checkParent(localParent, marker)) {
302                        return true;
303                    }
304                }
305            }
306
307            return false;
308        }
309
310        private static boolean checkParent(final Marker parent, final Marker marker) {
311            if (parent == marker) {
312                return true;
313            }
314            final Marker[] localParents = parent instanceof Log4jMarker ? ((Log4jMarker) parent).parents : parent
315                    .getParents();
316            if (localParents != null) {
317                final int localParentsLength = localParents.length;
318                if (localParentsLength == 1) {
319                    return checkParent(localParents[0], marker);
320                }
321                if (localParentsLength == 2) {
322                    return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
323                }
324                // noinspection ForLoopReplaceableByForEach
325                for (int i = 0; i < localParentsLength; i++) {
326                    final Marker localParent = localParents[i];
327                    if (checkParent(localParent, marker)) {
328                        return true;
329                    }
330                }
331            }
332            return false;
333        }
334
335        /*
336         * Called from add while synchronized.
337         */
338        private static boolean contains(final Marker parent, final Marker... localParents) {
339            // noinspection ForLoopReplaceableByForEach
340            // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms
341            for (int i = 0, localParentsLength = localParents.length; i < localParentsLength; i++) {
342                final Marker marker = localParents[i];
343                if (marker == parent) {
344                    return true;
345                }
346            }
347            return false;
348        }
349
350        @Override
351        public boolean equals(final Object o) {
352            if (this == o) {
353                return true;
354            }
355            if (o == null || !(o instanceof Marker)) {
356                return false;
357            }
358            final Marker marker = (Marker) o;
359            return name.equals(marker.getName());
360        }
361
362        @Override
363        public int hashCode() {
364            return name.hashCode();
365        }
366
367        @Override
368        public String toString() {
369            // FIXME: might want to use an initial capacity; the default is 16 (or str.length() + 16)
370            final StringBuilder sb = new StringBuilder(name);
371            final Marker[] localParents = parents;
372            if (localParents != null) {
373                addParentInfo(sb, localParents);
374            }
375            return sb.toString();
376        }
377
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}