diff --git a/org.springframework.integration/.classpath b/org.springframework.integration/.classpath
index 25f781f33a..34c01dfac1 100644
--- a/org.springframework.integration/.classpath
+++ b/org.springframework.integration/.classpath
@@ -15,6 +15,5 @@
-
diff --git a/org.springframework.integration/ivy.xml b/org.springframework.integration/ivy.xml
index 4a191a1cf6..144dc2d43e 100644
--- a/org.springframework.integration/ivy.xml
+++ b/org.springframework.integration/ivy.xml
@@ -26,7 +26,6 @@
-
\ No newline at end of file
diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/scheduling/CronSequenceGenerator.java b/org.springframework.integration/src/main/java/org/springframework/integration/scheduling/CronSequenceGenerator.java
new file mode 100644
index 0000000000..1f53243cec
--- /dev/null
+++ b/org.springframework.integration/src/main/java/org/springframework/integration/scheduling/CronSequenceGenerator.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.
+ */
+
+package org.springframework.integration.scheduling;
+
+import java.util.BitSet;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * Date sequence generator for a Crontab pattern allowing
+ * client to specify a pattern that the sequence matches. The pattern is a list
+ * of 6 single space separated fields representing (second, minute, hour, day,
+ * month, weekday). Month and weekday names can be given as the first three
+ * letters of the English names.
+ *
+ * Example patterns
+ *
+ * - "0 0 * * * *" = the top of every hour of every day.
+ * - "*/10 * * * * *" = every ten seconds.
+ * - "0 0 8-10 * * *" = 8, 9 and 10 o'clock of every day.
+ * - "0 0 8-10/30 * * *" = 8:00, 8:30, 9:00, 9:30 and 10 o'clock every day.
+ * - "0 0 9-17 * * MON-FRI" = on the hour nine-to-five weekdays
+ * - "0 0 0 25 12 ?" = every Christmas Day at midnight
+ *
+ *
+ * @author Dave Syer
+ */
+public class CronSequenceGenerator {
+
+ private final BitSet seconds = new BitSet(60);
+
+ private final BitSet minutes = new BitSet(60);
+
+ private final BitSet hours = new BitSet(24);
+
+ private final BitSet daysOfWeek = new BitSet(7);
+
+ private final BitSet daysOfMonth = new BitSet(31);
+
+ private final BitSet months = new BitSet(12);
+
+ private final String pattern;
+
+
+ /**
+ * Construct a {@link CronSequenceGenerator} from the pattern provided.
+ *
+ * @param pattern a single space separated list of time fields
+ *
+ * @throws IllegalArgumentException if the pattern cannot be parsed
+ */
+ public CronSequenceGenerator(String pattern) throws IllegalArgumentException {
+ this.pattern = pattern;
+ parse(pattern);
+ }
+
+
+ /**
+ * Get the next {@link Date} in the sequence matching the Cron pattern and
+ * after the value provided. The return value will have a whole number of
+ * seconds, and will be after the input value.
+ *
+ * @param date a seed value
+ * @return the next value matching the pattern
+ */
+ public Date next(Date date) {
+
+ Calendar calendar = new GregorianCalendar();
+ calendar.setTime(date);
+
+ // Truncate to the next whole second
+ calendar.add(Calendar.SECOND, 1);
+ calendar.set(Calendar.MILLISECOND, 0);
+
+ int second = calendar.get(Calendar.SECOND);
+ int minute = calendar.get(Calendar.MINUTE);
+ int hour = calendar.get(Calendar.HOUR_OF_DAY);
+ int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
+ int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
+ int month = calendar.get(Calendar.MONTH);
+
+ month = findNext(months, month, 12, calendar, Calendar.MONTH, Calendar.DAY_OF_MONTH, Calendar.HOUR_OF_DAY,
+ Calendar.MINUTE, Calendar.SECOND);
+ dayOfMonth = findNextDay(calendar, daysOfMonth, dayOfMonth, daysOfWeek, dayOfWeek, 366);
+ hour = findNext(hours, hour, 24, calendar, Calendar.HOUR, Calendar.MINUTE, Calendar.SECOND);
+ minute = findNext(minutes, minute, 60, calendar, Calendar.MINUTE, Calendar.SECOND);
+ second = findNext(seconds, second, 60, calendar, Calendar.SECOND);
+
+ return calendar.getTime();
+
+ }
+
+ /**
+ * @param calendar
+ * @return
+ */
+ private int findNextDay(Calendar calendar, BitSet daysOfMonth, int dayOfMonth, BitSet daysOfWeek, int dayOfWeek,
+ int max) {
+ int count = 0;
+ while ((!daysOfMonth.get(dayOfMonth) || !daysOfWeek.get(dayOfWeek)) && count++ < max) {
+ calendar.add(Calendar.DAY_OF_MONTH, 1);
+ dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
+ dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
+ reset(calendar, Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND);
+ }
+ if (count > max) {
+ throw new IllegalStateException("Overflow in day for expression=" + pattern);
+ }
+ return dayOfMonth;
+ }
+
+ /**
+ * Search the bits provided for the next set bit after the value provided,
+ * and reset the calendar.
+ *
+ * @param bits a {@link BitSet} representing the allowed values of the field
+ * @param value the current value of the field
+ * @param max the largest value that the field can have
+ * @param calendar the calendar to increment as we move through the bits
+ * @param field the field to increment in the calendar (@see
+ * {@link Calendar} for the static constants defining valid fields)
+ * @param lowerOrders the Calendar field ids that should be reset (i.e. the
+ * ones of lower significance than the field of interest)
+ *
+ * @return the value of the calendar field that is next in the sequence
+ */
+ private int findNext(BitSet bits, int value, int max, Calendar calendar, int field, int... lowerOrders) {
+ // TODO: more efficient to use BitSet.nextSet(int)
+ int count = 0;
+ while (!bits.get(value) && count++ < max) {
+ calendar.add(field, 1);
+ value = calendar.get(field);
+ reset(calendar, lowerOrders);
+ }
+ if (count > max) {
+ throw new IllegalStateException(String.format("Overflow in field=%d for expression=%s", field, pattern));
+ }
+ return value;
+ }
+
+ /**
+ * Reset the calendar setting all the fields provided to zero.
+ *
+ * @param calendar
+ * @param fields
+ */
+ private void reset(Calendar calendar, int... fields) {
+ for (int field : fields) {
+ calendar.set(field, 0);
+ }
+ }
+
+ /**
+ * @param expression
+ */
+ private void parse(String expression) throws IllegalArgumentException {
+ String[] fields = StringUtils.delimitedListToStringArray(expression, " ");
+ if (fields.length != 6) {
+ throw new IllegalArgumentException(String.format("Expression must consist of 6 fields (found %d in %s)",
+ fields.length, fields));
+ }
+ setNumberHits(seconds, fields[0], 60);
+ setNumberHits(minutes, fields[1], 60);
+ setNumberHits(hours, fields[2], 24);
+ setDays(daysOfMonth, fields[3], 31);
+ setNumberHits(months, replaceOrdinals(fields[4], "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC"), 12);
+ setDays(daysOfWeek, replaceOrdinals(fields[5], "SUN,MON,TUE,WED,THU,FRI,SAT"), 8);
+ if (daysOfWeek.get(7)) {
+ // Sunday can be represented as 0 or 7
+ daysOfWeek.set(0);
+ daysOfWeek.clear(7);
+ }
+ }
+
+ /**
+ * Replace the values in the commaSeparatedList (case insensitive) with
+ * their index in the list.
+ *
+ * @param value
+ * @param commaSeparatedList
+ * @return a new string with the values from the list replaced
+ */
+ private String replaceOrdinals(String value, String commaSeparatedList) {
+ String[] list = StringUtils.commaDelimitedListToStringArray(commaSeparatedList);
+ for (int i = 0; i < list.length; i++) {
+ String item = list[i].toUpperCase();
+ value = StringUtils.replace(value.toUpperCase(), item, "" + i);
+ }
+ return value;
+ }
+
+ /**
+ * @param bits
+ * @param field
+ * @param max
+ */
+ private void setDays(BitSet bits, String field, int max) {
+ if (field.contains("?")) {
+ field = "*";
+ }
+ setNumberHits(bits, field, max);
+ }
+
+ /**
+ * @param bits
+ * @param value
+ * @param max
+ * @return
+ */
+ private void setNumberHits(BitSet bits, String value, int max) {
+
+ String[] fields = StringUtils.delimitedListToStringArray(value, ",");
+
+ for (String field : fields) {
+
+ if (!field.contains("/")) {
+
+ // Not an incrementer so it must be a range (possibly empty)
+ int[] range = getRange(field, max);
+ bits.set(range[0], range[1] + 1);
+
+ }
+ else {
+
+ String[] split = StringUtils.delimitedListToStringArray(field, "/");
+ if (split.length > 2) {
+ throw new IllegalArgumentException("Incrementer has more than two fields: " + field);
+ }
+ int[] range = getRange(split[0], max);
+ if (!split[0].contains("-")) {
+ range[1] = max - 1;
+ }
+ int delta = Integer.valueOf(split[1]);
+ for (int i = range[0]; i <= range[1]; i += delta) {
+ bits.set(i);
+ }
+
+ }
+ }
+ }
+
+ /**
+ * @param field
+ * @return
+ */
+ private int[] getRange(String field, int max) {
+ int[] result = new int[2];
+ if (field.contains("*")) {
+ result[0] = 0;
+ result[1] = max - 1;
+ return result;
+ }
+ if (!field.contains("-")) {
+ result[0] = result[1] = Integer.valueOf(field);
+ }
+ else {
+ String[] split = StringUtils.delimitedListToStringArray(field, "-");
+ if (split.length > 2) {
+ throw new IllegalArgumentException("Range has more than two fields: " + field);
+ }
+ result[0] = Integer.valueOf(split[0]);
+ result[1] = Integer.valueOf(split[1]);
+ }
+ return result;
+ }
+
+ /**
+ * @see Object#equals(Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof CronSequenceGenerator)) {
+ return false;
+ }
+ CronSequenceGenerator cron = (CronSequenceGenerator) obj;
+ return cron.months.equals(months) && cron.daysOfMonth.equals(daysOfMonth) && cron.daysOfWeek.equals(daysOfWeek)
+ && cron.hours.equals(hours) && cron.minutes.equals(minutes) && cron.seconds.equals(seconds);
+ }
+
+ /**
+ * @see Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return 37 + 17 * months.hashCode() + 29 * daysOfMonth.hashCode() + 37 * daysOfWeek.hashCode() + 41
+ * hours.hashCode() + 53 * minutes.hashCode() + 61 * seconds.hashCode();
+ }
+
+}
diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/scheduling/CronTrigger.java b/org.springframework.integration/src/main/java/org/springframework/integration/scheduling/CronTrigger.java
index dc922305d8..58dea9eb01 100644
--- a/org.springframework.integration/src/main/java/org/springframework/integration/scheduling/CronTrigger.java
+++ b/org.springframework.integration/src/main/java/org/springframework/integration/scheduling/CronTrigger.java
@@ -16,41 +16,49 @@
package org.springframework.integration.scheduling;
-import java.text.ParseException;
import java.util.Date;
-import org.quartz.CronExpression;
-
/**
- * A trigger that uses a cron expression.
+ * A trigger that uses a cron expression. See {@link CronSequenceGenerator}
+ * for a detailed description of the expression pattern syntax.
*
* @author Mark Fisher
*/
public class CronTrigger implements Trigger {
- private final CronExpression expression;
+ private final CronSequenceGenerator cronSequenceGenerator;
/**
* Create a trigger for the given cron expression.
*/
- public CronTrigger(String expression) {
- try {
- this.expression = new CronExpression(expression);
- }
- catch (ParseException e) {
- throw new IllegalArgumentException(
- "failed to parse cron expression: " + expression);
- }
+ public CronTrigger(String expression) throws IllegalArgumentException {
+ this.cronSequenceGenerator = new CronSequenceGenerator(expression);
}
/**
- * Return the next time a task should run. Determined by this trigger's
- * cron expression.
+ * Return the next time a task should run. Determined by consulting this
+ * trigger's cron expression compared with the lastCompleteTime. If the
+ * lastCompleteTime is null, the current time is used.
*/
public Date getNextRunTime(Date lastScheduledRunTime, Date lastCompleteTime) {
- return this.expression.getNextValidTimeAfter(new Date());
+ Date date = (lastCompleteTime != null) ? lastCompleteTime : new Date();
+ return this.cronSequenceGenerator.next(date);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null || !(other instanceof CronTrigger)) {
+ return false;
+ }
+ return this.cronSequenceGenerator.equals(
+ ((CronTrigger) other).cronSequenceGenerator);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.cronSequenceGenerator.hashCode();
}
}
diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/scheduling/CronTriggerTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/scheduling/CronTriggerTests.java
new file mode 100644
index 0000000000..fbebf41dd6
--- /dev/null
+++ b/org.springframework.integration/src/test/java/org/springframework/integration/scheduling/CronTriggerTests.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.
+ */
+
+package org.springframework.integration.scheduling;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Dave Syer
+ * @author Mark Fisher
+ */
+public class CronTriggerTests {
+
+ private Calendar calendar = new GregorianCalendar();
+
+ private Date date = new Date();
+
+
+ /**
+ * @param calendar
+ */
+ private void roundup(Calendar calendar) {
+ calendar.add(Calendar.SECOND, 1);
+ calendar.set(Calendar.MILLISECOND, 0);
+ }
+
+
+ @Before
+ public void setUp() {
+ calendar.setTime(date);
+ roundup(calendar);
+ }
+
+ @Test
+ public void testMatchAll() throws Exception {
+ CronTrigger trigger = new CronTrigger("* * * * * *");
+ assertEquals(calendar.getTime(), trigger.getNextRunTime(null, date));
+ }
+
+ @Test
+ public void testMatchLastSecond() throws Exception {
+ CronTrigger trigger = new CronTrigger("* * * * * *");
+ GregorianCalendar calendar = new GregorianCalendar();
+ calendar.set(Calendar.SECOND, 58);
+ assertMatchesNextSecond(trigger, calendar);
+ }
+
+ @Test
+ public void testMatchSpecificSecond() throws Exception {
+ CronTrigger trigger = new CronTrigger("10 * * * * *");
+ GregorianCalendar calendar = new GregorianCalendar();
+ calendar.set(Calendar.SECOND, 9);
+ assertMatchesNextSecond(trigger, calendar);
+ }
+
+ @Test
+ public void testIncrementSecondByOne() throws Exception {
+ CronTrigger trigger = new CronTrigger("11 * * * * *");
+ calendar.set(Calendar.SECOND, 10);
+ Date date = calendar.getTime();
+ calendar.add(Calendar.SECOND, 1);
+ assertEquals(calendar.getTime(), trigger.getNextRunTime(null, date));
+ }
+
+ @Test
+ public void testIncrementSecondAndRollover() throws Exception {
+ CronTrigger trigger = new CronTrigger("10 * * * * *");
+ calendar.set(Calendar.SECOND, 11);
+ Date date = calendar.getTime();
+ calendar.add(Calendar.SECOND, 59);
+ assertEquals(calendar.getTime(), trigger.getNextRunTime(null, date));
+ }
+
+ @Test
+ public void testSecondRange() throws Exception {
+ CronTrigger trigger = new CronTrigger("10-15 * * * * *");
+ calendar.set(Calendar.SECOND, 9);
+ assertMatchesNextSecond(trigger, calendar);
+ calendar.set(Calendar.SECOND, 14);
+ assertMatchesNextSecond(trigger, calendar);
+ }
+
+ @Test
+ public void testIncrementMinuteByOne() throws Exception {
+ CronTrigger trigger = new CronTrigger("* 11 * * * *");
+ calendar.set(Calendar.MINUTE, 10);
+ Date date = calendar.getTime();
+ calendar.add(Calendar.MINUTE, 1);
+ calendar.set(Calendar.SECOND, 0);
+ assertEquals(calendar.getTime(), trigger.getNextRunTime(null, date));
+ }
+
+ @Test
+ public void testIncrementMinuteAndRollover() throws Exception {
+ CronTrigger trigger = new CronTrigger("* 10 * * * *");
+ calendar.set(Calendar.MINUTE, 11);
+ Date date = calendar.getTime();
+ calendar.add(Calendar.MINUTE, 59);
+ calendar.set(Calendar.SECOND, 0);
+ assertEquals(calendar.getTime(), trigger.getNextRunTime(null, date));
+ }
+
+ @Test
+ public void testIncrementDayOfMonthByOne() throws Exception {
+ CronTrigger trigger = new CronTrigger("* * * 10 * *");
+ calendar.set(Calendar.DAY_OF_MONTH, 9);
+ Date date = calendar.getTime();
+ calendar.add(Calendar.DAY_OF_MONTH, 1);
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ assertEquals(calendar.getTime(), trigger.getNextRunTime(null, date));
+ }
+
+ @Test
+ public void testIncrementDayOfMonthAndRollover() throws Exception {
+ CronTrigger trigger = new CronTrigger("* * * 10 * *");
+ calendar.set(Calendar.DAY_OF_MONTH, 11);
+ Date date = calendar.getTime();
+ calendar.add(Calendar.MONTH, 1);
+ calendar.set(Calendar.DAY_OF_MONTH, 10);
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ assertEquals(calendar.getTime(), trigger.getNextRunTime(null, date));
+ }
+
+ @Test
+ public void testIncrementDayOfWeekByOne() throws Exception {
+ CronTrigger trigger = new CronTrigger("* * * * * 2");
+ calendar.set(Calendar.DAY_OF_WEEK, 1);
+ Date date = calendar.getTime();
+ calendar.add(Calendar.DAY_OF_WEEK, 1);
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ assertEquals(calendar.getTime(), trigger.getNextRunTime(null, date));
+ }
+
+ @Test
+ public void testIncrementDayOfWeekAndRollover() throws Exception {
+ CronTrigger trigger = new CronTrigger("* * * * * 2");
+ calendar.set(Calendar.DAY_OF_WEEK, 3);
+ Date date = calendar.getTime();
+ calendar.add(Calendar.DAY_OF_MONTH, 6);
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ assertEquals(calendar.getTime(), trigger.getNextRunTime(null, date));
+ }
+
+ @Test
+ public void testDayOfWeekIndifferent() throws Exception {
+ CronTrigger trigger1 = new CronTrigger("* * * 2 * *");
+ CronTrigger trigger2 = new CronTrigger("* * * 2 * ?");
+ assertEquals(trigger1, trigger2);
+ }
+
+ @Test
+ public void testSecondIncrementer() throws Exception {
+ CronTrigger trigger1 = new CronTrigger("57,59 * * * * *");
+ CronTrigger trigger2 = new CronTrigger("57/2 * * * * *");
+ assertEquals(trigger1, trigger2);
+ }
+
+ @Test
+ public void testSecondIncrementerWithRange() throws Exception {
+ CronTrigger trigger1 = new CronTrigger("1,3,5 * * * * *");
+ CronTrigger trigger2 = new CronTrigger("1-6/2 * * * * *");
+ assertEquals(trigger1, trigger2);
+ }
+
+ @Test
+ public void testHourIncrementer() throws Exception {
+ CronTrigger trigger1 = new CronTrigger("* * 4,8,12,16,20 * * *");
+ CronTrigger trigger2 = new CronTrigger("* * 4/4 * * *");
+ assertEquals(trigger1, trigger2);
+ }
+
+ @Test
+ public void testDayNames() throws Exception {
+ CronTrigger trigger1 = new CronTrigger("* * * * * 0-6");
+ CronTrigger trigger2 = new CronTrigger("* * * * * TUE,WED,THU,FRI,SAT,SUN,MON");
+ assertEquals(trigger1, trigger2);
+ }
+
+ @Test
+ public void testSundaySynonym() throws Exception {
+ CronTrigger trigger1 = new CronTrigger("* * * * * 0");
+ CronTrigger trigger2 = new CronTrigger("* * * * * 7");
+ assertEquals(trigger1, trigger2);
+ }
+
+ @Test
+ public void testMonthNames() throws Exception {
+ CronTrigger trigger1 = new CronTrigger("* * * * 0-11 *");
+ CronTrigger trigger2 = new CronTrigger("* * * * FEB,JAN,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC *");
+ assertEquals(trigger1, trigger2);
+ }
+
+ @Test
+ public void testMonthNamesMixedCase() throws Exception {
+ CronTrigger trigger1 = new CronTrigger("* * * * 1 *");
+ CronTrigger trigger2 = new CronTrigger("* * * * Feb *");
+ assertEquals(trigger1, trigger2);
+ }
+
+
+ /**
+ * @param trigger
+ * @param calendar
+ */
+ private void assertMatchesNextSecond(CronTrigger trigger, Calendar calendar) {
+ Date date = calendar.getTime();
+ roundup(calendar);
+ assertEquals(calendar.getTime(), trigger.getNextRunTime(null, date));
+ }
+
+}
diff --git a/org.springframework.integration/template.mf b/org.springframework.integration/template.mf
index 17d65d0821..b8ed01ab5c 100644
--- a/org.springframework.integration/template.mf
+++ b/org.springframework.integration/template.mf
@@ -5,8 +5,7 @@ Bundle-ManifestVersion: 2
Import-Template:
org.springframework.*;version="[2.5.5.A, 3.0.0)",
org.apache.commons.logging;version="[1.1.1, 2.0.0)",
- org.aopalliance.*;version="[1.0.0,2.0.0)",
- org.quartz.*;version="[1.6.0, 2.0.0)";resolution:=optional
+ org.aopalliance.*;version="[1.0.0,2.0.0)"
Unversioned-Imports:
org.w3c.dom