001: /*
002: * (C) Copyright IBM Corp. 1998-2004. All Rights Reserved.
003: *
004: * The program is provided "as is" without any warranty express or
005: * implied, including the warranty of non-infringement and the implied
006: * warranties of merchantibility and fitness for a particular purpose.
007: * IBM will not be liable for any damages suffered by you as a result
008: * of using the Program. In no event will IBM be liable for any
009: * special, indirect or consequential damages or lost profits even if
010: * IBM has been advised of the possibility of their occurrence. IBM
011: * will not be liable for any third party claims against you.
012: */
013: // Requires Java2
014: package com.ibm.richtext.textformat;
015:
016: import com.ibm.richtext.styledtext.MConstText;
017: import java.text.CharacterIterator;
018:
019: import com.ibm.richtext.textlayout.attributes.AttributeMap;
020:
021: ///*JDK12IMPORTS
022: import java.text.AttributedCharacterIterator;
023: import java.util.Map;
024: import java.util.Set;
025:
026: //JDK12IMPORTS*/
027:
028: /*JDK11IMPORTS
029: import com.ibm.richtext.textlayout.attributes.AttributedCharacterIterator;
030: import com.ibm.richtext.textlayout.attributes.AttributedCharacterIterator.Attribute;
031: import com.ibm.richtext.textlayout.attributes.Map;
032: JDK11IMPORTS*/
033:
034: /**
035: * An AttributedCharacterIterator over an MConstText.
036: */
037: public final class MTextIterator implements
038: AttributedCharacterIterator, Cloneable {
039:
040: static final String COPYRIGHT = "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
041:
042: // memory leak, since this cache is never flushed
043:
044: private static class Matcher {
045:
046: boolean matches(Map lhs, Map rhs, Object query) {
047:
048: Object lhsVal = lhs.get(query);
049: Object rhsVal = rhs.get(query);
050:
051: if (lhsVal == null) {
052: return rhsVal == null;
053: } else {
054: return lhsVal.equals(rhsVal);
055: }
056: }
057: }
058:
059: private static final Matcher ATTR_MATCHER = new Matcher();
060:
061: // Not quite optimal. Could have a matcher that would decompose
062: // a set once for repeated queries. Of course that would require
063: // allocation...
064: ///*JDK12IMPORTS
065: private static final Matcher SET_MATCHER = new Matcher() {
066:
067: boolean matches(Map lhs, Map rhs, Object query) {
068:
069: // Not using Iterator to simplify 1.1 port.
070: Object[] elements = ((Set) query).toArray();
071: for (int i = 0; i < elements.length; i++) {
072: if (!super .matches(lhs, rhs, elements[i])) {
073: return false;
074: }
075: }
076: return true;
077: }
078: };
079:
080: //JDK12IMPORTS*/
081:
082: private final class StyleCache {
083:
084: private int fRunStart = 0;
085: private int fRunLimit = -1;
086: private int fRangeStart;
087: private int fRangeLimit;
088: private AttributeMap fStyle;
089:
090: StyleCache(MConstText text, int start, int limit) {
091: fText = text;
092: fRangeStart = start;
093: fRangeLimit = limit;
094: update(start);
095: }
096:
097: private void update(int pos) {
098: if (pos < fRunStart || pos >= fRunLimit) {
099: AttributeMap style = AttributeMap.EMPTY_ATTRIBUTE_MAP;
100: if (pos < fRangeStart) {
101: fRunLimit = fRangeStart;
102: fRunStart = Integer.MIN_VALUE;
103: } else if (pos > fRangeLimit) {
104: fRunStart = fRangeLimit;
105: fRunLimit = Integer.MAX_VALUE;
106: } else {
107: fRunStart = Math.max(fRangeStart, fText
108: .characterStyleStart(pos));
109: fRunStart = Math.max(fRunStart, fText
110: .paragraphStart(pos));
111:
112: fRunLimit = Math.min(fRangeLimit, fText
113: .characterStyleLimit(pos));
114: fRunLimit = Math.min(fRunLimit, fText
115: .paragraphLimit(pos));
116: if (fRunStart < fRunLimit) {
117: style = fText.paragraphStyleAt(pos);
118: style = style.addAttributes(fText
119: .characterStyleAt(pos));
120: }
121: }
122: fStyle = fFontResolver.applyFont(style);
123: }
124: }
125:
126: int getRunStart(int pos) {
127: update(pos);
128: return fRunStart;
129: }
130:
131: int getRunLimit(int pos) {
132: update(pos);
133: return fRunLimit;
134: }
135:
136: Map getStyle(int pos) {
137: update(pos);
138: return fStyle;
139: }
140: }
141:
142: private MConstText fText;
143: private CharacterIterator fCharIter;
144: private FontResolver fFontResolver;
145:
146: private StyleCache fStyleCache;
147:
148: /**
149: * Create an MTextIterator over the range [start, limit).
150: */
151: public MTextIterator(MConstText text, FontResolver resolver,
152: int start, int limit) {
153:
154: fText = text;
155: fFontResolver = resolver;
156: fCharIter = text.createCharacterIterator(start, limit);
157:
158: fStyleCache = new StyleCache(text, start, limit);
159: }
160:
161: /**
162: * Sets the position to getBeginIndex() and returns the character at that
163: * position.
164: * @return the first character in the text, or DONE if the text is empty
165: * @see #getBeginIndex
166: */
167: public char first() {
168: return fCharIter.first();
169: }
170:
171: /**
172: * Sets the position to getEndIndex()-1 (getEndIndex() if the text is empty)
173: * and returns the character at that position.
174: * @return the last character in the text, or DONE if the text is empty
175: * @see #getEndIndex
176: */
177: public char last() {
178: return fCharIter.last();
179: }
180:
181: /**
182: * Gets the character at the current position (as returned by getIndex()).
183: * @return the character at the current position or DONE if the current
184: * position is off the end of the text.
185: * @see #getIndex
186: */
187: public char current() {
188: return fCharIter.current();
189: }
190:
191: /**
192: * Increments the iterator's index by one and returns the character
193: * at the new index. If the resulting index is greater or equal
194: * to getEndIndex(), the current index is reset to getEndIndex() and
195: * a value of DONE is returned.
196: * @return the character at the new position or DONE if the new
197: * position is off the end of the text range.
198: */
199: public char next() {
200: return fCharIter.next();
201: }
202:
203: /**
204: * Decrements the iterator's index by one and returns the character
205: * at the new index. If the current index is getBeginIndex(), the index
206: * remains at getBeginIndex() and a value of DONE is returned.
207: * @return the character at the new position or DONE if the current
208: * position is equal to getBeginIndex().
209: */
210: public char previous() {
211: return fCharIter.previous();
212: }
213:
214: /**
215: * Sets the position to the specified position in the text and returns that
216: * character.
217: * @param position the position within the text. Valid values range from
218: * getBeginIndex() to getEndIndex(). An IllegalArgumentException is thrown
219: * if an invalid value is supplied.
220: * @return the character at the specified position or DONE if the specified position is equal to getEndIndex()
221: */
222: public char setIndex(int position) {
223: return fCharIter.setIndex(position);
224: }
225:
226: /**
227: * Returns the start index of the text.
228: * @return the index at which the text begins.
229: */
230: public int getBeginIndex() {
231: return fCharIter.getBeginIndex();
232: }
233:
234: /**
235: * Returns the end index of the text. This index is the index of the first
236: * character following the end of the text.
237: * @return the index after the last character in the text
238: */
239: public int getEndIndex() {
240: return fCharIter.getEndIndex();
241: }
242:
243: /**
244: * Returns the current index.
245: * @return the current index.
246: */
247: public int getIndex() {
248: return fCharIter.getIndex();
249: }
250:
251: /**
252: * Returns the index of the first character of the run
253: * with respect to all attributes containing the current character.
254: */
255: public int getRunStart() {
256: return fStyleCache.getRunStart(fCharIter.getIndex());
257: }
258:
259: /**
260: * Returns the index of the first character of the run
261: * with respect to the given attribute containing the current character.
262: */
263: public int getRunStart(Object attribute) {
264:
265: return getRunStart(attribute, ATTR_MATCHER);
266: }
267:
268: /**
269: * Returns the index of the first character of the run
270: * with respect to the given attribute containing the current character.
271: */
272: ///*JDK12IMPORTS
273: public int getRunStart(Attribute attribute) {
274:
275: return getRunStart(attribute, ATTR_MATCHER);
276: }
277:
278: //JDK12IMPORTS*/
279:
280: /**
281: * Returns the index of the first character of the run
282: * with respect to the given attributes containing the current character.
283: */
284: ///*JDK12IMPORTS
285: public int getRunStart(Set attributes) {
286:
287: return getRunStart(attributes, SET_MATCHER);
288: }
289:
290: //JDK12IMPORTS*/
291:
292: private int getRunStart(Object query, Matcher matcher) {
293:
294: int runStart = getRunStart();
295: int rangeStart = getBeginIndex();
296: Map initialStyle = getAttributes();
297:
298: while (runStart > rangeStart) {
299: AttributeMap style = fText.characterStyleAt(runStart - 1);
300: if (!matcher.matches(initialStyle, style, query)) {
301: return runStart;
302: }
303: runStart = fText.characterStyleStart(runStart - 1);
304: }
305: return rangeStart;
306: }
307:
308: /**
309: * Returns the index of the first character following the run
310: * with respect to all attributes containing the current character.
311: */
312: public int getRunLimit() {
313: return fStyleCache.getRunLimit(fCharIter.getIndex());
314: }
315:
316: /**
317: * Returns the index of the first character following the run
318: * with respect to the given attribute containing the current character.
319: */
320: public int getRunLimit(Object attribute) {
321:
322: return getRunLimit(attribute, ATTR_MATCHER);
323: }
324:
325: /**
326: * Returns the index of the first character following the run
327: * with respect to the given attribute containing the current character.
328: */
329: ///*JDK12IMPORTS
330: public int getRunLimit(Attribute attribute) {
331:
332: return getRunLimit(attribute, ATTR_MATCHER);
333: }
334:
335: //JDK12IMPORTS*/
336:
337: /**
338: * Returns the index of the first character following the run
339: * with respect to the given attributes containing the current character.
340: */
341: ///*JDK12IMPORTS
342: public int getRunLimit(Set attributes) {
343:
344: return getRunLimit(attributes, SET_MATCHER);
345: }
346:
347: //JDK12IMPORTS*/
348:
349: private int getRunLimit(Object query, Matcher matcher) {
350:
351: int runLimit = getRunLimit();
352: int rangeLimit = getEndIndex();
353: Map initialStyle = getAttributes();
354:
355: while (runLimit < rangeLimit) {
356: AttributeMap style = fText.characterStyleAt(runLimit);
357: if (!matcher.matches(initialStyle, style, query)) {
358: return runLimit;
359: }
360: runLimit = fText.characterStyleLimit(runLimit);
361: }
362: return rangeLimit;
363: }
364:
365: /**
366: * Returns a map with the attributes defined on the current
367: * character.
368: */
369: public Map getAttributes() {
370: return fStyleCache.getStyle(fCharIter.getIndex());
371: }
372:
373: /**
374: * Returns the value of the named attribute for the current character.
375: * Returns null if the attribute is not defined.
376: * @param attribute the key of the attribute whose value is requested.
377: */
378: public Object getAttribute(Object attribute) {
379: return getAttributes().get(attribute);
380: }
381:
382: /**
383: * Returns the value of the named attribute for the current character.
384: * Returns null if the attribute is not defined.
385: * @param attribute the key of the attribute whose value is requested.
386: */
387: public Object getAttribute(Attribute attribute) {
388: return getAttributes().get(attribute);
389: }
390:
391: /**
392: * Returns the keys of all attributes defined on the
393: * iterator's text range. The set is empty if no
394: * attributes are defined.
395: */
396: ///*JDK12IMPORTS
397: public Set getAllAttributeKeys() {
398: throw new Error("Implement this method!");
399: }
400:
401: //JDK12IMPORTS*/
402:
403: public Object clone() {
404: return new MTextIterator(fText, fFontResolver, getBeginIndex(),
405: getEndIndex());
406: }
407: }
|