Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.eclipse.emf.common.util.SegmentSequence Maven / Gradle / Ivy
/**
* 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 ==
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 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 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 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 extends AbstractList 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 extends AbstractList 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 segmentsList()
{
return segments == EMPTY_ARRAY ? Collections.emptyList() : new UnmodifiableArrayList(segments);
}
/**
* Returns a read-only list view of the segments from the given start.
*/
public List 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 subSegmentsList(int start, int end)
{
rangeCheck(segments, start, end);
return new UnmodifiableArraySubList(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 null
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 null
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 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
{
private static final long serialVersionUID = 1L;
/**
* Creates an instance managed by the given queue.
*/
public StringArrayPool(ReferenceQueue 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 null
{
/**
* 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 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
{
private static final long serialVersionUID = 1L;
@Override
public StringAccessUnit pop(boolean isExclusive)
{
return (StringAccessUnit)super.pop(isExclusive);
}
@Override
protected AccessUnit 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
{
private static final long serialVersionUID = 1L;
@Override
public SubstringAccessUnit pop(boolean isExclusive)
{
return (SubstringAccessUnit)super.pop(isExclusive);
}
@Override
protected AccessUnit 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
{
private static final long serialVersionUID = 1L;
@Override
public SegmentsAndSegmentAccessUnit pop(boolean isExclusive)
{
return (SegmentsAndSegmentAccessUnit)super.pop(isExclusive);
}
@Override
protected AccessUnit 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
{
private static final long serialVersionUID = 1L;
@Override
public SegmentsAndSubsegmentAccessUnit pop(boolean isExclusive)
{
return (SegmentsAndSubsegmentAccessUnit)super.pop(isExclusive);
}
@Override
protected AccessUnit 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
{
private static final long serialVersionUID = 1L;
@Override
public SegmentsAndSegmentCountAccessUnit pop(boolean isExclusive)
{
return (SegmentsAndSegmentCountAccessUnit)super.pop(isExclusive);
}
@Override
protected AccessUnit 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
{
private static final long serialVersionUID = 1L;
@Override
public SegmentsAccessUnit pop(boolean isExclusive)
{
return (SegmentsAccessUnit)super.pop(isExclusive);
}
@Override
protected AccessUnit 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}(true, true, value, value.length)
.
*/
@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
{
private static final long serialVersionUID = 1L;
@Override
public SegmentsAndSegmentsAccessUnit pop(boolean isExclusive)
{
return (SegmentsAndSegmentsAccessUnit)super.pop(isExclusive);
}
@Override
protected AccessUnit 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
{
private static final long serialVersionUID = 1L;
@Override
public SubsegmentsAndSubsegmentsAccessUnit pop(boolean isExclusive)
{
return (SubsegmentsAndSubsegmentsAccessUnit)super.pop(isExclusive);
}
@Override
protected AccessUnit 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
{
private static final long serialVersionUID = 1L;
/**
* A base class for all this pool's access units.
*/
protected static class AccessUnitBase extends AccessUnit
{
protected AccessUnitBase(Pool.AccessUnit.Queue 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
{
private static final long serialVersionUID = 1L;
@Override
public StringAccessUnit pop(boolean isExclusive)
{
return (StringAccessUnit)super.pop(isExclusive);
}
@Override
protected AccessUnit 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
{
private static final long serialVersionUID = 1L;
@Override
public SegmentsAccessUnit pop(boolean isExclusive)
{
return (SegmentsAccessUnit)super.pop(isExclusive);
}
@Override
protected AccessUnit 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
{
private static final long serialVersionUID = 1L;
@Override
public SegmentsAndSegmentAccessUnit pop(boolean isExclusive)
{
return (SegmentsAndSegmentAccessUnit)super.pop(isExclusive);
}
@Override
protected AccessUnit 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
{
private static final long serialVersionUID = 1L;
@Override
public SegmentsAndSegmentsAccessUnit pop(boolean isExclusive)
{
return (SegmentsAndSegmentsAccessUnit)super.pop(isExclusive);
}
@Override
protected AccessUnit 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 cachedToStrings;
/**
* Creates an instance managed the given external queue.
* If it's null
, and internal queue will be used to managed the cached strings.
*/
public SegmentSequencePool(ReferenceQueue queue)
{
super(1031, null, queue);
cachedToStrings = externalQueue == null ? new ReferenceQueue() : 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
{
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 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 null
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 null
.
*/
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 null
.
*/
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 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(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 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(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 == null ? "" : 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);
}
}