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 if (this.parents == null) { 235 return null; 236 } 237 return Arrays.copyOf(this.parents, this.parents.length); 238 } 239 240 @Override 241 public boolean hasParents() { 242 return this.parents != null; 243 } 244 245 @Override 246 @PerformanceSensitive({"allocation", "unrolled"}) 247 public boolean isInstanceOf(final Marker marker) { 248 requireNonNull(marker, "A marker parameter is required"); 249 if (this == marker) { 250 return true; 251 } 252 final Marker[] localParents = parents; 253 if (localParents != null) { 254 // With only one or two parents the for loop is slower. 255 final int localParentsLength = localParents.length; 256 if (localParentsLength == 1) { 257 return checkParent(localParents[0], marker); 258 } 259 if (localParentsLength == 2) { 260 return checkParent(localParents[0], marker) || checkParent(localParents[1], marker); 261 } 262 // noinspection ForLoopReplaceableByForEach 263 for (int i = 0; i < localParentsLength; i++) { 264 final Marker localParent = localParents[i]; 265 if (checkParent(localParent, marker)) { 266 return true; 267 } 268 } 269 } 270 return false; 271 } 272 273 @Override 274 @PerformanceSensitive({"allocation", "unrolled"}) 275 public boolean isInstanceOf(final String markerName) { 276 requireNonNull(markerName, "A marker name is required"); 277 if (markerName.equals(this.getName())) { 278 return true; 279 } 280 // Use a real marker for child comparisons. It is faster than comparing the names. 281 final Marker marker = MARKERS.get(markerName); 282 if (marker == null) { 283 return false; 284 } 285 final Marker[] localParents = parents; 286 if (localParents != null) { 287 final int localParentsLength = localParents.length; 288 if (localParentsLength == 1) { 289 return checkParent(localParents[0], marker); 290 } 291 if (localParentsLength == 2) { 292 return checkParent(localParents[0], marker) || checkParent(localParents[1], marker); 293 } 294 // noinspection ForLoopReplaceableByForEach 295 for (int i = 0; i < localParentsLength; i++) { 296 final Marker localParent = localParents[i]; 297 if (checkParent(localParent, marker)) { 298 return true; 299 } 300 } 301 } 302 303 return false; 304 } 305 306 @PerformanceSensitive({"allocation", "unrolled"}) 307 private static boolean checkParent(final Marker parent, final Marker marker) { 308 if (parent == marker) { 309 return true; 310 } 311 final Marker[] localParents = parent instanceof Log4jMarker ? ((Log4jMarker) parent).parents : parent 312 .getParents(); 313 if (localParents != null) { 314 final int localParentsLength = localParents.length; 315 if (localParentsLength == 1) { 316 return checkParent(localParents[0], marker); 317 } 318 if (localParentsLength == 2) { 319 return checkParent(localParents[0], marker) || checkParent(localParents[1], marker); 320 } 321 // noinspection ForLoopReplaceableByForEach 322 for (int i = 0; i < localParentsLength; i++) { 323 final Marker localParent = localParents[i]; 324 if (checkParent(localParent, marker)) { 325 return true; 326 } 327 } 328 } 329 return false; 330 } 331 332 /* 333 * Called from add while synchronized. 334 */ 335 @PerformanceSensitive("allocation") 336 private static boolean contains(final Marker parent, final Marker... localParents) { 337 // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms 338 // noinspection ForLoopReplaceableByForEach 339 for (int i = 0, localParentsLength = localParents.length; i < localParentsLength; i++) { 340 final Marker marker = localParents[i]; 341 if (marker == parent) { 342 return true; 343 } 344 } 345 return false; 346 } 347 348 @Override 349 public boolean equals(final Object o) { 350 if (this == o) { 351 return true; 352 } 353 if (o == null || !(o instanceof Marker)) { 354 return false; 355 } 356 final Marker marker = (Marker) o; 357 return name.equals(marker.getName()); 358 } 359 360 @Override 361 public int hashCode() { 362 return name.hashCode(); 363 } 364 365 @Override 366 public String toString() { 367 // FIXME: might want to use an initial capacity; the default is 16 (or str.length() + 16) 368 final StringBuilder sb = new StringBuilder(); 369 formatTo(sb); 370 return sb.toString(); 371 } 372 373 @Override 374 public void formatTo(final StringBuilder sb) { 375 sb.append(name); 376 final Marker[] localParents = parents; 377 if (localParents != null) { 378 addParentInfo(sb, localParents); 379 } 380 } 381 382 @PerformanceSensitive("allocation") 383 private static void addParentInfo(final StringBuilder sb, final Marker... parents) { 384 sb.append("[ "); 385 boolean first = true; 386 // noinspection ForLoopReplaceableByForEach 387 for (int i = 0, parentsLength = parents.length; i < parentsLength; i++) { 388 final Marker marker = parents[i]; 389 if (!first) { 390 sb.append(", "); 391 } 392 first = false; 393 sb.append(marker.getName()); 394 final Marker[] p = marker instanceof Log4jMarker ? ((Log4jMarker) marker).parents : marker.getParents(); 395 if (p != null) { 396 addParentInfo(sb, p); 397 } 398 } 399 sb.append(" ]"); 400 } 401 } 402 403 // this method wouldn't be necessary if Marker methods threw an NPE instead of an IAE for null values ;) 404 private static void requireNonNull(final Object obj, final String message) { 405 if (obj == null) { 406 throw new IllegalArgumentException(message); 407 } 408 } 409}