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 023/** 024 * Applications create Markers by using the Marker Manager. All Markers created by this Manager are immutable. 025 */ 026public final class MarkerManager { 027 028 private static final ConcurrentMap<String, Marker> MARKERS = new ConcurrentHashMap<>(); 029 030 private MarkerManager() { 031 // do nothing 032 } 033 034 /** 035 * Clears all markers. 036 */ 037 public static void clear() { 038 MARKERS.clear(); 039 } 040 041 /** 042 * Tests existence of the given marker. 043 * 044 * @param key the marker name 045 * @return true if the marker exists. 046 * @since 2.4 047 */ 048 public static boolean exists(final String key) { 049 return MARKERS.containsKey(key); 050 } 051 052 /** 053 * Retrieves a Marker or create a Marker that has no parent. 054 * 055 * @param name The name of the Marker. 056 * @return The Marker with the specified name. 057 * @throws IllegalArgumentException if the argument is {@code null} 058 */ 059 public static Marker getMarker(final String name) { 060 MARKERS.putIfAbsent(name, new Log4jMarker(name)); 061 return MARKERS.get(name); 062 } 063 064 /** 065 * Retrieves or creates a Marker with the specified parent. The parent must have been previously created. 066 * 067 * @param name The name of the Marker. 068 * @param parent The name of the parent Marker. 069 * @return The Marker with the specified name. 070 * @throws IllegalArgumentException if the parent Marker does not exist. 071 * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release. 072 */ 073 @Deprecated 074 public static Marker getMarker(final String name, final String parent) { 075 final Marker parentMarker = MARKERS.get(parent); 076 if (parentMarker == null) { 077 throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined"); 078 } 079 @SuppressWarnings("deprecation") 080 final Marker marker = getMarker(name, parentMarker); 081 return marker; 082 } 083 084 /** 085 * Retrieves or creates a Marker with the specified parent. 086 * 087 * @param name The name of the Marker. 088 * @param parent The parent Marker. 089 * @return The Marker with the specified name. 090 * @throws IllegalArgumentException if any argument is {@code null} 091 * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release. 092 */ 093 @Deprecated 094 public static Marker getMarker(final String name, final Marker parent) { 095 MARKERS.putIfAbsent(name, new Log4jMarker(name)); 096 return MARKERS.get(name).addParents(parent); 097 } 098 099 /** 100 * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em> 101 * <p> 102 * The actual Marker implementation. 103 * </p> 104 * <p> 105 * <em>Internal note: We could make this class package private instead of public if the class 106 * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn} 107 * is moved to this package and would of course stay in its current module.</em> 108 * </p> 109 */ 110 public static class Log4jMarker implements Marker { 111 112 private static final long serialVersionUID = 100L; 113 114 private final String name; 115 116 private volatile Marker[] parents; 117 118 /** 119 * Required by JAXB and Jackson for XML and JSON IO. 120 */ 121 @SuppressWarnings("unused") 122 private Log4jMarker() { 123 this.name = null; 124 this.parents = null; 125 } 126 127 /** 128 * Constructs a new Marker. 129 * 130 * @param name the name of the Marker. 131 * @throws IllegalArgumentException if the argument is {@code null} 132 */ 133 public Log4jMarker(final String name) { 134 if (name == null) { 135 // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker 136 // name seems rather pointless. To get an "anonymous" Marker, just use an empty string. 137 throw new IllegalArgumentException("Marker name cannot be null."); 138 } 139 this.name = name; 140 this.parents = null; 141 } 142 143 // TODO: use java.util.concurrent 144 145 @Override 146 public synchronized Marker addParents(final Marker... parentMarkers) { 147 if (parentMarkers == null) { 148 throw new IllegalArgumentException("A parent marker must be specified"); 149 } 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 if (parent == null) { 186 throw new IllegalArgumentException("A parent marker must be specified"); 187 } 188 final Marker[] localParents = this.parents; 189 if (localParents == null) { 190 return false; 191 } 192 final int localParentsLength = localParents.length; 193 if (localParentsLength == 1) { 194 if (localParents[0].equals(parent)) { 195 parents = null; 196 return true; 197 } 198 return false; 199 } 200 int index = 0; 201 final Marker[] markers = new Marker[localParentsLength - 1]; 202 // noinspection ForLoopReplaceableByForEach 203 for (int i = 0; i < localParentsLength; i++) { 204 final Marker marker = localParents[i]; 205 if (!marker.equals(parent)) { 206 if (index == localParentsLength - 1) { 207 // no need to swap array 208 return false; 209 } 210 markers[index++] = marker; 211 } 212 } 213 parents = markers; 214 return true; 215 } 216 217 @Override 218 public Marker setParents(final Marker... markers) { 219 if (markers == null || markers.length == 0) { 220 this.parents = null; 221 } else { 222 final Marker[] array = new Marker[markers.length]; 223 System.arraycopy(markers, 0, array, 0, markers.length); 224 this.parents = array; 225 } 226 return this; 227 } 228 229 @Override 230 public String getName() { 231 return this.name; 232 } 233 234 @Override 235 public Marker[] getParents() { 236 if (this.parents == null) { 237 return null; 238 } 239 return Arrays.copyOf(this.parents, this.parents.length); 240 } 241 242 @Override 243 public boolean hasParents() { 244 return this.parents != null; 245 } 246 247 @Override 248 public boolean isInstanceOf(final Marker marker) { 249 if (marker == null) { 250 throw new IllegalArgumentException("A marker parameter is required"); 251 } 252 if (this == marker) { 253 return true; 254 } 255 final Marker[] localParents = parents; 256 if (localParents != null) { 257 // With only one or two parents the for loop is slower. 258 final int localParentsLength = localParents.length; 259 if (localParentsLength == 1) { 260 return checkParent(localParents[0], marker); 261 } 262 if (localParentsLength == 2) { 263 return checkParent(localParents[0], marker) || checkParent(localParents[1], marker); 264 } 265 // noinspection ForLoopReplaceableByForEach 266 for (int i = 0; i < localParentsLength; i++) { 267 final Marker localParent = localParents[i]; 268 if (checkParent(localParent, marker)) { 269 return true; 270 } 271 } 272 } 273 return false; 274 } 275 276 @Override 277 public boolean isInstanceOf(final String markerName) { 278 if (markerName == null) { 279 throw new IllegalArgumentException("A marker name is required"); 280 } 281 if (markerName.equals(this.getName())) { 282 return true; 283 } 284 // Use a real marker for child comparisons. It is faster than comparing the names. 285 final Marker marker = MARKERS.get(markerName); 286 if (marker == null) { 287 return false; 288 } 289 final Marker[] localParents = parents; 290 if (localParents != null) { 291 final int localParentsLength = localParents.length; 292 if (localParentsLength == 1) { 293 return checkParent(localParents[0], marker); 294 } 295 if (localParentsLength == 2) { 296 return checkParent(localParents[0], marker) || checkParent(localParents[1], marker); 297 } 298 // noinspection ForLoopReplaceableByForEach 299 for (int i = 0; i < localParentsLength; i++) { 300 final Marker localParent = localParents[i]; 301 if (checkParent(localParent, marker)) { 302 return true; 303 } 304 } 305 } 306 307 return false; 308 } 309 310 private static boolean checkParent(final Marker parent, final Marker marker) { 311 if (parent == marker) { 312 return true; 313 } 314 final Marker[] localParents = parent instanceof Log4jMarker ? ((Log4jMarker) parent).parents : parent 315 .getParents(); 316 if (localParents != null) { 317 final int localParentsLength = localParents.length; 318 if (localParentsLength == 1) { 319 return checkParent(localParents[0], marker); 320 } 321 if (localParentsLength == 2) { 322 return checkParent(localParents[0], marker) || checkParent(localParents[1], marker); 323 } 324 // noinspection ForLoopReplaceableByForEach 325 for (int i = 0; i < localParentsLength; i++) { 326 final Marker localParent = localParents[i]; 327 if (checkParent(localParent, marker)) { 328 return true; 329 } 330 } 331 } 332 return false; 333 } 334 335 /* 336 * Called from add while synchronized. 337 */ 338 private static boolean contains(final Marker parent, final Marker... localParents) { 339 // noinspection ForLoopReplaceableByForEach 340 // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms 341 for (int i = 0, localParentsLength = localParents.length; i < localParentsLength; i++) { 342 final Marker marker = localParents[i]; 343 if (marker == parent) { 344 return true; 345 } 346 } 347 return false; 348 } 349 350 @Override 351 public boolean equals(final Object o) { 352 if (this == o) { 353 return true; 354 } 355 if (o == null || !(o instanceof Marker)) { 356 return false; 357 } 358 final Marker marker = (Marker) o; 359 return name.equals(marker.getName()); 360 } 361 362 @Override 363 public int hashCode() { 364 return name.hashCode(); 365 } 366 367 @Override 368 public String toString() { 369 // FIXME: might want to use an initial capacity; the default is 16 (or str.length() + 16) 370 final StringBuilder sb = new StringBuilder(name); 371 final Marker[] localParents = parents; 372 if (localParents != null) { 373 addParentInfo(sb, localParents); 374 } 375 return sb.toString(); 376 } 377 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}