Package com.foundationdb.server.types.aksql.aktypes

Source Code of com.foundationdb.server.types.aksql.aktypes.AkInterval$AkIntervalParser$ParseCompilation

/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.foundationdb.server.types.aksql.aktypes;

import com.foundationdb.server.error.AkibanInternalException;
import com.foundationdb.server.types.Attribute;
import com.foundationdb.server.types.IllegalNameException;
import com.foundationdb.server.types.TBundleID;
import com.foundationdb.server.types.TClass;
import com.foundationdb.server.types.TClassBase;
import com.foundationdb.server.types.TClassFormatter;
import com.foundationdb.server.types.TExecutionContext;
import com.foundationdb.server.types.TInstance;
import com.foundationdb.server.types.TParser;
import com.foundationdb.server.types.FormatOptions;
import com.foundationdb.server.types.aksql.AkBundle;
import com.foundationdb.server.types.aksql.AkCategory;
import com.foundationdb.server.types.value.UnderlyingType;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.server.types.value.ValueTarget;
import com.foundationdb.sql.types.DataTypeDescriptor;
import com.foundationdb.sql.types.TypeId;
import com.foundationdb.util.AkibanAppender;
import com.google.common.math.LongMath;

import java.sql.Types;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class AkInterval extends TClassBase {

    public TClass widestComparable()
    {
        return this;
    }
   
    private static TClassFormatter monthsFormatter = new TClassFormatter() {
        @Override
        public void format(TInstance type, ValueSource source, AkibanAppender out) {
            long months = source.getInt64();
            boolean negative = false;
            if(months < 0) {
                negative = true;
                months = -months;
            }
            long years = months / 12;
            months -= (years * 12);
            Formatter formatter = new Formatter(out.getAppendable());
            if(negative)
                formatter.format("INTERVAL '-%d-%d'", years, months);
            else
                formatter.format("INTERVAL '%d-%d'", years, months);

        }

        @Override
        public void formatAsLiteral(TInstance type, ValueSource source, AkibanAppender out) {
            long value = source.getInt64();
            Formatter formatter = new Formatter(out.getAppendable());
            out.append("INTERVAL '");
            long years, months;
            if (value < 0) {
                out.append('-');
                months = -value;
            }
            else {
                months = value;
            }
            years = months / 12;
            months -= years * 12;
            String hi = null, lo = null;
            if (years > 0) {
                formatter.format("%d", years);
                hi = lo = "YEAR";
            }
            if ((months > 0) || (hi == null)) {
                if (hi != null) {
                    formatter.format("-%02d", months);
                }
                else {
                    formatter.format("%d", months);
                }
                lo = "MONTH";
                if (hi == null) hi = lo;
            }
            out.append("' ");
            out.append(hi);
            if (hi != lo) {
                out.append(" TO ");
                out.append(lo);
            }
        }

        @Override
        public void formatAsJson(TInstance type, ValueSource source, AkibanAppender out, FormatOptions options) {
            long months = source.getInt64();
            out.append(Long.toString(months));
        }
    };

    private static TClassFormatter secondsFormatter = new TClassFormatter() {
        @Override
        public void format(TInstance type, ValueSource source, AkibanAppender out) {

            boolean negative = false;
            long micros = secondsIntervalAs(source, TimeUnit.MICROSECONDS);
            if(micros < 0) {
                negative = true;
                micros = -micros;
            }
            long days = secondsIntervalAs(micros, TimeUnit.DAYS);
            micros -= TimeUnit.DAYS.toMicros(days);
            long hours = secondsIntervalAs(micros, TimeUnit.HOURS);
            micros -= TimeUnit.HOURS.toMicros(hours);
            long minutes = secondsIntervalAs(micros, TimeUnit.MINUTES);
            micros -= TimeUnit.MINUTES.toMicros(minutes);
            long seconds = secondsIntervalAs(micros, TimeUnit.SECONDS);
            micros -= TimeUnit.SECONDS.toMicros(seconds);

            Formatter formatter = new Formatter(out.getAppendable());
            if(negative)
                formatter.format("INTERVAL '-%d %d:%d:%d.%05d'", days, hours, minutes, seconds, micros);
            else
                formatter.format("INTERVAL '%d %d:%d:%d.%05d'", days, hours, minutes, seconds, micros);

        }

        @Override
        public void formatAsLiteral(TInstance type, ValueSource source, AkibanAppender out) {
            long value = secondsIntervalAs(source, TimeUnit.MICROSECONDS);
            Formatter formatter = new Formatter(out.getAppendable());
            out.append("INTERVAL '");
            long days, hours, mins, secs, micros;
            if (value < 0) {
                out.append('-');
                micros = -value;
            }
            else {
                micros = value;
            }
            // Could be data-driven, but just enough special cases that
            // that would be pretty complicated.
            secs = micros / 1000000;
            micros -= secs * 1000000;
            mins = secs / 60;
            secs -= mins * 60;
            hours = mins / 60;
            mins -= hours * 60;
            days = hours / 24;
            hours -= days * 24;
            String hi = null, lo = null;
            if (days > 0) {
                formatter.format("%d", days);
                hi = lo = "DAY";
            }
            if ((hours > 0) ||
                ((hi != null) && ((mins > 0) || (secs > 0) || (micros > 0)))) {
                if (hi != null) {
                    formatter.format(":%02d", hours);
                }
                else {
                    formatter.format("%d", hours);
                }
                lo = "HOUR";
                if (hi == null) hi = lo;
            }
            if ((mins > 0) ||
                ((hi != null) && ((secs > 0) || (micros > 0)))) {
                if (hi != null) {
                    formatter.format(":%02d", mins);
                }
                else {
                    formatter.format("%d", mins);
                }
                lo = "MINUTE";
                if (hi == null) hi = lo;
            }
            if ((secs > 0) || (hi == null) || (micros > 0)) {
                if (hi != null) {
                    formatter.format(":%02d", secs);
                }
                else {
                    formatter.format("%d", secs);
                }
                lo = "SECOND";
                if (hi == null) hi = lo;
            }
            if (micros > 0) {
                if ((micros % 1000) == 0)
                    formatter.format(".%03d", micros / 1000);
                else
                    formatter.format(".%06d", micros);
            }
            out.append("' ");
            out.append(hi);
            if (hi != lo) {
                out.append(" TO ");
                out.append(lo);
            }
        }

        @Override
        public void formatAsJson(TInstance type, ValueSource source, AkibanAppender out, FormatOptions options) {
            long value = secondsIntervalAs(source, TimeUnit.MICROSECONDS);
            long secs = value / 1000000;
            long micros = value % 1000000;
            Formatter formatter = new Formatter(out.getAppendable());
            formatter.format("%d.%06d", secs, micros);
        }
    };

    /**
     * A MONTHS interval, whose 64-bit value represents number of months.
     */
    public static final AkInterval MONTHS = new AkInterval(
            AkBundle.INSTANCE.id(),
            "interval months",
            AkCategory.DATE_TIME,
            MonthsAttrs.class,
            monthsFormatter,
            1,
            1,
            8,
            UnderlyingType.INT_64,
            MonthsAttrs.FORMAT,
            AkIntervalMonthsFormat.values());

    /**
     * <p>A SECONDS interval, whose value 64-bit value does <em>not</em> necessarily represent number of seconds.
     * In fact, it almost definitely is not number of seconds; instead, the value is in some private unit. That said,
     * it will still be a unit of time, so you can work with it intuitively. For instance, adding two values of
     * the raw form will result in a number in the same unit that represents the sum of the two durations, and
     * multiply the raw form by some number K will result in a duration K times as long as the original. The value
     * 0 represents no time. In short, you can think of the unit as "Fooseconds", where Foo might be micro, nano,
     * or something else but similar.</p>
     *
     * <p>To get values of this TClass in a meaningful way, you should use one of the {@linkplain #secondsIntervalAs}
     * overloads, specifying the units you want. Units will truncate (not round) their values, as is standard in the
     * JDK's TimeUnit implementation.</p>
     *
     * <p>If you have a value in some format, and want to convert it to the SECONDS raw format, use
     * {@linkplain #secondsRawFrom(long, TimeUnit)} or {@linkplain #secondsRawFromFractionalSeconds(long)}.
     * The resulting value can be added to other raw SECONDS values intuitively, as explained above.</p>
     */
    public static final AkInterval SECONDS = new AkInterval(
            AkBundle.INSTANCE.id(),
            "interval seconds",
            AkCategory.DATE_TIME,
            SecondsAttrs.class,
            secondsFormatter,
            1,
            1,
            8,
            UnderlyingType.INT_64,
            SecondsAttrs.FORMAT,
            AkIntervalSecondsFormat.values()
    );

    /**
     * Gets the interval from a source, which should correspond to an AkInterval.SECONDS value, in some unit.
     * @param source the source
     * @param as the desired unit
     * @return the source's value in the requested unit
     */
    public static long secondsIntervalAs(ValueSource source, TimeUnit as) {
        return secondsIntervalAs(source.getInt64(), as);
    }

    /**
     * Gets the interval from a raw long, which should correspond to an AkInterval.SECONDS value, in some unit
     * @param secondsIntervalRaw the raw form of the seconds value
     * @param as the desired unit
     * @return the raw value, translated to the requested unit
     */
    public static long secondsIntervalAs(long secondsIntervalRaw, TimeUnit as) {
        return as.convert(secondsIntervalRaw, AkIntervalSecondsFormat.UNDERLYING_UNIT);
    }

    /**
     * Gets a raw SECONDS value from an interval specified in some unit.
     * @param source the interval to translate to the raw form
     * @param sourceUnit the incoming interval's unit
     * @return the raw form
     */
    public static long secondsRawFrom(long source, TimeUnit sourceUnit) {
        return AkIntervalSecondsFormat.UNDERLYING_UNIT.convert(source, sourceUnit);
    }

    /**
     * <p>Gets the raw SECONDS value from a number that represents fractions of a second. For instance, 1 would
     * represent a tenth of a second; 123 would represent 123 milliseconds, etc. Values representing a greater
     * precision than the raw form supports will be truncated. The raw form won't be more precise than nanoseconds.</p>
     *
     * <p>Negative values are fine and are interpreted as if the negative sign were in front of the whole number.</p>
     *
     * <p>Examples:
     * <ul>
     *     <li>123 represents 123 milliseconds, and corresponds to 0.123 seconds.</li>
     *     <li>-4 represents 4 tenths of a second in the past, and corresponds to -0.4 seconds.</li>
     *     <li>123456789444 represents 123456789 nanoseconds, since the trailing 444 are past nanosecond resolution
     *     (and are thus sure to be truncated)</li>
     * </ul>
     * </p>
     * @param source the fractional component of time, as explained above
     * @return the raw form
     */
    public static long secondsRawFromFractionalSeconds(long source) {
        // We'll normalize this to nanoseconds, and then convert those nanos to the underlying unit. This may be
        // slightly inefficient, but it keeps a nice separation of concerns. The JDK's TimeUnit doesn't go further
        // than nanos, so we don't need to, either.
        int numberOfDigits = 0;
        for (long tmp = source; tmp != 0; tmp /= 10)
            ++numberOfDigits;

        final int GOAL = 9;
        int tooManyDigits = numberOfDigits - GOAL;

        if (tooManyDigits > 0) {
            // need to truncate
            while (tooManyDigits-- > 0)
                source /= 10;
        }

        if (tooManyDigits < 0) {
            // need to multiply, so that 1 becomes 100000000
            while (tooManyDigits++ < 0)
                source *= 10;
        }

        // source is now in nanos.
        return secondsRawFrom(source, TimeUnit.NANOSECONDS);
    }

    private static enum SecondsAttrs implements Attribute {
        FORMAT
    }

    private static enum MonthsAttrs implements Attribute {
        FORMAT
    }

    @Override
    public boolean attributeIsPhysical(int attributeIndex) {
        return false;
    }

    @Override
    protected boolean attributeAlwaysDisplayed(int attributeIndex) {
        return false;
    }

    @Override
    public void attributeToString(int attributeIndex, long value, StringBuilder output) {
        if (attributeIndex == formatAttribute.ordinal())
            attributeToString(formatters, value, output);
        else
            super.attributeToString(attributeIndex, value,  output);
       
    }

    @Override
    protected TInstance doPickInstance(TInstance left, TInstance right, boolean suggestedNullability) {
        return instance(suggestedNullability);
    }

    @Override
    public TInstance instance(boolean nullable) {
        return instance(formatAttribute.ordinal(), nullable);
    }

    @Override
    protected void validate(TInstance type) {
        int formatId = type.attribute(formatAttribute);
        if ( (formatId < 0) || (formatId >= formatters.length) )
            throw new IllegalNameException("unrecognized literal format ID: " + formatId);
    }

    @Override
    public int jdbcType() {
        return Types.OTHER;
    }

    @Override
    protected DataTypeDescriptor dataTypeDescriptor(TInstance type) {
        Boolean isNullable = type.nullability(); // on separate line to make NPE easier to catch
        int literalFormatId = type.attribute(formatAttribute);
        IntervalFormat format = formatters[literalFormatId];
        TypeId typeId = format.getTypeId();
        return new DataTypeDescriptor(typeId, isNullable);
    }

    public TInstance typeFrom(DataTypeDescriptor type) {
        TypeId typeId = type.getTypeId();
        IntervalFormat format = typeIdToFormat.get(typeId);
        if (format == null)
            throw new IllegalArgumentException("couldn't convert " + type + " to " + name());
        return instance(format.ordinal(), type.isNullable());
    }

    private <A extends Enum<A> & Attribute> AkInterval(TBundleID bundle, String name,
                                               Enum<?> category, Class<A> enumClass,
                                               TClassFormatter formatter,
                                               int internalRepVersion, int sVersion, int sSize,
                                               UnderlyingType underlyingType,
                                               A formatAttribute,
                                               IntervalFormat[] formatters)
    {
        super(bundle, name, category, enumClass, formatter, internalRepVersion, sVersion, sSize, underlyingType,
                createParser(formatAttribute, formatters), 128); // varchar len is arbitrary; I don't expect to use it
        this.formatters = formatters;
        this.formatAttribute = formatAttribute;
        this.typeIdToFormat = createTypeIdToFormatMap(formatters);

    }

    public boolean isDate(TInstance ins)
    {
        if (ins.typeClass() instanceof AkInterval)
            return formatters[0] instanceof AkIntervalMonthsFormat
                    || formatters[ins.attribute(formatAttribute)] == AkIntervalSecondsFormat.DAY;
        else
            return false;
    }
   
    public boolean isTime(TInstance ins)
    {
        if (ins.typeClass() instanceof AkInterval)
            return !isDate(ins);
        else
            return false;
    }

    private static void attributeToString(IntervalFormat[] formatters, long arrayIndex, StringBuilder output) {
        if ( (formatters == null) || (arrayIndex < 0) || arrayIndex >= formatters.length)
            output.append(arrayIndex);
        else
            output.append(formatters[(int)arrayIndex]);
    }

    private final IntervalFormat[] formatters;
    private final Attribute formatAttribute;
    private final Map<TypeId,IntervalFormat> typeIdToFormat;

    interface IntervalFormat {
        long parse(String string);
        TypeId getTypeId();
        int ordinal();
    }

    private static <F extends IntervalFormat> TParser createParser(final Attribute formatAttribute,
                                                                   final F[] formatters)
    {
        return new TParser() {
            @Override
            public void parse(TExecutionContext context, ValueSource in, ValueTarget out) {
                TInstance instance = context.outputType();
                int literalFormatId = instance.attribute(formatAttribute);
                F format = formatters[literalFormatId];
                String inString = in.getString();
                long months = format.parse(inString);
                out.putInt64(months);
            }
        };
    }

    private static <F extends IntervalFormat> Map<TypeId, F> createTypeIdToFormatMap(F[] values) {
        Map<TypeId, F> map = new HashMap<>(values.length);
        for (F literalFormat : values)
            map.put(literalFormat.getTypeId(), literalFormat);
        return map;
    }

    static enum AkIntervalMonthsFormat implements IntervalFormat {
        YEAR("Y+", TypeId.INTERVAL_YEAR_ID),
        MONTH("M+", TypeId.INTERVAL_MONTH_ID),
        YEAR_MONTH("Y+-M?", TypeId.INTERVAL_YEAR_MONTH_ID)
        ;

        @Override
        public TypeId getTypeId() {
            return typeId;
        }

        @Override
        public long parse(String string) {
            return parser.parse(string);
        }

        AkIntervalMonthsFormat(String pattern, TypeId typeId) {
            this.parser = new MonthsParser(this, pattern);
            this.typeId = typeId;

        }

        private final AkIntervalParser<?> parser;
        private final TypeId typeId;

        private static class MonthsParser extends AkIntervalParser<Boolean> {

            private MonthsParser(Enum<?> onBehalfOf, String pattern) {
                super(onBehalfOf, pattern);
            }

            @Override
            protected boolean buildChar(char c, boolean checkBounds, ParseCompilation<? super Boolean> result) {
                switch (c) {
                case 'Y':
                    result.addUnit(Boolean.TRUE, -1, checkBounds);
                    break;
                case 'M':
                    result.addUnit(Boolean.FALSE, 12, checkBounds);
                    break;
                case '-':
                    return true;
                default:
                    throw new IllegalArgumentException("illegal pattern: " + result.inputPattern());
                }
                return false;
            }

            @Override
            protected long parseLong(long parsed, Boolean isYear) {
                if (isYear)
                    parsed = LongMath.checkedMultiply(parsed, 12);
                return parsed;
            }
        }
    }

    static enum AkIntervalSecondsFormat implements IntervalFormat {

        DAY("D+", TypeId.INTERVAL_DAY_ID),
        HOUR("H+", TypeId.INTERVAL_HOUR_ID),
        MINUTE("M+", TypeId.INTERVAL_MINUTE_ID),
        SECOND("S+u", TypeId.INTERVAL_SECOND_ID, true),
        DAY_HOUR("D+ H+", TypeId.INTERVAL_DAY_HOUR_ID),
        DAY_MINUTE("D+ H?:M?", TypeId.INTERVAL_DAY_MINUTE_ID),
        DAY_SECOND("D+ H?:M?:S?u", TypeId.INTERVAL_DAY_SECOND_ID),
        HOUR_MINUTE("H+:M?", TypeId.INTERVAL_HOUR_MINUTE_ID),
        HOUR_SECOND("H+:M?:S?u", TypeId.INTERVAL_HOUR_SECOND_ID),
        MINUTE_SECOND("M+:S?u", TypeId.INTERVAL_MINUTE_SECOND_ID)
        ;

        static TimeUnit UNDERLYING_UNIT = TimeUnit.MICROSECONDS;

        @Override
        public TypeId getTypeId() {
            return typeId;
        }

        @Override
        public long parse(String string) {
            return parser.parse(string);
        }

        AkIntervalSecondsFormat(String pattern, TypeId typeId) {
            this(pattern, typeId, false);
        }

        AkIntervalSecondsFormat(String pattern, TypeId typeId, boolean needsLeadingZero) {
            this.parser = new SecondsParser(this, pattern, needsLeadingZero);
            this.typeId = typeId;
        }

        private final AkIntervalParser<?> parser;
        private final TypeId typeId;

        private static class SecondsParser extends AkIntervalParser<TimeUnit> {

            private SecondsParser(Enum<?> onBehalfOf, String pattern, boolean needsLeadingZero) {
                super(onBehalfOf, pattern);
                this.needsLeadingZero = needsLeadingZero;
            }

            @Override
            protected String preParse(String string) {
                return (needsLeadingZero && (string.charAt(0) == '.'))
                        ? '0' + string
                        : string;
            }

            @Override
            protected boolean buildChar(char c, boolean checkBOunds, ParseCompilation<? super TimeUnit> result) {
                switch (c) {
                case 'D':
                    result.addUnit(TimeUnit.DAYS, 31, checkBOunds);
                    break;
                case 'H':
                    result.addUnit(TimeUnit.HOURS, 32, checkBOunds);
                    break;
                case 'M':
                    result.addUnit(TimeUnit.MINUTES, 59, checkBOunds);
                    break;
                case 'S':
                    result.addUnit(TimeUnit.SECONDS, 59, checkBOunds);
                    break;
                case 'u':
                    result.addUnit(null, -1, checkBOunds); // fractional component
                    break;
                case ' ':
                case ':':
                    return true;
                default:
                    throw new IllegalArgumentException("illegal pattern: " + result.inputPattern());
                }
                return false;
            }

            @Override
            protected String preParseSegment(String string, TimeUnit unit) {
                if (string == null)
                    return "0"; // inefficient because we'll just parse this, but oh well
                if ( (unit == null) && (string.length() > 8) )
                    string = string.substring(0, 9);
                return string;
            }

            @Override
            protected long parseLong(long parsedLong, TimeUnit parsedUnit) {
                if (parsedUnit != null) {
                    return UNDERLYING_UNIT.convert(parsedLong, parsedUnit);
                }
                else {
                    // Fractional seconds component. Need to be careful about how many digits were given.
                    // We'll normalize to nanoseconds, then convert to what we need. This isn't the most efficient,
                    // but it means we can change the underlying scale without having to remember this code.
                    // It's just a couple multiplications and one division, anyway.
                    return secondsRawFromFractionalSeconds(parsedLong);
                }
            }

            private final boolean needsLeadingZero;
        }
    }

    /**
     * A simple parser. The rules are:
     * <ul>
     *     <li>capital letters are special and correspond to numerical digits. If you have a capital letter followed
     *     by a '+', it means one or more digits, and the number's bounds shouldn't be checked. If it's followed by
     *     a '?', it means one or two digits, and the number's bounds should be checked. Otherwise, however many
     *     of the same character are in a row, that's how many digits are required (no more, no less). For instance,
     *     <tt>Y+ M? DD</tt> means any number of year digits (and the number can be as big as we want), followed by
     *     1 or 2 month digits (and the number's bounds will be checked), followed by exactly two days digits (and
     *     the number's bounds will be checked). The bounds come from #buildChar</li>
     *     <li></li>
     *     <li>a lowercase 'u' means a fractional component</li>
     *     <li>all other letters are non-special</li>
     * </ul>
     * @param <U>
     */
    static abstract class AkIntervalParser<U> {

        @SuppressWarnings("unchecked")
        public long parse(String string) {
            // string could be a floating-point number
           
            if (units.length == 1)
            {
                try
                {
                    double val = Double.parseDouble(string);
                    return parseLong(Math.round(val), (U)units[0]);
                }
                catch (NumberFormatException e)
                {
                    // does nothing.
                    // Move on to the next step
                }
            }

            boolean isNegative = (string.charAt(0) == '-');
            if (isNegative)
                string = string.substring(1);
            string = preParse(string);
            Matcher matcher = regex.matcher(string);
            if (!matcher.matches())
                throw new AkibanInternalException("couldn't parse string as " + onBehalfOf.name() + ": " + string);
            long result = 0;
            for (int i = 0, len = matcher.groupCount(); i < len; ++i) {
                String group = matcher.group(i+1);
                @SuppressWarnings("unchecked")
                U unit = (U) units[i];
                String preparsedGroup = preParseSegment(group, unit);
                Long longValue = Long.parseLong(preparsedGroup);
                int max = maxes[i];
                if (longValue > max)
                    throw new AkibanInternalException("out of range: " + group + " while parsing " + onBehalfOf);
                long parsed = parseLong(longValue, unit);
                result = LongMath.checkedAdd(result, parsed);
            }
            return isNegative ? -result : result;
        }

        protected abstract boolean buildChar(char c, boolean checkBounds, ParseCompilation<? super U> result);
        protected abstract long parseLong(long value, U unit);

        protected String preParse(String string) {
            return string;
        }

        protected String preParseSegment(String string, U unit) {
            return string;
        }

        protected AkIntervalParser(Enum<?> onBehalfOf, String pattern) {
            ParseCompilation<U> built = compile(pattern);
            this.regex = Pattern.compile(built.patternBuilder.toString());
            this.units = built.unitsList.toArray();
            this.onBehalfOf = onBehalfOf;
            int maxesSize = built.maxes.size();
            this.maxes = new int[maxesSize];
            for (int i = 0; i < maxesSize; ++i) {
                int max = built.maxes.get(i);
                this.maxes[i] = (max >= 0) ? max : Integer.MAX_VALUE;
            }
        }

        private final Enum<?> onBehalfOf;
        private final Pattern regex;
        private final Object[] units;
        private final int[] maxes;

        private static final int WILD_PLUS = -1;
        private static final int WILD_QUESTION = -2;

        private ParseCompilation<U> compile(String pattern) {
            ParseCompilation<U> result = new ParseCompilation<>(pattern);
            for (int i = 0, len = pattern.length(); i < len; ++i) {
                boolean checkBounds = true;
                char c = pattern.charAt(i);
                if (c == 'u') {
                    result.patternBuilder.append("(?:\\.(\\d+))?");
                }
                else if (Character.isUpperCase(c)) {
                    int count;
                    int lookahead = i + 1;
                    if (lookahead == len) {
                        count = 1;
                    }
                    else if (pattern.charAt(lookahead) == '+') {
                        count = WILD_PLUS;
                    }
                    else if (pattern.charAt(lookahead) == '?') {
                        count = WILD_QUESTION;
                    }
                    else {
                        for(; lookahead < len; ++lookahead) {
                            if (pattern.charAt(lookahead) != c)
                                break;
                        }
                        count = lookahead - i;
                    }
                    switch (count) {
                    case WILD_PLUS:
                        result.patternBuilder.append("(\\d+)");
                        ++i;
                        checkBounds = false;
                        break;
                    case WILD_QUESTION:
                        result.patternBuilder.append("(\\d{1,2})");
                        ++i;
                        break;
                    default:
                        assert count > 0 : count;
                        result.patternBuilder.append("(\\d{").append(count).append("})");
                        i += (count-1);
                        break;
                    }
                }
                if (buildChar(c, checkBounds, result))
                    result.patternBuilder.append(c);
            }
            return result;
        }

        static class ParseCompilation<U> {

            public void addUnit(U unit, int max, boolean checkBounds) {
                unitsList.add(unit);
                maxes.add(checkBounds ? max : -1);
            }

            public String inputPattern() {
                return inputPattern;
            }

            ParseCompilation(String inputPattern) {
                this.inputPattern = inputPattern;
            }

            private String inputPattern;
            private StringBuilder patternBuilder = new StringBuilder();
            private List<U> unitsList = new ArrayList<>();
            private List<Integer> maxes = new ArrayList<>();
        }
    }
}
TOP

Related Classes of com.foundationdb.server.types.aksql.aktypes.AkInterval$AkIntervalParser$ParseCompilation

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.