View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j;
18  
19  import java.util.Arrays;
20  import java.util.concurrent.ConcurrentHashMap;
21  import java.util.concurrent.ConcurrentMap;
22  
23  import org.apache.logging.log4j.util.PerformanceSensitive;
24  import org.apache.logging.log4j.util.StringBuilderFormattable;
25  
26  /**
27   * Applications create Markers by using the Marker Manager. All Markers created by this Manager are immutable.
28   */
29  public final class MarkerManager {
30  
31      private static final ConcurrentMap<String, Marker> MARKERS = new ConcurrentHashMap<>();
32  
33      private MarkerManager() {
34          // do nothing
35      }
36  
37      /**
38       * Clears all markers.
39       */
40      public static void clear() {
41          MARKERS.clear();
42      }
43  
44      /**
45       * Tests existence of the given marker.
46       *
47       * @param key the marker name
48       * @return true if the marker exists.
49       * @since 2.4
50       */
51      public static boolean exists(final String key) {
52          return MARKERS.containsKey(key);
53      }
54  
55      /**
56       * Retrieves a Marker or create a Marker that has no parent.
57       *
58       * @param name The name of the Marker.
59       * @return The Marker with the specified name.
60       * @throws IllegalArgumentException if the argument is {@code null}
61       */
62      public static Marker getMarker(final String name) {
63          Marker result = MARKERS.get(name);
64          if (result == null) {
65              MARKERS.putIfAbsent(name, new Log4jMarker(name));
66              result = MARKERS.get(name);
67          }
68          return result;
69      }
70  
71      /**
72       * Retrieves or creates a Marker with the specified parent. The parent must have been previously created.
73       *
74       * @param name The name of the Marker.
75       * @param parent The name of the parent Marker.
76       * @return The Marker with the specified name.
77       * @throws IllegalArgumentException if the parent Marker does not exist.
78       * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
79       */
80      @Deprecated
81      public static Marker getMarker(final String name, final String parent) {
82          final Marker parentMarker = MARKERS.get(parent);
83          if (parentMarker == null) {
84              throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined");
85          }
86          return getMarker(name, parentMarker);
87      }
88  
89      /**
90       * Retrieves or creates a Marker with the specified parent.
91       *
92       * @param name The name of the Marker.
93       * @param parent The parent Marker.
94       * @return The Marker with the specified name.
95       * @throws IllegalArgumentException if any argument is {@code null}
96       * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
97       */
98      @Deprecated
99      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             Marker[] parentsSnapshot = parents;
235             if (parentsSnapshot == null) {
236                 return null;
237             }
238             return Arrays.copyOf(parentsSnapshot, parentsSnapshot.length);
239         }
240 
241         @Override
242         public boolean hasParents() {
243             return this.parents != null;
244         }
245 
246         @Override
247         @PerformanceSensitive({"allocation", "unrolled"})
248         public boolean isInstanceOf(final Marker marker) {
249             requireNonNull(marker, "A marker parameter is required");
250             if (this == marker) {
251                 return true;
252             }
253             final Marker[] localParents = parents;
254             if (localParents != null) {
255                 // With only one or two parents the for loop is slower.
256                 final int localParentsLength = localParents.length;
257                 if (localParentsLength == 1) {
258                     return checkParent(localParents[0], marker);
259                 }
260                 if (localParentsLength == 2) {
261                     return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
262                 }
263                 // noinspection ForLoopReplaceableByForEach
264                 for (int i = 0; i < localParentsLength; i++) {
265                     final Marker localParent = localParents[i];
266                     if (checkParent(localParent, marker)) {
267                         return true;
268                     }
269                 }
270             }
271             return false;
272         }
273 
274         @Override
275         @PerformanceSensitive({"allocation", "unrolled"})
276         public boolean isInstanceOf(final String markerName) {
277             requireNonNull(markerName, "A marker name is required");
278             if (markerName.equals(this.getName())) {
279                 return true;
280             }
281             // Use a real marker for child comparisons. It is faster than comparing the names.
282             final Marker marker = MARKERS.get(markerName);
283             if (marker == null) {
284                 return false;
285             }
286             final Marker[] localParents = parents;
287             if (localParents != null) {
288                 final int localParentsLength = localParents.length;
289                 if (localParentsLength == 1) {
290                     return checkParent(localParents[0], marker);
291                 }
292                 if (localParentsLength == 2) {
293                     return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
294                 }
295                 // noinspection ForLoopReplaceableByForEach
296                 for (int i = 0; i < localParentsLength; i++) {
297                     final Marker localParent = localParents[i];
298                     if (checkParent(localParent, marker)) {
299                         return true;
300                     }
301                 }
302             }
303 
304             return false;
305         }
306 
307         @PerformanceSensitive({"allocation", "unrolled"})
308         private static boolean checkParent(final Marker parent, final Marker marker) {
309             if (parent == marker) {
310                 return true;
311             }
312             final Marker[] localParents = parent instanceof Log4jMarker ? ((Log4jMarker) parent).parents : parent
313                     .getParents();
314             if (localParents != null) {
315                 final int localParentsLength = localParents.length;
316                 if (localParentsLength == 1) {
317                     return checkParent(localParents[0], marker);
318                 }
319                 if (localParentsLength == 2) {
320                     return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
321                 }
322                 // noinspection ForLoopReplaceableByForEach
323                 for (int i = 0; i < localParentsLength; i++) {
324                     final Marker localParent = localParents[i];
325                     if (checkParent(localParent, marker)) {
326                         return true;
327                     }
328                 }
329             }
330             return false;
331         }
332 
333         /*
334          * Called from add while synchronized.
335          */
336         @PerformanceSensitive("allocation")
337         private static boolean contains(final Marker parent, final Marker... localParents) {
338             // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms
339             // noinspection ForLoopReplaceableByForEach
340             for (int i = 0, localParentsLength = localParents.length; i < localParentsLength; i++) {
341                 final Marker marker = localParents[i];
342                 if (marker == parent) {
343                     return true;
344                 }
345             }
346             return false;
347         }
348 
349         @Override
350         public boolean equals(final Object o) {
351             if (this == o) {
352                 return true;
353             }
354             if (o == null || !(o instanceof Marker)) {
355                 return false;
356             }
357             final Marker marker = (Marker) o;
358             return name.equals(marker.getName());
359         }
360 
361         @Override
362         public int hashCode() {
363             return name.hashCode();
364         }
365 
366         @Override
367         public String toString() {
368             // FIXME: might want to use an initial capacity; the default is 16 (or str.length() + 16)
369             final StringBuilder sb = new StringBuilder();
370             formatTo(sb);
371             return sb.toString();
372         }
373 
374         @Override
375         public void formatTo(final StringBuilder sb) {
376             sb.append(name);
377             final Marker[] localParents = parents;
378             if (localParents != null) {
379                 addParentInfo(sb, localParents);
380             }
381         }
382 
383         @PerformanceSensitive("allocation")
384         private static void addParentInfo(final StringBuilder sb, final Marker... parents) {
385             sb.append("[ ");
386             boolean first = true;
387             // noinspection ForLoopReplaceableByForEach
388             for (int i = 0, parentsLength = parents.length; i < parentsLength; i++) {
389                 final Marker marker = parents[i];
390                 if (!first) {
391                     sb.append(", ");
392                 }
393                 first = false;
394                 sb.append(marker.getName());
395                 final Marker[] p = marker instanceof Log4jMarker ? ((Log4jMarker) marker).parents : marker.getParents();
396                 if (p != null) {
397                     addParentInfo(sb, p);
398                 }
399             }
400             sb.append(" ]");
401         }
402     }
403 
404     // this method wouldn't be necessary if Marker methods threw an NPE instead of an IAE for null values ;)
405     private static void requireNonNull(final Object obj, final String message) {
406         if (obj == null) {
407             throw new IllegalArgumentException(message);
408         }
409     }
410 }