KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > xml > text > completion > SyntaxQueryHelper


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.xml.text.completion;
21
22 import java.util.Enumeration JavaDoc;
23 import javax.swing.text.BadLocationException JavaDoc;
24 import javax.swing.text.Document JavaDoc;
25 import javax.swing.event.DocumentListener JavaDoc;
26 import javax.swing.event.DocumentEvent JavaDoc;
27
28 import org.netbeans.editor.*;
29 import org.netbeans.editor.ext.*;
30 import org.netbeans.modules.xml.api.model.GrammarQuery;
31 import org.netbeans.modules.xml.api.model.HintContext;
32 import org.netbeans.modules.xml.text.syntax.*;
33 import org.netbeans.modules.xml.text.syntax.dom.*;
34 import org.netbeans.modules.xml.text.api.XMLDefaultTokenContext;
35 import org.w3c.dom.Element JavaDoc;
36 import org.w3c.dom.NamedNodeMap JavaDoc;
37 import org.w3c.dom.Node JavaDoc;
38 import org.openide.util.WeakListeners;
39 import org.openide.ErrorManager;
40
41 /**
42  * Helper class used in XMLCompletionQuery and other classes that use grammar
43  * Instances of this class must only be constructed and used from the AWT
44  * dispatch thread, because this implementation is not reentrant (see ctx field).
45  *
46  * @author asgeir@dimonsoftware.com
47  */

