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  
25  /**
26   * Applications create Markers by using the Marker Manager. All Markers created by this Manager are immutable.
27   */
28  public final class MarkerManager {
29  
30      private static final ConcurrentMap<String, Marker> MARKERS = new ConcurrentHashMap<>();
31  
32      private MarkerManager() {
33          // do nothing
34      }
35  
36      /**
37       * Clears all markers.
38       */
39      public static void clear() {
40          MARKERS.clear();
41      }
42  
43      /**
44       * Tests existence of the given marker.
45       *
46       * @param key the marker name
47       * @return true if the marker exists.
48       * @since 2.4
49       */
50      public static boolean exists(final String key) {
51          return MARKERS.containsKey(key);
52      }
53  
54      /**
55       * Retrieves a Marker or create a Marker that has no parent.
56       *
57       * @param name The name of the Marker.
58       * @return The Marker with the specified name.
59       * @throws IllegalArgumentException if the argument is {@code null}
60       */
61      public static Marker getMarker(final String name) {
62          Marker result = MARKERS.get(name);
63          if (result == null) {
64              MARKERS.putIfAbsent(name, new Log4jMarker(name));
65              result = MARKERS.get(name);
66          }
67          return result;
68      }
69  
70      /**
71       * Retrieves or creates a Marker with the specified parent. The parent must have been previously created.
72       *
73       * @param name The name of the Marker.
74       * @param parent The name of the parent Marker.
75       * @return The Marker with the specified name.
76       * @throws IllegalArgumentException if the parent Marker does not exist.
77       * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
78       */
79      @Deprecated
80      public static Marker getMarker(final String name, final String parent) {
81          final Marker parentMarker = MARKERS.get(parent);
82          if (parentMarker == null) {
83              throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined");
84          }
85          @SuppressWarnings("deprecation")
86          final Marker marker = getMarker(name, parentMarker);
87          return marker;
88      }
89  
90      /**
91       * Retrieves or creates a Marker with the specified parent.
92       *
93       * @param name The name of the Marker.
94       * @param parent The parent Marker.
95       * @return The Marker with the specified name.
96       * @throws IllegalArgumentException if any argument is {@code null}
97       * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
98       */
99      @Deprecated
100     public static Marker getMarker(final String name, final Marker parent) {
101         return getMarker(name).addParents(parent);
102     }
103 
104     /**
105      * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em>
106      * <p>
107      * The actual Marker implementation.
108      * </p>
109      * <p>
110      * <em>Internal note: We could make this class package private instead of public if the class
111      * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn}
112      * is moved to this package and would of course stay in its current module.</em>
113      * </p>
114      */
115     public static class Log4jMarker implements Marker {
116 
117         private static final long serialVersionUID = 100L;
118 
119         private final String name;
120 
121         private volatile Marker[] parents;
122 
123         /**
124          * Required by JAXB and Jackson for XML and JSON IO.
125          */
126         @SuppressWarnings("unused")
127         private Log4jMarker() {
128             this.name = null;
129             this.parents = null;
130         }
131 
132         /**
133          * Constructs a new Marker.
134          *
135          * @param name the name of the Marker.
136          * @throws IllegalArgumentException if the argument is {@code null}
137          */
138         public Log4jMarker(final String name) {
139             // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker
140             // name seems rather pointless. To get an "anonymous" Marker, just use an empty string.
141             requireNonNull(name, "Marker name cannot be null.");
142             this.name = name;
143             this.parents = null;
144         }
145 
146         // TODO: use java.util.concurrent
147 
148         @Override
149         public synchronized Marker addParents(final Marker... parentMarkers) {
150             requireNonNull(parentMarkers, "A parent marker must be specified");
151             // It is not strictly necessary to copy the variable here but it should perform better than
152             // Accessing a volatile variable multiple times.
153             final Marker[] localParents = this.parents;
154             // Don't add a parent that is already in the hierarchy.
155             int count = 0;
156             int size = parentMarkers.length;
157             if (localParents != null) {
158                 for (final Marker parent : parentMarkers) {
159                     if (!(contains(parent, localParents) || parent.isInstanceOf(this))) {
160                         ++count;
161                     }
162                 }
163                 if (count == 0) {
164                     return this;
165                 }
166                 size = localParents.length + count;
167             }
168             final Marker[] markers = new Marker[size];
169             if (localParents != null) {
170                 // It's perfectly OK to call arraycopy in a synchronized context; it's still faster
171                 // noinspection CallToNativeMethodWhileLocked
172                 System.arraycopy(localParents, 0, markers, 0, localParents.length);
173             }
174             int index = localParents == null ? 0 : localParents.length;
175             for (final Marker parent : parentMarkers) {
176                 if (localParents == null || !(contains(parent, localParents) || parent.isInstanceOf(this))) {
177                     markers[index++] = parent;
178                 }
179             }
180             this.parents = markers;
181             return this;
182         }
183 
184         @Override
185         public synchronized boolean remove(final Marker parent) {
186             requireNonNull(parent, "A parent marker must be specified");
187             final Marker[] localParents = this.parents;
188             if (localParents == null) {
189                 return false;
190             }
191             final int localParentsLength = localParents.length;
192             if (localParentsLength == 1) {
193                 if (localParents[0].equals(parent)) {
194                     parents = null;
195                     return true;
196                 }
197                 return false;
198             }
199             int index = 0;
200             final Marker[] markers = new Marker[localParentsLength - 1];
201             // noinspection ForLoopReplaceableByForEach
202             for (int i = 0; i < localParentsLength; i++) {
203                 final Marker marker = localParents[i];
204                 if (!marker.equals(parent)) {
205                     if (index == localParentsLength - 1) {
206                         // no need to swap array
207                         return false;
208                     }
209                     markers[index++] = marker;
210                 }
211             }
212             parents = markers;
213             return true;
214         }
215 
216         @Override
217         public Marker setParents(final Marker... markers) {
218             if (markers == null || markers.length == 0) {
219                 this.parents = null;
220             } else {
221                 final Marker[] array = new Marker[markers.length];
222                 System.arraycopy(markers, 0, array, 0, markers.length);
223                 this.parents = array;
224             }
225             return this;
226         }
227 
228         @Override
229         public String getName() {
230             return this.name;
231         }
232 
233         @Override
234         public Marker[] getParents() {
235             if (this.parents == null) {
236                 return null;
237             }
238             return Arrays.copyOf(this.parents, this.parents.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(name);
370             final Marker[] localParents = parents;
371             if (localParents != null) {
372                 addParentInfo(sb, localParents);
373             }
374             return sb.toString();
375         }
376 
377         @PerformanceSensitive("allocation")
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     // this method wouldn't be necessary if Marker methods threw an NPE instead of an IAE for null values ;)
399     private static void requireNonNull(final Object obj, final String message) {
400         if (obj == null) {
401             throw new IllegalArgumentException(message);
402         }
403     }
404 }