All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hudson.scheduler.CronTab Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 *
 * Copyright (c) 2004-2009 Oracle Corporation.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors: 
*
*    Kohsuke Kawaguchi, InfraDNA, Inc.
 *     
 *
 *******************************************************************************/ 

package hudson.scheduler;

import antlr.ANTLRException;

import java.io.StringReader;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;

import static java.util.Calendar.*;

/**
 * Table for driving scheduled tasks.
 *
 * @author Kohsuke Kawaguchi
 */
public final class CronTab {
    /**
     * bits[0]: minutes
     * bits[1]: hours
     * bits[2]: days
     * bits[3]: months
     *
     * false:not scheduled <-> true scheduled
     */
    final long[] bits = new long[4];

    int dayOfWeek;

    /**
     * Textual representation.
     */
    private String spec;

    public CronTab(String format) throws ANTLRException {
        this(format,1);
    }

    public CronTab(String format, int line) throws ANTLRException {
        set(format, line);
    }

    private void set(String format, int line) throws ANTLRException {
        CrontabLexer lexer = new CrontabLexer(new StringReader(format));
        lexer.setLine(line);
        CrontabParser parser = new CrontabParser(lexer);
        spec = format;

        parser.startRule(this);
        if((dayOfWeek&(1<<7))!=0)
            dayOfWeek |= 1; // copy bit 7 over to bit 0
    }


    /**
     * Returns true if the given calendar matches
     */
    boolean check(Calendar cal) {
        if(!checkBits(bits[0],cal.get(MINUTE)))
            return false;
        if(!checkBits(bits[1],cal.get(HOUR_OF_DAY)))
            return false;
        if(!checkBits(bits[2],cal.get(DAY_OF_MONTH)))
            return false;
        if(!checkBits(bits[3],cal.get(MONTH)+1))
            return false;
        if(!checkBits(dayOfWeek,cal.get(Calendar.DAY_OF_WEEK)-1))
            return false;

        return true;
    }

    private static abstract class CalendarField {
        /**
         * {@link Calendar} field ID.
         */
        final int field;
        /**
         * Lower field is a calendar field whose value needs to be reset when we change the value in this field.
         * For example, if we modify the value in HOUR, MINUTES must be reset.
         */
        final CalendarField lowerField;
        /**
         * Whether this field is 0-origin or 1-origin differs between Crontab and {@link Calendar},
         * so this field adjusts that. If crontab is 1 origin and calendar is 0 origin,  this field is 1
         * that is the value is {@code (cronOrigin-calendarOrigin)}
         */
        final int offset;
        /**
         * When we reset this field, we set the field to this value.
         * For example, resetting {@link Calendar#DAY_OF_MONTH} means setting it to 1.
         */
        final int min;
        /**
         * If this calendar field has other aliases such that a change in this field
         * modifies other field values, then true.
         */
        final boolean redoAdjustmentIfModified;

        /**
         * What is this field? Useful for debugging
         */
        private final String displayName;

        private CalendarField(String displayName, int field, int min, int offset, boolean redoAdjustmentIfModified, CalendarField lowerField) {
            this.displayName = displayName;
            this.field = field;
            this.min = min;
            this.redoAdjustmentIfModified= redoAdjustmentIfModified;
            this.lowerField = lowerField;
            this.offset = offset;
        }

        /**
         * Gets the current value of this field in the given calendar.
         */
        int valueOf(Calendar c) {
            return c.get(field)+offset;
        }

        void addTo(Calendar c, int i) {
            c.add(field,i);
        }

        void setTo(Calendar c, int i) {
            c.set(field,i-offset);
        }

        void clear(Calendar c) {
            setTo(c, min);
        }

        /**
         * Given the value 'n' (which represents the current value), finds the smallest x such that:
         *  1) x matches the specified {@link CronTab} (as far as this field is concerned.)
         *  2) x>=n   (inclusive)
         *
         * If there's no such bit, return -1. Note that if 'n' already matches the crontab, the same n will be returned.
         */
        private int ceil(CronTab c, int n) {
            long bits = bits(c);
            while ((bits|(1L<60)   return -1;
                n++;
            }
            return n;
        }

        /**
         * Given a bit mask, finds the first bit that's on, and return its index.
         */
        private int first(CronTab c) {
            return ceil(c,0);
        }

        private int floor(CronTab c, int n) {
            long bits = bits(c);
            while ((bits|(1L<
     * More precisely, given the time 't', computes another smallest time x such that:
     *
     * 
    *
  • x >= t (inclusive) *
  • x matches this crontab *
* *

* Note that if t already matches this cron, it's returned as is. */ public Calendar ceil(long t) { Calendar cal = new GregorianCalendar(Locale.US); cal.setTimeInMillis(t); return ceil(cal); } /** * See {@link #ceil(long)}. * * This method modifies the given calendar and returns the same object. */ public Calendar ceil(Calendar cal) { OUTER: while (true) { for (CalendarField f : CalendarField.ADJUST_ORDER) { int cur = f.valueOf(cal); int next = f.ceil(this,cur); if (cur==next) continue; // this field is already in a good shape. move on to next // we are modifying this field, so clear all the lower level fields for (CalendarField l=f.lowerField; l!=null; l=l.lowerField) l.clear(cal); if (next<0) { // we need to roll over to the next field. f.rollUp(cal, 1); f.setTo(cal,f.first(this)); // since higher order field is affected by this, we need to restart from all over continue OUTER; } else { f.setTo(cal,next); if (f.redoAdjustmentIfModified) continue OUTER; // when we modify DAY_OF_MONTH and DAY_OF_WEEK, do it all over from the top } } return cal; // all fields adjusted } } /** * Computes the nearest past timestamp that matched this cron tab. *

* More precisely, given the time 't', computes another smallest time x such that: * *

    *
  • x <= t (inclusive) *
  • x matches this crontab *
* *

* Note that if t already matches this cron, it's returned as is. */ public Calendar floor(long t) { Calendar cal = new GregorianCalendar(Locale.US); cal.setTimeInMillis(t); return floor(cal); } /** * See {@link #floor(long)} * * This method modifies the given calendar and returns the same object. */ public Calendar floor(Calendar cal) { OUTER: while (true) { for (CalendarField f : CalendarField.ADJUST_ORDER) { int cur = f.valueOf(cal); int next = f.floor(this,cur); if (cur==next) continue; // this field is already in a good shape. move on to next // we are modifying this field, so clear all the lower level fields for (CalendarField l=f.lowerField; l!=null; l=l.lowerField) l.clear(cal); if (next<0) { // we need to borrow from the next field. f.rollUp(cal,-1); // the problem here, in contrast with the ceil method, is that // the maximum value of the field is not always a fixed value (that is, day of month) // so we zero-clear all the lower fields, set the desired value +1, f.setTo(cal,f.last(this)); f.addTo(cal,1); // then subtract a minute to achieve maximum values on all the lower fields, // with the desired value in 'f' CalendarField.MINUTE.addTo(cal,-1); // since higher order field is affected by this, we need to restart from all over continue OUTER; } else { f.setTo(cal,next); f.addTo(cal,1); CalendarField.MINUTE.addTo(cal,-1); if (f.redoAdjustmentIfModified) continue OUTER; // when we modify DAY_OF_MONTH and DAY_OF_WEEK, do it all over from the top } } return cal; // all fields adjusted } } void set(String format) throws ANTLRException { set(format,1); } /** * Returns true if n-th bit is on. */ private boolean checkBits(long bitMask, int n) { return (bitMask|(1L<





© 2015 - 2025 Weber Informatics LLC | Privacy Policy