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  
24  /**
25   * Applications create Markers by using the Marker Manager. All Markers created by this Manager are
26   * 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       * @param key the marker name
46       * @return true if the marker exists.
47       * @since 2.4
48       */
49      public static boolean exists(final String key) {
50          return MARKERS.containsKey(key);
51      }
52  
53  
54      /**
55       * Retrieves a Marker or create a Marker that has no parent.
56       * @param name The name of the Marker.
57       * @return The Marker with the specified name.
58       * @throws IllegalArgumentException if the argument is {@code null}
59       */
60      public static Marker getMarker(final String name) {
61          MARKERS.putIfAbsent(name, new Log4jMarker(name));
62          return MARKERS.get(name);
63      }
64  
65      /**
66       * Retrieves or creates a Marker with the specified parent. The parent must have been previously created.
67       * @param name The name of the Marker.
68       * @param parent The name of the parent Marker.
69       * @return The Marker with the specified name.
70       * @throws IllegalArgumentException if the parent Marker does not exist.
71       * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
72       */
73      @Deprecated
74      public static Marker getMarker(final String name, final String parent) {
75          final Marker parentMarker = MARKERS.get(parent);
76          if (parentMarker == null) {
77              throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined");
78          }
79          @SuppressWarnings("deprecation")
80          final Marker marker = getMarker(name, parentMarker);
81          return marker;
82      }
83  
84      /**
85       * Retrieves or creates a Marker with the specified parent.
86       * @param name The name of the Marker.
87       * @param parent The parent Marker.
88       * @return The Marker with the specified name.
89       * @throws IllegalArgumentException if any argument is {@code null}
90       * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
91       */
92      @Deprecated
93      public static Marker getMarker(final String name, final Marker parent) {
94          MARKERS.putIfAbsent(name, new Log4jMarker(name));
95          return MARKERS.get(name).addParents(parent);
96      }
97  
98      /**
99       * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em>
100      * <p>
101      * The actual Marker implementation.
102      * </p>
103      * <p>
104      * <em>Internal note: We could make this class package private instead of public if the class
105      * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn}
106      * is moved to this package and would of course stay in its current module.</em>
107      * </p>
108      */
109     public static class Log4jMarker implements Marker {
110 
111         private static final long serialVersionUID = 100L;
112 
113         private final String name;
114 
115         private volatile Marker[] parents;
116 
117         /**
118          * Required by JAXB and Jackson for XML and JSON IO.
119          */
120         @SuppressWarnings("unused")
121         private Log4jMarker() {
122             this.name = null;
123             this.parents = null;
124         }
125 
126         /**
127          * Constructs a new Marker.
128          * @param name the name of the Marker.
129          * @throws IllegalArgumentException if the argument is {@code null}
130          */
131         public Log4jMarker(final String name) {
132             if (name == null) {
133                 // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker
134                 // name seems rather pointless. To get an "anonymous" Marker, just use an empty string.
135                 throw new IllegalArgumentException("Marker name cannot be null.");
136             }
137             this.name = name;
138             this.parents = null;
139         }
140 
141         // TODO: use java.util.concurrent
142 
143         @Override
144         public synchronized Marker addParents(final Marker... parents) {
145             if (parents == null) {
146                 throw new IllegalArgumentException("A parent marker must be specified");
147             }
148             // It is not strictly necessary to copy the variable here but it should perform better than
149             // Accessing a volatile variable multiple times.
150             final Marker[] localParents = this.parents;
151             // Don't add a parent that is already in the hierarchy.
152             int count = 0;
153             int size = parents.length;
154             if (localParents != null) {
155                 for (final Marker parent : parents) {
156                     if (!(contains(parent, localParents) || parent.isInstanceOf(this))) {
157                         ++count;
158                     }
159                 }
160                 if (count == 0) {
161                     return this;
162                 }
163                 size = localParents.length + count;
164             }
165             final Marker[] markers = new Marker[size];
166             if (localParents != null) {
167                 // It's perfectly OK to call arraycopy in a synchronized context; it's still faster
168                 //noinspection CallToNativeMethodWhileLocked
169                 System.arraycopy(localParents, 0, markers, 0, localParents.length);
170             }
171             int index = localParents == null ? 0 : localParents.length;
172             for (final Marker parent : parents) {
173                 if (localParents == null || !(contains(parent, localParents) || parent.isInstanceOf(this))) {
174                     markers[index++] = parent;
175                 }
176             }
177             this.parents = markers;
178             return this;
179         }
180 
181         @Override
182         public synchronized boolean remove(final Marker parent) {
183             if (parent == null) {
184                 throw new IllegalArgumentException("A parent marker must be specified");
185             }
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         public boolean isInstanceOf(final Marker marker) {
247             if (marker == null) {
248                 throw new IllegalArgumentException("A marker parameter is required");
249             }
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         public boolean isInstanceOf(final String markerName) {
276             if (markerName == null) {
277                 throw new IllegalArgumentException("A marker name is required");
278             }
279             if (markerName.equals(this.getName())) {
280                 return true;
281             }
282             // Use a real marker for child comparisons. It is faster than comparing the names.
283             final Marker marker = MARKERS.get(markerName);
284             if (marker == null) {
285                 return false;
286             }
287             final Marker[] localParents = parents;
288             if (localParents != null) {
289                 final int localParentsLength = localParents.length;
290                 if (localParentsLength == 1) {
291                     return checkParent(localParents[0], marker);
292                 }
293                 if (localParentsLength == 2) {
294                     return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
295                 }
296                 //noinspection ForLoopReplaceableByForEach
297                 for (int i = 0; i < localParentsLength; i++) {
298                     final Marker localParent = localParents[i];
299                     if (checkParent(localParent, marker)) {
300                         return true;
301                     }
302                 }
303             }
304 
305             return false;
306         }
307 
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.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         private static boolean contains(final Marker parent, final Marker... localParents) {
336             //noinspection ForLoopReplaceableByForEach
337             // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms
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         private static void addParentInfo(final StringBuilder sb, final Marker... parents) {
376             sb.append("[ ");
377             boolean first = true;
378             //noinspection ForLoopReplaceableByForEach
379             for (int i = 0, parentsLength = parents.length; i < parentsLength; i++) {
380                 final Marker marker = parents[i];
381                 if (!first) {
382                     sb.append(", ");
383                 }
384                 first = false;
385                 sb.append(marker.getName());
386                 final Marker[] p = marker instanceof Log4jMarker ? ((Log4jMarker) marker).parents : marker.getParents();
387                 if (p != null) {
388                     addParentInfo(sb, p);
389                 }
390             }
391             sb.append(" ]");
392         }
393     }
394 
395 }