001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.pattern;
018
019import java.util.ArrayList;
020import java.util.List;
021
022import org.apache.logging.log4j.util.PerformanceSensitive;
023
024
025/**
026 * NameAbbreviator generates abbreviated logger and class names.
027 */
028@PerformanceSensitive("allocation")
029public abstract class NameAbbreviator {
030    /**
031     * Default (no abbreviation) abbreviator.
032     */
033    private static final NameAbbreviator DEFAULT = new NOPAbbreviator();
034
035    /**
036     * Gets an abbreviator.
037     * <p>
038     * For example, "%logger{2}" will output only 2 elements of the logger name, "%logger{1.}" will output only the
039     * first character of the non-final elements in the name, "%logger(1~.2~} will output the first character of the
040     * first element, two characters of the second and subsequent elements and will use a tilde to indicate abbreviated
041     * characters.
042     * </p>
043     *
044     * @param pattern
045     *        abbreviation pattern.
046     * @return abbreviator, will not be null.
047     */
048    public static NameAbbreviator getAbbreviator(final String pattern) {
049        if (pattern.length() > 0) {
050            //  if pattern is just spaces and numbers then
051            //     use MaxElementAbbreviator
052            final String trimmed = pattern.trim();
053
054            if (trimmed.isEmpty()) {
055                return DEFAULT;
056            }
057
058            boolean isNegativeNumber;
059            final String number;
060
061            // check if number is a negative number
062            if (trimmed.length() > 1 && trimmed.charAt(0) == '-') {
063                isNegativeNumber = true;
064                number = trimmed.substring(1);
065            } else {
066                isNegativeNumber = false;
067                number = trimmed;
068            }
069
070            int i = 0;
071
072            while (i < number.length() && number.charAt(i) >= '0'
073                    && number.charAt(i) <= '9') {
074                i++;
075            }
076
077            //
078            //  if all blanks and digits
079            //
080            if (i == number.length()) {
081                return new MaxElementAbbreviator(Integer.parseInt(number),
082                        isNegativeNumber? MaxElementAbbreviator.Strategy.DROP : MaxElementAbbreviator.Strategy.RETAIN);
083            }
084
085            final ArrayList<PatternAbbreviatorFragment> fragments = new ArrayList<>(5);
086            char ellipsis;
087            int charCount;
088            int pos = 0;
089
090            while (pos < trimmed.length() && pos >= 0) {
091                int ellipsisPos = pos;
092
093                if (trimmed.charAt(pos) == '*') {
094                    charCount = Integer.MAX_VALUE;
095                    ellipsisPos++;
096                } else {
097                    if (trimmed.charAt(pos) >= '0' && trimmed.charAt(pos) <= '9') {
098                        charCount = trimmed.charAt(pos) - '0';
099                        ellipsisPos++;
100                    } else {
101                        charCount = 0;
102                    }
103                }
104
105                ellipsis = '\0';
106
107                if (ellipsisPos < trimmed.length()) {
108                    ellipsis = trimmed.charAt(ellipsisPos);
109
110                    if (ellipsis == '.') {
111                        ellipsis = '\0';
112                    }
113                }
114
115                fragments.add(new PatternAbbreviatorFragment(charCount, ellipsis));
116                pos = trimmed.indexOf('.', pos);
117
118                if (pos == -1) {
119                    break;
120                }
121
122                pos++;
123            }
124
125            return new PatternAbbreviator(fragments);
126        }
127
128        //
129        //  no matching abbreviation, return defaultAbbreviator
130        //
131        return DEFAULT;
132    }
133
134    /**
135     * Gets default abbreviator.
136     *
137     * @return default abbreviator.
138     */
139    public static NameAbbreviator getDefaultAbbreviator() {
140        return DEFAULT;
141    }
142
143    /**
144     * Abbreviates a name in a String.
145     *
146     * @param original the text to abbreviate, may not be null.
147     * @param destination StringBuilder to write the result to
148     */
149    public abstract void abbreviate(final String original, final StringBuilder destination);
150
151    /**
152     * Abbreviator that simply appends full name to buffer.
153     */
154    private static class NOPAbbreviator extends NameAbbreviator {
155        /**
156         * Constructor.
157         */
158        public NOPAbbreviator() {
159        }
160
161        /**
162         * {@inheritDoc}
163         */
164        @Override
165        public void abbreviate(final String original, final StringBuilder destination) {
166            destination.append(original);
167        }
168    }
169
170    /**
171     * Abbreviator that drops starting path elements.
172     */
173    private static class MaxElementAbbreviator extends NameAbbreviator {
174
175        /**
176         * <p>When the name is reduced in length by cutting parts, there can be two ways to do it.</p>
177         * 1. Remove a given number of parts starting from front - called DROP <br/>
178         * 2. Retain a given number of parts starting from the end - called RETAIN
179         */
180        private enum Strategy {
181            DROP(0) {
182                @Override
183                void abbreviate(final int count, final String original, final StringBuilder destination) {
184                    // If a path does not contain enough path elements to drop, none will be dropped.
185                    int start = 0;
186                    int nextStart;
187                    for (int i = 0; i < count; i++) {
188                        nextStart = original.indexOf('.', start);
189                        if (nextStart == -1) {
190                            destination.append(original);
191                            return;
192                        }
193                        start = nextStart + 1;
194                    }
195                    destination.append(original, start, original.length());
196                }
197            },
198            RETAIN(1) {
199                @Override
200                void abbreviate(final int count, final String original, final StringBuilder destination) {
201                    // We subtract 1 from 'len' when assigning to 'end' to avoid out of
202                    // bounds exception in return r.substring(end+1, len). This can happen if
203                    // precision is 1 and the category name ends with a dot.
204                    int end = original.length() - 1;
205
206                    for (int i = count; i > 0; i--) {
207                        end = original.lastIndexOf('.', end - 1);
208                        if (end == -1) {
209                            destination.append(original);
210                            return;
211                        }
212                    }
213                    destination.append(original, end + 1, original.length());
214                }
215            };
216
217            final int minCount;
218
219            Strategy(final int minCount) {
220                this.minCount = minCount;
221            }
222
223            abstract void abbreviate(final int count, final String original, final StringBuilder destination);
224        }
225
226        /**
227         * Maximum number of path elements to output.
228         */
229        private final int count;
230
231        /**
232         * Strategy used for cutting down the size of the name
233         */
234        private final Strategy strategy;
235
236        /**
237         * Create new instance.
238         *
239         * @param count maximum number of path elements to drop or output.
240         * @param strategy drop or retain
241         */
242        public MaxElementAbbreviator(final int count, final Strategy strategy) {
243            this.count = Math.max(count, strategy.minCount);
244            this.strategy = strategy;
245        }
246
247        /**
248         * Abbreviate name.
249         *
250         * @param original The String to abbreviate.
251         * @param destination the buffer to write the abbreviated name into
252         */
253        @Override
254        public void abbreviate(final String original, final StringBuilder destination) {
255            strategy.abbreviate(count, original, destination);
256        }
257    }
258
259    /**
260     * Fragment of an pattern abbreviator.
261     */
262    private static class PatternAbbreviatorFragment {
263        /**
264         * Count of initial characters of element to output.
265         */
266        private final int charCount;
267
268        /**
269         * Character used to represent dropped characters.
270         * '\0' indicates no representation of dropped characters.
271         */
272        private final char ellipsis;
273
274        /**
275         * Creates a PatternAbbreviatorFragment.
276         *
277         * @param charCount number of initial characters to preserve.
278         * @param ellipsis  character to represent elimination of characters,
279         *                  '\0' if no ellipsis is desired.
280         */
281        public PatternAbbreviatorFragment(
282            final int charCount, final char ellipsis) {
283            this.charCount = charCount;
284            this.ellipsis = ellipsis;
285        }
286
287        /**
288         * Abbreviate element of name.
289         *
290         * @param buf      buffer to receive element.
291         * @param startPos starting index of name element.
292         * @return starting index of next element.
293         */
294        public int abbreviate(final StringBuilder buf, final int startPos) {
295            final int start = (startPos < 0) ? 0 : startPos;
296            final int max = buf.length();
297            int nextDot = -1;
298            for (int i = start; i < max; i++) {
299                if (buf.charAt(i) == '.') {
300                    nextDot = i;
301                    break;
302                }
303            }
304            if (nextDot != -1) {
305                if (nextDot - startPos > charCount) {
306                    buf.delete(startPos + charCount, nextDot);
307                    nextDot = startPos + charCount;
308
309                    if (ellipsis != '\0') {
310                        buf.insert(nextDot, ellipsis);
311                        nextDot++;
312                    }
313                }
314                nextDot++;
315            }
316            return nextDot;
317        }
318    }
319
320    /**
321     * Pattern abbreviator.
322     */
323    private static class PatternAbbreviator extends NameAbbreviator {
324        /**
325         * Element abbreviation patterns.
326         */
327        private final PatternAbbreviatorFragment[] fragments;
328
329        /**
330         * Create PatternAbbreviator.
331         *
332         * @param fragments element abbreviation patterns.
333         */
334        public PatternAbbreviator(final List<PatternAbbreviatorFragment> fragments) {
335            if (fragments.isEmpty()) {
336                throw new IllegalArgumentException(
337                    "fragments must have at least one element");
338            }
339
340            this.fragments = new PatternAbbreviatorFragment[fragments.size()];
341            fragments.toArray(this.fragments);
342        }
343
344        /**
345         * Abbreviates name.
346         *
347         * @param original the original string to abbreviate
348         * @param destination buffer that abbreviated name is appended to
349         */
350        @Override
351        public void abbreviate(final String original, final StringBuilder destination) {
352            //
353            //  all non-terminal patterns are executed once
354            //
355            int pos = destination.length();
356            final int max = pos + original.length();
357
358            destination.append(original);
359
360            int fragmentIndex = 0;
361            while (pos < max && pos >= 0) {
362                pos = fragments[fragmentIndex].abbreviate(destination, pos);
363                // last pattern in executed repeatedly
364                if (fragmentIndex < fragments.length - 1) {
365                    fragmentIndex++;
366                }
367            }
368        }
369    }
370}