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 */ 017package org.apache.logging.log4j; 018 019import java.util.Arrays; 020import java.util.concurrent.ConcurrentHashMap; 021import java.util.concurrent.ConcurrentMap; 022 023import org.apache.logging.log4j.util.PerformanceSensitive; 024import org.apache.logging.log4j.util.StringBuilderFormattable; 025 026/** 027 * Applications create Markers by using the Marker Manager. All Markers created by this Manager are immutable. 028 */ 029public final class MarkerManager { 030 031 private static final ConcurrentMap<String, Marker> MARKERS = new ConcurrentHashMap<>(); 032 033 private MarkerManager() { 034 // do nothing 035 } 036 037 /** 038 * Clears all markers. 039 */ 040 public static void clear() { 041 MARKERS.clear(); 042 } 043 044 /** 045 * Tests existence of the given marker. 046 * 047 * @param key the marker name 048 * @return true if the marker exists. 049 * @since 2.4 050 */ 051 public static boolean exists(final String key) { 052 return MARKERS.containsKey(key); 053 } 054 055 /** 056 * Retrieves a Marker or create a Marker that has no parent. 057 * 058 * @param name The name of the Marker. 059 * @return The Marker with the specified name. 060 * @throws IllegalArgumentException if the argument is {@code null} 061 */ 062 public static Marker getMarker(final String name) { 063 Marker result = MARKERS.get(name); 064 if (result == null) { 065 MARKERS.putIfAbsent(name, new Log4jMarker(name)); 066 result = MARKERS.get(name); 067 } 068 return result; 069 } 070 071 /** 072 * Retrieves or creates a Marker with the specified parent. The parent must have been previously created. 073 * 074 * @param name The name of the Marker. 075 * @param parent The name of the parent Marker. 076 * @return The Marker with the specified name. 077 * @throws IllegalArgumentException if the parent Marker does not exist. 078 * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release. 079 */ 080 @Deprecated 081 public static Marker getMarker(final String name, final String parent) { 082 final Marker parentMarker = MARKERS.get(parent); 083 if (parentMarker == null) { 084 throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined"); 085 } 086 return getMarker(name, parentMarker); 087 } 088 089 /** 090 * Retrieves or creates a Marker with the specified parent. 091 * 092 * @param name The name of the Marker. 093 * @param parent The parent Marker. 094 * @return The Marker with the specified name. 095 * @throws IllegalArgumentException if any argument is {@code null} 096 * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release. 097 */ 098 @Deprecated 099 public static Marker getMarker(final String name, final Marker parent) { 100 return getMarker(name).addParents(parent); 101 } 102 103 /** 104 * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em> 105 * <p> 106 * The actual Marker implementation. 107 * </p> 108 * <p> 109 * <em>Internal note: We could make this class package private instead of public if the class 110 * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn} 111 * is moved to this package and would of course stay in its current module.</em> 112 * </p> 113 */ 114 public static class Log4jMarker implements Marker, StringBuilderFormattable { 115 116 private static final long serialVersionUID = 100L; 117 118 private final String name; 119 120 private volatile Marker[] parents; 121 122 /** 123 * Required by JAXB and Jackson for XML and JSON IO. 124 */ 125 @SuppressWarnings("unused") 126 private Log4jMarker() { 127 this.name = null; 128 this.parents = null; 129 } 130 131 /** 132 * Constructs a new Marker. 133 * 134 * @param name the name of the Marker. 135 * @throws IllegalArgumentException if the argument is {@code null} 136 */ 137 public Log4jMarker(final String name) { 138 // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker 139 // name seems rather pointless. To get an "anonymous" Marker, just use an empty string. 140 requireNonNull(name, "Marker name cannot be null."); 141 this.name = name; 142 this.parents = null; 143 } 144 145 // TODO: use java.util.concurrent 146 147 @Override 148 public synchronized Marker addParents(final Marker... parentMarkers) { 149 requireNonNull(parentMarkers, "A parent marker must be specified"); 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 requireNonNull(parent, "A parent marker must be specified"); 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 Marker[] parentsSnapshot = parents; 235 if (parentsSnapshot == null) { 236 return null; 237 } 238 return Arrays.copyOf(parentsSnapshot, parentsSnapshot.length); 239 } 240 241 @Override 242 public boolean hasParents() { 243 return this.parents != null; 244 } 245 246 @Override 247 @PerformanceSensitive({"allocation", "unrolled"}) 248 public boolean isInstanceOf(final Marker marker) { 249 requireNonNull(marker, "A marker parameter is required"); 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 @PerformanceSensitive({"allocation", "unrolled"}) 276 public boolean isInstanceOf(final String markerName) { 277 requireNonNull(markerName, "A marker name is required"); 278 if (markerName.equals(this.getName())) { 279 return true; 280 } 281 // Use a real marker for child comparisons. It is faster than comparing the names. 282 final Marker marker = MARKERS.get(markerName); 283 if (marker == null) { 284 return false; 285 } 286 final Marker[] localParents = parents; 287 if (localParents != null) { 288 final int localParentsLength = localParents.length; 289 if (localParentsLength == 1) { 290 return checkParent(localParents[0], marker); 291 } 292 if (localParentsLength == 2) { 293 return checkParent(localParents[0], marker) || checkParent(localParents[1], marker); 294 } 295 // noinspection ForLoopReplaceableByForEach 296 for (int i = 0; i < localParentsLength; i++) { 297 final Marker localParent = localParents[i]; 298 if (checkParent(localParent, marker)) { 299 return true; 300 } 301 } 302 } 303 304 return false; 305 } 306 307 @PerformanceSensitive({"allocation", "unrolled"}) 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 313 .getParents(); 314 if (localParents != null) { 315 final int localParentsLength = localParents.length; 316 if (localParentsLength == 1) { 317 return checkParent(localParents[0], marker); 318 } 319 if (localParentsLength == 2) { 320 return checkParent(localParents[0], marker) || checkParent(localParents[1], marker); 321 } 322 // noinspection ForLoopReplaceableByForEach 323 for (int i = 0; i < localParentsLength; i++) { 324 final Marker localParent = localParents[i]; 325 if (checkParent(localParent, marker)) { 326 return true; 327 } 328 } 329 } 330 return false; 331 } 332 333 /* 334 * Called from add while synchronized. 335 */ 336 @PerformanceSensitive("allocation") 337 private static boolean contains(final Marker parent, final Marker... localParents) { 338 // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms 339 // noinspection ForLoopReplaceableByForEach 340 for (int i = 0, localParentsLength = localParents.length; i < localParentsLength; i++) { 341 final Marker marker = localParents[i]; 342 if (marker == parent) { 343 return true; 344 } 345 } 346 return false; 347 } 348 349 @Override 350 public boolean equals(final Object o) { 351 if (this == o) { 352 return true; 353 } 354 if (o == null || !(o instanceof Marker)) { 355 return false; 356 } 357 final Marker marker = (Marker) o; 358 return name.equals(marker.getName()); 359 } 360 361 @Override 362 public int hashCode() { 363 return name.hashCode(); 364 } 365 366 @Override 367 public String toString() { 368 // FIXME: might want to use an initial capacity; the default is 16 (or str.length() + 16) 369 final StringBuilder sb = new StringBuilder(); 370 formatTo(sb); 371 return sb.toString(); 372 } 373 374 @Override 375 public void formatTo(final StringBuilder sb) { 376 sb.append(name); 377 final Marker[] localParents = parents; 378 if (localParents != null) { 379 addParentInfo(sb, localParents); 380 } 381 } 382 383 @PerformanceSensitive("allocation") 384 private static void addParentInfo(final StringBuilder sb, final Marker... parents) { 385 sb.append("[ "); 386 boolean first = true; 387 // noinspection ForLoopReplaceableByForEach 388 for (int i = 0, parentsLength = parents.length; i < parentsLength; i++) { 389 final Marker marker = parents[i]; 390 if (!first) { 391 sb.append(", "); 392 } 393 first = false; 394 sb.append(marker.getName()); 395 final Marker[] p = marker instanceof Log4jMarker ? ((Log4jMarker) marker).parents : marker.getParents(); 396 if (p != null) { 397 addParentInfo(sb, p); 398 } 399 } 400 sb.append(" ]"); 401 } 402 } 403 404 // this method wouldn't be necessary if Marker methods threw an NPE instead of an IAE for null values ;) 405 private static void requireNonNull(final Object obj, final String message) { 406 if (obj == null) { 407 throw new IllegalArgumentException(message); 408 } 409 } 410}