<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.lang3.time;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.SortedMap;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>FastDateParser is a fast and thread-safe version of
 * {@link java.text.SimpleDateFormat}.</p>
 * 
 * <p>This class can be used as a direct replacement to
 * <code>SimpleDateFormat</code> in most parsing situations.
 * This class is especially useful in multi-threaded server environments.
 * <code>SimpleDateFormat</code> is not thread-safe in any JDK version,
 * nor will it be as Sun have closed the 
 * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335">bug</a>/RFE.
 * </p>
 *
 * <p>Only parsing is supported, but all patterns are compatible with
 * SimpleDateFormat.</p>
 * 
 * <p>Timing tests indicate this class is as about as fast as SimpleDateFormat
 * in single thread applications and about 25% faster in multi-thread applications.</p>
 *
 * @since 3.2
 */
public class FastDateParser implements DateParser, Serializable {
    /**
     * Required for serialization support.
     * 
     * @see java.io.Serializable
     */
    private static final long serialVersionUID = 1L;
    
    private static final ConcurrentMap<Locale,TimeZoneStrategy> tzsCache= 
        new ConcurrentHashMap<Locale,TimeZoneStrategy>(3);
    
    // defining fields
    private final String pattern;
    private final TimeZone timeZone;
    private final Locale locale;
    
    // derived fields
    private transient Pattern parsePattern;
    private transient Strategy[] strategies;
    private transient int thisYear;
    private transient ConcurrentMap<Integer, KeyValue[]> nameValues;

    // dynamic fields to communicate with Strategy
    private transient String currentFormatField;
    private transient Strategy nextStrategy;

    /**
     * <p>Constructs a new FastDateParser.</p>
     * 
     * @param pattern non-null {@link java.text.SimpleDateFormat} compatible
     *  pattern
     * @param timeZone non-null time zone to use
     * @param locale non-null locale
     */
    protected FastDateParser(String pattern, TimeZone timeZone, Locale locale) {
        this.pattern = pattern;
        this.timeZone = timeZone;
        this.locale = locale;        
        init();
    }
    
    /**
     * Initialize derived fields from defining fields.
     * This is called from constructor and from readObject (de-serialization)   
     */
    private void init() {
        thisYear= Calendar.getInstance(timeZone, locale).get(Calendar.YEAR);
        
        nameValues= new ConcurrentHashMap<Integer, KeyValue[]>();
        
        StringBuilder regex= new StringBuilder();
        List<Strategy> collector = new ArrayList<Strategy>();
        
        Matcher patternMatcher= formatPattern.matcher(pattern);
        if(!patternMatcher.lookingAt()) {
            throw new IllegalArgumentException("Invalid pattern");
        }
        
        currentFormatField= patternMatcher.group();
        Strategy currentStrategy= getStrategy(currentFormatField);
        for(;;) {
            patternMatcher.region(patternMatcher.end(), patternMatcher.regionEnd());
            if(!patternMatcher.lookingAt()) {
                nextStrategy = null;
                break;
            }
            String nextFormatField= patternMatcher.group();
            nextStrategy = getStrategy(nextFormatField);
            if(currentStrategy.addRegex(this, regex)) {
                collector.add(currentStrategy);                
            }
            currentFormatField= nextFormatField;
            currentStrategy= nextStrategy;
        }
        if(currentStrategy.addRegex(this, regex)) {
            collector.add(currentStrategy);                
        }
        currentFormatField= null;
        strategies= collector.toArray(new Strategy[collector.size()]);
        parsePattern= Pattern.compile(regex.toString());
    }
    
    // Accessors
    //-----------------------------------------------------------------------
    /* (non-Javadoc)
     * @see org.apache.commons.lang3.time.DateParser#getPattern()
     */
    public String getPattern() {
        return pattern;
    }

    /* (non-Javadoc)
     * @see org.apache.commons.lang3.time.DateParser#getTimeZone()
     */
    public TimeZone getTimeZone() {
        return timeZone;
    }

