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; 024 025/** 026 * Applications create Markers by using the Marker Manager. All Markers created by this Manager are immutable. 027 */ 028public final class MarkerManager { 029 030 private static final ConcurrentMap<String, Marker> MARKERS = new ConcurrentHashMap<>(); 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 * Tests existence of the given marker. 045 * 046 * @param key the marker name 047 * @return true if the marker exists. 048 * @since 2.4 049 */ 050 public static boolean exists(final String key) { 051 return MARKERS.containsKey(key); 052 } 053 054 /** 055 * Retrieves a Marker or create a Marker that has no parent. 056 * 057 * @param name The name of the Marker. 058 * @return The Marker with the specified name. 059 * @throws IllegalArgumentException if the argument is {@code null} 060 */ 061 public static Marker getMarker(final String name) { 062 Marker result = MARKERS.get(name); 063 if (result == null) { 064 MARKERS.putIfAbsent(name, new Log4jMarker(name)); 065 result = MARKERS.get(name); 066 } 067 return result; 068 } 069 070 /** 071 * Retrieves or creates a Marker with the specified parent. The parent must have been previously created. 072 * 073 * @param name The name of the Marker. 074 * @param parent The name of the parent Marker. 075 * @return The Marker with the specified name. 076 * @throws IllegalArgumentException if the parent Marker does not exist. 077 * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release. 078 */ 079 @Deprecated 080 public static Marker getMarker(final String name, final String parent) { 081 final Marker parentMarker = MARKERS.get(parent); 082 if (parentMarker == null) { 083 throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined"); 084 } 085 @SuppressWarnings("deprecation") 086 final Marker marker = getMarker(name, parentMarker); 087 return marker; 088 } 089 090 /** 091 * Retrieves or creates a Marker with the specified parent. 092 * 093 * @param name The name of the Marker. 094 * @param parent The parent Marker. 095 * @return The Marker with the specified name. 096 * @throws IllegalArgumentException if any argument is {@code null} 097 * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release. 098 */ 099 @Deprecated 100 public static Marker getMarker(final String name, final Marker parent) { 101 return getMarker(name).addParents(parent); 102 } 103 104 /** 105 * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em> 106 * <p> 107 * The actual Marker implementation. 108 * </p> 109 * <p> 110 * <em>Internal note: We could make this class package private instead of public if the class 111 * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn} 112 * is moved to this package and would of course stay in its current module.</em> 113 * </p> 114 */ 115 public static class Log4jMarker implements Marker { 116 117 private static final long serialVersionUID = 100L; 118 119 private final String name; 120 121 private volatile Marker[] parents; 122 123 /** 124 * Required by JAXB and Jackson for XML and JSON IO. 125 */ 126 @SuppressWarnings("unused") 127 private Log4jMarker() { 128 this.name = null; 129 this.parents = null; 130 } 131 132 /** 133 * Constructs a new Marker. 134 * 135 * @param name the name of the Marker. 136 * @throws IllegalArgumentException if the argument is {@code null} 137 */ 138 public Log4jMarker(final String name) { 139 // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker 140 // name seems rather pointless. To get an "anonymous" Marker, just use an empty string. 141 requireNonNull(name, "Marker name cannot be null."); 142 this.name = name; 143 this.parents = null; 144 } 145 146 // TODO: use java.util.concurrent 147 148 @Override 149 public synchronized Marker addParents(final Marker... parentMarkers) { 150 requireNonNull(parentMarkers, "A parent marker must be specified"); 151 // It is not strictly necessary to copy the variable here but it should perform better than 152 // Accessing a volatile variable multiple times. 153 final Marker[] localParents = this.parents; 154 // Don't add a parent that is already in the hierarchy. 155 int count = 0; 156 int size = parentMarkers.length; 157 if (localParents != null) { 158 for (final Marker parent : parentMarkers) { 159 if (!(contains(parent, localParents) || parent.isInstanceOf(this))) { 160 ++count; 161 } 162 } 163 if (count == 0) { 164 return this; 165 } 166 size = localParents.length + count; 167 } 168 final Marker[] markers = new Marker[size]; 169 if (localParents != null) { 170 // It's perfectly OK to call arraycopy in a synchronized context; it's still faster 171 // noinspection CallToNativeMethodWhileLocked 172 System.arraycopy(localParents, 0, markers, 0, localParents.length); 173 } 174 int index = localParents == null ? 0 : localParents.length; 175 for (final Marker parent : parentMarkers) { 176 if (localParents == null || !(contains(parent, localParents) || parent.isInstanceOf(this))) { 177 markers[index++] = parent; 178 } 179 } 180 this.parents = markers; 181 return this; 182 } 183 184 @Override 185 public synchronized boolean remove(final Marker parent) { 186 requireNonNull(parent, "A parent marker must be specified"); 187 final Marker[] localParents = this.parents; 188 if (localParents == null) { 189 return false; 190 } 191 final int localParentsLength = localParents.length; 192 if (localParentsLength == 1) { 193 if (localParents[0].equals(parent)) { 194 parents = null; 195 return true; 196 } 197 return false; 198 } 199 int index = 0; 200 final Marker[] markers = new Marker[localParentsLength - 1]; 201 // noinspection ForLoopReplaceableByForEach 202 for (int i = 0; i < localParentsLength; i++) { 203 final Marker marker = localParents[i]; 204 if (!marker.equals(parent)) { 205 if (index == localParentsLength - 1) { 206 // no need to swap array 207 return false; 208 } 209 markers[index++] = marker; 210 } 211 } 212 parents = markers; 213 return true; 214 } 215 216 @Override 217 public Marker setParents(final Marker... markers) { 218 if (markers == null || markers.length == 0) { 219 this.parents = null; 220 } else { 221 final Marker[] array = new Marker[markers.length]; 222 System.arraycopy(markers, 0, array, 0, markers.length); 223 this.parents = array; 224 } 225 return this; 226 } 227 228 @Override 229 public String getName() { 230 return this.name; 231 } 232 233 @Override 234 public Marker[] getParents() { 235 if (this.parents == null) { 236 return null; 237 } 238 return Arrays.copyOf(this.parents, this.parents.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(name); 370 final Marker[] localParents = parents; 371 if (localParents != null) { 372 addParentInfo(sb, localParents); 373 } 374 return sb.toString(); 375 } 376 377 @PerformanceSensitive("allocation") 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 // this method wouldn't be necessary if Marker methods threw an NPE instead of an IAE for null values ;) 399 private static void requireNonNull(final Object obj, final String message) { 400 if (obj == null) { 401 throw new IllegalArgumentException(message); 402 } 403 } 404}