001: /*
002: * Sun Public License Notice
003: *
004: * The contents of this file are subject to the Sun Public License
005: * Version 1.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://www.sun.com/
008: *
009: * The Original Code is NetBeans. The Initial Developer of the Original
010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
011: * Microsystems, Inc. All Rights Reserved.
012: */
013:
014: package org.netbeans.editor;
015:
016: import java.util.HashMap;
017:
018: /**
019: * Immutable and 'interned' wrapper holding an array of the contexts starting
020: * with the original context in which the token is defined and ending with the
021: * target context from which the token is being returned. It is final and has no
022: * public constructor. The only entrypoint is through the <tt>get()</tt>
023: * method. It's guaranteed that the two context-paths containing the same
024: * contexts in the same order are the same objects and the equal-operator can be
025: * used instead of calling <tt>equals()</tt>.
026: *
027: * @author Miloslav Metelka
028: * @version 1.00
029: */
030:
031: public final class TokenContextPath {
032:
033: /** Cache containing [ArrayMatcher, TokenContextPath] pairs. */
034: private static final HashMap registry = new HashMap(199);
035:
036: /** Contexts contained in this context-path. */
037: private TokenContext[] contexts;
038:
039: /** Path for context-array without the last member. */
040: private TokenContextPath parent;
041:
042: /** Path corresponding to the first context in the context-array. */
043: private TokenContextPath base;
044:
045: /**
046: * Full name-prefix consisting of the prefixes of all the contexts in the
047: * path.
048: */
049: private String namePrefix;
050:
051: /** Map holding [token-name, prefixed-token-name] pairs. */
052: private final HashMap tokenNameCache = new HashMap();
053:
054: /** Map holding [start-path-replacement, replaced-path] */
055: private final HashMap replaceStartCache = new HashMap();
056:
057: /** Get the context-path for non-empty array of the contexts. */
058: static synchronized TokenContextPath get(TokenContext[] contexts) {
059: if (contexts == null || contexts.length == 0) {
060: throw new IllegalArgumentException(
061: "Contexts must be valid and non-empty.");
062: }
063:
064: ArrayMatcher am = new ArrayMatcher(contexts);
065: TokenContextPath path = (TokenContextPath) registry.get(am);
066: if (path == null) {
067: path = new TokenContextPath(contexts);
068: registry.put(am, path);
069: }
070:
071: return path;
072: }
073:
074: /** Construction from outside prohibited. */
075: private TokenContextPath(TokenContext[] contexts) {
076: this .contexts = contexts;
077:
078: if (contexts.length == 1) {
079: base = this ; // it's base for itself
080: }
081: }
082:
083: /**
084: * Retrieve the contexts of this context-path. The contexts of the
085: * context-array must NOT be modified in any way.
086: */
087: public TokenContext[] getContexts() {
088: return contexts;
089: }
090:
091: /** Get the length of the path returning the length of the contexts array. */
092: public int length() {
093: return contexts.length;
094: }
095:
096: /**
097: * Get parent context-path that consists of all the contexts of this path
098: * except the last one.
099: */
100: public TokenContextPath getParent() {
101: if (parent == null && contexts.length > 1) {
102: TokenContext[] parentContexts = new TokenContext[contexts.length - 1];
103: System.arraycopy(contexts, 0, parentContexts, 0,
104: contexts.length - 1);
105:
106: parent = get(parentContexts);
107: }
108:
109: return parent;
110: }
111:
112: /**
113: * Get the base path which corresponds to only the first context in the
114: * context-array. The base path can be used for fast checking of the origin
115: * path of the token.
116: */
117: public TokenContextPath getBase() {
118: if (base == null) {
119: base = getParent().getBase();
120: }
121:
122: return base;
123: }
124:
125: /**
126: * Does this path contain the given path. It corresponds to the situation
127: * when the contexts of the given path are at the begining of this path.
128: */
129: public boolean contains(TokenContextPath tcp) {
130: if (tcp == this ) {
131: return true;
132:
133: } else if (contexts.length > 1) {
134: return getParent().contains(tcp);
135:
136: } else {
137: return false;
138: }
139: }
140:
141: /**
142: * Get the path which has the initial part of the path (usually only the
143: * base path) replaced by the given path. The length of the replaced part of
144: * the path is the same as the length of the path that will replace it. For
145: * better performance the method caches the [byPath, result-path] in
146: * hashmap.
147: *
148: * @param byPath
149: * path that will replace the initial portion of this path. The
150: * length of the portion is the same as the length of this
151: * parameter.
152: * @return the path with the initial part being replaced.
153: */
154: public TokenContextPath replaceStart(TokenContextPath byPath) {
155: // Check whether byPath isn't longer than this path
156: if (contexts.length < byPath.contexts.length) {
157: throw new IllegalArgumentException("byPath=" + byPath
158: + " is too long.");
159: }
160:
161: synchronized (replaceStartCache) {
162: TokenContextPath ret = (TokenContextPath) replaceStartCache
163: .get(byPath);
164: if (ret == null) {
165: TokenContext[] targetContexts = (TokenContext[]) contexts
166: .clone();
167: for (int i = byPath.contexts.length - 1; i >= 0; i--) {
168: targetContexts[i] = byPath.contexts[i];
169: }
170: ret = get(targetContexts);
171: replaceStartCache.put(byPath, ret);
172: }
173:
174: return ret;
175: }
176: }
177:
178: /** Get the prefix that this context adds to the name of its tokens. */
179: public String getNamePrefix() {
180: if (namePrefix == null) {
181: if (contexts.length == 1) {
182: namePrefix = contexts[0].getNamePrefix();
183:
184: } else { // path has more contexts
185: namePrefix = (contexts[contexts.length - 1]
186: .getNamePrefix() + getParent().getNamePrefix())
187: .intern();
188: }
189: }
190:
191: return namePrefix;
192: }
193:
194: /**
195: * Get the token-name with the name-prefix of this context-path. It merges
196: * the token-name with the name-prefix of this context-path but it does it
197: * without creating a new object.
198: */
199: public String getFullTokenName(TokenCategory tokenIDOrCategory) {
200: String tokenName = tokenIDOrCategory.getName();
201: String fullName;
202: synchronized (tokenNameCache) {
203: fullName = (String) tokenNameCache.get(tokenName);
204: if (fullName == null) {
205: fullName = (getNamePrefix() + tokenName).intern();
206: tokenNameCache.put(tokenName, fullName);
207: }
208: }
209:
210: return fullName;
211: }
212:
213: public String toString() {
214: StringBuffer sb = new StringBuffer("|");
215: for (int i = 0; i < contexts.length; i++) {
216: String shortName = contexts[i].getClass().getName();
217: shortName = shortName
218: .substring(shortName.lastIndexOf('.') + 1);
219:
220: sb.append('<');
221: sb.append(shortName);
222: sb.append('>');
223: }
224: sb.append('|');
225:
226: return sb.toString();
227: }
228:
229: private static final class ArrayMatcher {
230:
231: /** Cached hash-code */
232: private int hash;
233:
234: private TokenContext[] contexts;
235:
236: ArrayMatcher(TokenContext[] contexts) {
237: this .contexts = contexts;
238: }
239:
240: public int hashCode() {
241: int h = hash;
242: if (h == 0) {
243: for (int i = contexts.length - 1; i >= 0; i--) {
244: h = h * 31 + contexts[i].hashCode(); // !!!
245: }
246: hash = h;
247: }
248:
249: return h;
250: }
251:
252: public boolean equals(Object o) {
253: if (this == o) {
254: return true;
255: }
256:
257: if (o instanceof ArrayMatcher) {
258: ArrayMatcher am = (ArrayMatcher) o;
259: if (contexts.length == am.contexts.length) {
260: for (int i = contexts.length - 1; i >= 0; i--) {
261: if (!contexts[i].equals(am.contexts[i])) {
262: return false;
263: }
264: }
265: return true;
266: }
267: }
268:
269: return false;
270: }
271:
272: }
273:
274: }
|