48 final class SyntaxQueryHelper {
49     
50     public final static int COMPLETION_TYPE_UNKNOWN = 0;
51     public final static int COMPLETION_TYPE_ATTRIBUTE = 1;
52     public final static int COMPLETION_TYPE_VALUE = 2;
53     public final static int COMPLETION_TYPE_ELEMENT = 3;
54     public final static int COMPLETION_TYPE_ENTITY = 4;
55     public final static int COMPLETION_TYPE_NOTATION = 5;
56     public final static int COMPLETION_TYPE_DTD = 6;
57
58     /** Currect oken or previous one if at token boundary */
59     private TokenItem token = null;
60     
61     private String JavaDoc preText = "";
62     
63     private int erase = 0;
64     
65     private int tunedOffset = 0;
66     
67     private SyntaxElement element;
68     
69     private int completionType = 0;
70     
71     private boolean tokenBoundary;
72
73     private DefaultContext ctx = new DefaultContext();
74
75     /** Creates a new instance of SyntaxQueryHelper */
76     public SyntaxQueryHelper(XMLSyntaxSupport sup, int offset) throws BadLocationException JavaDoc, IllegalStateException JavaDoc {
77         tunedOffset = offset;
78         token = sup.getPreviousToken( tunedOffset);
79         if( token != null ) { // inside document
80
tokenBoundary = token.getOffset() + token.getImage().length() == tunedOffset;
81         } else {
82             //??? start of document no choice now, but should be prolog if not followed by it
83
throw new BadLocationException JavaDoc("No token found at current position", offset); // NOI18N
84
}
85
86         // find out last typed chars that can hint
87

88         int itemOffset = token.getOffset();
89         preText = "";
90         erase = 0;
91         int eraseRight = 0;
92         int id = token.getTokenID().getNumericID();
93
94         // determine last typed text, prefix text
95

96         if ( tokenBoundary == false ) {
97
98             preText = token.getImage().substring( 0, tunedOffset - token.getOffset() );
99             if ("".equals(preText)) throw new IllegalStateException JavaDoc("Cannot get token prefix at " + tunedOffset);
100
101             // manipulate tunedOffset to delete rest of an old name
102
// for cases where it iseasy to locate original name end
103

104             if (sup.lastTypedChar() != '<' && sup.lastTypedChar() != '&') {
105                 switch (id) {
106
107                     case XMLDefaultTokenContext.TAG_ID:
108                     case XMLDefaultTokenContext.CHARACTER_ID:
109                     case XMLDefaultTokenContext.ARGUMENT_ID:
110
111                         int i = token.getImage().length();
112                         int tail = i - (tunedOffset - itemOffset);
113                         tunedOffset += tail;
114                         eraseRight = tail;
115                         break;
116                 }
117             }
118          } else {
119            switch (id) {
120                 case XMLDefaultTokenContext.TEXT_ID:
121                 case XMLDefaultTokenContext.TAG_ID:
122                 case XMLDefaultTokenContext.ARGUMENT_ID:
123                 case XMLDefaultTokenContext.CHARACTER_ID:
124                 case XMLCompletionQuery.PI_CONTENT_ID:
125                     preText = token.getImage();
126                     break;
127             }
128          }
129
130         // adjust how much do you want to erase from the preText
131

132         switch (id) {
133             case XMLDefaultTokenContext.TAG_ID:
134                 // do not erase start delimiters
135
erase = preText.length() - 1 + eraseRight;
136                 break;
137             case XMLDefaultTokenContext.CHARACTER_ID:
138                 //entity references
139
erase = preText.length() + -1 + eraseRight;
140                 //Issue #63757: test whether the text above CC offset is a entity reference - if not, do not owerwrite it
141
String JavaDoc tokenImage = token.getImage().substring(1); //cutoff the leading &
142
if(tokenImage.endsWith(";")) tokenImage = tokenImage.substring(0, tokenImage.length() - 1); //cutoff the ending ;
143
GrammarQuery gq = XMLCompletionQuery.getPerformer(sup.getDocument(), sup);
144                 if(gq != null) {
145                     Enumeration JavaDoc ents = gq.queryEntities(tokenImage);
146                     if(!ents.hasMoreElements()) {
147                         erase = preText.length() - 1;
148                         tunedOffset -= token.getImage().length() - preText.length();
149                     }
150                 }
151                 break;
152             case XMLDefaultTokenContext.ARGUMENT_ID:
153                 erase = preText.length() + eraseRight;
154                 break;
155             case XMLDefaultTokenContext.VALUE_ID:
156                 erase = preText.length();
157                 if (erase > 0 && (preText.charAt(0) == '\'' || preText.charAt(0) == '"')) {
158                     // Because of attribute values, preText is adjusted in initContext
159
erase--;
160                 } else
161                 break;
162         }
163
164         element = sup.getElementChain( tunedOffset);
165         
166         if (element == null) throw new IllegalStateException JavaDoc("There exists a token therefore a syntax element must exist at " + offset + ", too.");
167
168         // completion request originates from area covered by DOM,
169
if (element instanceof SyntaxNode && ((SyntaxNode)element).getNodeType() != Node.DOCUMENT_TYPE_NODE) {
170             completionType = initContext();
171         } else {
172             // prolog, internal DTD no completition yet
173
completionType = COMPLETION_TYPE_DTD;
174         }
175     }
176     
177     /**
178      * Find out what to complete: attribute, value, element, entity or notation?
179      * <p>
180      * <pre>
181      * Triggering criteria:
182      *
183      * ELEMENT TOKEN (,=seq) PRETEXT QUERY
184      * -------------------------------------------------------------------
185      * Text text &lt; element name
186      * Text text &lt;/ pairing end element
187      * StartTag tag &lt;prefix element name
188      * StartTag ws attribute name
189      * StartTag attr, operator = quoted attribute value
190      * StartTag value 'prefix attribute value
191      * StartTag tag > element value
192      * Text text &amp; entity ref name
193      * StartTag value &amp; entity ref name
194      * </pre>
195      *
196      * @return the type of completion which is one of
197      * COMPLETION_TYPE_UNKNOWN = 0,
198      * COMPLETION_TYPE_ATTRIBUTE = 1,
199      * COMPLETION_TYPE_VALUE = 2,
200      * COMPLETION_TYPE_ELEMENT = 3,
201      * COMPLETION_TYPE_ENTITY = 4,
202      * COMPLETION_TYPE_NOTATION = 5.
203      */

204     private int initContext() {
205         int id = token.getTokenID().getNumericID();
206         SyntaxNode syntaxNode = (SyntaxNode)element;
207
208         switch ( id) {
209             case XMLDefaultTokenContext.TEXT_ID:
210                 if ( preText.endsWith("<" )) {
211                     ctx.init(syntaxNode, "");
212                     return COMPLETION_TYPE_ELEMENT;
213                 } else if ( preText.startsWith("&")) {
214                     ctx.init(syntaxNode, preText.substring(1));
215                     return COMPLETION_TYPE_ENTITY;
216                 } else {
217                     //??? join all previous texts?
218
// No they are DOM nodes.
219
ctx.init(syntaxNode, preText);
220                     return COMPLETION_TYPE_VALUE;
221                 }
222 // break;
223

224             case XMLDefaultTokenContext.TAG_ID:
225                 if ( StartTag.class.equals(syntaxNode.getClass())
226                 || EmptyTag.class.equals(syntaxNode.getClass())) {
227                     if (preText.equals("")) {
228                         //??? should not occure
229
if (token.getImage().endsWith(">")) {
230                             ctx.init(syntaxNode, preText);
231                             return COMPLETION_TYPE_VALUE;
232                         } else {
233                             ctx.init(syntaxNode, preText);
234                             return COMPLETION_TYPE_ELEMENT;
235                         }
236                     } else if (preText.endsWith("/>")) {
237                         ctx.init(syntaxNode, "");
238                         return COMPLETION_TYPE_VALUE;
239                     } else if (preText.endsWith(">")) {
240                         ctx.init(syntaxNode, "");
241                         return COMPLETION_TYPE_VALUE;
242                     } else if (preText.startsWith("</")) {
243                         //??? replace immediatelly?
244
ctx.init(syntaxNode, preText.substring(2));
245                         return COMPLETION_TYPE_ELEMENT;
246                     } else if (preText.startsWith("<")) {
247                         ctx.init(syntaxNode, preText.substring(1));
248                         return COMPLETION_TYPE_ELEMENT;
249                     }
250                 } else if(EndTag.class.equals(syntaxNode.getClass()) && preText.startsWith("</")){
251                     //endtag
252
ctx.init(syntaxNode, preText.substring(2));
253                     return COMPLETION_TYPE_ELEMENT;
254                 } else {
255                     // pairing tag completion if not at boundary
256
if ("".equals(preText) && token.getImage().endsWith(">")) {
257                         ctx.init(syntaxNode, preText);
258                         return COMPLETION_TYPE_VALUE;
259                     }
260                 }
261                 break;
262                 
263             case XMLDefaultTokenContext.VALUE_ID:
264                 if (preText.endsWith("&")) {
265                     ctx.init(syntaxNode, "");
266                     return COMPLETION_TYPE_ENTITY;
267                 } else if ("".equals(preText)) { //??? improve check to addres inner '"'
268
String JavaDoc image = token.getImage();
269                     char ch = image.charAt(image.length()-1);
270                     
271                     // findout if it is closing '
272

273                     if (ch == '\'' || ch == '"') {
274                         
275                         if (image.charAt(0) == ch && image.length() > 1) {
276                             // we got whole quoted value as single token ("xxx"|)
277
return COMPLETION_TYPE_UNKNOWN;
278                         }
279
280                         boolean closing = false;
281                         TokenItem prev = token.getPrevious();
282
283                         while (prev != null) {
284                             int tid = prev.getTokenID().getNumericID();
285                             if (tid == XMLDefaultTokenContext.VALUE_ID) {
286                                 closing = true;
287                                 break;
288                             } else if (tid == XMLDefaultTokenContext.CHARACTER_ID) {
289                                 prev = prev.getPrevious();
290                             } else {
291                                 break;
292                             }
293                         }
294                         if (closing == false) {
295                             ctx.init(syntaxNode, preText);
296                             return COMPLETION_TYPE_VALUE;
297                         }
298                     } else {
299                         ctx.init(syntaxNode, preText);
300                         return COMPLETION_TYPE_VALUE;
301                     }
302                 } else {
303                     // This is probably an attribute value
304
// Let's find the matching attribute node and use it to initialize the context
305
NamedNodeMap JavaDoc attrs = syntaxNode.getAttributes();
306                     int maxOffsetLessThanCurrent = -1;
307                     Node JavaDoc curAttrNode = null;
308                     for (int ind = 0; ind < attrs.getLength(); ind++) {
309                         AttrImpl attr = (AttrImpl)attrs.item(ind);
310                         int attrTokOffset = attr.getFirstToken().getOffset();
311                         if (attrTokOffset > maxOffsetLessThanCurrent && attrTokOffset < token.getOffset()) {
312                             maxOffsetLessThanCurrent = attrTokOffset;
313                             curAttrNode = attr;
314                         }
315                     }
316
317                     // eliminate "'",'"' delimiters
318
if (preText.length() > 0) {
319                         preText = preText.substring(1);
320                     }
321                     if (curAttrNode != null) {
322                         ctx.init(curAttrNode, preText);
323                     } else {
324                         ctx.init(syntaxNode, preText);
325                     }
326                     return COMPLETION_TYPE_VALUE;
327                 }
328                 break;
329                 
330             case XMLDefaultTokenContext.OPERATOR_ID:
331                 if ("".equals(preText)) {
332                     if ("=".equals(token.getImage())) {
333                         ctx.init(syntaxNode, "");
334                         return COMPLETION_TYPE_VALUE;
335                     }
336                 }
337                 break;
338
339             case XMLDefaultTokenContext.WS_ID:
340                 if ((StartTag.class.equals(syntaxNode.getClass())
341                  || EmptyTag.class.equals(syntaxNode.getClass()))
342                  && !token.getImage().startsWith("/")) {
343                     ctx.init((Element JavaDoc)syntaxNode, ""); // GrammarQuery.v2 takes Element ctx
344
return COMPLETION_TYPE_ATTRIBUTE;
345                 } else {
346                     // end tag no attributes to complete
347
return COMPLETION_TYPE_UNKNOWN;
348                 }
349 // break;
350

351             case XMLDefaultTokenContext.ARGUMENT_ID:
352                 if (StartTag.class.equals(syntaxNode.getClass())
353                 || EmptyTag.class.equals(syntaxNode.getClass())) {
354                     //try to find the current attribute
355
Tag tag = (Tag)syntaxNode;
356                     NamedNodeMap JavaDoc nnm = tag.getAttributes();
357                     for(int i = 0; i < nnm.getLength(); i++) {
358                         AttrImpl attrNode = (AttrImpl)nnm.item(i);
359                         if(attrNode.getFirstToken().getOffset() == token.getOffset()) {
360                             ctx.init(attrNode, preText);
361                         }
362                     }
363                     if(!ctx.isInitialized()) {
364                         ctx.init((Element JavaDoc)syntaxNode, preText); // GrammarQuery.v2 takes Element ctx
365
}
366                     return COMPLETION_TYPE_ATTRIBUTE;
367                 }
368                 break;
369                 
370             case XMLDefaultTokenContext.CHARACTER_ID: // entity reference
371
if (preText.startsWith("&#")) {
372                     // character ref, ignore
373
return COMPLETION_TYPE_UNKNOWN;
374                 } else if (preText.endsWith(";")) {
375                         ctx.init(syntaxNode, "");
376                         return COMPLETION_TYPE_VALUE;
377                 } else if (preText.startsWith("&")) {
378                     ctx.init(syntaxNode, preText.substring(1));
379                     return COMPLETION_TYPE_ENTITY;
380                 } else if ("".equals(preText)) {
381                     if (token.getImage().endsWith(";")) {
382                         ctx.init(syntaxNode, preText);
383                         return COMPLETION_TYPE_VALUE;
384                     }
385                 }
386                 break;
387                 
388             default:
389
390         }
391         
392 // System.err.println("Cannot complete: " + syntaxNode + "\n\t" + token + "\n\t" + preText);
393
return COMPLETION_TYPE_UNKNOWN;
394     }
395     
396     public HintContext getContext() {
397         if (completionType != COMPLETION_TYPE_UNKNOWN && completionType != COMPLETION_TYPE_DTD) {
398             return ctx;
399         } else {
400             return null;
401         }
402     }
403
404     /** Current token or previous one if at token boundary. */
405     public TokenItem getToken() {
406         return token;
407     }
408     
409     public String JavaDoc getPreText() {
410         return preText;
411     }
412     
413     public int getEraseCount() {
414         return erase;
415     }
416     
417     public int getOffset() {
418         return tunedOffset;
419     }
420     
421     public SyntaxElement getSyntaxElement() {
422         return element;
423     }
424     
425     public int getCompletionType() {
426         return completionType;
427     }
428
429     /** token boundary */
430     public boolean isBoundary() {
431         return tokenBoundary;
432     }
433
434
435 }
436
Popular Tags