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     */
017    package org.apache.logging.log4j.core.pattern;
018    
019    import java.util.ArrayList;
020    import java.util.List;
021    
022    
023    /**
024     * NameAbbreviator generates abbreviated logger and class names.
025     */
026    public 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    }