KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > web > core > syntax > JspSyntaxSupport


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.web.core.syntax;
21
22 import java.io.File JavaDoc;
23 import java.util.*;
24 import java.net.URL JavaDoc;
25 import javax.swing.text.BadLocationException JavaDoc;
26 import javax.swing.text.EditorKit JavaDoc;
27 import javax.swing.JEditorPane JavaDoc;
28 import javax.swing.text.JTextComponent JavaDoc;
29 import javax.servlet.jsp.tagext.*;
30 import org.netbeans.api.lexer.Language;
31 import org.netbeans.api.lexer.TokenHierarchy;
32 import org.netbeans.api.lexer.TokenSequence;
33 import org.netbeans.editor.ext.html.HTMLCompletionQuery;
34 import org.netbeans.modules.web.core.syntax.deprecated.ELTokenContext;
35 import org.netbeans.modules.web.core.syntax.deprecated.JspDirectiveTokenContext;
36 import org.netbeans.modules.web.core.syntax.deprecated.JspMultiTokenContext;
37 import org.netbeans.modules.web.core.syntax.deprecated.JspTagTokenContext;
38 import org.openide.filesystems.FileObject;
39 import org.openide.ErrorManager;
40 import org.openide.loaders.DataObject;
41 import org.netbeans.modules.web.jsps.parserapi.JspParserAPI;
42 import org.netbeans.modules.web.jsps.parserapi.PageInfo;
43 import org.netbeans.editor.*;
44 import org.netbeans.editor.ext.ExtSyntaxSupport;
45 import org.netbeans.editor.ext.html.HTMLTokenContext;
46 import org.netbeans.editor.ext.java.JavaTokenContext;
47 import org.netbeans.modules.editor.NbEditorUtilities;
48 import org.openide.filesystems.FileUtil;
49 import org.openide.modules.InstalledFileLocator;
50 import org.netbeans.modules.web.core.syntax.completion.JspCompletionItem;
51 import org.openide.text.CloneableEditorSupport;
52
53 /**
54  *
55  * @author Petr Jiricka, Petr Nejedly
56  * @author Marek.Fukala@Sun.COM
57  */

