001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017 package org.apache.logging.log4j;
018
019 import java.util.Arrays;
020 import java.util.concurrent.ConcurrentHashMap;
021 import java.util.concurrent.ConcurrentMap;
022
023
024 /**
025 * Applications create Markers by using the Marker Manager. All Markers created by this Manager are
026 * immutable.
027 */
028 public final class MarkerManager {
029
030 private static final ConcurrentMap<String, Marker> MARKERS = new ConcurrentHashMap<String, Marker>();
031
032 private MarkerManager() {
033 // do nothing
034 }
035
036 /**
037 * Clears all markers.
038 */
039 public static void clear() {
040 MARKERS.clear();
041 }
042
043 /**
044 * Retrieve a Marker or create a Marker that has no parent.
045 * @param name The name of the Marker.
046 * @return The Marker with the specified name.
047 * @throws IllegalArgumentException if the argument is {@code null}
048 */
049 public static Marker getMarker(final String name) {
050 MARKERS.putIfAbsent(name, new Log4jMarker(name));
051 return MARKERS.get(name);
052 }
053
054 /**
055 * Retrieves or creates a Marker with the specified parent. The parent must have been previously created.
056 * @param name The name of the Marker.
057 * @param parent The name of the parent Marker.
058 * @return The Marker with the specified name.
059 * @throws IllegalArgumentException if the parent Marker does not exist.
060 * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
061 */
062 @Deprecated
063 public static Marker getMarker(final String name, final String parent) {
064 final Marker parentMarker = MARKERS.get(parent);
065 if (parentMarker == null) {
066 throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined");
067 }
068 @SuppressWarnings("deprecation")
069 final Marker marker = getMarker(name, parentMarker);
070 return marker;
071 }
072
073 /**
074 * Retrieves or creates a Marker with the specified parent.
075 * @param name The name of the Marker.
076 * @param parent The parent Marker.
077 * @return The Marker with the specified name.
078 * @throws IllegalArgumentException if any argument is {@code null}
079 * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
080 */
081 @Deprecated
082 public static Marker getMarker(final String name, final Marker parent) {
083 MARKERS.putIfAbsent(name, new Log4jMarker(name));
084 return MARKERS.get(name).addParents(parent);
085 }
086
087 /**
088 * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em>
089 * <p>
090 * The actual Marker implementation.
091 * </p>
092 * <p>
093 * <em>Internal note: We could make this class package private instead of public if the class
094 * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn}
095 * is moved to this package and would of course stay in its current module.</em>
096 * </p>
097 */
098 public static class Log4jMarker implements Marker {
099
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 }