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-2000 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.editor.ext.html;

import java.util.*;
import java.awt.Color;
import javax.swing.text.JTextComponent;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;

import org.netbeans.editor.*;
import org.netbeans.editor.Settings;
import org.netbeans.editor.SettingsChangeEvent;
import org.netbeans.editor.SettingsChangeListener;
import org.netbeans.editor.SettingsUtil;
import org.netbeans.editor.Utilities;
import org.netbeans.editor.ext.*;
import org.netbeans.editor.ext.html.dtd.*;

/**
 * HTML completion results finder
 *
 * @author Petr Nejedly
 * @version 1.00
 */
public class HTMLCompletionQuery implements CompletionQuery {
    
    private static boolean lowerCase;
    
    /** Perform the query on the given component. The query usually
     * gets the component's document, the caret position and searches back
     * to examine surrounding context. Then it returns the result.
     * @param component the component to use in this query.
     * @param offset position in the component's document to which the query will
     *   be performed. Usually it's a caret position.
     * @param support syntax-support that will be used during resolving of the query.
     * @return result of the query or null if there's no result.
     */
    public CompletionQuery.Result query(JTextComponent component, int offset, SyntaxSupport support) {
        Class kitClass = Utilities.getKitClass(component);
        if (kitClass != null) {
            lowerCase = SettingsUtil.getBoolean(kitClass,
            HTMLSettingsNames.COMPLETION_LOWER_CASE,
            HTMLSettingsDefaults.defaultCompletionLowerCase);
        }
        BaseDocument doc = (BaseDocument)component.getDocument();
        if( doc.getLength() == 0 ) return null; // nothing to examine
        HTMLSyntaxSupport sup = (HTMLSyntaxSupport)support.get(HTMLSyntaxSupport.class);
        if( sup == null ) return null;// No SyntaxSupport for us, no hint for user
        DTD dtd = sup.getDTD();
        if( dtd == null ) return null; // We have no knowledge about the structure!
        
        try {
            TokenItem item = null;
            TokenItem prev = null;
            // are we inside token or between tokens
            boolean inside = false;
            
            item = sup.getTokenChain( offset, offset+1 );
            
            if( item != null ) { // inside document
                prev = item.getPrevious();
                // this part of the code is smartcase deciding
                if (prev != null){
                    TokenItem prevv = prev;
                    String prevvImage = prevv.getImage();
                    int index = prevvImage.length() - 1;
                    // is in the previous tag a letter?
                    if (prevv != null && prevv.getTokenID().getNumericID() == HTMLTokenContext.TAG_ID
                            && prevv.getTokenID().getNumericID() == HTMLTokenContext.ARGUMENT_ID){
                        while (index > -1 && !Character.isLetter(prevvImage.charAt(index)))
                            index--;
                    }
                    else
                        index = -1;
                    // if not find first tag with a letter
                    while (index == -1 && prevv != null){
                        while (prevv != null
                        && ((prevv.getTokenID().getNumericID() != HTMLTokenContext.TAG_ID
                        && prevv.getTokenID().getNumericID() != HTMLTokenContext.ARGUMENT_ID)
                        || prevv.getImage().trim().equals(">"))){ // NOI18N
                            prevv = prevv.getPrevious();
                        }
                        if (prevv != null){
                            prevvImage = prevv.getImage();
                            index = 0;
                            while (index < prevvImage.length() && !Character.isLetter(prevvImage.charAt(index)))
                                index++;
                            if (index == prevvImage.length()){
                                index = -1;
                                prevv = prevv.getPrevious();
                            }
                        }
                    }
                    // is there a previous tag with a letter?
                    if (prevv != null && index != -1){
                        lowerCase = !Character.isUpperCase(prevvImage.charAt(index));
                    }
                    else{
                        lowerCase = true;
                    }

                }
                // end of smartcase deciding
                inside = item.getOffset() < offset;
            } else {                           // @ end of document
                prev = sup.getTokenChain( offset-1, offset ); //!!!
            }
            boolean begin = (prev == null && !inside);
/*
if( prev == null && !inside ) System.err.println( "Beginning of document, first token = " + item.getTokenID() );
else if( item == null ) System.err.println( "End of document, last token = " + prev.getTokenID() );
else if( ! inside ) System.err.println( "Between tokens " + prev.getTokenID() + " and " + item.getTokenID() );
else System.err.println( "Inside token " + item.getTokenID() );
 */
            
            if( begin ) return null;
            
            TokenID id = null;
            List l = null;
            int len = 1;
            int itemOffset = 0;
            String preText = null;
            
            if( inside ) {
                id = item.getTokenID();
                preText = item.getImage().substring( 0, offset - item.getOffset() );
                itemOffset = item.getOffset();
            } else {
                id = prev.getTokenID();
                preText = prev.getImage().substring( 0, offset - prev.getOffset() );
                itemOffset = prev.getOffset();
            }
/* Here are completion finders, each have its own set of rules and source of results
 * They are now written just for testing rules, I will rewrite them to more compact
 * and faster, tree form, as soon as i'll have them all.
 */
            
            /* Character reference finder */
            if( (id == HTMLTokenContext.TEXT || id == HTMLTokenContext.VALUE) && preText.endsWith( "&" ) ) { // NOI18N
                l = translateCharRefs( offset-len, len, dtd.getCharRefList( "" ) );
            } else if( id == HTMLTokenContext.CHARACTER ) {
                if( inside || !preText.endsWith( ";" ) ) { // NOI18N
                    len = offset - itemOffset;
                    l = translateCharRefs( offset-len, len, dtd.getCharRefList( preText.substring( 1 ) ) );
                }
                /* Tag finder */
            } else if( id == HTMLTokenContext.TEXT && preText.endsWith( "<" ) ) { // NOI18N
                // There will be lookup for possible StartTags, in SyntaxSupport
                //                l = translateTags( offset-len, len, sup.getPossibleStartTags ( offset-len, "" ) );
                l = translateTags( offset-len, len, dtd.getElementList( "" ) );
                
                //System.err.println("l = " + l );
            } else if( id == HTMLTokenContext.TAG && preText.startsWith( "<" ) && !preText.startsWith( " from the tag name
                    
                    if(preText.substring(1).equals(itemText)) {
                        //now I have to look ahead to get know whether
                        //there are some attributes or an end of the tag
                        
                        //define how far to look ahead
                        int lookLenght = 10; //default - thought up
                        if(offset + lookLenght > doc.getLength()) lookLenght = doc.getLength() - offset;
                        
                        TokenItem aheadChainToken = sup.getTokenChain( offset, offset+lookLenght );
                        //test if next token is a whitespace and the next a tag token or an attribute token
                        if(aheadChainToken != null && aheadChainToken.getTokenID().getNumericID() == HTMLTokenContext.WS_ID) {
                            aheadChainToken = aheadChainToken.getNext();
                            if(aheadChainToken != null &&
                            (aheadChainToken.getTokenID().getNumericID() == HTMLTokenContext.TAG_ID ||
                            aheadChainToken.getTokenID().getNumericID() == HTMLTokenContext.ARGUMENT_ID )) {
                                //do not put the item into CC - otherwise it will break the completed tag
                                l = null;
                            }
                        }
                    }
                }
                
                /* EndTag finder */
            } else if( id == HTMLTokenContext.TEXT && preText.endsWith( "
                if( elem.getType() == SyntaxElement.TYPE_ERROR ) {
                    elem = elem.getPrevious();
                    if( elem == null ) return null;
                }
                if( elem.getType() == SyntaxElement.TYPE_TAG ) {
                    SyntaxElement.Tag tagElem = (SyntaxElement.Tag)elem;
                    
                    String tagName = tagElem.getName().toUpperCase();
                    DTD.Element tag = dtd.getElement( tagName );
                    if( tag == null ) return null; // unknown tag
                    
                    TokenItem argItem = prev;
                    while( argItem != null && argItem.getTokenID() != HTMLTokenContext.ARGUMENT ) argItem = argItem.getPrevious();
                    if( argItem == null ) return null; // no ArgItem
                    String argName = argItem.getImage().toLowerCase();
                    
                    DTD.Attribute arg = tag.getAttribute( argName );
                    if( arg == null || arg.getType() != DTD.Attribute.TYPE_SET ) return null;
                    
                    if( id != HTMLTokenContext.VALUE ) {
                        len = 0;
                        l = translateValues( offset-len, len, arg.getValueList( "" ) );
                    } else {
                        len = offset - itemOffset;
                        l = translateValues( offset-len, len, arg.getValueList( preText ) );
                    }
                }
            }
            
            //System.err.println("l = " + l );
            if( l == null ) return null;
            else return new CompletionQuery.DefaultResult( component, "Results for DOCTYPE " + dtd.getIdentifier(), l, offset, len ); // NOI18N
            
        } catch (BadLocationException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    
    List translateCharRefs( int offset, int length, List refs ) {
        List result = new ArrayList( refs.size() );
        for( Iterator i = refs.iterator(); i.hasNext(); ) {
            result.add( new CharRefItem( ((DTD.CharRef)i.next()).getName(), offset, length ));
        }
        return result;
    }
    
    List translateTags( int offset, int length, List tags ) {
        List result = new ArrayList( tags.size() );
        for( Iterator i = tags.iterator(); i.hasNext(); ) {
            result.add( new TagItem( ((DTD.Element)i.next()).getName(), offset, length ));
        }
        return result;
    }
    
    List translateAttribs( int offset, int length, List attribs ) {
        List result = new ArrayList( attribs.size() );
        for( Iterator i = attribs.iterator(); i.hasNext(); ) {
            DTD.Attribute attrib = (DTD.Attribute)i.next();
            String name = attrib.getName();
            switch( attrib.getType() ) {
                case DTD.Attribute.TYPE_BOOLEAN:
                    result.add( new BooleanAttribItem( name, offset, length, attrib.isRequired() ) );
                    break;
                case DTD.Attribute.TYPE_SET:
                    result.add( new SetAttribItem( name, offset, length, attrib.isRequired() ) );
                    break;
                case DTD.Attribute.TYPE_BASE:
                    result.add( new PlainAttribItem( name, offset, length, attrib.isRequired() ) );
                    break;
            }
        }
        return result;
    }
    
    List translateValues( int offset, int length, List values ) {
        if( values == null ) return new ArrayList( 0 );
        List result = new ArrayList( values.size() );
        for( Iterator i = values.iterator(); i.hasNext(); ) {
            result.add( new ValueItem( ((DTD.Value)i.next()).getName(), offset, length ));
        }
        return result;
    }
    
    
    // Implementation of ResultItems for completion
    /** The simple result item operating over an instance of the string,
     * it is lightweight in the mean it doesn't allocate any new instances
     * of anything and every data creates lazily on request to avoid
     * creation of lot of string instances per completion result.
     */
    private static abstract class HTMLResultItem implements CompletionQuery.ResultItem {
        /** The Component used as a rubberStamp for painting items */
        static javax.swing.JLabel rubberStamp = new javax.swing.JLabel();
        
        static {
            rubberStamp.setOpaque( true );
        }
        
        /** The String on which is this ResultItem defined */
        String baseText;
        /** the remove and insert point for this item */
        int offset;
        /** The length of the text to be removed */
        int length;
        
        public HTMLResultItem( String baseText, int offset, int length ) {
            this.baseText = lowerCase ? baseText.toLowerCase() : baseText.toUpperCase();
            this.offset = offset;
            this.length = length;
        }
        
        boolean replaceText( JTextComponent component, String text ) {
            BaseDocument doc = (BaseDocument)component.getDocument();
            doc.atomicLock();
            try {
                doc.remove( offset, length );
                doc.insertString( offset, text, null);
            } catch( BadLocationException exc ) {
                return false;    //not sucessfull
            } finally {
                doc.atomicUnlock();
            }
            return true;
        }
        
        public boolean substituteCommonText( JTextComponent c, int a, int b, int subLen ) {
            return replaceText( c, getItemText().substring( 0, subLen ) );
        }
        
        public boolean substituteText( JTextComponent c, int a, int b, boolean shift ) {
            return replaceText( c, getItemText() );
        }
        
        /** @return Properly colored JLabel with text gotten from getPaintText(). */
        public java.awt.Component getPaintComponent( javax.swing.JList list, boolean isSelected, boolean cellHasFocus ) {
            // The space is prepended to avoid interpretation as HTML Label
            rubberStamp.setText( " " + getPaintText() );  // NOI18N
            if (isSelected) {
                rubberStamp.setBackground(list.getSelectionBackground());
                rubberStamp.setForeground(list.getSelectionForeground());
            } else {
                rubberStamp.setBackground(list.getBackground());
                rubberStamp.setForeground( getPaintColor() );
            }
            return rubberStamp;
        }
        
        /** The string used in painting by getPaintComponent().
         * It defaults to delegate to getItemText().
         * @return The String to be painted in Completion View.
         */
        String getPaintText() { return getItemText(); }
        
        abstract Color getPaintColor();
        
        /** @return The String used for looking up the common part of multiple
         * items and for default way of replacing the text */
        public String getItemText() { return baseText; }
    }
    
    static class EndTagItem extends HTMLResultItem {
        
        public EndTagItem( String baseText, int offset, int length ) {
            super( baseText, offset, length );
        }
        
        Color getPaintColor() { return Color.blue; }
        
        public String getItemText() { return ""; } // NOI18N
        
        public boolean substituteText( JTextComponent c, int a, int b, boolean shift ) {
            return super.substituteText( c, a, b, shift );
        }
    }
    
    private static class CharRefItem extends HTMLResultItem {
        
        public CharRefItem( String name, int offset, int length ) {
            super( name, offset, length );
        }
        
        Color getPaintColor() { return Color.red.darker(); }
        
        public String getItemText() { return "&" + baseText + ";"; } // NOI18N
    }
    
    private static class TagItem extends HTMLResultItem {
        
        public TagItem( String name, int offset, int length ) {
            super( name, offset, length );
        }
        
        public boolean substituteText( JTextComponent c, int a, int b, boolean shift ) {
            replaceText( c, "<" + baseText + (shift ? " >" : ">") ); // NOI18N
            if( shift ) {
                Caret caret = c.getCaret();
                caret.setDot( caret.getDot() - 1 );
            }
            return !shift; // flag == false;
        }
        
        Color getPaintColor() { return Color.blue; }
        
        public String getItemText() { return "<" + baseText + ">"; } // NOI18N
    }
    
    private  static class SetAttribItem extends HTMLResultItem {
        boolean required;
        
        public SetAttribItem( String name, int offset, int length, boolean required ) {
            super( name, offset, length );
            this.required = required;
        }
        
        Color getPaintColor() { return required ? Color.red : Color.green.darker(); }
        
        String getPaintText() { return baseText; }
        
        public String getItemText() { return baseText + "="; } //NOI18N
        
        public boolean substituteText( JTextComponent c, int a, int b, boolean shift ) {
            super.substituteText( c, 0, 0, shift );
            return false; // always refresh
        }
    }
    
    private static class BooleanAttribItem extends HTMLResultItem {
        
        boolean required;
        
        public BooleanAttribItem( String name, int offset, int length, boolean required ) {
            super( name, offset, length );
            this.required = required;
        }
        
        Color getPaintColor() { return required ? Color.red : Color.green.darker(); }
        
        
        public boolean substituteText( JTextComponent c, int a, int b, boolean shift ) {
            replaceText( c, shift ? baseText + " " : baseText ); // NOI18N
            return false; // always refresh
        }
    }
    
    private static class PlainAttribItem extends HTMLResultItem {
        
        boolean required;
        
        public PlainAttribItem( String name, int offset, int length, boolean required ) {
            super( name, offset, length );
            this.required = required;
        }
        
        Color getPaintColor() { return required ? Color.red : Color.green.darker(); }
        
        public boolean substituteText( JTextComponent c, int a, int b, boolean shift ) {
            replaceText( c, baseText + "=''" ); //NOI18N
            if( shift ) {
                Caret caret = c.getCaret();
                caret.setDot( caret.getDot() - 1 );
            }
            return false; // always refresh
        }
    }
    
    private static class ValueItem extends HTMLResultItem {
        
        public ValueItem( String name, int offset, int length ) {
            super( name, offset, length );
        }
        
        Color getPaintColor() { return Color.magenta; }
        
        public boolean substituteText( JTextComponent c, int a, int b, boolean shift ) {
            replaceText( c, shift ? baseText + " " : baseText ); // NOI18N
            return !shift;
        }
    }
}
... 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.