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,
036     * "%logger{1.}" will output only the first character of the non-final elements in the name,
037     * "%logger(1~.2~} will output the first character of the first element, two characters of
038     * the second and subsequent elements and will use a tilde to indicate abbreviated characters.
039     *
040     * @param pattern abbreviation pattern.
041     * @return abbreviator, will not be null.
042     */
043    public static NameAbbreviator getAbbreviator(final String pattern) {
044        if (pattern.length() > 0) {
045            //  if pattern is just spaces and numbers then
046            //     use MaxElementAbbreviator
047            final String trimmed = pattern.trim();
048
049            if (trimmed.isEmpty()) {
050                return DEFAULT;
051            }
052
053            int i = 0;
054
055            while (i < trimmed.length() && trimmed.charAt(i) >= '0'
056                    && trimmed.charAt(i) <= '9') {
057                i++;
058            }
059
060            //
061            //  if all blanks and digits
062            //
063            if (i == trimmed.length()) {
064                return new MaxElementAbbreviator(Integer.parseInt(trimmed));
065            }
066
067            final ArrayList<PatternAbbreviatorFragment> fragments = new ArrayList<PatternAbbreviatorFragment>(5);
068            char ellipsis;
069            int charCount;
070            int pos = 0;
071
072            while (pos < trimmed.length() && pos >= 0) {
073                int ellipsisPos = pos;
074
075                if (trimmed.charAt(pos) == '*') {
076                    charCount = Integer.MAX_VALUE;
077                    ellipsisPos++;
078                } else {
079                    if (trimmed.charAt(pos) >= '0' && trimmed.charAt(pos) <= '9') {
080                        charCount = trimmed.charAt(pos) - '0';
081                        ellipsisPos++;
082                    } else {
083                        charCount = 0;
084                    }
085                }
086
087                ellipsis = '\0';
088
089                if (ellipsisPos < trimmed.length()) {
090                    ellipsis = trimmed.charAt(ellipsisPos);
091
092                    if (ellipsis == '.') {
093                        ellipsis = '\0';
094                    }
095                }
096
097                fragments.add(new PatternAbbreviatorFragment(charCount, ellipsis));
098                pos = trimmed.indexOf('.', pos);
099
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 subtract 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}