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