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