/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.editor.ext;
import org.netbeans.editor.TokenItem;
import javax.swing.text.Position;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Support class for mapping the token-positions
* to the tokens and providing additional operations.
*
* @author Miloslav Metelka
* @version 1.00
*/
class FormatTokenPositionSupport {
private final FormatWriter formatWriter;
/** First save set in the chain */
private SaveSet firstSet;
/** Last save set in the chain */
private SaveSet lastSet;
/** Map holding the [token, token-position-list] pairs. */
private final HashMap tokens2positionLists = new HashMap();
FormatTokenPositionSupport(FormatWriter formatWriter) {
this.formatWriter = formatWriter;
}
private ArrayList getPosList(TokenItem token) {
ArrayList ret = (ArrayList)tokens2positionLists.get(token);
if (ret == null) {
ret = new ArrayList(3);
tokens2positionLists.put(token, ret);
}
return ret;
}
/** Get the token-position for the given token and offset.
* @param token token for which the token-position is being created.
* @param offset offset inside the token at which the position is being
* created.
*/
synchronized ExtTokenPosition getTokenPosition(TokenItem token, int offset,
Position.Bias bias) {
// Check offset correctness
if (token == null) {
if (offset != 0) {
throw new IllegalArgumentException(
"Ending token position has non-zero offset=" + offset);
}
} else if (offset >= token.getImage().length()) {
throw new IllegalArgumentException("Offset=" + offset
+ " >= tokenLength=" + token.getImage().length());
}
ArrayList posList = getPosList(token);
int cnt = posList.size();
ExtTokenPosition etp;
for (int i = 0; i < cnt; i++) {
etp = (ExtTokenPosition)posList.get(i);
if (etp.getOffset() == offset && etp.getBias() == bias) {
return etp;
}
}
etp = new ExtTokenPosition(token, offset, bias);
posList.add(etp);
return etp;
}
/** Notify that the previous token was created with
* the appropriate text taken from the start of this token.
* It's now necessary to split the marks according
* @param token token that was split
* @param startLength initial length of the token-text
* that was cut and inserted into the previous token
* in the chain.
*/
synchronized void splitStartTokenPositions(TokenItem token, int startLength) {
TokenItem prevToken = token.getPrevious();
if (prevToken != null) {
prevToken = formatWriter.findNonEmptyToken(prevToken, true);
}
ArrayList posList = getPosList(token);
int len = posList.size();
ArrayList prevPosList = getPosList(prevToken);
for (int i = 0; i < len; i++) {
ExtTokenPosition etp = (ExtTokenPosition)posList.get(i);
if (etp.offset < startLength) { // move to prevToken
etp.token = prevToken;
posList.remove(i);
prevPosList.add(etp);
i--;
len--;
}
}
}
/** Notify that the previous token was created with
* the appropriate text taken from the start of this token.
* It's now necessary to split the marks according
* @param token token that was split
* @param endLength initial length of the token-text
* that was cut and inserted into the previous token
* in the chain.
*/
synchronized void splitEndTokenPositions(TokenItem token, int endLength) {
TokenItem nextToken = token.getNext();
if (nextToken != null) {
nextToken = formatWriter.findNonEmptyToken(nextToken, false);
}
ArrayList nextPosList = getPosList(nextToken);
ArrayList posList = getPosList(token);
int len = posList.size();
int offset = token.getImage().length() - endLength;
for (int i = 0; i < len; i++) {
ExtTokenPosition etp = (ExtTokenPosition)posList.get(i);
if (etp.offset >= offset) { // move to nextToken
etp.token = nextToken;
etp.offset -= offset;
posList.remove(i);
nextPosList.add(etp);
i--;
len--;
}
}
}
/** Text in the token will be inserted. */
synchronized void tokenTextInsert(TokenItem token, int offset, int length) {
ArrayList posList = getPosList(token);
int len = posList.size();
// Add length to all positions after insertion point
for (int i = 0; i < len; i++) {
ExtTokenPosition etp = (ExtTokenPosition)posList.get(i);
if ((etp.bias == Position.Bias.Backward)
? (etp.offset > offset) : (etp.offset >= offset)) {
etp.offset += length;
}
}
// Move bwd-bias marks from the next token if insert at end
if (token.getImage().length() == offset) {
TokenItem nextToken = token.getNext();
if (nextToken != null) {
nextToken = formatWriter.findNonEmptyToken(nextToken, false);
}
posList = getPosList(nextToken);
len = posList.size();
for (int i = 0; i < len; i++) {
ExtTokenPosition etp = (ExtTokenPosition)posList.get(i);
if (etp.bias == Position.Bias.Backward && etp.offset == 0) {
etp.token = token;
etp.offset = offset;
}
}
}
}
/** Text in the token will be removed. */
synchronized void tokenTextRemove(TokenItem token, int offset, int length) {
ArrayList posList = getPosList(token);
int len = posList.size();
int newLen = token.getImage().length() - length;
ArrayList nextList = getPosList(token.getNext());
for (int i = 0; i < len; i++) {
ExtTokenPosition etp = (ExtTokenPosition)posList.get(i);
if (etp.offset >= offset + length) { // move to nextToken
etp.offset -= length;
} else if (etp.offset >= offset) {
etp.offset = offset;
}
// Check if pos right at the end of token and therefore invalid
if (etp.offset >= newLen) { // need to move to begining of next token
etp.token = token.getNext();
etp.offset = 0;
posList.remove(i);
nextList.add(etp);
i--;
len--;
}
}
}
/** Whole token being removed. */
synchronized void tokenRemove(TokenItem token) {
TokenItem nextToken = token.getNext();
if (nextToken != null) {
nextToken = formatWriter.findNonEmptyToken(nextToken, false);
}
ArrayList nextPosList = getPosList(nextToken);
ArrayList posList = getPosList(token);
int len = posList.size();
for (int i = 0; i < len; i++) {
ExtTokenPosition etp = (ExtTokenPosition)posList.get(i);
etp.token = nextToken;
etp.offset = 0;
nextPosList.add(etp);
}
posList.clear();
// Remove the token from registry
tokens2positionLists.remove(token);
}
/** Given token was inserted into the chain */
synchronized void tokenInsert(TokenItem token) {
if (token.getImage().length() > 0) { // only for non-zero size
ArrayList posList = getPosList(token);
TokenItem nextToken = token.getNext();
if (nextToken != null) {
nextToken = formatWriter.findNonEmptyToken(nextToken, false);
}
ArrayList nextPosList = getPosList(nextToken);
int nextLen = nextPosList.size();
for (int i = 0; i < nextLen; i++) {
ExtTokenPosition etp = (ExtTokenPosition)nextPosList.get(i);
if (etp.offset == 0 && etp.getBias() == Position.Bias.Backward) {
etp.token = token; // offset will stay equal to zero
nextPosList.remove(i);
i--;
nextLen--;
posList.add(etp);
}
}
}
}
/** Clear all the save-sets. */
synchronized void clearSaveSets() {
firstSet = null;
lastSet = null;
}
/** Add the save-set to the registry and perform the checking
* whether the offsets are OK.
*/
synchronized void addSaveSet(int baseOffset, int writtenLen,
int[] offsets, Position.Bias[] biases) {
// Check whether the offsets are OK
for (int i = 0; i < offsets.length; i++) {
if (offsets[i] < 0 || offsets[i] > writtenLen) {
throw new IllegalArgumentException(
"Invalid save-offset=" + offsets[i] + " at index=" + i // NOI18N
+ ". Written length is " + writtenLen); // NOI18N
}
}
SaveSet newSet = new SaveSet(baseOffset, offsets, biases);
if (firstSet != null) {
lastSet.next = newSet;
lastSet = newSet;
} else { // first set
firstSet = lastSet = newSet;
}
}
/** Create the token-positions for all the save sets */
synchronized void createPositions(FormatTokenPosition formatStartPosition) {
updateSaveOffsets(formatStartPosition);
SaveSet curSet = firstSet;
FormatWriter.FormatTokenItem token
= (FormatWriter.FormatTokenItem)formatStartPosition.getToken();
boolean noText = (token == null);
while (curSet != null) {
int len = curSet.offsets.length;
for (int i = 0; i < len; i++) {
if (noText) {
curSet.positions[i] = getTokenPosition(null, 0, curSet.biases[i]);
} else { // there's some text to be formatted
// Find the covering token and create the position
int offset = curSet.offsets[i];
while (token != null) {
if (offset < token.getSaveOffset()) {
token = (FormatWriter.FormatTokenItem)token.getPrevious();
} else if ((offset > token.getSaveOffset() + token.getImage().length())
|| token.getImage().length() == 0
) {
token = (FormatWriter.FormatTokenItem)token.getNext();
} else { // the right token
curSet.positions[i] = getTokenPosition(token,
offset - token.getSaveOffset(), curSet.biases[i]);
break; // break the loop
}
}
if (token == null) { // It is right at the end
curSet.positions[i] = getTokenPosition(null, 0, curSet.biases[i]);
token = (FormatWriter.FormatTokenItem)formatWriter.getLastToken();
}
}
}
curSet = curSet.next;
}
}
synchronized void updateSaveSets(FormatTokenPosition formatStartPosition) {
updateSaveOffsets(formatStartPosition);
SaveSet curSet = firstSet;
int endOffset = 0; // offset of the null token
if (formatStartPosition.getToken() != null) {
endOffset = ((FormatWriter.FormatTokenItem)formatWriter.getLastToken()).getSaveOffset()
+ formatWriter.getLastToken().getImage().length();
}
while (curSet != null) {
int len = curSet.offsets.length;
for (int i = 0; i < len; i++) {
FormatWriter.FormatTokenItem token
= (FormatWriter.FormatTokenItem)curSet.positions[i].getToken();
if (token == null) {
curSet.offsets[i] = endOffset;
} else { // non-null token
curSet.offsets[i] = token.getSaveOffset()
+ curSet.positions[i].getOffset();
}
}
}
}
/** Number the tokens so that they are OK for finding out the
* offsets.
*/
private void updateSaveOffsets(FormatTokenPosition formatStartPosition) {
if (firstSet != null) { // it has only sense if there are any save-sets
FormatWriter.FormatTokenItem ti
= (FormatWriter.FormatTokenItem)formatStartPosition.getToken();
int offset = -formatStartPosition.getOffset();
while (ti != null) {
ti.setSaveOffset(offset);
offset += ti.getImage().length();
ti = (FormatWriter.FormatTokenItem)ti.getNext();
}
}
}
/** Implementation of the extended-token-position that allows
* modification of its token and offset fields.
*/
class ExtTokenPosition implements FormatTokenPosition {
TokenItem token;
int offset;
/** Whether the position should stay the same if inserted right at it. */
Position.Bias bias;
ExtTokenPosition(TokenItem token, int offset) {
this(token, offset, Position.Bias.Forward);
}
ExtTokenPosition(TokenItem token, int offset, Position.Bias bias) {
this.token = token;
this.offset = offset;
this.bias = bias;
}
public TokenItem getToken() {
return token;
}
public int getOffset() {
return (token != null) ? offset : 0;
}
public Position.Bias getBias() {
return bias;
}
public boolean equals(Object o) {
return equals(o, true); // ignore bias in comparison
}
public boolean equals(Object o, boolean ignoreBias) {
if (o instanceof FormatTokenPosition) {
FormatTokenPosition tp = (FormatTokenPosition)o;
return token == tp.getToken() && offset == tp.getOffset()
&& (ignoreBias || bias == tp.getBias());
}
return false;
}
public String toString() {
return "<" + getToken() + ", " + getOffset() + ", " + getBias() + ">";
}
}
/** Class holding the info about the set of the offsets to save
* during the formatting.
*/
static class SaveSet {
/** Next set in the chain. */
SaveSet next;
/** Base offset of the buffer corresponding to the offsets */
int baseOffset;
/** Offsets to save */
int[] offsets;
/** Biases for the positions */
Position.Bias[] biases;
/** Token positions corresponding to the offsets */
FormatTokenPosition[] positions;
SaveSet(int baseOffset, int[] offsets, Position.Bias[] biases) {
this.baseOffset = baseOffset;
this.offsets = offsets;
this.biases = biases;
}
}
}