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 022 023/** 024 * NameAbbreviator generates abbreviated logger and class names. 025 */ 026public abstract class NameAbbreviator { 027 /** 028 * Default (no abbreviation) abbreviator. 029 */ 030 private static final NameAbbreviator DEFAULT = new NOPAbbreviator(); 031 032 /** 033 * Gets an abbreviator. 034 * <p> 035 * For example, "%logger{2}" will output only 2 elements of the logger name, "%logger{1.}" will output only the 036 * first character of the non-final elements in the name, "%logger(1~.2~} will output the first character of the 037 * first element, two characters of the second and subsequent elements and will use a tilde to indicate abbreviated 038 * characters. 039 * </p> 040 * 041 * @param pattern 042 * abbreviation pattern. 043 * @return abbreviator, will not be null. 044 */ 045 public static NameAbbreviator getAbbreviator(final String pattern) { 046 if (pattern.length() > 0) { 047 // if pattern is just spaces and numbers then 048 // use MaxElementAbbreviator 049 final String trimmed = pattern.trim(); 050 051 if (trimmed.isEmpty()) { 052 return DEFAULT; 053 } 054 055 boolean isNegativeNumber; 056 final String number; 057 058 // check if number is a negative number 059 if (trimmed.length() > 1 && trimmed.charAt(0) == '-') { 060 isNegativeNumber = true; 061 number = trimmed.substring(1); 062 } else { 063 isNegativeNumber = false; 064 number = trimmed; 065 } 066 067 int i = 0; 068 069 while (i < number.length() && number.charAt(i) >= '0' 070 && number.charAt(i) <= '9') { 071 i++; 072 } 073 074 // 075 // if all blanks and digits 076 // 077 if (i == number.length()) { 078 return new MaxElementAbbreviator(Integer.parseInt(number), 079 isNegativeNumber? MaxElementAbbreviator.Strategy.DROP : MaxElementAbbreviator.Strategy.RETAIN); 080 } 081 082 final ArrayList<PatternAbbreviatorFragment> fragments = new ArrayList<>(5); 083 char ellipsis; 084 int charCount; 085 int pos = 0; 086 087 while (pos < trimmed.length() && pos >= 0) { 088 int ellipsisPos = pos; 089 090 if (trimmed.charAt(pos) == '*') { 091 charCount = Integer.MAX_VALUE; 092 ellipsisPos++; 093 } else { 094 if (trimmed.charAt(pos) >= '0' && trimmed.charAt(pos) <= '9') { 095 charCount = trimmed.charAt(pos) - '0'; 096 ellipsisPos++; 097 } else { 098 charCount = 0; 099 } 100 } 101 102 ellipsis = '\0'; 103 104 if (ellipsisPos < trimmed.length()) { 105 ellipsis = trimmed.charAt(ellipsisPos); 106 107 if (ellipsis == '.') { 108 ellipsis = '\0'; 109 } 110 } 111 112 fragments.add(new PatternAbbreviatorFragment(charCount, ellipsis)); 113 pos = trimmed.indexOf('.', pos); 114 115 if (pos == -1) { 116 break; 117 } 118 119 pos++; 120 } 121 122 return new PatternAbbreviator(fragments); 123 } 124 125 // 126 // no matching abbreviation, return defaultAbbreviator 127 // 128 return DEFAULT; 129 } 130 131 /** 132 * Gets default abbreviator. 133 * 134 * @return default abbreviator. 135 */ 136 public static NameAbbreviator getDefaultAbbreviator() { 137 return DEFAULT; 138 } 139 140 /** 141 * Abbreviates a name in a String. 142 * 143 * @param original the text to abbreviate, may not be null. 144 * @param destination StringBuilder to write the result to 145 * @return The abbreviated String. 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, 180 RETAIN 181 }; 182 183 /** 184 * Maximum number of path elements to output. 185 */ 186 private final int count; 187 188 /** 189 * Strategy used for cutting down the size of the name 190 */ 191 private final Strategy strategy; 192 193 /** 194 * Create new instance. 195 * 196 * @param count maximum number of path elements to drop or output. 197 * @param strategy drop or retain 198 */ 199 public MaxElementAbbreviator(final int count, final Strategy strategy) { 200 final int minCount = getMinCount(strategy); 201 this.count = count < minCount ? minCount : count; 202 this.strategy = strategy; 203 } 204 205 private int getMinCount(final Strategy strategy) { 206 if (Strategy.DROP == strategy) { 207 return 0; 208 } else { // Strategy.RETAIN 209 return 1; 210 } 211 } 212 213 /** 214 * Abbreviate name. 215 * 216 * @param original The String to abbreviate. 217 * @param destination 218 * @return the abbreviated String. 219 */ 220 @Override 221 public void abbreviate(final String original, final StringBuilder destination) { 222 if (Strategy.DROP == strategy) { 223 abbreviateForDrop(original, destination); 224 } else { // Strategy.RETAIN 225 abbreviateForRetain(original, destination); 226 } 227 } 228 229 private void abbreviateForDrop(String original, StringBuilder destination) { 230 // If a path does not contain enough path elements to drop, none will be dropped. 231 int start = 0; 232 int nextStart = 0; 233 for (int i = 0; i < count; i++) { 234 nextStart = original.indexOf('.', start); 235 if (nextStart == -1) { 236 destination.append(original); 237 return; 238 } else { 239 start = nextStart + 1; 240 } 241 } 242 destination.append(original, start, original.length()); 243 } 244 245 private void abbreviateForRetain(String original, StringBuilder destination) { 246 // We subtract 1 from 'len' when assigning to 'end' to avoid out of 247 // bounds exception in return r.substring(end+1, len). This can happen if 248 // precision is 1 and the category name ends with a dot. 249 int end = original.length() - 1; 250 251 for (int i = count; i > 0; i--) { 252 end = original.lastIndexOf('.', end - 1); 253 if (end == -1) { 254 destination.append(original); 255 return; 256 } 257 } 258 destination.append(original, end + 1, original.length()); 259 } 260 } 261 262 /** 263 * Fragment of an pattern abbreviator. 264 */ 265 private static class PatternAbbreviatorFragment { 266 /** 267 * Count of initial characters of element to output. 268 */ 269 private final int charCount; 270 271 /** 272 * Character used to represent dropped characters. 273 * '\0' indicates no representation of dropped characters. 274 */ 275 private final char ellipsis; 276 277 /** 278 * Creates a PatternAbbreviatorFragment. 279 * 280 * @param charCount number of initial characters to preserve. 281 * @param ellipsis character to represent elimination of characters, 282 * '\0' if no ellipsis is desired. 283 */ 284 public PatternAbbreviatorFragment( 285 final int charCount, final char ellipsis) { 286 this.charCount = charCount; 287 this.ellipsis = ellipsis; 288 } 289 290 /** 291 * Abbreviate element of name. 292 * 293 * @param buf buffer to receive element. 294 * @param startPos starting index of name element. 295 * @return starting index of next element. 296 */ 297 public int abbreviate(final StringBuilder buf, final int startPos) { 298 final int start = (startPos < 0) ? 0 : startPos; 299 final int max = buf.length(); 300 int nextDot = -1; 301 for (int i = start; i < max; i++) { 302 if (buf.charAt(i) == '.') { 303 nextDot = i; 304 break; 305 } 306 } 307 if (nextDot != -1) { 308 if (nextDot - startPos > charCount) { 309 buf.delete(startPos + charCount, nextDot); 310 nextDot = startPos + charCount; 311 312 if (ellipsis != '\0') { 313 buf.insert(nextDot, ellipsis); 314 nextDot++; 315 } 316 } 317 nextDot++; 318 } 319 return nextDot; 320 } 321 } 322 323 /** 324 * Pattern abbreviator. 325 */ 326 private static class PatternAbbreviator extends NameAbbreviator { 327 /** 328 * Element abbreviation patterns. 329 */ 330 private final PatternAbbreviatorFragment[] fragments; 331 332 /** 333 * Create PatternAbbreviator. 334 * 335 * @param fragments element abbreviation patterns. 336 */ 337 public PatternAbbreviator(final List<PatternAbbreviatorFragment> fragments) { 338 if (fragments.isEmpty()) { 339 throw new IllegalArgumentException( 340 "fragments must have at least one element"); 341 } 342 343 this.fragments = new PatternAbbreviatorFragment[fragments.size()]; 344 fragments.toArray(this.fragments); 345 } 346 347 /** 348 * Abbreviates name. 349 * 350 * @param original buffer that abbreviated name is appended. 351 * @param destination 352 */ 353 @Override 354 public void abbreviate(final String original, final StringBuilder destination) { 355 // 356 // all non-terminal patterns are executed once 357 // 358 int pos = destination.length(); 359 final int max = pos + original.length(); 360 final StringBuilder sb = destination.append(original);//new StringBuilder(original); 361 362 for (int i = 0; i < fragments.length - 1 && pos < original.length(); i++) { 363 pos = fragments[i].abbreviate(sb, pos); 364 } 365 366 // 367 // last pattern in executed repeatedly 368 // 369 final PatternAbbreviatorFragment terminalFragment = fragments[fragments.length - 1]; 370 371 while (pos < max && pos >= 0) { 372 pos = terminalFragment.abbreviate(sb, pos); 373 } 374 } 375 } 376}