    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.impl;
    018
    019    import java.util.ArrayList;
    020    import java.util.List;
    021    import java.util.Scanner;
    022
    023    import org.apache.logging.log4j.core.util.Constants;
    024    import org.apache.logging.log4j.core.util.Patterns;
    025
    026    /**
    027     * Contains options which control how a {@link Throwable} pattern is formatted.
    028     */
    029    public final class ThrowableFormatOptions {
    030
    031        private static final int DEFAULT_LINES = Integer.MAX_VALUE;
    032
    033        /**
    034         * Default instance of {@code ThrowableFormatOptions}.
    035         */
    036        protected static final ThrowableFormatOptions DEFAULT = new ThrowableFormatOptions();
    037
    038        /**
    039         * Format the whole stack trace.
    040         */
    041        private static final String FULL = "full";
    042
    043        /**
    044         * Do not format the exception.
    045         */
    046        private static final String NONE = "none";
    047
    048        /**
    049         * Format only the first line of the throwable.
    050         */
    051        private static final String SHORT = "short";
    052
    053        /**
    054         * The number of lines to write.
    055         */
    056        private final int lines;
    057
    058        /**
    059         * The stack trace separator.
    060         */
    061        private final String separator;
    062
    063        /**
    064         * The list of packages to filter.
    065         */
    066        private final List<String> packages;
    067
    068        public static final String CLASS_NAME = "short.className";
    069        public static final String METHOD_NAME = "short.methodName";
    070        public static final String LINE_NUMBER = "short.lineNumber";
    071        public static final String FILE_NAME = "short.fileName";
    072        public static final String MESSAGE = "short.message";
    073        public static final String LOCALIZED_MESSAGE = "short.localizedMessage";
    074
    075        /**
    076         * Construct the options for printing stack trace.
    077         * @param lines The number of lines.
    078         * @param separator The stack trace separator.
    079         * @param packages The packages to filter.
    080         */
    081        protected ThrowableFormatOptions(final int lines, final String separator, final List<String> packages) {
    082            this.lines = lines;
    083            this.separator = separator == null ? Constants.LINE_SEPARATOR : separator;
    084            this.packages = packages;
    085        }
    086
    087        /**
    088         * Construct the options for printing stack trace.
    089         * @param packages The packages to filter.
    090         */
    091        protected ThrowableFormatOptions(final List<String> packages) {
    092            this(DEFAULT_LINES, null, packages);
    093        }
    094
    095        /**
    096         * Construct the options for printing stack trace.
    097         */
    098        protected ThrowableFormatOptions() {
    099            this(DEFAULT_LINES, null, null);
    100        }
    101
    102        /**
    103         * Returns the number of lines to write.
    104         * @return The number of lines to write.
    105         */
    106        public int getLines() {
    107            return this.lines;
    108        }
    109
    110        /**
    111         * Returns the stack trace separator.
    112         * @return The stack trace separator.
    113         */
    114        public String getSeparator() {
    115            return this.separator;
    116        }
    117
    118        /**
    119         * Returns the list of packages to filter.
    120         * @return The list of packages to filter.
    121         */
    122        public List<String> getPackages() {
    123            return this.packages;
    124        }
    125
    126        /**
    127         * Determines if all lines should be printed.
    128         * @return true for all lines, false otherwise.
    129         */
    130        public boolean allLines() {
    131            return this.lines == DEFAULT_LINES;
    132        }
    133
    134        /**
    135         * Determines if any lines should be printed.
    136         * @return true for any lines, false otherwise.
    137         */
    138        public boolean anyLines() {
    139            return this.lines > 0;
    140        }
    141
    142        /**
    143         * Returns the minimum between the lines and the max lines.
    144         * @param maxLines The maximum number of lines.
    145         * @return The number of lines to print.
    146         */
    147        public int minLines(final int maxLines) {
    148            return this.lines > maxLines ? maxLines : this.lines;
    149        }
    150
    151        /**
    152         * Determines if there are any packages to filter.
    153         * @return true if there are packages, false otherwise.
    154         */
    155        public boolean hasPackages() {
    156            return this.packages != null && !this.packages.isEmpty();
    157        }
    158
    159        /**
    160         * {@inheritDoc}
    161         */
    162        @Override
    163        public String toString() {
    164            final StringBuilder s = new StringBuilder();
    165            s.append('{').append(allLines() ? FULL : this.lines == 2 ? SHORT : anyLines() ? String.valueOf(this.lines) : NONE).append('}');
    166            s.append("{separator(").append(this.separator).append(")}");
    167            if (hasPackages()) {
    168                s.append("{filters(");
    169                for (final String p : this.packages) {
    170                    s.append(p).append(',');
    171                }
    172                s.deleteCharAt(s.length() - 1);
    173                s.append(")}");
    174            }
    175            return s.toString();
    176        }
    177
    178        /**
    179         * Create a new instance based on the array of options.
    180         * @param options The array of options.
    181         */
    182        public static ThrowableFormatOptions newInstance(String[] options) {
    183            if (options == null || options.length == 0) {
    184                return DEFAULT;
    185            }
    186            // NOTE: The following code is present for backward compatibility
    187            // and was copied from Extended/RootThrowablePatternConverter.
    188            // This supports a single option with the format:
    189            //     %xEx{["none"|"short"|"full"|depth],[filters(packages)}
    190            // However, the convention for multiple options should be:
    191            //     %xEx{["none"|"short"|"full"|depth]}[{filters(packages)}]
    192            if (options.length == 1 && options[0] != null && options[0].length() > 0) {
    193                final String[] opts = options[0].split(Patterns.COMMA_SEPARATOR, 2);
    194                final String first = opts[0].trim();
    195                final Scanner scanner = new Scanner(first);
    196                if (opts.length > 1 && (first.equalsIgnoreCase(FULL) || first.equalsIgnoreCase(SHORT) || first.equalsIgnoreCase(NONE) || scanner.hasNextInt())) {
    197                    options = new String[]{first, opts[1].trim()};
    198                }
    199                scanner.close();
    200            }
    201
    202            int lines = DEFAULT.lines;
    203            String separator = DEFAULT.separator;
    204            List<String> packages = DEFAULT.packages;
    205            for (final String rawOption : options) {
    206                if (rawOption != null) {
    207                    final String option = rawOption.trim();
    208                    if (option.isEmpty()) {
    209                        // continue;
    210                    } else if (option.startsWith("separator(") && option.endsWith(")")) {
    211                        separator = option.substring("separator(".length(), option.length() - 1);
    212                    } else if (option.startsWith("filters(") && option.endsWith(")")) {
    213                        final String filterStr = option.substring("filters(".length(), option.length() - 1);
    214                        if (filterStr.length() > 0) {
    215                            final String[] array = filterStr.split(Patterns.COMMA_SEPARATOR);
    216                            if (array.length > 0) {
    217                                packages = new ArrayList<String>(array.length);
    218                                for (String token : array) {
    219                                    token = token.trim();
    220                                    if (token.length() > 0) {
    221                                        packages.add(token);
    222                                    }
    223                                }
    224                            }
    225                        }
    226                    } else if (option.equalsIgnoreCase(NONE)) {
    227                        lines = 0;
    228                    } else if (option.equalsIgnoreCase(SHORT) || option.equalsIgnoreCase(CLASS_NAME) ||
    229                            option.equalsIgnoreCase(METHOD_NAME) || option.equalsIgnoreCase(LINE_NUMBER) ||
    230                            option.equalsIgnoreCase(FILE_NAME) || option.equalsIgnoreCase(MESSAGE) ||
    231                            option.equalsIgnoreCase(LOCALIZED_MESSAGE)) {
    232                        lines = 2;
    233                    } else if (!option.equalsIgnoreCase(FULL)) {
    234                        lines = Integer.parseInt(option);
    235                    }
    236                }
    237            }
    238            return new ThrowableFormatOptions(lines, separator, packages);
    239        }
    240    }