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