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,
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.length() == 0) {
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 substract 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    }