001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.functor.range;
018
019import java.util.Iterator;
020
021import org.apache.commons.functor.BinaryFunction;
022import org.apache.commons.lang3.Validate;
023
024/**
025 * A generator for a range of characters.
026 *
027 * @since 1.0
028 * @version $Revision$ $Date$
029 */
030public final class CharacterRange extends AbstractRange<Character, Integer> {
031
032    /**
033     * Calculate default step.
034     */
035    public static final BinaryFunction<Character, Character, Integer> DEFAULT_STEP =
036        new BinaryFunction<Character, Character, Integer>() {
037
038            public Integer evaluate(Character left, Character right) {
039                return left > right ? -1 : 1;
040            }
041        };
042
043    // constructors
044    // ---------------------------------------------------------------
045    /**
046     * Create a new CharacterRange.
047     *
048     * @param from start
049     * @param to end
050     */
051    public CharacterRange(char from, char to) {
052        this(from, to, DEFAULT_STEP.evaluate(from, to).intValue());
053    }
054
055    /**
056     * Create a new CharacterRange.
057     *
058     * @param from start
059     * @param to end
060     * @param step increment
061     */
062    public CharacterRange(char from, char to, int step) {
063        this(from, BoundType.CLOSED, to, BoundType.CLOSED, step);
064    }
065
066    /**
067     * Create a new CharacterRange.
068     *
069     * @param from start
070     * @param to end
071     * @throws NullPointerException if either {@link Endpoint} is {@code null}
072     */
073    public CharacterRange(Endpoint<Character> from, Endpoint<Character> to) {
074        this(from, to, DEFAULT_STEP.evaluate(from.getValue(), to.getValue()));
075    }
076
077    /**
078     * Create a new CharacterRange.
079     *
080     * @param from start
081     * @param to end
082     * @param step increment
083     * @throws NullPointerException if either {@link Endpoint} is {@code null}
084     */
085    public CharacterRange(Endpoint<Character> from, Endpoint<Character> to, int step) {
086        super(from, to, Integer.valueOf(step), new BinaryFunction<Character, Integer, Character>() {
087
088            public Character evaluate(Character left, Integer right) {
089                return Character.valueOf((char) (left.charValue() + right.intValue()));
090            }
091        });
092        final char f = from.getValue();
093        final char t = to.getValue();
094
095        Validate.isTrue(f == t || Integer.signum(step) == Integer.signum(t - f),
096            "Will never reach '%s' from '%s' using step %s", t, f, step);
097    }
098
099    /**
100     * Create a new CharacterRange.
101     *
102     * @param from start
103     * @param leftBoundType type of left bound
104     * @param to end
105     * @param rightBoundType type of right bound
106     * @throws NullPointerException if either bound type is {@code null}
107     */
108    public CharacterRange(char from, BoundType leftBoundType, char to, BoundType rightBoundType) {
109        this(from, leftBoundType, to, rightBoundType, DEFAULT_STEP.evaluate(from, to));
110    }
111
112    /**
113     * Create a new CharacterRange.
114     *
115     * @param from start
116     * @param leftBoundType type of left bound
117     * @param to end
118     * @param rightBoundType type of right bound
119     * @param step increment
120     * @throws NullPointerException if either bound type is {@code null}
121     */
122    public CharacterRange(char from, BoundType leftBoundType, char to, BoundType rightBoundType, int step) {
123        this(new Endpoint<Character>(from, leftBoundType), new Endpoint<Character>(to, rightBoundType), step);
124    }
125
126    // range methods
127    // ---------------------------------------------------------------
128
129    /**
130     * {@inheritDoc}
131     */
132    public boolean contains(Character obj) {
133        if (obj == null) {
134            return Boolean.FALSE;
135        }
136        char leftValue = this.getLeftEndpoint().getValue().charValue();
137        char rightValue = this.getRightEndpoint().getValue().charValue();
138        boolean includeLeft = this.getLeftEndpoint().getBoundType() == BoundType.CLOSED;
139        boolean includeRight = this.getRightEndpoint().getBoundType() == BoundType.CLOSED;
140        int step = this.getStep().intValue();
141        int value = (int) obj.charValue();
142
143        int firstValue = 0;
144        int lastValue = 0;
145
146        if (step < 0.0) {
147            firstValue = includeLeft ? leftValue : leftValue + step;
148            lastValue = includeRight ? rightValue : rightValue + 1;
149            if (value > firstValue || value < lastValue) {
150                return Boolean.FALSE;
151            }
152        } else {
153            firstValue = includeLeft ? leftValue : leftValue + step;
154            lastValue = includeRight ? rightValue : rightValue - 1;
155            if (value < firstValue || value > lastValue) {
156                return Boolean.FALSE;
157            }
158        }
159        return ((double) (value - firstValue) / step + 1) % 1.0 == 0.0;
160    }
161
162    /**
163     * {@inheritDoc}
164     */
165    protected Iterator<Character> createIterator() {
166        return new Iterator<Character>() {
167            private char currentValue;
168
169            {
170                currentValue = leftEndpoint.getValue();
171
172                if (leftEndpoint.getBoundType() == BoundType.OPEN) {
173                    this.currentValue += step;
174                }
175            }
176
177            public void remove() {
178                throw new UnsupportedOperationException();
179            }
180
181            public Character next() {
182                final int step = getStep();
183                final char r = currentValue;
184                currentValue += step;
185                return Character.valueOf(r);
186            }
187
188            public boolean hasNext() {
189                final int cmp = Character.valueOf(currentValue).compareTo(rightEndpoint.getValue());
190
191                if (cmp == 0) {
192                    return rightEndpoint.getBoundType() == BoundType.CLOSED;
193                }
194                if (step > 0) {
195                    return cmp < 0;
196                }
197                return cmp > 0;
198            }
199        };
200    }
201
202}