001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.editor.ext;
043:
044: import java.util.HashMap;
045: import java.util.ArrayList;
046: import javax.swing.text.Position;
047: import org.netbeans.editor.TokenItem;
048:
049: /**
050: * Support class for mapping the token-positions
051: * to the tokens and providing additional operations.
052: *
053: * @author Miloslav Metelka
054: * @version 1.00
055: */
056:
057: class FormatTokenPositionSupport {
058:
059: private final FormatWriter formatWriter;
060:
061: /** First save set in the chain */
062: private SaveSet firstSet;
063:
064: /** Last save set in the chain */
065: private SaveSet lastSet;
066:
067: /** Map holding the [token, token-position-list] pairs. */
068: private final HashMap tokens2positionLists = new HashMap();
069:
070: FormatTokenPositionSupport(FormatWriter formatWriter) {
071: this .formatWriter = formatWriter;
072: }
073:
074: private ArrayList getPosList(TokenItem token) {
075: ArrayList ret = (ArrayList) tokens2positionLists.get(token);
076: if (ret == null) {
077: ret = new ArrayList(3);
078: tokens2positionLists.put(token, ret);
079: }
080: return ret;
081: }
082:
083: /** Get the token-position for the given token and offset.
084: * @param token token for which the token-position is being created.
085: * @param offset offset inside the token at which the position is being
086: * created.
087: */
088: synchronized ExtTokenPosition getTokenPosition(TokenItem token,
089: int offset, Position.Bias bias) {
090: // Check offset correctness
091: if (token == null) {
092: if (offset != 0) {
093: throw new IllegalArgumentException(
094: "Ending token position has non-zero offset="
095: + offset); // NOI18N
096: }
097:
098: } else if (offset >= token.getImage().length()) {
099: throw new IllegalArgumentException("Offset=" + offset // NOI18N
100: + " >= tokenLength=" + token.getImage().length()); // NOI18N
101: }
102:
103: ArrayList posList = getPosList(token);
104: int cnt = posList.size();
105: ExtTokenPosition etp;
106: for (int i = 0; i < cnt; i++) {
107: etp = (ExtTokenPosition) posList.get(i);
108: if (etp.getOffset() == offset && etp.getBias() == bias) {
109: return etp;
110: }
111: }
112:
113: etp = new ExtTokenPosition(token, offset, bias);
114: posList.add(etp);
115: return etp;
116: }
117:
118: /** Notify that the previous token was created with
119: * the appropriate text taken from the start of this token.
120: * It's now necessary to split the marks according
121: * @param token token that was split
122: * @param startLength initial length of the token-text
123: * that was cut and inserted into the previous token
124: * in the chain.
125: */
126: synchronized void splitStartTokenPositions(TokenItem token,
127: int startLength) {
128: TokenItem prevToken = token.getPrevious();
129: if (prevToken != null) {
130: prevToken = formatWriter.findNonEmptyToken(prevToken, true);
131: }
132: ArrayList posList = getPosList(token);
133: int len = posList.size();
134: ArrayList prevPosList = getPosList(prevToken);
135: for (int i = 0; i < len; i++) {
136: ExtTokenPosition etp = (ExtTokenPosition) posList.get(i);
137: if (etp.offset < startLength) { // move to prevToken
138: etp.token = prevToken;
139: posList.remove(i);
140: prevPosList.add(etp);
141: i--;
142: len--;
143: }
144: }
145: }
146:
147: /** Notify that the previous token was created with
148: * the appropriate text taken from the start of this token.
149: * It's now necessary to split the marks according
150: * @param token token that was split
151: * @param endLength initial length of the token-text
152: * that was cut and inserted into the previous token
153: * in the chain.
154: */
155: synchronized void splitEndTokenPositions(TokenItem token,
156: int endLength) {
157: TokenItem nextToken = token.getNext();
158: if (nextToken != null) {
159: nextToken = formatWriter
160: .findNonEmptyToken(nextToken, false);
161: }
162: ArrayList nextPosList = getPosList(nextToken);
163:
164: ArrayList posList = getPosList(token);
165: int len = posList.size();
166: int offset = token.getImage().length() - endLength;
167: for (int i = 0; i < len; i++) {
168: ExtTokenPosition etp = (ExtTokenPosition) posList.get(i);
169: if (etp.offset >= offset) { // move to nextToken
170: etp.token = nextToken;
171: etp.offset -= offset;
172: posList.remove(i);
173: nextPosList.add(etp);
174: i--;
175: len--;
176: }
177: }
178: }
179:
180: /** Text in the token will be inserted. */
181: synchronized void tokenTextInsert(TokenItem token, int offset,
182: int length) {
183: ArrayList posList = getPosList(token);
184: int len = posList.size();
185: // Add length to all positions after insertion point
186: for (int i = 0; i < len; i++) {
187: ExtTokenPosition etp = (ExtTokenPosition) posList.get(i);
188: if ((etp.bias == Position.Bias.Backward) ? (etp.offset > offset)
189: : (etp.offset >= offset)) {
190: etp.offset += length;
191: }
192: }
193:
194: // Move bwd-bias marks from the next token if insert at end
195: if (token.getImage().length() == offset) {
196: TokenItem nextToken = token.getNext();
197: if (nextToken != null) {
198: nextToken = formatWriter.findNonEmptyToken(nextToken,
199: false);
200: }
201: posList = getPosList(nextToken);
202: len = posList.size();
203: for (int i = 0; i < len; i++) {
204: ExtTokenPosition etp = (ExtTokenPosition) posList
205: .get(i);
206: if (etp.bias == Position.Bias.Backward
207: && etp.offset == 0) {
208: etp.token = token;
209: etp.offset = offset;
210: }
211: }
212: }
213:
214: }
215:
216: /** Text in the token will be removed. */
217: synchronized void tokenTextRemove(TokenItem token, int offset,
218: int length) {
219: ArrayList posList = getPosList(token);
220: int len = posList.size();
221: int newLen = token.getImage().length() - length;
222: ArrayList nextList = getPosList(token.getNext());
223: for (int i = 0; i < len; i++) {
224: ExtTokenPosition etp = (ExtTokenPosition) posList.get(i);
225: if (etp.offset >= offset + length) { // move to nextToken
226: etp.offset -= length;
227:
228: } else if (etp.offset >= offset) {
229: etp.offset = offset;
230: }
231:
232: // Check if pos right at the end of token and therefore invalid
233: if (etp.offset >= newLen) { // need to move to begining of next token
234: etp.token = token.getNext();
235: etp.offset = 0;
236: posList.remove(i);
237: nextList.add(etp);
238: i--;
239: len--;
240: }
241: }
242: }
243:
244: /** Whole token being removed. */
245: synchronized void tokenRemove(TokenItem token) {
246: TokenItem nextToken = token.getNext();
247: if (nextToken != null) {
248: nextToken = formatWriter
249: .findNonEmptyToken(nextToken, false);
250: }
251: ArrayList nextPosList = getPosList(nextToken);
252:
253: ArrayList posList = getPosList(token);
254: int len = posList.size();
255: for (int i = 0; i < len; i++) {
256: ExtTokenPosition etp = (ExtTokenPosition) posList.get(i);
257: etp.token = nextToken;
258: etp.offset = 0;
259: nextPosList.add(etp);
260: }
261: posList.clear();
262:
263: // Remove the token from registry
264: tokens2positionLists.remove(token);
265: }
266:
267: /** Given token was inserted into the chain */
268: synchronized void tokenInsert(TokenItem token) {
269: if (token.getImage().length() > 0) { // only for non-zero size
270: ArrayList posList = getPosList(token);
271:
272: TokenItem nextToken = token.getNext();
273: if (nextToken != null) {
274: nextToken = formatWriter.findNonEmptyToken(nextToken,
275: false);
276: }
277: ArrayList nextPosList = getPosList(nextToken);
278:
279: int nextLen = nextPosList.size();
280: for (int i = 0; i < nextLen; i++) {
281: ExtTokenPosition etp = (ExtTokenPosition) nextPosList
282: .get(i);
283: if (etp.offset == 0
284: && etp.getBias() == Position.Bias.Backward) {
285: etp.token = token; // offset will stay equal to zero
286: nextPosList.remove(i);
287: i--;
288: nextLen--;
289: posList.add(etp);
290: }
291: }
292: }
293: }
294:
295: /** Clear all the save-sets. */
296: synchronized void clearSaveSets() {
297: firstSet = null;
298: lastSet = null;
299: }
300:
301: /** Add the save-set to the registry and perform the checking
302: * whether the offsets are OK.
303: */
304: synchronized void addSaveSet(int baseOffset, int writtenLen,
305: int[] offsets, Position.Bias[] biases) {
306: // Check whether the offsets are OK
307: for (int i = 0; i < offsets.length; i++) {
308: if (offsets[i] < 0 || offsets[i] > writtenLen) {
309: throw new IllegalArgumentException(
310: "Invalid save-offset=" + offsets[i]
311: + " at index=" + i // NOI18N
312: + ". Written length is " + writtenLen); // NOI18N
313: }
314: }
315:
316: SaveSet newSet = new SaveSet(baseOffset, offsets, biases);
317:
318: if (firstSet != null) {
319: lastSet.next = newSet;
320: lastSet = newSet;
321:
322: } else { // first set
323: firstSet = lastSet = newSet;
324: }
325: }
326:
327: /** Create the token-positions for all the save sets */
328: synchronized void createPositions(
329: FormatTokenPosition formatStartPosition) {
330: updateSaveOffsets(formatStartPosition);
331:
332: SaveSet curSet = firstSet;
333: FormatWriter.FormatTokenItem token = (FormatWriter.FormatTokenItem) formatStartPosition
334: .getToken();
335: boolean noText = (token == null);
336:
337: while (curSet != null) {
338: int len = curSet.offsets.length;
339: for (int i = 0; i < len; i++) {
340: if (noText) {
341: curSet.positions[i] = getTokenPosition(null, 0,
342: curSet.biases[i]);
343:
344: } else { // there's some text to be formatted
345:
346: // Find the covering token and create the position
347: int offset = curSet.offsets[i];
348: while (token != null) {
349: if (offset < token.getSaveOffset()) {
350: token = (FormatWriter.FormatTokenItem) token
351: .getPrevious();
352:
353: } else if ((offset > token.getSaveOffset()
354: + token.getImage().length())
355: || token.getImage().length() == 0) {
356: token = (FormatWriter.FormatTokenItem) token
357: .getNext();
358:
359: } else { // the right token
360: curSet.positions[i] = getTokenPosition(
361: token, offset
362: - token.getSaveOffset(),
363: curSet.biases[i]);
364: break; // break the loop
365: }
366: }
367:
368: if (token == null) { // It is right at the end
369: curSet.positions[i] = getTokenPosition(null, 0,
370: curSet.biases[i]);
371: token = (FormatWriter.FormatTokenItem) formatWriter
372: .getLastToken();
373: }
374: }
375: }
376:
377: curSet = curSet.next;
378: }
379: }
380:
381: synchronized void updateSaveSets(
382: FormatTokenPosition formatStartPosition) {
383: updateSaveOffsets(formatStartPosition);
384:
385: SaveSet curSet = firstSet;
386: int endOffset = 0; // offset of the null token
387: if (formatStartPosition.getToken() != null) {
388: endOffset = ((FormatWriter.FormatTokenItem) formatWriter
389: .getLastToken()).getSaveOffset()
390: + formatWriter.getLastToken().getImage().length();
391: }
392:
393: while (curSet != null) {
394: int len = curSet.offsets.length;
395: for (int i = 0; i < len; i++) {
396: FormatWriter.FormatTokenItem token = (FormatWriter.FormatTokenItem) curSet.positions[i]
397: .getToken();
398: if (token == null) {
399: curSet.offsets[i] = endOffset;
400:
401: } else { // non-null token
402: curSet.offsets[i] = token.getSaveOffset()
403: + curSet.positions[i].getOffset();
404: }
405: }
406: }
407: }
408:
409: /** Number the tokens so that they are OK for finding out the
410: * offsets.
411: */
412: private void updateSaveOffsets(
413: FormatTokenPosition formatStartPosition) {
414: if (firstSet != null) { // it has only sense if there are any save-sets
415: FormatWriter.FormatTokenItem ti = (FormatWriter.FormatTokenItem) formatStartPosition
416: .getToken();
417: int offset = -formatStartPosition.getOffset();
418:
419: while (ti != null) {
420: ti.setSaveOffset(offset);
421: offset += ti.getImage().length();
422:
423: ti = (FormatWriter.FormatTokenItem) ti.getNext();
424: }
425: }
426: }
427:
428: /** Implementation of the extended-token-position that allows
429: * modification of its token and offset fields.
430: */
431: class ExtTokenPosition implements FormatTokenPosition {
432:
433: TokenItem token;
434:
435: int offset;
436:
437: /** Whether the position should stay the same if inserted right at it. */
438: Position.Bias bias;
439:
440: ExtTokenPosition(TokenItem token, int offset) {
441: this (token, offset, Position.Bias.Forward);
442: }
443:
444: ExtTokenPosition(TokenItem token, int offset, Position.Bias bias) {
445: this .token = token;
446: this .offset = offset;
447: this .bias = bias;
448: }
449:
450: public TokenItem getToken() {
451: return token;
452: }
453:
454: public int getOffset() {
455: return (token != null) ? offset : 0;
456: }
457:
458: public Position.Bias getBias() {
459: return bias;
460: }
461:
462: public boolean equals(Object o) {
463: return equals(o, true); // ignore bias in comparison
464: }
465:
466: public boolean equals(Object o, boolean ignoreBias) {
467: if (o instanceof FormatTokenPosition) {
468: FormatTokenPosition tp = (FormatTokenPosition) o;
469:
470: return token == tp.getToken()
471: && offset == tp.getOffset()
472: && (ignoreBias || bias == tp.getBias());
473: }
474:
475: return false;
476: }
477:
478: public String toString() {
479: return "<" + getToken() + ", " + getOffset() + ", "
480: + getBias() + ">"; // NOI18N
481: }
482:
483: }
484:
485: /** Class holding the info about the set of the offsets to save
486: * during the formatting.
487: */
488: static class SaveSet {
489:
490: /** Next set in the chain. */
491: SaveSet next;
492:
493: /** Base offset of the buffer corresponding to the offsets */
494: int baseOffset;
495:
496: /** Offsets to save */
497: int[] offsets;
498:
499: /** Biases for the positions */
500: Position.Bias[] biases;
501:
502: /** Token positions corresponding to the offsets */
503: FormatTokenPosition[] positions;
504:
505: SaveSet(int baseOffset, int[] offsets, Position.Bias[] biases) {
506: this.baseOffset = baseOffset;
507: this.offsets = offsets;
508: this.biases = biases;
509: }
510:
511: }
512:
513: }
|