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            int i = 0;
056
057            while (i < trimmed.length() && trimmed.charAt(i) >= '0'
058                    && trimmed.charAt(i) <= '9') {
059                i++;
060            }
061
062            //
063            //  if all blanks and digits
064            //
065            if (i == trimmed.length()) {
066                return new MaxElementAbbreviator(Integer.parseInt(trimmed));
067            }
068
069            final ArrayList<PatternAbbreviatorFragment> fragments = new ArrayList<PatternAbbreviatorFragment>(5);
070            char ellipsis;
071            int charCount;
072            int pos = 0;
073
074            while (pos < trimmed.length() && pos >= 0) {
075                int ellipsisPos = pos;
076
077                if (trimmed.charAt(pos) == '*') {
078                    charCount = Integer.MAX_VALUE;
079                    ellipsisPos++;
080                } else {
081                    if (trimmed.charAt(pos) >= '0' && trimmed.charAt(pos) <= '9') {
082                        charCount = trimmed.charAt(pos) - '0';
083                        ellipsisPos++;
084                    } else {
085                        charCount = 0;
086                    }
087                }
088
089                ellipsis = '\0';
090
091                if (ellipsisPos < trimmed.length()) {
092                    ellipsis = trimmed.charAt(ellipsisPos);
093
094                    if (ellipsis == '.') {
095                        ellipsis = '\0';
096                    }
097                }
098
099                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}