2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.editor.ext;
44
import java.util.HashMap;
45
import java.util.ArrayList;
46
import javax.swing.text.Position;
47
import org.netbeans.editor.TokenItem;
50
* Support class for mapping the token-positions
51
* to the tokens and providing additional operations.
53
* @author Miloslav Metelka
57
class FormatTokenPositionSupport {
59
private final FormatWriter formatWriter;
61
/** First save set in the chain */
62
private SaveSet firstSet;
64
/** Last save set in the chain */
65
private SaveSet lastSet;
67
/** Map holding the [token, token-position-list] pairs. */
68
private final HashMap tokens2positionLists = new HashMap();
70
FormatTokenPositionSupport(FormatWriter formatWriter) {
71
this.formatWriter = formatWriter;
74
private ArrayList getPosList(TokenItem token) {
75
ArrayList ret = (ArrayList)tokens2positionLists.get(token);
77
ret = new ArrayList(3);
78
tokens2positionLists.put(token, ret);
83
/** Get the token-position for the given token and offset.
84
* @param token token for which the token-position is being created.
85
* @param offset offset inside the token at which the position is being
88
synchronized ExtTokenPosition getTokenPosition(TokenItem token, int offset,
90
// Check offset correctness
93
throw new IllegalArgumentException(
94
"Ending token position has non-zero offset=" + offset); // NOI18N
97
} else if (offset >= token.getImage().length()) {
98
throw new IllegalArgumentException("Offset=" + offset // NOI18N
99
+ " >= tokenLength=" + token.getImage().length()); // NOI18N
102
ArrayList posList = getPosList(token);
103
int cnt = posList.size();
104
ExtTokenPosition etp;
105
for (int i = 0; i < cnt; i++) {
106
etp = (ExtTokenPosition)posList.get(i);
107
if (etp.getOffset() == offset && etp.getBias() == bias) {
112
etp = new ExtTokenPosition(token, offset, bias);
117
/** Notify that the previous token was created with
118
* the appropriate text taken from the start of this token.
119
* It's now necessary to split the marks according
120
* @param token token that was split
121
* @param startLength initial length of the token-text
122
* that was cut and inserted into the previous token
125
synchronized void splitStartTokenPositions(TokenItem token, int startLength) {
126
TokenItem prevToken = token.getPrevious();
127
if (prevToken != null) {
128
prevToken = formatWriter.findNonEmptyToken(prevToken, true);
130
ArrayList posList = getPosList(token);
131
int len = posList.size();
132
ArrayList prevPosList = getPosList(prevToken);
133
for (int i = 0; i < len; i++) {
134
ExtTokenPosition etp = (ExtTokenPosition)posList.get(i);
135
if (etp.offset < startLength) { // move to prevToken
136
etp.token = prevToken;
138
prevPosList.add(etp);
145
/** Notify that the previous token was created with
146
* the appropriate text taken from the start of this token.
147
* It's now necessary to split the marks according
148
* @param token token that was split
149
* @param endLength initial length of the token-text
150
* that was cut and inserted into the previous token
153
synchronized void splitEndTokenPositions(TokenItem token, int endLength) {
154
TokenItem nextToken = token.getNext();
155
if (nextToken != null) {
156
nextToken = formatWriter.findNonEmptyToken(nextToken, false);
158
ArrayList nextPosList = getPosList(nextToken);
160
ArrayList posList = getPosList(token);
161
int len = posList.size();
162
int offset = token.getImage().length() - endLength;
163
for (int i = 0; i < len; i++) {
164
ExtTokenPosition etp = (ExtTokenPosition)posList.get(i);
165
if (etp.offset >= offset) { // move to nextToken
166
etp.token = nextToken;
167
etp.offset -= offset;
169
nextPosList.add(etp);
176
/** Text in the token will be inserted. */
177
synchronized void tokenTextInsert(TokenItem token, int offset, int length) {
178
ArrayList posList = getPosList(token);
179
int len = posList.size();
180
// Add length to all positions after insertion point
181
for (int i = 0; i < len; i++) {
182
ExtTokenPosition etp = (ExtTokenPosition)posList.get(i);
183
if ((etp.bias == Position.Bias.Backward)
184
? (etp.offset > offset) : (etp.offset >= offset)) {
185
etp.offset += length;
189
// Move bwd-bias marks from the next token if insert at end
190
if (token.getImage().length() == offset) {
191
TokenItem nextToken = token.getNext();
192
if (nextToken != null) {
193
nextToken = formatWriter.findNonEmptyToken(nextToken, false);
195
posList = getPosList(nextToken);
196
len = posList.size();
197
for (int i = 0; i < len; i++) {
198
ExtTokenPosition etp = (ExtTokenPosition)posList.get(i);
199
if (etp.bias == Position.Bias.Backward && etp.offset == 0) {
209
/** Text in the token will be removed. */
210
synchronized void tokenTextRemove(TokenItem token, int offset, int length) {
211
ArrayList posList = getPosList(token);
212
int len = posList.size();
213
int newLen = token.getImage().length() - length;
214
ArrayList nextList = getPosList(token.getNext());
215
for (int i = 0; i < len; i++) {
216
ExtTokenPosition etp = (ExtTokenPosition)posList.get(i);
217
if (etp.offset >= offset + length) { // move to nextToken
218
etp.offset -= length;
220
} else if (etp.offset >= offset) {
224
// Check if pos right at the end of token and therefore invalid
225
if (etp.offset >= newLen) { // need to move to begining of next token
226
etp.token = token.getNext();
236
/** Whole token being removed. */
237
synchronized void tokenRemove(TokenItem token) {
238
TokenItem nextToken = token.getNext();
239
if (nextToken != null) {
240
nextToken = formatWriter.findNonEmptyToken(nextToken, false);
242
ArrayList nextPosList = getPosList(nextToken);
244
ArrayList posList = getPosList(token);
245
int len = posList.size();
246
for (int i = 0; i < len; i++) {
247
ExtTokenPosition etp = (ExtTokenPosition)posList.get(i);
248
etp.token = nextToken;
250
nextPosList.add(etp);
254
// Remove the token from registry
255
tokens2positionLists.remove(token);
258
/** Given token was inserted into the chain */
259
synchronized void tokenInsert(TokenItem token) {
260
if (token.getImage().length() > 0) { // only for non-zero size
261
ArrayList posList = getPosList(token);
263
TokenItem nextToken = token.getNext();
264
if (nextToken != null) {
265
nextToken = formatWriter.findNonEmptyToken(nextToken, false);
267
ArrayList nextPosList = getPosList(nextToken);
269
int nextLen = nextPosList.size();
270
for (int i = 0; i < nextLen; i++) {
271
ExtTokenPosition etp = (ExtTokenPosition)nextPosList.get(i);
272
if (etp.offset == 0 && etp.getBias() == Position.Bias.Backward) {
273
etp.token = token; // offset will stay equal to zero
274
nextPosList.remove(i);
283
/** Clear all the save-sets. */
284
synchronized void clearSaveSets() {
289
/** Add the save-set to the registry and perform the checking
290
* whether the offsets are OK.
292
synchronized void addSaveSet(int baseOffset, int writtenLen,
293
int[] offsets, Position.Bias[] biases) {
294
// Check whether the offsets are OK
295
for (int i = 0; i < offsets.length; i++) {
296
if (offsets[i] < 0 || offsets[i] > writtenLen) {
297
throw new IllegalArgumentException(
298
"Invalid save-offset=" + offsets[i] + " at index=" + i // NOI18N
299
+ ". Written length is " + writtenLen); // NOI18N
303
SaveSet newSet = new SaveSet(baseOffset, offsets, biases);
305
if (firstSet != null) {
306
lastSet.next = newSet;
309
} else { // first set
310
firstSet = lastSet = newSet;
314
/** Create the token-positions for all the save sets */
315
synchronized void createPositions(FormatTokenPosition formatStartPosition) {
316
updateSaveOffsets(formatStartPosition);
318
SaveSet curSet = firstSet;
319
FormatWriter.FormatTokenItem token
320
= (FormatWriter.FormatTokenItem)formatStartPosition.getToken();
321
boolean noText = (token == null);
323
while (curSet != null) {
324
int len = curSet.offsets.length;
325
for (int i = 0; i < len; i++) {
327
curSet.positions[i] = getTokenPosition(null, 0, curSet.biases[i]);
329
} else { // there's some text to be formatted
331
// Find the covering token and create the position
332
int offset = curSet.offsets[i];
333
while (token != null) {
334
if (offset < token.getSaveOffset()) {
335
token = (FormatWriter.FormatTokenItem)token.getPrevious();
337
} else if ((offset > token.getSaveOffset() + token.getImage().length())
338
|| token.getImage().length() == 0
340
token = (FormatWriter.FormatTokenItem)token.getNext();
342
} else { // the right token
343
curSet.positions[i] = getTokenPosition(token,
344
offset - token.getSaveOffset(), curSet.biases[i]);
345
break; // break the loop
349
if (token == null) { // It is right at the end
350
curSet.positions[i] = getTokenPosition(null, 0, curSet.biases[i]);
351
token = (FormatWriter.FormatTokenItem)formatWriter.getLastToken();
356
curSet = curSet.next;
360
synchronized void updateSaveSets(FormatTokenPosition formatStartPosition) {
361
updateSaveOffsets(formatStartPosition);
363
SaveSet curSet = firstSet;
364
int endOffset = 0; // offset of the null token
365
if (formatStartPosition.getToken() != null) {
366
endOffset = ((FormatWriter.FormatTokenItem)formatWriter.getLastToken()).getSaveOffset()
367
+ formatWriter.getLastToken().getImage().length();
370
while (curSet != null) {
371
int len = curSet.offsets.length;
372
for (int i = 0; i < len; i++) {
373
FormatWriter.FormatTokenItem token
374
= (FormatWriter.FormatTokenItem)curSet.positions[i].getToken();
376
curSet.offsets[i] = endOffset;
378
} else { // non-null token
379
curSet.offsets[i] = token.getSaveOffset()
380
+ curSet.positions[i].getOffset();
386
/** Number the tokens so that they are OK for finding out the
389
private void updateSaveOffsets(FormatTokenPosition formatStartPosition) {
390
if (firstSet != null) { // it has only sense if there are any save-sets
391
FormatWriter.FormatTokenItem ti
392
= (FormatWriter.FormatTokenItem)formatStartPosition.getToken();
393
int offset = -formatStartPosition.getOffset();
396
ti.setSaveOffset(offset);
397
offset += ti.getImage().length();
399
ti = (FormatWriter.FormatTokenItem)ti.getNext();
404
/** Implementation of the extended-token-position that allows
405
* modification of its token and offset fields.
407
class ExtTokenPosition implements FormatTokenPosition {
413
/** Whether the position should stay the same if inserted right at it. */
416
ExtTokenPosition(TokenItem token, int offset) {
417
this(token, offset, Position.Bias.Forward);
420
ExtTokenPosition(TokenItem token, int offset, Position.Bias bias) {
422
this.offset = offset;
426
public TokenItem getToken() {
430
public int getOffset() {
431
return (token != null) ? offset : 0;
434
public Position.Bias getBias() {
438
public boolean equals(Object o) {
439
return equals(o, true); // ignore bias in comparison
442
public boolean equals(Object o, boolean ignoreBias) {
443
if (o instanceof FormatTokenPosition) {
444
FormatTokenPosition tp = (FormatTokenPosition)o;
446
return token == tp.getToken() && offset == tp.getOffset()
447
&& (ignoreBias || bias == tp.getBias());
453
public String toString() {
454
return "<" + getToken() + ", " + getOffset() + ", " + getBias() + ">"; // NOI18N
459
/** Class holding the info about the set of the offsets to save
460
* during the formatting.
462
static class SaveSet {
464
/** Next set in the chain. */
467
/** Base offset of the buffer corresponding to the offsets */
470
/** Offsets to save */
473
/** Biases for the positions */
474
Position.Bias[] biases;
476
/** Token positions corresponding to the offsets */
477
FormatTokenPosition[] positions;
479
SaveSet(int baseOffset, int[] offsets, Position.Bias[] biases) {
480
this.baseOffset = baseOffset;
481
this.offsets = offsets;
482
this.biases = biases;