1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.rolling.helper;
15  
16  import java.time.Instant;
17  import java.util.Date;
18  import java.util.HashMap;
19  import java.util.Map;
20  import java.util.function.Supplier;
21  
22  import ch.qos.logback.core.Context;
23  import ch.qos.logback.core.pattern.Converter;
24  import ch.qos.logback.core.pattern.ConverterUtil;
25  import ch.qos.logback.core.pattern.DynamicConverter;
26  import ch.qos.logback.core.pattern.LiteralConverter;
27  import ch.qos.logback.core.pattern.parser.Node;
28  import ch.qos.logback.core.pattern.parser.Parser;
29  import ch.qos.logback.core.spi.ScanException;
30  import ch.qos.logback.core.pattern.util.AlmostAsIsEscapeUtil;
31  import ch.qos.logback.core.spi.ContextAwareBase;
32  
33  /**
34   * After parsing file name patterns, given a number or a date, instances of this
35   * class can be used to compute a file name according to the file name pattern
36   * and the current date or integer.
37   * 
38   * @author Ceki Gülcü
39   * 
40   */
41  public class FileNamePattern extends ContextAwareBase {
42  
43      static final Map<String, Supplier<DynamicConverter>> CONVERTER_MAP = new HashMap<>();
44      static {
45          CONVERTER_MAP.put(IntegerTokenConverter.CONVERTER_KEY, IntegerTokenConverter::new);
46          CONVERTER_MAP.put(DateTokenConverter.CONVERTER_KEY, DateTokenConverter::new);
47      }
48  
49      String pattern;
50      Converter<Object> headTokenConverter;
51  
52      public FileNamePattern(String patternArg, Context contextArg) {
53          // the pattern is slashified
54          setPattern(FileFilterUtil.slashify(patternArg));
55          setContext(contextArg);
56          parse();
57          ConverterUtil.startConverters(this.headTokenConverter);
58      }
59  
60      void parse() {
61          try {
62              // http://jira.qos.ch/browse/LOGBACK-197
63              // we escape ')' for parsing purposes. Note that the original pattern is
64              // preserved
65              // because it is shown to the user in status messages. We don't want the escaped
66              // version
67              // to leak out.
68              String patternForParsing = escapeRightParantesis(pattern);
69              Parser<Object> p = new Parser<Object>(patternForParsing, new AlmostAsIsEscapeUtil());
70              p.setContext(context);
71              Node t = p.parse();
72              this.headTokenConverter = p.compile(t, CONVERTER_MAP);
73  
74          } catch (ScanException sce) {
75              addError("Failed to parse pattern \"" + pattern + "\".", sce);
76          }
77      }
78  
79      String escapeRightParantesis(String in) {
80          return pattern.replace(")", "\\)");
81      }
82  
83      public String toString() {
84          return pattern;
85      }
86  
87      @Override
88      public int hashCode() {
89          final int prime = 31;
90          int result = 1;
91          result = prime * result + ((pattern == null) ? 0 : pattern.hashCode());
92          return result;
93      }
94  
95      @Override
96      public boolean equals(Object obj) {
97          if (this == obj)
98              return true;
99          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 }