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     */
017    package org.apache.logging.log4j;
018    
019    import java.util.Arrays;
020    import java.util.concurrent.ConcurrentHashMap;
021    import 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     */
028    public 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    }