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