| 1 | package org.springframework.batch.item.file.transform; |
| 2 | |
| 3 | import java.beans.PropertyEditorSupport; |
| 4 | import java.util.Arrays; |
| 5 | import java.util.Comparator; |
| 6 | |
| 7 | import org.springframework.util.Assert; |
| 8 | import org.springframework.util.StringUtils; |
| 9 | |
| 10 | /** |
| 11 | * Property editor implementation which parses string and creates array of |
| 12 | * ranges. Ranges can be provided in any order. </br> Input string should be |
| 13 | * provided in following format: 'range1, range2, range3,...' where range is |
| 14 | * specified as: |
| 15 | * <ul> |
| 16 | * <li>'X-Y', where X is minimum value and Y is maximum value (condition X<=Y |
| 17 | * is verified)</li> |
| 18 | * <li>or 'Z', where Z is minimum and maximum is calculated as (minimum of |
| 19 | * adjacent range - 1). Maximum of the last range is never calculated. Range |
| 20 | * stays unbound at maximum side if maximum value is not provided.</li> |
| 21 | * </ul> |
| 22 | * Minimum and maximum values can be from interval <1, Integer.MAX_VALUE-1> |
| 23 | * <p> |
| 24 | * Examples:</br> |
| 25 | * '1, 15, 25, 38, 55-60' is equal to '1-14, 15-24, 25-37, 38-54, 55-60' </br> |
| 26 | * '36, 14, 1-10, 15, 49-57' is equal to '36-48, 14-14, 1-10, 15-35, 49-57' |
| 27 | * <p> |
| 28 | * Property editor also allows to validate whether ranges are disjoint. Validation |
| 29 | * can be turned on/off by using {@link #setForceDisjointRanges(boolean)}. By default |
| 30 | * validation is turned off. |
| 31 | * |
| 32 | * @author peter.zozom |
| 33 | */ |
| 34 | public class RangeArrayPropertyEditor extends PropertyEditorSupport { |
| 35 | |
| 36 | private boolean forceDisjointRanges = false; |
| 37 | |
| 38 | /** |
| 39 | * Set force disjoint ranges. If set to TRUE, ranges are validated to be disjoint. |
| 40 | * For example: defining ranges '1-10, 5-15' will cause IllegalArgumentException in |
| 41 | * case of forceDisjointRanges=TRUE. |
| 42 | * @param forceDisjointRanges |
| 43 | */ |
| 44 | public void setForceDisjointRanges(boolean forceDisjointRanges) { |
| 45 | this.forceDisjointRanges = forceDisjointRanges; |
| 46 | } |
| 47 | |
| 48 | public void setAsText(String text) throws IllegalArgumentException { |
| 49 | |
| 50 | //split text into ranges |
| 51 | String[] strRanges = text.split(","); |
| 52 | Range[] ranges = new Range[strRanges.length]; |
| 53 | |
| 54 | //parse ranges and create array of Range objects |
| 55 | for (int i = 0; i < strRanges.length; i++) { |
| 56 | String[] range = strRanges[i].split("-"); |
| 57 | |
| 58 | int min; |
| 59 | int max; |
| 60 | |
| 61 | if ((range.length == 1) && (StringUtils.hasText(range[0]))) { |
| 62 | min = Integer.parseInt(range[0].trim()); |
| 63 | // correct max value will be assigned later |
| 64 | ranges[i] = new Range(min); |
| 65 | } else if ((range.length == 2) && (StringUtils.hasText(range[0])) |
| 66 | && (StringUtils.hasText(range[1]))) { |
| 67 | min = Integer.parseInt(range[0].trim()); |
| 68 | max = Integer.parseInt(range[1].trim()); |
| 69 | ranges[i] = new Range(min,max); |
| 70 | } else { |
| 71 | throw new IllegalArgumentException("Range[" + i + "]: range (" + strRanges[i] + ") is invalid"); |
| 72 | } |
| 73 | |
| 74 | } |
| 75 | |
| 76 | setMaxValues(ranges); |
| 77 | setValue(ranges); |
| 78 | } |
| 79 | |
| 80 | public String getAsText() { |
| 81 | Range[] ranges = (Range[])getValue(); |
| 82 | |
| 83 | StringBuffer sb = new StringBuffer(); |
| 84 | |
| 85 | for (int i = 0; i < ranges.length; i++) { |
| 86 | if(i>0) { |
| 87 | sb.append(", "); |
| 88 | } |
| 89 | sb.append(ranges[i]); |
| 90 | } |
| 91 | return sb.toString(); |
| 92 | } |
| 93 | |
| 94 | private void setMaxValues(Range[] ranges) { |
| 95 | |
| 96 | //clone array, original array should stay same |
| 97 | Range[] c = (Range[])ranges.clone(); |
| 98 | |
| 99 | //sort array of Ranges |
| 100 | Arrays.sort(c, new Comparator() { |
| 101 | public int compare(Object o1, Object o2) { |
| 102 | Range c1 = (Range)o1; |
| 103 | Range c2 = (Range)o2; |
| 104 | return c1.getMin()-c2.getMin(); |
| 105 | } |
| 106 | } |
| 107 | ); |
| 108 | |
| 109 | //set max values for all unbound ranges (except last range) |
| 110 | for (int i = 0; i < c.length - 1; i++) { |
| 111 | if (!c[i].hasMaxValue()) { |
| 112 | //set max value to (min value - 1) of the next range |
| 113 | c[i].setMax(c[i+1].getMin() - 1); |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | if (forceDisjointRanges) { |
| 118 | verifyRanges(c); |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | |
| 123 | private void verifyRanges(Range[] ranges) { |
| 124 | //verify that ranges are disjoint |
| 125 | for(int i = 1; i < ranges.length;i++) { |
| 126 | Assert.isTrue(ranges[i-1].getMax() < ranges[i].getMin(), |
| 127 | "Ranges must be disjoint. Range[" + (i-1) + "]: (" + ranges[i-1] + |
| 128 | ") Range[" + i +"]: (" + ranges[i] + ")"); |
| 129 | } |
| 130 | } |
| 131 | } |