/** * Copyright (c) 2013 Eclipse contributors and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.emf.common.util; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.AbstractList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.RandomAccess; /** * A memory efficient structure to store a sequence of delimited string segments. * A segment sequence can only be created through one the create methods. * Pooling ensures that <code>==</code> can be used instead of {@link Object#equals(Object)} to test for equality, * i.e., at most one instance can exist for any given delimiter and sequence of segments. * The {@link #hashCode() hash code} implementation ensures that the hash code of a segment sequence is identical to the hash code of it's {@link #toString string} representation. * In addition, the delimiter and the segments use string and string array pooling. * Null segments are not permitted. * @since 2.9 */ public final class SegmentSequence implements CharSequence { /** * The instance used to represent no segments. */ static final String[] EMPTY_ARRAY = new String [0]; /** * The array containing only the empty string. */ static final String[] EMPTY_STRING_ARRAY = { "" }; /** * The cached pool for the segment sequence instances. */ protected static final SegmentSequencePool POOL = new SegmentSequencePool(CommonUtil.REFERENCE_CLEARING_QUEUE); /** * The cached pool for string arrays. */ protected static final StringArrayPool STRING_ARRAY_POOL = new StringArrayPool(CommonUtil.REFERENCE_CLEARING_QUEUE); /** * The cached hash code for this instance. */ protected final int hashCode; /** * The delimiter for this instance. */ protected final String delimiter; /** * The segments for this instance. */ protected final String[] segments; /** * The cached value for {@link #toString()}. */ protected WeakReference<String> toString; /** * Creates an instance with the given segments and hash code. */ SegmentSequence(String delimiter, String[] segments, int hashCode) { this.delimiter = delimiter; this.segments = segments; this.hashCode = hashCode; } /** * Returns an instance with the given additional segments. * If this segment sequence's {@link #delimiter() delimiter} is different from that of the argument, * the segments of the argument will be split if they contain the delimiter. */ public SegmentSequence append(SegmentSequence segmentSequence) { // If the segments are empty... // if (segments == EMPTY_ARRAY) { // Ensure that the result has the same delimiter... // if (delimiter != segmentSequence.delimiter) { // Intern the instance for the delimiter and the argument's segments, split if necessary. // String[] splitSegments = split(delimiter, segmentSequence.segments, segmentSequence.segments.length); return POOL.intern(false, false, delimiter, splitSegments, splitSegments.length); } else { // We can just return the argument. // return segmentSequence; } } else { if (delimiter != segmentSequence.delimiter) { // We must compose the segments with the arguments segments. // String[] splitSegments = split(delimiter, segmentSequence.segments, segmentSequence.segments.length); return POOL.intern(hashCode, delimiter, segments, splitSegments, false); } else { return POOL.intern(hashCode, delimiter, segments, segmentSequence.segments, false); } } } /** * Returns an instance with the additional segment or segments, if the given segment contains '/'. */ public SegmentSequence append(String segment) { // If there are no segments... // if (segments == EMPTY_ARRAY) { // Create an instance for the segment, including a check with the segment contain delimiters. // return create(delimiter, segment); } // If the segment contains the delimiter... // else if (segment.indexOf(delimiter) != -1) { // Create a segment sequence for the segment and append that instead. // return append(create(delimiter, segment)); } else { // Look in the pool for an instance with this one additional segment included. // The pool will create the new instance, if necessary. // return POOL.intern(hashCode, delimiter, segments, segment); } } public SegmentSequence append(String... segments) { // If there are no segments... // if (this.segments == EMPTY_ARRAY) { // Create an instance for the segment, including a check with the segment contain delimiters. // return create(delimiter, segments); } else { String[] splitSegments = split(delimiter, segments, segments.length); return POOL.intern(hashCode, delimiter, this.segments, splitSegments, true); } } /** * Returns the number of characters in the {@link #toString() string} representation. */ public int length() { int segmentCount = segments.length; switch (segmentCount) { case 0: { // If there are no segments, it's the empty string, so return 0. // return 0; } case 1: { // If there is one segment, it's the length of that one segment. // return segments[0].length(); } default: { // If we have a cached string... // WeakReference<String> toString = this.toString; if (toString != null) { // If it's garbage collected... // String result = toString.get(); if (result == null) { // Clear it now. // toString.clear(); } else { // Use the length of the cached result. // return result.length(); } } // Compute the length of the final string. // int count = delimiter.length() * (segmentCount - 1); for (int i = 0; i < segmentCount; i++) { count += segments[i].length(); } return count; } } } /** * Returns the specified character in the {@link #toString() string} representation of the sequence. */ public char charAt(int index) { return toString().charAt(index); } /** * Returns the requested subsequence in the {@ink #toString() string} representation of the sequence. */ public CharSequence subSequence(int start, int end) { return toString().subSequence(start, end); } /** * Returns the string representation for this sequence, * i.e., the concatenation of the {@link #delimiter() delimiter}-separated {@link #segments() segments}. */ @Override public String toString() { int segmentCount = segments.length; switch (segmentCount) { case 0: { // If there are no segments, return the empty string; // return ""; } case 1: { // If there is one segment, we can just return the segment itself. // return segments[0]; } default: { // If we don't have a cached result... // WeakReference<String> toString = this.toString; if (toString != null) { String result = toString.get(); if (result == null) { toString.clear(); } else { return result; } } // Compute the length of the final string. // int count = delimiter.length() * (segmentCount - 1); for (int i = 0; i < segmentCount; i++) { count += segments[i].length(); } // Allocate a buffer to hold the result characters. // char[] buffer = new char[count]; // Copy the first segments characters to the buffer. // String segment = segments[0]; int length = segment.length(); segment.getChars(0, length, buffer, 0); // Iterate over the remaining segments... // for (int i = 1, index = length, delimiterLength = delimiter.length(); i < segmentCount; ++i) { // Copy the delimiter's characters into the buffer. // delimiter.getChars(0, delimiterLength, buffer, index); index += delimiterLength; // Copy the segment's characters into the buffer. // segment = segments[i]; length = segment.length(); segment.getChars(0, length, buffer, index); index += length; } // Intern the resulting characters. // String result = CommonUtil.intern(buffer, 0, count); // Cache the string for reuse on subsequent calls. // this.toString = POOL.newCachedToString(this, result); // Return the cached result. // return result; } } } /** * Returns the delimiter of the segment sequence. */ public String delimiter() { return delimiter; } /** * Returns the segments of this segment sequence. */ public String[] segments() { return segments.clone(); } /** * Returns the segments from the given start. */ public String[] subSegments(int start) { return subSegments(start, segments.length); } /** * Returns the segments from the given start and before the given end. */ public String[] subSegments(int start, int end) { rangeCheck(segments, start, end); int count = end - start; String[] result = new String[count]; System.arraycopy(segments, start, result, 0, count); return result; } /** * A list that's a read-only view of an array. */ static class UnmodifiableArrayList<E> extends AbstractList<E> implements RandomAccess { final protected E[] array; public UnmodifiableArrayList(E[] array) { this.array = array; } @Override public E get(int index) { return array[index]; } @Override public int size() { return array.length; } } /** * Checks that the start and end are well formed. */ static void rangeCheck(Object[] array, int start, int end) { if (start < 0) { throw new IndexOutOfBoundsException("start=" + start); } if (start > array.length) { throw new IndexOutOfBoundsException("end = " + end); } if (start > end) { throw new IndexOutOfBoundsException("start=" + start + " > " + "end="+ end); } } /** * A list that's a read-only view of part of an array. */ static class UnmodifiableArraySubList<E> extends AbstractList<E> implements RandomAccess { final int start; final int end; final protected E[] array; public UnmodifiableArraySubList(E[] array, int start, int end) { this.start = start; this.end = end; this.array = array; } @Override public E get(int index) { if (index < 0 || index > end - start) { throw new IndexOutOfBoundsException("index=" + index + ", size=" + size()); } return array[start + index]; } @Override public int size() { return end - start; } } /** * Returns a read-only list view of the segments. */ public List<String> segmentsList() { return segments == EMPTY_ARRAY ? Collections.<String>emptyList() : new UnmodifiableArrayList<String>(segments); } /** * Returns a read-only list view of the segments from the given start. */ public List<String> subSegmentsList(int start) { return subSegmentsList(start, segments.length); } /** * Returns a read-only list view of the segments from the given start and before the given end. */ public List<String> subSegmentsList(int start, int end) { rangeCheck(segments, start, end); return new UnmodifiableArraySubList<String>(segments, start, end); } /** * Returns the number of segments in the segment sequence. */ public int segmentCount() { return segments.length; } /** * Returns the segment at the given index of the segment sequence. */ public String segment(int index) { return segments[index]; } /** * Returns the last segment of the segment sequence, or <code>null</code> if there are no segments. */ public String lastSegment() { return segments == EMPTY_ARRAY ? null : segments[segments.length - 1]; } /** * Returns the first segment of the segment sequence, or <code>null</code> if there are no segments. */ public String firstSegment() { return segments == EMPTY_ARRAY ? null : segments[0]; } /** * Although there is an override of hash code, there is no override of {@link #equals(Object)} * because the static methods for creation guarantee that there is at most one instance created for each unique segment sequence. */ @Override public int hashCode() { return hashCode; } /** * Returns true if the delimiter is the same as this segment sequence's delimiter and the {@link #toString string representation} of this segment sequence is equal to the given string. */ protected boolean matches(String delimiter, String string) { // If the delimiters are different, return false. // if (!this.delimiter.equals(delimiter)) { return false; } else { // Consider the three cases: no segments, one segment, and more than one segments... // int segmentCount = segments.length; if (segmentCount == 0) { // This case can't match, except for the special case of the empty delimiter and the empty string. // return string.length() == 0 && delimiter.length() == 0; } else if (segmentCount == 1) { // The one segment must be equal. // return segments[0].equals(string); } else if (toString != null) { // If there is a cached string with a referent, it must be equal to the argument. // WeakReference<String> toString = this.toString; if (toString != null) { String stringValue = toString.get(); if (stringValue == null) { toString.clear(); } else { return stringValue.equals(string); } } } // We need to match against all the segments, so start with the first. // String segment = segments[0]; // If the argument doesn't start with the segment, return false. // if (!string.startsWith(segment)) { return false; } // Iterate over all the remaining segments... // for (int i = 1, index = segment.length(), length = string.length(), delimiterLength = delimiter.length(); i < segmentCount; ++i) { // If we've gone too far or there isn't a delimiter as expected at the current index, return false. // if (index >= length || delimiterLength != 0 && !string.startsWith(delimiter, index)) { return false; } // Skip past the delimiter. // index += delimiterLength; // If the argument doesn't start with the segment at the current index, return false. // segment = segments[i]; if (!string.startsWith(segment, index)) { return false; } // Skip past this segment. // index += segment.length(); } // The string matches, so given we don't have a cached toString, we may as well cache this one. // TODO Maybe we want to cache only interned strings... // toString = POOL.newCachedToString(this, string); // No contradiction was found, so it must match. // return true; } } /** * A specialized pool for caching string arrays. */ protected static class StringArrayPool extends Pool<String[]> { private static final long serialVersionUID = 1L; /** * Creates an instance managed by the given queue. */ public StringArrayPool(ReferenceQueue<Object> queue) { super(1031, new SegmentsAccessUnit.Queue(), queue); addEntry(1, newEntry(EMPTY_ARRAY, 1)); addEntry(31, newEntry(EMPTY_STRING_ARRAY, 31)); segmentsAccessUnits = (SegmentsAccessUnit.Queue)primaryAccessUnits; } /** * Returns the {@link Arrays#hashCode(Object[])}'s value for this object. */ @Override protected int hashCode(String[] object) { return Arrays.hashCode(object); } /** * Returns the {@link Arrays#equals(Object[], Object[])}'s value for these two objects object. */ @Override protected boolean equals(String[] object, String[] otherObject) { return Arrays.equals(object, otherObject); } /** * Checks whether the object is string array and casts it or return <code>null</code. */ @Override protected String[] asInstance(Object object) { return object instanceof String[] ? (String[])object : null; } /** * A base class for all this pool's access units. */ protected static class AccessUnitBase extends AccessUnit<String[]> { /** * An access unit for exclusive use in {@link #internString(String)}. */ protected CommonUtil.StringPool.StringAccessUnit stringAccessUnit = new CommonUtil.StringPool.StringAccessUnit(CommonUtil.STRING_POOL, null); /** * An access unit for exclusive use in {@link #internString(String, int, int, int)}. */ protected CommonUtil.StringPool.SubstringAccessUnit substringAccessUnit = new CommonUtil.StringPool.SubstringAccessUnit(null); protected AccessUnitBase(AccessUnit.Queue<String[]> queue) { super(queue); } @Override protected String[] getValue() { throw new UnsupportedOperationException(); } @Override protected void setValue(String[] value) { throw new UnsupportedOperationException(); } @Override protected boolean setArbitraryValue(Object value) { throw new UnsupportedOperationException(); } @Override protected boolean matches(String[] value) { throw new UnsupportedOperationException(); } protected String internString(String string) { stringAccessUnit.setValue(string); return CommonUtil.STRING_POOL.doIntern(false, stringAccessUnit); } protected String internString(String string, int offset, int count, int hashCode) { // If there are no characters, we can just return the empty string, which we've ensured in the constructor is actually in the pool. // if (count == 0) { return CommonUtil.StringPool.EMPTY_STRING; } else { // Retrieve an access unit for exclusive use in this call for the current thread thread. // substringAccessUnit.setValue(string, offset, count, hashCode); return CommonUtil.STRING_POOL.doIntern(false, substringAccessUnit); } } } /** * Access units for single segment access. */ protected final StringAccessUnit.Queue stringAccessUnits = new StringAccessUnit.Queue(); /** * An access unit for single segment access. * */ protected static class StringAccessUnit extends AccessUnitBase { protected static class Queue extends AccessUnit.Queue<String[]> { private static final long serialVersionUID = 1L; @Override public StringAccessUnit pop(boolean isExclusive) { return (StringAccessUnit)super.pop(isExclusive); } @Override protected AccessUnit<String[]> newAccessUnit() { return new StringAccessUnit(this); } } /** * The segment value being accessed. */ protected String value; /** * Whether the value needs to be interned. */ protected boolean needsInterning; /** * Creates an instance managed by the given queue. */ protected StringAccessUnit(Queue queue) { super(queue); } /** * Caches the parameters and computes the hash code. */ protected void setValue(String value, boolean needsInterning) { this.value = value; this.needsInterning = needsInterning; this.hashCode = 31 + value.hashCode(); } @Override protected boolean matches(String[] value) { return value.length == 1 && this.value.equals(value[0]); } @Override public String[] getInternalizedValue() { return new String [] { needsInterning ? internString(value) : value }; } @Override public void reset(boolean isExclusive) { value = null; super.reset(isExclusive); } } /** * Access units for accessing a single segment that's a substring of a larger string. */ protected final SubstringAccessUnit.Queue substringAccessUnits = new SubstringAccessUnit.Queue(); /** * An Access units for accessing a single segment that's a substring of a larger string. */ protected static class SubstringAccessUnit extends AccessUnitBase { protected static class Queue extends AccessUnit.Queue<String[]> { private static final long serialVersionUID = 1L; @Override public SubstringAccessUnit pop(boolean isExclusive) { return (SubstringAccessUnit)super.pop(isExclusive); } @Override protected AccessUnit<String[]> newAccessUnit() { return new SubstringAccessUnit(this); } } /** * The value being accessed. */ protected String value; /** * The offset within the value. */ protected int offset; /** * The number of characters from the offset. */ protected int count; /** * Creates an instance managed by the given queue. */ protected SubstringAccessUnit(Queue queue) { super(queue); } /** * Caches the parameters. */ protected void setValue(String value, int offset, int count, int hashCode) { this.value = value; this.offset = offset; this.count = count; this.hashCode = 31 + hashCode; } @Override protected boolean matches(String[] value) { if (value.length != 1) { return false; } else { String segment = value[0]; return segment.length() == count && this.value.regionMatches(offset, segment, 0, count); } } @Override public String[] getInternalizedValue() { return new String [] { internString(value, offset, count, hashCode - 31) }; } @Override public void reset(boolean isExclusive) { value = null; super.reset(isExclusive); } } /** * Access units for accessing segments and one additional segment. */ protected final SegmentsAndSegmentAccessUnit.Queue segmentsAndSegmentAccessUnits = new SegmentsAndSegmentAccessUnit.Queue(); /** * An access units for accessing segments and one additional segment. */ protected static class SegmentsAndSegmentAccessUnit extends AccessUnitBase { protected static class Queue extends AccessUnit.Queue<String[]> { private static final long serialVersionUID = 1L; @Override public SegmentsAndSegmentAccessUnit pop(boolean isExclusive) { return (SegmentsAndSegmentAccessUnit)super.pop(isExclusive); } @Override protected AccessUnit<String[]> newAccessUnit() { return new SegmentsAndSegmentAccessUnit(this); } } /** * The segments being accessed. */ protected String[] segments; /** * The number of those segments to include. */ protected int segmentCount; /** * The one additional segment. */ protected String segment; /** * Whether that additional segment needs interning. */ protected boolean needsInterning; /** * Creates an instance managed by the given queue. */ protected SegmentsAndSegmentAccessUnit(Queue queue) { super(queue); } /** * Caches the parameters and computes the hash code. */ protected void setValue(String[] segments, int segmentCount, String segment, boolean needsInterning) { assert segments.length > 0 && segmentCount > 0; this.segments = segments; this.segmentCount = segmentCount; this.segment = segment; this.needsInterning = needsInterning; int hashCode = 1; for (int i = 0; i < segmentCount; ++i) { String value = segments[i]; hashCode = 31 * hashCode + value.hashCode(); } this.hashCode = 31 * hashCode + segment.hashCode(); } @Override protected boolean matches(String[] value) { int length = segmentCount; if (length + 1 != value.length) { return false; } else { for (int i = 0; i < length; ++i) { if (value[i] != segments[i]) { return false; } } return needsInterning ? segment.equals(value[length]) : segment == value[length]; } } @Override public String[] getInternalizedValue() { int length = segmentCount; String[] newSegments = new String [length + 1]; System.arraycopy(segments, 0, newSegments, 0, length); newSegments[length] = needsInterning ? internString(segment) : segment; return newSegments; } @Override public void reset(boolean isExclusive) { segments = null; segment = null; super.reset(isExclusive); } } /** * Access units for accessing subsegments and a substring of one additional segment. */ protected final SegmentsAndSubsegmentAccessUnit.Queue segmentsAndSubsegmentAccessUnits = new SegmentsAndSubsegmentAccessUnit.Queue(); /** * An access unit for accessing segments and a substring of one additional segment. */ protected static class SegmentsAndSubsegmentAccessUnit extends AccessUnitBase { protected static class Queue extends AccessUnit.Queue<String[]> { private static final long serialVersionUID = 1L; @Override public SegmentsAndSubsegmentAccessUnit pop(boolean isExclusive) { return (SegmentsAndSubsegmentAccessUnit)super.pop(isExclusive); } @Override protected AccessUnit<String[]> newAccessUnit() { return new SegmentsAndSubsegmentAccessUnit(this); } } /** * The segments being accessed. */ protected String[] segments; /** * The number of segments to consider. */ protected int segmentCount; /** * The one additional segment. */ protected String segment; /** * The offset within the segment. */ protected int offset; /** * The number of characters from the offset. */ protected int count; /** * Hash code of character range of the additional segment. */ protected int segmentHashCode; /** * Creates an instance managed by the given queue. */ protected SegmentsAndSubsegmentAccessUnit(Queue queue) { super(queue); } /** * Caches the parameters and computes the hash code. */ protected void setValue(int hashCode, String[] segments, int segmentCount, String segment, int offset, int count, int segmentHashCode) { assert segments.length > 0; this.segments = segments; this.segmentCount = segmentCount; this.segment = segment; this.offset = offset; this.count = count; this.segmentHashCode = segmentHashCode; this.hashCode = 31 * hashCode + segmentHashCode; } /** * Caches the parameters and computes the hash code. */ protected void setValue(String[] segments, int segmentCount, String segment, int offset, int count, int segmentHashCode) { assert segments.length > 0; this.segments = segments; this.segmentCount = segmentCount; this.segment = segment; this.offset = offset; this.count = count; this.segmentHashCode = segmentHashCode; int hashCode = 1; for (int i = 0; i < segmentCount; ++i) { String value = segments[i]; hashCode = 31 * hashCode + value.hashCode(); } this.hashCode = 31 * hashCode + segmentHashCode; } @Override protected boolean matches(String[] value) { int length = segmentCount; if (length + 1 != value.length) { return false; } else { for (int i = 0; i < length; ++i) { if (value[i] != segments[i]) { return false; } } String lastValue = value[length]; return lastValue.length() == count && segment.regionMatches(offset, lastValue, 0, count); } } @Override public String[] getInternalizedValue() { int length = segmentCount; String[] newSegments = new String [length + 1]; System.arraycopy(segments, 0, newSegments, 0, length); newSegments[length] = internString(segment, offset, count, segmentHashCode); return newSegments; } @Override public void reset(boolean isExclusive) { segments = null; segment = null; super.reset(isExclusive); } } /** * Access units for accessing a range of segments. */ protected final SegmentsAndSegmentCountAccessUnit.Queue segmentsAndSegmentCountAccessUnits = new SegmentsAndSegmentCountAccessUnit.Queue(); /** * An Access units for accessing a range of segments. */ protected static class SegmentsAndSegmentCountAccessUnit extends AccessUnitBase { protected static class Queue extends AccessUnit.Queue<String[]> { private static final long serialVersionUID = 1L; @Override public SegmentsAndSegmentCountAccessUnit pop(boolean isExclusive) { return (SegmentsAndSegmentCountAccessUnit)super.pop(isExclusive); } @Override protected AccessUnit<String[]> newAccessUnit() { return new SegmentsAndSegmentCountAccessUnit(this); } } /** * The segments being accessed. */ protected String[] segments; /** * The offset within the segments. */ protected int offset; /** * The number of segments from the offset. */ protected int segmentCount; protected SegmentsAndSegmentCountAccessUnit(Queue queue) { super(queue); } /** * Caches the parameters and computes the hash code. */ protected void setValue(String[] segments, int offset, int segmentCount) { this.segments = segments; this.offset = offset; this.segmentCount = segmentCount; int hashCode = 1; for (int i = 0; i < segmentCount; ++i) { hashCode = 31 * hashCode + segments[offset + i].hashCode(); } this.hashCode = hashCode; } /** * Caches the parameters and computes. */ protected void setValue(String[] segments, int offset, int segmentCount, int hashCode) { this.segments = segments; this.offset = offset; this.segmentCount = segmentCount; this.hashCode = hashCode; } @Override protected boolean matches(String[] value) { int length = value.length; if (length != segmentCount) { return false; } else { for (int i = 0, offset = this.offset; i < length; ++i) { if (value[i] != segments[offset + i]) { return false; } } return true; } } @Override public String[] getInternalizedValue() { String[] newSegments = new String [segmentCount]; System.arraycopy(segments, offset, newSegments, 0, segmentCount); return newSegments; } @Override public void reset(boolean isExclusive) { segments = null; super.reset(isExclusive); } } /** * Access units for accessing segments that may need copying or interning. */ protected final SegmentsAccessUnit.Queue segmentsAccessUnits; /** * An Access unit for accessing segments that may need copying or interning. */ protected static final class SegmentsAccessUnit extends AccessUnitBase { protected static class Queue extends AccessUnit.Queue<String[]> { private static final long serialVersionUID = 1L; @Override public SegmentsAccessUnit pop(boolean isExclusive) { return (SegmentsAccessUnit)super.pop(isExclusive); } @Override protected AccessUnit<String[]> newAccessUnit() { return new SegmentsAccessUnit(this); } } /** * Whether the segments need copying when interned. */ protected boolean needsCopying; /** * Whether the strings with in the segments needto be interned. */ protected boolean needsToIntern; /** * The segments being access. */ protected String[] segments; /** * The number of segments to consider. */ protected int count; /** * Creates an instance managed by the given queue. * @param queue */ protected SegmentsAccessUnit(Queue queue) { super(queue); } /** * Delegates to {@link #setValue(boolean, boolean, String[], int) setValue}<code>(true, true, value, value.length)</code>. */ @Override protected void setValue(String[] value) { setValue(true, true, value, value.length); } /** * Checks of the value is a string array and delegates to {@link #setValue(String[])} if that's the case.. */ @Override protected boolean setArbitraryValue(Object value) { if (!(value instanceof String[])) { return false; } else { setValue((String[])value); return true; } } /** * Caches the parameters and computes the hash code. */ protected void setValue(boolean needsCopying, boolean needsToIntern, String[] segments, int count) { this.needsCopying = needsCopying; this.needsToIntern = needsToIntern; this.segments = segments; this.count = count; int hashCode = 1; for (int i = 0; i < count; ++i) { String value = segments[i]; hashCode = 31 * hashCode + value.hashCode(); } this.hashCode = hashCode; } @Override protected boolean matches(String[] value) { int length = value.length; if (length != count) { return false; } else { if (needsToIntern) { for (int i = 0; i < length; ++i) { if (!value[i].equals(segments[i])) { return false; } } } else { for (int i = 0; i < length; ++i) { if (value[i] != segments[i]) { return false; } } } return true; } } @Override public String[] getInternalizedValue() { int length = count; String[] newSegments = segments; if (needsCopying) { if (length == 0) { // We always should end up with this special constant for the case of no segments. // newSegments = EMPTY_ARRAY; } else { // Allocate a new array of the right size. // newSegments = new String [length]; if (needsToIntern) { // Intern each segment... // for (int i = 0; i < length; ++i) { newSegments[i] = internString(segments[i]); } } else { // Copy over the already interned segments. // System.arraycopy(segments, 0, newSegments, 0, length); } } } else if (needsToIntern) { // Intern each segment in the reusable array. // for (int i = 0; i < length; ++i) { segments[i] = internString(segments[i]); } } return newSegments; } @Override public void reset(boolean isExclusive) { segments = null; super.reset(isExclusive); } } /** * Access units for accessing the composition of two sequences of segments. */ protected final SegmentsAndSegmentsAccessUnit.Queue segmentsAndSegmentsAccessUnits = new SegmentsAndSegmentsAccessUnit.Queue(); /** * An access units for accessing the composition of two sequences of segments. */ protected static class SegmentsAndSegmentsAccessUnit extends AccessUnitBase { protected static class Queue extends AccessUnit.Queue<String[]> { private static final long serialVersionUID = 1L; @Override public SegmentsAndSegmentsAccessUnit pop(boolean isExclusive) { return (SegmentsAndSegmentsAccessUnit)super.pop(isExclusive); } @Override protected AccessUnit<String[]> newAccessUnit() { return new SegmentsAndSegmentsAccessUnit(this); } } /** * The first segments. */ protected String[] segments1; /** * The first segments. */ protected String[] segments2; /** * Whether the second segment's string need to be interned. */ protected boolean needsToIntern; /** * Creates an instance managed by the given queue. */ protected SegmentsAndSegmentsAccessUnit(Queue queue) { super(queue); } /** * Caches the parameters and computes the hash code. */ protected void setValue(String[] segments1, String[] segments2, boolean needsToIntern) { this.segments1 = segments1; this.segments2 = segments2; this.needsToIntern = needsToIntern; int hashCode = 1; for (String value : segments1) { hashCode = 31 * hashCode + value.hashCode(); } for (String value : segments2) { hashCode = 31 * hashCode + value.hashCode(); } this.hashCode = hashCode; } @Override protected boolean matches(String[] value) { int length1 = segments1.length; int length2 = segments2.length; if (length1 + length2 != value.length) { return false; } else { int i = 0; for ( ; i < length1; ++i) { if (segments1[i] != value[i]) { return false; } } if (needsToIntern) { for (int j = 0; j < length2; ++j, ++i) { if (!segments2[j].equals(value[i])) { return false; } } } else { for (int j = 0; j < length2; ++j, ++i) { if (segments2[j] != value[i]) { return false; } } } return true; } } @Override public String[] getInternalizedValue() { int length1 = segments1.length; int length2 = segments2.length; int length = length1 + length2; String[] newSegments = new String [length]; System.arraycopy(segments1, 0, newSegments, 0, length1); if (needsToIntern) { for (int i = 0, j = length1; i < length2; ++i, ++j) { newSegments[j] = internString(segments2[i]); } } else { System.arraycopy(segments2, 0, newSegments, length1, length2); } return newSegments; } @Override public void reset(boolean isExclusive) { segments1 = null; segments2 = null; super.reset(isExclusive); } } /** * Access units for accessing the composition of two subsequences. */ protected final SubsegmentsAndSubsegmentsAccessUnit.Queue subsegmentsAndSubsegmentsAccessUnits = new SubsegmentsAndSubsegmentsAccessUnit.Queue(); /** * An access units for accessing the composition of two subsequences. */ protected static class SubsegmentsAndSubsegmentsAccessUnit extends AccessUnitBase { protected static class Queue extends AccessUnit.Queue<String[]> { private static final long serialVersionUID = 1L; @Override public SubsegmentsAndSubsegmentsAccessUnit pop(boolean isExclusive) { return (SubsegmentsAndSubsegmentsAccessUnit)super.pop(isExclusive); } @Override protected AccessUnit<String[]> newAccessUnit() { return new SubsegmentsAndSubsegmentsAccessUnit(this); } } /** * The first segments. */ protected String[] segments1; /** * The offset within the first segments. */ protected int offset1; /** * The number of segments from the offset of the first segments. */ protected int count1; /** * The second segments. */ protected String[] segments2; /** * The offset within the second segments. */ protected int offset2; /** * The number of segments from the offset of the second segments. */ protected int count2; /** * Creates an instance managed by the given queue. * @param queue */ protected SubsegmentsAndSubsegmentsAccessUnit(Queue queue) { super(queue); } /** * Caches the parameters and computes the hash code. */ protected void setValue(String[] segments1, int offset1, int count1, String[] segments2, int offset2, int count2) { this.segments1 = segments1; this.offset1 = offset1; this.count1 = count1; this.segments2 = segments2; this.offset2 = offset2; this.count2 = count2; int hashCode = 1; for (int i = 0; i < count1; ++i) { hashCode = 31 * hashCode + segments1[offset1 + i].hashCode(); } for (int i = 0; i < count2; ++i) { hashCode = 31 * hashCode + segments2[offset2 + i].hashCode(); } this.hashCode = hashCode; } @Override protected boolean matches(String[] value) { int length1 = count1; int length2 = count2; if (length1 + length2 != value.length) { return false; } else { int i = 0; for (int offset1 = this.offset1; i < length1; ++i) { if (segments1[offset1 + i] != value[i]) { return false; } } for (int j = 0, offset2 = this.offset2; j < length2; ++j, ++i) { if (segments2[offset2 + j] != value[i]) { return false; } } return true; } } @Override public String[] getInternalizedValue() { int length1 = count1; int length2 = count2; int length = length1 + length2; String[] newSegments = new String [length]; System.arraycopy(segments1, offset1, newSegments, 0, length1); System.arraycopy(segments2, offset2, newSegments, length1, length2); return newSegments; } @Override public void reset(boolean isExclusive) { segments1 = null; segments2 = null; super.reset(isExclusive); } } /** * Intern a single segment. */ protected String[] intern(String segment, boolean needsInterning) { StringAccessUnit accessUnit = stringAccessUnits.pop(false); accessUnit.setValue(segment, needsInterning); return doIntern(false, accessUnit); } /** * Intern a single segment that's a substring of a larger string. */ protected String[] intern(String segment, int offset, int count, int hashCode) { SubstringAccessUnit accessUnit = substringAccessUnits.pop(false); accessUnit.setValue(segment, offset, count, hashCode); return doIntern(false, accessUnit); } /** * Intern segments and one additional segment. */ protected String[] intern(String[] segments, int segmentCount, String segment, boolean needsInterning) { if (segmentCount == 0) { return intern(segment, needsInterning); } else { SegmentsAndSegmentAccessUnit accessUnit = segmentsAndSegmentAccessUnits.pop(false); accessUnit.setValue(segments, segmentCount, segment, needsInterning); return doIntern(false, accessUnit); } } /** * Intern segments and a substring of one additional segment. */ protected String[] intern(int hashCode, String[] segments, int segmentCount, String segment, int offset, int count, int segmentHashCode) { if (segments.length == 0) { return intern(segment, offset, count, segmentHashCode); } else { SegmentsAndSubsegmentAccessUnit accessUnit = segmentsAndSubsegmentAccessUnits.pop(false); accessUnit.setValue(hashCode, segments, segmentCount, segment, offset, count, segmentHashCode); return doIntern(false, accessUnit); } } /** * Intern subsegments and a substring of one additional segment. */ protected String[] intern(String[] segments, int segmentCount, String segment, int offset, int count, int segmentHashCode) { if (segments.length == 0) { return intern(segment, offset, count, segmentHashCode); } else { SegmentsAndSubsegmentAccessUnit accessUnit = segmentsAndSubsegmentAccessUnits.pop(false); accessUnit.setValue(segments, segmentCount, segment, offset, count, segmentHashCode); return doIntern(false, accessUnit); } } /** * Intern a range of segments. */ protected String[] intern(String[] segments, int offset, int segmentCount) { SegmentsAndSegmentCountAccessUnit accessUnit = segmentsAndSegmentCountAccessUnits.pop(false); accessUnit.setValue(segments, offset, segmentCount); return doIntern(false, accessUnit); } /** * Intern a range of segments. */ protected String[] intern(String[] segments, int offset, int segmentCount, int hashCode) { SegmentsAndSegmentCountAccessUnit accessUnit = segmentsAndSegmentCountAccessUnits.pop(false); accessUnit.setValue(segments, offset, segmentCount, hashCode); return doIntern(false, accessUnit); } /** * Intern segments that may need copying or interning. */ protected String[] intern(boolean needsCopying, boolean needsToIntern, String[] segments, int count) { if (segments == null) { return EMPTY_ARRAY; } else { SegmentsAccessUnit accessUnit = segmentsAccessUnits.pop(false); accessUnit.setValue(needsCopying, needsToIntern, segments, count); return doIntern(false, accessUnit); } } /** * Intern the composition of two sequences of segments. */ protected String[] intern(String[] segments1, String[] segments2, boolean needsToIntern) { SegmentsAndSegmentsAccessUnit accessUnit = segmentsAndSegmentsAccessUnits.pop(false); accessUnit.setValue(segments1, segments2, needsToIntern); return doIntern(false, accessUnit); } /** * Intern the composition of two subsequences. */ protected String[] intern(String[] segments1, int offset1, int count1, String[] segments2, int offset2, int count2) { SubsegmentsAndSubsegmentsAccessUnit accessUnit = subsegmentsAndSubsegmentsAccessUnits.pop(false); accessUnit.setValue(segments1, offset1, count1, segments2, offset2, count2); return doIntern(false, accessUnit); } } /** * A cached pool of weakly referenced segment sequences. * There are several ways of interning instances, all of which seek to avoid creating new objects whenever possible. * Note that we've carefully ensured that a segment sequence's hash code is the same as that of its string representation. * This is exploited to allow fast lookup of strings against their already segmented representation. */ protected static class SegmentSequencePool extends Pool<SegmentSequence> { private static final long serialVersionUID = 1L; /** * A base class for all this pool's access units. */ protected static class AccessUnitBase extends AccessUnit<SegmentSequence> { protected AccessUnitBase(Pool.AccessUnit.Queue<SegmentSequence> queue) { super(queue); } @Override protected SegmentSequence getValue() { throw new UnsupportedOperationException(); } @Override protected void setValue(SegmentSequence value) { throw new UnsupportedOperationException(); } @Override protected boolean setArbitraryValue(Object value) { throw new UnsupportedOperationException(); } /** * A helper utility for computing the hash code starting with a base hash code and taking the given segments into account. * It emulates the computation done by {@link String#hashCode()}. */ protected static int hashCode(int initialHashCode, int delimiterHashCode, int delimiterPowerOf31, String[] segments, int length) { // The hash code for no segments is just the initial hash code. // if (length == 0) { return initialHashCode; } else { // Factor in the first segment's hash code contribution. // String segment = segments[0]; int hashCode = (initialHashCode == 0 ? 0 : initialHashCode * CommonUtil.powerOf31(segment.length())) + segment.hashCode(); // Iterate over the remaining segments. // for (int i = 1; i < length; ++i) { // Factor in the delimiter's hash code contribution. // hashCode = (hashCode * delimiterPowerOf31) + delimiterHashCode; // Factor in the segment's hash code contribution. // segment = segments[i]; hashCode = (hashCode * CommonUtil.powerOf31(segment.length())) + segment.hashCode(); } return hashCode; } } } /** * Access units for basic string access. */ protected final StringAccessUnit.Queue stringAccessUnits = new StringAccessUnit.Queue(); /** * An access unit for basic string access. */ protected static class StringAccessUnit extends AccessUnitBase { protected static class Queue extends AccessUnit.Queue<SegmentSequence> { private static final long serialVersionUID = 1L; @Override public StringAccessUnit pop(boolean isExclusive) { return (StringAccessUnit)super.pop(isExclusive); } @Override protected AccessUnit<SegmentSequence> newAccessUnit() { return new StringAccessUnit(this); } } /** * A buffer for character level processing; it grows as needed. */ protected char[] buffer = new char [100]; /** * A buffer to hold the delimiter's characters; it grows as needed. */ protected char[] delimiterBuffer = new char [2]; /** * A buffer for building up string segments; it grows as needed. */ protected String[] segmentBuffer = new String [100]; /** * The delimiter of the value being accessed. */ protected String delimiter; /** * The value being access. */ protected String string; /** * An access unit for exclusive use in {@link #internString(char[], int, int, int)}. */ protected CommonUtil.StringPool.CharactersAccessUnit charactersAccessUnit = new CommonUtil.StringPool.CharactersAccessUnit(null); /** * Create an instance managed by the given queue. * @param queue */ protected StringAccessUnit(Queue queue) { super(queue); } protected String internString(char[] characters, int offset, int count, int hashCode) { charactersAccessUnit.setValue(characters, offset, count, hashCode); return CommonUtil.STRING_POOL.doIntern(false, charactersAccessUnit); } /** * Records the parameters and computes the hash code. */ protected void setValue(String delimiter, String value) { this.delimiter = delimiter; this.string = value; this.hashCode = value.hashCode(); } @Override public SegmentSequence getInternalizedValue() { // We need to split the string // int delimiterLength = delimiter.length(); if (delimiterLength == 0) { if (string.length() == 0) { return new SegmentSequence(delimiter, EMPTY_ARRAY, hashCode); } else { // Create an array with the one interned segment, create and create segment sequence from it. // return new SegmentSequence(delimiter, STRING_ARRAY_POOL.intern(string, true), hashCode); } } else { // Prepare the buffer to hold enough characters. // int length = string.length(); if (buffer.length < length) { buffer = new char [length]; } // Prepare the buffer for the delimiter characters. // if (delimiterBuffer.length < delimiterLength) { delimiterBuffer = new char[delimiterLength]; } // Fill the buffers with the characters; this avoids so many calls to charAt! // string.getChars(0, length, buffer, 0); delimiter.getChars(0, delimiterLength, delimiterBuffer, 0); // Keep track of how many segments we've slit out. // int segmentCount = 0; // Compute the hash code of each segment while we're already looking at each character anyway. // int segmentHashCode = 0; // Remember the starting index of the current segment. // int start = 0; // Prepare to match the first delimiter character. // char delimiterChar = delimiterBuffer[0]; // Iterate over all the characters in the buffer. // for (int i = start, delimiterIndex = 0; i < length; ++i) { // If match we match the current delimiter character... // char c = buffer[i]; if (c == delimiterChar) { // If we've matched the whole delimiter... // if (++delimiterIndex == delimiterLength) { // Ensure that our segment buffer is big enough. // ensureSegmentCapacity(segmentCount); // Put the interned segment into the segment buffer. // Note that we do this without needing to create a string. // segmentBuffer[segmentCount++] = internString(buffer, start, i - start, segmentHashCode); // Mark the start of the next segment. // start = i + 1; // Reset the hash code for the segment. // segmentHashCode = 0; // Reset the delimiter index and delimiter character. // delimiterIndex = 0; delimiterChar = delimiterBuffer[0]; // Continue at the start of the loop. // continue; } // Prepare to match the next delimiter character. // delimiterChar = delimiterBuffer[delimiterIndex]; } // Compose the segment's hash code. // segmentHashCode = 31 * segmentHashCode + c; } // Add the interned final segment to the segment buffer as well. // ensureSegmentCapacity(segmentCount); segmentBuffer[segmentCount++] = internString(buffer, start, length - start, segmentHashCode); // Allocate the new segments, fill them, and create an instance from that. // Note that there will always be at least one segment to store the value. // SegmentSequence segmentSequence = new SegmentSequence(delimiter, STRING_ARRAY_POOL.intern(segmentBuffer, 0, segmentCount), hashCode); if (segmentCount > 1) { // Cache the toString if it would need to be computed later. // TODO we may want to intern it. // segmentSequence.toString = POOL.newCachedToString(segmentSequence, string); } return segmentSequence; } } @Override protected boolean matches(SegmentSequence value) { // If the segment sequence of the entry matches the argument, return it. // return value.matches(delimiter, string); } /** * Ensures that the segment buffer is give enough to hold the given number of segments. */ protected void ensureSegmentCapacity(int segmentCount) { if (segmentCount >= segmentBuffer.length) { // Create a new one copying over the existing contents. // String[] oldSegments = segmentBuffer; segmentBuffer = new String [2 * segmentBuffer.length]; System.arraycopy(oldSegments, 0, segmentBuffer, 0, segmentCount); } } @Override public void reset(boolean isExclusive) { delimiter = null; string = null; super.reset(isExclusive); } } /** * Access units for accessing a delimiter and segments. */ protected final SegmentsAccessUnit.Queue segmentsAccessUnits = new SegmentsAccessUnit.Queue(); /** * An access unit for accessing a delimiter and segments. */ protected static class SegmentsAccessUnit extends AccessUnitBase { protected static class Queue extends AccessUnit.Queue<SegmentSequence> { private static final long serialVersionUID = 1L; @Override public SegmentsAccessUnit pop(boolean isExclusive) { return (SegmentsAccessUnit)super.pop(isExclusive); } @Override protected AccessUnit<SegmentSequence> newAccessUnit() { return new SegmentsAccessUnit(this); } } /** * Whether the segments need copying when interned. */ protected boolean needsCopying; /** * Whether the string sin the segments need interning. */ protected boolean needsToIntern; /** * The delimiter being accessed. */ protected String delimiter; /** * The segments being accessed. */ protected String[] segments; /** * The number of segments to consider. */ protected int count; protected SegmentsAccessUnit(Queue queue) { super(queue); } /** * Caches the arguments the computes the hash code. */ protected void setValue(boolean needsCopying, boolean needsToIntern, String delimiter, String[] segments, int count) { this.needsCopying = needsCopying; this.needsToIntern = needsToIntern; this.delimiter = delimiter; this.segments = segments; this.count = count; hashCode = hashCode(0, delimiter.hashCode(), CommonUtil.powerOf31(delimiter.length()), segments, count); } @Override public SegmentSequence getInternalizedValue() { return new SegmentSequence(delimiter, STRING_ARRAY_POOL.intern(needsCopying, needsToIntern, segments, count), hashCode); } @Override protected boolean matches(SegmentSequence value) { // If the delimiter and segments are the same, return this instance. // return delimiter.equals(value.delimiter) && equals(segments, count, value.segments); } /** * Returns true if the arrays are of the same length and have equal strings. */ protected static boolean equals(String[] segments1, int length, String[] segments2) { if (segments2.length != length) { return false; } for (int i = 0; i < length; i++) { if (!segments1[i].equals(segments2[i])) { return false; } } return true; } @Override public void reset(boolean isExclusive) { delimiter = null; segments = null; super.reset(isExclusive); } } /** * Access units for accessing segments and one additional segment. */ protected final SegmentsAndSegmentAccessUnit.Queue segmentsAndSegmentAccessUnits = new SegmentsAndSegmentAccessUnit.Queue(); /** * An access unit for accessing segments and one additional segment. */ protected static class SegmentsAndSegmentAccessUnit extends AccessUnitBase { protected static class Queue extends AccessUnit.Queue<SegmentSequence> { private static final long serialVersionUID = 1L; @Override public SegmentsAndSegmentAccessUnit pop(boolean isExclusive) { return (SegmentsAndSegmentAccessUnit)super.pop(isExclusive); } @Override protected AccessUnit<SegmentSequence> newAccessUnit() { return new SegmentsAndSegmentAccessUnit(this); } } /** * The delimiters being accessed. */ protected String delimiter; /** * The segments being accessed. */ protected String[] segments; /** * The one additional segment being accessed */ protected String segment; /** * Creates an instance managed by the given queue. * @param queue */ protected SegmentsAndSegmentAccessUnit(Queue queue) { super(queue); } /** * Caches the parameters and computes the hash code. */ protected void setValue(int hashCode, String delimiter, String[] segments, String segment) { this.delimiter = delimiter; this.segments = segments; this.segment = segment; // Factor in the additional delimiter and segment hash code contributions. // hashCode = hashCode * CommonUtil.powerOf31(delimiter.length()) + delimiter.hashCode(); hashCode = hashCode * CommonUtil.powerOf31(segment.length()) + segment.hashCode(); this.hashCode = hashCode; } @Override public SegmentSequence getInternalizedValue() { return new SegmentSequence(delimiter, STRING_ARRAY_POOL.intern(segments, segments.length, segment, true), hashCode); } @Override protected boolean matches(SegmentSequence value) { // If the delimiters match... // if (delimiter.equals(delimiter)) { // If the segment count matches... // String[] entrySegments = value.segments; int length = segments.length; if (length + 1 == entrySegments.length) { // If the segments are not identical strings---remember, they're all interned, so we can use == to test them---then return false. // int i = 0; for (; i < length; ++i) { if (entrySegments[i] != segments[i]) { return false; } } // If the last segment of the entry matches the additional segment, we've found our result, so return true. // if (segment.equals(entrySegments[i])) { return true; } } } return false; } @Override public void reset(boolean isExclusive) { delimiter = null; segments = null; segment = null; super.reset(isExclusive); } } /** * Access units for accessing the composition of two sequences of segments. */ protected final SegmentsAndSegmentsAccessUnit.Queue segmentsAndSegmentsAccessUnits = new SegmentsAndSegmentsAccessUnit.Queue(); /** * An access units for accessing the composition of two sets of segments. */ protected static class SegmentsAndSegmentsAccessUnit extends AccessUnitBase { protected static class Queue extends AccessUnit.Queue<SegmentSequence> { private static final long serialVersionUID = 1L; @Override public SegmentsAndSegmentsAccessUnit pop(boolean isExclusive) { return (SegmentsAndSegmentsAccessUnit)super.pop(isExclusive); } @Override protected AccessUnit<SegmentSequence> newAccessUnit() { return new SegmentsAndSegmentsAccessUnit(this); } } /** * The delimiter being accessed. */ protected String delimiter; /** * The first segments being accessed. */ protected String[] segments1; /** * The second segments being accessed. */ protected String[] segments2; /** * Whether the strings in the second segments need interning. */ protected boolean needsToIntern; protected SegmentsAndSegmentsAccessUnit(Queue queue) { super(queue); } /** * Caches the parameters and computes the hash code. */ protected void setValue(int hashCode, String delimiter, String[] segments1, String[] segments2, boolean needsToIntern) { this.delimiter = delimiter; this.segments1 = segments1; this.segments2 = segments2; this.needsToIntern = needsToIntern; if (segments2.length > 0) { // Factor in the additional hash code contributions of one more separator and the additional segments. // int delimiterHashCode = delimiter.hashCode(); int delimiterPowerOf31 = CommonUtil.powerOf31(delimiter.length()); hashCode = (hashCode * delimiterPowerOf31) + delimiterHashCode; hashCode = hashCode(hashCode, delimiterHashCode, delimiterPowerOf31, segments2, segments2.length); } this.hashCode = hashCode; } @Override public SegmentSequence getInternalizedValue() { return new SegmentSequence(delimiter, STRING_ARRAY_POOL.intern(segments1, segments2, needsToIntern), hashCode); } @Override protected boolean matches(SegmentSequence value) { // If the delimiters match... // if (delimiter.equals(value.delimiter)) { int length1 = segments1.length; int length2 = segments2.length; int length = length1 + length2; // If the entry segment count matches... // String[] entrySegments = value.segments; if (length == entrySegments.length) { // If the leading segments are not identical strings---remember, they're all interned, so we can use == to compare them---return false; // int i = 0; for (; i < length1; ++i) { if (entrySegments[i] != segments1[i]) { return false; } } if (needsToIntern) { for (int j = 0; j < length2; ++i, ++j) { if (!entrySegments[i].equals(segments2[j])) { return false; } } } else { // If the remaining segments are not identical strings, return false; // for (int j = 0; j < length2; ++i, ++j) { if (entrySegments[i] != segments2[j]) { return false; } } } // Otherwise it's a match, so return true. // return true; } else { return false; } } else { return false; } } @Override public void reset(boolean isExclusive) { delimiter = null; segments1 = null; segments2 = null; super.reset(isExclusive); } } /** * A reference queue for internally managing the collection of cached {@link SegmentSequence#toString}s. */ protected final ReferenceQueue<String> cachedToStrings; /** * Creates an instance managed the given external queue. * If it's <codee>null</code>, and internal queue will be used to managed the cached strings. */ public SegmentSequencePool(ReferenceQueue<Object> queue) { super(1031, null, queue); cachedToStrings = externalQueue == null ? new ReferenceQueue<String>() : null; } /** * Specialized to also clean up the {@link #cachedToStrings}. */ @Override protected void doCleanup() { super.doCleanup(); for (;;) { Reference<? extends String> cachedToString = cachedToStrings.poll(); if (cachedToString == null) { return; } else { // A cached string's knows from which SegmentSequence to remove itself. // cachedToString.clear(); } } } /** * A weak reference for {@link SegmentSequence#toString caching} a segment sequence's {@link SegmentSequence#toString() string} representation. */ protected static class CachedToString extends WeakReference<String> { protected final SegmentSequence segmentSequence; public CachedToString(SegmentSequence segmentSequence, String string, ReferenceQueue<? super String> queue) { super(string, queue); this.segmentSequence = segmentSequence; } @Override public void clear() { segmentSequence.toString = null; super.clear(); } } /** * Creates a new weak reference managed by the appropriate {@link #externalQueue external} or {@link #cachedToStrings internal} queue. * @param segmentSequence * @param string * @return */ protected WeakReference<String> newCachedToString(SegmentSequence segmentSequence, String string) { return cachedToStrings == null ? new CachedToString(segmentSequence, string, externalQueue) : new CachedToString(segmentSequence, string, cachedToStrings); } /** * Interns the string representation of a segment sequence, splitting it into appropriate segments based on the delimiter. */ protected SegmentSequence intern(String delimiter, String value) { // Null isn't stored in the pool. // if (value == null) { return null; } else { // Retrieve an access unit for exclusive use in this call for the current thread thread. // StringAccessUnit accessUnit = stringAccessUnits.pop(false); accessUnit.setValue(delimiter, value); return doIntern(false, accessUnit); } } /** * Interns the segments, taking into account the delimiter, * copying the array, when needed, * and interning the segments themselves, when needed. */ protected SegmentSequence intern(boolean needsCopying, boolean needsToIntern, String delimiter, String[] segments, int count) { // Retrieve an access unit for exclusive use in this call for the current thread thread. // SegmentsAccessUnit accessUnit = segmentsAccessUnits.pop(false); accessUnit.setValue(needsCopying, needsToIntern, delimiter, segments, count); return doIntern(false, accessUnit); } /** * Interns the segments along with the one additional segment, taking into account the delimiter and interning the additional segment. * All callers of this method will already have interned the segments. */ protected SegmentSequence intern(int hashCode, String delimiter, String[] segments, String segment) { // Retrieve an access unit for exclusive use in this call for the current thread thread. // SegmentsAndSegmentAccessUnit accessUnit = segmentsAndSegmentAccessUnits.pop(false); accessUnit.setValue(hashCode, delimiter, segments, segment); return doIntern(false, accessUnit); } /** * Interns the composed segments, taking into account the delimiter. * All callers of this method will already have interned the segments. */ protected SegmentSequence intern(int hashCode, String delimiter, String[] segments1, String[] segments2, boolean needsToIntern) { // Retrieve an access unit for exclusive use in this call for the current thread thread. // SegmentsAndSegmentsAccessUnit accessUnit = segmentsAndSegmentsAccessUnits.pop(false); accessUnit.setValue(hashCode, delimiter, segments1, segments2, needsToIntern); return doIntern(false, accessUnit); } } /** * Returns the segment sequence for the value, splitting it into segments as appropriate, or <code>null</code> if the value is null. */ public static SegmentSequence create(String delimiter, String value) { return POOL.intern(delimiter, value); } /** * Returns the empty segment sequence for the given delimiter. */ public static SegmentSequence create(String delimiter) { return POOL.intern(false, false, delimiter, EMPTY_ARRAY, 0); } /** * Returns the segment sequence for the given segments, * splitting individual segments as necessary, i.e., if they contain the delimiter character, * or omitting empty segments in the case of the empty delimiter. * @throws NullPointerException if the segments itself or any individual segment among the segments is <code>null</code>. */ public static SegmentSequence create(String delimiter, String... segments) { String[] splitSegments = split(delimiter, segments, segments.length); return POOL.intern(segments == splitSegments && segments != EMPTY_ARRAY, true, delimiter, splitSegments, splitSegments.length); } protected static SegmentSequence create(String delimiter, String[] segments, int count) { String[] splitSegments = split(delimiter, segments, count); return splitSegments == segments ? POOL.intern(true, true, delimiter, splitSegments, count) : POOL.intern(false, true, delimiter, splitSegments, splitSegments.length); } /** * Splits individual segments as necessary, i.e., if they contain the delimiter character, * or omits empty segments in the case of the empty delimiter. * If no segment contains the delimiter, or in the case of the empty delimiter, no segments are empty, the original argument is returned. * @throws NullPointerException if the segments itself or any individual segments among the segments is <code>null</code>. */ protected static String[] split(String delimiter, String[] segments, int length) { // The empty array is mapped to the special constant. // if (length == 0) { return EMPTY_ARRAY; } // The empty delimiter is not used for splitting. // else if (delimiter.length() == 0) { // Iterate over the segments and create a new list of segments only when needed for omitting empty segments. // List<String> collapsedSegments = null; for (int i = 0; i < length; ++i) { // If the segment is empty... // String segment = segments[i]; if (segment.length() == 0) { // If we haven't already created a list for the result... // if (collapsedSegments == null) { // Create one with a suitable capacity and add all the segments we've looked at so far, omitting the empty segment. // collapsedSegments = new ArrayList<String>(length - 1); for (int j = 0; j < i; ++j) { collapsedSegments.add(segments[j]); } } } else if (collapsedSegments != null) { // If we're building a new result, add this non-empty segment. // collapsedSegments.add(segment); } } // Return either original argument, or the array from the new result, or the original argument. // return collapsedSegments == null ? segments : collapsedSegments.isEmpty() ? EMPTY_ARRAY : collapsedSegments.toArray(new String [collapsedSegments.size()]); } else { // Iterate over the segments and create a new list of segments only when needed. // List<String> expandedSegments = null; for (int i = 0; i < length; ++i) { // If the segment contains the delimiter... // String segment = segments[i]; if (segment.indexOf(delimiter) != -1) { // Create a segment sequence for the segment, to reuse the efficient logic for splitting a name, and pull out the segments from that result. // String[] subsegments = create(delimiter, segment).segments; // If we haven't already created a list for the result... // if (expandedSegments == null) { // Create one with a suitable capacity and add all the segments we've looked at so far... // expandedSegments = new ArrayList<String>(length + subsegments.length); for (int j = 0; j < i; ++j) { expandedSegments.add(segments[j]); } } // Add the subsegments of this segment as well. // for (int j = 0, subsegmentsLength = subsegments.length; j < subsegmentsLength; ++j) { expandedSegments.add(subsegments[j]); } } else if (expandedSegments != null) { // If we're building a new result, add this segment. // expandedSegments.add(segment); } } // Return either original argument, or the array from the new result, or the original argument. // return expandedSegments == null ? segments : expandedSegments.toArray(new String [expandedSegments.size()]); } } /** * A builder for creating a {@link #toString() composed string} or a {@link #toSegmentSequence()}. * It may be a more efficient alternative to {@link StringBuilder} because the strings are not copied to a character-based buffer until {@link #toString()} is called. * At that time, the total number of characters needed is known, so a character array can be allocated and used direct to create a string. * Using the empty string as the delimiter is particularly useful for that purpose. */ public static final class Builder { /** * The delimiter of the builder. */ protected final String delimiter; /** * The number of strings in the builder. */ protected int size; /** * The string sin the builder. */ protected String[] strings; /** * Creates an instance with the given capacity. */ protected Builder(String delimiter, int capacity) { if (delimiter == null) { throw new NullPointerException("delimiter=null"); } this.delimiter = delimiter; strings = new String[capacity]; } /** * Appends a string to the builder and returns the builder itself. */ public Builder append(String string) { if (string == null) { string = "null"; } if (size == strings.length) { String[] newStrings = new String[2 * size]; System.arraycopy(strings, 0, newStrings, 0, size); strings = newStrings; } strings[size++] = string; return this; } /** * Appends a string representation of the character to the builder and returns the builder itself. */ public Builder append(char c) { if (c < CommonUtil.StringPool.StringsAccessUnit.CHAR_STRINGS_COUNT) { append(CommonUtil.StringPool.StringsAccessUnit.CHAR_STRINGS[c]); } else { append(String.valueOf(c)); } return this; } /** * Reverses the strings in the builder and returns the builder itself. */ public Builder reverse() { for (int i = 0, j = size - 1; i < j; ++i, --j) { String string = strings[i]; strings[i] = strings[j]; strings[j] = string; } return this; } /** * Converts the builder to a corresponding segment sequence. */ public SegmentSequence toSegmentSequence() { return create(delimiter, strings, size); } /** * Converts the builder to a string representation, i.e,. the delimiter-separated composition of the appended strings. */ @Override public String toString() { int size = this.size; if (size == 0) { return ""; } else if (size == 1) { return strings[0]; } else { String[] strings = this.strings; int delimiterLength = delimiter.length(); String firstString = strings[0]; int offset = firstString.length(); int count = offset + (size - 1) * delimiterLength; for (int i = 1; i < size; ++i) { count += strings[i].length(); } char[] result = new char[count]; firstString.getChars(0, offset, result, 0); for (int i = 1; i < size; ++i) { delimiter.getChars(0, delimiterLength, result, offset); offset += delimiterLength; String string = strings[i]; int stringLength = string.length(); string.getChars(0, stringLength, result, offset); offset += stringLength; } return new String(result); } } } /** * Creates a new builder for the given delimiter. * The delimiter may be the empty string. * @throws NullPointerException if the delimiter is null. */ public static Builder newBuilder(String delimiter) { return new Builder(delimiter, 10); } /** * Creates a new builder for the given delimiter with the given capacity of strings before the buffer needs to grow. * The delimiter may be the empty string. * @throws NullPointerException if the delimiter is null. */ public static Builder newBuilder(String delimiter, int capacity) { return new Builder(delimiter, capacity); } }