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          return getMarker(name, parentMarker);
86      }
87  
88      /**
89       * Retrieves or creates a Marker with the specified parent.
90       *
91       * @param name The name of the Marker.
92       * @param parent The parent Marker.
93       * @return The Marker with the specified name.
94       * @throws IllegalArgumentException if any argument is {@code null}
95       * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
96       */
97      @Deprecated
98      public static Marker getMarker(final String name, final Marker parent) {
99          return getMarker(name).addParents(parent);
100     }
101 
102     /**
103      * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em>
104      * <p>
105      * The actual Marker implementation.
106      * </p>
107      * <p>
108      * <em>Internal note: We could make this class package private instead of public if the class
109      * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn}
110      * is moved to this package and would of course stay in its current module.</em>
111      * </p>
112      */
113     public static class Log4jMarker implements Marker {
114 
115         private static final long serialVersionUID = 100L;
116 
117         private final String name;
118 
119         private volatile Marker[] parents;
120 
121         /**
122          * Required by JAXB and Jackson for XML and JSON IO.
123          */
124         @SuppressWarnings("unused")
125         private Log4jMarker() {
126             this.name = null;
127             this.parents = null;
128         }
129 
130         /**
131          * Constructs a new Marker.
132          *
133          * @param name the name of the Marker.
134          * @throws IllegalArgumentException if the argument is {@code null}
135          */
136         public Log4jMarker(final String name) {
137             // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker
138             // name seems rather pointless. To get an "anonymous" Marker, just use an empty string.
139             requireNonNull(name, "Marker name cannot be null.");
140             this.name = name;
141             this.parents = null;
142         }
143 
144         // TODO: use java.util.concurrent
145 
146         @Override
147         public synchronized Marker addParents(final Marker... parentMarkers) {
148             requireNonNull(parentMarkers, "A parent marker must be specified");
149             // It is not strictly necessary to copy the variable here but it should perform better than
150             // Accessing a volatile variable multiple times.
151             final Marker[] localParents = this.parents;
152             // Don't add a parent that is already in the hierarchy.
153             int count = 0;
154             int size = parentMarkers.length;
155             if (localParents != null) {
156                 for (final Marker parent : parentMarkers) {
157                     if (!(contains(parent, localParents) || parent.isInstanceOf(this))) {
158                         ++count;
159                     }
160                 }
161                 if (count == 0) {
162                     return this;
163                 }
164                 size = localParents.length + count;
165             }
166             final Marker[] markers = new Marker[size];
167             if (localParents != null) {
168                 // It's perfectly OK to call arraycopy in a synchronized context; it's still faster
169                 // noinspection CallToNativeMethodWhileLocked
170                 System.arraycopy(localParents, 0, markers, 0, localParents.length);
171             }
172             int index = localParents == null ? 0 : localParents.length;
173             for (final Marker parent : parentMarkers) {
174                 if (localParents == null || !(contains(parent, localParents) || parent.isInstanceOf(this))) {
175                     markers[index++] = parent;
176                 }
177             }
178             this.parents = markers;
179             return this;
180         }
181 
182         @Override
183         public synchronized boolean remove(final Marker parent) {
184             requireNonNull(parent, "A parent marker must be specified");
185             final Marker[] localParents = this.parents;
186             if (localParents == null) {
187                 return false;
188             }
189             final int localParentsLength = localParents.length;
190             if (localParentsLength == 1) {
191                 if (localParents[0].equals(parent)) {
192                     parents = null;
193                     return true;
194                 }
195                 return false;
196             }
197             int index = 0;
198             final Marker[] markers = new Marker[localParentsLength - 1];
199             // noinspection ForLoopReplaceableByForEach
200             for (int i = 0; i < localParentsLength; i++) {
201                 final Marker marker = localParents[i];
202                 if (!marker.equals(parent)) {
203                     if (index == localParentsLength - 1) {
204                         // no need to swap array
205                         return false;
206                     }
207                     markers[index++] = marker;
208                 }
209             }
210             parents = markers;
211             return true;
212         }
213 
214         @Override
215         public Marker setParents(final Marker... markers) {
216             if (markers == null || markers.length == 0) {
217                 this.parents = null;
218             } else {
219                 final Marker[] array = new Marker[markers.length];
220                 System.arraycopy(markers, 0, array, 0, markers.length);
221                 this.parents = array;
222             }
223             return this;
224         }
225 
226         @Override
227         public String getName() {
228             return this.name;
229         }
230 
231         @Override
232         public Marker[] getParents() {
233             if (this.parents == null) {
234                 return null;
235             }
236             return Arrays.copyOf(this.parents, this.parents.length);
237         }
238 
239         @Override
240         public boolean hasParents() {
241             return this.parents != null;
242         }
243 
244         @Override
245         @PerformanceSensitive({"allocation", "unrolled"})
246         public boolean isInstanceOf(final Marker marker) {
247             requireNonNull(marker, "A marker parameter is required");
248             if (this == marker) {
249                 return true;
250             }
251             final Marker[] localParents = parents;
252             if (localParents != null) {
253                 // With only one or two parents the for loop is slower.
254                 final int localParentsLength = localParents.length;
255                 if (localParentsLength == 1) {
256                     return checkParent(localParents[0], marker);
257                 }
258                 if (localParentsLength == 2) {
259                     return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
260                 }
261                 // noinspection ForLoopReplaceableByForEach
262                 for (int i = 0; i < localParentsLength; i++) {
263                     final Marker localParent = localParents[i];
264                     if (checkParent(localParent, marker)) {
265                         return true;
266                     }
267                 }
268             }
269             return false;
270         }
271 
272         @Override
273         @PerformanceSensitive({"allocation", "unrolled"})
274         public boolean isInstanceOf(final String markerName) {
275             requireNonNull(markerName, "A marker name is required");
276             if (markerName.equals(this.getName())) {
277                 return true;
278             }
279             // Use a real marker for child comparisons. It is faster than comparing the names.
280             final Marker marker = MARKERS.get(markerName);
281             if (marker == null) {
282                 return false;
283             }
284             final Marker[] localParents = parents;
285             if (localParents != null) {
286                 final int localParentsLength = localParents.length;
287                 if (localParentsLength == 1) {
288                     return checkParent(localParents[0], marker);
289                 }
290                 if (localParentsLength == 2) {
291                     return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
292                 }
293                 // noinspection ForLoopReplaceableByForEach
294                 for (int i = 0; i < localParentsLength; i++) {
295                     final Marker localParent = localParents[i];
296                     if (checkParent(localParent, marker)) {
297                         return true;
298                     }
299                 }
300             }
301 
302             return false;
303         }
304 
305         @PerformanceSensitive({"allocation", "unrolled"})
306         private static boolean checkParent(final Marker parent, final Marker marker) {
307             if (parent == marker) {
308                 return true;
309             }
310             final Marker[] localParents = parent instanceof Log4jMarker ? ((Log4jMarker) parent).parents : parent
311                     .getParents();
312             if (localParents != null) {
313                 final int localParentsLength = localParents.length;
314                 if (localParentsLength == 1) {
315                     return checkParent(localParents[0], marker);
316                 }
317                 if (localParentsLength == 2) {
318                     return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
319                 }
320                 // noinspection ForLoopReplaceableByForEach
321                 for (int i = 0; i < localParentsLength; i++) {
322                     final Marker localParent = localParents[i];
323                     if (checkParent(localParent, marker)) {
324                         return true;
325                     }
326                 }
327             }
328             return false;
329         }
330 
331         /*
332          * Called from add while synchronized.
333          */
334         @PerformanceSensitive("allocation")
335         private static boolean contains(final Marker parent, final Marker... localParents) {
336             // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms
337             // noinspection ForLoopReplaceableByForEach
338             for (int i = 0, localParentsLength = localParents.length; i < localParentsLength; i++) {
339                 final Marker marker = localParents[i];
340                 if (marker == parent) {
341                     return true;
342                 }
343             }
344             return false;
345         }
346 
347         @Override
348         public boolean equals(final Object o) {
349             if (this == o) {
350                 return true;
351             }
352             if (o == null || !(o instanceof Marker)) {
353                 return false;
354             }
355             final Marker marker = (Marker) o;
356             return name.equals(marker.getName());
357         }
358 
359         @Override
360         public int hashCode() {
361             return name.hashCode();
362         }
363 
364         @Override
365         public String toString() {
366             // FIXME: might want to use an initial capacity; the default is 16 (or str.length() + 16)
367             final StringBuilder sb = new StringBuilder(name);
368             final Marker[] localParents = parents;
369             if (localParents != null) {
370                 addParentInfo(sb, localParents);
371             }
372             return sb.toString();
373         }
374 
375         @PerformanceSensitive("allocation")
376         private static void addParentInfo(final StringBuilder sb, final Marker... parents) {
377             sb.append("[ ");
378             boolean first = true;
379             // noinspection ForLoopReplaceableByForEach
380             for (int i = 0, parentsLength = parents.length; i < parentsLength; i++) {
381                 final Marker marker = parents[i];
382                 if (!first) {
383                     sb.append(", ");
384                 }
385                 first = false;
386                 sb.append(marker.getName());
387                 final Marker[] p = marker instanceof Log4jMarker ? ((Log4jMarker) marker).parents : marker.getParents();
388                 if (p != null) {
389                     addParentInfo(sb, p);
390                 }
391             }
392             sb.append(" ]");
393         }
394     }
395 
396     // this method wouldn't be necessary if Marker methods threw an NPE instead of an IAE for null values ;)
397     private static void requireNonNull(final Object obj, final String message) {
398         if (obj == null) {
399             throw new IllegalArgumentException(message);
400         }
401     }
402 }