1 /******************************************************************************* 2 * Copyright (c) 2010, 2011 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 ******************************************************************************/ 14 package org.eclipse.equinox.bidi.internal; 15 16 import java.lang.ref.SoftReference; 17 import org.eclipse.equinox.bidi.StructuredTextTypeHandlerFactory; 18 19 /** 20 * Records strings which contain structured text. Several static 21 * methods in this class allow to record such strings in a pool, and to find if 22 * a given string is member of the pool. 23 * <p> 24 * Instances of this class are the records which are members of the pool. 25 * </p><p> 26 * The pool is managed as a cyclic list. When the pool is full, 27 * each new element overrides the oldest element in the list. 28 * </p><p> 29 * A string may be itself entirely a structured text, or it may contain 30 * segments each of which is a structured text of a given type. Each such 31 * segment is identified by its starting and ending offsets within the 32 * string, and by the handler which is appropriate to handle it. 33 */ 34 public class StructuredTextStringRecord { 35 /** 36 * Number of entries in the pool of recorded strings 37 */ 38 public static final int POOLSIZE = 100; 39 40 // maximum index allowed 41 private static final int MAXINDEX = POOLSIZE - 1; 42 43 // index of the last entered record 44 private static int last = -1; 45 46 // flag indicating that the pool has wrapped around 47 private static boolean wrapAround; 48 49 // the pool 50 @SuppressWarnings("unchecked") 51 private static SoftReference<StructuredTextStringRecord>[] recordRefs = new SoftReference[POOLSIZE]; 52 53 // hash code of the recorded strings 54 private static int[] hashArray = new int[POOLSIZE]; 55 56 // total number of segments in the record 57 private int totalSegmentCount; 58 59 // number of used segments in the record 60 private int usedSegmentCount; 61 62 // reference to the recorded string 63 private String string; 64 65 // reference to the handlers of the STT segments in the recorded string 66 private String[] handlers; 67 68 // reference to the boundaries of the STT segments in the recorded string 69 // (entries 0, 2, 4, ... are start offsets; entries 1, 3, 5, ... are 70 // ending offsets) 71 private short[] boundaries; 72 73 /** 74 * Constructor 75 */ StructuredTextStringRecord()76 private StructuredTextStringRecord() { 77 // inhibit creation of new instances by customers 78 } 79 80 /** 81 * Records a string in the pool. The caller must specify the number 82 * of segments in the record (at least 1), and the handler, starting 83 * and ending offsets for the first segment. 84 * 85 * @param string the string to record. 86 * @param segmentCount number of segments allowed in this string. 87 * This number must be >= 1. 88 * @param handlerID identifier for the handler appropriate to handle 89 * the type of structured text present in the first segment. 90 * It may be one of the predefined identifiers in 91 * {@link StructuredTextTypeHandlerFactory}, or it may be an identifier 92 * for a type handler created by a plug-in or by the application. 93 * @param start offset in the string of the starting character of the first 94 * segment. It must be >= 0 and less than the length of the string. 95 * @param limit offset of the character following the first segment. It 96 * must be greater than the <code>start</code> argument and 97 * not greater than the length of the string. 98 * 99 * @return an instance of StructuredTextRecordString which represents this record. 100 * This instance may be used to specify additional segments with 101 * {@link #addSegment addSegment}. 102 * 103 * @throws IllegalArgumentException if <code>string</code> is null or 104 * if <code>segmentCount</code> is less than 1. 105 * @throws IllegalArgumentException if <code>handlerID</code> is null, 106 * or if <code>start</code> or <code>limit</code> have invalid 107 * values. 108 * @throws IllegalStateException if the current segment exceeds the 109 * number of segments specified by <code>segmentCount</code> 110 * in the call to {@link #addRecord addRecord} which created 111 * the StructuredTextStringRecord instance. 112 */ addRecord(String string, int segmentCount, String handlerID, int start, int limit)113 public static StructuredTextStringRecord addRecord(String string, int segmentCount, String handlerID, int start, int limit) { 114 if (string == null) 115 throw new IllegalArgumentException("The string argument must not be null!"); //$NON-NLS-1$ 116 if (segmentCount < 1) 117 throw new IllegalArgumentException("The segment count must be at least 1!"); //$NON-NLS-1$ 118 synchronized (recordRefs) { 119 if (last < MAXINDEX) 120 last++; 121 else { 122 wrapAround = true; 123 last = 0; 124 } 125 } 126 StructuredTextStringRecord record = null; 127 if (recordRefs[last] != null) 128 record = recordRefs[last].get(); 129 if (record == null) { 130 record = new StructuredTextStringRecord(); 131 recordRefs[last] = new SoftReference<>(record); 132 } 133 hashArray[last] = string.hashCode(); 134 for (int i = 0; i < record.usedSegmentCount; i++) 135 record.handlers[i] = null; 136 if (segmentCount > record.totalSegmentCount) { 137 record.handlers = new String[segmentCount]; 138 record.boundaries = new short[segmentCount * 2]; 139 record.totalSegmentCount = segmentCount; 140 } 141 record.usedSegmentCount = 0; 142 record.string = string; 143 record.addSegment(handlerID, start, limit); 144 return record; 145 } 146 147 /** 148 * Adds a second or further segment to a record. 149 * 150 * @param handlerID identifier for the handler appropriate to handle 151 * the type of structured text present in the first segment. 152 * It may be one of the predefined identifiers in 153 * {@link StructuredTextTypeHandlerFactory}, or it may be an identifier 154 * for a type handler created by a plug-in or by the application. 155 * @param start offset in the string of the starting character of the 156 * segment. It must be >= 0 and less than the length of the string. 157 * @param limit offset of the character following the segment. It must be 158 * greater than the <code>start</code> argument and not greater 159 * than the length of the string. 160 * 161 * @throws IllegalArgumentException if <code>handlerID</code> is null, 162 * or if <code>start</code> or <code>limit</code> have invalid 163 * values. 164 * @throws IllegalStateException if the current segment exceeds the 165 * number of segments specified by <code>segmentCount</code> 166 * in the call to {@link #addRecord addRecord} which created 167 * the StructuredTextStringRecord instance. 168 */ addSegment(String handlerID, int start, int limit)169 public void addSegment(String handlerID, int start, int limit) { 170 if (handlerID == null) 171 throw new IllegalArgumentException("The handlerID argument must not be null!"); //$NON-NLS-1$ 172 if (start < 0 || start >= string.length()) 173 throw new IllegalArgumentException("The start position must be at least 0 and less than the length of the string!"); //$NON-NLS-1$ 174 if (limit <= start || limit > string.length()) 175 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$ 176 if (usedSegmentCount >= totalSegmentCount) 177 throw new IllegalStateException("All segments of the record are already used!"); //$NON-NLS-1$ 178 handlers[usedSegmentCount] = handlerID; 179 boundaries[usedSegmentCount * 2] = (short) start; 180 boundaries[usedSegmentCount * 2 + 1] = (short) limit; 181 usedSegmentCount++; 182 } 183 184 /** 185 * Checks if a string is recorded and retrieves its record. 186 * 187 * @param string the string to check. 188 * 189 * @return <code>null</code> if the string is not recorded in the pool; 190 * otherwise, return the <code>StructuredTextStringRecord</code> instance 191 * which records this string.<br> 192 * Once a record has been found, the number of its segments can 193 * be retrieved using {@link #getSegmentCount getSegmentCount}, 194 * its handler ID can 195 * be retrieved using {@link #getHandler getHandler}, 196 * its starting offset can 197 * be retrieved using {@link #getStart getStart}, 198 * its ending offset can 199 * be retrieved using {@link #getLimit getLimit}, 200 */ getRecord(String string)201 public static StructuredTextStringRecord getRecord(String string) { 202 if (last < 0) // no records at all 203 return null; 204 if (string == null || string.length() < 1) 205 return null; 206 StructuredTextStringRecord record; 207 int myLast = last; 208 int hash = string.hashCode(); 209 for (int i = myLast; i >= 0; i--) { 210 if (hash != hashArray[i]) 211 continue; 212 record = recordRefs[i].get(); 213 if (record == null) 214 continue; 215 if (string.equals(record.string)) 216 return record; 217 } 218 if (!wrapAround) // never recorded past myLast 219 return null; 220 for (int i = MAXINDEX; i > myLast; i--) { 221 if (hash != hashArray[i]) 222 continue; 223 record = recordRefs[i].get(); 224 if (record == null) 225 continue; 226 if (string.equals(record.string)) 227 return record; 228 } 229 return null; 230 } 231 232 /** 233 * Retrieves the number of segments in a record. 234 * 235 * @return the number of segments in the current record 236 */ getSegmentCount()237 public int getSegmentCount() { 238 return usedSegmentCount; 239 } 240 checkSegmentNumber(int segmentNumber)241 private void checkSegmentNumber(int segmentNumber) { 242 if (segmentNumber >= usedSegmentCount) 243 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$ 244 } 245 246 /** 247 * Retrieves the handler ID of a given segment. 248 * 249 * @param segmentNumber number of the segment about which information 250 * is required. It must be >= 0 and less than the number of 251 * segments returned by {@link #getSegmentCount}. 252 * 253 * @return the handler ID of the handler appropriate to 254 * process the structured text in the segment 255 * specified by <code>segmentNumber</code>. 256 * 257 * @throws IllegalArgumentException if <code>segmentNumber</code> 258 * has an invalid value. 259 */ getHandler(int segmentNumber)260 public String getHandler(int segmentNumber) { 261 checkSegmentNumber(segmentNumber); 262 return handlers[segmentNumber]; 263 } 264 265 /** 266 * Retrieves the starting offset of a given segment. 267 * 268 * @param segmentNumber number of the segment about which information 269 * is required. It must be >= 0 and less than the number of 270 * segments returned by {@link #getSegmentCount}. 271 * 272 * @return the starting offset within the string of the segment 273 * specified by <code>segmentNumber</code>. 274 * 275 * @throws IllegalArgumentException if <code>segmentNumber</code> 276 * has an invalid value. 277 */ getStart(int segmentNumber)278 public int getStart(int segmentNumber) { 279 checkSegmentNumber(segmentNumber); 280 return boundaries[segmentNumber * 2]; 281 } 282 283 /** 284 * Retrieves the ending offset of a given segment. 285 * 286 * @param segmentNumber number of the segment about which information 287 * is required. It must be >= 0 and less than the number of 288 * segments returned by {@link #getSegmentCount}. 289 * 290 * @return the offset of the position following the segment 291 * specified by <code>segmentNumber</code>. 292 * 293 * @throws IllegalArgumentException if <code>segmentNumber</code> 294 * has an invalid value. 295 */ getLimit(int segmentNumber)296 public int getLimit(int segmentNumber) { 297 checkSegmentNumber(segmentNumber); 298 return boundaries[segmentNumber * 2 + 1]; 299 } 300 301 /** 302 * Clears the pool. All elements of the pool are erased and any associated 303 * memory is freed. 304 */ clear()305 public static synchronized void clear() { 306 for (int i = 0; i <= MAXINDEX; i++) { 307 hashArray[i] = 0; 308 SoftReference<StructuredTextStringRecord> softRef = recordRefs[i]; 309 if (softRef == null) 310 continue; 311 StructuredTextStringRecord record = softRef.get(); 312 if (record == null) 313 continue; 314 record.boundaries = null; 315 record.handlers = null; 316 record.totalSegmentCount = 0; 317 record.usedSegmentCount = 0; 318 recordRefs[i].clear(); 319 } 320 last = -1; 321 wrapAround = false; 322 } 323 324 } 325