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