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