| /******************************************************************************* |
| * 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; |
| } |
| |
| } |