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 RolloverFrequency frequency = null;
050    
051        /**
052         * Constructor.
053         * @param pattern The file pattern.
054         */
055        public PatternProcessor(String pattern) {
056            PatternParser parser = createPatternParser();
057            List<PatternConverter> converters = new ArrayList<PatternConverter>();
058            List<FormattingInfo> fields = new ArrayList<FormattingInfo>();
059            parser.parse(pattern, converters, fields);
060            FormattingInfo[] infoArray = new FormattingInfo[fields.size()];
061            patternFields = fields.toArray(infoArray);
062            ArrayPatternConverter[] converterArray = new ArrayPatternConverter[converters.size()];
063            patternConverters = converters.toArray(converterArray);
064    
065            for (ArrayPatternConverter converter : patternConverters) {
066                if (converter instanceof DatePatternConverter) {
067                    DatePatternConverter dateConverter = (DatePatternConverter) converter;
068                    frequency = calculateFrequency(dateConverter.getPattern());
069                }
070            }
071        }
072    
073        /**
074         * Returns the next potential rollover time.
075         * @param current The current time.
076         * @param increment The increment to the next time.
077         * @return the next potential rollover time.
078         */
079        public long getNextTime(long current, int increment, boolean modulus) {
080            if (frequency == null) {
081                throw new IllegalStateException("Pattern does not contain a date");
082            }
083            Calendar currentCal = Calendar.getInstance();
084            currentCal.setTimeInMillis(current);
085            Calendar cal = Calendar.getInstance();
086            cal.set(currentCal.get(Calendar.YEAR), 0, 1, 0, 0, 0);
087            cal.set(Calendar.MILLISECOND, 0);
088            if (frequency == RolloverFrequency.ANNUALLY) {
089                increment(cal, Calendar.YEAR, increment, modulus);
090                return cal.getTimeInMillis();
091            }
092            if (frequency == RolloverFrequency.MONTHLY) {
093                increment(cal, Calendar.MONTH, increment, modulus);
094                return cal.getTimeInMillis();
095            }
096            if (frequency == RolloverFrequency.WEEKLY) {
097                increment(cal, Calendar.WEEK_OF_YEAR, increment, modulus);
098                return cal.getTimeInMillis();
099            }
100            cal.set(Calendar.DAY_OF_YEAR, currentCal.get(Calendar.DAY_OF_YEAR));
101            if (frequency == RolloverFrequency.DAILY) {
102                increment(cal, Calendar.DAY_OF_YEAR, increment, modulus);
103                return cal.getTimeInMillis();
104            }
105            cal.set(Calendar.HOUR, currentCal.get(Calendar.HOUR));
106            if (frequency == RolloverFrequency.HOURLY) {
107                increment(cal, Calendar.HOUR, increment, modulus);
108                return cal.getTimeInMillis();
109            }
110            cal.set(Calendar.MINUTE, currentCal.get(Calendar.MINUTE));
111            if (frequency == RolloverFrequency.EVERY_MINUTE) {
112                increment(cal, Calendar.MINUTE, increment, modulus);
113                return cal.getTimeInMillis();
114            }
115            cal.set(Calendar.SECOND, currentCal.get(Calendar.SECOND));
116            if (frequency == RolloverFrequency.EVERY_SECOND) {
117                increment(cal, Calendar.SECOND, increment, modulus);
118                return cal.getTimeInMillis();
119            }
120            increment(cal, Calendar.MILLISECOND, increment, modulus);
121            return cal.getTimeInMillis();
122        }
123    
124        private void increment(Calendar cal, int type, int increment, boolean modulate) {
125            int interval =  modulate ? increment - (cal.get(type) % increment) : increment;
126            cal.add(type, interval);
127        }
128    
129        /**
130         * Format file name.
131         * @param buf string buffer to which formatted file name is appended, may not be null.
132         * @param obj object to be evaluated in formatting, may not be null.
133         */
134        protected final void formatFileName(final StringBuilder buf, final Object obj) {
135            formatFileName(buf, new Date(System.currentTimeMillis()), obj);
136        }
137    
138        /**
139         * Format file name.
140         * @param buf string buffer to which formatted file name is appended, may not be null.
141         * @param objects objects to be evaluated in formatting, may not be null.
142         */
143        protected final void formatFileName(final StringBuilder buf, final Object... objects) {
144            for (int i = 0; i < patternConverters.length; i++) {
145                int fieldStart = buf.length();
146                patternConverters[i].format(buf, objects);
147    
148                if (patternFields[i] != null) {
149                    patternFields[i].format(fieldStart, buf);
150                }
151            }
152        }
153    
154        private RolloverFrequency calculateFrequency(String pattern) {
155            if (patternContains(pattern, MILLIS_CHAR)) {
156                return RolloverFrequency.EVERY_MILLISECOND;
157            }
158            if (patternContains(pattern, SECOND_CHAR)) {
159                return RolloverFrequency.EVERY_SECOND;
160            }
161            if (patternContains(pattern, MINUTE_CHAR)) {
162                return RolloverFrequency.EVERY_MINUTE;
163            }
164            if (patternContains(pattern, HOUR_CHARS)) {
165                return RolloverFrequency.HOURLY;
166            }
167            if (patternContains(pattern, DAY_CHARS)) {
168                return RolloverFrequency.DAILY;
169            }
170            if (patternContains(pattern, WEEK_CHARS)) {
171                return RolloverFrequency.WEEKLY;
172            }
173            if (patternContains(pattern, MONTH_CHAR)) {
174                return RolloverFrequency.MONTHLY;
175            }
176            if (patternContains(pattern, YEAR_CHAR)) {
177                return RolloverFrequency.ANNUALLY;
178            }
179            return null;
180        }
181    
182        private PatternParser createPatternParser() {
183    
184            return new PatternParser(null, KEY, null);
185        }
186    
187        private boolean patternContains(String pattern, char... chars) {
188            for (char character : chars) {
189                if (patternContains(pattern, character)) {
190                    return true;
191                }
192            }
193            return false;
194        }
195    
196        private boolean patternContains(String pattern, char character) {
197            return pattern.indexOf(character) >= 0;
198        }
199    }