001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2014-2017 ForgeRock AS. 016 */ 017package org.opends.server.backends.task; 018 019import java.text.SimpleDateFormat; 020import java.util.Date; 021import java.util.GregorianCalendar; 022import java.util.Iterator; 023import java.util.List; 024import java.util.StringTokenizer; 025import java.util.regex.Matcher; 026import java.util.regex.Pattern; 027 028import org.forgerock.i18n.LocalizableMessage; 029import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1; 030import org.forgerock.i18n.slf4j.LocalizedLogger; 031import org.forgerock.opendj.ldap.ByteString; 032import org.forgerock.opendj.ldap.ResultCode; 033import org.forgerock.opendj.ldap.schema.AttributeType; 034import org.opends.server.core.DirectoryServer; 035import org.opends.server.core.ServerContext; 036import org.opends.server.types.Attribute; 037import org.opends.server.types.Attributes; 038import org.forgerock.opendj.ldap.DN; 039import org.opends.server.types.DirectoryException; 040import org.opends.server.types.Entry; 041import org.opends.server.types.InitializationException; 042import org.forgerock.opendj.ldap.RDN; 043 044import static java.util.Calendar.*; 045 046import static org.opends.messages.BackendMessages.*; 047import static org.opends.server.config.ConfigConstants.*; 048import static org.opends.server.util.ServerConstants.*; 049import static org.opends.server.util.StaticUtils.*; 050 051/** 052 * This class defines a information about a recurring task, which will be used 053 * to repeatedly schedule tasks for processing. 054 * <br> 055 * It also provides some static methods that allow to validate strings in 056 * crontab (5) format. 057 */ 058public class RecurringTask 059{ 060 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 061 062 /** The DN of the entry that actually defines this task. */ 063 private final DN recurringTaskEntryDN; 064 065 /** The entry that actually defines this task. */ 066 private final Entry recurringTaskEntry; 067 068 /** The unique ID for this recurring task. */ 069 private final String recurringTaskID; 070 071 /** 072 * The fully-qualified name of the class that will be used to implement the 073 * class. 074 */ 075 private final String taskClassName; 076 077 /** Task instance. */ 078 private Task task; 079 080 /** Task scheduler for this task. */ 081 private final TaskScheduler taskScheduler; 082 083 /** Number of tokens in the task schedule tab. */ 084 private static final int TASKTAB_NUM_TOKENS = 5; 085 086 /** Maximum year month days. */ 087 static final int MONTH_LENGTH[] 088 = {31,28,31,30,31,30,31,31,30,31,30,31}; 089 090 /** Maximum leap year month days. */ 091 static final int LEAP_MONTH_LENGTH[] 092 = {31,29,31,30,31,30,31,31,30,31,30,31}; 093 094 /** Task tab fields. */ 095 private static enum TaskTab {MINUTE, HOUR, DAY, MONTH, WEEKDAY} 096 097 private static final int MINUTE_INDEX = 0; 098 private static final int HOUR_INDEX = 1; 099 private static final int DAY_INDEX = 2; 100 private static final int MONTH_INDEX = 3; 101 private static final int WEEKDAY_INDEX = 4; 102 103 /** Wildcard match pattern. */ 104 private static final Pattern wildcardPattern = Pattern.compile("^\\*(?:/(\\d+))?"); 105 106 /** Exact match pattern. */ 107 private static final Pattern exactPattern = Pattern.compile("(\\d+)"); 108 109 /** Range match pattern. */ 110 private static final Pattern rangePattern = Pattern.compile("(\\d+)-(\\d+)(?:/(\\d+))?"); 111 112 /** Boolean arrays holding task tab slots. */ 113 private final boolean[] minutesArray; 114 private final boolean[] hoursArray; 115 private final boolean[] daysArray; 116 private final boolean[] monthArray; 117 private final boolean[] weekdayArray; 118 119 private final ServerContext serverContext; 120 121 /** 122 * Creates a new recurring task based on the information in the provided 123 * entry. 124 * 125 * @param serverContext 126 * The server context. 127 * 128 * @param taskScheduler A reference to the task scheduler that may be 129 * used to schedule new tasks. 130 * @param recurringTaskEntry The entry containing the information to use to 131 * define the task to process. 132 * 133 * @throws DirectoryException If the provided entry does not contain a valid 134 * recurring task definition. 135 */ 136 public RecurringTask(ServerContext serverContext, TaskScheduler taskScheduler, Entry recurringTaskEntry) 137 throws DirectoryException 138 { 139 this.serverContext = serverContext; 140 this.taskScheduler = taskScheduler; 141 this.recurringTaskEntry = recurringTaskEntry; 142 this.recurringTaskEntryDN = recurringTaskEntry.getName(); 143 144 // Get the recurring task ID from the entry. If there isn't one, then fail. 145 Attribute attr = getSingleAttribute(recurringTaskEntry, ATTR_RECURRING_TASK_ID, 146 ERR_RECURRINGTASK_NO_ID_ATTRIBUTE, ERR_RECURRINGTASK_MULTIPLE_ID_TYPES, ERR_RECURRINGTASK_NO_ID); 147 recurringTaskID = getSingleAttributeValue(attr, 148 ResultCode.OBJECTCLASS_VIOLATION, ERR_RECURRINGTASK_MULTIPLE_ID_VALUES, ATTR_RECURRING_TASK_ID); 149 150 // Get the schedule for this task. 151 attr = getSingleAttribute(recurringTaskEntry, ATTR_RECURRING_TASK_SCHEDULE,ERR_RECURRINGTASK_NO_SCHEDULE_ATTRIBUTE, 152 ERR_RECURRINGTASK_MULTIPLE_SCHEDULE_TYPES, ERR_RECURRINGTASK_NO_SCHEDULE_VALUES); 153 String taskScheduleTab = getSingleAttributeValue(attr, 154 ResultCode.CONSTRAINT_VIOLATION, ERR_RECURRINGTASK_MULTIPLE_SCHEDULE_VALUES, ATTR_RECURRING_TASK_SCHEDULE); 155 156 boolean[][] taskArrays = new boolean[][]{null, null, null, null, null}; 157 158 parseTaskTab(taskScheduleTab, taskArrays, true); 159 160 minutesArray = taskArrays[MINUTE_INDEX]; 161 hoursArray = taskArrays[HOUR_INDEX]; 162 daysArray = taskArrays[DAY_INDEX]; 163 monthArray = taskArrays[MONTH_INDEX]; 164 weekdayArray = taskArrays[WEEKDAY_INDEX]; 165 166 // Get the class name from the entry. If there isn't one, then fail. 167 attr = getSingleAttribute(recurringTaskEntry, ATTR_TASK_CLASS, ERR_TASKSCHED_NO_CLASS_ATTRIBUTE, 168 ERR_TASKSCHED_MULTIPLE_CLASS_TYPES, ERR_TASKSCHED_NO_CLASS_VALUES); 169 taskClassName = getSingleAttributeValue(attr, 170 ResultCode.CONSTRAINT_VIOLATION, ERR_TASKSCHED_MULTIPLE_CLASS_VALUES, ATTR_TASK_CLASS); 171 172 173 // Make sure that the specified class can be loaded. 174 Class<?> taskClass; 175 try 176 { 177 taskClass = DirectoryServer.loadClass(taskClassName); 178 } 179 catch (Exception e) 180 { 181 logger.traceException(e); 182 183 LocalizableMessage message = ERR_RECURRINGTASK_CANNOT_LOAD_CLASS. 184 get(taskClassName, ATTR_TASK_CLASS, getExceptionMessage(e)); 185 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message, e); 186 } 187 188 189 // Make sure that the specified class can be instantiated as a task. 190 try 191 { 192 task = (Task) taskClass.newInstance(); 193 } 194 catch (Exception e) 195 { 196 logger.traceException(e); 197 198 LocalizableMessage message = ERR_RECURRINGTASK_CANNOT_INSTANTIATE_CLASS_AS_TASK.get( 199 taskClassName, Task.class.getName()); 200 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message, e); 201 } 202 203 204 // Make sure that we can initialize the task with the information in the 205 // provided entry. 206 try 207 { 208 task.initializeTaskInternal(serverContext, taskScheduler, recurringTaskEntry); 209 } 210 catch (InitializationException ie) 211 { 212 logger.traceException(ie); 213 214 LocalizableMessage message = ERR_RECURRINGTASK_CANNOT_INITIALIZE_INTERNAL.get( taskClassName, ie.getMessage()); 215 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, ie); 216 } 217 218 task.initializeTask(); 219 } 220 221 private String getSingleAttributeValue(Attribute attr, ResultCode erorrRc, Arg1<Object> multipleAttrValueErrorMsg, 222 String attrName) throws DirectoryException 223 { 224 Iterator<ByteString> it = attr.iterator(); 225 ByteString value = it.next(); 226 if (it.hasNext()) 227 { 228 throw new DirectoryException(erorrRc, multipleAttrValueErrorMsg.get(attrName)); 229 } 230 return value.toString(); 231 } 232 233 private Attribute getSingleAttribute(Entry taskEntry, String attrName, Arg1<Object> noEntryErrorMsg, 234 Arg1<Object> multipleEntriesErrorMsg, Arg1<Object> noAttrValueErrorMsg) throws DirectoryException 235 { 236 AttributeType attrType = DirectoryServer.getSchema().getAttributeType(attrName); 237 List<Attribute> attrList = taskEntry.getAttribute(attrType); 238 if (attrList.isEmpty()) 239 { 240 LocalizableMessage message = noEntryErrorMsg.get(attrName); 241 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 242 } 243 if (attrList.size() > 1) 244 { 245 LocalizableMessage message = multipleEntriesErrorMsg.get(attrName); 246 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 247 } 248 Attribute attr = attrList.get(0); 249 if (attr.isEmpty()) 250 { 251 LocalizableMessage message = noAttrValueErrorMsg.get(attrName); 252 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 253 } 254 return attr; 255 } 256 257 258 259 /** 260 * Retrieves the unique ID assigned to this recurring task. 261 * 262 * @return The unique ID assigned to this recurring task. 263 */ 264 public String getRecurringTaskID() 265 { 266 return recurringTaskID; 267 } 268 269 270 271 /** 272 * Retrieves the DN of the entry containing the data for this recurring task. 273 * 274 * @return The DN of the entry containing the data for this recurring task. 275 */ 276 public DN getRecurringTaskEntryDN() 277 { 278 return recurringTaskEntryDN; 279 } 280 281 282 283 /** 284 * Retrieves the entry containing the data for this recurring task. 285 * 286 * @return The entry containing the data for this recurring task. 287 */ 288 public Entry getRecurringTaskEntry() 289 { 290 return recurringTaskEntry; 291 } 292 293 294 295 /** 296 * Retrieves the fully-qualified name of the Java class that provides the 297 * implementation logic for this recurring task. 298 * 299 * @return The fully-qualified name of the Java class that provides the 300 * implementation logic for this recurring task. 301 */ 302 public String getTaskClassName() 303 { 304 return taskClassName; 305 } 306 307 308 309 /** 310 * Schedules the next iteration of this recurring task for processing. 311 * @param calendar date and time to schedule next iteration from. 312 * @return The task that has been scheduled for processing. 313 * @throws DirectoryException to indicate an error. 314 */ 315 public Task scheduleNextIteration(GregorianCalendar calendar) 316 throws DirectoryException 317 { 318 Task nextTask = null; 319 Date nextTaskDate = null; 320 321 try { 322 nextTaskDate = getNextIteration(calendar); 323 } catch (IllegalArgumentException e) { 324 logger.traceException(e); 325 326 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 327 ERR_RECURRINGTASK_INVALID_TOKENS_COMBO.get( 328 ATTR_RECURRING_TASK_SCHEDULE)); 329 } 330 331 SimpleDateFormat dateFormat = new SimpleDateFormat( 332 DATE_FORMAT_COMPACT_LOCAL_TIME); 333 String nextTaskStartTime = dateFormat.format(nextTaskDate); 334 335 try { 336 // Make a regular task iteration from this recurring task. 337 nextTask = task.getClass().newInstance(); 338 Entry nextTaskEntry = recurringTaskEntry.duplicate(false); 339 SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS"); 340 String nextTaskID = task.getTaskID() + "-" + df.format(nextTaskDate); 341 String nextTaskIDName = NAME_PREFIX_TASK + "id"; 342 nextTaskEntry.replaceAttribute(Attributes.create(nextTaskIDName, nextTaskID)); 343 344 RDN nextTaskRDN = new RDN(DirectoryServer.getSchema().getAttributeType(nextTaskIDName), nextTaskID); 345 DN nextTaskDN = taskScheduler.getTaskBackend().getScheduledTasksParentDN().child(nextTaskRDN); 346 nextTaskEntry.setDN(nextTaskDN); 347 348 String nextTaskStartTimeName = NAME_PREFIX_TASK + "scheduled-start-time"; 349 nextTaskEntry.replaceAttribute(Attributes.create(nextTaskStartTimeName, nextTaskStartTime)); 350 351 nextTask.initializeTaskInternal(serverContext, taskScheduler, nextTaskEntry); 352 nextTask.initializeTask(); 353 } catch (Exception e) { 354 // Should not happen, debug log it otherwise. 355 logger.traceException(e); 356 } 357 358 return nextTask; 359 } 360 361 /** 362 * Parse and validate recurring task schedule. 363 * @param taskSchedule recurring task schedule tab in crontab(5) format. 364 * @throws DirectoryException to indicate an error. 365 */ 366 public static void parseTaskTab(String taskSchedule) throws DirectoryException 367 { 368 parseTaskTab(taskSchedule, new boolean[][]{null, null, null, null, null}, 369 false); 370 } 371 372 /** 373 * Parse and validate recurring task schedule. 374 * @param taskSchedule recurring task schedule tab in crontab(5) format. 375 * @param arrays an array of 5 boolean arrays. The array has the following 376 * structure: {minutesArray, hoursArray, daysArray, monthArray, weekdayArray}. 377 * @param referToTaskEntryAttribute whether the error messages must refer 378 * to the task entry attribute or not. This is used to have meaningful 379 * messages when the {@link #parseTaskTab(String)} is called to validate 380 * a crontab formatted string. 381 * @throws DirectoryException to indicate an error. 382 */ 383 private static void parseTaskTab(String taskSchedule, boolean[][] arrays, 384 boolean referToTaskEntryAttribute) throws DirectoryException 385 { 386 StringTokenizer st = new StringTokenizer(taskSchedule); 387 388 if (st.countTokens() != TASKTAB_NUM_TOKENS) { 389 if (referToTaskEntryAttribute) 390 { 391 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 392 ERR_RECURRINGTASK_INVALID_N_TOKENS.get( 393 ATTR_RECURRING_TASK_SCHEDULE)); 394 } 395 else 396 { 397 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 398 ERR_RECURRINGTASK_INVALID_N_TOKENS_SIMPLE.get()); 399 } 400 } 401 402 for (TaskTab taskTabToken : TaskTab.values()) { 403 String token = st.nextToken(); 404 switch (taskTabToken) { 405 case MINUTE: 406 try { 407 arrays[MINUTE_INDEX] = parseTaskTabField(token, 0, 59); 408 } catch (IllegalArgumentException e) { 409 if (referToTaskEntryAttribute) 410 { 411 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 412 ERR_RECURRINGTASK_INVALID_MINUTE_TOKEN.get( 413 ATTR_RECURRING_TASK_SCHEDULE)); 414 } 415 else 416 { 417 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 418 ERR_RECURRINGTASK_INVALID_MINUTE_TOKEN_SIMPLE.get()); 419 } 420 } 421 break; 422 case HOUR: 423 try { 424 arrays[HOUR_INDEX] = parseTaskTabField(token, 0, 23); 425 } catch (IllegalArgumentException e) { 426 if (referToTaskEntryAttribute) 427 { 428 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 429 ERR_RECURRINGTASK_INVALID_HOUR_TOKEN.get( 430 ATTR_RECURRING_TASK_SCHEDULE)); 431 } 432 else 433 { 434 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 435 ERR_RECURRINGTASK_INVALID_HOUR_TOKEN_SIMPLE.get()); 436 } 437 } 438 break; 439 case DAY: 440 try { 441 arrays[DAY_INDEX] = parseTaskTabField(token, 1, 31); 442 } catch (IllegalArgumentException e) { 443 if (referToTaskEntryAttribute) 444 { 445 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 446 ERR_RECURRINGTASK_INVALID_DAY_TOKEN.get( 447 ATTR_RECURRING_TASK_SCHEDULE)); 448 } 449 else 450 { 451 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 452 ERR_RECURRINGTASK_INVALID_DAY_TOKEN_SIMPLE.get()); 453 } 454 } 455 break; 456 case MONTH: 457 try { 458 arrays[MONTH_INDEX] = parseTaskTabField(token, 1, 12); 459 } catch (IllegalArgumentException e) { 460 if (referToTaskEntryAttribute) 461 { 462 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 463 ERR_RECURRINGTASK_INVALID_MONTH_TOKEN.get( 464 ATTR_RECURRING_TASK_SCHEDULE)); 465 } 466 else 467 { 468 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 469 ERR_RECURRINGTASK_INVALID_MONTH_TOKEN_SIMPLE.get()); 470 } 471 } 472 break; 473 case WEEKDAY: 474 try { 475 arrays[WEEKDAY_INDEX] = parseTaskTabField(token, 0, 6); 476 } catch (IllegalArgumentException e) { 477 if (referToTaskEntryAttribute) 478 { 479 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 480 ERR_RECURRINGTASK_INVALID_WEEKDAY_TOKEN.get( 481 ATTR_RECURRING_TASK_SCHEDULE)); 482 } 483 else 484 { 485 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 486 ERR_RECURRINGTASK_INVALID_WEEKDAY_TOKEN_SIMPLE.get()); 487 } 488 } 489 break; 490 } 491 } 492 } 493 494 /** 495 * Parse and validate recurring task schedule field. 496 * 497 * @param tabField recurring task schedule field in crontab(5) format. 498 * @param minValue minimum value allowed for this field. 499 * @param maxValue maximum value allowed for this field. 500 * @return boolean schedule slots range set according to the schedule field. 501 * @throws IllegalArgumentException if tab field is invalid. 502 */ 503 public static boolean[] parseTaskTabField(String tabField, 504 int minValue, int maxValue) throws IllegalArgumentException 505 { 506 boolean[] valueList = new boolean[maxValue + 1]; 507 508 // Wildcard with optional increment. 509 Matcher m = wildcardPattern.matcher(tabField); 510 if (m.matches() && m.groupCount() == 1) 511 { 512 String stepString = m.group(1); 513 int increment = isValueAbsent(stepString) ? 1 : Integer.parseInt(stepString); 514 for (int i = minValue; i <= maxValue; i += increment) 515 { 516 valueList[i] = true; 517 } 518 return valueList; 519 } 520 521 // List. 522 for (String listVal : tabField.split(",")) 523 { 524 // Single number. 525 m = exactPattern.matcher(listVal); 526 if (m.matches() && m.groupCount() == 1) 527 { 528 String exactValue = m.group(1); 529 if (isValueAbsent(exactValue)) 530 { 531 throw new IllegalArgumentException(); 532 } 533 int value = Integer.parseInt(exactValue); 534 if (value < minValue || value > maxValue) 535 { 536 throw new IllegalArgumentException(); 537 } 538 valueList[value] = true; 539 continue; 540 } 541 542 // Range of numbers with optional increment. 543 m = rangePattern.matcher(listVal); 544 if (m.matches() && m.groupCount() == 3) { 545 String startString = m.group(1); 546 String endString = m.group(2); 547 String stepString = m.group(3); 548 int increment = isValueAbsent(stepString) ? 1 : Integer.parseInt(stepString); 549 if (isValueAbsent(startString) || isValueAbsent(endString)) 550 { 551 throw new IllegalArgumentException(); 552 } 553 int startValue = Integer.parseInt(startString); 554 int endValue = Integer.parseInt(endString); 555 if (startValue > endValue || startValue < minValue || endValue > maxValue) 556 { 557 throw new IllegalArgumentException(); 558 } 559 for (int i = startValue; i <= endValue; i += increment) 560 { 561 valueList[i] = true; 562 } 563 continue; 564 } 565 566 // Can only have a list of numbers and ranges. 567 throw new IllegalArgumentException(); 568 } 569 570 return valueList; 571 } 572 573 /** 574 * Check if a String from a Matcher group is absent. Matcher returns empty strings 575 * for optional groups that are absent. 576 * 577 * @param s A string returned from Matcher.group() 578 * @return true if the string is unusable, false if it is usable. 579 */ 580 private static boolean isValueAbsent(String s) 581 { 582 return s == null || s.length() == 0; 583 } 584 /** 585 * Get next recurring slot from the range. 586 * @param timesList the range. 587 * @param fromNow the current slot. 588 * @return next recurring slot in the range. 589 */ 590 private int getNextTimeSlice(boolean[] timesList, int fromNow) 591 { 592 for (int i = fromNow; i < timesList.length; i++) { 593 if (timesList[i]) { 594 return i; 595 } 596 } 597 return -1; 598 } 599 600 /** 601 * Get next task iteration date according to recurring schedule. 602 * @param calendar date and time to schedule from. 603 * @return next task iteration date. 604 * @throws IllegalArgumentException if recurring schedule is invalid. 605 */ 606 private Date getNextIteration(GregorianCalendar calendar) 607 throws IllegalArgumentException 608 { 609 int minute, hour, day, month, weekday; 610 calendar.setFirstDayOfWeek(GregorianCalendar.SUNDAY); 611 calendar.add(GregorianCalendar.MINUTE, 1); 612 calendar.set(GregorianCalendar.SECOND, 0); 613 calendar.set(GregorianCalendar.MILLISECOND, 0); 614 calendar.setLenient(true); 615 616 // Weekday 617 for (;;) { 618 // Month 619 for (;;) { 620 // Day 621 for (;;) { 622 // Hour 623 for (;;) { 624 // Minute 625 for (;;) { 626 minute = getNextTimeSlice(minutesArray, calendar.get(MINUTE)); 627 if (minute == -1) { 628 calendar.set(GregorianCalendar.MINUTE, 0); 629 calendar.add(GregorianCalendar.HOUR_OF_DAY, 1); 630 } else { 631 calendar.set(GregorianCalendar.MINUTE, minute); 632 break; 633 } 634 } 635 hour = getNextTimeSlice(hoursArray, 636 calendar.get(GregorianCalendar.HOUR_OF_DAY)); 637 if (hour == -1) { 638 calendar.set(GregorianCalendar.HOUR_OF_DAY, 0); 639 calendar.add(GregorianCalendar.DAY_OF_MONTH, 1); 640 } else { 641 calendar.set(GregorianCalendar.HOUR_OF_DAY, hour); 642 break; 643 } 644 } 645 day = getNextTimeSlice(daysArray, 646 calendar.get(GregorianCalendar.DAY_OF_MONTH)); 647 if (day == -1 || day > calendar.getActualMaximum(DAY_OF_MONTH)) 648 { 649 calendar.set(GregorianCalendar.DAY_OF_MONTH, 1); 650 calendar.add(GregorianCalendar.MONTH, 1); 651 } else { 652 calendar.set(GregorianCalendar.DAY_OF_MONTH, day); 653 break; 654 } 655 } 656 month = getNextTimeSlice(monthArray, calendar.get(MONTH) + 1); 657 if (month == -1) { 658 calendar.set(GregorianCalendar.MONTH, 0); 659 calendar.add(GregorianCalendar.YEAR, 1); 660 } 661 else if (day > LEAP_MONTH_LENGTH[month - 1] 662 && (getNextTimeSlice(daysArray, 1) != day 663 || getNextTimeSlice(monthArray, 1) != month)) 664 { 665 calendar.set(DAY_OF_MONTH, 1); 666 calendar.add(MONTH, 1); 667 } else if (day > MONTH_LENGTH[month - 1] 668 && !calendar.isLeapYear(calendar.get(YEAR))) { 669 calendar.add(YEAR, 1); 670 } else { 671 calendar.set(MONTH, month - 1); 672 break; 673 } 674 } 675 weekday = getNextTimeSlice(weekdayArray, calendar.get(DAY_OF_WEEK) - 1); 676 if (weekday == -1 677 || weekday != calendar.get(DAY_OF_WEEK) - 1) 678 { 679 calendar.add(GregorianCalendar.DAY_OF_MONTH, 1); 680 } else { 681 break; 682 } 683 } 684 685 return calendar.getTime(); 686 } 687}