alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.modules.xml.text.completion;

import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;

import org.netbeans.editor.*;
import org.netbeans.editor.ext.*;
import org.netbeans.modules.xml.api.model.HintContext;
import org.netbeans.modules.xml.text.syntax.*;
import org.netbeans.modules.xml.text.syntax.dom.*;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.openide.util.WeakListeners;
import org.openide.ErrorManager;

/**
 * Helper class used in XMLCompletionQuery and other classes that use grammar
 * Instances of this class must only be constructed and used from the AWT
 * dispatch thread, because this implementation is not reentrant (see ctx field).
 *
 * @author  asgeir@dimonsoftware.com
 */
final class SyntaxQueryHelper {
    
    public final static int COMPLETION_TYPE_UNKNOWN = 0;
    public final static int COMPLETION_TYPE_ATTRIBUTE = 1;
    public final static int COMPLETION_TYPE_VALUE = 2;
    public final static int COMPLETION_TYPE_ELEMENT = 3;
    public final static int COMPLETION_TYPE_ENTITY = 4;
    public final static int COMPLETION_TYPE_NOTATION = 5;
    public final static int COMPLETION_TYPE_DTD = 6;

    /** Currect oken or previous one if at token boundary */
    private TokenItem token = null;
    
    private String preText = "";
    
    private int erase = 0;
    
    private int tunedOffset = 0;
    
    private SyntaxElement element;
    
    private int completionType = 0;
    
    private boolean tokenBoundary;

    // shared context instance, - we are always called from AWT thread
    private static DefaultContext ctx = new DefaultContext();

    /** Creates a new instance of SyntaxQueryHelper */
    public SyntaxQueryHelper(XMLSyntaxSupport sup, int offset) throws BadLocationException, IllegalStateException {
        tunedOffset = offset;
        token = sup.getPreviousToken( tunedOffset);
        if( token != null ) { // inside document
            tokenBoundary = token.getOffset() + token.getImage().length() == tunedOffset;
        } else {
            //??? start of document no choice now, but should be prolog if not followed by it
            throw new BadLocationException("No token found at current position", offset); // NOI18N
        }

        // find out last typed chars that can hint

        int itemOffset = token.getOffset();
        preText = "";
        erase = 0;
        int eraseRight = 0;
        int id = token.getTokenID().getNumericID();

        // determine last typed text, prefix text

        if ( tokenBoundary == false ) {

            preText = token.getImage().substring( 0, tunedOffset - token.getOffset() );
            if ("".equals(preText)) throw new IllegalStateException("Cannot get token prefix at " + tunedOffset);

            // manipulate tunedOffset to delete rest of an old name
            // for cases where it iseasy to locate original name end

            if (sup.lastTypedChar() != '<' && sup.lastTypedChar() != '&') {
                switch (id) {

                    case XMLDefaultTokenContext.TAG_ID:
                    case XMLDefaultTokenContext.CHARACTER_ID:
                    case XMLDefaultTokenContext.ARGUMENT_ID:

                        int i = token.getImage().length();
                        int tail = i - (tunedOffset - itemOffset);
                        tunedOffset += tail;
                        eraseRight = tail;
                        break;
                }
            }
         } else {
           switch (id) {
                case XMLDefaultTokenContext.TEXT_ID:
                case XMLDefaultTokenContext.TAG_ID:
                case XMLDefaultTokenContext.ARGUMENT_ID:
                case XMLDefaultTokenContext.CHARACTER_ID:
                case XMLCompletionQuery.PI_CONTENT_ID:
                    preText = token.getImage();
                    break;                        
            }
         }

        // adjust how much do you want to erase from the preText

        switch (id) {
            case XMLDefaultTokenContext.TAG_ID:
            case XMLDefaultTokenContext.CHARACTER_ID:
                // do not erase start delimiters
                erase = preText.length() - 1 + eraseRight;
                break;

            case XMLDefaultTokenContext.ARGUMENT_ID:
                erase = preText.length() + eraseRight;
                break;
            case XMLDefaultTokenContext.VALUE_ID:
                erase = preText.length();
                if (erase > 0 && (preText.charAt(0) == '\'' || preText.charAt(0) == '"')) {
                    // Because of attribute values, preText is adjusted in initContext
                    erase--;
                } else
                break;
        }

        element =  sup.getElementChain( tunedOffset);
        
        if (element == null) throw new IllegalStateException("There exists a token therefore a syntax element must exist at " + offset + ", too.");

        // completion request originates from area covered by DOM, 
        if (element instanceof SyntaxNode && ((SyntaxNode)element).getNodeType() != Node.DOCUMENT_TYPE_NODE) {
            completionType = initContext();
        } else {
            // prolog, internal DTD no completition yet
            completionType = COMPLETION_TYPE_DTD;
        }
    }
    
