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