    /* (non-Javadoc)
     * @see org.apache.commons.lang3.time.DateParser#getLocale()
     */
    public Locale getLocale() {
        return locale;
    }

    // Basics
    //-----------------------------------------------------------------------
    /**
     * <p>Compare another object for equality with this object.</p>
     * 
     * @param obj  the object to compare to
     * @return <code>true</code>if equal to this instance
     */
    @Override
    public boolean equals(Object obj) {
        if (! (obj instanceof FastDateParser) ) {
            return false;
        }
        FastDateParser other = (FastDateParser) obj;
        return pattern.equals(other.pattern)
            && timeZone.equals(other.timeZone) 
            && locale.equals(other.locale);
    }

    /**
     * <p>Return a hashcode compatible with equals.</p>
     * 
     * @return a hashcode compatible with equals
     */
    @Override
    public int hashCode() {
        return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode());
    }

    /**
     * <p>Get a string version of this formatter.</p>
     * 
     * @return a debugging string
     */
    @Override
    public String toString() {
        return "FastDateParser[" + pattern + "," + locale + "," + timeZone.getID() + "]";
    }

    // Serializing
    //-----------------------------------------------------------------------
    /**
     * Create the object after serialization. This implementation reinitializes the 
     * transient properties.
     *
     * @param in ObjectInputStream from which the object is being deserialized.
     * @throws IOException if there is an IO issue.
     * @throws ClassNotFoundException if a class cannot be found.
     */
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        init();
    }
    
    /* (non-Javadoc)
     * @see org.apache.commons.lang3.time.DateParser#parseObject(java.lang.String)
     */
    public Object parseObject(String source) throws ParseException {
        return parse(source);
    }

    /* (non-Javadoc)
     * @see org.apache.commons.lang3.time.DateParser#parse(java.lang.String)
     */
    public Date parse(String source) throws ParseException {
        Date date= parse(source, new ParsePosition(0));
        if(date==null) {
            throw new ParseException(source+" does not match "+parsePattern.pattern(), 0);            
        }
        return date;
    }

    /* (non-Javadoc)
     * @see org.apache.commons.lang3.time.DateParser#parseObject(java.lang.String, java.text.ParsePosition)
     */
    public Object parseObject(String source, ParsePosition pos) {
        return parse(source, pos);
    }

    /* (non-Javadoc)
     * @see org.apache.commons.lang3.time.DateParser#parse(java.lang.String, java.text.ParsePosition)
     */
    public Date parse(String source, ParsePosition pos) {
        int offset= pos.getIndex();
        Matcher matcher= parsePattern.matcher(source.substring(offset));
        if(!matcher.lookingAt()) {
            return null;
        }
        // timing tests indicate getting new instance is 19% faster than cloning
        Calendar cal= Calendar.getInstance(timeZone, locale);
        cal.clear();

        for(int i=0; i<strategies.length;) {
            Strategy strategy= strategies[i++];
            strategy.setCalendar(this, cal, matcher.group(i));
        }
        pos.setIndex(offset+matcher.end());
        return cal.getTime();
    }
        
    // Support for strategies
    //-----------------------------------------------------------------------

    /**
     * Escape constant fields into regular expression
     * @param regex The destination regex
     * @param value The source field
     * @param unquote If true, replace two success quotes ('') with single quote (')
     * @return The <code>StringBuilder</code>
     */
    private static StringBuilder escapeRegex(StringBuilder regex, String value, boolean unquote) {
        boolean wasWhite= false;
        for(int i= 0; i<value.length(); ++i) {
            char c= value.charAt(i);
            if(Character.isWhitespace(c)) {
                if(!wasWhite) {
                    wasWhite= true;
                    regex.append("\\s*+");
                }
                continue;
            }
            wasWhite= false;
            switch(c) {
            case '\'':
                if(unquote) {
                    if(++i==value.length()) {
                        return regex;
                    }
                    c= value.charAt(i);
                }
                break;
            case '?':
            case '[':
            case ']':
            case '(':
            case ')':
            case '{':
            case '}':
            case '\\':
            case '|':
            case '*':
            case '+':
            case '^':
            case '$':
            case '.':
                regex.append('\\');
            }
            regex.append(c);
        }
        return regex;
    }
    
    /**
     * A class to store Key / Value pairs
     */
    private static class KeyValue {
        public String key;
        public int value;

        /**
         * Construct a Key / Value pair 
         * @param key The key
         * @param value The value
         */
        public KeyValue(String key, int value) {
            this.key= key;
            this.value= value;
        }
    }
    
    /**
     * ignore case comparison of keys
     */
    private static final Comparator<KeyValue> IGNORE_CASE_COMPARATOR = new Comparator<KeyValue> () {
        public int compare(KeyValue left, KeyValue right) {
            return left.key.compareToIgnoreCase(right.key);
        }        
    };

    /**
     * Get the short and long values displayed for a field
     * @param field The field of interest
     * @return A sorted array of the field key / value pairs
     */
    KeyValue[] getDisplayNames(int field) {
        KeyValue[] fieldKeyValues= nameValues.get(field);
        if(fieldKeyValues==null) {
            DateFormatSymbols symbols= DateFormatSymbols.getInstance(locale);
            switch(field) {
            case Calendar.ERA:
                fieldKeyValues= createKeyValues(symbols.getEras(), null);
                break;
            case Calendar.DAY_OF_WEEK:
                fieldKeyValues= createKeyValues(symbols.getWeekdays(), symbols.getShortWeekdays());
                break;
            case Calendar.AM_PM:
                fieldKeyValues= createKeyValues(symbols.getAmPmStrings(), null);
                break;<commit_msg>getInstance(locale) is Java 1.6+<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.lang3.time;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.SortedMap;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>FastDateParser is a fast and thread-safe version of
 * {@link java.text.SimpleDateFormat}.</p>
 * 
 * <p>This class can be used as a direct replacement to
 * <code>SimpleDateFormat</code> in most parsing situations.
 * This class is especially useful in multi-threaded server environments.
 * <code>SimpleDateFormat</code> is not thread-safe in any JDK version,
 * nor will it be as Sun have closed the 
 * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335">bug</a>/RFE.
 * </p>
 *
 * <p>Only parsing is supported, but all patterns are compatible with
 * SimpleDateFormat.</p>
 * 
 * <p>Timing tests indicate this class is as about as fast as SimpleDateFormat
 * in single thread applications and about 25% faster in multi-thread applications.</p>
 *
 * @since 3.2
 */
public class FastDateParser implements DateParser, Serializable {
    /**
     * Required for serialization support.
     * 
     * @see java.io.Serializable
     */
    private static final long serialVersionUID = 1L;
    
    private static final ConcurrentMap<Locale,TimeZoneStrategy> tzsCache= 
        new ConcurrentHashMap<Locale,TimeZoneStrategy>(3);
    
    // defining fields
    private final String pattern;
    private final TimeZone timeZone;
    private final Locale locale;
    
    // derived fields
    private transient Pattern parsePattern;
    private transient Strategy[] strategies;
    private transient int thisYear;
    private transient ConcurrentMap<Integer, KeyValue[]> nameValues;

    // dynamic fields to communicate with Strategy
    private transient String currentFormatField;
    private transient Strategy nextStrategy;

    /**
     * <p>Constructs a new FastDateParser.</p>
     * 
     * @param pattern non-null {@link java.text.SimpleDateFormat} compatible
     *  pattern
     * @param timeZone non-null time zone to use
     * @param locale non-null locale
     */
    protected FastDateParser(String pattern, TimeZone timeZone, Locale locale) {
        this.pattern = pattern;
        this.timeZone = timeZone;
        this.locale = locale;        
        init();
    }
    
    /**
     * Initialize derived fields from defining fields.
     * This is called from constructor and from readObject (de-serialization)   
     */
    private void init() {
        thisYear= Calendar.getInstance(timeZone, locale).get(Calendar.YEAR);
        
        nameValues= new ConcurrentHashMap<Integer, KeyValue[]>();
        
        StringBuilder regex= new StringBuilder();
        List<Strategy> collector = new ArrayList<Strategy>();
        
        Matcher patternMatcher= formatPattern.matcher(pattern);
        if(!patternMatcher.lookingAt()) {
            throw new IllegalArgumentException("Invalid pattern");
        }
        
        currentFormatField= patternMatcher.group();
        Strategy currentStrategy= getStrategy(currentFormatField);
        for(;;) {
            patternMatcher.region(patternMatcher.end(), patternMatcher.regionEnd());
            if(!patternMatcher.lookingAt()) {
                nextStrategy = null;
                break;
            }
            String nextFormatField= patternMatcher.group();
            nextStrategy = getStrategy(nextFormatField);
            if(currentStrategy.addRegex(this, regex)) {
                collector.add(currentStrategy);                
            }
            currentFormatField= nextFormatField;
            currentStrategy= nextStrategy;
        }
        if(currentStrategy.addRegex(this, regex)) {
            collector.add(currentStrategy);                
        }
        currentFormatField= null;
        strategies= collector.toArray(new Strategy[collector.size()]);
        parsePattern= Pattern.compile(regex.toString());
    }
    
    // Accessors
    //-----------------------------------------------------------------------
    /* (non-Javadoc)
     * @see org.apache.commons.lang3.time.DateParser#getPattern()
     */
    public String getPattern() {
        return pattern;
    }

    /* (non-Javadoc)
     * @see org.apache.commons.lang3.time.DateParser#getTimeZone()
     */
    public TimeZone getTimeZone() {
        return timeZone;
    }

    /* (non-Javadoc)
     * @see org.apache.commons.lang3.time.DateParser#getLocale()
     */
    public Locale getLocale() {
        return locale;
    }

    // Basics
    //-----------------------------------------------------------------------
    /**
     * <p>Compare another object for equality with this object.</p>
     * 
     * @param obj  the object to compare to
     * @return <code>true</code>if equal to this instance
     */
    @Override
    public boolean equals(Object obj) {
        if (! (obj instanceof FastDateParser) ) {
            return false;
        }
        FastDateParser other = (FastDateParser) obj;
        return pattern.equals(other.pattern)
            && timeZone.equals(other.timeZone) 
            && locale.equals(other.locale);
    }

    /**
     * <p>Return a hashcode compatible with equals.</p>
     * 
     * @return a hashcode compatible with equals
     */
    @Override
    public int hashCode() {
        return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode());
    }

    /**
     * <p>Get a string version of this formatter.</p>
     * 
     * @return a debugging string
     */
    @Override
    public String toString() {
        return "FastDateParser[" + pattern + "," + locale + "," + timeZone.getID() + "]";
    }

    // Serializing
    //-----------------------------------------------------------------------
    /**
     * Create the object after serialization. This implementation reinitializes the 
     * transient properties.
     *
     * @param in ObjectInputStream from which the object is being deserialized.
     * @throws IOException if there is an IO issue.
     * @throws ClassNotFoundException if a class cannot be found.
     */
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        init();
    }
    
    /* (non-Javadoc)
     * @see org.apache.commons.lang3.time.DateParser#parseObject(java.lang.String)
     */
    public Object parseObject(String source) throws ParseException {
        return parse(source);
    }

    /* (non-Javadoc)
     * @see org.apache.commons.lang3.time.DateParser#parse(java.lang.String)
     */
    public Date parse(String source) throws ParseException {
        Date date= parse(source, new ParsePosition(0));
        if(date==null) {
            throw new ParseException(source+" does not match "+parsePattern.pattern(), 0);            
        }
        return date;
    }

    /* (non-Javadoc)
     * @see org.apache.commons.lang3.time.DateParser#parseObject(java.lang.String, java.text.ParsePosition)
     */
    public Object parseObject(String source, ParsePosition pos) {
        return parse(source, pos);
    }

    /* (non-Javadoc)
     * @see org.apache.commons.lang3.time.DateParser#parse(java.lang.String, java.text.ParsePosition)
     */
    public Date parse(String source, ParsePosition pos) {
        int offset= pos.getIndex();
        Matcher matcher= parsePattern.matcher(source.substring(offset));
        if(!matcher.lookingAt()) {
            return null;
        }
        // timing tests indicate getting new instance is 19% faster than cloning
        Calendar cal= Calendar.getInstance(timeZone, locale);
        cal.clear();

        for(int i=0; i<strategies.length;) {
            Strategy strategy= strategies[i++];
            strategy.setCalendar(this, cal, matcher.group(i));
        }
        pos.setIndex(offset+matcher.end());
        return cal.getTime();
    }
        
    // Support for strategies
    //-----------------------------------------------------------------------

    /**
     * Escape constant fields into regular expression
     * @param regex The destination regex
     * @param value The source field
     * @param unquote If true, replace two success quotes ('') with single quote (')
     * @return The <code>StringBuilder</code>
     */
    private static StringBuilder escapeRegex(StringBuilder regex, String value, boolean unquote) {
        boolean wasWhite= false;
        for(int i= 0; i<value.length(); ++i) {
            char c= value.charAt(i);
            if(Character.isWhitespace(c)) {
                if(!wasWhite) {
                    wasWhite= true;
                    regex.append("\\s*+");
                }
                continue;
            }
            wasWhite= false;
            switch(c) {
            case '\'':
                if(unquote) {
                    if(++i==value.length()) {
                        return regex;
                    }
                    c= value.charAt(i);
                }
                break;
            case '?':
            case '[':
            case ']':
            case '(':
            case ')':
            case '{':
            case '}':
            case '\\':
            case '|':
            case '*':
            case '+':
            case '^':
            case '$':
            case '.':
                regex.append('\\');
            }
            regex.append(c);
        }
        return regex;
    }
    
    /**
     * A class to store Key / Value pairs
     */
    private static class KeyValue {
        public String key;
        public int value;

        /**
         * Construct a Key / Value pair 
         * @param key The key
         * @param value The value
         */
        public KeyValue(String key, int value) {
            this.key= key;
            this.value= value;
        }
    }
    
    /**
     * ignore case comparison of keys
     */
    private static final Comparator<KeyValue> IGNORE_CASE_COMPARATOR = new Comparator<KeyValue> () {
        public int compare(KeyValue left, KeyValue right) {
            return left.key.compareToIgnoreCase(right.key);
        }        
    };

    /**
     * Get the short and long values displayed for a field
     * @param field The field of interest
     * @return A sorted array of the field key / value pairs
     */
    KeyValue[] getDisplayNames(int field) {
        KeyValue[] fieldKeyValues= nameValues.get(field);
        if(fieldKeyValues==null) {
            DateFormatSymbols symbols= new DateFormatSymbols(locale);
            switch(field) {
            case Calendar.ERA:
                fieldKeyValues= createKeyValues(symbols.getEras(), null);
                break;
            case Calendar.DAY_OF_WEEK:
                fieldKeyValues= createKeyValues(symbols.getWeekdays(), symbols.getShortWeekdays());
                break;
            case Calendar.AM_PM:
                fieldKeyValues= createKeyValues(symbols.getAmPmStrings(), null);
                break;
{'fraction_non_alphanumeric': 0.12296983758700696, 'fraction_numerical': 0.002930760776651606, 'mean_word_length': 2.030467497224621, 'pattern_counts': {'":': 0, '<': 75, '<?xml version=': 0, '>': 71, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4440788', 'n_tokens_mistral': 6506, 'n_tokens_neox': 6305, 'n_words': 1767}