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 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 < 0 || index >= this.length())
57			throw new IndexOutOfBoundsException("Rope index out of range: " + index);
58
59		if (index < this.left.length())
60			return this.left.charAt(index);
61		else
62			return this.right.charAt(index - this.left.length());
63	}
64
65	@Override
66	public byte depth() {
67		return this.depth;
68	}
69
70	@Override
71	public CharSequence getForSequentialAccess() {
72		return this.getForSequentialAccess(this);
73	}
74
75	/*
76	 * Returns this object as a char sequence optimized for
77	 * regular expression searches.
78	 * <p>
79	 * <emph>This method is public only to facilitate unit
80	 * testing.</emph>
81	 */
82	private CharSequence getForSequentialAccess(final Rope rope) {
83		return new CharSequence() {
84
85			private final ConcatenationRopeIteratorImpl iterator = (ConcatenationRopeIteratorImpl) rope.iterator(0);
86
87			@Override
88			public char charAt(final int index) {
89				if (index > this.iterator.getPos()) {
90					this.iterator.skip(index-this.iterator.getPos()-1);
91					try {
92						final char c = this.iterator.next();
93						return c;
94					} catch (final IllegalArgumentException e) {
95						System.out.println("Rope length is: " + rope.length() + " charAt is " + index);
96						throw e;
97					}
98				} else { /* if (index <= lastIndex) */
99					final int toMoveBack = this.iterator.getPos() - index + 1;
100					if (this.iterator.canMoveBackwards(toMoveBack)) {
101						this.iterator.moveBackwards(toMoveBack);
102						return this.iterator.next();
103					} else {
104						return rope.charAt(index);
105					}
106				}
107			}
108
109			@Override
110			public int length() {
111				return rope.length();
112			}
113
114			@Override
115			public CharSequence subSequence(final int start, final int end) {
116				return rope.subSequence(start, end);
117			}
118
119		};
120	}
121
122	/**
123	 * Return the left-hand rope.
124	 * @return the left-hand rope.
125	 */
126	public Rope getLeft() {
127		return this.left;
128	}
129
130	/**
131	 * Return the right-hand rope.
132	 * @return the right-hand rope.
133	 */
134	public Rope getRight() {
135		return this.right;
136	}
137
138	@Override
139	public Iterator<Character> iterator(final int start) {
140		if (start < 0 || start > this.length())
141			throw new IndexOutOfBoundsException("Rope index out of range: " + start);
142		if (start >= this.left.length()) {
143			return this.right.iterator(start - this.left.length());
144		} else {
145			return new ConcatenationRopeIteratorImpl(this, start);
146		}
147	}
148
149	@Override
150	public int length() {
151		return this.length;
152	}
153
154	@Override
155	public Rope rebalance() {
156		return RopeUtilities.INSTANCE.rebalance(this);
157	}
158
159	@Override
160	public Rope reverse() {
161		return new ConcatenationRope(this.getRight().reverse(), this.getLeft().reverse());
162	}
163
164	@Override
165	public Iterator<Character> reverseIterator(final int start) {
166		if (start < 0 || start > this.length())
167			throw new IndexOutOfBoundsException("Rope index out of range: " + start);
168		if (start >= this.right.length()) {
169			return this.left.reverseIterator(start - this.right.length());
170		} else {
171			return new ConcatenationRopeReverseIteratorImpl(this, start);
172		}
173	}
174
175	@Override
176	public Rope subSequence(final int start, final int end) {
177		if (start < 0 || end > this.length())
178			throw new IllegalArgumentException("Illegal subsequence (" + start + "," + end + ")");
179		if (start == 0 && end == this.length())
180			return this;
181		final int l = this.left.length();
182		if (end <= l)
183			return this.left.subSequence(start, end);
184		if (start >= l)
185			return this.right.subSequence(start - l, end - l);
186		return RopeUtilities.INSTANCE.concatenate(
187			this.left.subSequence(start, l),
188			this.right.subSequence(0, end - l));
189	}
190
191	@Override
192	public void write(final Writer out) throws IOException {
193		this.left.write(out);
194		this.right.write(out);
195	}
196
197	@Override
198	public void write(final Writer out, final int offset, final int length) throws IOException {
199		if (offset + length < this.left.length()) {
200			this.left.write(out, offset, length);
201		} else if (offset >= this.left.length()) {
202			this.right.write(out, offset - this.left.length(), length);
203		} else {
204			final int writeLeft = this.left.length() - offset;
205			this.left.write(out, offset, writeLeft);
206			this.right.write(out, 0, this.right.length() - writeLeft);
207		}
208	}
209}
210