com.jbirdvegas.mgerrit.search.AgeSearch.java Source code

Java tutorial

Introduction

Here is the source code for com.jbirdvegas.mgerrit.search.AgeSearch.java

Source

package com.jbirdvegas.mgerrit.search;

/*
 * Copyright (C) 2013 Android Open Kang Project (AOKP)
 *  Author: Evan Conway (P4R4N01D), 2013
 *
 *  Licensed 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.
 */

import com.jbirdvegas.mgerrit.database.UserChanges;
import com.jbirdvegas.mgerrit.objects.ServerVersion;

import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.DurationFieldType;
import org.joda.time.Instant;
import org.joda.time.Period;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class AgeSearch extends SearchKeyword implements Comparable<AgeSearch> {

    public static final String OP_NAME = "age";

    // Set only if a relative time period was given. Should not be set if mInstant is set
    private Period mPeriod;
    // Set only if an absolute time was given. Should not be set if mPeriod is set
    private Instant mInstant;

    /**
     * An array of supported time units and their corresponding meaning
     *  as a DurationFieldType. This is used in the parsing of the query
     *  parameter.
     */
    private static final HashMap<String, DurationFieldType> replacers;
    static {
        replacers = new HashMap<>();
        replacers.put("s", DurationFieldType.seconds());
        replacers.put("sec", DurationFieldType.seconds());
        replacers.put("secs", DurationFieldType.seconds());
        replacers.put("second", DurationFieldType.seconds());
        replacers.put("seconds", DurationFieldType.seconds());

        replacers.put("m", DurationFieldType.minutes());
        replacers.put("min", DurationFieldType.minutes());
        replacers.put("mins", DurationFieldType.minutes());
        replacers.put("minute", DurationFieldType.minutes());
        replacers.put("minutes", DurationFieldType.minutes());

        replacers.put("h", DurationFieldType.hours());
        replacers.put("hr", DurationFieldType.hours());
        replacers.put("hrs", DurationFieldType.hours());
        replacers.put("hour", DurationFieldType.hours());
        replacers.put("hours", DurationFieldType.hours());

        replacers.put("d", DurationFieldType.days());
        replacers.put("day", DurationFieldType.days());
        replacers.put("days", DurationFieldType.days());

        replacers.put("w", DurationFieldType.weeks());
        replacers.put("week", DurationFieldType.weeks());
        replacers.put("weeks", DurationFieldType.weeks());

        replacers.put("mon", DurationFieldType.months());
        replacers.put("mons", DurationFieldType.months());
        replacers.put("mth", DurationFieldType.months());
        replacers.put("mths", DurationFieldType.months());
        replacers.put("month", DurationFieldType.months());
        replacers.put("months", DurationFieldType.months());

        replacers.put("y", DurationFieldType.years());
        replacers.put("yr", DurationFieldType.years());
        replacers.put("yrs", DurationFieldType.years());
        replacers.put("year", DurationFieldType.years());
        replacers.put("years", DurationFieldType.years());
    }

    /** Used for serialising the period into a string and must be output
     *   in a format that can be re-parsed later */
    protected static PeriodFormatter periodParser = new PeriodFormatterBuilder().appendYears()
            .appendSuffix(" years ").appendMonths().appendSuffix(" months ").appendWeeks().appendSuffix(" weeks ")
            .appendDays().appendSuffix(" days ").appendHours().appendSuffix(" hours ").appendMinutes()
            .appendSuffix(" minutes ").appendSeconds().appendSuffix(" seconds").toFormatter();

    protected static final DateTimeFormatter sInstantFormatter;

    static {
        registerKeyword(OP_NAME, AgeSearch.class);

        sInstantFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").withLocale(Locale.US);
    }

    public AgeSearch(String param, String operator) {
        super(OP_NAME, operator, param);
        parseDate(param);
    }

    public AgeSearch(String param) {
        // We need to extract the operator and the parameter from the string
        this(extractParameter(param), extractOperator(param));
    }

    public AgeSearch(long timestamp, String operator) {
        super(OP_NAME, operator, String.valueOf(timestamp));
        mInstant = new Instant(timestamp);
        mPeriod = null;
    }

    @Override
    public String buildSearch() {
        String operator = getOperator();
        if ("=".equals(operator)) {
            /* Note that since datetime is an SQLite function it must be included
             *  directly in the query */
            return UserChanges.C_UPDATED + " BETWEEN datetime(?) AND datetime(?)";
        } else {
            return UserChanges.C_UPDATED + " " + operator + " datetime(?)";
        }
    }

    @Override
    public String[] getEscapeArgument() {
        DateTime now = new DateTime();
        Period period = mPeriod;

        // Equals: we need to do some arithmetic to get a range from the period
        if ("=".equals(getOperator())) {
            if (period == null) {
                period = new Period(mInstant, Instant.now());
            }
            DateTime earlier = now.minus(adjust(period, +1));
            DateTime later = now.minus(period);
            return new String[] { earlier.toString(), later.toString() };
        } else {
            if (period == null) {
                return new String[] { mInstant.toString() };
            }
            return new String[] { now.minus(period).toString() };
        }
    }

    private static String extractParameter(String param) {
        return param.replaceFirst("[=<>]+", "");
    }

    private void parseDate(String param) {
        try {
            if (param.endsWith("Z")) {
                /* The string representation of an instant includes a Z at the end, but this is not
                 *  a valid format for the parser. */
                String newParam = param.substring(0, param.length() - 1);
                mInstant = Instant.parse(newParam, ISODateTimeFormat.localDateOptionalTimeParser());
            } else {
                mInstant = Instant.parse(param, ISODateTimeFormat.localDateOptionalTimeParser());
            }
            mPeriod = null;
        } catch (IllegalArgumentException ignored) {
            mPeriod = toPeriod(param);
            mInstant = null;
        }
    }

    @Override
    public String toString() {
        return toString(OP_NAME);
    }

    public String toString(String keywordName) {
        /* Use the same format as the one initially provided. I.e. if a
         *  relative time period was set initially, we want a relative period
         *  to come back out. Otherwise it is a different search */
        String operator = getOperator();
        if ("=".equals(operator))
            operator = "";
        String string = keywordName + ":\"" + operator;
        if (mPeriod != null) {
            return string + periodParser.print(mPeriod) + '"';
        } else {
            return string + mInstant.toString() + '"';
        }
    }

    /**
     * Parses a string into a Period object according to the replacers
     *  mapping. This allows for duplicate fields (e.g. seconds being
     *  declared twice as in "2s 3 sec") with the duplicate fields being
     *  added together (the above example would be the same as "5 seconds").
     * The order of the fields is not important.
     *
     * @param dateOffset The parameter without the operator. If the operator
     *                   is passed in it will be ignored
     * @return A period corresponding to the parsed input string
     */
    private Period toPeriod(final String dateOffset) {
        String regexp = "(\\d+) *([a-zA-z]+)";
        Period period = new Period();

        if (dateOffset == null || dateOffset.isEmpty())
            return period;

        Pattern pattern = Pattern.compile(regexp);
        Matcher matcher = pattern.matcher(dateOffset);
        while (matcher.find()) {
            String svalue = matcher.toMatchResult().group(1);
            DurationFieldType fieldType = replacers.get(matcher.toMatchResult().group(2));
            if (fieldType != null) {
                // Note that both these methods do not modify their objects
                period = period.withFieldAdded(fieldType, Integer.parseInt(svalue));
            }
        }

        return period;
    }

    /**
     * Adds adjustment to the shortest set time range in period. E.g.
     *  period("5 days 3 hours", 1) -> "5 days 4 hours". This will fall
     *  back to adjusting years if no field in the period is set.
     * @param period The period to be adjusted
     * @param adjustment The adjustment. Note that positive values will result
     *                   in larger periods and an earlier time
     * @return The adjusted period
     */
    private Period adjust(final Period period, int adjustment) {
        if (adjustment == 0)
            return period;

        // Order is VERY important here
        LinkedHashMap<Integer, DurationFieldType> map = new LinkedHashMap<>();
        map.put(period.getSeconds(), DurationFieldType.seconds());
        map.put(period.getMinutes(), DurationFieldType.minutes());
        map.put(period.getHours(), DurationFieldType.hours());
        map.put(period.getDays(), DurationFieldType.days());
        map.put(period.getWeeks(), DurationFieldType.weeks());
        map.put(period.getMonths(), DurationFieldType.months());
        map.put(period.getYears(), DurationFieldType.years());

        for (Map.Entry<Integer, DurationFieldType> entry : map.entrySet()) {
            if (entry.getKey() > 0) {
                return period.withFieldAdded(entry.getValue(), adjustment);
            }
        }
        // Fall back to modifying years
        return period.withFieldAdded(DurationFieldType.years(), adjustment);
    }

    /**
     * Calculates the number of days spanned in a period assuming 365 days per year, 30 days per
     * month, 7 days per week, 24 hours per day, 60 minutes per hour and 60 seconds per minute.
     * @param period A period to retrieve the number of standard days for
     * @return The number of days spanned by the period.
     */
    protected static int getDaysInPeriod(final Period period) {
        int totalDays = 0;
        Period temp = new Period(period);
        if (period.getYears() > 0) {
            int years = period.getYears();
            totalDays += 365 * years;
            temp = temp.minusYears(years);
        }
        if (period.getMonths() > 0) {
            int months = period.getMonths();
            totalDays += 30 * period.getMonths();
            temp = temp.minusMonths(months);
        }
        return totalDays + temp.toStandardDays().getDays();
    }

    @Override
    public String getGerritQuery(ServerVersion serverVersion) {
        String operator = getOperator();

        if ("<=".equals(operator) || "<".equals(operator)) {
            return BeforeSearch._getGerritQuery(this, serverVersion);
        } else if (">=".equals(operator) || ">".equals(operator)) {
            return AfterSearch._getGerritQuery(this, serverVersion);
        }

        if (serverVersion != null && serverVersion.isFeatureSupported(ServerVersion.VERSION_BEFORE_SEARCH)
                && mInstant != null) {
            // Use a combination of before and after to get an interval
            if (mPeriod == null) {
                mPeriod = new Period(mInstant, Instant.now());
            }
            DateTime now = new DateTime();
            DateTime earlier = now.minus(adjust(mPeriod, +1));
            DateTime later = now.minus(mPeriod);

            SearchKeyword newer = new AfterSearch(earlier.toString());
            SearchKeyword older = new BeforeSearch(later.toString());
            return newer.getGerritQuery(serverVersion) + "+" + older.getGerritQuery(serverVersion);
        } else {
            // Need to leave off the operator and make sure we are using relative format
            /* Gerrit only supports specifying one time unit, so we will normalize the period
             *  into days.  */
            return OP_NAME + ":" + String.valueOf(toDays()) + "d";
        }
    }

    protected int toDays() {
        // Need to leave off the operator and make sure we are using relative format
        Period period = mPeriod;
        if (period == null) {
            period = new Period(mInstant, Instant.now());
        }
        /* Gerrit only supports specifying one time unit, so we will normalize the period
         *  into days.  */
        return getDaysInPeriod(period);
    }

    protected Period getPeriod() {
        return mPeriod;
    }

    protected Instant getInstant() {
        return mInstant;
    }

    protected static Instant getInstantFromPeriod(Period period) {
        Instant now = new Instant();
        Duration duration = period.toDurationTo(now);
        return now.minus(duration);
    }

    @Override
    public int compareTo(AgeSearch rhs) {
        if (this.equals(rhs))
            return 0;
        else if (mInstant != null && rhs.mInstant != null) {
            return mInstant.compareTo(rhs.mInstant);
        } else {
            // Compare the normalised period format (i.e. the period in days)
            return toDays() - rhs.toDays();
        }
    }
}