    /**
     * Find out what to complete: attribute, value, element, entity or notation?
     * 

*

     * Triggering criteria:
     *
     * ELEMENT      TOKEN (,=seq)   PRETEXT         QUERY
     * -------------------------------------------------------------------
     * Text         text            <            element name
     * Text         text            </           pairing end element
     * StartTag     tag             <prefix      element name
     * StartTag     ws                              attribute name
     * StartTag     attr, operator  =               quoted attribute value
     * StartTag     value           'prefix         attribute value
     * StartTag     tag             >               element value
     * Text         text            &           entity ref name     
     * StartTag     value           &           entity ref name
     * 
* * @return the type of completion which is one of * COMPLETION_TYPE_UNKNOWN = 0, * COMPLETION_TYPE_ATTRIBUTE = 1, * COMPLETION_TYPE_VALUE = 2, * COMPLETION_TYPE_ELEMENT = 3, * COMPLETION_TYPE_ENTITY = 4, * COMPLETION_TYPE_NOTATION = 5. */ private int initContext() { int id = token.getTokenID().getNumericID(); SyntaxNode syntaxNode = (SyntaxNode)element; switch ( id) { case XMLDefaultTokenContext.TEXT_ID: if ( preText.endsWith("<" )) { ctx.init(syntaxNode, ""); return COMPLETION_TYPE_ELEMENT; } else if ( preText.startsWith("&")) { ctx.init(syntaxNode, preText.substring(1)); return COMPLETION_TYPE_ENTITY; } else { //??? join all previous texts? // No they are DOM nodes. ctx.init(syntaxNode, preText); return COMPLETION_TYPE_VALUE; } // break; case XMLDefaultTokenContext.TAG_ID: if ( StartTag.class.equals(syntaxNode.getClass()) || EmptyTag.class.equals(syntaxNode.getClass())) { if (preText.equals("")) { //??? should not occure if (token.getImage().endsWith(">")) { ctx.init(syntaxNode, preText); return COMPLETION_TYPE_VALUE; } else { ctx.init(syntaxNode, preText); return COMPLETION_TYPE_ELEMENT; } } else if (preText.endsWith("/>")) { ctx.init(syntaxNode, ""); return COMPLETION_TYPE_VALUE; } else if (preText.endsWith(">")) { ctx.init(syntaxNode, ""); return COMPLETION_TYPE_VALUE; } else if (preText.startsWith("")) { ctx.init(syntaxNode, preText); return COMPLETION_TYPE_VALUE; } } break; case XMLDefaultTokenContext.VALUE_ID: if (preText.endsWith("&")) { ctx.init(syntaxNode, ""); return COMPLETION_TYPE_ENTITY; } else if ("".equals(preText)) { //??? improve check to addres inner '"' String image = token.getImage(); char ch = image.charAt(image.length()-1); // findout if it is closing ' if (ch == '\'' || ch == '"') { if (image.charAt(0) == ch && image.length() > 1) { // we got whole quoted value as single token ("xxx"|) return COMPLETION_TYPE_UNKNOWN; } boolean closing = false; TokenItem prev = token.getPrevious(); while (prev != null) { int tid = prev.getTokenID().getNumericID(); if (tid == XMLDefaultTokenContext.VALUE_ID) { closing = true; break; } else if (tid == XMLDefaultTokenContext.CHARACTER_ID) { prev = prev.getPrevious(); } else { break; } } if (closing == false) { ctx.init(syntaxNode, preText); return COMPLETION_TYPE_VALUE; } } else { ctx.init(syntaxNode, preText); return COMPLETION_TYPE_VALUE; } } else { // This is probably an attribute value // Let's find the matching attribute node and use it to initialize the context NamedNodeMap attrs = syntaxNode.getAttributes(); int maxOffsetLessThanCurrent = -1; Node curAttrNode = null; for (int ind = 0; ind < attrs.getLength(); ind++) { AttrImpl attr = (AttrImpl)attrs.item(ind); int attrTokOffset = attr.getFirstToken().getOffset(); if (attrTokOffset > maxOffsetLessThanCurrent && attrTokOffset < token.getOffset()) { maxOffsetLessThanCurrent = attrTokOffset; curAttrNode = attr; } } // eliminate "'",'"' delimiters if (preText.length() > 0) { preText = preText.substring(1); } if (curAttrNode != null) { ctx.init(curAttrNode, preText); } else { ctx.init(syntaxNode, preText); } return COMPLETION_TYPE_VALUE; } break; case XMLDefaultTokenContext.OPERATOR_ID: if ("".equals(preText)) { if ("=".equals(token.getImage())) { ctx.init(syntaxNode, ""); return COMPLETION_TYPE_VALUE; } } break; case XMLDefaultTokenContext.WS_ID: if ((StartTag.class.equals(syntaxNode.getClass()) || EmptyTag.class.equals(syntaxNode.getClass())) && !token.getImage().startsWith("/")) { ctx.init((Element)syntaxNode, ""); // GrammarQuery.v2 takes Element ctx return COMPLETION_TYPE_ATTRIBUTE; } else { // end tag no attributes to complete return COMPLETION_TYPE_UNKNOWN; } // break; case XMLDefaultTokenContext.ARGUMENT_ID: if (StartTag.class.equals(syntaxNode.getClass()) || EmptyTag.class.equals(syntaxNode.getClass())) { ctx.init((Element)syntaxNode, preText); // GrammarQuery.v2 takes Element ctx return COMPLETION_TYPE_ATTRIBUTE; } break; case XMLDefaultTokenContext.CHARACTER_ID: // entity reference if (preText.startsWith("&#")) { // character ref, ignore return COMPLETION_TYPE_UNKNOWN; } else if (preText.endsWith(";")) { ctx.init(syntaxNode, ""); return COMPLETION_TYPE_VALUE; } else if (preText.startsWith("&")) { ctx.init(syntaxNode, preText.substring(1)); return COMPLETION_TYPE_ENTITY; } else if ("".equals(preText)) { if (token.getImage().endsWith(";")) { ctx.init(syntaxNode, preText); return COMPLETION_TYPE_VALUE; } } break; default: } // System.err.println("Cannot complete: " + syntaxNode + "\n\t" + token + "\n\t" + preText); return COMPLETION_TYPE_UNKNOWN; } public HintContext getContext() { if (completionType != COMPLETION_TYPE_UNKNOWN && completionType != COMPLETION_TYPE_DTD) { return ctx; } else { return null; } } /** Current token or previous one if at token boundary. */ public TokenItem getToken() { return token; } public String getPreText() { return preText; } public int getEraseCount() { return erase; } public int getOffset() { return tunedOffset; } public SyntaxElement getSyntaxElement() { return element; } public int getCompletionType() { return completionType; } /** token boundary */ public boolean isBoundary() { return tokenBoundary; } }
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.