    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    }