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}