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