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  
23  /**
24   * NameAbbreviator generates abbreviated logger and class names.
25   */
26  public abstract class NameAbbreviator {
27      /**
28       * Default (no abbreviation) abbreviator.
29       */
30      private static final NameAbbreviator DEFAULT = new NOPAbbreviator();
31  
32      /**
33       * Gets an abbreviator.
34       * <p>
35       * For example, "%logger{2}" will output only 2 elements of the logger name, "%logger{1.}" will output only the
36       * first character of the non-final elements in the name, "%logger(1~.2~} will output the first character of the
37       * first element, two characters of the second and subsequent elements and will use a tilde to indicate abbreviated
38       * characters.
39       * </p>
40       *
41       * @param pattern
42       *        abbreviation pattern.
43       * @return abbreviator, will not be null.
44       */
45      public static NameAbbreviator getAbbreviator(final String pattern) {
46          if (pattern.length() > 0) {
47              //  if pattern is just spaces and numbers then
48              //     use MaxElementAbbreviator
49              final String trimmed = pattern.trim();
50  
51              if (trimmed.isEmpty()) {
52                  return DEFAULT;
53              }
54  
55              boolean isNegativeNumber;
56              final String number;
57  
58              // check if number is a negative number
59              if (trimmed.length() > 1 && trimmed.charAt(0) == '-') {
60                  isNegativeNumber = true;
61                  number = trimmed.substring(1);
62              } else {
63                  isNegativeNumber = false;
64                  number = trimmed;
65              }
66  
67              int i = 0;
68  
69              while (i < number.length() && number.charAt(i) >= '0'
70                      && number.charAt(i) <= '9') {
71                  i++;
72              }
73  
74              //
75              //  if all blanks and digits
76              //
77              if (i == number.length()) {
78                  return new MaxElementAbbreviator(Integer.parseInt(number),
79                          isNegativeNumber? MaxElementAbbreviator.Strategy.DROP : MaxElementAbbreviator.Strategy.RETAIN);
80              }
81  
82              final ArrayList<PatternAbbreviatorFragment> fragments = new ArrayList<>(5);
83              char ellipsis;
84              int charCount;
85              int pos = 0;
86  
87              while (pos < trimmed.length() && pos >= 0) {
88                  int ellipsisPos = pos;
89  
90                  if (trimmed.charAt(pos) == '*') {
91                      charCount = Integer.MAX_VALUE;
92                      ellipsisPos++;
93                  } else {
94                      if (trimmed.charAt(pos) >= '0' && trimmed.charAt(pos) <= '9') {
95                          charCount = trimmed.charAt(pos) - '0';
96                          ellipsisPos++;
97                      } else {
98                          charCount = 0;
99                      }
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(final String original, final 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(final String original, final 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 }