001/*
002 * Copyright 2006-2007 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.batch.item.file.transform;
018
019import org.springframework.util.Assert;
020import org.springframework.util.StringUtils;
021
022import java.beans.PropertyEditorSupport;
023import java.util.Arrays;
024import java.util.Comparator;
025
026/**
027 * Property editor implementation which parses string and creates array of
028 * ranges. Ranges can be provided in any order. <br> Input string should be
029 * provided in following format: 'range1, range2, range3,...' where range is
030 * specified as:
031 * <ul>
032 * <li>'X-Y', where X is minimum value and Y is maximum value (condition X&lt;=Y
033 * is verified)</li>
034 * <li>or 'Z', where Z is minimum and maximum is calculated as (minimum of
035 * adjacent range - 1). Maximum of the last range is never calculated. Range
036 * stays unbound at maximum side if maximum value is not provided.</li>
037 * </ul>
038 * Minimum and maximum values can be from interval &lt;1, Integer.MAX_VALUE-1&gt;
039 * <p>
040 * Examples:<br>
041 * '1, 15, 25, 38, 55-60' is equal to '1-14, 15-24, 25-37, 38-54, 55-60' <br>
042 * '36, 14, 1-10, 15, 49-57' is equal to '36-48, 14-14, 1-10, 15-35, 49-57'
043 * <p>
044 * Property editor also allows to validate whether ranges are disjoint. Validation
045 * can be turned on/off by using {@link #setForceDisjointRanges(boolean)}. By default 
046 * validation is turned off.
047 * 
048 * @author peter.zozom
049 */
050public class RangeArrayPropertyEditor extends PropertyEditorSupport {
051        
052        private boolean forceDisjointRanges = false;
053        
054        /**
055         * Set force disjoint ranges. If set to TRUE, ranges are validated to be disjoint.
056         * For example: defining ranges '1-10, 5-15' will cause IllegalArgumentException in
057         * case of forceDisjointRanges=TRUE.  
058         * @param forceDisjointRanges  true to force disjoint ranges.
059         */
060        public void setForceDisjointRanges(boolean forceDisjointRanges) {
061                this.forceDisjointRanges = forceDisjointRanges;
062        }
063
064    @Override
065        public void setAsText(String text) throws IllegalArgumentException {
066                
067                //split text into ranges
068                String[] strRanges = text.split(",");
069                Range[] ranges = new Range[strRanges.length];
070                
071                //parse ranges and create array of Range objects 
072                for (int i = 0; i < strRanges.length; i++) {                    
073                        String[] range = strRanges[i].split("-");
074                
075                        int min;
076                        int max;
077                        
078                        if ((range.length == 1) && (StringUtils.hasText(range[0]))) {
079                                min = Integer.parseInt(range[0].trim());
080                                // correct max value will be assigned later
081                                ranges[i] = new Range(min);
082                        } else if ((range.length == 2) && (StringUtils.hasText(range[0]))
083                                        && (StringUtils.hasText(range[1]))) {
084                                min = Integer.parseInt(range[0].trim());
085                                max = Integer.parseInt(range[1].trim());
086                                ranges[i] = new Range(min,max);
087                        } else {
088                                throw new IllegalArgumentException("Range[" + i + "]: range (" + strRanges[i] + ") is invalid");
089                        }                       
090                        
091                }
092        
093                setMaxValues(ranges);
094                setValue(ranges);
095        }
096        
097    @Override
098        public String getAsText() {
099                Range[] ranges = (Range[])getValue();
100                
101                StringBuilder sb = new StringBuilder();
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}