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 int i = 0;
56
57 while (i < trimmed.length() && trimmed.charAt(i) >= '0'
58 && trimmed.charAt(i) <= '9') {
59 i++;
60 }
61
62 //
63 // if all blanks and digits
64 //
65 if (i == trimmed.length()) {
66 return new MaxElementAbbreviator(Integer.parseInt(trimmed));
67 }
68
69 final ArrayList<PatternAbbreviatorFragment> fragments = new ArrayList<PatternAbbreviatorFragment>(5);
70 char ellipsis;
71 int charCount;
72 int pos = 0;
73
74 while (pos < trimmed.length() && pos >= 0) {
75 int ellipsisPos = pos;
76
77 if (trimmed.charAt(pos) == '*') {
78 charCount = Integer.MAX_VALUE;
79 ellipsisPos++;
80 } else {
81 if (trimmed.charAt(pos) >= '0' && trimmed.charAt(pos) <= '9') {
82 charCount = trimmed.charAt(pos) - '0';
83 ellipsisPos++;
84 } else {
85 charCount = 0;
86 }
87 }
88
89 ellipsis = '\0';
90
91 if (ellipsisPos < trimmed.length()) {
92 ellipsis = trimmed.charAt(ellipsisPos);
93
94 if (ellipsis == '.') {
95 ellipsis = '\0';
96 }
97 }
98
99 fragments.add(new PatternAbbreviatorFragment(charCount, ellipsis));
100 pos = trimmed.indexOf('.', pos);
101
102 if (pos == -1) {
103 break;
104 }
105
106 pos++;
107 }
108
109 return new PatternAbbreviator(fragments);
110 }
111
112 //
113 // no matching abbreviation, return defaultAbbreviator
114 //
115 return DEFAULT;
116 }
117
118 /**
119 * Gets default abbreviator.
120 *
121 * @return default abbreviator.
122 */
123 public static NameAbbreviator getDefaultAbbreviator() {
124 return DEFAULT;
125 }
126
127 /**
128 * Abbreviates a name in a String.
129 *
130 * @param buf buffer, may not be null.
131 * @return The abbreviated String.
132 */
133 public abstract String abbreviate(final String buf);
134
135 /**
136 * Abbreviator that simply appends full name to buffer.
137 */
138 private static class NOPAbbreviator extends NameAbbreviator {
139 /**
140 * Constructor.
141 */
142 public NOPAbbreviator() {
143 }
144
145 /**
146 * {@inheritDoc}
147 */
148 @Override
149 public String abbreviate(final String buf) {
150 return buf;
151 }
152 }
153
154 /**
155 * Abbreviator that drops starting path elements.
156 */
157 private static class MaxElementAbbreviator extends NameAbbreviator {
158 /**
159 * Maximum number of path elements to output.
160 */
161 private final int count;
162
163 /**
164 * Create new instance.
165 *
166 * @param count maximum number of path elements to output.
167 */
168 public MaxElementAbbreviator(final int count) {
169 this.count = count < 1 ? 1 : count;
170 }
171
172 /**
173 * Abbreviate name.
174 *
175 * @param buf The String to abbreviate.
176 * @return the abbreviated String.
177 */
178 @Override
179 public String abbreviate(final String buf) {
180
181 // We subtract 1 from 'len' when assigning to 'end' to avoid out of
182 // bounds exception in return r.substring(end+1, len). This can happen if
183 // precision is 1 and the category name ends with a dot.
184 int end = buf.length() - 1;
185
186 for (int i = count; i > 0; i--) {
187 end = buf.lastIndexOf('.', end - 1);
188 if (end == -1) {
189 return buf;
190 }
191 }
192
193 return buf.substring(end + 1);
194 }
195 }
196
197 /**
198 * Fragment of an pattern abbreviator.
199 */
200 private static class PatternAbbreviatorFragment {
201 /**
202 * Count of initial characters of element to output.
203 */
204 private final int charCount;
205
206 /**
207 * Character used to represent dropped characters.
208 * '\0' indicates no representation of dropped characters.
209 */
210 private final char ellipsis;
211
212 /**
213 * Creates a PatternAbbreviatorFragment.
214 *
215 * @param charCount number of initial characters to preserve.
216 * @param ellipsis character to represent elimination of characters,
217 * '\0' if no ellipsis is desired.
218 */
219 public PatternAbbreviatorFragment(
220 final int charCount, final char ellipsis) {
221 this.charCount = charCount;
222 this.ellipsis = ellipsis;
223 }
224
225 /**
226 * Abbreviate element of name.
227 *
228 * @param buf buffer to receive element.
229 * @param startPos starting index of name element.
230 * @return starting index of next element.
231 */
232 public int abbreviate(final StringBuilder buf, final int startPos) {
233 int nextDot = buf.toString().indexOf('.', startPos);
234
235 if (nextDot != -1) {
236 if (nextDot - startPos > charCount) {
237 buf.delete(startPos + charCount, nextDot);
238 nextDot = startPos + charCount;
239
240 if (ellipsis != '\0') {
241 buf.insert(nextDot, ellipsis);
242 nextDot++;
243 }
244 }
245
246 nextDot++;
247 }
248
249 return nextDot;
250 }
251 }
252
253 /**
254 * Pattern abbreviator.
255 */
256 private static class PatternAbbreviator extends NameAbbreviator {
257 /**
258 * Element abbreviation patterns.
259 */
260 private final PatternAbbreviatorFragment[] fragments;
261
262 /**
263 * Create PatternAbbreviator.
264 *
265 * @param fragments element abbreviation patterns.
266 */
267 public PatternAbbreviator(final List<PatternAbbreviatorFragment> fragments) {
268 if (fragments.isEmpty()) {
269 throw new IllegalArgumentException(
270 "fragments must have at least one element");
271 }
272
273 this.fragments = new PatternAbbreviatorFragment[fragments.size()];
274 fragments.toArray(this.fragments);
275 }
276
277 /**
278 * Abbreviates name.
279 *
280 * @param buf buffer that abbreviated name is appended.
281 */
282 @Override
283 public String abbreviate(final String buf) {
284 //
285 // all non-terminal patterns are executed once
286 //
287 int pos = 0;
288 final StringBuilder sb = new StringBuilder(buf);
289
290 for (int i = 0; i < fragments.length - 1 && pos < buf.length();
291 i++) {
292 pos = fragments[i].abbreviate(sb, pos);
293 }
294
295 //
296 // last pattern in executed repeatedly
297 //
298 final PatternAbbreviatorFragment terminalFragment = fragments[fragments.length - 1];
299
300 while (pos < buf.length() && pos >= 0) {
301 pos = terminalFragment.abbreviate(sb, pos);
302 }
303 return sb.toString();
304 }
305 }
306 }