001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements. See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache license, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License. You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the license for the specific language governing permissions and
015     * limitations under the license.
016     */
017    package org.apache.logging.log4j.core.appender.rolling;
018    
019    import org.apache.logging.log4j.core.pattern.ArrayPatternConverter;
020    import org.apache.logging.log4j.core.pattern.DatePatternConverter;
021    import org.apache.logging.log4j.core.pattern.FormattingInfo;
022    import org.apache.logging.log4j.core.pattern.PatternConverter;
023    import org.apache.logging.log4j.core.pattern.PatternParser;
024    
025    import java.util.ArrayList;
026    import java.util.Calendar;
027    import java.util.Date;
028    import java.util.List;
029    
030    /**
031     * Parse the rollover pattern.
032     */
033    public class PatternProcessor {
034    
035        private static final String KEY = "FileConverter";
036    
037        private static final char YEAR_CHAR = 'y';
038        private static final char MONTH_CHAR = 'M';
039        private static final char[] WEEK_CHARS = {'w', 'W'};
040        private static final char[] DAY_CHARS = {'D', 'd', 'F', 'E'};
041        private static final char[] HOUR_CHARS = {'H', 'K', 'h', 'k'};
042        private static final char MINUTE_CHAR = 'm';
043        private static final char SECOND_CHAR = 's';
044        private static final char MILLIS_CHAR = 'S';
045    
046        private final ArrayPatternConverter[] patternConverters;
047        private final FormattingInfo[] patternFields;
048    
049        private long prevFileTime = 0;
050        private long nextFileTime = 0;
051    
052        private RolloverFrequency frequency = null;
053    
054        /**
055         * Constructor.
056         * @param pattern The file pattern.
057         */
058        public PatternProcessor(final String pattern) {
059            final PatternParser parser = createPatternParser();
060            final List<PatternConverter> converters = new ArrayList<PatternConverter>();
061            final List<FormattingInfo> fields = new ArrayList<FormattingInfo>();
062            parser.parse(pattern, converters, fields);
063            final FormattingInfo[] infoArray = new FormattingInfo[fields.size()];
064            patternFields = fields.toArray(infoArray);
065            final ArrayPatternConverter[] converterArray = new ArrayPatternConverter[converters.size()];
066            patternConverters = converters.toArray(converterArray);
067    
068            for (final ArrayPatternConverter converter : patternConverters) {
069                if (converter instanceof DatePatternConverter) {
070                    final DatePatternConverter dateConverter = (DatePatternConverter) converter;
071                    frequency = calculateFrequency(dateConverter.getPattern());
072                }
073            }
074        }
075    
076        /**
077         * Returns the next potential rollover time.
078         * @param current The current time.
079         * @param increment The increment to the next time.
080         * @param modulus If true the time will be rounded to occur on a boundary aligned with the increment.
081         * @return the next potential rollover time and the timestamp for the target file.
082         */
083        public long getNextTime(final long current, final int increment, final boolean modulus) {
084            prevFileTime = nextFileTime;
085            long nextTime;
086    
087            if (frequency == null) {
088                throw new IllegalStateException("Pattern does not contain a date");
089            }
090            final Calendar currentCal = Calendar.getInstance();
091            currentCal.setTimeInMillis(current);
092            final Calendar cal = Calendar.getInstance();
093            cal.set(currentCal.get(Calendar.YEAR), 0, 1, 0, 0, 0);
094            cal.set(Calendar.MILLISECOND, 0);
095            if (frequency == RolloverFrequency.ANNUALLY) {
096                increment(cal, Calendar.YEAR, increment, modulus);
097                nextTime = cal.getTimeInMillis();
098                cal.add(Calendar.YEAR, -1);
099                nextFileTime = cal.getTimeInMillis();
100                return nextTime;
101            }
102            if (frequency == RolloverFrequency.MONTHLY) {
103                increment(cal, Calendar.MONTH, increment, modulus);
104                nextTime = cal.getTimeInMillis();
105                cal.add(Calendar.MONTH, -1);
106                nextFileTime = cal.getTimeInMillis();
107                return nextTime;
108            }
109            if (frequency == RolloverFrequency.WEEKLY) {
110                increment(cal, Calendar.WEEK_OF_YEAR, increment, modulus);
111                nextTime = cal.getTimeInMillis();
112                cal.add(Calendar.WEEK_OF_YEAR, -1);
113                nextFileTime = cal.getTimeInMillis();
114                return nextTime;
115            }
116            cal.set(Calendar.DAY_OF_YEAR, currentCal.get(Calendar.DAY_OF_YEAR));
117            if (frequency == RolloverFrequency.DAILY) {
118                increment(cal, Calendar.DAY_OF_YEAR, increment, modulus);
119                nextTime = cal.getTimeInMillis();
120                cal.add(Calendar.DAY_OF_YEAR, -1);
121                nextFileTime = cal.getTimeInMillis();
122                return nextTime;
123            }
124            cal.set(Calendar.HOUR, currentCal.get(Calendar.HOUR));
125            if (frequency == RolloverFrequency.HOURLY) {
126                increment(cal, Calendar.HOUR, increment, modulus);
127                nextTime = cal.getTimeInMillis();
128                cal.add(Calendar.HOUR, -1);
129                nextFileTime = cal.getTimeInMillis();
130                return nextTime;
131            }
132            cal.set(Calendar.MINUTE, currentCal.get(Calendar.MINUTE));
133            if (frequency == RolloverFrequency.EVERY_MINUTE) {
134                increment(cal, Calendar.MINUTE, increment, modulus);
135                nextTime = cal.getTimeInMillis();
136                cal.add(Calendar.MINUTE, -1);
137                nextFileTime = cal.getTimeInMillis();
138                return nextTime;
139            }
140            cal.set(Calendar.SECOND, currentCal.get(Calendar.SECOND));
141            if (frequency == RolloverFrequency.EVERY_SECOND) {
142                increment(cal, Calendar.SECOND, increment, modulus);
143                nextTime = cal.getTimeInMillis();
144                cal.add(Calendar.SECOND, -1);
145                nextFileTime = cal.getTimeInMillis();
146                return nextTime;
147            }
148            increment(cal, Calendar.MILLISECOND, increment, modulus);
149            nextTime = cal.getTimeInMillis();
150            cal.add(Calendar.MILLISECOND, -1);
151            nextFileTime = cal.getTimeInMillis();
152            return nextTime;
153        }
154    
155        private void increment(final Calendar cal, final int type, final int increment, final boolean modulate) {
156            final int interval =  modulate ? increment - (cal.get(type) % increment) : increment;
157            cal.add(type, interval);
158        }
159    
160        /**
161         * Format file name.
162         * @param buf string buffer to which formatted file name is appended, may not be null.
163         * @param obj object to be evaluated in formatting, may not be null.
164         */
165        public final void formatFileName(final StringBuilder buf, final Object obj) {
166            final long time = prevFileTime == 0 ? System.currentTimeMillis() : prevFileTime;
167            formatFileName(buf, new Date(time), obj);
168        }
169    
170        /**
171         * Format file name.
172         * @param buf string buffer to which formatted file name is appended, may not be null.
173         * @param objects objects to be evaluated in formatting, may not be null.
174         */
175        protected final void formatFileName(final StringBuilder buf, final Object... objects) {
176            for (int i = 0; i < patternConverters.length; i++) {
177                final int fieldStart = buf.length();
178                patternConverters[i].format(buf, objects);
179    
180                if (patternFields[i] != null) {
181                    patternFields[i].format(fieldStart, buf);
182                }
183            }
184        }
185    
186        private RolloverFrequency calculateFrequency(final String pattern) {
187            if (patternContains(pattern, MILLIS_CHAR)) {
188                return RolloverFrequency.EVERY_MILLISECOND;
189            }
190            if (patternContains(pattern, SECOND_CHAR)) {
191                return RolloverFrequency.EVERY_SECOND;
192            }
193            if (patternContains(pattern, MINUTE_CHAR)) {
194                return RolloverFrequency.EVERY_MINUTE;
195            }
196            if (patternContains(pattern, HOUR_CHARS)) {
197                return RolloverFrequency.HOURLY;
198            }
199            if (patternContains(pattern, DAY_CHARS)) {
200                return RolloverFrequency.DAILY;
201            }
202            if (patternContains(pattern, WEEK_CHARS)) {
203                return RolloverFrequency.WEEKLY;
204            }
205            if (patternContains(pattern, MONTH_CHAR)) {
206                return RolloverFrequency.MONTHLY;
207            }
208            if (patternContains(pattern, YEAR_CHAR)) {
209                return RolloverFrequency.ANNUALLY;
210            }
211            return null;
212        }
213    
214        private PatternParser createPatternParser() {
215    
216            return new PatternParser(null, KEY, null);
217        }
218    
219        private boolean patternContains(final String pattern, final char... chars) {
220            for (final char character : chars) {
221                if (patternContains(pattern, character)) {
222                    return true;
223                }
224            }
225            return false;
226        }
227    
228        private boolean patternContains(final String pattern, final char character) {
229            return pattern.indexOf(character) >= 0;
230        }
231    }