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 { 097 if (trimmed.charAt(pos) >= '0' && trimmed.charAt(pos) <= '9') { 098 charCount = trimmed.charAt(pos) - '0'; 099 ellipsisPos++; 100 } else { 101 charCount = 0; 102 } 103 } 104 105 ellipsis = '\0'; 106 107 if (ellipsisPos < trimmed.length()) { 108 ellipsis = trimmed.charAt(ellipsisPos); 109 110 if (ellipsis == '.') { 111 ellipsis = '\0'; 112 } 113 } 114 115 fragments.add(new PatternAbbreviatorFragment(charCount, ellipsis)); 116 pos = trimmed.indexOf('.', pos); 117 118 if (pos == -1) { 119 break; 120 } 121 122 pos++; 123 } 124 125 return new PatternAbbreviator(fragments); 126 } 127 128 // 129 // no matching abbreviation, return defaultAbbreviator 130 // 131 return DEFAULT; 132 } 133 134 /** 135 * Gets default abbreviator. 136 * 137 * @return default abbreviator. 138 */ 139 public static NameAbbreviator getDefaultAbbreviator() { 140 return DEFAULT; 141 } 142 143 /** 144 * Abbreviates a name in a String. 145 * 146 * @param original the text to abbreviate, may not be null. 147 * @param destination StringBuilder to write the result to 148 */ 149 public abstract void abbreviate(final String original, final StringBuilder destination); 150 151 /** 152 * Abbreviator that simply appends full name to buffer. 153 */ 154 private static class NOPAbbreviator extends NameAbbreviator { 155 /** 156 * Constructor. 157 */ 158 public NOPAbbreviator() { 159 } 160 161 /** 162 * {@inheritDoc} 163 */ 164 @Override 165 public void abbreviate(final String original, final StringBuilder destination) { 166 destination.append(original); 167 } 168 } 169 170 /** 171 * Abbreviator that drops starting path elements. 172 */ 173 private static class MaxElementAbbreviator extends NameAbbreviator { 174 175 /** 176 * <p>When the name is reduced in length by cutting parts, there can be two ways to do it.</p> 177 * 1. Remove a given number of parts starting from front - called DROP <br/> 178 * 2. Retain a given number of parts starting from the end - called RETAIN 179 */ 180 private enum Strategy { 181 DROP(0) { 182 @Override 183 void abbreviate(final int count, final String original, final StringBuilder destination) { 184 // If a path does not contain enough path elements to drop, none will be dropped. 185 int start = 0; 186 int nextStart; 187 for (int i = 0; i < count; i++) { 188 nextStart = original.indexOf('.', start); 189 if (nextStart == -1) { 190 destination.append(original); 191 return; 192 } 193 start = nextStart + 1; 194 } 195 destination.append(original, start, original.length()); 196 } 197 }, 198 RETAIN(1) { 199 @Override 200 void abbreviate(final int count, final String original, final StringBuilder destination) { 201 // We subtract 1 from 'len' when assigning to 'end' to avoid out of 202 // bounds exception in return r.substring(end+1, len). This can happen if 203 // precision is 1 and the category name ends with a dot. 204 int end = original.length() - 1; 205 206 for (int i = count; i > 0; i--) { 207 end = original.lastIndexOf('.', end - 1); 208 if (end == -1) { 209 destination.append(original); 210 return; 211 } 212 } 213 destination.append(original, end + 1, original.length()); 214 } 215 }; 216 217 final int minCount; 218 219 Strategy(final int minCount) { 220 this.minCount = minCount; 221 } 222 223 abstract void abbreviate(final int count, final String original, final StringBuilder destination); 224 } 225 226 /** 227 * Maximum number of path elements to output. 228 */ 229 private final int count; 230 231 /** 232 * Strategy used for cutting down the size of the name 233 */ 234 private final Strategy strategy; 235 236 /** 237 * Create new instance. 238 * 239 * @param count maximum number of path elements to drop or output. 240 * @param strategy drop or retain 241 */ 242 public MaxElementAbbreviator(final int count, final Strategy strategy) { 243 this.count = Math.max(count, strategy.minCount); 244 this.strategy = strategy; 245 } 246 247 /** 248 * Abbreviate name. 249 * 250 * @param original The String to abbreviate. 251 * @param destination the buffer to write the abbreviated name into 252 */ 253 @Override 254 public void abbreviate(final String original, final StringBuilder destination) { 255 strategy.abbreviate(count, original, destination); 256 } 257 } 258 259 /** 260 * Fragment of an pattern abbreviator. 261 */ 262 private static class PatternAbbreviatorFragment { 263 /** 264 * Count of initial characters of element to output. 265 */ 266 private final int charCount; 267 268 /** 269 * Character used to represent dropped characters. 270 * '\0' indicates no representation of dropped characters. 271 */ 272 private final char ellipsis; 273 274 /** 275 * Creates a PatternAbbreviatorFragment. 276 * 277 * @param charCount number of initial characters to preserve. 278 * @param ellipsis character to represent elimination of characters, 279 * '\0' if no ellipsis is desired. 280 */ 281 public PatternAbbreviatorFragment( 282 final int charCount, final char ellipsis) { 283 this.charCount = charCount; 284 this.ellipsis = ellipsis; 285 } 286 287 /** 288 * Abbreviate element of name. 289 * 290 * @param buf buffer to receive element. 291 * @param startPos starting index of name element. 292 * @return starting index of next element. 293 */ 294 public int abbreviate(final StringBuilder buf, final int startPos) { 295 final int start = (startPos < 0) ? 0 : startPos; 296 final int max = buf.length(); 297 int nextDot = -1; 298 for (int i = start; i < max; i++) { 299 if (buf.charAt(i) == '.') { 300 nextDot = i; 301 break; 302 } 303 } 304 if (nextDot != -1) { 305 if (nextDot - startPos > charCount) { 306 buf.delete(startPos + charCount, nextDot); 307 nextDot = startPos + charCount; 308 309 if (ellipsis != '\0') { 310 buf.insert(nextDot, ellipsis); 311 nextDot++; 312 } 313 } 314 nextDot++; 315 } 316 return nextDot; 317 } 318 } 319 320 /** 321 * Pattern abbreviator. 322 */ 323 private static class PatternAbbreviator extends NameAbbreviator { 324 /** 325 * Element abbreviation patterns. 326 */ 327 private final PatternAbbreviatorFragment[] fragments; 328 329 /** 330 * Create PatternAbbreviator. 331 * 332 * @param fragments element abbreviation patterns. 333 */ 334 public PatternAbbreviator(final List<PatternAbbreviatorFragment> fragments) { 335 if (fragments.isEmpty()) { 336 throw new IllegalArgumentException( 337 "fragments must have at least one element"); 338 } 339 340 this.fragments = new PatternAbbreviatorFragment[fragments.size()]; 341 fragments.toArray(this.fragments); 342 } 343 344 /** 345 * Abbreviates name. 346 * 347 * @param original the original string to abbreviate 348 * @param destination buffer that abbreviated name is appended to 349 */ 350 @Override 351 public void abbreviate(final String original, final StringBuilder destination) { 352 // 353 // all non-terminal patterns are executed once 354 // 355 int pos = destination.length(); 356 final int max = pos + original.length(); 357 358 destination.append(original); 359 360 int fragmentIndex = 0; 361 while (pos < max && pos >= 0) { 362 pos = fragments[fragmentIndex].abbreviate(destination, pos); 363 // last pattern in executed repeatedly 364 if (fragmentIndex < fragments.length - 1) { 365 fragmentIndex++; 366 } 367 } 368 } 369 } 370}