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 if (trimmed.charAt(pos) >= '0' && trimmed.charAt(pos) <= '9') {
097                    charCount = trimmed.charAt(pos) - '0';
098                    ellipsisPos++;
099                } else {
100                    charCount = 0;
101                }
102
103                ellipsis = '\0';
104
105                if (ellipsisPos < trimmed.length()) {
106                    ellipsis = trimmed.charAt(ellipsisPos);
107
108                    if (ellipsis == '.') {
109                        ellipsis = '\0';
110                    }
111                }
112
113                fragments.add(new PatternAbbreviatorFragment(charCount, ellipsis));
114                pos = trimmed.indexOf('.', pos);
115
116                if (pos == -1) {
117                    break;
118                }
119
120                pos++;
121            }
122
123            return new PatternAbbreviator(fragments);
124        }
125
126        //
127        //  no matching abbreviation, return defaultAbbreviator
128        //
129        return DEFAULT;
130    }
131
132    /**
133     * Gets default abbreviator.
134     *
135     * @return default abbreviator.
136     */
137    public static NameAbbreviator getDefaultAbbreviator() {
138        return DEFAULT;
139    }
140
141    /**
142     * Abbreviates a name in a String.
143     *
144     * @param original the text to abbreviate, may not be null.
145     * @param destination StringBuilder to write the result to
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(0) {
180                @Override
181                void abbreviate(final int count, final String original, final StringBuilder destination) {
182                    // If a path does not contain enough path elements to drop, none will be dropped.
183                    int start = 0;
184                    int nextStart;
185                    for (int i = 0; i < count; i++) {
186                        nextStart = original.indexOf('.', start);
187                        if (nextStart == -1) {
188                            destination.append(original);
189                            return;
190                        }
191                        start = nextStart + 1;
192                    }
193                    destination.append(original, start, original.length());
194                }
195            },
196            RETAIN(1) {
197                @Override
198                void abbreviate(final int count, final String original, final StringBuilder destination) {
199                    // We subtract 1 from 'len' when assigning to 'end' to avoid out of
200                    // bounds exception in return r.substring(end+1, len). This can happen if
201                    // precision is 1 and the category name ends with a dot.
202                    int end = original.length() - 1;
203
204                    for (int i = count; i > 0; i--) {
205                        end = original.lastIndexOf('.', end - 1);
206                        if (end == -1) {
207                            destination.append(original);
208                            return;
209                        }
210                    }
211                    destination.append(original, end + 1, original.length());
212                }
213            };
214
215            final int minCount;
216
217            Strategy(final int minCount) {
218                this.minCount = minCount;
219            }
220
221            abstract void abbreviate(final int count, final String original, final StringBuilder destination);
222        }
223
224        /**
225         * Maximum number of path elements to output.
226         */
227        private final int count;
228
229        /**
230         * Strategy used for cutting down the size of the name
231         */
232        private final Strategy strategy;
233
234        /**
235         * Create new instance.
236         *
237         * @param count maximum number of path elements to drop or output.
238         * @param strategy drop or retain
239         */
240        public MaxElementAbbreviator(final int count, final Strategy strategy) {
241            this.count = Math.max(count, strategy.minCount);
242            this.strategy = strategy;
243        }
244
245        /**
246         * Abbreviate name.
247         *
248         * @param original The String to abbreviate.
249         * @param destination the buffer to write the abbreviated name into
250         */
251        @Override
252        public void abbreviate(final String original, final StringBuilder destination) {
253            strategy.abbreviate(count, original, destination);
254        }
255    }
256
257    /**
258     * Fragment of an pattern abbreviator.
259     */
260    private static final class PatternAbbreviatorFragment {
261        /**
262         * Count of initial characters of element to output.
263         */
264        private final int charCount;
265
266        /**
267         * Character used to represent dropped characters.
268         * '\0' indicates no representation of dropped characters.
269         */
270        private final char ellipsis;
271
272        /**
273         * Creates a PatternAbbreviatorFragment.
274         *
275         * @param charCount number of initial characters to preserve.
276         * @param ellipsis  character to represent elimination of characters,
277         *                  '\0' if no ellipsis is desired.
278         */
279        PatternAbbreviatorFragment(
280            final int charCount, final char ellipsis) {
281            this.charCount = charCount;
282            this.ellipsis = ellipsis;
283        }
284
285        /**
286         * Abbreviate element of name.
287         *
288         * @param input      input string which is being written to the output {@code buf}.
289         * @param inputIndex starting index of name element in the {@code input} string.
290         * @param buf        buffer to receive element.
291         * @return starting  index of next element.
292         */
293        int abbreviate(final String input, final int inputIndex, final StringBuilder buf) {
294            // Note that indexOf(char) performs worse than indexOf(String) on pre-16 JREs
295            // due to missing intrinsics for the character implementation. The difference
296            // is a few nanoseconds in most cases, so we opt to give the jre as much
297            // information as possible for best performance on new runtimes, with the
298            // possibility that such optimizations may be back-ported.
299            // See https://bugs.openjdk.java.net/browse/JDK-8173585
300            int nextDot = input.indexOf('.', inputIndex);
301            if (nextDot < 0) {
302                buf.append(input, inputIndex, input.length());
303                return nextDot;
304            }
305            if (nextDot - inputIndex > charCount) {
306                buf.append(input, inputIndex, inputIndex + charCount);
307                if (ellipsis != '\0') {
308                    buf.append(ellipsis);
309                }
310                buf.append('.');
311            } else {
312                // Include the period to reduce interactions with the buffer
313                buf.append(input, inputIndex, nextDot + 1);
314            }
315            return nextDot + 1;
316        }
317    }
318
319    /**
320     * Pattern abbreviator.
321     */
322    private static final class PatternAbbreviator extends NameAbbreviator {
323        /**
324         * Element abbreviation patterns.
325         */
326        private final PatternAbbreviatorFragment[] fragments;
327
328        /**
329         * Create PatternAbbreviator.
330         *
331         * @param fragments element abbreviation patterns.
332         */
333        PatternAbbreviator(final List<PatternAbbreviatorFragment> fragments) {
334            if (fragments.isEmpty()) {
335                throw new IllegalArgumentException(
336                    "fragments must have at least one element");
337            }
338
339            this.fragments = fragments.toArray(new PatternAbbreviatorFragment[0]);
340        }
341
342        /**
343         * Abbreviates name.
344         *
345         * @param original the original string to abbreviate
346         * @param destination buffer that abbreviated name is appended to
347         */
348        @Override
349        public void abbreviate(final String original, final StringBuilder destination) {
350            // non-terminal patterns are executed once
351            int originalIndex = 0;
352            int iteration = 0;
353            int originalLength = original.length();
354            while (originalIndex >= 0 && originalIndex < originalLength) {
355                originalIndex = fragment(iteration++).abbreviate(original, originalIndex, destination);
356            }
357        }
358
359        PatternAbbreviatorFragment fragment(int index) {
360            return fragments[Math.min(index, fragments.length - 1)];
361        }
362    }
363}