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
022
023/**
024 * NameAbbreviator generates abbreviated logger and class names.
025 */
026public abstract class NameAbbreviator {
027    /**
028     * Default (no abbreviation) abbreviator.
029     */
030    private static final NameAbbreviator DEFAULT = new NOPAbbreviator();
031
032    /**
033     * Gets an abbreviator.
034     * <p>
035     * For example, "%logger{2}" will output only 2 elements of the logger name, "%logger{1.}" will output only the
036     * first character of the non-final elements in the name, "%logger(1~.2~} will output the first character of the
037     * first element, two characters of the second and subsequent elements and will use a tilde to indicate abbreviated
038     * characters.
039     * </p>
040     *
041     * @param pattern
042     *        abbreviation pattern.
043     * @return abbreviator, will not be null.
044     */
045    public static NameAbbreviator getAbbreviator(final String pattern) {
046        if (pattern.length() > 0) {
047            //  if pattern is just spaces and numbers then
048            //     use MaxElementAbbreviator
049            final String trimmed = pattern.trim();
050
051            if (trimmed.isEmpty()) {
052                return DEFAULT;
053            }
054
055            boolean isNegativeNumber;
056            final String number;
057
058            // check if number is a negative number
059            if (trimmed.length() > 1 && trimmed.charAt(0) == '-') {
060                isNegativeNumber = true;
061                number = trimmed.substring(1);
062            } else {
063                isNegativeNumber = false;
064                number = trimmed;
065            }
066
067            int i = 0;
068
069            while (i < number.length() && number.charAt(i) >= '0'
070                    && number.charAt(i) <= '9') {
071                i++;
072            }
073
074            //
075            //  if all blanks and digits
076            //
077            if (i == number.length()) {
078                return new MaxElementAbbreviator(Integer.parseInt(number),
079                        isNegativeNumber? MaxElementAbbreviator.Strategy.DROP : MaxElementAbbreviator.Strategy.RETAIN);
080            }
081
082            final ArrayList<PatternAbbreviatorFragment> fragments = new ArrayList<>(5);
083            char ellipsis;
084            int charCount;
085            int pos = 0;
086
087            while (pos < trimmed.length() && pos >= 0) {
088                int ellipsisPos = pos;
089
090                if (trimmed.charAt(pos) == '*') {
091                    charCount = Integer.MAX_VALUE;
092                    ellipsisPos++;
093                } else {
094                    if (trimmed.charAt(pos) >= '0' && trimmed.charAt(pos) <= '9') {
095                        charCount = trimmed.charAt(pos) - '0';
096                        ellipsisPos++;
097                    } else {
098                        charCount = 0;
099                    }
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}