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             if (this.parents == null) {
235                 return null;
236             }
237             return Arrays.copyOf(this.parents, this.parents.length);
238         }
239 
240         @Override
241         public boolean hasParents() {
242             return this.parents != null;
243         }
244 
245         @Override
246         @PerformanceSensitive({"allocation", "unrolled"})
247         public boolean isInstanceOf(final Marker marker) {
248             requireNonNull(marker, "A marker parameter is required");
249             if (this == marker) {
250                 return true;
251             }
252             final Marker[] localParents = parents;
253             if (localParents != null) {
254                 // With only one or two parents the for loop is slower.
255                 final int localParentsLength = localParents.length;
256                 if (localParentsLength == 1) {
257                     return checkParent(localParents[0], marker);
258                 }
259                 if (localParentsLength == 2) {
260                     return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
261                 }
262                 // noinspection ForLoopReplaceableByForEach
263                 for (int i = 0; i < localParentsLength; i++) {
264                     final Marker localParent = localParents[i];
265                     if (checkParent(localParent, marker)) {
266                         return true;
267                     }
268                 }
269             }
270             return false;
271         }
272 
273         @Override
274         @PerformanceSensitive({"allocation", "unrolled"})
275         public boolean isInstanceOf(final String markerName) {
276             requireNonNull(markerName, "A marker name is required");
277             if (markerName.equals(this.getName())) {
278                 return true;
279             }
280             // Use a real marker for child comparisons. It is faster than comparing the names.
281             final Marker marker = MARKERS.get(markerName);
282             if (marker == null) {
283                 return false;
284             }
285             final Marker[] localParents = parents;
286             if (localParents != null) {
287                 final int localParentsLength = localParents.length;
288                 if (localParentsLength == 1) {
289                     return checkParent(localParents[0], marker);
290                 }
291                 if (localParentsLength == 2) {
292                     return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
293                 }
294                 // noinspection ForLoopReplaceableByForEach
295                 for (int i = 0; i < localParentsLength; i++) {
296                     final Marker localParent = localParents[i];
297                     if (checkParent(localParent, marker)) {
298                         return true;
299                     }
300                 }
301             }
302 
303             return false;
304         }
305 
306         @PerformanceSensitive({"allocation", "unrolled"})
307         private static boolean checkParent(final Marker parent, final Marker marker) {
308             if (parent == marker) {
309                 return true;
310             }
311             final Marker[] localParents = parent instanceof Log4jMarker ? ((Log4jMarker) parent).parents : parent
312                     .getParents();
313             if (localParents != null) {
314                 final int localParentsLength = localParents.length;
315                 if (localParentsLength == 1) {
316                     return checkParent(localParents[0], marker);
317                 }
318                 if (localParentsLength == 2) {
319                     return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
320                 }
321                 // noinspection ForLoopReplaceableByForEach
322                 for (int i = 0; i < localParentsLength; i++) {
323                     final Marker localParent = localParents[i];
324                     if (checkParent(localParent, marker)) {
325                         return true;
326                     }
327                 }
328             }
329             return false;
330         }
331 
332         /*
333          * Called from add while synchronized.
334          */
335         @PerformanceSensitive("allocation")
336         private static boolean contains(final Marker parent, final Marker... localParents) {
337             // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms
338             // noinspection ForLoopReplaceableByForEach
339             for (int i = 0, localParentsLength = localParents.length; i < localParentsLength; i++) {
340                 final Marker marker = localParents[i];
341                 if (marker == parent) {
342                     return true;
343                 }
344             }
345             return false;
346         }
347 
348         @Override
349         public boolean equals(final Object o) {
350             if (this == o) {
351                 return true;
352             }
353             if (o == null || !(o instanceof Marker)) {
354                 return false;
355             }
356             final Marker marker = (Marker) o;
357             return name.equals(marker.getName());
358         }
359 
360         @Override
361         public int hashCode() {
362             return name.hashCode();
363         }
364 
365         @Override
366         public String toString() {
367             // FIXME: might want to use an initial capacity; the default is 16 (or str.length() + 16)
368             final StringBuilder sb = new StringBuilder();
369             formatTo(sb);
370             return sb.toString();
371         }
372 
373         @Override
374         public void formatTo(final StringBuilder sb) {
375             sb.append(name);
376             final Marker[] localParents = parents;
377             if (localParents != null) {
378                 addParentInfo(sb, localParents);
379             }
380         }
381 
382         @PerformanceSensitive("allocation")
383         private static void addParentInfo(final StringBuilder sb, final Marker... parents) {
384             sb.append("[ ");
385             boolean first = true;
386             // noinspection ForLoopReplaceableByForEach
387             for (int i = 0, parentsLength = parents.length; i < parentsLength; i++) {
388                 final Marker marker = parents[i];
389                 if (!first) {
390                     sb.append(", ");
391                 }
392                 first = false;
393                 sb.append(marker.getName());
394                 final Marker[] p = marker instanceof Log4jMarker ? ((Log4jMarker) marker).parents : marker.getParents();
395                 if (p != null) {
396                     addParentInfo(sb, p);
397                 }
398             }
399             sb.append(" ]");
400         }
401     }
402 
403     // this method wouldn't be necessary if Marker methods threw an NPE instead of an IAE for null values ;)
404     private static void requireNonNull(final Object obj, final String message) {
405         if (obj == null) {
406             throw new IllegalArgumentException(message);
407         }
408     }
409 }