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   * Applications create Markers by using the Marker Manager. All Markers created by this Manager are immutable.
25   */
26  public final class MarkerManager {
27  
28      private static final ConcurrentMap<String, Marker> MARKERS = new ConcurrentHashMap<>();
29  
30      private MarkerManager() {
31          // do nothing
32      }
33  
34      /**
35       * Clears all markers.
36       */
37      public static void clear() {
38          MARKERS.clear();
39      }
40  
41      /**
42       * Tests existence of the given marker.
43       * 
44       * @param key the marker name
45       * @return true if the marker exists.
46       * @since 2.4
47       */
48      public static boolean exists(final String key) {
49          return MARKERS.containsKey(key);
50      }
51  
52      /**
53       * Retrieves a Marker or create a Marker that has no parent.
54       * 
55       * @param name The name of the Marker.
56       * @return The Marker with the specified name.
57       * @throws IllegalArgumentException if the argument is {@code null}
58       */
59      public static Marker getMarker(final String name) {
60          MARKERS.putIfAbsent(name, new Log4jMarker(name));
61          return MARKERS.get(name);
62      }
63  
64      /**
65       * Retrieves or creates a Marker with the specified parent. The parent must have been previously created.
66       * 
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       * 
87       * @param name The name of the Marker.
88       * @param parent The parent Marker.
89       * @return The Marker with the specified name.
90       * @throws IllegalArgumentException if any argument is {@code null}
91       * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
92       */
93      @Deprecated
94      public static Marker getMarker(final String name, final Marker parent) {
95          MARKERS.putIfAbsent(name, new Log4jMarker(name));
96          return MARKERS.get(name).addParents(parent);
97      }
98  
99      /**
100      * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em>
101      * <p>
102      * The actual Marker implementation.
103      * </p>
104      * <p>
105      * <em>Internal note: We could make this class package private instead of public if the class
106      * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn}
107      * is moved to this package and would of course stay in its current module.</em>
108      * </p>
109      */
110     public static class Log4jMarker implements Marker {
111 
112         private static final long serialVersionUID = 100L;
113 
114         private final String name;
115 
116         private volatile Marker[] parents;
117 
118         /**
119          * Required by JAXB and Jackson for XML and JSON IO.
120          */
121         @SuppressWarnings("unused")
122         private Log4jMarker() {
123             this.name = null;
124             this.parents = null;
125         }
126 
127         /**
128          * Constructs a new Marker.
129          * 
130          * @param name the name of the Marker.
131          * @throws IllegalArgumentException if the argument is {@code null}
132          */
133         public Log4jMarker(final String name) {
134             if (name == null) {
135                 // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker
136                 // name seems rather pointless. To get an "anonymous" Marker, just use an empty string.
137                 throw new IllegalArgumentException("Marker name cannot be null.");
138             }
139             this.name = name;
140             this.parents = null;
141         }
142 
143         // TODO: use java.util.concurrent
144 
145         @Override
146         public synchronized Marker addParents(final Marker... parentMarkers) {
147             if (parentMarkers == null) {
148                 throw new IllegalArgumentException("A parent marker must be specified");
149             }
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             if (parent == null) {
186                 throw new IllegalArgumentException("A parent marker must be specified");
187             }
188             final Marker[] localParents = this.parents;
189             if (localParents == null) {
190                 return false;
191             }
192             final int localParentsLength = localParents.length;
193             if (localParentsLength == 1) {
194                 if (localParents[0].equals(parent)) {
195                     parents = null;
196                     return true;
197                 }
198                 return false;
199             }
200             int index = 0;
201             final Marker[] markers = new Marker[localParentsLength - 1];
202             // noinspection ForLoopReplaceableByForEach
203             for (int i = 0; i < localParentsLength; i++) {
204                 final Marker marker = localParents[i];
205                 if (!marker.equals(parent)) {
206                     if (index == localParentsLength - 1) {
207                         // no need to swap array
208                         return false;
209                     }
210                     markers[index++] = marker;
211                 }
212             }
213             parents = markers;
214             return true;
215         }
216 
217         @Override
218         public Marker setParents(final Marker... markers) {
219             if (markers == null || markers.length == 0) {
220                 this.parents = null;
221             } else {
222                 final Marker[] array = new Marker[markers.length];
223                 System.arraycopy(markers, 0, array, 0, markers.length);
224                 this.parents = array;
225             }
226             return this;
227         }
228 
229         @Override
230         public String getName() {
231             return this.name;
232         }
233 
234         @Override
235         public Marker[] getParents() {
236             if (this.parents == null) {
237                 return null;
238             }
239             return Arrays.copyOf(this.parents, this.parents.length);
240         }
241 
242         @Override
243         public boolean hasParents() {
244             return this.parents != null;
245         }
246 
247         @Override
248         public boolean isInstanceOf(final Marker marker) {
249             if (marker == null) {
250                 throw new IllegalArgumentException("A marker parameter is required");
251             }
252             if (this == marker) {
253                 return true;
254             }
255             final Marker[] localParents = parents;
256             if (localParents != null) {
257                 // With only one or two parents the for loop is slower.
258                 final int localParentsLength = localParents.length;
259                 if (localParentsLength == 1) {
260                     return checkParent(localParents[0], marker);
261                 }
262                 if (localParentsLength == 2) {
263                     return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
264                 }
265                 // noinspection ForLoopReplaceableByForEach
266                 for (int i = 0; i < localParentsLength; i++) {
267                     final Marker localParent = localParents[i];
268                     if (checkParent(localParent, marker)) {
269                         return true;
270                     }
271                 }
272             }
273             return false;
274         }
275 
276         @Override
277         public boolean isInstanceOf(final String markerName) {
278             if (markerName == null) {
279                 throw new IllegalArgumentException("A marker name is required");
280             }
281             if (markerName.equals(this.getName())) {
282                 return true;
283             }
284             // Use a real marker for child comparisons. It is faster than comparing the names.
285             final Marker marker = MARKERS.get(markerName);
286             if (marker == null) {
287                 return false;
288             }
289             final Marker[] localParents = parents;
290             if (localParents != null) {
291                 final int localParentsLength = localParents.length;
292                 if (localParentsLength == 1) {
293                     return checkParent(localParents[0], marker);
294                 }
295                 if (localParentsLength == 2) {
296                     return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
297                 }
298                 // noinspection ForLoopReplaceableByForEach
299                 for (int i = 0; i < localParentsLength; i++) {
300                     final Marker localParent = localParents[i];
301                     if (checkParent(localParent, marker)) {
302                         return true;
303                     }
304                 }
305             }
306 
307             return false;
308         }
309 
310         private static boolean checkParent(final Marker parent, final Marker marker) {
311             if (parent == marker) {
312                 return true;
313             }
314             final Marker[] localParents = parent instanceof Log4jMarker ? ((Log4jMarker) parent).parents : parent
315                     .getParents();
316             if (localParents != null) {
317                 final int localParentsLength = localParents.length;
318                 if (localParentsLength == 1) {
319                     return checkParent(localParents[0], marker);
320                 }
321                 if (localParentsLength == 2) {
322                     return checkParent(localParents[0], marker) || checkParent(localParents[1], marker);
323                 }
324                 // noinspection ForLoopReplaceableByForEach
325                 for (int i = 0; i < localParentsLength; i++) {
326                     final Marker localParent = localParents[i];
327                     if (checkParent(localParent, marker)) {
328                         return true;
329                     }
330                 }
331             }
332             return false;
333         }
334 
335         /*
336          * Called from add while synchronized.
337          */
338         private static boolean contains(final Marker parent, final Marker... localParents) {
339             // noinspection ForLoopReplaceableByForEach
340             // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms
341             for (int i = 0, localParentsLength = localParents.length; i < localParentsLength; i++) {
342                 final Marker marker = localParents[i];
343                 if (marker == parent) {
344                     return true;
345                 }
346             }
347             return false;
348         }
349 
350         @Override
351         public boolean equals(final Object o) {
352             if (this == o) {
353                 return true;
354             }
355             if (o == null || !(o instanceof Marker)) {
356                 return false;
357             }
358             final Marker marker = (Marker) o;
359             return name.equals(marker.getName());
360         }
361 
362         @Override
363         public int hashCode() {
364             return name.hashCode();
365         }
366 
367         @Override
368         public String toString() {
369             // FIXME: might want to use an initial capacity; the default is 16 (or str.length() + 16)
370             final StringBuilder sb = new StringBuilder(name);
371             final Marker[] localParents = parents;
372             if (localParents != null) {
373                 addParentInfo(sb, localParents);
374             }
375             return sb.toString();
376         }
377 
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 }