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 }