| 1 | /* |
| 2 | * Copyright 2006-2007 the original author or authors. |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package org.springframework.batch.item.file.transform; |
| 18 | |
| 19 | import java.beans.PropertyEditorSupport; |
| 20 | import java.util.Arrays; |
| 21 | import java.util.Comparator; |
| 22 | |
| 23 | import org.springframework.util.Assert; |
| 24 | import org.springframework.util.StringUtils; |
| 25 | |
| 26 | /** |
| 27 | * Property editor implementation which parses string and creates array of |
| 28 | * ranges. Ranges can be provided in any order. </br> Input string should be |
| 29 | * provided in following format: 'range1, range2, range3,...' where range is |
| 30 | * specified as: |
| 31 | * <ul> |
| 32 | * <li>'X-Y', where X is minimum value and Y is maximum value (condition X<=Y |
| 33 | * is verified)</li> |
| 34 | * <li>or 'Z', where Z is minimum and maximum is calculated as (minimum of |
| 35 | * adjacent range - 1). Maximum of the last range is never calculated. Range |
| 36 | * stays unbound at maximum side if maximum value is not provided.</li> |
| 37 | * </ul> |
| 38 | * Minimum and maximum values can be from interval <1, Integer.MAX_VALUE-1> |
| 39 | * <p> |
| 40 | * Examples:</br> |
| 41 | * '1, 15, 25, 38, 55-60' is equal to '1-14, 15-24, 25-37, 38-54, 55-60' </br> |
| 42 | * '36, 14, 1-10, 15, 49-57' is equal to '36-48, 14-14, 1-10, 15-35, 49-57' |
| 43 | * <p> |
| 44 | * Property editor also allows to validate whether ranges are disjoint. Validation |
| 45 | * can be turned on/off by using {@link #setForceDisjointRanges(boolean)}. By default |
| 46 | * validation is turned off. |
| 47 | * |
| 48 | * @author peter.zozom |
| 49 | */ |
| 50 | public class RangeArrayPropertyEditor extends PropertyEditorSupport { |
| 51 | |
| 52 | private boolean forceDisjointRanges = false; |
| 53 | |
| 54 | /** |
| 55 | * Set force disjoint ranges. If set to TRUE, ranges are validated to be disjoint. |
| 56 | * For example: defining ranges '1-10, 5-15' will cause IllegalArgumentException in |
| 57 | * case of forceDisjointRanges=TRUE. |
| 58 | * @param forceDisjointRanges |
| 59 | */ |
| 60 | public void setForceDisjointRanges(boolean forceDisjointRanges) { |
| 61 | this.forceDisjointRanges = forceDisjointRanges; |
| 62 | } |
| 63 | |
| 64 | @Override |
| 65 | public void setAsText(String text) throws IllegalArgumentException { |
| 66 | |
| 67 | //split text into ranges |
| 68 | String[] strRanges = text.split(","); |
| 69 | Range[] ranges = new Range[strRanges.length]; |
| 70 | |
| 71 | //parse ranges and create array of Range objects |
| 72 | for (int i = 0; i < strRanges.length; i++) { |
| 73 | String[] range = strRanges[i].split("-"); |
| 74 | |
| 75 | int min; |
| 76 | int max; |
| 77 | |
| 78 | if ((range.length == 1) && (StringUtils.hasText(range[0]))) { |
| 79 | min = Integer.parseInt(range[0].trim()); |
| 80 | // correct max value will be assigned later |
| 81 | ranges[i] = new Range(min); |
| 82 | } else if ((range.length == 2) && (StringUtils.hasText(range[0])) |
| 83 | && (StringUtils.hasText(range[1]))) { |
| 84 | min = Integer.parseInt(range[0].trim()); |
| 85 | max = Integer.parseInt(range[1].trim()); |
| 86 | ranges[i] = new Range(min,max); |
| 87 | } else { |
| 88 | throw new IllegalArgumentException("Range[" + i + "]: range (" + strRanges[i] + ") is invalid"); |
| 89 | } |
| 90 | |
| 91 | } |
| 92 | |
| 93 | setMaxValues(ranges); |
| 94 | setValue(ranges); |
| 95 | } |
| 96 | |
| 97 | @Override |
| 98 | public String getAsText() { |
| 99 | Range[] ranges = (Range[])getValue(); |
| 100 | |
| 101 | StringBuffer sb = new StringBuffer(); |
| 102 | |
| 103 | for (int i = 0; i < ranges.length; i++) { |
| 104 | if(i>0) { |
| 105 | sb.append(", "); |
| 106 | } |
| 107 | sb.append(ranges[i]); |
| 108 | } |
| 109 | return sb.toString(); |
| 110 | } |
| 111 | |
| 112 | private void setMaxValues(final Range[] ranges) { |
| 113 | |
| 114 | // Array of integers to track range values by index |
| 115 | Integer[] c = new Integer[ranges.length]; |
| 116 | for (int i=0; i<c.length; i++) { |
| 117 | c[i] = i; |
| 118 | } |
| 119 | |
| 120 | //sort array of Ranges |
| 121 | Arrays.sort(c, new Comparator<Integer>() { |
| 122 | @Override |
| 123 | public int compare(Integer r1, Integer r2) { |
| 124 | return ranges[r1].getMin()-ranges[r2].getMin(); |
| 125 | } |
| 126 | } |
| 127 | ); |
| 128 | |
| 129 | //set max values for all unbound ranges (except last range) |
| 130 | for (int i = 0; i < c.length - 1; i++) { |
| 131 | if (!ranges[c[i]].hasMaxValue()) { |
| 132 | //set max value to (min value - 1) of the next range |
| 133 | ranges[c[i]] = new Range(ranges[c[i]].getMin(),ranges[c[i+1]].getMin() - 1); |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | if (forceDisjointRanges) { |
| 138 | verifyRanges(ranges); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | |
| 143 | private void verifyRanges(Range[] ranges) { |
| 144 | //verify that ranges are disjoint |
| 145 | for(int i = 1; i < ranges.length;i++) { |
| 146 | Assert.isTrue(ranges[i-1].getMax() < ranges[i].getMin(), |
| 147 | "Ranges must be disjoint. Range[" + (i-1) + "]: (" + ranges[i-1] + |
| 148 | ") Range[" + i +"]: (" + ranges[i] + ")"); |
| 149 | } |
| 150 | } |
| 151 | } |