1/*
2 *  ConcatenationRope.java
3 *  Copyright (C) 2007 Amin Ahmad.
4 *
5 *  This file is part of Java Ropes.
6 *
7 *  Java Ropes is free software: you can redistribute it and/or modify
8 *  it under the terms of the GNU General Public License as published by
9 *  the Free Software Foundation, either version 3 of the License, or
10 *  (at your option) any later version.
11 *
12 *  Java Ropes is distributed in the hope that it will be useful,
13 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 *  GNU General Public License for more details.
16 *
17 *  You should have received a copy of the GNU General Public License
18 *  along with Java Ropes.  If not, see <http://www.gnu.org/licenses/>.
19 *
20 *  Amin Ahmad can be contacted at amin.ahmad@gmail.com or on the web at
21 *  www.ahmadsoft.org.
22 */
23package org.ahmadsoft.ropes.impl;
24
25import java.io.IOException;
26import java.io.Writer;
27import java.util.Iterator;
28
29import org.ahmadsoft.ropes.Rope;
30
31/**
32 * A rope that represents the concatenation of two other ropes.
33 * @author Amin Ahmad
34 */
35public final class ConcatenationRope extends AbstractRope {
36
37    private final Rope left;
38    private final Rope right;
39    private final byte depth;
40    private final int length;
41
42    /**
43     * Create a new concatenation rope from two ropes.
44     * @param left the first rope.
45     * @param right the second rope.
46     */
47    public ConcatenationRope(final Rope left, final Rope right) {
48        this.left   = left;
49        this.right  = right;
50        this.depth  = (byte) (Math.max(RopeUtilities.INSTANCE.depth(left), RopeUtilities.INSTANCE.depth(right)) + 1);
51        this.length = left.length() + right.length();
52    }
53
54    @Override
55    public char charAt(final int index) {
56        if (index >= this.length())
57            throw new IndexOutOfBoundsException("Rope index out of range: " + index);
58
59        return (index < this.left.length() ? this.left.charAt(index): this.right.charAt(index - this.left.length()));
60    }
61
62    @Override
63    public byte depth() {
64        return this.depth;
65    }
66
67    @Override
68    public CharSequence getForSequentialAccess() {
69        return this.getForSequentialAccess(this);
70    }
71
72    /*
73     * Returns this object as a char sequence optimized for
74     * regular expression searches.
75     * <p>
76     * <emph>This method is public only to facilitate unit
77     * testing.</emph>
78     */
79    private CharSequence getForSequentialAccess(final Rope rope) {
80        return new CharSequence() {
81
82            private final ConcatenationRopeIteratorImpl iterator = (ConcatenationRopeIteratorImpl) rope.iterator(0);
83
84            @Override
85            public char charAt(final int index) {
86                if (index > this.iterator.getPos()) {
87                    this.iterator.skip(index-this.iterator.getPos()-1);
88                    try {
89                        final char c = this.iterator.next();
90                        return c;
91                    } catch (final IllegalArgumentException e) {
92                        System.out.println("Rope length is: " + rope.length() + " charAt is " + index);
93                        throw e;
94                    }
95                } else { /* if (index <= lastIndex) */
96                    final int toMoveBack = this.iterator.getPos() - index + 1;
97                    if (this.iterator.canMoveBackwards(toMoveBack)) {
98                        this.iterator.moveBackwards(toMoveBack);
99                        return this.iterator.next();
100                    } else {
101                        return rope.charAt(index);
102                    }
103                }
104            }
105
106            @Override
107            public int length() {
108                return rope.length();
109            }
110
111            @Override
112            public CharSequence subSequence(final int start, final int end) {
113                return rope.subSequence(start, end);
114            }
115
116        };
117    }
118
119    /**
120     * Return the left-hand rope.
121     * @return the left-hand rope.
122     */
123    public Rope getLeft() {
124        return this.left;
125    }
126
127    /**
128     * Return the right-hand rope.
129     * @return the right-hand rope.
130     */
131    public Rope getRight() {
132        return this.right;
133    }
134
135    @Override
136    public Iterator<Character> iterator(final int start) {
137        if (start < 0 || start > this.length())
138            throw new IndexOutOfBoundsException("Rope index out of range: " + start);
139        if (start >= this.left.length()) {
140            return this.right.iterator(start - this.left.length());
141        } else {
142            return new ConcatenationRopeIteratorImpl(this, start);
143        }
144    }
145
146    @Override
147    public int length() {
148        return this.length;
149    }
150
151    @Override
152    public Rope rebalance() {
153        return RopeUtilities.INSTANCE.rebalance(this);
154    }
155
156    @Override
157    public Rope reverse() {
158        return RopeUtilities.INSTANCE.concatenate(this.getRight().reverse(), this.getLeft().reverse());
159    }
160
161    @Override
162    public Iterator<Character> reverseIterator(final int start) {
163        if (start < 0 || start > this.length())
164            throw new IndexOutOfBoundsException("Rope index out of range: " + start);
165        if (start >= this.right.length()) {
166            return this.left.reverseIterator(start - this.right.length());
167        } else {
168            return new ConcatenationRopeReverseIteratorImpl(this, start);
169        }
170    }
171
172    @Override
173    public Rope subSequence(final int start, final int end) {
174        if (start < 0 || end > this.length())
175            throw new IllegalArgumentException("Illegal subsequence (" + start + "," + end + ")");
176        if (start == 0 && end == this.length())
177            return this;
178        final int l = this.left.length();
179        if (end <= l)
180            return this.left.subSequence(start, end);
181        if (start >= l)
182            return this.right.subSequence(start - l, end - l);
183        return RopeUtilities.INSTANCE.concatenate(
184            this.left.subSequence(start, l),
185            this.right.subSequence(0, end - l));
186    }
187
188    @Override
189    public void write(final Writer out) throws IOException {
190        this.left.write(out);
191        this.right.write(out);
192    }
193
194    @Override
195    public void write(final Writer out, final int offset, final int length) throws IOException {
196        if (offset + length <= this.left.length()) {
197            this.left.write(out, offset, length);
198        } else if (offset >= this.left.length()) {
199            this.right.write(out, offset - this.left.length(), length);
200        } else {
201            final int writeLeft = this.left.length() - offset;
202            this.left.write(out, offset, writeLeft);
203            this.right.write(out, 0, length - writeLeft);
204        }
205    }
206
207
208//  /**
209//   * Not currently used. Can be used if rebalancing is performed
210//   * during concatenation.
211//   **/
212//  private ConcatenationRope rotateLeft(final ConcatenationRope input) {
213//      final Rope _R = input.getRight();
214//      if (!(_R instanceof ConcatenationRope))
215//          return input;
216//      final ConcatenationRope R = (ConcatenationRope) _R;
217//      final Rope L = input.getLeft();
218//      final Rope A = R.getLeft();
219//      final Rope B = R.getRight();
220//      return new ConcatenationRope(new ConcatenationRope(L, A), B);
221//  }
222//
223//  /**
224//   * Not currently used. Can be used if rebalancing is performed
225//   * during concatenation.
226//   **/
227//  private ConcatenationRope rotateRight(final ConcatenationRope input) {
228//      final Rope _L = input.getLeft();
229//      if (!(_L instanceof ConcatenationRope))
230//          return input;
231//      final ConcatenationRope L = (ConcatenationRope) _L;
232//      final Rope R = input.getRight();
233//      final Rope A = L.getLeft();
234//      final Rope B = L.getRight();
235//      return new ConcatenationRope(A, new ConcatenationRope(B, R));
236//  }
237}
238