001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.rolling.helper;
015
016import java.time.Instant;
017import java.util.Date;
018import java.util.HashMap;
019import java.util.Map;
020import java.util.function.Supplier;
021
022import ch.qos.logback.core.Context;
023import ch.qos.logback.core.pattern.Converter;
024import ch.qos.logback.core.pattern.ConverterUtil;
025import ch.qos.logback.core.pattern.DynamicConverter;
026import ch.qos.logback.core.pattern.LiteralConverter;
027import ch.qos.logback.core.pattern.parser.Node;
028import ch.qos.logback.core.pattern.parser.Parser;
029import ch.qos.logback.core.spi.ScanException;
030import ch.qos.logback.core.pattern.util.AlmostAsIsEscapeUtil;
031import ch.qos.logback.core.spi.ContextAwareBase;
032
033/**
034 * After parsing file name patterns, given a number or a date, instances of this
035 * class can be used to compute a file name according to the file name pattern
036 * and the current date or integer.
037 * 
038 * @author Ceki Gülcü
039 * 
040 */
041public class FileNamePattern extends ContextAwareBase {
042
043    static final Map<String, Supplier<DynamicConverter>> CONVERTER_MAP = new HashMap<>();
044    static {
045        CONVERTER_MAP.put(IntegerTokenConverter.CONVERTER_KEY, IntegerTokenConverter::new);
046        CONVERTER_MAP.put(DateTokenConverter.CONVERTER_KEY, DateTokenConverter::new);
047    }
048
049    String pattern;
050    Converter<Object> headTokenConverter;
051
052    public FileNamePattern(String patternArg, Context contextArg) {
053        // the pattern is slashified
054        setPattern(FileFilterUtil.slashify(patternArg));
055        setContext(contextArg);
056        parse();
057        ConverterUtil.startConverters(this.headTokenConverter);
058    }
059
060    void parse() {
061        try {
062            // http://jira.qos.ch/browse/LOGBACK-197
063            // we escape ')' for parsing purposes. Note that the original pattern is
064            // preserved
065            // because it is shown to the user in status messages. We don't want the escaped
066            // version
067            // to leak out.
068            String patternForParsing = escapeRightParantesis(pattern);
069            Parser<Object> p = new Parser<Object>(patternForParsing, new AlmostAsIsEscapeUtil());
070            p.setContext(context);
071            Node t = p.parse();
072            this.headTokenConverter = p.compile(t, CONVERTER_MAP);
073
074        } catch (ScanException sce) {
075            addError("Failed to parse pattern \"" + pattern + "\".", sce);
076        }
077    }
078
079    String escapeRightParantesis(String in) {
080        return pattern.replace(")", "\\)");
081    }
082
083    public String toString() {
084        return pattern;
085    }
086
087    @Override
088    public int hashCode() {
089        final int prime = 31;
090        int result = 1;
091        result = prime * result + ((pattern == null) ? 0 : pattern.hashCode());
092        return result;
093    }
094
095    @Override
096    public boolean equals(Object obj) {
097        if (this == obj)
098            return true;
099        if (obj == null)
100            return false;
101        if (getClass() != obj.getClass())
102            return false;
103        FileNamePattern other = (FileNamePattern) obj;
104        if (pattern == null) {
105            if (other.pattern != null)
106                return false;
107        } else if (!pattern.equals(other.pattern))
108            return false;
109        return true;
110    }
111
112    public DateTokenConverter<Object> getPrimaryDateTokenConverter() {
113        Converter<Object> p = headTokenConverter;
114
115        while (p != null) {
116            if (p instanceof DateTokenConverter) {
117                DateTokenConverter<Object> dtc = (DateTokenConverter<Object>) p;
118                // only primary converters should be returned as
119                if (dtc.isPrimary())
120                    return dtc;
121            }
122
123            p = p.getNext();
124        }
125
126        return null;
127    }
128
129    public IntegerTokenConverter getIntegerTokenConverter() {
130        Converter<Object> p = headTokenConverter;
131
132        while (p != null) {
133            if (p instanceof IntegerTokenConverter) {
134                return (IntegerTokenConverter) p;
135            }
136
137            p = p.getNext();
138        }
139        return null;
140    }
141
142    public boolean hasIntegerTokenCOnverter() {
143        IntegerTokenConverter itc = getIntegerTokenConverter();
144        return itc != null;
145    }
146
147    public String convertMultipleArguments(Object... objectList) {
148        StringBuilder buf = new StringBuilder();
149        Converter<Object> c = headTokenConverter;
150        while (c != null) {
151            if (c instanceof MonoTypedConverter) {
152                MonoTypedConverter monoTyped = (MonoTypedConverter) c;
153                for (Object o : objectList) {
154                    if (monoTyped.isApplicable(o)) {
155                        buf.append(c.convert(o));
156                    }
157                }
158            } else {
159                buf.append(c.convert(objectList));
160            }
161            c = c.getNext();
162        }
163        return buf.toString();
164    }
165
166    public String convert(Object o) {
167        StringBuilder buf = new StringBuilder();
168        Converter<Object> p = headTokenConverter;
169        while (p != null) {
170            buf.append(p.convert(o));
171            p = p.getNext();
172        }
173        return buf.toString();
174    }
175
176    public String convertInt(int i) {
177        return convert(i);
178    }
179
180    public void setPattern(String pattern) {
181        if (pattern != null) {
182            // Trailing spaces in the pattern are assumed to be undesired.
183            this.pattern = pattern.trim();
184        }
185    }
186
187    public String getPattern() {
188        return pattern;
189    }
190
191    /**
192     * Given date, convert this instance to a regular expression.
193     *
194     * Used to compute sub-regex when the pattern has both %d and %i, and the date
195     * is known.
196     * 
197     * @param date - known date
198     */
199    public String toRegexForFixedDate(Date date) {
200        StringBuilder buf = new StringBuilder();
201        Converter<Object> p = headTokenConverter;
202        while (p != null) {
203            if (p instanceof LiteralConverter) {
204                buf.append(p.convert(null));
205            } else if (p instanceof IntegerTokenConverter) {
206                buf.append("(\\d+)");
207            } else if (p instanceof DateTokenConverter) {
208                buf.append(p.convert(date));
209            }
210            p = p.getNext();
211        }
212        return buf.toString();
213    }
214
215
216    /**
217     * Given date, convert this instance to a regular expression.
218     *
219     * Used to compute sub-regex when the pattern has both %d and %i, and the date
220     * is known.
221     *
222     * @param instant - known instant
223     */
224    public String toRegexForFixedDate(Instant instant) {
225        StringBuilder buf = new StringBuilder();
226        Converter<Object> p = headTokenConverter;
227        while (p != null) {
228            if (p instanceof LiteralConverter) {
229                buf.append(p.convert(null));
230            } else if (p instanceof IntegerTokenConverter) {
231                buf.append("(\\d+)");
232            } else if (p instanceof DateTokenConverter) {
233                buf.append(p.convert(instant));
234            }
235            p = p.getNext();
236        }
237        return buf.toString();
238    }
239
240
241    /**
242     * Given date, convert this instance to a regular expression
243     */
244    public String toRegex() {
245        StringBuilder buf = new StringBuilder();
246        Converter<Object> p = headTokenConverter;
247        while (p != null) {
248            if (p instanceof LiteralConverter) {
249                buf.append(p.convert(null));
250            } else if (p instanceof IntegerTokenConverter) {
251                buf.append("\\d+");
252            } else if (p instanceof DateTokenConverter) {
253                DateTokenConverter<Object> dtc = (DateTokenConverter<Object>) p;
254                buf.append(dtc.toRegex());
255            }
256            p = p.getNext();
257        }
258        return buf.toString();
259    }
260}