View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.pattern;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  
22  import org.apache.logging.log4j.util.PerformanceSensitive;
23  
24  
25  /**
26   * NameAbbreviator generates abbreviated logger and class names.
27   */
28  @PerformanceSensitive("allocation")
29  public abstract class NameAbbreviator {
30      /**
31       * Default (no abbreviation) abbreviator.
32       */
33      private static final NameAbbreviator DEFAULT = new NOPAbbreviator();
34  
35      /**
36       * Gets an abbreviator.
37       * <p>
38       * For example, "%logger{2}" will output only 2 elements of the logger name, "%logger{1.}" will output only the
39       * first character of the non-final elements in the name, "%logger(1~.2~} will output the first character of the
40       * first element, two characters of the second and subsequent elements and will use a tilde to indicate abbreviated
41       * characters.
42       * </p>
43       *
44       * @param pattern
45       *        abbreviation pattern.
46       * @return abbreviator, will not be null.
47       */
48      public static NameAbbreviator getAbbreviator(final String pattern) {
49          if (pattern.length() > 0) {
50              //  if pattern is just spaces and numbers then
51              //     use MaxElementAbbreviator
52              final String trimmed = pattern.trim();
53  
54              if (trimmed.isEmpty()) {
55                  return DEFAULT;
56              }
57  
58              boolean isNegativeNumber;
59              final String number;
60  
61              // check if number is a negative number
62              if (trimmed.length() > 1 && trimmed.charAt(0) == '-') {
63                  isNegativeNumber = true;
64                  number = trimmed.substring(1);
65              } else {
66                  isNegativeNumber = false;
67                  number = trimmed;
68              }
69  
70              int i = 0;
71  
72              while (i < number.length() && number.charAt(i) >= '0'
73                      && number.charAt(i) <= '9') {
74                  i++;
75              }
76  
77              //
78              //  if all blanks and digits
79              //
80              if (i == number.length()) {
81                  return new MaxElementAbbreviator(Integer.parseInt(number),
82                          isNegativeNumber? MaxElementAbbreviator.Strategy.DROP : MaxElementAbbreviator.Strategy.RETAIN);
83              }
84  
85              final ArrayList<PatternAbbreviatorFragment> fragments = new ArrayList<>(5);
86              char ellipsis;
87              int charCount;
88              int pos = 0;
89  
90              while (pos < trimmed.length() && pos >= 0) {
91                  int ellipsisPos = pos;
92  
93                  if (trimmed.charAt(pos) == '*') {
94                      charCount = Integer.MAX_VALUE;
95                      ellipsisPos++;
96                  } else {
97                      if (trimmed.charAt(pos) >= '0' && trimmed.charAt(pos) <= '9') {
98                          charCount = trimmed.charAt(pos) - '0';
99                          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 }