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.core.pattern; 018 019import java.util.ArrayList; 020import java.util.List; 021 022import org.apache.logging.log4j.util.PerformanceSensitive; 023 024 025/** 026 * NameAbbreviator generates abbreviated logger and class names. 027 */ 028@PerformanceSensitive("allocation") 029public abstract class NameAbbreviator { 030 /** 031 * Default (no abbreviation) abbreviator. 032 */ 033 private static final NameAbbreviator DEFAULT = new NOPAbbreviator(); 034 035 /** 036 * Gets an abbreviator. 037 * <p> 038 * For example, "%logger{2}" will output only 2 elements of the logger name, "%logger{1.}" will output only the 039 * first character of the non-final elements in the name, "%logger(1~.2~} will output the first character of the 040 * first element, two characters of the second and subsequent elements and will use a tilde to indicate abbreviated 041 * characters. 042 * </p> 043 * 044 * @param pattern 045 * abbreviation pattern. 046 * @return abbreviator, will not be null. 047 */ 048 public static NameAbbreviator getAbbreviator(final String pattern) { 049 if (pattern.length() > 0) { 050 // if pattern is just spaces and numbers then 051 // use MaxElementAbbreviator 052 final String trimmed = pattern.trim(); 053 054 if (trimmed.isEmpty()) { 055 return DEFAULT; 056 } 057 058 boolean isNegativeNumber; 059 final String number; 060 061 // check if number is a negative number 062 if (trimmed.length() > 1 && trimmed.charAt(0) == '-') { 063 isNegativeNumber = true; 064 number = trimmed.substring(1); 065 } else { 066 isNegativeNumber = false; 067 number = trimmed; 068 } 069 070 int i = 0; 071 072 while (i < number.length() && number.charAt(i) >= '0' 073 && number.charAt(i) <= '9') { 074 i++; 075 } 076 077 // 078 // if all blanks and digits 079 // 080 if (i == number.length()) { 081 return new MaxElementAbbreviator(Integer.parseInt(number), 082 isNegativeNumber? MaxElementAbbreviator.Strategy.DROP : MaxElementAbbreviator.Strategy.RETAIN); 083 } 084 085 final ArrayList<PatternAbbreviatorFragment> fragments = new ArrayList<>(5); 086 char ellipsis; 087 int charCount; 088 int pos = 0; 089 090 while (pos < trimmed.length() && pos >= 0) { 091 int ellipsisPos = pos; 092 093 if (trimmed.charAt(pos) == '*') { 094 charCount = Integer.MAX_VALUE; 095 ellipsisPos++; 096 } else if (trimmed.charAt(pos) >= '0' && trimmed.charAt(pos) <= '9') { 097 charCount = trimmed.charAt(pos) - '0'; 098 ellipsisPos++; 099 } else { 100 charCount = 0; 101 } 102 103 ellipsis = '\0'; 104 105 if (ellipsisPos < trimmed.length()) { 106 ellipsis = trimmed.charAt(ellipsisPos); 107 108 if (ellipsis == '.') { 109 ellipsis = '\0'; 110 } 111 } 112 113 fragments.add(new PatternAbbreviatorFragment(charCount, ellipsis)); 114 pos = trimmed.indexOf('.', pos); 115 116 if (pos == -1) { 117 break; 118 } 119 120 pos++; 121 } 122 123 return new PatternAbbreviator(fragments); 124 } 125 126 // 127 // no matching abbreviation, return defaultAbbreviator 128 // 129 return DEFAULT; 130 } 131 132 /** 133 * Gets default abbreviator. 134 * 135 * @return default abbreviator. 136 */ 137 public static NameAbbreviator getDefaultAbbreviator() { 138 return DEFAULT; 139 } 140 141 /** 142 * Abbreviates a name in a String. 143 * 144 * @param original the text to abbreviate, may not be null. 145 * @param destination StringBuilder to write the result to 146 */ 147 public abstract void abbreviate(final String original, final StringBuilder destination); 148 149 /** 150 * Abbreviator that simply appends full name to buffer. 151 */ 152 private static class NOPAbbreviator extends NameAbbreviator { 153 /** 154 * Constructor. 155 */ 156 public NOPAbbreviator() { 157 } 158 159 /** 160 * {@inheritDoc} 161 */ 162 @Override 163 public void abbreviate(final String original, final StringBuilder destination) { 164 destination.append(original); 165 } 166 } 167 168 /** 169 * Abbreviator that drops starting path elements. 170 */ 171 private static class MaxElementAbbreviator extends NameAbbreviator { 172 173 /** 174 * <p>When the name is reduced in length by cutting parts, there can be two ways to do it.</p> 175 * 1. Remove a given number of parts starting from front - called DROP <br/> 176 * 2. Retain a given number of parts starting from the end - called RETAIN 177 */ 178 private enum Strategy { 179 DROP(0) { 180 @Override 181 void abbreviate(final int count, final String original, final StringBuilder destination) { 182 // If a path does not contain enough path elements to drop, none will be dropped. 183 int start = 0; 184 int nextStart; 185 for (int i = 0; i < count; i++) { 186 nextStart = original.indexOf('.', start); 187 if (nextStart == -1) { 188 destination.append(original); 189 return; 190 } 191 start = nextStart + 1; 192 } 193 destination.append(original, start, original.length()); 194 } 195 }, 196 RETAIN(1) { 197 @Override 198 void abbreviate(final int count, final String original, final StringBuilder destination) { 199 // We subtract 1 from 'len' when assigning to 'end' to avoid out of 200 // bounds exception in return r.substring(end+1, len). This can happen if 201 // precision is 1 and the category name ends with a dot. 202 int end = original.length() - 1; 203 204 for (int i = count; i > 0; i--) { 205 end = original.lastIndexOf('.', end - 1); 206 if (end == -1) { 207 destination.append(original); 208 return; 209 } 210 } 211 destination.append(original, end + 1, original.length()); 212 } 213 }; 214 215 final int minCount; 216 217 Strategy(final int minCount) { 218 this.minCount = minCount; 219 } 220 221 abstract void abbreviate(final int count, final String original, final StringBuilder destination); 222 } 223 224 /** 225 * Maximum number of path elements to output. 226 */ 227 private final int count; 228 229 /** 230 * Strategy used for cutting down the size of the name 231 */ 232 private final Strategy strategy; 233 234 /** 235 * Create new instance. 236 * 237 * @param count maximum number of path elements to drop or output. 238 * @param strategy drop or retain 239 */ 240 public MaxElementAbbreviator(final int count, final Strategy strategy) { 241 this.count = Math.max(count, strategy.minCount); 242 this.strategy = strategy; 243 } 244 245 /** 246 * Abbreviate name. 247 * 248 * @param original The String to abbreviate. 249 * @param destination the buffer to write the abbreviated name into 250 */ 251 @Override 252 public void abbreviate(final String original, final StringBuilder destination) { 253 strategy.abbreviate(count, original, destination); 254 } 255 } 256 257 /** 258 * Fragment of an pattern abbreviator. 259 */ 260 private static final class PatternAbbreviatorFragment { 261 /** 262 * Count of initial characters of element to output. 263 */ 264 private final int charCount; 265 266 /** 267 * Character used to represent dropped characters. 268 * '\0' indicates no representation of dropped characters. 269 */ 270 private final char ellipsis; 271 272 /** 273 * Creates a PatternAbbreviatorFragment. 274 * 275 * @param charCount number of initial characters to preserve. 276 * @param ellipsis character to represent elimination of characters, 277 * '\0' if no ellipsis is desired. 278 */ 279 PatternAbbreviatorFragment( 280 final int charCount, final char ellipsis) { 281 this.charCount = charCount; 282 this.ellipsis = ellipsis; 283 } 284 285 /** 286 * Abbreviate element of name. 287 * 288 * @param input input string which is being written to the output {@code buf}. 289 * @param inputIndex starting index of name element in the {@code input} string. 290 * @param buf buffer to receive element. 291 * @return starting index of next element. 292 */ 293 int abbreviate(final String input, final int inputIndex, final StringBuilder buf) { 294 // Note that indexOf(char) performs worse than indexOf(String) on pre-16 JREs 295 // due to missing intrinsics for the character implementation. The difference 296 // is a few nanoseconds in most cases, so we opt to give the jre as much 297 // information as possible for best performance on new runtimes, with the 298 // possibility that such optimizations may be back-ported. 299 // See https://bugs.openjdk.java.net/browse/JDK-8173585 300 int nextDot = input.indexOf('.', inputIndex); 301 if (nextDot < 0) { 302 buf.append(input, inputIndex, input.length()); 303 return nextDot; 304 } 305 if (nextDot - inputIndex > charCount) { 306 buf.append(input, inputIndex, inputIndex + charCount); 307 if (ellipsis != '\0') { 308 buf.append(ellipsis); 309 } 310 buf.append('.'); 311 } else { 312 // Include the period to reduce interactions with the buffer 313 buf.append(input, inputIndex, nextDot + 1); 314 } 315 return nextDot + 1; 316 } 317 } 318 319 /** 320 * Pattern abbreviator. 321 */ 322 private static final class PatternAbbreviator extends NameAbbreviator { 323 /** 324 * Element abbreviation patterns. 325 */ 326 private final PatternAbbreviatorFragment[] fragments; 327 328 /** 329 * Create PatternAbbreviator. 330 * 331 * @param fragments element abbreviation patterns. 332 */ 333 PatternAbbreviator(final List<PatternAbbreviatorFragment> fragments) { 334 if (fragments.isEmpty()) { 335 throw new IllegalArgumentException( 336 "fragments must have at least one element"); 337 } 338 339 this.fragments = fragments.toArray(new PatternAbbreviatorFragment[0]); 340 } 341 342 /** 343 * Abbreviates name. 344 * 345 * @param original the original string to abbreviate 346 * @param destination buffer that abbreviated name is appended to 347 */ 348 @Override 349 public void abbreviate(final String original, final StringBuilder destination) { 350 // non-terminal patterns are executed once 351 int originalIndex = 0; 352 int iteration = 0; 353 int originalLength = original.length(); 354 while (originalIndex >= 0 && originalIndex < originalLength) { 355 originalIndex = fragment(iteration++).abbreviate(original, originalIndex, destination); 356 } 357 } 358 359 PatternAbbreviatorFragment fragment(int index) { 360 return fragments[Math.min(index, fragments.length - 1)]; 361 } 362 } 363}