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