/* * AbstractRope.java * Copyright (C) 2007 Amin Ahmad. * * This file is part of Java Ropes. * * Java Ropes is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Java Ropes is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Java Ropes. If not, see . * * Amin Ahmad can be contacted at amin.ahmad@gmail.com or on the web at * www.ahmadsoft.org. */ package org.ahmadsoft.ropes.impl; import java.io.IOException; import java.io.ObjectStreamException; import java.io.StringWriter; import java.util.Arrays; import java.util.Iterator; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.ahmadsoft.ropes.Rope; /** * Abstract base class for ropes that implements many of the common operations. * @author Amin Ahmad */ public abstract class AbstractRope implements Rope { protected int hashCode = 0; @Override public Rope append(final char c) { return RopeUtilities.INSTANCE.concatenate(this, Rope.BUILDER.build(String.valueOf(c))); } @Override public Rope append(final CharSequence suffix) { return RopeUtilities.INSTANCE.concatenate(this, Rope.BUILDER.build(suffix)); } @Override public Rope append(final CharSequence csq, final int start, final int end) { return RopeUtilities.INSTANCE.concatenate(this, Rope.BUILDER.build(csq).subSequence(start, end)); } @Override public int compareTo(final CharSequence sequence) { final int compareTill = Math.min(sequence.length(), this.length()); final Iterator i = this.iterator(); for (int j=0; j i1 = this.iterator(); final Iterator i2 = rope.iterator(); while (i1.hasNext()) { final char a = i1.next(); final char b = i2.next(); if (a != b) return false; } return true; } return false; } /** * A utility method that returns an instance of this rope optimized * for sequential access. * @return */ protected CharSequence getForSequentialAccess() { return this; } @Override public int hashCode() { if (this.hashCode == 0 && this.length() > 0) { if (this.length() < 6) { for (final char c: this) this.hashCode = 31 * this.hashCode + c; } else { final Iterator i = this.iterator(); for (int j=0;j<5; ++j) this.hashCode = 31 * this.hashCode + i.next(); this.hashCode = 31 * this.hashCode + this.charAt(this.length() - 1); } } return this.hashCode; } @Override public int indexOf(final char ch) { int index = -1; for (final char c: this) { ++index; if (c == ch) return index; } return -1; } @Override public boolean startsWith(CharSequence prefix) { return startsWith(prefix, 0); } @Override public boolean startsWith(CharSequence prefix, int offset) { if (offset < 0 || offset > this.length()) throw new IndexOutOfBoundsException("Rope offset out of range: " + offset); if (offset + prefix.length() > this.length()) return false; int x=0; for (Iterator i=this.iterator(offset); i.hasNext() && x < prefix.length(); ) { if (i.next().charValue() != prefix.charAt(x++)) return false; } return true; } @Override public boolean endsWith(CharSequence suffix) { return endsWith(suffix, 0); } @Override public boolean endsWith(CharSequence suffix, int offset) { return startsWith(suffix, length() - suffix.length() - offset); } @Override public int indexOf(final char ch, final int fromIndex) { if (fromIndex < 0 || fromIndex >= this.length()) throw new IndexOutOfBoundsException("Rope index out of range: " + fromIndex); int index = fromIndex - 1; for (final Iterator i=this.iterator(fromIndex); i.hasNext(); ) { ++index; if (i.next().charValue() == ch) return index; } return -1; } @Override public int indexOf(final CharSequence sequence) { return this.indexOf(sequence, 0); } @Override public int indexOf(final CharSequence sequence, final int fromIndex) { final CharSequence me = this.getForSequentialAccess(); // Implementation of Boyer-Moore-Horspool algorithm with // special support for unicode. // step 0. sanity check. final int length = sequence.length(); if (length == 0) return -1; if (length == 1) return this.indexOf(sequence.charAt(0), fromIndex); final int[] bcs = new int[256]; // bad character shift Arrays.fill(bcs, length); // step 1. preprocessing. for (int j=0; j this.length()) throw new IndexOutOfBoundsException(dstOffset + " is out of insert range [" + 0 + ":" + this.length() + "]"); return this.subSequence(0, dstOffset).append(r).append(this.subSequence(dstOffset, this.length())); } @Override public Iterator iterator() { return this.iterator(0); } @Override public Rope trimStart() { int index = -1; for (final char c: this) { ++index; if (c > 0x20 && !Character.isWhitespace(c)) break; } if (index <= 0) return this; else return this.subSequence(index, this.length()); } @Override public Matcher matcher(final Pattern pattern) { return pattern.matcher(this.getForSequentialAccess()); } @Override public boolean matches(final Pattern regex) { return regex.matcher(this.getForSequentialAccess()).matches(); } @Override public boolean matches(final String regex) { return Pattern.matches(regex, this.getForSequentialAccess()); } @Override public Rope rebalance() { return this; } @Override public Iterator reverseIterator() { return this.reverseIterator(0); } @Override public Rope trimEnd() { int index = this.length() + 1; for (final Iterator i=this.reverseIterator(); i.hasNext();) { final char c = i.next(); --index; if (c > 0x20 && !Character.isWhitespace(c)) break; } if (index >= this.length()) return this; else return this.subSequence(0, index); } @Override public String toString() { final StringWriter out = new StringWriter(this.length()); try { this.write(out); out.close(); } catch (final IOException e) { throw new RuntimeException(e); } return out.toString(); } @Override public Rope trim() { return this.trimStart().trimEnd(); } public Object writeReplace() throws ObjectStreamException { return new SerializedRope(this); } @Override public Rope padStart(final int toWidth) { return padStart(toWidth, ' '); } @Override public Rope padStart(final int toWidth, final char padChar) { final int toPad = toWidth - this.length(); if (toPad < 1) return this; return RopeUtilities.INSTANCE.concatenate( Rope.BUILDER.build(new RepeatedCharacterSequence(padChar, toPad)), this); } @Override public Rope padEnd(final int toWidth) { return padEnd(toWidth, ' '); } @Override public Rope padEnd(final int toWidth, final char padChar) { final int toPad = toWidth - this.length(); if (toPad < 1) return this; return RopeUtilities.INSTANCE.concatenate( this, Rope.BUILDER.build(new RepeatedCharacterSequence(padChar, toPad))); } @Override public boolean isEmpty() { return length() == 0; } }