Index: src/java/org/apache/lucene/analysis/tokenattributes/CharTermAttribute.java =================================================================== --- src/java/org/apache/lucene/analysis/tokenattributes/CharTermAttribute.java (revision 934695) +++ src/java/org/apache/lucene/analysis/tokenattributes/CharTermAttribute.java (working copy) @@ -68,4 +68,24 @@ public CharTermAttribute append(CharSequence csq, int start, int end); public CharTermAttribute append(char c); + /** Appends the specified {@code String} to this character sequence. + *

The characters of the {@code String} argument are appended, in order, increasing the length of + * this sequence by the length of the argument. If argument is {@code null}, then the four + * characters {@code "null"} are appended. + */ + public CharTermAttribute append(String s); + + /** Appends the specified {@code StringBuilder} to this character sequence. + *

The characters of the {@code StringBuilder} argument are appended, in order, increasing the length of + * this sequence by the length of the argument. If argument is {@code null}, then the four + * characters {@code "null"} are appended. + */ + public CharTermAttribute append(StringBuilder sb); + + /** Appends the contents of the other {@code CharTermAttribute} to this character sequence. + *

The characters of the {@code CharTermAttribute} argument are appended, in order, increasing the length of + * this sequence by the length of the argument. If argument is {@code null}, then the four + * characters {@code "null"} are appended. + */ + public CharTermAttribute append(CharTermAttribute termAtt); } Index: src/java/org/apache/lucene/analysis/tokenattributes/CharTermAttributeImpl.java =================================================================== --- src/java/org/apache/lucene/analysis/tokenattributes/CharTermAttributeImpl.java (revision 934695) +++ src/java/org/apache/lucene/analysis/tokenattributes/CharTermAttributeImpl.java (working copy) @@ -41,7 +41,7 @@ return new String(termBuffer, 0, termLength); } - public void copyBuffer(char[] buffer, int offset, int length) { + public final void copyBuffer(char[] buffer, int offset, int length) { growTermBuffer(length); System.arraycopy(buffer, offset, termBuffer, 0, length); termLength = length; @@ -69,7 +69,7 @@ termLength = length; } - public char[] buffer() { + public final char[] buffer() { return termBuffer; } @@ -78,7 +78,7 @@ return termBuffer; } - public char[] resizeBuffer(int newSize) { + public final char[] resizeBuffer(int newSize) { if(termBuffer.length < newSize){ // Not big enough; create a new array with slight // over allocation and preserve content @@ -107,14 +107,14 @@ return termLength; } - public CharTermAttribute setLength(int length) { + public final CharTermAttribute setLength(int length) { if (length > termBuffer.length) throw new IllegalArgumentException("length " + length + " exceeds the size of the termBuffer (" + termBuffer.length + ")"); termLength = length; return this; } - public CharTermAttribute setEmpty() { + public final CharTermAttribute setEmpty() { termLength = 0; return this; } @@ -125,7 +125,7 @@ } // *** TermToBytesRefAttribute interface *** - public int toBytesRef(BytesRef target) { + public final int toBytesRef(BytesRef target) { // TODO: Maybe require that bytes is already initialized? TermsHashPerField ensures this. if (target.bytes == null) { target.bytes = new byte[termLength * 4]; @@ -134,53 +134,107 @@ } // *** CharSequence interface *** - public int length() { + public final int length() { return termLength; } - public char charAt(int index) { + public final char charAt(int index) { if (index >= termLength) throw new IndexOutOfBoundsException(); return termBuffer[index]; } - public CharSequence subSequence(final int start, final int end) { + public final CharSequence subSequence(final int start, final int end) { if (start > termLength || end > termLength) throw new IndexOutOfBoundsException(); return new String(termBuffer, start, end - start); } // *** Appendable interface *** - public CharTermAttribute append(CharSequence csq) { + + public final CharTermAttribute append(CharSequence csq) { + if (csq == null) // needed for Appendable compliance + return appendNull(); return append(csq, 0, csq.length()); } - public CharTermAttribute append(CharSequence csq, int start, int end) { - resizeBuffer(termLength + end - start); - if (csq instanceof String) { - ((String) csq).getChars(start, end, termBuffer, termLength); - } else if (csq instanceof StringBuilder) { - ((StringBuilder) csq).getChars(start, end, termBuffer, termLength); - } else if (csq instanceof StringBuffer) { - ((StringBuffer) csq).getChars(start, end, termBuffer, termLength); - } else if (csq instanceof CharBuffer && ((CharBuffer) csq).hasArray()) { - final CharBuffer cb = (CharBuffer) csq; - System.arraycopy(cb.array(), cb.arrayOffset() + cb.position() + start, termBuffer, termLength, end - start); + public final CharTermAttribute append(CharSequence csq, int start, int end) { + if (csq == null) // needed for Appendable compliance + csq = "null"; + final int len = end - start, csqlen = csq.length(); + if (len < 0 || start > csqlen || end > csqlen) + throw new IndexOutOfBoundsException(); + resizeBuffer(termLength + len); + if (len > 8) { // only use instanceof check series for longer CSQs, else simply iterate + if (csq instanceof String) { + ((String) csq).getChars(start, end, termBuffer, termLength); + } else if (csq instanceof StringBuilder) { + ((StringBuilder) csq).getChars(start, end, termBuffer, termLength); + } else if (csq instanceof CharTermAttribute) { + System.arraycopy(((CharTermAttribute) csq).buffer(), start, termBuffer, termLength, len); + } else if (csq instanceof CharBuffer && ((CharBuffer) csq).hasArray()) { + final CharBuffer cb = (CharBuffer) csq; + System.arraycopy(cb.array(), cb.arrayOffset() + cb.position() + start, termBuffer, termLength, len); + } else if (csq instanceof StringBuffer) { + ((StringBuffer) csq).getChars(start, end, termBuffer, termLength); + } else { + while (start < end) + termBuffer[termLength++] = csq.charAt(start++); + // no fall-through here, as termLength is updated! + return this; + } + termLength += len; + return this; } else { while (start < end) termBuffer[termLength++] = csq.charAt(start++); - // no fall-through here, as termLength is updated! return this; } - termLength += end - start; - return this; } - public CharTermAttribute append(char c) { + public final CharTermAttribute append(char c) { resizeBuffer(termLength + 1)[termLength++] = c; return this; } + // *** For performance some convenience methods in addition to CSQ's *** + + public final CharTermAttribute append(String s) { + if (s == null) // needed for Appendable compliance + return appendNull(); + final int len = s.length(); + s.getChars(0, len, resizeBuffer(termLength + len), termLength); + termLength += len; + return this; + } + + public final CharTermAttribute append(StringBuilder s) { + if (s == null) // needed for Appendable compliance + return appendNull(); + final int len = s.length(); + s.getChars(0, len, resizeBuffer(termLength + len), termLength); + termLength += len; + return this; + } + + public final CharTermAttribute append(CharTermAttribute ta) { + if (ta == null) // needed for Appendable compliance + return appendNull(); + final int len = ta.length(); + System.arraycopy(ta.buffer(), 0, resizeBuffer(termLength + len), termLength, len); + termLength += len; + return this; + } + + private CharTermAttribute appendNull() { + resizeBuffer(termLength + 4); + termBuffer[termLength++] = 'n'; + termBuffer[termLength++] = 'u'; + termBuffer[termLength++] = 'l'; + termBuffer[termLength++] = 'l'; + return this; + } + // *** AttributeImpl *** @Override Index: src/test/org/apache/lucene/analysis/tokenattributes/TestCharTermAttributeImpl.java =================================================================== --- src/test/org/apache/lucene/analysis/tokenattributes/TestCharTermAttributeImpl.java (revision 934695) +++ src/test/org/apache/lucene/analysis/tokenattributes/TestCharTermAttributeImpl.java (working copy) @@ -157,21 +157,107 @@ assertEquals("12345678", t.toString()); t.append('9'); assertEquals("123456789", t.toString()); - t.append("0"); + t.append((CharSequence) "0"); assertEquals("1234567890", t.toString()); - t.append("0123456789", 1, 3); + t.append((CharSequence) "0123456789", 1, 3); assertEquals("123456789012", t.toString()); - t.append(CharBuffer.wrap("0123456789".toCharArray()), 3, 5); + t.append((CharSequence) CharBuffer.wrap("0123456789".toCharArray()), 3, 5); assertEquals("12345678901234", t.toString()); - t.append(t); + t.append((CharSequence) t); assertEquals("1234567890123412345678901234", t.toString()); - t.append(new StringBuilder("0123456789"), 5, 7); + t.append((CharSequence) new StringBuilder("0123456789"), 5, 7); assertEquals("123456789012341234567890123456", t.toString()); - t.append(new StringBuffer(t)); + t.append((CharSequence) new StringBuffer(t)); assertEquals("123456789012341234567890123456123456789012341234567890123456", t.toString()); // very wierd, to test if a subSlice is wrapped correct :) - t.setEmpty().append(CharBuffer.wrap("0123456789".toCharArray(), 3, 5) /* "34" */, 1, 2); + CharBuffer buf = CharBuffer.wrap("0123456789".toCharArray(), 3, 5); + assertEquals("34567", buf.toString()); + t.setEmpty().append((CharSequence) buf, 1, 2); assertEquals("4", t.toString()); + CharTermAttribute t2 = new CharTermAttributeImpl(); + t2.append("test"); + t.append((CharSequence) t2); + assertEquals("4test", t.toString()); + t.append((CharSequence) t2, 1, 2); + assertEquals("4teste", t.toString()); + + try { + t.append((CharSequence) t2, 1, 5); + fail("Should throw IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException iobe) { + } + + try { + t.append((CharSequence) t2, 1, 0); + fail("Should throw IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException iobe) { + } + + t.append((CharSequence) null); + assertEquals("4testenull", t.toString()); } + public void testAppendableInterfaceWithLongSequences() { + CharTermAttributeImpl t = new CharTermAttributeImpl(); + t.append((CharSequence) "01234567890123456789012345678901234567890123456789"); + t.append((CharSequence) CharBuffer.wrap("01234567890123456789012345678901234567890123456789".toCharArray()), 3, 50); + assertEquals("0123456789012345678901234567890123456789012345678934567890123456789012345678901234567890123456789", t.toString()); + t.setEmpty().append((CharSequence) new StringBuilder("01234567890123456789"), 5, 17); + assertEquals((CharSequence) "567890123456", t.toString()); + t.append(new StringBuffer(t)); + assertEquals((CharSequence) "567890123456567890123456", t.toString()); + // very wierd, to test if a subSlice is wrapped correct :) + CharBuffer buf = CharBuffer.wrap("012345678901234567890123456789".toCharArray(), 3, 15); + assertEquals("345678901234567", buf.toString()); + t.setEmpty().append(buf, 1, 14); + assertEquals("4567890123456", t.toString()); + } + + public void testNonCharSequenceAppend() { + CharTermAttributeImpl t = new CharTermAttributeImpl(); + t.append("0123456789"); + t.append("0123456789"); + assertEquals("01234567890123456789", t.toString()); + t.append(new StringBuilder("0123456789")); + assertEquals("012345678901234567890123456789", t.toString()); + CharTermAttribute t2 = new CharTermAttributeImpl(); + t2.append("test"); + t.append(t2); + assertEquals("012345678901234567890123456789test", t.toString()); + t.append((String) null); + t.append((StringBuilder) null); + t.append((CharTermAttribute) null); + assertEquals("012345678901234567890123456789testnullnullnull", t.toString()); + } + + public void testExceptions() { + CharTermAttributeImpl t = new CharTermAttributeImpl(); + t.append("test"); + assertEquals("test", t.toString()); + + try { + t.charAt(-1); + fail("Should throw IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException iobe) { + } + + try { + t.charAt(4); + fail("Should throw IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException iobe) { + } + + try { + t.subSequence(0, 5); + fail("Should throw IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException iobe) { + } + + try { + t.subSequence(5, 0); + fail("Should throw IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException iobe) { + } + } + }