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