blob: e2dad2aba1f07355f54c000e02ab38b910f7b941 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2011 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
******************************************************************************/
package org.eclipse.equinox.bidi.internal;
import java.lang.ref.SoftReference;
import org.eclipse.equinox.bidi.StructuredTextTypeHandlerFactory;
/**
* Records strings which contain structured text. Several static
* methods in this class allow to record such strings in a pool, and to find if
* a given string is member of the pool.
* <p>
* Instances of this class are the records which are members of the pool.
* </p><p>
* The pool is managed as a cyclic list. When the pool is full,
* each new element overrides the oldest element in the list.
* </p><p>
* A string may be itself entirely a structured text, or it may contain
* segments each of which is a structured text of a given type. Each such
* segment is identified by its starting and ending offsets within the
* string, and by the handler which is appropriate to handle it.
*/
public class StructuredTextStringRecord {
/**
* Number of entries in the pool of recorded strings
*/
public static final int POOLSIZE = 100;
// maximum index allowed
private static final int MAXINDEX = POOLSIZE - 1;
// index of the last entered record
private static int last = -1;
// flag indicating that the pool has wrapped around
private static boolean wrapAround;
// the pool
@SuppressWarnings("unchecked")
private static SoftReference<StructuredTextStringRecord>[] recordRefs = new SoftReference[POOLSIZE];
// hash code of the recorded strings
private static int[] hashArray = new int[POOLSIZE];
// total number of segments in the record
private int totalSegmentCount;
// number of used segments in the record
private int usedSegmentCount;
// reference to the recorded string
private String string;
// reference to the handlers of the STT segments in the recorded string
private String[] handlers;
// reference to the boundaries of the STT segments in the recorded string
// (entries 0, 2, 4, ... are start offsets; entries 1, 3, 5, ... are
// ending offsets)
private short[] boundaries;
/**
* Constructor
*/
private StructuredTextStringRecord() {
// inhibit creation of new instances by customers
}
/**
* Records a string in the pool. The caller must specify the number
* of segments in the record (at least 1), and the handler, starting
* and ending offsets for the first segment.
*
* @param string the string to record.
* @param segmentCount number of segments allowed in this string.
* This number must be >= 1.
* @param handlerID identifier for the handler appropriate to handle
* the type of structured text present in the first segment.
* It may be one of the predefined identifiers in
* {@link StructuredTextTypeHandlerFactory}, or it may be an identifier
* for a type handler created by a plug-in or by the application.
* @param start offset in the string of the starting character of the first
* segment. It must be >= 0 and less than the length of the string.
* @param limit offset of the character following the first segment. It
* must be greater than the <code>start</code> argument and
* not greater than the length of the string.
*
* @return an instance of StructuredTextRecordString which represents this record.
* This instance may be used to specify additional segments with
* {@link #addSegment addSegment}.
*
* @throws IllegalArgumentException if <code>string</code> is null or
* if <code>segmentCount</code> is less than 1.
* @throws IllegalArgumentException if <code>handlerID</code> is null,
* or if <code>start</code> or <code>limit</code> have invalid
* values.
* @throws IllegalStateException if the current segment exceeds the
* number of segments specified by <code>segmentCount</code>
* in the call to {@link #addRecord addRecord} which created
* the StructuredTextStringRecord instance.
*/
public static StructuredTextStringRecord addRecord(String string, int segmentCount, String handlerID, int start, int limit) {
if (string == null)
throw new IllegalArgumentException("The string argument must not be null!"); //$NON-NLS-1$
if (segmentCount < 1)
throw new IllegalArgumentException("The segment count must be at least 1!"); //$NON-NLS-1$
synchronized (recordRefs) {
if (last < MAXINDEX)
last++;
else {
wrapAround = true;
last = 0;
}
}
StructuredTextStringRecord record = null;
if (recordRefs[last] != null)
record = recordRefs[last].get();
if (record == null) {
record = new StructuredTextStringRecord();
recordRefs[last] = new SoftReference<>(record);
}
hashArray[last] = string.hashCode();
for (int i = 0; i < record.usedSegmentCount; i++)
record.handlers[i] = null;
if (segmentCount > record.totalSegmentCount) {
record.handlers = new String[segmentCount];
record.boundaries = new short[segmentCount * 2];
record.totalSegmentCount = segmentCount;
}
record.usedSegmentCount = 0;
record.string = string;
record.addSegment(handlerID, start, limit);
return record;
}
/**
* Adds a second or further segment to a record.
*
* @param handlerID identifier for the handler appropriate to handle
* the type of structured text present in the first segment.
* It may be one of the predefined identifiers in
* {@link StructuredTextTypeHandlerFactory}, or it may be an identifier
* for a type handler created by a plug-in or by the application.
* @param start offset in the string of the starting character of the
* segment. It must be >= 0 and less than the length of the string.
* @param limit offset of the character following the segment. It must be
* greater than the <code>start</code> argument and not greater
* than the length of the string.
*
* @throws IllegalArgumentException if <code>handlerID</code> is null,
* or if <code>start</code> or <code>limit</code> have invalid
* values.
* @throws IllegalStateException if the current segment exceeds the
* number of segments specified by <code>segmentCount</code>
* in the call to {@link #addRecord addRecord} which created
* the StructuredTextStringRecord instance.
*/
public void addSegment(String handlerID, int start, int limit) {
if (handlerID == null)
throw new IllegalArgumentException("The handlerID argument must not be null!"); //$NON-NLS-1$
if (start < 0 || start >= string.length())
throw new IllegalArgumentException("The start position must be at least 0 and less than the length of the string!"); //$NON-NLS-1$
if (limit <= start || limit > string.length())
throw new IllegalArgumentException("The limit position must be greater than the start position but no greater than the length of the string!"); //$NON-NLS-1$
if (usedSegmentCount >= totalSegmentCount)
throw new IllegalStateException("All segments of the record are already used!"); //$NON-NLS-1$
handlers[usedSegmentCount] = handlerID;
boundaries[usedSegmentCount * 2] = (short) start;
boundaries[usedSegmentCount * 2 + 1] = (short) limit;
usedSegmentCount++;
}
/**
* Checks if a string is recorded and retrieves its record.
*
* @param string the string to check.
*
* @return <code>null</code> if the string is not recorded in the pool;
* otherwise, return the <code>StructuredTextStringRecord</code> instance
* which records this string.<br>
* Once a record has been found, the number of its segments can
* be retrieved using {@link #getSegmentCount getSegmentCount},
* its handler ID can
* be retrieved using {@link #getHandler getHandler},
* its starting offset can
* be retrieved using {@link #getStart getStart},
* its ending offset can
* be retrieved using {@link #getLimit getLimit},
*/
public static StructuredTextStringRecord getRecord(String string) {
if (last < 0) // no records at all
return null;
if (string == null || string.length() < 1)
return null;
StructuredTextStringRecord record;
int myLast = last;
int hash = string.hashCode();
for (int i = myLast; i >= 0; i--) {
if (hash != hashArray[i])
continue;
record = recordRefs[i].get();
if (record == null)
continue;
if (string.equals(record.string))
return record;
}
if (!wrapAround) // never recorded past myLast
return null;
for (int i = MAXINDEX; i > myLast; i--) {
if (hash != hashArray[i])
continue;
record = recordRefs[i].get();
if (record == null)
continue;
if (string.equals(record.string))
return record;
}
return null;
}
/**
* Retrieves the number of segments in a record.
*
* @return the number of segments in the current record
*/
public int getSegmentCount() {
return usedSegmentCount;
}
private void checkSegmentNumber(int segmentNumber) {
if (segmentNumber >= usedSegmentCount)
throw new IllegalArgumentException("The segment number " + segmentNumber + " is greater than the total number of segments = " + usedSegmentCount + "!"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/**
* Retrieves the handler ID of a given segment.
*
* @param segmentNumber number of the segment about which information
* is required. It must be >= 0 and less than the number of
* segments returned by {@link #getSegmentCount}.
*
* @return the handler ID of the handler appropriate to
* process the structured text in the segment
* specified by <code>segmentNumber</code>.
*
* @throws IllegalArgumentException if <code>segmentNumber</code>
* has an invalid value.
*/
public String getHandler(int segmentNumber) {
checkSegmentNumber(segmentNumber);
return handlers[segmentNumber];
}
/**
* Retrieves the starting offset of a given segment.
*
* @param segmentNumber number of the segment about which information
* is required. It must be >= 0 and less than the number of
* segments returned by {@link #getSegmentCount}.
*
* @return the starting offset within the string of the segment
* specified by <code>segmentNumber</code>.
*
* @throws IllegalArgumentException if <code>segmentNumber</code>
* has an invalid value.
*/
public int getStart(int segmentNumber) {
checkSegmentNumber(segmentNumber);
return boundaries[segmentNumber * 2];
}
/**
* Retrieves the ending offset of a given segment.
*
* @param segmentNumber number of the segment about which information
* is required. It must be >= 0 and less than the number of
* segments returned by {@link #getSegmentCount}.
*
* @return the offset of the position following the segment
* specified by <code>segmentNumber</code>.
*
* @throws IllegalArgumentException if <code>segmentNumber</code>
* has an invalid value.
*/
public int getLimit(int segmentNumber) {
checkSegmentNumber(segmentNumber);
return boundaries[segmentNumber * 2 + 1];
}
/**
* Clears the pool. All elements of the pool are erased and any associated
* memory is freed.
*/
public static synchronized void clear() {
for (int i = 0; i <= MAXINDEX; i++) {
hashArray[i] = 0;
SoftReference<StructuredTextStringRecord> softRef = recordRefs[i];
if (softRef == null)
continue;
StructuredTextStringRecord record = softRef.get();
if (record == null)
continue;
record.boundaries = null;
record.handlers = null;
record.totalSegmentCount = 0;
record.usedSegmentCount = 0;
recordRefs[i].clear();
}
last = -1;
wrapAround = false;
}
}