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/** 025 * Applications create Markers by using the Marker Manager. All Markers created by this Manager are 026 * 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 * @param key the marker name 046 * @return true if the marker exists. 047 * @since 2.4 048 */ 049 public static boolean exists(final String key) { 050 return MARKERS.containsKey(key); 051 } 052 053 054 /** 055 * Retrieves a Marker or create a Marker that has no parent. 056 * @param name The name of the Marker. 057 * @return The Marker with the specified name. 058 * @throws IllegalArgumentException if the argument is {@code null} 059 */ 060 public static Marker getMarker(final String name) { 061 MARKERS.putIfAbsent(name, new Log4jMarker(name)); 062 return MARKERS.get(name); 063 } 064 065 /** 066 * Retrieves or creates a Marker with the specified parent. The parent must have been previously created. 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 * @param name The name of the Marker. 087 * @param parent The parent Marker. 088 * @return The Marker with the specified name. 089 * @throws IllegalArgumentException if any argument is {@code null} 090 * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release. 091 */ 092 @Deprecated 093 public static Marker getMarker(final String name, final Marker parent) { 094 MARKERS.putIfAbsent(name, new Log4jMarker(name)); 095 return MARKERS.get(name).addParents(parent); 096 } 097 098 /** 099 * <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em> 100 * <p> 101 * The actual Marker implementation. 102 * </p> 103 * <p> 104 * <em>Internal note: We could make this class package private instead of public if the class 105 * {@code org.apache.logging.log4j.core.jackson.MarkerMixIn} 106 * is moved to this package and would of course stay in its current module.</em> 107 * </p> 108 */ 109 public static class Log4jMarker implements Marker { 110 111 private static final long serialVersionUID = 100L; 112 113 private final String name; 114 115 private volatile Marker[] parents; 116 117 /** 118 * Required by JAXB and Jackson for XML and JSON IO. 119 */ 120 @SuppressWarnings("unused") 121 private Log4jMarker() { 122 this.name = null; 123 this.parents = null; 124 } 125 126 /** 127 * Constructs a new Marker. 128 * @param name the name of the Marker. 129 * @throws IllegalArgumentException if the argument is {@code null} 130 */ 131 public Log4jMarker(final String name) { 132 if (name == null) { 133 // we can't store null references in a ConcurrentHashMap as it is, not to mention that a null Marker 134 // name seems rather pointless. To get an "anonymous" Marker, just use an empty string. 135 throw new IllegalArgumentException("Marker name cannot be null."); 136 } 137 this.name = name; 138 this.parents = null; 139 } 140 141 // TODO: use java.util.concurrent 142 143 @Override 144 public synchronized Marker addParents(final Marker... parents) { 145 if (parents == null) { 146 throw new IllegalArgumentException("A parent marker must be specified"); 147 } 148 // It is not strictly necessary to copy the variable here but it should perform better than 149 // Accessing a volatile variable multiple times. 150 final Marker[] localParents = this.parents; 151 // Don't add a parent that is already in the hierarchy. 152 int count = 0; 153 int size = parents.length; 154 if (localParents != null) { 155 for (final Marker parent : parents) { 156 if (!(contains(parent, localParents) || parent.isInstanceOf(this))) { 157 ++count; 158 } 159 } 160 if (count == 0) { 161 return this; 162 } 163 size = localParents.length + count; 164 } 165 final Marker[] markers = new Marker[size]; 166 if (localParents != null) { 167 // It's perfectly OK to call arraycopy in a synchronized context; it's still faster 168 //noinspection CallToNativeMethodWhileLocked 169 System.arraycopy(localParents, 0, markers, 0, localParents.length); 170 } 171 int index = localParents == null ? 0 : localParents.length; 172 for (final Marker parent : parents) { 173 if (localParents == null || !(contains(parent, localParents) || parent.isInstanceOf(this))) { 174 markers[index++] = parent; 175 } 176 } 177 this.parents = markers; 178 return this; 179 } 180 181 @Override 182 public synchronized boolean remove(final Marker parent) { 183 if (parent == null) { 184 throw new IllegalArgumentException("A parent marker must be specified"); 185 } 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 public boolean isInstanceOf(final Marker marker) { 247 if (marker == null) { 248 throw new IllegalArgumentException("A marker parameter is required"); 249 } 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 public boolean isInstanceOf(final String markerName) { 276 if (markerName == null) { 277 throw new IllegalArgumentException("A marker name is required"); 278 } 279 if (markerName.equals(this.getName())) { 280 return true; 281 } 282 // Use a real marker for child comparisons. It is faster than comparing the names. 283 final Marker marker = MARKERS.get(markerName); 284 if (marker == null) { 285 return false; 286 } 287 final Marker[] localParents = parents; 288 if (localParents != null) { 289 final int localParentsLength = localParents.length; 290 if (localParentsLength == 1) { 291 return checkParent(localParents[0], marker); 292 } 293 if (localParentsLength == 2) { 294 return checkParent(localParents[0], marker) || checkParent(localParents[1], marker); 295 } 296 //noinspection ForLoopReplaceableByForEach 297 for (int i = 0; i < localParentsLength; i++) { 298 final Marker localParent = localParents[i]; 299 if (checkParent(localParent, marker)) { 300 return true; 301 } 302 } 303 } 304 305 return false; 306 } 307 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.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 private static boolean contains(final Marker parent, final Marker... localParents) { 336 //noinspection ForLoopReplaceableByForEach 337 // performance tests showed a normal for loop is slightly faster than a for-each loop on some platforms 338 for (int i = 0, localParentsLength = localParents.length; i < localParentsLength; i++) { 339 final Marker marker = localParents[i]; 340 if (marker == parent) { 341 return true; 342 } 343 } 344 return false; 345 } 346 347 @Override 348 public boolean equals(final Object o) { 349 if (this == o) { 350 return true; 351 } 352 if (o == null || !(o instanceof Marker)) { 353 return false; 354 } 355 final Marker marker = (Marker) o; 356 return name.equals(marker.getName()); 357 } 358 359 @Override 360 public int hashCode() { 361 return name.hashCode(); 362 } 363 364 @Override 365 public String toString() { 366 // FIXME: might want to use an initial capacity; the default is 16 (or str.length() + 16) 367 final StringBuilder sb = new StringBuilder(name); 368 final Marker[] localParents = parents; 369 if (localParents != null) { 370 addParentInfo(sb, localParents); 371 } 372 return sb.toString(); 373 } 374 375 private static void addParentInfo(final StringBuilder sb, final Marker... parents) { 376 sb.append("[ "); 377 boolean first = true; 378 //noinspection ForLoopReplaceableByForEach 379 for (int i = 0, parentsLength = parents.length; i < parentsLength; i++) { 380 final Marker marker = parents[i]; 381 if (!first) { 382 sb.append(", "); 383 } 384 first = false; 385 sb.append(marker.getName()); 386 final Marker[] p = marker instanceof Log4jMarker ? ((Log4jMarker) marker).parents : marker.getParents(); 387 if (p != null) { 388 addParentInfo(sb, p); 389 } 390 } 391 sb.append(" ]"); 392 } 393 } 394 395}