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