1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.functor.generator.range;
19  
20  import java.util.Collection;
21  
22  import org.apache.commons.functor.BinaryFunction;
23  import org.apache.commons.functor.UnaryProcedure;
24  import org.apache.commons.functor.generator.loop.LoopGenerator;
25  import org.apache.commons.lang3.Validate;
26  
27  /**
28   * A generator for a range of characters.
29   *
30   * @since 1.0
31   * @version $Revision: $ $Date: $
32   */
33  public final class CharacterRange extends LoopGenerator<Character> implements Range<Character, Integer> {
34  
35      // attributes
36      // ---------------------------------------------------------------
37      /**
38       * Default left bound type.
39       */
40      public static final BoundType DEFAULT_LEFT_BOUND_TYPE = BoundType.CLOSED;
41  
42      /**
43       * Default right bound type.
44       */
45      public static final BoundType DEFAULT_RIGHT_BOUND_TYPE = BoundType.CLOSED;
46  
47      /**
48       * Left limit.
49       */
50      private final Endpoint<Character> leftEndpoint;
51  
52      /**
53       * Right limit.
54       */
55      private final Endpoint<Character> rightEndpoint;
56  
57      /**
58       * Increment step.
59       */
60      private final int step;
61  
62      /**
63       * Calculate default step.
64       */
65      public static final BinaryFunction<Character, Character, Integer> DEFAULT_STEP
66          = new BinaryFunction<Character, Character, Integer>() {
67  
68          public Integer evaluate(Character left, Character right) {
69              return left > right ? -1 : 1;
70          }
71      };
72  
73      // constructors
74      // ---------------------------------------------------------------
75      /**
76       * Create a new CharacterRange.
77       *
78       * @param from start
79       * @param to end
80       */
81      public CharacterRange(char from, char to) {
82          this(from, to, DEFAULT_STEP.evaluate(from, to).intValue());
83      }
84  
85      /**
86       * Create a new CharacterRange.
87       *
88       * @param from start
89       * @param to end
90       * @param step increment
91       */
92      public CharacterRange(char from, char to, int step) {
93          this(from, BoundType.CLOSED, to, BoundType.CLOSED, step);
94      }
95  
96      /**
97       * Create a new CharacterRange.
98       *
99       * @param from start
100      * @param leftBoundType type of left bound
101      * @param to end
102      * @param rightBoundType type of right bound
103      * @param step increment
104      */
105     public CharacterRange(char from, BoundType leftBoundType, char to,
106                           BoundType rightBoundType, int step) {
107         this.leftEndpoint = Validate
108             .notNull(new Endpoint<Character>(from, leftBoundType),
109                      "Left Endpoint argument must not be null");
110         this.rightEndpoint = Validate
111             .notNull(new Endpoint<Character>(to, rightBoundType),
112                      "Right Endpoint argument must not be null");
113         this.step = step;
114         if (from != to && Integer.signum(step) != Integer.signum(to - from)) {
115             throw new IllegalArgumentException("Will never reach " + to
116                                                + " from " + from
117                                                + " using step " + step);
118         }
119     }
120 
121     /**
122      * Create a new CharacterRange.
123      *
124      * @param from start
125      * @param to end
126      * @param step increment
127      */
128     public CharacterRange(Endpoint<Character> from, Endpoint<Character> to,
129                           int step) {
130         this.leftEndpoint = Validate
131             .notNull(from, "Left Endpoint argument must not be null");
132         this.rightEndpoint = Validate
133             .notNull(to, "Right Endpoint argument must not be null");
134         this.step = step;
135         if (from != to
136             && Integer.signum(step) != Integer.signum(to.getValue()
137                                                    - from.getValue())) {
138             throw new IllegalArgumentException("Will never reach " + to
139                                                + " from " + from
140                                                + " using step " + step);
141         }
142     }
143 
144     // methods
145     // ---------------------------------------------------------------
146     /**
147      * {@inheritDoc}
148      */
149     public Endpoint<Character> getLeftEndpoint() {
150         return this.leftEndpoint;
151     }
152 
153     /**
154      * {@inheritDoc}
155      */
156     public Endpoint<Character> getRightEndpoint() {
157         return this.rightEndpoint;
158     }
159 
160     /**
161      * {@inheritDoc}
162      */
163     public Integer getStep() {
164         return this.step;
165     }
166 
167     /**
168      * {@inheritDoc}
169      */
170     public void run(UnaryProcedure<? super Character> proc) {
171         final int step = this.getStep();
172         final boolean includeLeftValue = this.getLeftEndpoint()
173             .getBoundType() == BoundType.CLOSED;
174         final boolean includeRightValue = this.getRightEndpoint()
175             .getBoundType() == BoundType.CLOSED;
176         final char leftValue = this.getLeftEndpoint().getValue();
177         final char rightValue = this.getRightEndpoint().getValue();
178         if (step < 0) {
179             final char from = (char) (includeLeftValue ? leftValue : leftValue
180                                                                      + step);
181             if (includeRightValue) {
182                 for (char i = from; i >= rightValue; i += step) {
183                     proc.run(i);
184                 }
185             } else {
186                 for (char i = from; i > rightValue; i += step) {
187                     proc.run(i);
188                 }
189             }
190         } else {
191             final char from = (char) (includeLeftValue ? this
192                 .getLeftEndpoint().getValue() : (this.getLeftEndpoint()
193                 .getValue() + step));
194             if (includeRightValue) {
195                 for (char i = from; i <= rightValue; i += step) {
196                     proc.run(i);
197                 }
198             } else {
199                 for (char i = from; i < rightValue; i += step) {
200                     proc.run(i);
201                 }
202             }
203         }
204     }
205 
206     /**
207      * {@inheritDoc}
208      */
209     @Override
210     public String toString() {
211         return "CharacterRange<" + this.leftEndpoint.toLeftString() + ", "
212                 + this.rightEndpoint.toRightString() + ", " + step + ">";
213     }
214 
215     /**
216      * {@inheritDoc}
217      */
218     @Override
219     public boolean equals(Object obj) {
220         if (obj == this) {
221             return true;
222         }
223         if (!(obj instanceof CharacterRange)) {
224             return false;
225         }
226         CharacterRange that = (CharacterRange) obj;
227         return this.leftEndpoint.equals(that.leftEndpoint)
228                 && this.rightEndpoint.equals(that.rightEndpoint)
229                 && this.step == that.step;
230     }
231 
232     /**
233      * {@inheritDoc}
234      */
235     @Override
236     public int hashCode() {
237         int hash = "CharacterRange".hashCode();
238         hash <<= 2;
239         hash ^= this.leftEndpoint.getValue();
240         hash <<= 2;
241         hash ^= this.rightEndpoint.getValue();
242         hash <<= 2;
243         hash ^= this.step;
244         return hash;
245     }
246 
247     /**
248      * {@inheritDoc}
249      */
250     public boolean isEmpty() {
251         double leftValue = this.getLeftEndpoint().getValue().charValue();
252         double rightValue = this.getRightEndpoint().getValue().charValue();
253         boolean closedLeft = this.getLeftEndpoint().getBoundType() == BoundType.CLOSED;
254         boolean closedRight = this.getRightEndpoint().getBoundType() == BoundType.CLOSED;
255         if (!closedLeft && !closedRight
256                 && this.getLeftEndpoint().equals(this.getRightEndpoint())) {
257             return Boolean.TRUE;
258         }
259         double step = this.getStep().intValue();
260         if (step > 0.0) {
261             double firstValue = closedLeft ? leftValue : leftValue + step;
262             return closedRight ? firstValue > rightValue
263                               : firstValue >= rightValue;
264         } else {
265             double firstValue = closedLeft ? leftValue : leftValue + step;
266             return closedRight ? firstValue < rightValue
267                               : firstValue <= rightValue;
268         }
269     }
270 
271     /**
272      * {@inheritDoc}
273      */
274     public boolean contains(Character obj) {
275         if (obj == null) {
276             return Boolean.FALSE;
277         }
278         char leftValue = this.getLeftEndpoint().getValue().charValue();
279         char rightValue = this.getRightEndpoint().getValue().charValue();
280         boolean includeLeft = this.getLeftEndpoint().getBoundType() == BoundType.CLOSED;
281         boolean includeRight = this.getRightEndpoint().getBoundType() == BoundType.CLOSED;
282         int step = this.getStep().intValue();
283         int value = (int) obj.charValue();
284 
285         int firstValue = 0;
286         int lastValue = 0;
287 
288         if (step < 0.0) {
289             firstValue = includeLeft ? leftValue : leftValue + step;
290             lastValue = includeRight ? rightValue : rightValue + 1;
291             if (value > firstValue || value < lastValue) {
292                 return Boolean.FALSE;
293             }
294         } else {
295             firstValue = includeLeft ? leftValue : leftValue + step;
296             lastValue = includeRight ? rightValue : rightValue - 1;
297             if (value < firstValue || value > lastValue) {
298                 return Boolean.FALSE;
299             }
300         }
301         return ((double) (value - firstValue) / step + 1) % 1.0 == 0.0;
302     }
303 
304     /**
305      * {@inheritDoc}
306      */
307     public boolean containsAll(Collection<Character> col) {
308         if (col == null || col.size() == 0) {
309             return Boolean.FALSE;
310         }
311         boolean r = Boolean.TRUE;
312         for (Character t : col) {
313             if (!this.contains(t)) {
314                 r = Boolean.FALSE;
315                 break;
316             }
317         }
318         return r;
319     }
320 
321 }