| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache license, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the license for the specific language governing permissions and |
| * limitations under the license. |
| */ |
| package org.apache.logging.log4j; |
| |
| import java.util.Arrays; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| |
| import org.apache.logging.log4j.util.PerformanceSensitive; |
| import org.apache.logging.log4j.util.StringBuilderFormattable; |
| |
| /** |
| * Applications create Markers by using the Marker Manager. All Markers created by this Manager are immutable. |
| */ |
| public final class MarkerManager { |
| |
| private static final ConcurrentMap<String, Marker> MARKERS = new ConcurrentHashMap<>(); |
| |
| private MarkerManager() { |
| // do nothing |
| } |
| |
| /** |
| * Clears all markers. |
| */ |
| public static void clear() { |
| MARKERS.clear(); |
| } |
| |
| /** |
| * Tests existence of the given marker. |
| * |
| * @param key the marker name |
| * @return true if the marker exists. |
| * @since 2.4 |
| */ |
| public static boolean exists(final String key) { |
| return MARKERS.containsKey(key); |
| } |
| |
| /** |
| * Retrieves a Marker or create a Marker that has no parent. |
| * |
| * @param name The name of the Marker. |
| * @return The Marker with the specified name. |
| * @throws IllegalArgumentException if the argument is {@code null} |
| */ |
| public static Marker getMarker(final String name) { |
| Marker result = MARKERS.get(name); |
| if (result == null) { |
| MARKERS.putIfAbsent(name, new Log4jMarker(name)); |
| result = MARKERS.get(name); |
| } |
| return result; |
| } |
| |
| /** |
| * Retrieves or creates a Marker with the specified parent. The parent must have been previously created. |
| * |
| * @param name The name of the Marker. |
| * @param parent The name of the parent Marker. |
| * @return The Marker with the specified name. |
| * @throws IllegalArgumentException if the parent Marker does not exist. |
| * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release. |
| */ |
| @Deprecated |
| public static Marker getMarker(final String name, final String parent) { |
| final Marker parentMarker = MARKERS.get(parent); |
| if (parentMarker == null) { |
| throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined"); |
| } |
| return getMarker(name, parentMarker); |
| } |
| |
| /** |
| * Retrieves or creates a Marker with the specified parent. |
| * |
| * @param name The name of the Marker. |
| * @param parent The parent Marker. |
| * @return The Marker with the specified name. |
| * @throws IllegalArgumentException if any argument is {@code null} |
| * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release. |
| */ |
| @Deprecated |
| public static Marker getMarker(final String name, final Marker parent) { |
| return getMarker(name).addParents(parent); |
| } |
| |
| /** |
| * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em> |
| * <p> |
| * The actual Marker implementation. |
| * </p> |
| * <p> |
| * <em>Internal note: We could make this class package private instead of public if the class |
| * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn} |
| * is moved to this package and would of course stay in its current module.</em> |
| * </p> |
| */ |
| public static class Log4jMarker implements Marker, StringBuilderFormattable { |
| |
| private static final long serialVersionUID = 100L; |
| |
| private final String name; |
| |
| private volatile Marker[] parents; |
| |
| /** |
| * Required by JAXB and Jackson for XML and JSON IO. |
| */ |
| @SuppressWarnings("unused") |
| private Log4jMarker() { |
| this.name = null; |
| this.parents = null; |
| } |
| |
| /** |
| * Constructs a new Marker. |
| * |
| * @param name the name of the Marker. |
| * @throws IllegalArgumentException if the argument is {@code null} |
| */ |
| public Log4jMarker(final String name) { |
| // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker |
| // name seems rather pointless. To get an "anonymous" Marker, just use an empty string. |
| requireNonNull(name, "Marker name cannot be null."); |
| this.name = name; |
| this.parents = null; |
| } |
| |
| // TODO: use java.util.concurrent |
| |
| @Override |
| public synchronized Marker addParents(final Marker... parentMarkers) { |
| requireNonNull(parentMarkers, "A parent marker must be specified"); |
| // It is not strictly necessary to copy the variable here but it should perform better than |
| // Accessing a volatile variable multiple times. |
| final Marker[] localParents = this.parents; |
| // Don't add a parent that is already in the hierarchy. |
| int count = 0; |
| int size = parentMarkers.length; |
| if (localParents != null) { |
| for (final Marker parent : parentMarkers) { |
| if (!(contains(parent, localParents) || parent.isInstanceOf(this))) { |
| ++count; |
| } |
| } |
| if (count == 0) { |
| return this; |
| } |
| size = localParents.length + count; |
| } |
| final Marker[] markers = new Marker[size]; |
| if (localParents != null) { |
| // It's perfectly OK to call arraycopy in a synchronized context; it's still faster |
| // noinspection CallToNativeMethodWhileLocked |
| System.arraycopy(localParents, 0, markers, 0, localParents.length); |
| } |
| int index = localParents == null ? 0 : localParents.length; |
| for (final Marker parent : parentMarkers) { |
| if (localParents == null || !(contains(parent, localParents) || parent.isInstanceOf(this))) { |
| markers[index++] = parent; |
| } |
| } |
| this.parents = markers; |
| return this; |
| } |
| |
| @Override |
| public synchronized boolean remove(final Marker parent) { |
| requireNonNull(parent, "A parent marker must be specified"); |
| final Marker[] localParents = this.parents; |
| if (localParents == null) { |
| return false; |
| } |
| final int localParentsLength = localParents.length; |
| if (localParentsLength == 1) { |
| if (localParents[0].equals(parent)) { |
| parents = null; |
| return true; |
| } |
| return false; |
| } |
| int index = 0; |
| final Marker[] markers = new Marker[localParentsLength - 1]; |
| // noinspection ForLoopReplaceableByForEach |
| for (int i = 0; i < localParentsLength; i++) { |
| final Marker marker = localParents[i]; |
| if (!marker.equals(parent)) { |
| if (index == localParentsLength - 1) { |
| // no need to swap array |
| return false; |
| } |
| markers[index++] = marker; |
| } |
| } |
| parents = markers; |
| return true; |
| } |
| |
| @Override |
| public Marker setParents(final Marker... markers) { |
| if (markers == null || markers.length == 0) { |
| this.parents = null; |
| } else { |
| final Marker[] array = new Marker[markers.length]; |
| System.arraycopy(markers, 0, array, 0, markers.length); |
| this.parents = array; |
| } |
| return this; |
| } |
| |
| @Override |
| public String getName() { |
| return this.name; |
| } |
| |
| @Override |
| public Marker[] getParents() { |
| if (this.parents == null) { |
| return null; |
| } |
| return Arrays.copyOf(this.parents, this.parents.length); |
| } |
| |
| @Override |
| public boolean hasParents() { |
| return this.parents != null; |
| } |
| |
| @Override |
| @PerformanceSensitive({"allocation", "unrolled"}) |
| public boolean isInstanceOf(final Marker marker) { |
| requireNonNull(marker, "A marker parameter is required"); |
| if (this == marker) { |
| return true; |
| } |
| final Marker[] localParents = parents; |
| if (localParents != null) { |
| // With only one or two parents the for loop is slower. |
| final int localParentsLength = localParents.length; |
| if (localParentsLength == 1) { |
| return checkParent(localParents[0], marker); |
| } |
| if (localParentsLength == 2) { |
| return checkParent(localParents[0], marker) || checkParent(localParents[1], marker); |
| } |
| // noinspection ForLoopReplaceableByForEach |
| for (int i = 0; i < localParentsLength; i++) { |
| final Marker localParent = localParents[i]; |
| if (checkParent(localParent, marker)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| @PerformanceSensitive({"allocation", "unrolled"}) |
| public boolean isInstanceOf(final String markerName) { |
| requireNonNull(markerName, "A marker name is required"); |
| if (markerName.equals(this.getName())) { |
| return true; |
| } |
| // Use a real marker for child comparisons. It is faster than comparing the names. |
| final Marker marker = MARKERS.get(markerName); |
| if (marker == null) { |
| return false; |
| } |
| final Marker[] localParents = parents; |
| if (localParents != null) { |
| final int localParentsLength = localParents.length; |
| if (localParentsLength == 1) { |
| return checkParent(localParents[0], marker); |
| } |
| if (localParentsLength == 2) { |
| return checkParent(localParents[0], marker) || checkParent(localParents[1], marker); |
| } |
| // noinspection ForLoopReplaceableByForEach |
| for (int i = 0; i < localParentsLength; i++) { |
| final Marker localParent = localParents[i]; |
| if (checkParent(localParent, marker)) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| @PerformanceSensitive({"allocation", "unrolled"}) |
| private static boolean checkParent(final Marker parent, final Marker marker) { |
| if (parent == marker) { |
| return true; |
| } |
| final Marker[] localParents = parent instanceof Log4jMarker ? ((Log4jMarker) parent).parents : parent |
| .getParents(); |
| if (localParents != null) { |
| final int localParentsLength = localParents.length; |
| if (localParentsLength == 1) { |
| return checkParent(localParents[0], marker); |
| } |
| if (localParentsLength == 2) { |
| return checkParent(localParents[0], marker) || checkParent(localParents[1], marker); |
| } |
| // noinspection ForLoopReplaceableByForEach |
| for (int i = 0; i < localParentsLength; i++) { |
| final Marker localParent = localParents[i]; |
| if (checkParent(localParent, marker)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Called from add while synchronized. |
| */ |
| @PerformanceSensitive("allocation") |
| private static boolean contains(final Marker parent, final Marker... localParents) { |
| // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms |
| // noinspection ForLoopReplaceableByForEach |
| for (int i = 0, localParentsLength = localParents.length; i < localParentsLength; i++) { |
| final Marker marker = localParents[i]; |
| if (marker == parent) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean equals(final Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (o == null || !(o instanceof Marker)) { |
| return false; |
| } |
| final Marker marker = (Marker) o; |
| return name.equals(marker.getName()); |
| } |
| |
| @Override |
| public int hashCode() { |
| return name.hashCode(); |
| } |
| |
| @Override |
| public String toString() { |
| // FIXME: might want to use an initial capacity; the default is 16 (or str.length() + 16) |
| final StringBuilder sb = new StringBuilder(); |
| formatTo(sb); |
| return sb.toString(); |
| } |
| |
| @Override |
| public void formatTo(final StringBuilder sb) { |
| sb.append(name); |
| final Marker[] localParents = parents; |
| if (localParents != null) { |
| addParentInfo(sb, localParents); |
| } |
| } |
| |
| @PerformanceSensitive("allocation") |
| private static void addParentInfo(final StringBuilder sb, final Marker... parents) { |
| sb.append("[ "); |
| boolean first = true; |
| // noinspection ForLoopReplaceableByForEach |
| for (int i = 0, parentsLength = parents.length; i < parentsLength; i++) { |
| final Marker marker = parents[i]; |
| if (!first) { |
| sb.append(", "); |
| } |
| first = false; |
| sb.append(marker.getName()); |
| final Marker[] p = marker instanceof Log4jMarker ? ((Log4jMarker) marker).parents : marker.getParents(); |
| if (p != null) { |
| addParentInfo(sb, p); |
| } |
| } |
| sb.append(" ]"); |
| } |
| } |
| |
| // this method wouldn't be necessary if Marker methods threw an NPE instead of an IAE for null values ;) |
| private static void requireNonNull(final Object obj, final String message) { |
| if (obj == null) { |
| throw new IllegalArgumentException(message); |
| } |
| } |
| } |