58 public class JspSyntaxSupport extends ExtSyntaxSupport {
59     
60     /** ErrorManager shared by whole module (package) for logging */
61     static final ErrorManager err =
62             ErrorManager.getDefault().getInstance("org.netbeans.modules.web.jspsyntax"); // NOI18N
63

64     /* Constants for various contexts in the text from the point of
65     view of JSP completion.*/

66     
67     /** Completion context for JSP tags (standard or custom) */
68     public static final int TAG_COMPLETION_CONTEXT = 1;
69     /** Completion context for JSP end tags (standard or custom) */
70     public static final int ENDTAG_COMPLETION_CONTEXT = 2;
71     /** Completion context for JSP directives */
72     public static final int DIRECTIVE_COMPLETION_CONTEXT = 3;
73     /** Completion context for JSP comments */
74     public static final int COMMENT_COMPLETION_CONTEXT = 4;
75     /** Completion context for other JSP text - such as body of custom tags
76      * with TAG_DEPENDENT body content. */

77     public static final int TEXT_COMPLETION_CONTEXT = 5;
78     /** Completion context for the content language */
79     public static final int CONTENTL_COMPLETION_CONTEXT = 6;
80     /** Completion context for the scripting language */
81     public static final int SCRIPTINGL_COMPLETION_CONTEXT = 7;
82     /** Completion context for error */
83     public static final int ERROR_COMPLETION_CONTEXT = 8;
84     /** Completion context for expression language */
85     public static final int EL_COMPLETION_CONTEXT = 9;
86     
87     
88     
89     private static final String JavaDoc STANDARD_JSP_PREFIX = "jsp"; // NOI18N
90
/** Data for completion: TreeMap for standard JSP tags
91      * (tag name, array of attributes). */

92     private static TagInfo[] standardJspTagDatas;
93     
94     private static TagInfo[] standardTagTagDatas;
95     /** Data for completion, when the jsp page is in XML syntax
96      **/

97     private static TagInfo[] xmlJspTagDatas;
98     
99     /** Data for completion, when the tag file is in XML syntax
100      **/

101     private static TagInfo[] xmlTagFileTagDatas;
102     
103     /** Data for completion: TreeMap for JSP directives
104      * (directive name, array of attributes). */

105     private static TagInfo[] directiveJspData;
106     private static TagInfo[] directiveTagFileData;
107     
108     /** Mapping the URI of tag library -> URL where the help files are.
109      */

110     private static HashMap helpMap = null;
111     
112     private static final TokenID[] JSP_BRACKET_SKIP_TOKENS = new TokenID[] {
113         JavaTokenContext.LINE_COMMENT,
114         JavaTokenContext.BLOCK_COMMENT,
115         JavaTokenContext.CHAR_LITERAL,
116         JavaTokenContext.STRING_LITERAL,
117         JspTagTokenContext.ATTR_VALUE,
118         JspTagTokenContext.COMMENT
119     };
120     
121     protected FileObject fobj;
122     
123     /** Content language SyntaxSupport cached for getContentLanguageSyntaxSupport */
124     private ExtSyntaxSupport contentLanguageSyntaxSupport = null;
125     
126     /** Special bracket finder is used when caret is in JSP context */
127     private boolean useCustomBracketFinder = true;
128     
129     private boolean isXmlSyntax = false;
130     
131     private static final TagNameComparator TAG_NAME_COMPARATOR = new TagNameComparator();
132     
133     /** Creates new HTMLSyntaxSupport */
134     
135     public JspSyntaxSupport(BaseDocument doc, boolean isXml) {
136         super(doc);
137         fobj = null;
138         if (doc != null){
139             DataObject dobj = NbEditorUtilities.getDataObject(doc);
140             fobj = (dobj != null) ? NbEditorUtilities.getDataObject(doc).getPrimaryFile(): null;
141         }
142         
143         isXmlSyntax = isXml;
144     }
145     
146     public JspSyntaxSupport(BaseDocument doc) {
147         this(doc, false);
148     }
149     
150     public String JavaDoc[] getImports(){
151         JspParserAPI.ParseResult pre = getParseResult();
152         if (pre != null){
153             PageInfo pi = pre.getPageInfo();
154             if(pi == null) {
155                 //report error but do not break the entire CC
156
ErrorManager.getDefault().notify(ErrorManager.WARNING, new NullPointerException JavaDoc("PageInfo obtained from JspParserAPI.ParseResult is null!"));
157                 return null;
158             }
159             List<String JavaDoc> imports = pi.getImports();
160             return imports.toArray(new String JavaDoc[imports.size()]);
161         }
162         
163         return null;
164     }
165     
166     public boolean isXmlSyntax(){
167         return isXmlSyntax;
168     }
169     
170     protected JspParserAPI.ParseResult getParseResult() {
171         JspParserAPI.ParseResult result = JspUtils.getCachedParseResult(getDocument(), fobj, true, false);
172         if (result == null) {
173             result = JspUtils.getCachedParseResult(getDocument(), fobj, false, false);
174         }
175         return result;
176     }
177     
178     /** Returns a map of prefix -> URI that maps tag libraries on prefixes.
179      * For the XML syntax this mapping may only be approximate.
180      */

181     private Map getPrefixMapper() {
182         // PENDING - must also take xmlPrefixMapper into account
183
JspParserAPI.ParseResult result = getParseResult();
184         Map prefixMapper = null;
185         if (result != null && result.getPageInfo() != null) {
186             //if (result.isParsingSuccess()) {
187
// PENDING - can we somehow get incomplete parsed information ?
188
if (result.getPageInfo().getXMLPrefixMapper().size() > 0) {
189                 prefixMapper = result.getPageInfo().getApproxXmlPrefixMapper();
190                 if (prefixMapper.size() == 0){
191                     prefixMapper = result.getPageInfo().getXMLPrefixMapper();
192                 }
193                 prefixMapper.putAll(result.getPageInfo().getJspPrefixMapper());
194                 
195             } else {
196                 prefixMapper = result.getPageInfo().getJspPrefixMapper();
197             }
198             //}
199
}
200         return prefixMapper;
201     }
202     
203     private Map getTagLibraries() {
204         //refresh tag libraries mappings - this call causes the WebAppParseSupport to refresh taglibs mapping
205
getTagLibraryMappings();
206         //force the parser to update the parse information for the file
207
JspParserAPI.ParseResult result = JspUtils.getCachedParseResult(getDocument(), fobj, false, true, true);
208         if (result != null) return result.getPageInfo().getTagLibraries();
209         
210         return null; //an error
211
}
212     
213     private TagInfo[] getSortedTagInfos(TagInfo[] tinfos) {
214         Arrays.sort(tinfos, new Comparator() {
215             public int compare(Object JavaDoc o1, Object JavaDoc o2) {
216                 TagInfo ti1 = (TagInfo)o1;
217                 TagInfo ti2 = (TagInfo)o2;
218                 String JavaDoc tname1 = (ti1.getDisplayName() == null ? ti1.getTagName() : ti1.getDisplayName());
219                 String JavaDoc tname2 = (ti2.getDisplayName() == null ? ti2.getTagName() : ti2.getDisplayName());
220                 if(tname1 == null || tname2 == null) return 0;
221                 return tname1.compareTo(tname2);
222             }
223             public boolean equals(Object JavaDoc o) {
224                 return o.equals(this);
225             }
226         });
227         return tinfos;
228     }
229     
230     private TagLibraryInfo getTagLibrary(String JavaDoc prefix) {
231         Map mapper = getPrefixMapper();
232         if (mapper != null) {
233             Object JavaDoc uri = mapper.get(prefix);
234             if (uri != null) {
235                 Map taglibs = getTagLibraries();
236                 if (taglibs != null) {
237                     return (TagLibraryInfo)taglibs.get(uri);
238                 }
239             }
240         }
241         return null;
242     }
243     
244     protected SyntaxSupport createSyntaxSupport(Class JavaDoc syntaxSupportClass) {
245         // if (syntaxSupportClass.isAssignableFrom(JspJavaSyntaxSupport.class)) {
246
// return new JspJavaSyntaxSupport(getDocument(), this);
247
// }
248
SyntaxSupport support = super.createSyntaxSupport(syntaxSupportClass);
249         if (support != null)
250             return support;
251         //System.out.println("JspSyntaxSupport- createSyntaxSupport - " + NbEditorUtilities.getMimeType(getDocument()) );
252

253         EditorKit JavaDoc kit;
254         // try the content language support
255
kit = CloneableEditorSupport.getEditorKit(JspUtils.getContentLanguage());
256         if (kit instanceof BaseKit) {
257             support = ((BaseKit)kit).createSyntaxSupport(getDocument());
258             if (support != null)
259                 return support;
260         }
261         // try the scripting language support
262
kit = CloneableEditorSupport.getEditorKit(JspUtils.getScriptingLanguage());
263         if (kit instanceof BaseKit) {
264             support = ((BaseKit)kit).createSyntaxSupport(getDocument());
265             if (support != null)
266                 return support;
267         }
268         return null;
269     }
270     
271     /** Returns SyntaxSupport corresponding to content type of JSP data object.
272      * HTMLSyntaxSupport is used when we can't find it. */

273     protected ExtSyntaxSupport getContentLanguageSyntaxSupport() {
274         if (contentLanguageSyntaxSupport != null) {
275             return contentLanguageSyntaxSupport;
276         }
277         
278         EditorKit JavaDoc kit =
279                 JEditorPane.createEditorKitForContentType(JspUtils.getContentLanguage());
280         if (kit instanceof BaseKit) {
281             SyntaxSupport support = ((BaseKit)kit).createSyntaxSupport(getDocument());
282             if (support != null && support instanceof ExtSyntaxSupport) {
283                 contentLanguageSyntaxSupport = (ExtSyntaxSupport) support;
284                 return contentLanguageSyntaxSupport;
285             }
286         }
287         return (ExtSyntaxSupport)get( org.netbeans.editor.ext.html.HTMLSyntaxSupport.class );
288     }
289     
290     /** This method decides what kind of completion (html, java, jsp-tag, ...) should be opened
291      * or whether the completion window should be closed if it is opened.
292      */

293     public int checkCompletion(JTextComponent JavaDoc target, String JavaDoc typedText, boolean visible ) {
294         
295         char first = typedText.charAt(0); //get typed char
296

297         TokenItem item = null; //get token on the cursor
298
try{
299             item = getItemAtOrBefore(target.getCaret().getDot());
300         }catch(BadLocationException JavaDoc e) {
301             return COMPLETION_HIDE;
302         }
303         if (item == null) return COMPLETION_HIDE;
304         
305         TokenContextPath tcp = item.getTokenContextPath();
306         
307         //System.out.println("typed '" + first + "' ;token = " + item);
308

309         if(tcp.contains(HTMLTokenContext.contextPath)) {
310             //we are in content language
311
if (err.isLoggable(ErrorManager.INFORMATIONAL)) err.log("CONTENTL_COMPLETION_CONTEXT"); // NOI18N
312
ExtSyntaxSupport support = getContentLanguageSyntaxSupport();
313             if (support != null) {
314                 return support.checkCompletion( target, typedText, visible );
315             }
316         }
317         
318         if(tcp.contains(JavaTokenContext.contextPath)) {
319             return COMPLETION_CANCEL; //the JavaCompletionProvider handles this
320
}
321         
322         //JSP tag or directive
323
if(tcp.contains(JspTagTokenContext.contextPath)) {
324             //need to distinguish tag/end_tag/directive - search back throught the token chain for <%@ , </ or < tokens
325
TokenItem tracking = item;
326             
327             //the maxBacktrace number says how far back we are willing to look for
328
//the start tag tokens <%, </ and < (simply the JSP tag or directive beginning).
329
//There may happen a situation when user starts to write a tag that the a large
330
//part of the document behind the cursor is recognized as a jsp tag.
331
//In such a case typing somewhere in the end of this 'badly' recognized block
332
//would slow down the typing rapidy due to the backtracking oven a long token chain.
333
//
334
//So we suppose that tag has less than 20 attributes (and one attribute-value pair consumes 4 tokens)
335
int maxBacktrace = 20 * 4;
336             
337             do {
338                 //test whether the token is not an error token
339
if(tracking.getTokenID() == JspTagTokenContext.ERROR) return COMPLETION_HIDE;
340                 
341                 String JavaDoc image = tracking.getImage();
342                 //System.out.println("tracking: " + tracking);
343

344                 //this is a case when use types %> which is recognized as a JspTagToken, but before that there are java tokens
345
if(image.equals("%>")) return COMPLETION_HIDE;
346                 
347                 if(tracking.getImage().startsWith("<%")) {
348                     //we are in a directive
349
if (err.isLoggable(ErrorManager.INFORMATIONAL)) err.log("DIRECTIVE_COMPLETION_CONTEXT"); // NOI18N
350

351                     //open completion also in such a case: <%=|
352
if( !visible && first == '=' && tracking.getImage().equals("<%")) return COMPLETION_POPUP;
353                     
354                     if( !visible && first == '%' || first == '@' || first == ' ' ) return COMPLETION_POPUP;
355                     if( visible && first == '=' || first == '>' ) return COMPLETION_HIDE;
356                     return visible ? COMPLETION_POST_REFRESH : COMPLETION_CANCEL;
357                 }
358                 if(tracking.getImage().equals("<")) {
359                     //we are in a tag
360
if (err.isLoggable(ErrorManager.INFORMATIONAL)) err.log("TAG_COMPLETION_CONTEXT"); // NOI18N
361
if( !visible && first == ' ' || first == ':' ) return COMPLETION_POPUP;
362                     if( visible && first == '>' ) return COMPLETION_HIDE;
363                     return visible ? COMPLETION_POST_REFRESH : COMPLETION_CANCEL;
364                 }
365                 if(tracking.getImage().equals("</")) {
366                     //we are in an end tag
367
if (err.isLoggable(ErrorManager.INFORMATIONAL)) err.log("ENDTAG_COMPLETION_CONTEXT" ); // NOI18N
368
if( visible && first == '>' ) return COMPLETION_HIDE;
369                     return visible ? COMPLETION_POST_REFRESH : COMPLETION_CANCEL;
370                 }
371                 //test whether we are still in the tag context
372
if(!tracking.getTokenContextPath().contains(JspTagTokenContext.contextPath)) {
373                     if (err.isLoggable(ErrorManager.INFORMATIONAL)) err.log("We are out of jsp tag without finding any tag start token!");
374                     break;
375                 }
376                 
377                 if(tracking.getImage().equals(">")) {
378                     try {
379                         //check if the cursor is behind an open tag
380
SyntaxElement se = getElementChain(tracking.getOffset());
381                         if(se != null && (se instanceof SyntaxElement.Tag)) {
382                             return COMPLETION_POPUP;
383                         }
384                     }catch(BadLocationException JavaDoc e) {
385                         //do nothing
386
}
387                     return COMPLETION_HIDE;
388                 }
389                 
390                 //search previous token
391
tracking = tracking.getPrevious();
392                 
393             } while((maxBacktrace-- > 0) && (tracking != null));
394             
395         }//eof JSP tag
396

397         if(tcp.contains(ELTokenContext.contextPath)) {
398             //we are in expression language - we do not provide any code completion so far
399
if (visible) return COMPLETION_HIDE;
400         }
401         
402         return COMPLETION_HIDE;
403     }
404     
405     /** Returns offset where the next offset after this offset starts. */
406     private final int getTokenEnd( TokenItem item ) {
407         if (item == null)
408             return 0; //getDocument().getLength();
409
return item.getOffset() + item.getImage().length();
410     }
411     
412     /** Filters list of strings so only strings starting
413      * with a given prefix are returned in the new List. */

414     public final List filterList(List toFilter, String JavaDoc prefix) {
415         List newList = new ArrayList();
416         Object JavaDoc item;
417         for (int i = 0; i < toFilter.size(); i++) {
418             item = toFilter.get(i);
419             String JavaDoc txt;
420             if (item instanceof TagInfo)
421                 txt = ((TagInfo)item).getTagName();
422             else if (item instanceof TagAttributeInfo)
423                 txt = ((TagAttributeInfo)item).getName();
424             else
425                 txt = (String JavaDoc)item;
426             
427             if (txt != null && txt.startsWith(prefix)) {
428                 newList.add(item);
429             }
430         }
431         return newList;
432     }
433     
434     /** Gets all 'jsp prefixes' whose 'string prefix' matches complPrefix as a list of Strings. */
435     public final List getTagPrefixes(String JavaDoc complPrefix) {
436         return filterList(getAllTagPrefixes(), complPrefix);
437     }
438     
439     /** Gets all tags whose 'string prefix' matches complPrefix as a list of Strings.
440      * Assumes that complPrefix also includes the 'jsp prefix'.
441      */

442     public final List getTags(String JavaDoc complPrefix) {
443         int colonIndex = complPrefix.indexOf(":"); // NOI18N
444
if (colonIndex == -1)
445             throw new IllegalArgumentException JavaDoc();
446         return getTags(complPrefix.substring(0, colonIndex),
447                 complPrefix.substring(colonIndex + 1));
448     }
449     
450     /** Gets all tags whose 'string prefix' matches complPrefix and whose 'jsp prefix'
451      * is tagPrefix as a list of Strings.
452      * Assumes that complPrefix does not include the 'jsp prefix'.
453      */

454     public final List getTags(String JavaDoc tagPrefix, String JavaDoc complPrefix) {
455         return filterList(getAllTags(tagPrefix), complPrefix);
456     }
457     
458     /** Gets attributes for tag whose prefix + name
459      * is tagPrefixName as a list of Strings.
460      * The attribute's 'string prefix' must match complPrefix.
461      */

462     public final List getTagAttributes(String JavaDoc tagPrefixName, String JavaDoc complPrefix) {
463         int colonIndex = tagPrefixName.indexOf(":"); // NOI18N
464
if (colonIndex == -1)
465             throw new IllegalArgumentException JavaDoc();
466         return getTagAttributes(tagPrefixName.substring(0, colonIndex),
467                 tagPrefixName.substring(colonIndex + 1), complPrefix);
468     }
469     
470     /** Gets attributes for tag whose 'jsp prefix'
471      * is tagPrefix and whose tag name is tagName as a list of Strings.
472      * The attribute's 'string prefix' must match complPrefix.
473      */

474     protected final List getTagAttributes(String JavaDoc tagPrefix, String JavaDoc tagName, String JavaDoc complPrefix) {
475         return filterList(getAllTagAttributes(tagPrefix, tagName), complPrefix);
476     }
477     
478     /** Gets all directives whose 'string prefix' matches complPrefix as a list of Strings. */
479     public final List getDirectives(String JavaDoc complPrefix) {
480         return filterList(getAllDirectives(), complPrefix);
481     }
482     
483     /** Gets attributes for directive <code>directive</code> as a list of Strings.
484      * The attribute's 'string prefix' must match complPrefix. */

485     public final List getDirectiveAttributes(String JavaDoc directive, String JavaDoc complPrefix) {
486         return filterList(getAllDirectiveAttributes(directive), complPrefix);
487     }
488     
489     /**
490      * Returns a list of strings - prefixes available in this support context (JSP file).
491      */

492     protected List getAllTagPrefixes() {
493         List items = new ArrayList();
494         
495         // jsp: prefix
496
items.add(STANDARD_JSP_PREFIX);
497         
498         Map mapper = getPrefixMapper();
499         if (mapper != null) {
500             // sort it
501
TreeSet ts = new TreeSet();
502             ts.addAll(mapper.keySet());
503             ts.remove("jsp"); // remove jsp prefix when is declared in xml syntax
504
items.addAll(ts);
505         }
506         // prefixes for tag libraries
507
/* TagLibParseSupport support = (dobj == null) ?
508             null : (TagLibParseSupport)dobj.getCookie(TagLibParseSupport.class);
509         if (support != null) {
510             // add all prefixes from the support
511             TagLibParseSupport.TagLibData[] tagLibData = support.getTagLibEditorData().getTagLibData();
512             for (int i = 0; i < tagLibData.length; i++)
513                 items.add(tagLibData[i].getPrefix());
514         }
515  */

516         return items;
517     }
518     
519     /** Returns a list of strings - tag names available for a particular prefix.
520      */

521     protected List getAllTags(String JavaDoc prefix) {
522         List items = new ArrayList();
523         
524         // standard JSP tags (jsp:)
525
initCompletionData();
526         if (STANDARD_JSP_PREFIX.equals(prefix)) {
527             TagInfo[] stanTagDatas = getTagInfos();
528             for (int i=0; i<stanTagDatas.length; i++) {
529                 items.add(stanTagDatas[i]);
530             }
531         }
532         
533         TagLibraryInfo info = getTagLibrary(prefix);
534         if (info != null && info.getTags() != null) {
535             TagInfo[] tags = getSortedTagInfos(info.getTags());
536             String JavaDoc url = (String JavaDoc)helpMap.get(info.getURI());
537             if (url != null && !url.equals("")){
538                 for (int i = 0; i < tags.length; i++) {
539                     items.add(new TagInfo(tags[i].getTagName(),
540                             tags[i].getTagClassName(), tags[i].getBodyContent(),
541                             url + tags[i].getTagName() + ".html#tag-start-" + tags[i].getTagName()
542                             + "#tag-end-" + tags[i].getTagName(), info,
543                             tags[i].getTagExtraInfo(), tags[i].getAttributes(),
544                             tags[i].getDisplayName(), tags[i].getSmallIcon(), tags[i].getLargeIcon(),
545                             tags[i].getTagVariableInfos(), tags[i].hasDynamicAttributes()));
546                 }
547             } else {
548                 for (int i = 0; i < tags.length; i++) {
549                     items.add(tags[i]);
550                 }
551             }
552         }
553         return items;
554     }
555     
556     /** Should be overriden ny subclasses to support JSP 1.1.
557      * Returns a list of strings - attribute names available for a particular prefix and tag name.
558      */

559     protected List getAllTagAttributes(String JavaDoc prefix, String JavaDoc tag) {
560         List items = new ArrayList();
561         
562         // attributes for standard JSP tags (jsp:)
563
initCompletionData();
564         if (STANDARD_JSP_PREFIX.equals(prefix)) {
565             TagInfo[] stanTagDatas = getTagInfos();
566             for (int i=0; i<stanTagDatas.length; i++) {
567                 if (stanTagDatas[i].getTagName().equals(tag)) {
568                     TagAttributeInfo[] attrs = stanTagDatas[i].getAttributes();
569                     for (int j=0; j<attrs.length; j++)
570                         items.add(attrs[j]);
571                     break;
572                 }
573             }
574         }
575         
576         TagLibraryInfo info = getTagLibrary(prefix);
577         if (info != null) {
578             TagInfo tagInfo = info.getTag(tag);
579             if (tagInfo != null) {
580                 TagAttributeInfo[] attributes = tagInfo.getAttributes();
581                 String JavaDoc url = (String JavaDoc)helpMap.get(tagInfo.getTagLibrary().getURI());
582                 if (url != null && !url.equals("")){
583                     for (int i = 0; i < attributes.length; i++) {
584                         items.add(new TagAttributeInfo(attributes[i].getName(),
585                                 attributes[i].isRequired(),
586                                 url + tagInfo.getTagName() + ".html#attribute-start-" + attributes[i].getName()
587                                 + "#attribute-end-" + attributes[i].getName(),
588                                 attributes[i].canBeRequestTime(),
589                                 attributes[i].isFragment()));
590                     }
591                 } else {
592                     for (int i = 0; i < attributes.length; i++)
593                         items.add(attributes[i]);
594                 }
595             }
596         }
597         return items;
598     }
599     
600     
601     
602     /** Should be overriden ny subclasses to support JSP 1.1. */
603     protected List getAllDirectives() {
604         initCompletionData();
605         List items = new ArrayList();
606         
607         //Is xml syntax? => return nothing.
608
if (isXmlSyntax) return items;
609         
610         TagInfo[] directiveData;
611         if(NbEditorUtilities.getMimeType(getDocument()).equals(JspUtils.TAG_MIME_TYPE))
612             directiveData = directiveTagFileData;
613         else {
614             directiveData = directiveJspData;
615             
616         }
617         for (int i = 0; i < directiveData.length; i++){
618             items.add(directiveData[i]);
619         }
620         return items;
621     }
622     
623     /** Should be overriden ny subclasses to support JSP 1.1. */
624     protected List getAllDirectiveAttributes(String JavaDoc directive) {
625         initCompletionData();
626         List items = new ArrayList();
627         //Is xml syntax? => return nothing.
628
if (isXmlSyntax) return items;
629         
630         TagInfo[] directiveData;
631         if(NbEditorUtilities.getMimeType(getDocument()).equals(JspUtils.TAG_MIME_TYPE))
632             directiveData = directiveTagFileData;
633         else
634             directiveData = directiveJspData;
635         for (int i=0; i<directiveData.length; i++) {
636             if (directiveData[i].getTagName().equals(directive)) {
637                 TagAttributeInfo[] attrs = directiveData[i].getAttributes();
638                 for (int j=0; j<attrs.length; j++)
639                     items.add(attrs[j]);
640                 break;
641             }
642         }
643         return items;
644     }
645     
646     public PageInfo.BeanData[] getBeanData() {
647         JspParserAPI.ParseResult result = getParseResult();
648         if (result != null) {
649             return result.getPageInfo().getBeans();
650         }
651         /*TagLibParseSupport support = (dobj == null) ?
652             null : (TagLibParseSupport)dobj.getCookie(TagLibParseSupport.class);
653         return support.getTagLibEditorData().getBeanData();*/

654         return null;
655     }
656     
657     public boolean isErrorPage() {
658         JspParserAPI.ParseResult result = getParseResult();
659         if (result != null) {
660             if (result.getPageInfo() != null)
661                 return result.getPageInfo().isErrorPage();
662         }
663         /*TagLibParseSupport support = (dobj == null) ?
664             null : (TagLibParseSupport)dobj.getCookie(TagLibParseSupport.class);
665         return support.getTagLibEditorData().isErrorPage ();*/

666         return false;
667     }
668     
669     
670     /**
671      * The mapping of the 'global' tag library URI to the location
672      * (resource path) of the TLD associated with that tag library.
673      * The location is returned as a String array:
674      * [0] The location
675      * [1] If the location is a jar file, this is the location
676      * of the tld.
677      */

678     public Map getTagLibraryMappings() {
679         if (fobj == null) {
680             return null;
681         }
682         return JspUtils.getTaglibMap(getDocument(), fobj);
683     }
684     
685     private static void initHelp(){
686         if (helpMap == null){
687             String JavaDoc url="";
688             File JavaDoc f = InstalledFileLocator.getDefault().locate("docs/jstl11-doc.zip", null, false); //NoI18N
689
if (f != null){
690                 try {
691                     URL JavaDoc urll = f.toURL();
692                     urll = FileUtil.getArchiveRoot(urll);
693                     url = urll.toString();
694                 } catch (java.net.MalformedURLException JavaDoc e){
695                     ErrorManager.getDefault().notify(ErrorManager.EXCEPTION, e);
696                     // nothing to do
697
}
698             }
699             helpMap = new HashMap();
700             // The URI from the tld file URL, where are the files for the library
701
helpMap.put("http://java.sun.com/jsp/jstl/core", url + "c/");
702             helpMap.put("http://java.sun.com/jstl/core", url + "c/");
703             helpMap.put("http://java.sun.com/jstl/core_rt", url + "c_rt/");
704             helpMap.put("http://java.sun.com/jsp/jstl/fmt", url + "fmt/");
705             helpMap.put("http://java.sun.com/jstl/fmt", url + "fmt/");
706             helpMap.put("http://java.sun.com/jstl/fmt_rt", url + "fmt_rt/");
707             helpMap.put("http://java.sun.com/jsp/jstl/functions", url + "fn/");
708             helpMap.put("http://java.sun.com/jstl/functions", url + "fn/");
709             helpMap.put("http://jakarta.apache.org/taglibs/standard/permittedTaglibs", url+"permittedTaglibs/");
710             helpMap.put("http://jakarta.apache.org/taglibs/standard/scriptfree", url+ "scriptfree/");
711             helpMap.put("http://java.sun.com/jsp/jstl/sql", url + "sql/");
712             helpMap.put("http://java.sun.com/jstl/sql", url + "sql/");
713             helpMap.put("http://java.sun.com/jstl/sql_rt", url + "sql_rt/");
714             helpMap.put("http://java.sun.com/jsp/jstl/xml", url + "x/");
715             helpMap.put("http://java.sun.com/jstl/xml", url + "x/");
716             helpMap.put("http://java.sun.com/jstl/xml_rt", url + "x_rt/");
717             f = InstalledFileLocator.getDefault().locate("docs/jsf12-tlddoc.zip", null, false); //NoI18N
718
if (f != null){
719                 try {
720                     URL JavaDoc urll = f.toURL();
721                     urll = FileUtil.getArchiveRoot(urll);
722                     url = urll.toString();
723                     helpMap.put("http://java.sun.com/jsf/html", url + "h/");
724                     helpMap.put("http://java.sun.com/jsf/core", url + "f/");
725                 } catch (java.net.MalformedURLException JavaDoc e){
726                     ErrorManager.getDefault().notify(ErrorManager.EXCEPTION, e);
727                     // nothing to do
728
}
729             }
730             f = InstalledFileLocator.getDefault().locate("docs/struts-tags.zip", null, false);
731             if (f != null){
732                 try {
733                     URL JavaDoc urll = f.toURL();
734                     urll = FileUtil.getArchiveRoot(urll);
735                     url = urll.toString();
736                     helpMap.put("http://jakarta.apache.org/struts/tags-bean", url + "bean/");
737                     helpMap.put("http://struts.apache.org/tags-bean", url + "bean/");
738                     helpMap.put("/WEB-INF/struts-bean.tld", url + "bean/");
739                     helpMap.put("http://jakarta.apache.org/struts/tags-html", url + "html/");
740                     helpMap.put("http://struts.apache.org/tags-html", url + "html/");
741                     helpMap.put("/WEB-INF/struts-html.tld", url + "html/");
742                     helpMap.put("http://jakarta.apache.org/struts/tags-logic", url + "logic/");
743                     helpMap.put("http://struts.apache.org/tags-logic", url + "logic/");
744                     helpMap.put("/WEB-INF/struts-logic.tld", url + "logic/");
745                     helpMap.put("http://jakarta.apache.org/struts/tags-nested", url + "nested/");
746                     helpMap.put("http://struts.apache.org/tags-nested", url + "nested/");
747                     helpMap.put("/WEB-INF/struts-nested.tld", url + "nested/");
748                     helpMap.put("http://jakarta.apache.org/struts/tags-tiles", url + "tiles/");
749                     helpMap.put("http://struts.apache.org/tags-tiles", url + "tiles/");
750                     helpMap.put("/WEB-INF/struts-tiles.tld", url + "tiles/");
751                 } catch (java.net.MalformedURLException JavaDoc e){
752                     ErrorManager.getDefault().notify(ErrorManager.EXCEPTION, e);
753                     // nothing to do
754
}
755             }
756             
757         }
758         
759     }
760     
761     private static void initCompletionData() {
762         if (helpMap == null)
763             initHelp();
764         String JavaDoc url = ""; // NOI18N
765
if (standardJspTagDatas == null) {
766             final String JavaDoc helpFiles = "docs/syntaxref20.zip"; //NoI18N
767
File JavaDoc f = InstalledFileLocator.getDefault().locate(helpFiles, null, true); //NoI18N
768
if (f != null){
769                 try {
770                     URL JavaDoc urll = f.toURL();
771                     urll = FileUtil.getArchiveRoot(urll);
772                     url = urll.toString();
773                 } catch (java.net.MalformedURLException JavaDoc e){
774                     err.notify(ErrorManager.EXCEPTION, e);
775                     // nothing to do
776
}
777             }
778             standardJspTagDatas = new TagInfo[] {
779                 new TagInfo("attribute", null, TagInfo.BODY_CONTENT_JSP, url + "syntaxref2014.html", // NOI18N
780
null, null, new TagAttributeInfo[] { new TagAttributeInfo("name", true, url + "syntaxref2014.html#1003581#1006483", false), // NOI18N
781
new TagAttributeInfo("trim", false, url + "syntaxref2014.html#1006483#1003583", false)}), // NOI18N
782
new TagInfo("body", null, TagInfo.BODY_CONTENT_JSP, url + "syntaxref2015.html#1006731#1003768", // NOI18N
783
null, null, new TagAttributeInfo[]{}),
784                         new TagInfo("element", null, TagInfo.BODY_CONTENT_JSP, url + "syntaxref2016.html#1003696#1003708", // NOI18N
785
null, null, new TagAttributeInfo[] { new TagAttributeInfo("name", true, url + "syntaxref2016.html#1003706#1003708", false)}), // NOI18N
786
new TagInfo("expression", null, TagInfo.BODY_CONTENT_JSP, url+"syntaxref205.html#1004353#11268", // NOI18N
787
null, null, new TagAttributeInfo[] {}),
788                         new TagInfo("fallback", null, TagInfo.BODY_CONTENT_JSP, url + "syntaxref2023.html#11583#19029", // NOI18N
789
null, null, new TagAttributeInfo[] {}),
790                         new TagInfo("forward", null, TagInfo.BODY_CONTENT_JSP, url + "syntaxref2018.html#1003349#15708", // NOI18N
791
null, null, new TagAttributeInfo[] { new TagAttributeInfo("page", true, url + "syntaxref2018.html#15704#15708", true)}), // NOI18N
792
new TagInfo("getProperty", null, TagInfo.BODY_CONTENT_EMPTY, url + "syntaxref2019.html#8820#9201", // NOI18N
793
null, null, new TagAttributeInfo[] { new TagAttributeInfo("name", true, url + "syntaxref2019.html#15748#10919", false), // NOI18N
794
new TagAttributeInfo("property", true, url + "syntaxref2019.html#10919#19482", false)}), // NOI18N
795
new TagInfo("include", null, TagInfo.BODY_CONTENT_JSP, url + "syntaxref2020.html#8828#9228", // NOI18N
796
null, null, new TagAttributeInfo[] { new TagAttributeInfo("flush", true, url + "syntaxref2020.html#17145#18376", false), // NOI18N
797
new TagAttributeInfo("page", true, url + "syntaxref2020.html#10930#17145", true)}), // NOI18N
798
new TagInfo("param", null, TagInfo.BODY_CONTENT_EMPTY, url + "syntaxref2023.html#11538#11583", // NOI18N
799
null, null, new TagAttributeInfo[] { new TagAttributeInfo("name", true, url + "syntaxref2023.html#11538#11583", false), // NOI18N
800
new TagAttributeInfo("value", true, url + "syntaxref2023.html#11538#11583", true)}), // NOI18N
801
new TagInfo("params", null, TagInfo.BODY_CONTENT_JSP, url + "syntaxref2023.html#11538#11583", // NOI18N
802
null, null, new TagAttributeInfo[] {}),
803                         new TagInfo("plugin", null, TagInfo.BODY_CONTENT_JSP, url + "syntaxref2023.html#1004158#19029", // NOI18N
804
null, null, new TagAttributeInfo[] { new TagAttributeInfo("align", false, url + "syntaxref2023.html#11516#11518", false), // NOI18N
805
new TagAttributeInfo("archive", false, url + "syntaxref2023.html#11553#11516", false), // NOI18N
806
new TagAttributeInfo("code", true, url + "syntaxref2023.html#11514#11515", false), // NOI18N
807
new TagAttributeInfo("codebase", true, url + "syntaxref2023.html#11515#11547", false), // NOI18N
808
new TagAttributeInfo("height", false, url + "syntaxref2023.html#11518#11568", false), // NOI18N
809
new TagAttributeInfo("hspace", false, url + "syntaxref2023.html#11568#11520", false), // NOI18N
810
new TagAttributeInfo("iepluginurl", false, url + "syntaxref2023.html#11526#11538", false),// NOI18N
811
new TagAttributeInfo("jreversion", false, url + "syntaxref2023.html#11520#11525", false), // NOI18N
812
new TagAttributeInfo("name", false, url + "syntaxref2023.html#11547#11553", false), // NOI18N
813
new TagAttributeInfo("nspluginurl", false,url + "syntaxref2023.html#11525#11526", false),// NOI18N
814
new TagAttributeInfo("type", true, url + "syntaxref2023.html#10935#11514", false), // NOI18N
815
new TagAttributeInfo("vspace", false, url + "syntaxref2023.html#11568#11520", false), // NOI18N
816
new TagAttributeInfo("width", false, url + "syntaxref2023.html#11518#11568", false)}), // NOI18N
817
new TagInfo("setProperty", null, TagInfo.BODY_CONTENT_EMPTY, url + "syntaxref2025.html#8856#9329", // NOI18N
818
null, null, new TagAttributeInfo[] { new TagAttributeInfo("name", true, url + "syntaxref2025.html#17612#1001786", true), // NOI18N
819
new TagAttributeInfo("param", false, url + "syntaxref2025.html#9919#20483", false), // NOI18N
820
new TagAttributeInfo("property", false, url + "syntaxref2025.html#1001786#9329", false), // NOI18N
821
new TagAttributeInfo("value", false, url + "syntaxref2025.html#20483#9329", true)}), // NOI18N
822
new TagInfo("text", null, TagInfo.BODY_CONTENT_JSP, url + "syntaxref2026.html",
823                         null, null, new TagAttributeInfo[]{}),
824                         new TagInfo("useBean", null, TagInfo.BODY_CONTENT_JSP, url + "syntaxref2027.html#8865#9359", // NOI18N
825
null, null, new TagAttributeInfo[] { new TagAttributeInfo("beanName", false, url + "syntaxref2027.html#15804#9359", false), // NOI18N
826
new TagAttributeInfo("class", false, url + "syntaxref2027.html#10968#19433", false), // NOI18N
827
new TagAttributeInfo("id", true, url + "syntaxref2027.html#10964#10966", false), // NOI18N
828
new TagAttributeInfo("scope", true, url + "syntaxref2027.html#10966#10968", false), // NOI18N
829
new TagAttributeInfo("type", false, url + "syntaxref2027.html#19433#18019", false)}) // NOI18N
830
};
831             
832             standardTagTagDatas = new TagInfo[standardJspTagDatas.length + 2 ];
833             standardTagTagDatas[0] = standardJspTagDatas[0]; //"attribute"
834
standardTagTagDatas[1] = standardJspTagDatas[1]; //"body"
835
standardTagTagDatas[2] = new TagInfo("doBody", null, TagInfo.BODY_CONTENT_EMPTY, url + "syntaxref2017.html", null, null, // NOI18N
836
new TagAttributeInfo[] { new TagAttributeInfo("scope", false, url + "syntaxref2017.html#1006246#syntaxref20.html", false), // NOI18N
837
new TagAttributeInfo("var", false, url + "syntaxref2017.html#1006234#1006240", false), // NOI18N
838
new TagAttributeInfo("varReader", false, url + "syntaxref2017.html#1006240#1006246", false)}); // NOI18N
839

840             standardTagTagDatas[3] = standardJspTagDatas[2]; //"element"
841
standardTagTagDatas[4] = standardJspTagDatas[3]; //"expression"
842
standardTagTagDatas[5] = standardJspTagDatas[4]; //"fallback"
843
standardTagTagDatas[6] = standardJspTagDatas[5]; //"forward"
844
standardTagTagDatas[7] = standardJspTagDatas[6]; //"getProperty"
845
standardTagTagDatas[8] = standardJspTagDatas[7]; //"include"
846
standardTagTagDatas[9] = new TagInfo("invoke", null, TagInfo.BODY_CONTENT_JSP, url + "syntaxref2021.html#8837#1003634", null, null, // NOI18N
847
new TagAttributeInfo[] { new TagAttributeInfo("fragment", true, url + "syntaxref2021.html#1007359#1007361", false), // NOI18N
848
new TagAttributeInfo("scope", false, url + "syntaxref2021.html#1007373#1003634", false), // NOI18N
849
new TagAttributeInfo("var", false, url + "syntaxref2021.html#1007361#1007367", false), // NOI18N
850
new TagAttributeInfo("varReader", false, url + "syntaxref2021.html#1007367#1007373", false)}); // NOI18N
851

852             standardTagTagDatas[10] = standardJspTagDatas[8]; //"param"
853
standardTagTagDatas[11] = standardJspTagDatas[9]; //"params"
854
standardTagTagDatas[12] = standardJspTagDatas[10]; //"plugin"
855
standardTagTagDatas[13] = standardJspTagDatas[11]; //"setProperty"
856
standardTagTagDatas[14] = standardJspTagDatas[12]; //"text"
857
standardTagTagDatas[15] = standardJspTagDatas[13]; //"useBean"
858
}
859         
860         
861         if (directiveJspData == null){
862             directiveJspData = new TagInfo[] {
863                 new TagInfo("include", null, TagInfo.BODY_CONTENT_EMPTY, url+"syntaxref209.html#1003408#8975", null, null, // NOI18N
864
new TagAttributeInfo[] { new TagAttributeInfo("file", true, url + "syntaxref209.html#16836#10636", false)}), // NOI18N
865
new TagInfo("page", null, TagInfo.BODY_CONTENT_EMPTY, url+"syntaxref2010.html", null, null, // NOI18N
866
new TagAttributeInfo[] { new TagAttributeInfo("autoFlush", false, url+"syntaxref2010.html#15673#15675", false), // NOI18N
867
new TagAttributeInfo("buffer", false, url+"syntaxref2010.html#15671#15673", false), // NOI18N
868
new TagAttributeInfo("contentType", false, url+"syntaxref2010.html#15683#1001361", false), // NOI18N
869
new TagAttributeInfo("errorPage", false, url+"syntaxref2010.html#15679#15681", false), // NOI18N
870
new TagAttributeInfo("extends", false, url+"syntaxref2010.html#15665#16862", false), // NOI18N
871
new TagAttributeInfo("import", false, url+"syntaxref2010.html#16862#15669", false), // NOI18N
872
new TagAttributeInfo("info", false, url+"syntaxref2010.html#15677#15679", false), // NOI18N
873
new TagAttributeInfo("isELIgnored", false, url+"syntaxref2010.html#1011216#18865", false), // NOI18N
874
new TagAttributeInfo("isErrorPage", false, url+"syntaxref2010.html#15681#15683", false), // NOI18N
875
new TagAttributeInfo("isThreadSafe", false, url+"syntaxref2010.html#15675#15677", false), // NOI18N
876
new TagAttributeInfo("language", false, url+"syntaxref2010.html#15663#15665", false), // NOI18N
877
new TagAttributeInfo("pageEncoding", false, url+"syntaxref2010.html#1001361#1011216", false), // NOI18N
878
new TagAttributeInfo("session", false, url+"syntaxref2010.html#15669#15671", false)}), // NOI18N
879
new TagInfo("taglib", null, TagInfo.BODY_CONTENT_EMPTY, url+"syntaxref2012.html#1003416#1002041", null, null, // NOI18N
880
new TagAttributeInfo[] { new TagAttributeInfo("prefix", true, url+"syntaxref2012.html#1011290#1002041", false), // NOI18N
881
new TagAttributeInfo("uri", false, url+"syntaxref2012.html#10721#1011294", false), // NOI18N
882
new TagAttributeInfo("tagdir", false, url + "syntaxref2012.html#1011294#1011290", false)}) // NOI18N
883
};
884         }
885         
886         
887         if (directiveTagFileData == null){
888             
889             directiveTagFileData = new TagInfo[]{
890                 new TagInfo("attribute", null, TagInfo.BODY_CONTENT_EMPTY, url + "syntaxref208.html", null, null, // NOI18N
891
new TagAttributeInfo[] { new TagAttributeInfo("description", false, url + "syntaxref208.html#1004672", false), // NOI18N
892
new TagAttributeInfo("fragment", false, url + "syntaxref208.html#1004657#1004666", false), // NOI18N
893
new TagAttributeInfo("name", true, url + "syntaxref208.html#1004648#1004655", false), // NOI18N
894
new TagAttributeInfo("required", false, url + "syntaxref208.html#1004655#1004657", false), // NOI18N
895
new TagAttributeInfo("rtexprvalue", false, url + "syntaxref208.html#1004666#1004669", false), // NOI18N
896
new TagAttributeInfo("type", false, url + "syntaxref208.html#1004669#1004672", false)}), // NOI18N
897
directiveJspData[0],
898                         new TagInfo("tag", null, TagInfo.BODY_CONTENT_EMPTY, url + "syntaxref2011.html", null, null, // NOI18N
899
new TagAttributeInfo[] { new TagAttributeInfo("body-content", false, url + "syntaxref2011.html#1005164#005172", false), // NOI18N
900
new TagAttributeInfo("description", false, url + "syntaxref2011.html#1005196#1005198", false), // NOI18N
901
new TagAttributeInfo("display-name", false, url + "syntaxref2011.html#1005161#1005164", false), // NOI18N
902
new TagAttributeInfo("dynamic-attributes", false, url + "syntaxref2011.html#005172#1005190", false), // NOI18N
903
new TagAttributeInfo("example", false, url + "syntaxref2011.html#1005198#1005201", false), // NOI18N
904
new TagAttributeInfo("import", false, url + "syntaxref2011.html#1005203#1005209", false), // NOI18N
905
new TagAttributeInfo("isELIgnored", false, url + "syntaxref2011.html#1005214#1005291#1005291", false), // NOI18N
906
//new TagAttributeInfo("isScriptingEnabled", false, url + "syntaxref2011.html#", false), // NOI18N
907
new TagAttributeInfo("large-icon", false, url + "syntaxref2011.html#1005193#1005196", false), // NOI18N
908
new TagAttributeInfo("language", false, url + "syntaxref2011.html#1005201#1005203", false), // NOI18N
909
new TagAttributeInfo("pageEncoding", false, url + "syntaxref2011.html#1005209#1005214", false), // NOI18N
910
new TagAttributeInfo("small-icon", false, url + "syntaxref2011.html#1005190#1005193", false)}), // NOI18N
911
directiveJspData[2],
912                         new TagInfo("variable", null, TagInfo.BODY_CONTENT_EMPTY, url + "syntaxref2013.html#15694#1003563", null, null, // NOI18N
913
new TagAttributeInfo[] { new TagAttributeInfo("alias", false, url + "syntaxref2013.html#1005914#1005956", false), // NOI18N
914
new TagAttributeInfo("declare", false, url + "syntaxref2013.html#1006001#1006019", false), // NOI18N
915
new TagAttributeInfo("description", false, url + "syntaxref2013.html#1005991#1003563", false), // NOI18N
916
new TagAttributeInfo("name-given", false, url + "syntaxref2013.html#1003561#1005914", false), // NOI18N
917
new TagAttributeInfo("scope", false, url + "syntaxref2013.html#1006019#1005991", false), // NOI18N
918
new TagAttributeInfo("variable-class", false, url + "syntaxref2013.html#1005956#1006001", false)}) // NOI18N
919
};
920         }
921         
922         if (xmlJspTagDatas == null) {
923             TagInfo[] commonXMLTagDatas;
924             commonXMLTagDatas = new TagInfo[]{
925                 new TagInfo("declaration", null, TagInfo.BODY_CONTENT_JSP, url+"syntaxref204.html#10983#10991", // NOI18N
926
null, null, new TagAttributeInfo[] {}),
927                         new TagInfo("output", null, TagInfo.BODY_CONTENT_JSP, url + "syntaxref2022.html#1004130#1007521", // NOI18N
928
null, null, new TagAttributeInfo[] {new TagAttributeInfo("doctype-public", false, "url + syntaxref2022.html#1007534#1007521", false), // NOI18N
929
new TagAttributeInfo("doctype-root-element", false, "url + syntaxref2022.html#1007528#1007532", false), // NOI18N
930
new TagAttributeInfo("doctype-system", false, url + "syntaxref2022.html#1007532#1007534", false), // NOI18N
931
new TagAttributeInfo("omit-xml-declaration", false, url + "syntaxref2022.html#1007525#1007528" , false)}), // NOI18N
932
new TagInfo("scriptlet", null, TagInfo.BODY_CONTENT_JSP, url+"syntaxref206.html#10996#11007", // NOI18N
933
null, null, new TagAttributeInfo[] {}),
934                         new TagInfo("root", null, TagInfo.BODY_CONTENT_JSP, url+"syntaxref2024.html#1003283#1003311", // NOI18N
935
null, null, new TagAttributeInfo[] {new TagAttributeInfo("version", false, url+"syntaxref2024.html#1003299#1003301", false),
936                         new TagAttributeInfo("xmlns:jsp", false, url+"syntaxref2024.html#1003297#1003299", false),
937                         new TagAttributeInfo("xmlns:x", false, url+"syntaxref2024.html#1003301#1003311", false)})
938             };
939             
940             xmlJspTagDatas = new TagInfo[] {
941                 new TagInfo("directive.page", null, TagInfo.BODY_CONTENT_EMPTY, directiveJspData[1].getInfoString(), // NOI18N
942
null, null, directiveJspData[1].getAttributes()),
943                         new TagInfo("directive.include", null, TagInfo.BODY_CONTENT_EMPTY, directiveJspData[1].getInfoString(), // NOI18N
944
null, null, directiveJspData[0].getAttributes()),
945             };
946             
947             ArrayList list = new ArrayList();
948             for (int i = 0; i < xmlJspTagDatas.length; i++){
949                 list.add(xmlJspTagDatas[i]);
950             }
951             for (int i = 0; i < standardJspTagDatas.length; i++){
952                 list.add(standardJspTagDatas[i]);
953             }
954             for (int i = 0; i < commonXMLTagDatas.length; i++){
955                 list.add(commonXMLTagDatas[i]);
956             }
957             
958             // sort the list of xml tags
959
Collections.sort(list, TAG_NAME_COMPARATOR);
960             
961             xmlJspTagDatas = new TagInfo[list.size()];
962             for (int i = 0; i < list.size(); i++)
963                 xmlJspTagDatas[i] = (TagInfo)list.get(i);
964             
965             xmlTagFileTagDatas = new TagInfo[] {
966                 new TagInfo("directive.tag", null, TagInfo.BODY_CONTENT_EMPTY, directiveTagFileData[2].getInfoString(), // NOI18N
967
null, null, directiveTagFileData[2].getAttributes()),
968                         new TagInfo("directive.attribute", null, TagInfo.BODY_CONTENT_EMPTY, directiveTagFileData[0].getInfoString(), // NOI18N
969
null, null, directiveTagFileData[0].getAttributes()),
970                         new TagInfo("directive.variable", null, TagInfo.BODY_CONTENT_EMPTY, directiveTagFileData[4].getInfoString(), // NOI18N
971
null, null, directiveTagFileData[4].getAttributes()),
972                         new TagInfo("directive.include", null, TagInfo.BODY_CONTENT_EMPTY, directiveJspData[1].getInfoString(), // NOI18N
973
null, null, directiveJspData[0].getAttributes())
974             };
975             
976             list = new ArrayList();
977             for (int i = 0; i < xmlTagFileTagDatas.length; i++){
978                 list.add(xmlTagFileTagDatas[i]);
979             }
980             for (int i = 0; i < standardTagTagDatas.length; i++){
981                 list.add(standardTagTagDatas[i]);
982             }
983             for (int i = 0; i < commonXMLTagDatas.length; i++){
984                 list.add(commonXMLTagDatas[i]);
985             }
986             
987             Collections.sort(list, TAG_NAME_COMPARATOR);
988             xmlTagFileTagDatas = new TagInfo[list.size()];
989             for (int i = 0; i < list.size(); i++)
990                 xmlTagFileTagDatas[i] = (TagInfo)list.get(i);
991             
992         }
993         
994         
995     }
996     
997     private TagInfo[] getTagInfos(){
998         TagInfo[] rValue;
999         if ("text/x-jsp".equals(fobj.getMIMEType()) || "text/x-tag".equals(fobj.getMIMEType())){
1000            if ( isXmlSyntax()){
1001                if (NbEditorUtilities.getMimeType(getDocument()).equals(JspUtils.TAG_MIME_TYPE))
1002                    rValue = xmlTagFileTagDatas;
1003                else
1004                    rValue = xmlJspTagDatas;
1005            } else
1006                if (NbEditorUtilities.getMimeType(getDocument()).equals(JspUtils.TAG_MIME_TYPE))
1007                    rValue = standardTagTagDatas;
1008                else
1009                    rValue = standardJspTagDatas;
1010        } else
1011            rValue = new TagInfo[0];
1012        return rValue;
1013    }
1014    
1015    public String JavaDoc toString() {
1016        return printJspCompletionInfo();
1017    }
1018    
1019    /** Debug output of all tags and directives. */
1020    private String JavaDoc printJspCompletionInfo() {
1021        StringBuffer JavaDoc output = new StringBuffer JavaDoc();
1022        
1023        output.append("TAGS\n"); // NOI18N
1024
List tagPrefixes = getTagPrefixes(""); // NOI18N
1025
for (int i = 0; i < tagPrefixes.size(); i++) {
1026            String JavaDoc prefix = (String JavaDoc)tagPrefixes.get(i);
1027            output.append(" " + prefix + "\n"); // NOI18N
1028
List tags = getTags(prefix, ""); // NOI18N
1029
for (int j = 0; j < tags.size(); j++) {
1030                if (tags.get(j) instanceof TagInfo){
1031                    TagInfo ti = (TagInfo) tags.get(j);
1032                    output.append(" " + ti.getTagName() + "\n");
1033                    TagAttributeInfo[] attributes = ti.getAttributes();
1034                    for (int k = 0; k < attributes.length; k++) {
1035                        output.append(" " + attributes[k].getName() + "\n");// NOI18N
1036
}
1037                } else {
1038                    String JavaDoc tagName = (String JavaDoc)tags.get(j);
1039                    output.append(" " + tagName + "\n"); // NOI18N
1040
List attributes = getTagAttributes(prefix, tagName, "");// NOI18N
1041
for (int k = 0; k < attributes.size(); k++) {
1042                        String JavaDoc attribute = (String JavaDoc)attributes.get(k);
1043                        output.append(" " + attribute + "\n");// NOI18N
1044
}
1045                }
1046            }
1047            
1048        }
1049        
1050        output.append("DIRECTIVES\n");// NOI18N
1051
List directives = getDirectives("");// NOI18N
1052
for (int i = 0; i < directives.size(); i++) {
1053            if (directives.get(i) instanceof TagInfo){
1054                TagInfo ti = (TagInfo) directives.get(i);
1055                output.append(" " + ti.getTagName() + "\n");
1056                TagAttributeInfo[] attributes = ti.getAttributes();
1057                for (int k = 0; k < attributes.length; k++) {
1058                    output.append(" " + attributes[k].getName() + "\n");// NOI18N
1059
}
1060            } else {
1061                String JavaDoc directive = (String JavaDoc)directives.get(i);
1062                output.append(" " + directive + "\n");// NOI18N
1063
List attributes = getDirectiveAttributes(directive, "");// NOI18N
1064
for (int k = 0; k < attributes.size(); k++) {
1065                    String JavaDoc attribute = (String JavaDoc)attributes.get(k);
1066                    output.append(" " + attribute + "\n");// NOI18N
1067
}
1068            }
1069        }
1070        
1071        return output.toString();
1072    }
1073    
1074    /** Returns an item on offset <code>offset</code>
1075     * This method is largely a workaround for a bug in getTokenChain().
1076     * If offset falls right between two items, returns one which is just before
1077     * offset. If <code>offset == 0</code>, retruns null. */

1078    @Deprecated JavaDoc()
1079    public TokenItem getItemAtOrBefore(int offset) throws BadLocationException JavaDoc {
1080        TokenItem backItem = null;
1081        int chainLength = 100;
1082        while (backItem == null) {
1083            if (offset < getDocument().getLength()) {
1084                backItem = getTokenChain( offset,
1085                        Math.min(offset + chainLength, getDocument().getLength())/*, false*/ );
1086            } else {
1087                // @ end of document
1088
backItem = getTokenChain(Math.max(offset-50, 0), offset);
1089            }
1090            
1091            if (chainLength++ > 1000)
1092                break;
1093        }
1094        if (backItem == null)
1095            return null;
1096        
1097        // forward to the offset where our token definitely is
1098
//System.out.println("looking for item at offset " + offset);
1099
//System.out.println("backitem " + backItem);
1100
TokenItem item;
1101        while (true) {
1102            item = backItem.getNext();
1103            //System.out.println("looking at item " + item);
1104
if (item == null) {
1105                item = backItem;
1106                //System.out.println("break1");
1107
break;
1108            }
1109            if (item.getOffset() > offset) {
1110                item = backItem;
1111                //System.out.println("break2");
1112
break;
1113            }
1114            backItem = item;
1115            //System.out.println("backitem2 " + backItem);
1116
}
1117        
1118        //System.out.println("REAL Token at offset " + offset + " is " + item );
1119
TokenItem adjustedItem = (item.getOffset() == offset) ?
1120            item.getPrevious() : item;
1121        //System.out.println("ADJUSTED Token at offset " + offset + " is " + adjustedItem );
1122

1123        TokenID id = (adjustedItem == null) ?
1124            item.getTokenID() : adjustedItem.getTokenID();
1125        //System.out.println("TokenID (adjusted) at offset " + offset + " is " + id );
1126
return adjustedItem;
1127    }
1128    
1129    /** Returns SyntaxElement instance for block of tokens, which is either
1130     * surrounding given offset, or is just after the offset.
1131     * @param offset offset in document where to search for SyntaxElement
1132     * @return SyntaxElement surrounding or laying before the offset
1133     */

1134    public SyntaxElement getElementChain( int offset ) throws BadLocationException JavaDoc {
1135        TokenItem item = getItemAtOrBefore(offset);
1136        if (item == null)
1137            return null;
1138        TokenID id = item.getTokenID();
1139        
1140        if (/*id == JspTagTokenContext.COMMENT || */
1141                id == JspTagTokenContext.ERROR ||
1142                id == JspTagTokenContext.TEXT ||
1143                id == JspMultiTokenContext.ERROR ||
1144                /*id == JspDirectiveTokenContext.COMMENT || */
1145                id == JspDirectiveTokenContext.ERROR ||
1146                id == JspDirectiveTokenContext.TEXT
1147                ) {
1148            //System.out.println("uninteresting JspTag token");
1149
return null;
1150        }
1151        
1152        //JSP comment
1153
if(id == JspTagTokenContext.COMMENT || id == JspDirectiveTokenContext.COMMENT) {
1154            return getCommentChain(item, offset);
1155        }
1156        
1157        //Expression language handling
1158
if(item.getTokenContextPath().contains(ELTokenContext.contextPath)) {
1159            return getELChain(item, offset);
1160        }
1161        
1162        if (id == JspTagTokenContext.SYMBOL2 || id == JspDirectiveTokenContext.SYMBOL2) {
1163            if (isScriptStartToken(item)) {
1164                return getScriptingChain(item, offset);
1165            }
1166            
1167            if ((getTokenEnd(item) == offset) && isScriptEndToken(item)) {
1168                TokenItem nextItem = item.getNext();
1169                if (!isTagDirToken(item))
1170                    return getContentChain(item, offset);
1171            }
1172            return null;
1173        }
1174        
1175        if (id == JspTagTokenContext.TAG ||
1176                id == JspTagTokenContext.SYMBOL ||
1177                id == JspTagTokenContext.ATTRIBUTE ||
1178                id == JspTagTokenContext.ATTR_VALUE ||
1179                id == JspTagTokenContext.WHITESPACE ||
1180                id == JspTagTokenContext.EOL ||
1181                id == JspDirectiveTokenContext.TAG ||
1182                id == JspDirectiveTokenContext.SYMBOL ||
1183                id == JspDirectiveTokenContext.ATTRIBUTE ||
1184                id == JspDirectiveTokenContext.ATTR_VALUE ||
1185                id == JspDirectiveTokenContext.WHITESPACE ||
1186                id == JspDirectiveTokenContext.EOL) {
1187            // may be intetesting: tag, directive,
1188
// but may also be a comment. Look back for SYMBOL: <, </, <%@
1189
// or COMMENT
1190
TokenItem elementStart = item;
1191            do {
1192                //System.out.println("backtracking, elementStart = " + elementStart);
1193
if (elementStart == null)
1194                    return null;
1195                if (elementStart.getTokenID() == JspTagTokenContext.SYMBOL
1196                        || elementStart.getTokenID() == JspDirectiveTokenContext.SYMBOL) {
1197                    if (elementStart.getImage().equals("<")) { // NOI18N
1198
return getTagOrDirectiveChain(true, elementStart, offset);
1199                    }
1200                    if (elementStart.getImage().equals("</")) { // NOI18N
1201
return getEndTagChain(elementStart, offset);
1202                    }
1203                    if (elementStart.getImage().equals("<%@")) { // NOI18N
1204
return getTagOrDirectiveChain(false, elementStart, offset);
1205                    }
1206                }
1207                if(elementStart.getTokenID() == JspTagTokenContext.ERROR
1208                        || elementStart.getTokenID() == JspDirectiveTokenContext.ERROR) {
1209                    //an error in JSP code
1210
return null;
1211                }
1212                if (elementStart.getTokenID() == JspTagTokenContext.COMMENT
1213                        || elementStart.getTokenID() == JspDirectiveTokenContext.COMMENT) {
1214                    return null;
1215                }
1216                elementStart = elementStart.getPrevious();
1217            }
1218            while (true);
1219        }
1220        
1221        // now we are either in the scripting language or in the content language.
1222
// to determine which one it is, look back for SYMBOL2: <%, <%=, <%!
1223
// (scripting language) or for any other JspTag token (content language).
1224
// if nothing found, we are in the content language
1225
if (isScriptingOrContentToken(item)) {
1226            TokenItem elementStart = item;
1227            do {
1228                if (elementStart.getPrevious() == null) {
1229                    // we backtracked to the beginning without finding
1230
// a distinguishing symbol - we are in the content language
1231
return getContentChain(elementStart, offset);
1232                }
1233                elementStart = elementStart.getPrevious(); // now non-null
1234
if (!isScriptingOrContentToken(elementStart)
1235                        || elementStart.getTokenID() == JspTagTokenContext.COMMENT
1236                        || elementStart.getTokenID() == JspDirectiveTokenContext.COMMENT
1237                        || elementStart.getTokenContextPath().contains(ELTokenContext.contextPath)) {
1238                    // something from JSP
1239
if (isScriptStartToken(elementStart)) {
1240                        return getScriptingChain(elementStart.getNext(), offset);
1241                    } else {
1242                        return getContentChain(elementStart.getNext(), offset);
1243                    }
1244                }
1245            }
1246            while (true);
1247        }
1248        
1249        //System.out.println("muddy waters");
1250
return null;
1251    }
1252    
1253    /** Returns true if item is a starting symbol for a block in
1254     * the scripting language. */

1255    private boolean isScriptStartToken(TokenItem item) {
1256        if (item == null)
1257            return false;
1258        TokenID id = item.getTokenID();
1259        if (id == JspTagTokenContext.SYMBOL2
1260                || id == JspDirectiveTokenContext.SYMBOL2) {
1261            String JavaDoc image = item.getImage();
1262            if (image.equals("<%") || // NOI18N
1263
image.equals("<%=") || // NOI18N
1264
image.equals("<%!")) // NOI18N
1265
return true;
1266        }
1267        return false;
1268    }
1269    
1270    /** Returns true if item is an ending symbol for a block in
1271     * the scripting language. */

1272    private boolean isScriptEndToken(TokenItem item) {
1273        if (item == null)
1274            return false;
1275        TokenID id = item.getTokenID();
1276        if (id == JspTagTokenContext.SYMBOL2
1277                || id == JspDirectiveTokenContext.SYMBOL2) {
1278            String JavaDoc image = item.getImage();
1279            if (image.equals("%>")) // NOI18N
1280
return true;
1281        }
1282        return false;
1283    }
1284    
1285    /** Returns true if item is an item which can be INSIDE
1286     * a JSP tag or directive (i.e. excuding delimeters). */

1287    private boolean isInnerTagDirToken(TokenItem item) {
1288        if (!isTagDirToken(item))
1289            return false;
1290        TokenID id = item.getTokenID();
1291        if (id == JspTagTokenContext.SYMBOL
1292                || id == JspDirectiveTokenContext.SYMBOL) {
1293            String JavaDoc image = item.getImage();
1294            if (image.equals("<") || // NOI18N
1295
image.equals("</") || // NOI18N
1296
image.equals("<%@") || // NOI18N
1297
image.equals("%>") || // NOI18N
1298
image.equals(">") || // NOI18N
1299
image.equals("/>")) // NOI18N
1300
return false;
1301        }
1302        return true;
1303    }
1304    
1305    /** Returns true if item is an item which can be INSIDE
1306     * a JSP tag or directive (i.e. excuding delimeters). */

1307    private boolean isTagDirToken(TokenItem item) {
1308        if (item == null)
1309            return false;
1310        TokenID id = item.getTokenID();
1311        if (id == null)
1312            return false;
1313        if ((id != JspTagTokenContext.TEXT) &&
1314                (id != JspTagTokenContext.ERROR) &&
1315                (id != JspTagTokenContext.TAG) &&
1316                (id != JspTagTokenContext.SYMBOL) &&
1317                (id != JspTagTokenContext.ATTRIBUTE) &&
1318                (id != JspTagTokenContext.ATTR_VALUE) &&
1319                (id != JspTagTokenContext.WHITESPACE) &&
1320                (id != JspTagTokenContext.EOL) &&
1321                (id != JspDirectiveTokenContext.TEXT) &&
1322                (id != JspDirectiveTokenContext.ERROR) &&
1323                (id != JspDirectiveTokenContext.TAG) &&
1324                (id != JspDirectiveTokenContext.SYMBOL) &&
1325                (id != JspDirectiveTokenContext.ATTRIBUTE) &&
1326                (id != JspDirectiveTokenContext.ATTR_VALUE) &&
1327                (id != JspDirectiveTokenContext.WHITESPACE) &&
1328                (id != JspDirectiveTokenContext.EOL )) {
1329            return false;
1330        }
1331        // PENDING - EOL can still be a comment
1332
return true;
1333    }
1334    
1335    /** Return true if this item does not belong to JSP syntax
1336     * and belongs to one of the syntaxes we delegate to. */

1337    private boolean isScriptingOrContentToken(TokenItem item) {
1338        if (item == null)
1339            return true;
1340        TokenID id = item.getTokenID();
1341        if (id == null)
1342            return true;
1343        if ((id == JspTagTokenContext.TEXT) ||
1344                (id == JspTagTokenContext.ERROR) ||
1345                (id == JspTagTokenContext.TAG) ||
1346                (id == JspTagTokenContext.SYMBOL) ||
1347                (id == JspTagTokenContext.ATTRIBUTE) ||
1348                (id == JspTagTokenContext.ATTR_VALUE) ||
1349                (id == JspTagTokenContext.SYMBOL2) ||
1350                (id == JspTagTokenContext.EOL) ||
1351                (id == JspDirectiveTokenContext.TEXT) ||
1352                (id == JspDirectiveTokenContext.ERROR) ||
1353                (id == JspDirectiveTokenContext.TAG) ||
1354                (id == JspDirectiveTokenContext.SYMBOL) ||
1355                (id == JspDirectiveTokenContext.ATTRIBUTE) ||
1356                (id == JspDirectiveTokenContext.ATTR_VALUE) ||
1357                (id == JspDirectiveTokenContext.SYMBOL2) ||
1358                (id == JspDirectiveTokenContext.EOL) ||
1359                (id == JspMultiTokenContext.ERROR))
1360            return false;
1361        return true;
1362    }
1363    
1364    public boolean isValueBeginning(String JavaDoc text) {
1365        if (text.trim().endsWith("\"\"")) // NOI18N
1366
return false;
1367        for (int i = 0; i < text.length(); i++) {
1368            char c = text.charAt(i);
1369            if ((c != ' ') &&
1370                    (c != '=') &&
1371                    (c != '"'))
1372                return false;
1373        }
1374        return true;
1375    }
1376    
1377    // ------- METHODS FOR CONSTRUCTING SEMANTICALLY LIKNKED CHAINS OF TOKENS ------
1378

1379    private SyntaxElement getCommentChain(TokenItem token, int offset) {
1380        //we are somewhere in a JSP comment - need to find its start and end
1381
//backtrace for start
1382
TokenItem start = null;
1383        TokenItem search = token;
1384        do {
1385            if(search != null && search.getImage().startsWith("<%--")) { //NOI18N
1386
start = search;
1387                break;
1388            }
1389            search = search.getPrevious();
1390        } while(search != null);
1391        
1392        if(start == null) {
1393            //didn't find a comment start - strange???
1394
return null;
1395        }
1396        
1397        //find comment end
1398
TokenItem end = null;
1399        TokenItem prevNonNullToken = token;
1400        search = token;
1401        do {
1402            if(search != null && search.getImage().endsWith("--%>")) { //NOI18N
1403
end = search;
1404                break;
1405            }
1406            prevNonNullToken = search;
1407            search = search.getNext();
1408        } while(search != null);
1409        if(end == null) {
1410            //comment to the end of the file - may happed
1411
end = prevNonNullToken; //last non-null token
1412
}
1413        
1414        return new SyntaxElement.Comment(this, start.getOffset(), getTokenEnd(end));
1415    }
1416    
1417    private SyntaxElement getELChain(TokenItem token, int offset) {
1418        //we are somewhere in an expression language - need to find its start and end
1419
//backtrace for start
1420
TokenItem scan = token;
1421        TokenItem start = null;
1422        do {
1423            start = scan;
1424            scan = scan.getPrevious();
1425        } while(scan != null && scan.getTokenContextPath().contains(ELTokenContext.contextPath));
1426        
1427        //find comment end
1428
TokenItem end = null;
1429        scan = token;
1430        do {
1431            end = scan;
1432            scan = scan.getNext();
1433        } while(scan != null && scan.getTokenContextPath().contains(ELTokenContext.contextPath));
1434        
1435        return new SyntaxElement.ExpressionLanguage(this, start.getOffset(), getTokenEnd(end));
1436    }
1437    
1438    
1439    /** Gets an element representing a tag or directive starting with token item firstToken. */
1440    private SyntaxElement getTagOrDirectiveChain(boolean tag, TokenItem firstToken, int offset) {
1441        //suppose we are in a tag or directive => we do not have to distinguish tokenIDs -
1442
//it is sufficient to work with numeric ids (solves directive/tag tokenID problem)
1443
TokenItem item = firstToken.getNext();
1444        String JavaDoc name = getWholeWord(item, JspTagTokenContext.TAG);
1445        while ((item != null) && (item.getTokenID().getNumericID() == JspTagTokenContext.TAG_ID ||
1446                item.getTokenID().getNumericID() == JspTagTokenContext.WHITESPACE_ID ))
1447            item = item.getNext();
1448        TreeMap attributes = new TreeMap();
1449        while (isInnerTagDirToken(item)) {
1450            // collect the attributes
1451
if (item.getTokenID().getNumericID() == JspTagTokenContext.ATTRIBUTE_ID) {
1452                String JavaDoc attributeName = getWholeWord(item, JspTagTokenContext.ATTRIBUTE);
1453                // forward to the next non-ATTRIBUTE token
1454
while ((item != null) && (item.getTokenID().getNumericID() == JspTagTokenContext.ATTRIBUTE_ID))
1455                    item = item.getNext();
1456                // find the value
1457
while ((item != null) &&
1458                        (item.getTokenID().getNumericID() == JspTagTokenContext.SYMBOL_ID) &&
1459                        (isValueBeginning(item.getImage())))
1460                    item = item.getNext();
1461                StringBuffer JavaDoc value = new StringBuffer JavaDoc();
1462                while ((item != null)
1463                        && ((item.getTokenID().getNumericID() == JspTagTokenContext.ATTR_VALUE_ID)
1464                        || (item.getTokenID().getNumericID() == JspTagTokenContext.EOL_ID))) {
1465                    value.append(item.getImage());
1466                    item = item.getNext();
1467                    // request time values
1468
if ((item != null) && (item.getTokenID().getNumericID() == JspTagTokenContext.SYMBOL2_ID)) {
1469                        // scripting language - something like request time value of a JSP tag
1470
while (!isScriptEndToken(item)) {
1471                            if (item == null)
1472                                break;
1473                            else {
1474                                value.append(item.getImage());
1475                                item = item.getNext();
1476                            }
1477                        }
1478                        // now it's a script end token
1479
if (item != null) {
1480                            value.append(item.getImage());
1481                            item = item.getNext();
1482                        }
1483                    }
1484                    
1485                    //expression language - just put its content into attribute value
1486
if((item != null) && (item.getTokenContextPath().contains(ELTokenContext.contextPath))) {
1487                        //go over all EL tokens and add them to the attribute value
1488
while(item.getTokenContextPath().contains(ELTokenContext.contextPath)) {
1489                            item = item.getNext();
1490                            if(item == null) break;
1491                            value.append(item.getImage());
1492                        }
1493                    }
1494                    
1495                }
1496                String JavaDoc vString = value.toString();
1497                // cut off the beginning and ending quotes
1498
if (vString.startsWith("\"")) // NOI18N
1499
vString = vString.substring(1);
1500                if (vString.endsWith("\"")) // NOI18N
1501
vString = vString.substring(0, vString.length() - 1);
1502                attributes.put(attributeName, vString);
1503                continue;
1504            }
1505            if (item.getTokenID().getNumericID() == JspTagTokenContext.SYMBOL2_ID) {
1506                // scripting language - something like request time value of a JSP tag
1507
while (!isScriptEndToken(item)) {
1508                    if (item == null)
1509                        break;
1510                    else
1511                        item = item.getNext();
1512                }
1513                // now it's a script end token
1514
if (item != null)
1515                    item = item.getNext();
1516                continue;
1517            }
1518            // a token I am not interested in
1519
item = item.getNext();
1520        }
1521        if (tag) {
1522            boolean endslash= false;
1523            if (item != null)
1524                endslash = (item.getImage().equals("/>"))? true: false; // NOI18N
1525

1526            return new SyntaxElement.Tag(this, firstToken.getOffset(),
1527                    (item != null)? getTokenEnd(item): getDocument().getLength(),
1528                    name, attributes, endslash);
1529        } else {
1530            return new SyntaxElement.Directive(this, firstToken.getOffset(),
1531                    (item != null)? getTokenEnd(item): getDocument().getLength(),
1532                    name, attributes);
1533        }
1534    }
1535    
1536    private SyntaxElement getEndTagChain(TokenItem firstToken, int offset) {
1537        TokenItem item = firstToken.getNext();
1538        String JavaDoc name = getWholeWord(item, JspTagTokenContext.TAG);
1539        while ((item != null) && (item.getTokenID() == JspTagTokenContext.TAG))
1540            item = item.getNext();
1541        while (isInnerTagDirToken(item)) {
1542            item = item.getNext();
1543        }
1544        return new SyntaxElement.EndTag(this, firstToken.getOffset(),
1545                getTokenEnd(item), name);
1546    }
1547    
1548    private String JavaDoc getWholeWord(TokenItem firstToken, TokenID requestedTokenID) {
1549        StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
1550        while ((firstToken != null) && (firstToken.getTokenID().getNumericID() == requestedTokenID.getNumericID())) {
1551            sb.append(firstToken.getImage());
1552            firstToken = firstToken.getNext();
1553        }
1554        return sb.toString().trim();
1555    }
1556    
1557    /** Returns an element of scripting language starting with firstToken.
1558     * If forstToken is null, returns element representing end of the document.
1559     */

1560    private SyntaxElement getScriptingChain(TokenItem firstToken, int offset) {
1561        if (firstToken == null) {
1562            return new SyntaxElement.ScriptingL(this,
1563                    getDocument().getLength(), getDocument().getLength());
1564        }
1565        TokenItem item = firstToken;
1566        do {
1567            TokenItem nextItem = item.getNext();
1568            if (nextItem == null) {
1569                return new SyntaxElement.ScriptingL(this,
1570                        firstToken.getOffset(), getDocument().getLength());
1571            }
1572            if (!isScriptingOrContentToken(nextItem))
1573                return new SyntaxElement.ScriptingL(this,
1574                        firstToken.getOffset(), getTokenEnd(nextItem));
1575            item = nextItem;
1576        }
1577        while (true);
1578    }
1579    
1580    /** Returns an element of content language starting with firstToken.
1581     * If forstToken is null, returns element representing end of the document.
1582     */

1583    private SyntaxElement getContentChain(TokenItem firstToken, int offset) {
1584        if (firstToken == null) {
1585            return new SyntaxElement.ContentL(this,
1586                    getDocument().getLength(), getDocument().getLength());
1587        }
1588        TokenItem item = firstToken;
1589        do {
1590            TokenItem nextItem = item.getNext();
1591            if (nextItem == null) {
1592                return new SyntaxElement.ContentL(this,
1593                        firstToken.getOffset(), getDocument().getLength());
1594            }
1595            if (!isScriptingOrContentToken(nextItem)
1596                    || nextItem.getTokenID() == JspTagTokenContext.COMMENT
1597                    || nextItem.getTokenID() == JspDirectiveTokenContext.COMMENT
1598                    || nextItem.getTokenContextPath().contains(ELTokenContext.contextPath))
1599                return new SyntaxElement.ContentL(this,
1600                        firstToken.getOffset(), getTokenEnd(item));
1601            item = nextItem;
1602        }
1603        while (true);
1604    }
1605    
1606    /** The way how to get previous SyntaxElement in document. It is not intended
1607     * for direct usage, and thus is not public. Usually, it is called from
1608     * SyntaxElement's method getPrevious()
1609     */

1610    SyntaxElement getPreviousElement( int offset ) throws BadLocationException JavaDoc {
1611        if (offset == 0)
1612            return null;
1613        SyntaxElement elem = null;
1614        offset--;
1615        do {
1616            elem = getElementChain( offset);
1617            if (elem == null){
1618                TokenItem ti = getItemAtOrBefore(offset);
1619                if (ti == null)
1620                    return null;
1621                offset = ti.getOffset() -1 ;
1622            }
1623        } while (elem == null && offset >= 0);
1624        return elem;
1625    }
1626    
1627    SyntaxElement getNextElement(int offset) throws BadLocationException JavaDoc {
1628        int doclen = getDocument().getLength();
1629        if(offset >= doclen)
1630            return null;
1631        
1632        SyntaxElement elem = null;
1633        offset++;
1634        do {
1635            elem = getElementChain(offset);
1636            if(elem == null) {
1637                TokenItem ti = getItemAtOrBefore(offset);
1638                if(ti == null)
1639                    return null;
1640                offset = getTokenEnd(ti) + 1;
1641            }
1642        } while(elem == null && offset < doclen);
1643        
1644        return elem;
1645    }
1646    
1647    public List getPossibleEndTags(int offset, String JavaDoc pattern) throws BadLocationException JavaDoc {
1648        return getPossibleEndTags(offset, pattern, false); //return all end tags
1649
}
1650    
1651    public List getPossibleEndTags(int offset, String JavaDoc pattern, boolean firstOnly) throws BadLocationException JavaDoc {
1652        SyntaxElement elem = getElementChain( offset );
1653        Stack stack = new Stack();
1654        List result = new ArrayList();
1655        Set found = new HashSet();
1656        
1657        if( elem != null ) {
1658            elem = elem.getPrevious(); // we need smtg. before our </
1659
} else { // End of Document
1660
if( offset > 0 ) {
1661                elem = getElementChain( offset-1 );
1662            } else { // beginning of document too, not much we can do on empty doc
1663
return result;
1664            }
1665        }
1666        
1667        
1668        for( ; elem != null; elem = elem.getPrevious() ) {
1669            
1670            if( elem instanceof SyntaxElement.EndTag ) {
1671                stack.push( ((SyntaxElement.EndTag)elem).getName() );
1672            } else if( elem instanceof SyntaxElement.Tag ) {
1673                SyntaxElement.Tag tag = (SyntaxElement.Tag)elem;
1674                
1675                if (tag.isClosed())
1676                    continue;
1677                
1678                String JavaDoc image = tag.getName();
1679                String JavaDoc prefix = image.substring(0, image.indexOf(':'));
1680                String JavaDoc name = image.substring(image.indexOf(':')+1);
1681                TagInfo ti = null;
1682                
1683                TagLibraryInfo tli = getTagLibrary(prefix);
1684                if (tli != null) {
1685                    ti = tli.getTag(name);
1686                }
1687                
1688                if (STANDARD_JSP_PREFIX.equals(prefix)) {
1689                    initCompletionData();
1690                    TagInfo[] stanTagDatas = getTagInfos();
1691                    for (int i=0; i<stanTagDatas.length; i++) {
1692                        if (stanTagDatas[i].getTagName().equals(name)) {
1693                            ti = stanTagDatas[i];
1694                            break;
1695                        }
1696                    }
1697                }
1698                
1699                if (ti == null) continue; // Unknown tag - ignore
1700

1701                if( stack.empty() ) { // empty stack - we are on the same tree deepnes - can close this tag
1702
if( image.startsWith( pattern ) && !found.contains( image ) ) { // add only new items
1703
found.add( image );
1704                        
1705                        if (ti.getBodyContent().equalsIgnoreCase(TagInfo.BODY_CONTENT_EMPTY))
1706                            continue;
1707                        
1708                        result.add( new JspCompletionItem.Tag( "/"+image, ti ) ); // NOI18N
1709

1710                        if(firstOnly) break; //return only the first found not-finished start token
1711
}
1712                    // if( ! tag.hasOptionalEnd() ) break; // If this tag have required EndTag, we can't go higher until completing this tag
1713
} else { // not empty - we match content of stack
1714
if( stack.peek().equals( image ) ) { // match - close this branch of document tree
1715
stack.pop();
1716                    } // else if( ! tag.hasOptionalEnd() ) break; // we reached error in document structure, give up
1717
}
1718                
1719                // this is error - end of empty tag
1720
if (ti.getBodyContent().equalsIgnoreCase(TagInfo.BODY_CONTENT_EMPTY))
1721                    continue;
1722                
1723                // if( tag.isEmpty() ) continue; // ignore empty Tags - they are like start and imediate end
1724

1725            }
1726        }
1727        
1728        return result;
1729    }
1730    
1731    public List getAutocompletedEndTag(int offset) {
1732        List l = new ArrayList();
1733        try {
1734            SyntaxElement elem = getElementChain( offset - 1);
1735            if(elem != null && elem instanceof SyntaxElement.Tag) {
1736                String JavaDoc tagName = ((SyntaxElement.Tag)elem).getName();
1737                HTMLCompletionQuery.ResultItem eti = new HTMLCompletionQuery.AutocompleteEndTagItem(tagName, offset, false);
1738                l.add(eti);
1739            }
1740        }catch(BadLocationException JavaDoc e) {
1741            //just ignore
1742
}
1743        return l;
1744    }
1745    
1746    public FileObject getFileObject() {
1747        return fobj;
1748    }
1749    
1750    /** Get the bracket finder that will search for the matching bracket
1751     * or null if the bracket character doesn't belong to bracket
1752     * characters.
1753     *
1754     * Customized finder recognizes also '<' and '>' as bracket chars. It is set to be used
1755     * in findMatchingBlock.
1756     */

1757    protected ExtSyntaxSupport.BracketFinder getMatchingBracketFinder(char bracketChar) {
1758        if (useCustomBracketFinder) {
1759            JspSyntaxSupport.BracketFinder bf = new JspSyntaxSupport.BracketFinder(bracketChar);
1760            return bf.isValid()? bf: null;
1761        } else{
1762            return super.getMatchingBracketFinder(bracketChar);
1763        }
1764    }
1765    
1766    /** Find matching bracket or more generally block
1767     * that matches with the current position.
1768     * @param offset position of the starting bracket
1769     * @param simple whether the search should skip comment and possibly other areas.
1770     * This can be useful when the speed is critical, because the simple
1771     * search is faster.
1772     * @return array of integers containing starting and ending position
1773     * of the block in the document. Null is returned if there's
1774     * no matching block.
1775     */

1776    public int[] findMatchingBlock(int offset, boolean simpleSearch)
1777            throws BadLocationException JavaDoc {
1778        
1779        int [] r_value = null;
1780        
1781        TokenItem token = getItemAtOrBefore((offset<getDocument().getLength())?offset+1:offset);
1782        if (token != null){
1783            if (token.getTokenContextPath().contains(HTMLTokenContext.contextPath)){
1784                r_value = getContentLanguageSyntaxSupport().findMatchingBlock(offset, simpleSearch);
1785            } else {
1786                //do we need to match jsp comment?
1787
if (token.getTokenID() == JspTagTokenContext.COMMENT) {
1788                    return findMatchingJspComment(token, offset);
1789                }
1790                // Is it matching of scriptlet delimiters?
1791
if (token.getTokenContextPath().contains(JspTagTokenContext.contextPath)
1792                        && token.getTokenID().getNumericID() == JspTagTokenContext.SYMBOL2_ID){
1793                    return findMatchingScripletDelimiter(token);
1794                }
1795                // Try to match the tags.
1796
if (token.getTokenContextPath().contains(JspTagTokenContext.contextPath)){
1797                    return findMatchingTag(token);
1798                } else {
1799                    if (isScriptingOrContentToken(token)) {
1800                        useCustomBracketFinder = false;
1801                    } else {
1802                        useCustomBracketFinder = true;
1803                    }
1804                    r_value = super.findMatchingBlock(offset, simpleSearch);
1805                }
1806            }
1807        }
1808        
1809        return r_value;
1810    }
1811    
1812    private int[] findMatchingJspComment(TokenItem token, int offset) {
1813        String JavaDoc tokenImage = token.getImage();
1814        if(tokenImage.startsWith("<%--") && (offset < (token.getOffset()) + "<%--".length())) { //NOI18N
1815
//start html token - we need to find the end token of the html comment
1816
while(token != null) {
1817                if((token.getTokenID() == JspTagTokenContext.COMMENT)
1818                        || (token.getTokenID() == JspTagTokenContext.EOL)) {
1819                    if(token.getImage().endsWith("--%>")) { //NOI18N
1820
//found end token
1821
int start = token.getOffset() + token.getImage().length() - "--%>".length(); //NOI18N
1822
int end = token.getOffset() + token.getImage().length();
1823                        return new int[] {start, end};
1824                    }
1825                } else break;
1826                token = token.getNext();
1827            }
1828        } else if(tokenImage.endsWith("--%>") && (offset >= (token.getOffset()) + token.getImage().length() - "--%>".length())) { //NOI18N
1829
//end html token - we need to find the start token of the html comment
1830
while(token != null) {
1831                if((token.getTokenID() == JspTagTokenContext.COMMENT)
1832                        || (token.getTokenID() == JspTagTokenContext.EOL)) {
1833                    if(token.getImage().startsWith("<%--")) { //NOI18N
1834
//found end token
1835
int start = token.getOffset();
1836                        int end = token.getOffset() + "<%--".length(); //NOI18N
1837
return new int[] {start, end};
1838                    }
1839                } else break;
1840                token = token.getPrevious();
1841            }
1842        }
1843        return null;
1844    }
1845    
1846    private int [] findMatchingScripletDelimiter(TokenItem token){
1847        if (token.getImage().charAt(0) == '<'){
1848            do{
1849                token = token.getNext();
1850            } while (token != null
1851                    && !(token.getTokenContextPath().contains(JspTagTokenContext.contextPath)
1852                    &&token.getTokenID().getNumericID() == JspTagTokenContext.SYMBOL2_ID));
1853        } else {
1854            do{
1855                token = token.getPrevious();
1856            } while (token != null
1857                    && !(token.getTokenContextPath().contains(JspTagTokenContext.contextPath)
1858                    &&token.getTokenID().getNumericID() == JspTagTokenContext.SYMBOL2_ID));
1859        }
1860        if (token != null){
1861            return new int [] {token.getOffset(), token.getOffset() + token.getImage().length()};
1862        }
1863        return null;
1864    }
1865    
1866    private int[] findMatchingTag(TokenItem token){
1867        // TODO - replanning to the other thread. Now it's in awt thread
1868
// if the curret is after jsp tag ( after the char '>' ), ship inside the tag
1869
if (token != null && token.getTokenID().getNumericID() == JspTagTokenContext.SYMBOL_ID
1870                && token.getImage().charAt(token.getImage().length()-1) == '>')
1871            token = token.getPrevious();
1872        boolean isInside = false; // flag, whether the curret is somewhere in a jsp tag
1873
if (token != null
1874                && ((token.getTokenID().getNumericID() == JspTagTokenContext.TAG_ID && token.getImage().trim().length() > 0)
1875                || (token.getTokenID().getNumericID() == JspTagTokenContext.SYMBOL_ID && token.getImage().charAt(0)=='<'))){
1876            if (token.getTokenID().getNumericID() == JspTagTokenContext.SYMBOL_ID) // the starting of the jsp tag
1877
// we are somewhere at beginning of the jsp tag. Find out the token with the jsp tag.
1878
while (token !=null
1879                && !(token.getTokenID().getNumericID() == JspTagTokenContext.TAG_ID
1880                && token.getImage().trim().length() > 0))
1881                    token = token.getNext(); // move at the jsp tag
1882
isInside = true; // the curret is somewhere in '<jsptag' or '</jsptag'
1883
} else {
1884            // find out whether the curret is inside a jsp tag
1885
if (token != null
1886                    && !(token.getTokenID().getNumericID() == JspTagTokenContext.TAG_ID && token.getImage().trim().length() > 0)){
1887                token = token.getPrevious();
1888                //try to find the beginning of the tag.
1889
while (token!=null
1890                        && !(token.getTokenID().getNumericID() == JspTagTokenContext.TAG_ID
1891                        && token.getImage().trim().length() > 0) // this is hack, because a whitspaces are returned are with TAG_ID
1892
&& !(token.getTokenID().getNumericID() == JspTagTokenContext.SYMBOL_ID
1893                        && token.getImage().charAt(token.getImage().length()-1) == '>'))
1894                    token = token.getPrevious();
1895                if (token!=null && token.getTokenID().getNumericID() == JspTagTokenContext.TAG_ID)
1896                    isInside = true;
1897            }
1898        }
1899        // Now we have the begining of the tag and we can start with the finding opposit tag.
1900
if (token != null && token.getTokenID().getNumericID() == JspTagTokenContext.TAG_ID && isInside){
1901            
1902            int start; // possition where the matched tag starts
1903
int end; // possition where the matched tag ends
1904
int poss = 0; // how many the same tags is inside the mathed tag
1905
String JavaDoc tag = token.getImage().trim();
1906            
1907            while (token != null && token.getTokenID().getNumericID() != JspTagTokenContext.SYMBOL_ID) {
1908                token = token.getPrevious();
1909            }
1910            if (token == null)
1911                return null;
1912            if ((token.getImage().length() == 2) && token.getImage().charAt(1) == '/'){
1913                while ( token != null){
1914                    if (token.getTokenID().getNumericID() == JspTagTokenContext.TAG_ID) {
1915                        if (token.getImage().trim().equals(tag)){
1916                            while (token != null && token.getTokenID().getNumericID() != JspTagTokenContext.SYMBOL_ID) {
1917                                token = token.getPrevious();
1918                            }
1919                            if (token != null) {
1920                                if (token.getImage().length() == 1){
1921                                    if (poss == 0){
1922                                        start = token.getOffset();
1923                                        token = token.getNext();
1924                                        end = token.getOffset()+token.getImage().length();
1925                                        
1926                                        //add ending > or /> into the selection
1927
TokenItem next = token.getNext();
1928                                        if(next != null && next.getTokenID() == JspTagTokenContext.SYMBOL && next.getImage().endsWith(">"))
1929                                            end += next.getImage().length();
1930                                        
1931                                        return new int[] {start, end};
1932                                    } else {
1933                                        poss++;
1934                                    }
1935                                }
1936                                if (token.getImage().length() == 2){
1937                                    poss--;
1938                                }
1939                            }
1940                            
1941                        }
1942                    }
1943                    token = token.getPrevious();
1944                }
1945                
1946            } else{
1947                if ((token.getImage().length() == 1) && token.getImage().charAt(0) == '<'){
1948                    poss = 1;
1949                    TokenItem hToken;
1950                    while ( token != null){
1951                        if (token.getTokenID().getNumericID() == JspTagTokenContext.TAG_ID) {
1952                            if (token.getImage().trim().equals(tag)){
1953                                hToken = token;
1954                                while (token != null && token.getTokenID().getNumericID() != JspTagTokenContext.SYMBOL_ID) {
1955                                    token = token.getPrevious();
1956                                }
1957                                if (token != null) {
1958                                    if (token.getImage().length() == 2){
1959                                        if (poss == 0){
1960                                            start = token.getOffset();
1961                                            end = hToken.getOffset()+hToken.getImage().length()+1;
1962                                            token = token.getNext();
1963                                            
1964                                            while (token != null && (token.getTokenID().getNumericID() != JspTagTokenContext.SYMBOL_ID
1965                                                    || token.getImage().charAt(0)!='>')){
1966                                                token = token.getNext();
1967                                            }
1968                                            if (token != null)
1969                                                end = token.getOffset()+1;
1970                                            return new int[] {start, end};
1971                                        } else {
1972                                            poss++;
1973                                        }
1974                                    }
1975                                    if (token.getImage().length() == 1){
1976                                        poss--;
1977                                    }
1978                                }
1979                                token = hToken;
1980                            }
1981                        }
1982                        token = token.getNext();
1983                    }
1984                }
1985            }
1986        }
1987        return null;
1988    }
1989    
1990    /** Finds out whether the given tagTokenItem is a part of a singleton tag (e.g. <div style=""/>).
1991     * @tagTokenItem a token item whithin a tag
1992     * @return true is the token is a part of singleton tag
1993     */

1994    public boolean isSingletonTag(TokenItem tagTokenItem) {
1995        TokenItem ti = tagTokenItem;
1996        while(ti != null) {
1997            if(ti.getTokenID() == JspTagTokenContext.SYMBOL){
1998                if("/>".equals(ti.getImage())) { // NOI18N
1999
//it is a singleton tag => do not match
2000
return true;
2001                }
2002                if(">".equals(ti.getImage())) return false; // NOI18N
2003
}
2004            //break the loop on TEXT or on another open tag symbol
2005
//(just to prevent long loop in case the tag is not closed)
2006
if(ti.getTokenID() == JspTagTokenContext.TEXT) break;
2007            
2008            ti = ti.getNext();
2009        }
2010        return false;
2011    }
2012    
2013    /** Get the array of token IDs that should be skipped when
2014     * searching for matching bracket. It usually includes comments
2015     * and character and string constants. Returns empty array by default.
2016     */

2017    protected TokenID[] getBracketSkipTokens() {
2018        return JSP_BRACKET_SKIP_TOKENS;
2019    }
2020    
2021    public static TokenSequence tokenSequence(TokenHierarchy hi, Language language, int offset) {
2022        TokenSequence ts = hi.tokenSequence(language);
2023        if(ts == null) {
2024            //HTML language is not top level one
2025
ts = hi.tokenSequence();
2026            int diff = ts.move(offset);
2027            if(!ts.moveNext() && !ts.movePrevious()) {
2028                return null; //no token found
2029
} else {
2030                ts = ts.embedded(language);
2031            }
2032        }
2033        return ts;
2034    }
2035    
2036    
2037    /** Finder for the matching bracket. It gets the original bracket char
2038     * and searches for the appropriate matching bracket character.
2039     */

2040    public class BracketFinder extends ExtSyntaxSupport.BracketFinder {
2041        
2042        BracketFinder(char c) {
2043            super(c);
2044        }
2045        
2046        /** Check whether the bracketChar really contains
2047         * the bracket character. If so assign the matchChar
2048         * and moveCount variables.
2049         */

2050        protected boolean updateStatus() {
2051            if (super.updateStatus())
2052                return true;
2053            boolean valid = true;
2054            switch (bracketChar) {
2055                case '<':
2056                    matchChar = '>';
2057                    moveCount = +1;
2058                    break;
2059                case '>':
2060                    matchChar = '<';
2061                    moveCount = -1;
2062                    break;
2063                default:
2064                    valid = false;
2065            }
2066            return valid;
2067        }
2068        
2069        boolean isValid() {
2070            return (moveCount != 0);
2071        }
2072        
2073    }
2074    
2075    private static class TagNameComparator implements Comparator{
2076        
2077        public TagNameComparator() {}
2078        
2079        public int compare(Object JavaDoc o1, Object JavaDoc o2) {
2080            if (o1 == o2 || o1 == null || o2 == null){
2081                return 0;
2082            }
2083            String JavaDoc tagNameOne = ((TagInfo)o1).getTagName();
2084            String JavaDoc tagNameTwo = ((TagInfo)o2).getTagName();
2085            if (tagNameOne == null || tagNameTwo == null){
2086                return 0;
2087            }
2088            return tagNameOne.compareTo(tagNameTwo);
2089        }
2090    }
2091}
2092
Popular Tags