Source Code Cross Referenced for FastCronParser.java in  » Cache » OSCache » com » opensymphony » oscache » util » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
C# / C Sharp
C# / CSharp Tutorial
ASP.Net
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
PHP
Python
SQL Server / T-SQL
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Cache » OSCache » com.opensymphony.oscache.util 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         * Copyright (c) 2002-2003 by OpenSymphony
0003:         * All rights reserved.
0004:         */
0005:        package com.opensymphony.oscache.util;
0006:
0007:        import java.text.ParseException;
0008:
0009:        import java.util.*;
0010:        import java.util.Calendar;
0011:
0012:        /**
0013:         * Parses cron expressions and determines at what time in the past is the
0014:         * most recent match for the supplied expression.
0015:         *
0016:         * @author <a href="&#109;a&#105;&#108;&#116;&#111;:chris&#64;swebtec.&#99;&#111;&#109;">Chris Miller</a>
0017:         * @author $Author: ltorunski $
0018:         * @version $Revision: 340 $
0019:         */
0020:        public class FastCronParser {
0021:            private static final int NUMBER_OF_CRON_FIELDS = 5;
0022:            private static final int MINUTE = 0;
0023:            private static final int HOUR = 1;
0024:            private static final int DAY_OF_MONTH = 2;
0025:            private static final int MONTH = 3;
0026:            private static final int DAY_OF_WEEK = 4;
0027:
0028:            // Lookup tables that hold the min/max/size of each of the above field types.
0029:            // These tables are precalculated for performance.
0030:            private static final int[] MIN_VALUE = { 0, 0, 1, 1, 0 };
0031:            private static final int[] MAX_VALUE = { 59, 23, 31, 12, 6 };
0032:
0033:            /**
0034:             * A lookup table holding the number of days in each month (with the obvious exception
0035:             * that February requires special handling).
0036:             */
0037:            private static final int[] DAYS_IN_MONTH = { 31, 29, 31, 30, 31,
0038:                    30, 31, 31, 30, 31, 30, 31 };
0039:
0040:            /**
0041:             * Holds the raw cron expression that this parser is handling.
0042:             */
0043:            private String cronExpression = null;
0044:
0045:            /**
0046:             * This is the main lookup table that holds a parsed cron expression. each long
0047:             * represents one of the above field types. Bits in each long value correspond
0048:             * to one of the possbile field values - eg, for the minute field, bits 0 -> 59 in
0049:             * <code>lookup[MINUTE]</code> map to minutes 0 -> 59 respectively. Bits are set if
0050:             * the corresponding value is enabled. So if the minute field in the cron expression
0051:             * was <code>"0,2-8,50"</code>, bits 0, 2, 3, 4, 5, 6, 7, 8 and 50 will be set.
0052:             * If the cron expression is <code>"*"</code>, the long value is set to
0053:             * <code>Long.MAX_VALUE</code>.
0054:             */
0055:            private long[] lookup = { Long.MAX_VALUE, Long.MAX_VALUE,
0056:                    Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE };
0057:
0058:            /**
0059:             * This is based on the contents of the <code>lookup</code> table. It holds the
0060:             * <em>highest</em> valid field value for each field type.
0061:             */
0062:            private int[] lookupMax = { -1, -1, -1, -1, -1 };
0063:
0064:            /**
0065:             * This is based on the contents of the <code>lookup</code> table. It holds the
0066:             * <em>lowest</em> valid field value for each field type.
0067:             */
0068:            private int[] lookupMin = { Integer.MAX_VALUE, Integer.MAX_VALUE,
0069:                    Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE };
0070:
0071:            /**
0072:             * Creates a FastCronParser that uses a default cron expression of <code>"* * * * *"</cron>.
0073:             * This will match any time that is supplied.
0074:             */
0075:            public FastCronParser() {
0076:            }
0077:
0078:            /**
0079:             * Constructs a new FastCronParser based on the supplied expression.
0080:             *
0081:             * @throws ParseException if the supplied expression is not a valid cron expression.
0082:             */
0083:            public FastCronParser(String cronExpression) throws ParseException {
0084:                setCronExpression(cronExpression);
0085:            }
0086:
0087:            /**
0088:             * Resets the cron expression to the value supplied.
0089:             *
0090:             * @param cronExpression the new cron expression.
0091:             *
0092:             * @throws ParseException if the supplied expression is not a valid cron expression.
0093:             */
0094:            public void setCronExpression(String cronExpression)
0095:                    throws ParseException {
0096:                if (cronExpression == null) {
0097:                    throw new IllegalArgumentException(
0098:                            "Cron time expression cannot be null");
0099:                }
0100:
0101:                this .cronExpression = cronExpression;
0102:                parseExpression(cronExpression);
0103:            }
0104:
0105:            /**
0106:             * Retrieves the current cron expression.
0107:             *
0108:             * @return the current cron expression.
0109:             */
0110:            public String getCronExpression() {
0111:                return this .cronExpression;
0112:            }
0113:
0114:            /**
0115:             * Determines whether this cron expression matches a date/time that is more recent
0116:             * than the one supplied.
0117:             *
0118:             * @param time The time to compare the cron expression against.
0119:             *
0120:             * @return <code>true</code> if the cron expression matches a time that is closer
0121:             * to the current time than the supplied time is, <code>false</code> otherwise.
0122:             */
0123:            public boolean hasMoreRecentMatch(long time) {
0124:                return time < getTimeBefore(System.currentTimeMillis());
0125:            }
0126:
0127:            /**
0128:             * Find the most recent time that matches this cron expression. This time will
0129:             * always be in the past, ie a lower value than the supplied time.
0130:             *
0131:             * @param time The time (in milliseconds) that we're using as our upper bound.
0132:             *
0133:             * @return The time (in milliseconds) when this cron event last occurred.
0134:             */
0135:            public long getTimeBefore(long time) {
0136:                // It would be nice to get rid of the Calendar class for speed, but it's a lot of work...
0137:                // We create this
0138:                Calendar cal = new GregorianCalendar();
0139:                cal.setTimeInMillis(time);
0140:
0141:                int minute = cal.get(Calendar.MINUTE);
0142:                int hour = cal.get(Calendar.HOUR_OF_DAY);
0143:                int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
0144:                int month = cal.get(Calendar.MONTH) + 1; // Calendar is 0-based for this field, and we are 1-based
0145:                int year = cal.get(Calendar.YEAR);
0146:
0147:                long validMinutes = lookup[MINUTE];
0148:                long validHours = lookup[HOUR];
0149:                long validDaysOfMonth = lookup[DAY_OF_MONTH];
0150:                long validMonths = lookup[MONTH];
0151:                long validDaysOfWeek = lookup[DAY_OF_WEEK];
0152:
0153:                // Find out if we have a Day of Week or Day of Month field
0154:                boolean haveDOM = validDaysOfMonth != Long.MAX_VALUE;
0155:                boolean haveDOW = validDaysOfWeek != Long.MAX_VALUE;
0156:
0157:                boolean skippedNonLeapYear = false;
0158:
0159:                while (true) {
0160:                    boolean retry = false;
0161:
0162:                    // Clean up the month if it was wrapped in a previous iteration
0163:                    if (month < 1) {
0164:                        month += 12;
0165:                        year--;
0166:                    }
0167:
0168:                    // get month...................................................
0169:                    boolean found = false;
0170:
0171:                    if (validMonths != Long.MAX_VALUE) {
0172:                        for (int i = month + 11; i > (month - 1); i--) {
0173:                            int testMonth = (i % 12) + 1;
0174:
0175:                            // Check if the month is valid
0176:                            if (((1L << (testMonth - 1)) & validMonths) != 0) {
0177:                                if ((testMonth > month) || skippedNonLeapYear) {
0178:                                    year--;
0179:                                }
0180:
0181:                                // Check there are enough days in this month (catches non leap-years trying to match the 29th Feb)
0182:                                int numDays = numberOfDaysInMonth(testMonth,
0183:                                        year);
0184:
0185:                                if (!haveDOM
0186:                                        || (numDays >= lookupMin[DAY_OF_MONTH])) {
0187:                                    if ((month != testMonth)
0188:                                            || skippedNonLeapYear) {
0189:                                        // New DOM = min(maxDOM, prevDays);  ie, the highest valid value
0190:                                        dayOfMonth = (numDays <= lookupMax[DAY_OF_MONTH]) ? numDays
0191:                                                : lookupMax[DAY_OF_MONTH];
0192:                                        hour = lookupMax[HOUR];
0193:                                        minute = lookupMax[MINUTE];
0194:                                        month = testMonth;
0195:                                    }
0196:
0197:                                    found = true;
0198:                                    break;
0199:                                }
0200:                            }
0201:                        }
0202:
0203:                        skippedNonLeapYear = false;
0204:
0205:                        if (!found) {
0206:                            // The only time we drop out here is when we're searching for the 29th of February and no other date!
0207:                            skippedNonLeapYear = true;
0208:                            continue;
0209:                        }
0210:                    }
0211:
0212:                    // Clean up if the dayOfMonth was wrapped. This takes leap years into account.
0213:                    if (dayOfMonth < 1) {
0214:                        month--;
0215:                        dayOfMonth += numberOfDaysInMonth(month, year);
0216:                        hour = lookupMax[HOUR];
0217:                        continue;
0218:                    }
0219:
0220:                    // get day...................................................
0221:                    if (haveDOM && !haveDOW) { // get day using just the DAY_OF_MONTH token
0222:
0223:                        int daysInThisMonth = numberOfDaysInMonth(month, year);
0224:                        int daysInPreviousMonth = numberOfDaysInMonth(
0225:                                month - 1, year);
0226:
0227:                        // Find the highest valid day that is below the current day
0228:                        for (int i = dayOfMonth + 30; i > (dayOfMonth - 1); i--) {
0229:                            int testDayOfMonth = (i % 31) + 1;
0230:
0231:                            // Skip over any days that don't actually exist (eg 31st April)
0232:                            if ((testDayOfMonth <= dayOfMonth)
0233:                                    && (testDayOfMonth > daysInThisMonth)) {
0234:                                continue;
0235:                            }
0236:
0237:                            if ((testDayOfMonth > dayOfMonth)
0238:                                    && (testDayOfMonth > daysInPreviousMonth)) {
0239:                                continue;
0240:                            }
0241:
0242:                            if (((1L << (testDayOfMonth - 1)) & validDaysOfMonth) != 0) {
0243:                                if (testDayOfMonth > dayOfMonth) {
0244:                                    // We've found a valid day, but we had to move back a month
0245:                                    month--;
0246:                                    retry = true;
0247:                                }
0248:
0249:                                if (dayOfMonth != testDayOfMonth) {
0250:                                    hour = lookupMax[HOUR];
0251:                                    minute = lookupMax[MINUTE];
0252:                                }
0253:
0254:                                dayOfMonth = testDayOfMonth;
0255:                                break;
0256:                            }
0257:                        }
0258:
0259:                        if (retry) {
0260:                            continue;
0261:                        }
0262:                    } else if (haveDOW && !haveDOM) { // get day using just the DAY_OF_WEEK token
0263:
0264:                        int daysLost = 0;
0265:                        int currentDOW = dayOfWeek(dayOfMonth, month, year);
0266:
0267:                        for (int i = currentDOW + 7; i > currentDOW; i--) {
0268:                            int testDOW = i % 7;
0269:
0270:                            if (((1L << testDOW) & validDaysOfWeek) != 0) {
0271:                                dayOfMonth -= daysLost;
0272:
0273:                                if (dayOfMonth < 1) {
0274:                                    // We've wrapped back a month
0275:                                    month--;
0276:                                    dayOfMonth += numberOfDaysInMonth(month,
0277:                                            year);
0278:                                    retry = true;
0279:                                }
0280:
0281:                                if (currentDOW != testDOW) {
0282:                                    hour = lookupMax[HOUR];
0283:                                    minute = lookupMax[MINUTE];
0284:                                }
0285:
0286:                                break;
0287:                            }
0288:
0289:                            daysLost++;
0290:                        }
0291:
0292:                        if (retry) {
0293:                            continue;
0294:                        }
0295:                    }
0296:
0297:                    // Clean up if the hour has been wrapped
0298:                    if (hour < 0) {
0299:                        hour += 24;
0300:                        dayOfMonth--;
0301:                        continue;
0302:                    }
0303:
0304:                    // get hour...................................................
0305:                    if (validHours != Long.MAX_VALUE) {
0306:                        // Find the highest valid hour that is below the current hour
0307:                        for (int i = hour + 24; i > hour; i--) {
0308:                            int testHour = i % 24;
0309:
0310:                            if (((1L << testHour) & validHours) != 0) {
0311:                                if (testHour > hour) {
0312:                                    // We've found an hour, but we had to move back a day
0313:                                    dayOfMonth--;
0314:                                    retry = true;
0315:                                }
0316:
0317:                                if (hour != testHour) {
0318:                                    minute = lookupMax[MINUTE];
0319:                                }
0320:
0321:                                hour = testHour;
0322:                                break;
0323:                            }
0324:                        }
0325:
0326:                        if (retry) {
0327:                            continue;
0328:                        }
0329:                    }
0330:
0331:                    // get minute.................................................
0332:                    if (validMinutes != Long.MAX_VALUE) {
0333:                        // Find the highest valid minute that is below the current minute
0334:                        for (int i = minute + 60; i > minute; i--) {
0335:                            int testMinute = i % 60;
0336:
0337:                            if (((1L << testMinute) & validMinutes) != 0) {
0338:                                if (testMinute > minute) {
0339:                                    // We've found a minute, but we had to move back an hour
0340:                                    hour--;
0341:                                    retry = true;
0342:                                }
0343:
0344:                                minute = testMinute;
0345:                                break;
0346:                            }
0347:                        }
0348:
0349:                        if (retry) {
0350:                            continue;
0351:                        }
0352:                    }
0353:
0354:                    break;
0355:                }
0356:
0357:                // OK, all done. Return the adjusted time value (adjusting this is faster than creating a new Calendar object)
0358:                cal.set(Calendar.YEAR, year);
0359:                cal.set(Calendar.MONTH, month - 1); // Calendar is 0-based for this field, and we are 1-based
0360:                cal.set(Calendar.DAY_OF_MONTH, dayOfMonth);
0361:                cal.set(Calendar.HOUR_OF_DAY, hour);
0362:                cal.set(Calendar.MINUTE, minute);
0363:                cal.set(Calendar.SECOND, 0);
0364:                cal.set(Calendar.MILLISECOND, 0);
0365:
0366:                return cal.getTime().getTime();
0367:            }
0368:
0369:            /**
0370:             * Takes a cron expression as an input parameter, and extracts from it the
0371:             * relevant minutes/hours/days/months that the expression matches.
0372:             *
0373:             * @param expression  A valid cron expression.
0374:             * @throws ParseException If the supplied expression could not be parsed.
0375:             */
0376:            private void parseExpression(String expression)
0377:                    throws ParseException {
0378:                try {
0379:                    // Reset all the lookup data
0380:                    for (int i = 0; i < lookup.length; lookup[i++] = 0) {
0381:                        lookupMin[i] = Integer.MAX_VALUE;
0382:                        lookupMax[i] = -1;
0383:                    }
0384:
0385:                    // Create some character arrays to hold the extracted field values
0386:                    char[][] token = new char[NUMBER_OF_CRON_FIELDS][];
0387:
0388:                    // Extract the supplied expression into another character array
0389:                    // for speed
0390:                    int length = expression.length();
0391:                    char[] expr = new char[length];
0392:                    expression.getChars(0, length, expr, 0);
0393:
0394:                    int field = 0;
0395:                    int startIndex = 0;
0396:                    boolean inWhitespace = true;
0397:
0398:                    // Extract the various cron fields from the expression
0399:                    for (int i = 0; (i < length)
0400:                            && (field < NUMBER_OF_CRON_FIELDS); i++) {
0401:                        boolean haveChar = (expr[i] != ' ')
0402:                                && (expr[i] != '\t');
0403:
0404:                        if (haveChar) {
0405:                            // We have a text character of some sort
0406:                            if (inWhitespace) {
0407:                                startIndex = i; // Remember the start of this token
0408:                                inWhitespace = false;
0409:                            }
0410:                        }
0411:
0412:                        if (i == (length - 1)) { // Adjustment for when we reach the end of the expression
0413:                            i++;
0414:                        }
0415:
0416:                        if (!(haveChar || inWhitespace) || (i == length)) {
0417:                            // We've reached the end of a token. Copy it into a new char array
0418:                            token[field] = new char[i - startIndex];
0419:                            System.arraycopy(expr, startIndex, token[field], 0,
0420:                                    i - startIndex);
0421:                            inWhitespace = true;
0422:                            field++;
0423:                        }
0424:                    }
0425:
0426:                    if (field < NUMBER_OF_CRON_FIELDS) {
0427:                        throw new ParseException(
0428:                                "Unexpected end of expression while parsing \""
0429:                                        + expression
0430:                                        + "\". Cron expressions require 5 separate fields.",
0431:                                length);
0432:                    }
0433:
0434:                    // OK, we've broken the string up into the 5 cron fields, now lets add
0435:                    // each field to their lookup table.
0436:                    for (field = 0; field < NUMBER_OF_CRON_FIELDS; field++) {
0437:                        startIndex = 0;
0438:
0439:                        boolean inDelimiter = true;
0440:
0441:                        // We add each comma-delimited element seperately.
0442:                        int elementLength = token[field].length;
0443:
0444:                        for (int i = 0; i < elementLength; i++) {
0445:                            boolean haveElement = token[field][i] != ',';
0446:
0447:                            if (haveElement) {
0448:                                // We have a character from an element in the token
0449:                                if (inDelimiter) {
0450:                                    startIndex = i;
0451:                                    inDelimiter = false;
0452:                                }
0453:                            }
0454:
0455:                            if (i == (elementLength - 1)) { // Adjustment for when we reach the end of the token
0456:                                i++;
0457:                            }
0458:
0459:                            if (!(haveElement || inDelimiter)
0460:                                    || (i == elementLength)) {
0461:                                // We've reached the end of an element. Copy it into a new char array
0462:                                char[] element = new char[i - startIndex];
0463:                                System.arraycopy(token[field], startIndex,
0464:                                        element, 0, i - startIndex);
0465:
0466:                                // Add the element to our datastructure.
0467:                                storeExpressionValues(element, field);
0468:
0469:                                inDelimiter = true;
0470:                            }
0471:                        }
0472:
0473:                        if (lookup[field] == 0) {
0474:                            throw new ParseException(
0475:                                    "Token "
0476:                                            + new String(token[field])
0477:                                            + " contains no valid entries for this field.",
0478:                                    0);
0479:                        }
0480:                    }
0481:
0482:                    // Remove any months that will never be valid
0483:                    switch (lookupMin[DAY_OF_MONTH]) {
0484:                    case 31:
0485:                        lookup[MONTH] &= (0xFFF - 0x528); // Binary 010100101000 - the months that have 30 days
0486:                    case 30:
0487:                        lookup[MONTH] &= (0xFFF - 0x2); // Binary 000000000010 - February
0488:
0489:                        if (lookup[MONTH] == 0) {
0490:                            throw new ParseException(
0491:                                    "The cron expression \""
0492:                                            + expression
0493:                                            + "\" will never match any months - the day of month field is out of range.",
0494:                                    0);
0495:                        }
0496:                    }
0497:
0498:                    // Check that we don't have both a day of month and a day of week field.
0499:                    if ((lookup[DAY_OF_MONTH] != Long.MAX_VALUE)
0500:                            && (lookup[DAY_OF_WEEK] != Long.MAX_VALUE)) {
0501:                        throw new ParseException(
0502:                                "The cron expression \""
0503:                                        + expression
0504:                                        + "\" is invalid. Having both a day-of-month and day-of-week field is not supported.",
0505:                                0);
0506:                    }
0507:                } catch (Exception e) {
0508:                    if (e instanceof  ParseException) {
0509:                        throw (ParseException) e;
0510:                    } else {
0511:                        throw new ParseException(
0512:                                "Illegal cron expression format ("
0513:                                        + e.toString() + ")", 0);
0514:                    }
0515:                }
0516:            }
0517:
0518:            /**
0519:             * Stores the values for the supplied cron element into the specified field.
0520:             *
0521:             * @param element  The cron element to store. A cron element is a single component
0522:             * of a cron expression. For example, the complete set of elements for the cron expression
0523:             * <code>30 0,6,12,18 * * *</code> would be <code>{"30", "0", "6", "12", "18", "*", "*", "*"}</code>.
0524:             * @param field  The field that this expression belongs to. Valid values are {@link #MINUTE},
0525:             * {@link #HOUR}, {@link #DAY_OF_MONTH}, {@link #MONTH} and {@link #DAY_OF_WEEK}.
0526:             *
0527:             * @throws ParseException if there was a problem parsing the supplied element.
0528:             */
0529:            private void storeExpressionValues(char[] element, int field)
0530:                    throws ParseException {
0531:                int i = 0;
0532:
0533:                int start = -99;
0534:                int end = -99;
0535:                int interval = -1;
0536:                boolean wantValue = true;
0537:                boolean haveInterval = false;
0538:
0539:                while ((interval < 0) && (i < element.length)) {
0540:                    char ch = element[i++];
0541:
0542:                    // Handle the wildcard character - it can only ever occur at the start of an element
0543:                    if ((i == 1) && (ch == '*')) {
0544:                        // Handle the special case where we have '*' and nothing else
0545:                        if (i >= element.length) {
0546:                            addToLookup(-1, -1, field, 1);
0547:                            return;
0548:                        }
0549:
0550:                        start = -1;
0551:                        end = -1;
0552:                        wantValue = false;
0553:                        continue;
0554:                    }
0555:
0556:                    if (wantValue) {
0557:                        // Handle any numbers
0558:                        if ((ch >= '0') && (ch <= '9')) {
0559:                            ValueSet vs = getValue(ch - '0', element, i);
0560:
0561:                            if (start == -99) {
0562:                                start = vs.value;
0563:                            } else if (!haveInterval) {
0564:                                end = vs.value;
0565:                            } else {
0566:                                if (end == -99) {
0567:                                    end = MAX_VALUE[field];
0568:                                }
0569:
0570:                                interval = vs.value;
0571:                            }
0572:
0573:                            i = vs.pos;
0574:                            wantValue = false;
0575:                            continue;
0576:                        }
0577:
0578:                        if (!haveInterval && (end == -99)) {
0579:                            // Handle any months that have been suplied as words
0580:                            if (field == MONTH) {
0581:                                if (start == -99) {
0582:                                    start = getMonthVal(ch, element, i++);
0583:                                } else {
0584:                                    end = getMonthVal(ch, element, i++);
0585:                                }
0586:
0587:                                wantValue = false;
0588:
0589:                                // Skip past the rest of the month name
0590:                                while (++i < element.length) {
0591:                                    int c = element[i] | 0x20;
0592:
0593:                                    if ((c < 'a') || (c > 'z')) {
0594:                                        break;
0595:                                    }
0596:                                }
0597:
0598:                                continue;
0599:                            } else if (field == DAY_OF_WEEK) {
0600:                                if (start == -99) {
0601:                                    start = getDayOfWeekVal(ch, element, i++);
0602:                                } else {
0603:                                    end = getDayOfWeekVal(ch, element, i++);
0604:                                }
0605:
0606:                                wantValue = false;
0607:
0608:                                // Skip past the rest of the day name
0609:                                while (++i < element.length) {
0610:                                    int c = element[i] | 0x20;
0611:
0612:                                    if ((c < 'a') || (c > 'z')) {
0613:                                        break;
0614:                                    }
0615:                                }
0616:
0617:                                continue;
0618:                            }
0619:                        }
0620:                    } else {
0621:                        // Handle the range character. A range character is only valid if we have a start but no end value
0622:                        if ((ch == '-') && (start != -99) && (end == -99)) {
0623:                            wantValue = true;
0624:                            continue;
0625:                        }
0626:
0627:                        // Handle an interval. An interval is valid as long as we have a start value
0628:                        if ((ch == '/') && (start != -99)) {
0629:                            wantValue = true;
0630:                            haveInterval = true;
0631:                            continue;
0632:                        }
0633:                    }
0634:
0635:                    throw makeParseException(
0636:                            "Invalid character encountered while parsing element",
0637:                            element, i);
0638:                }
0639:
0640:                if (element.length > i) {
0641:                    throw makeParseException(
0642:                            "Extraneous characters found while parsing element",
0643:                            element, i);
0644:                }
0645:
0646:                if (end == -99) {
0647:                    end = start;
0648:                }
0649:
0650:                if (interval < 0) {
0651:                    interval = 1;
0652:                }
0653:
0654:                addToLookup(start, end, field, interval);
0655:            }
0656:
0657:            /**
0658:             * Extracts a numerical value from inside a character array.
0659:             *
0660:             * @param value    The value of the first character
0661:             * @param element  The character array we're extracting the value from
0662:             * @param i        The index into the array of the next character to process
0663:             *
0664:             * @return the new index and the extracted value
0665:             */
0666:            private ValueSet getValue(int value, char[] element, int i) {
0667:                ValueSet result = new ValueSet();
0668:                result.value = value;
0669:
0670:                if (i >= element.length) {
0671:                    result.pos = i;
0672:                    return result;
0673:                }
0674:
0675:                char ch = element[i];
0676:
0677:                while ((ch >= '0') && (ch <= '9')) {
0678:                    result.value = (result.value * 10) + (ch - '0');
0679:
0680:                    if (++i >= element.length) {
0681:                        break;
0682:                    }
0683:
0684:                    ch = element[i];
0685:                }
0686:
0687:                result.pos = i;
0688:
0689:                return result;
0690:            }
0691:
0692:            /**
0693:             * Adds a group of valid values to the lookup table for the specified field. This method
0694:             * handles ranges that increase in arbitrary step sizes. It is also possible to add a single
0695:             * value by specifying a range with the same start and end values.
0696:             *
0697:             * @param start The starting value for the range. Supplying a value that is less than zero
0698:             * will cause the minimum allowable value for the specified field to be used as the start value.
0699:             * @param end   The maximum value that can be added (ie the upper bound). If the step size is
0700:             * greater than one, this maximum value may not necessarily end up being added. Supplying a
0701:             * value that is less than zero will cause the maximum allowable value for the specified field
0702:             * to be used as the upper bound.
0703:             * @param field The field that the values should be added to.
0704:             * @param interval Specifies the step size for the range. Any values less than one will be
0705:             * treated as a single step interval.
0706:             */
0707:            private void addToLookup(int start, int end, int field, int interval)
0708:                    throws ParseException {
0709:                // deal with the supplied range
0710:                if (start == end) {
0711:                    if (start < 0) {
0712:                        // We're setting the entire range of values
0713:                        start = lookupMin[field] = MIN_VALUE[field];
0714:                        end = lookupMax[field] = MAX_VALUE[field];
0715:
0716:                        if (interval <= 1) {
0717:                            lookup[field] = Long.MAX_VALUE;
0718:                            return;
0719:                        }
0720:                    } else {
0721:                        // We're only setting a single value - check that it is in range
0722:                        if (start < MIN_VALUE[field]) {
0723:                            throw new ParseException(
0724:                                    "Value "
0725:                                            + start
0726:                                            + " in field "
0727:                                            + field
0728:                                            + " is lower than the minimum allowable value for this field (min="
0729:                                            + MIN_VALUE[field] + ")", 0);
0730:                        } else if (start > MAX_VALUE[field]) {
0731:                            throw new ParseException(
0732:                                    "Value "
0733:                                            + start
0734:                                            + " in field "
0735:                                            + field
0736:                                            + " is higher than the maximum allowable value for this field (max="
0737:                                            + MAX_VALUE[field] + ")", 0);
0738:                        }
0739:                    }
0740:                } else {
0741:                    // For ranges, if the start is bigger than the end value then swap them over
0742:                    if (start > end) {
0743:                        end ^= start;
0744:                        start ^= end;
0745:                        end ^= start;
0746:                    }
0747:
0748:                    if (start < 0) {
0749:                        start = MIN_VALUE[field];
0750:                    } else if (start < MIN_VALUE[field]) {
0751:                        throw new ParseException(
0752:                                "Value "
0753:                                        + start
0754:                                        + " in field "
0755:                                        + field
0756:                                        + " is lower than the minimum allowable value for this field (min="
0757:                                        + MIN_VALUE[field] + ")", 0);
0758:                    }
0759:
0760:                    if (end < 0) {
0761:                        end = MAX_VALUE[field];
0762:                    } else if (end > MAX_VALUE[field]) {
0763:                        throw new ParseException(
0764:                                "Value "
0765:                                        + end
0766:                                        + " in field "
0767:                                        + field
0768:                                        + " is higher than the maximum allowable value for this field (max="
0769:                                        + MAX_VALUE[field] + ")", 0);
0770:                    }
0771:                }
0772:
0773:                if (interval < 1) {
0774:                    interval = 1;
0775:                }
0776:
0777:                int i = start - MIN_VALUE[field];
0778:
0779:                // Populate the lookup table by setting all the bits corresponding to the valid field values
0780:                for (i = start - MIN_VALUE[field]; i <= (end - MIN_VALUE[field]); i += interval) {
0781:                    lookup[field] |= (1L << i);
0782:                }
0783:
0784:                // Make sure we remember the minimum value set so far
0785:                // Keep track of the highest and lowest values that have been added to this field so far
0786:                if (lookupMin[field] > start) {
0787:                    lookupMin[field] = start;
0788:                }
0789:
0790:                i += (MIN_VALUE[field] - interval);
0791:
0792:                if (lookupMax[field] < i) {
0793:                    lookupMax[field] = i;
0794:                }
0795:            }
0796:
0797:            /**
0798:             * Indicates if a year is a leap year or not.
0799:             *
0800:             * @param year The year to check
0801:             *
0802:             * @return <code>true</code> if the year is a leap year, <code>false</code> otherwise.
0803:             */
0804:            private boolean isLeapYear(int year) {
0805:                return (((year % 4) == 0) && ((year % 100) != 0))
0806:                        || ((year % 400) == 0);
0807:            }
0808:
0809:            /**
0810:             * Calculate the day of the week. Sunday = 0, Monday = 1, ... , Saturday = 6. The formula
0811:             * used is an optimized version of Zeller's Congruence.
0812:             *
0813:             * @param day        The day of the month (1-31)
0814:             * @param month      The month (1 - 12)
0815:             * @param year       The year
0816:             * @return
0817:             */
0818:            private int dayOfWeek(int day, int month, int year) {
0819:                day += ((month < 3) ? year-- : (year - 2));
0820:                return ((((23 * month) / 9) + day + 4 + (year / 4))
0821:                        - (year / 100) + (year / 400)) % 7;
0822:            }
0823:
0824:            /**
0825:             * Retrieves the number of days in the supplied month, taking into account leap years.
0826:             * If the month value is outside the range <code>MIN_VALUE[MONTH] - MAX_VALUE[MONTH]</code>
0827:             * then the year will be adjusted accordingly and the correct number of days will still
0828:             * be returned.
0829:             *
0830:             * @param month The month of interest.
0831:             * @param year  The year we are checking.
0832:             *
0833:             * @return The number of days in the month.
0834:             */
0835:            private int numberOfDaysInMonth(int month, int year) {
0836:                while (month < 1) {
0837:                    month += 12;
0838:                    year--;
0839:                }
0840:
0841:                while (month > 12) {
0842:                    month -= 12;
0843:                    year++;
0844:                }
0845:
0846:                if (month == 2) {
0847:                    return isLeapYear(year) ? 29 : 28;
0848:                } else {
0849:                    return DAYS_IN_MONTH[month - 1];
0850:                }
0851:            }
0852:
0853:            /**
0854:             * Quickly retrieves the day of week value (Sun = 0, ... Sat = 6) that corresponds to the
0855:             * day name that is specified in the character array. Only the first 3 characters are taken
0856:             * into account; the rest are ignored.
0857:             *
0858:             * @param element The character array
0859:             * @param i       The index to start looking at
0860:             * @return        The day of week value
0861:             */
0862:            private int getDayOfWeekVal(char ch1, char[] element, int i)
0863:                    throws ParseException {
0864:                if ((i + 1) >= element.length) {
0865:                    throw makeParseException(
0866:                            "Unexpected end of element encountered while parsing a day name",
0867:                            element, i);
0868:                }
0869:
0870:                int ch2 = element[i] | 0x20;
0871:                int ch3 = element[i + 1] | 0x20;
0872:
0873:                switch (ch1 | 0x20) {
0874:                case 's': // Sunday, Saturday
0875:
0876:                    if ((ch2 == 'u') && (ch3 == 'n')) {
0877:                        return 0;
0878:                    }
0879:
0880:                    if ((ch2 == 'a') && (ch3 == 't')) {
0881:                        return 6;
0882:                    }
0883:
0884:                    break;
0885:                case 'm': // Monday
0886:
0887:                    if ((ch2 == 'o') && (ch3 == 'n')) {
0888:                        return 1;
0889:                    }
0890:
0891:                    break;
0892:                case 't': // Tuesday, Thursday
0893:
0894:                    if ((ch2 == 'u') && (ch3 == 'e')) {
0895:                        return 2;
0896:                    }
0897:
0898:                    if ((ch2 == 'h') && (ch3 == 'u')) {
0899:                        return 4;
0900:                    }
0901:
0902:                    break;
0903:                case 'w': // Wednesday
0904:
0905:                    if ((ch2 == 'e') && (ch3 == 'd')) {
0906:                        return 3;
0907:                    }
0908:
0909:                    break;
0910:                case 'f': // Friday
0911:
0912:                    if ((ch2 == 'r') && (ch3 == 'i')) {
0913:                        return 5;
0914:                    }
0915:
0916:                    break;
0917:                }
0918:
0919:                throw makeParseException(
0920:                        "Unexpected character while parsing a day name",
0921:                        element, i - 1);
0922:            }
0923:
0924:            /**
0925:             * Quickly retrieves the month value (Jan = 1, ..., Dec = 12) that corresponds to the month
0926:             * name that is specified in the character array. Only the first 3 characters are taken
0927:             * into account; the rest are ignored.
0928:             *
0929:             * @param element The character array
0930:             * @param i       The index to start looking at
0931:             * @return        The month value
0932:             */
0933:            private int getMonthVal(char ch1, char[] element, int i)
0934:                    throws ParseException {
0935:                if ((i + 1) >= element.length) {
0936:                    throw makeParseException(
0937:                            "Unexpected end of element encountered while parsing a month name",
0938:                            element, i);
0939:                }
0940:
0941:                int ch2 = element[i] | 0x20;
0942:                int ch3 = element[i + 1] | 0x20;
0943:
0944:                switch (ch1 | 0x20) {
0945:                case 'j': // January, June, July
0946:
0947:                    if ((ch2 == 'a') && (ch3 == 'n')) {
0948:                        return 1;
0949:                    }
0950:
0951:                    if (ch2 == 'u') {
0952:                        if (ch3 == 'n') {
0953:                            return 6;
0954:                        }
0955:
0956:                        if (ch3 == 'l') {
0957:                            return 7;
0958:                        }
0959:                    }
0960:
0961:                    break;
0962:                case 'f': // February
0963:
0964:                    if ((ch2 == 'e') && (ch3 == 'b')) {
0965:                        return 2;
0966:                    }
0967:
0968:                    break;
0969:                case 'm': // March, May
0970:
0971:                    if (ch2 == 'a') {
0972:                        if (ch3 == 'r') {
0973:                            return 3;
0974:                        }
0975:
0976:                        if (ch3 == 'y') {
0977:                            return 5;
0978:                        }
0979:                    }
0980:
0981:                    break;
0982:                case 'a': // April, August
0983:
0984:                    if ((ch2 == 'p') && (ch3 == 'r')) {
0985:                        return 4;
0986:                    }
0987:
0988:                    if ((ch2 == 'u') && (ch3 == 'g')) {
0989:                        return 8;
0990:                    }
0991:
0992:                    break;
0993:                case 's': // September
0994:
0995:                    if ((ch2 == 'e') && (ch3 == 'p')) {
0996:                        return 9;
0997:                    }
0998:
0999:                    break;
1000:                case 'o': // October
1001:
1002:                    if ((ch2 == 'c') && (ch3 == 't')) {
1003:                        return 10;
1004:                    }
1005:
1006:                    break;
1007:                case 'n': // November
1008:
1009:                    if ((ch2 == 'o') && (ch3 == 'v')) {
1010:                        return 11;
1011:                    }
1012:
1013:                    break;
1014:                case 'd': // December
1015:
1016:                    if ((ch2 == 'e') && (ch3 == 'c')) {
1017:                        return 12;
1018:                    }
1019:
1020:                    break;
1021:                }
1022:
1023:                throw makeParseException(
1024:                        "Unexpected character while parsing a month name",
1025:                        element, i - 1);
1026:            }
1027:
1028:            /**
1029:             * Recreates the original human-readable cron expression based on the internal
1030:             * datastructure values.
1031:             *
1032:             * @return A cron expression that corresponds to the current state of the
1033:             * internal data structure.
1034:             */
1035:            public String getExpressionSummary() {
1036:                StringBuffer buf = new StringBuffer();
1037:
1038:                buf.append(getExpressionSetSummary(MINUTE)).append(' ');
1039:                buf.append(getExpressionSetSummary(HOUR)).append(' ');
1040:                buf.append(getExpressionSetSummary(DAY_OF_MONTH)).append(' ');
1041:                buf.append(getExpressionSetSummary(MONTH)).append(' ');
1042:                buf.append(getExpressionSetSummary(DAY_OF_WEEK));
1043:
1044:                return buf.toString();
1045:            }
1046:
1047:            /**
1048:             * <p>Converts the internal datastructure that holds a particular cron field into
1049:             * a human-readable list of values of the field's contents. For example, if the
1050:             * <code>DAY_OF_WEEK</code> field was submitted that had Sunday and Monday specified,
1051:             * the string <code>0,1</code> would be returned.</p>
1052:             *
1053:             * <p>If the field contains all possible values, <code>*</code> will be returned.
1054:             *
1055:             * @param field The field.
1056:             *
1057:             * @return A human-readable string representation of the field's contents.
1058:             */
1059:            private String getExpressionSetSummary(int field) {
1060:                if (lookup[field] == Long.MAX_VALUE) {
1061:                    return "*";
1062:                }
1063:
1064:                StringBuffer buf = new StringBuffer();
1065:
1066:                boolean first = true;
1067:
1068:                for (int i = MIN_VALUE[field]; i <= MAX_VALUE[field]; i++) {
1069:                    if ((lookup[field] & (1L << (i - MIN_VALUE[field]))) != 0) {
1070:                        if (!first) {
1071:                            buf.append(",");
1072:                        } else {
1073:                            first = false;
1074:                        }
1075:
1076:                        buf.append(String.valueOf(i));
1077:                    }
1078:                }
1079:
1080:                return buf.toString();
1081:            }
1082:
1083:            /**
1084:             * Makes a <code>ParseException</code>. The exception message is constructed by
1085:             * taking the given message parameter and appending the supplied character data
1086:             * to the end of it. for example, if <code>msg == "Invalid character
1087:             * encountered"</code> and <code>data == {'A','g','u','s','t'}</code>, the resultant
1088:             * error message would be <code>"Invalid character encountered [Agust]"</code>.
1089:             *
1090:             * @param msg       The error message
1091:             * @param data      The data that the message
1092:             * @param offset    The offset into the data where the error was encountered.
1093:             *
1094:             * @return a newly created <code>ParseException</code> object.
1095:             */
1096:            private ParseException makeParseException(String msg, char[] data,
1097:                    int offset) {
1098:                char[] buf = new char[msg.length() + data.length + 3];
1099:                int msgLen = msg.length();
1100:                System.arraycopy(msg.toCharArray(), 0, buf, 0, msgLen);
1101:                buf[msgLen] = ' ';
1102:                buf[msgLen + 1] = '[';
1103:                System.arraycopy(data, 0, buf, msgLen + 2, data.length);
1104:                buf[buf.length - 1] = ']';
1105:                return new ParseException(new String(buf), offset);
1106:            }
1107:        }
1108:
1109:        class ValueSet {
1110:            public int pos;
1111:            public int value;
1112:        }
www.java2java.com | Contact Us
Copyright 2010 - 2030 Java Source and Support. All rights reserved.
All other trademarks are property of their respective owners.