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.java;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import javax.swing.text.BadLocationException;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;
import javax.swing.text.JTextComponent;
import org.netbeans.editor.SyntaxSupport;
import org.netbeans.editor.BaseDocument;
import org.netbeans.editor.Utilities;
import org.netbeans.editor.TextBatchProcessor;
import org.netbeans.editor.FinderFactory;
import org.netbeans.editor.Syntax;
import org.netbeans.editor.Analyzer;
import org.netbeans.editor.TokenID;
import org.netbeans.editor.TokenContextPath;
import org.netbeans.editor.ext.ExtSyntaxSupport;

/**
* Support methods for syntax analyzes
*
* @author Miloslav Metelka
* @version 1.00
*/

abstract public class JavaSyntaxSupport extends ExtSyntaxSupport {

    // Internal java declaration token processor states
    static final int INIT = 0;
    static final int AFTER_TYPE = 1;
    static final int AFTER_VARIABLE = 2;
    static final int AFTER_COMMA = 3;
    static final int AFTER_DOT = 4;
    static final int AFTER_TYPE_LSB = 5;
    static final int AFTER_MATCHING_VARIABLE_LSB = 6;
    static final int AFTER_MATCHING_VARIABLE = 7;
    static final int AFTER_EQUAL = 8; // in decl after "var ="

    private static final TokenID[] COMMENT_TOKENS = new TokenID[] {
                JavaTokenContext.LINE_COMMENT,
                JavaTokenContext.BLOCK_COMMENT
            };

    private static final TokenID[] BRACKET_SKIP_TOKENS = new TokenID[] {
                JavaTokenContext.LINE_COMMENT,
                JavaTokenContext.BLOCK_COMMENT,
                JavaTokenContext.CHAR_LITERAL,
                JavaTokenContext.STRING_LITERAL
            };

    private static final char[] COMMAND_SEPARATOR_CHARS = new char[] {
                ';', '{', '}'
            };

    private JavaImport javaImport;

    /** Whether java 1.5 constructs are recognized. */
    private boolean java15;

    public JavaSyntaxSupport(BaseDocument doc) {
        super(doc);

        tokenNumericIDsValid = true;
    }

    abstract protected JCFinder getFinder();
    
    protected JavaImport createJavaImport(){
        return new JavaImport(this);
    }
    
    protected void documentModified(DocumentEvent evt) {
        super.documentModified(evt);
        if (javaImport != null) {
            javaImport.documentModifiedAtPosition(evt.getOffset(), getDocument());
        }
    }
    
    protected void setJava15(boolean java15) {
        this.java15 = java15;
    }

    public TokenID[] getCommentTokens() {
        return COMMENT_TOKENS;
    }

    public TokenID[] getBracketSkipTokens() {
        return BRACKET_SKIP_TOKENS;
    }

    /** Return the position of the last command separator before
    * the given position.
    */
    public int getLastCommandSeparator(int pos)
    throws BadLocationException {
        TextBatchProcessor tbp = new TextBatchProcessor() {
                                     public int processTextBatch(BaseDocument doc, int startPos, int endPos,
                                                                 boolean lastBatch) {
                                         try {
                                             int[] blks = getCommentBlocks(endPos, startPos);
                                             FinderFactory.CharArrayBwdFinder cmdFinder
                                             = new FinderFactory.CharArrayBwdFinder(COMMAND_SEPARATOR_CHARS);
                                             int lastSeparatorOffset = findOutsideBlocks(cmdFinder, startPos, endPos, blks);
                                             if (lastSeparatorOffset<1) return lastSeparatorOffset;
                                             TokenID separatorID = getTokenID(lastSeparatorOffset);
                                             if (separatorID.getNumericID() == JavaTokenContext.RBRACE_ID) {
                                                 int matchingBrkPos = findMatchingBlock(lastSeparatorOffset, true)[0];
                                                 int prev = Utilities.getFirstNonWhiteBwd(getDocument(), matchingBrkPos);
                                                 if (getTokenID(prev).getNumericID() == JavaTokenContext.RBRACKET_ID)
                                                     return getLastCommandSeparator(prev);
                                             }
                                             if (separatorID.getNumericID() != JavaTokenContext.LBRACE_ID &&
                                                 separatorID.getNumericID() != JavaTokenContext.RBRACE_ID &&
                                                 separatorID.getNumericID() != JavaTokenContext.SEMICOLON_ID){
                                                     lastSeparatorOffset = processTextBatch(doc, lastSeparatorOffset, 0, lastBatch);
                                             }
                                             return lastSeparatorOffset;
                                         } catch (BadLocationException e) {
                                             e.printStackTrace();
                                             return -1;
                                         }
                                     }
                                 };
        return getDocument().processText(tbp, pos, 0);
    }

    /** Get the class from name. The import sections are consulted to find
    * the proper package for the name. If the search in import sections fails
    * the method can ask the finder to search just by the given name.
    * @param className name to resolve. It can be either the full name
    *   or just the name without the package.
    * @param searchByName if true and the resolving through the import sections fails
    *   the finder is asked to find the class just by the given name
    */
    public JCClass getClassFromName(String className, boolean searchByName) {
        refreshJavaImport();
        JCClass ret = JavaCompletion.getPrimitiveClass(className);
        if (ret == null) {
            
            ret = getJavaImport().getClazz(className);
        }
        if (ret == null && searchByName) {
            if (isUnknownImport(className)) return null;    
            List clsList = getFinder().findClasses(null, className, true);
            if (clsList != null && clsList.size() > 0) {
                if (clsList.size() > 0) { // more matching classes
                    ret = (JCClass)clsList.get(0); // get the first one
                }
            }

        }
        return ret;
    }
    
    public synchronized JavaImport getJavaImport(){
        if (javaImport == null) {
            javaImport = createJavaImport();
        }
        javaImport.update(getDocument());
        return javaImport;
    }
    
    protected boolean isUnknownImport(String className){
        return getJavaImport().isUnknownImport(className);
    }
    
    /** Returns all imports that aren't in Code Completion DB yet */
    protected List getUnknownImports(){
        return getJavaImport().getUnknownImports();
    }
    
    /** Returns true if the given class is in the import statement directly or
     *  indirectly (package.name.*) */    
    public boolean isImported(JCClass cls){
        return getJavaImport().isImported(cls);
    }

    public void refreshJavaImport() {
        if (javaImport != null) {
            javaImport.update(getDocument());
        }
    }

    protected void refreshClassInfo() {
    }

    protected List getImportedInnerClasses(){
        refreshJavaImport();
        return getJavaImport().getInnerClasses();
    }
    
    /** Get the class that belongs to the given position */
    public JCClass getClass(int pos) {
        return null;
    }

    public boolean isStaticBlock(int pos) {
        return false;
    }

    protected DeclarationTokenProcessor createDeclarationTokenProcessor(
        String varName, int startPos, int endPos) {
        return java15
            ? (DeclarationTokenProcessor)new JavaDeclarationProcessor(this, varName)
            : (DeclarationTokenProcessor)new JavaDeclarationTokenProcessor(this, varName);
    }

    protected VariableMapTokenProcessor createVariableMapTokenProcessor(
        int startPos, int endPos) {
        return java15
            ? (VariableMapTokenProcessor)new JavaDeclarationProcessor(this, null)
            : (VariableMapTokenProcessor)new JavaDeclarationTokenProcessor(this, null);
    }
    
    /** Checks, whether caret is inside method */
    private boolean insideMethod(JTextComponent textComp, int startPos){
        try{
            int level = 0;
            BaseDocument doc = (BaseDocument)textComp.getDocument();
            for(int i = startPos-1; i>0; i--){
                char ch = doc.getChars(i, 1)[0];
                if (ch == ';') return false;
                if (ch == ')') level++;
                if (ch == '('){
                    if (level == 0){
                        return true;
                    }else{
                        level--;
                    }
                }
            }
            return false;
        } catch (BadLocationException e) {
            return false;
        }
    }

    /** Check and possibly popup, hide or refresh the completion */
    public int checkCompletion(JTextComponent target, String typedText, boolean visible ) {
        if (!visible) { // pane not visible yet
            int dotPos = target.getCaret().getDot();                            
            switch (typedText.charAt(0)) {
                case ' ':
                    BaseDocument doc = (BaseDocument)target.getDocument();
                    
                    if (dotPos >= 2) { // last char before inserted space
                        int pos = Math.max(dotPos - 8, 0);
                        try {
                            String txtBeforeSpace = doc.getText(pos, dotPos - pos);
                            
                            if ( txtBeforeSpace.endsWith("import ") // NOI18N
                                && !Character.isJavaIdentifierPart(txtBeforeSpace.charAt(0))) {
                                return ExtSyntaxSupport.COMPLETION_POPUP;
                            }
                            
                            if (txtBeforeSpace.endsWith(", ")) { // NOI18N
                                // autoPopup completion only if caret is inside method
                                if (insideMethod(target, dotPos)) return ExtSyntaxSupport.COMPLETION_POPUP;
                            }
                        } catch (BadLocationException e) {
                        }
                    }
                    break;

                case '.':
                    return ExtSyntaxSupport.COMPLETION_POPUP;
                case ',':
                    // autoPopup completion only if caret is inside method
                    if (insideMethod(target, dotPos)) return ExtSyntaxSupport.COMPLETION_POPUP;
                default:
                    if (Character.isJavaIdentifierStart(typedText.charAt(0))) {
                        if (dotPos >= 5) { // last char before inserted space
                            try {
                                String maybeNew = target.getDocument().getText(dotPos - 5, 4);
                                if (maybeNew.equals("new ")){ // NOI18N
                                    return ExtSyntaxSupport.COMPLETION_POPUP;
                                }
                            } catch (BadLocationException e) {
                            }
                        }
                    }
                }
                return ExtSyntaxSupport.COMPLETION_CANCEL;
                
        } else { // the pane is already visible
            switch (typedText.charAt(0)) {
                case '=':
                case '{':
                case ';':
                    return ExtSyntaxSupport.COMPLETION_HIDE;
                default:
                    return ExtSyntaxSupport.COMPLETION_POST_REFRESH;
            }
        }
    }
    
    public boolean isAssignable(JCType from, JCType to) {
        JCClass fromCls = from.getClazz();
        JCClass toCls = to.getClazz();
        
        if (fromCls.equals(JavaCompletion.NULL_CLASS)) {
            return to.getArrayDepth() > 0 || !JavaCompletion.isPrimitiveClass(toCls);
        }
        
        if (toCls.equals(JavaCompletion.OBJECT_CLASS)) { // everything is object
            return (from.getArrayDepth() > to.getArrayDepth())
            || (from.getArrayDepth() == to.getArrayDepth()
            && !JavaCompletion.isPrimitiveClass(fromCls));
        }
        
        if (from.getArrayDepth() != to.getArrayDepth()) {
            return false;
        }
        
        if (fromCls.equals(toCls)) {
            return true; // equal classes
        }
        
        if (fromCls.isInterface()) {
            return toCls.isInterface()
            && (JCUtilities.getAllInterfaces(getFinder(), fromCls).indexOf(toCls) >= 0);
        } else { // fromCls is a class
            TokenID fromClsKwd = JavaTokenContext.getKeyword(fromCls.getName());
            if (fromClsKwd != null) { // primitive class
                TokenID toClsKwd = JavaTokenContext.getKeyword(toCls.getName());
                return toClsKwd != null
                && JCUtilities.getPrimitivesAssignable(fromClsKwd.getNumericID(), toClsKwd.getNumericID());
            } else {
                if (toCls.isInterface()) {
                    return (JCUtilities.getAllInterfaces(getFinder(), fromCls).indexOf(toCls) >= 0);
                } else { // toCls is a class
                    return (JCUtilities.getSuperclasses(getFinder(), fromCls).indexOf(toCls) >= 0);
                }
            }
        }
    }
    
    public JCType getCommonType(JCType typ1, JCType typ2) {
        if (typ1.equals(typ2)) {
            return typ1;
        }
        
        // The following part
        TokenID cls1Kwd = JavaTokenContext.getKeyword(typ1.getClazz().getName());
        TokenID cls2Kwd = JavaTokenContext.getKeyword(typ2.getClazz().getName());
        if (cls1Kwd == null && cls2Kwd == null) { // non-primitive classes
            if (isAssignable(typ1, typ2)) {
                return typ1;
            } else if (isAssignable(typ2, typ1)) {
                return typ2;
            } else {
                return null;
            }
        } else { // at least one primitive class
            if (typ1.getArrayDepth() != typ2.getArrayDepth()) {
                return null;
            }
            if (cls1Kwd != null && cls2Kwd != null) {
                return JavaCompletion.getType(
                JCUtilities.getPrimitivesCommonClass(cls1Kwd.getNumericID(), cls2Kwd.getNumericID()),
                typ1.getArrayDepth());
            } else { // one primitive but other not
                return null;
            }
        }
    }
    
    /** Filter the list of the methods (usually returned from
     * Finder.findMethods()) or the list of the constructors
     * by the given parameter specification.
     * @param methodList list of the methods. They should have the same
     *   name but in fact they don't have to.
     * @param parmTypes parameter types specification. If set to null, no filtering
     *   is performed and the same list is returned. If a particular
     * @param acceptMoreParameters useful for code completion to get
     *   even the methods with more parameters.
     */
    public List filterMethods(List methodList, List parmTypeList,
    boolean acceptMoreParameters) {
        if (parmTypeList == null) {
            return methodList;
        }
        
        List ret = new ArrayList();
        int parmTypeCnt = parmTypeList.size();
        int cnt = methodList.size();
        for (int i = 0; i < cnt; i++) {
            // Use constructor conversion to allow to use it too for the constructors
            JCConstructor m = (JCConstructor)methodList.get(i);
            JCParameter[] methodParms = m.getParameters();
            if (methodParms.length == parmTypeCnt
            || (acceptMoreParameters && methodParms.length >= parmTypeCnt)
            ) {
                boolean accept = true;
                boolean bestMatch = !acceptMoreParameters;
                for (int j = 0; accept && j < parmTypeCnt; j++) {
                    JCType mpt = methodParms[j].getType();
                    JCType t = (JCType)parmTypeList.get(j);
                    if (t != null) {
                        if (!t.equals(mpt)) {
                            bestMatch = false;
                            if (!isAssignable(t, mpt)) {
                                accept = false;
                                break;
                            }
                        }
                    } else { // type in list is null
                        bestMatch = false;
                    }
                }
                
                if (accept) {
                    if (bestMatch) {
                        ret.clear();
                    }
                    ret.add(m);
                    if (bestMatch) {
                        break;
                    }
                }
                
            }
        }
        return ret;
    }
    
    /**
     * Interface that can be implemented by the values (in the key-value Map terminology)
     * of the variableMap provided by VariableMapTokenProcessor implementations.
     */
    public interface JavaVariable {
        
        /**
         * Get type expression of the variable declaration.
         * 
* For example for "List l;" it would be an expression formed * from "List". * * @return type expression for this variable. */ public JCExpression getTypeExpression(); /** * Get variable expression of the variable declaration. *
* Typically it is just the declaration variable but for arrays * it can include array depths - for example * for "int i[];" it would be an expression formed * from "i[]". * * @return type expression for this variable. */ public JCExpression getVariableExpression(); } public static class JavaDeclarationTokenProcessor implements DeclarationTokenProcessor, VariableMapTokenProcessor { protected JavaSyntaxSupport sup; /** Position of the begining of the declaration to be returned */ protected int decStartPos = -1; protected int decArrayDepth; /** Starting position of the declaration type */ protected int typeStartPos; /** Position of the end of the type */ protected int typeEndPos; /** Offset of the name of the variable */ protected int decVarNameOffset; /** Length of the name of the variable */ protected int decVarNameLen; /** Currently inside parenthesis, i.e. comma delimits declarations */ protected int parenthesisCounter; /** Depth of the array when there is an array declaration */ protected int arrayDepth; protected char[] buffer; protected int bufferStartPos; protected String varName; protected int state; /** Map filled with the [varName, type] pairs */ protected HashMap varMap; /** Construct new token processor * @param varName it contains valid varName name or null to search * for all variables and construct the variable map. */ public JavaDeclarationTokenProcessor(JavaSyntaxSupport sup, String varName) { this.sup = sup; this.varName = varName; if (varName == null) { varMap = new HashMap(); } } public int getDeclarationPosition() { return decStartPos; } public Map getVariableMap() { return varMap; } protected void processDeclaration() { if (varName == null) { // collect all variables String decType = new String(buffer, typeStartPos - bufferStartPos, typeEndPos - typeStartPos); if (decType.indexOf(' ') >= 0) { decType = Analyzer.removeSpaces(decType); } String decVarName = new String(buffer, decVarNameOffset, decVarNameLen); // Maybe it's inner class. Stick an outerClass before it ... JCClass innerClass = null; JCClass outerCls = sup.getClass(decVarNameOffset); if (outerCls != null){ String outerClassName = outerCls.getFullName(); innerClass = sup.getFinder().getExactClass(outerClassName+"."+decType); //NOI18N if (innerClass != null){ varMap.put(decVarName, JavaCompletion.getType(innerClass, decArrayDepth)); } } if (innerClass==null){ JCClass cls = sup.getClassFromName(decType, true); if (cls != null) { varMap.put(decVarName, JavaCompletion.getType(cls, decArrayDepth)); } } } else { decStartPos = typeStartPos; } } public boolean token(TokenID tokenID, TokenContextPath tokenContextPath, int tokenOffset, int tokenLen) { int pos = bufferStartPos + tokenOffset; // Check whether we are really recognizing the java tokens if (!tokenContextPath.contains(JavaTokenContext.contextPath)) { state = INIT; return true; } switch (tokenID.getNumericID()) { case JavaTokenContext.BOOLEAN_ID: case JavaTokenContext.BYTE_ID: case JavaTokenContext.CHAR_ID: case JavaTokenContext.DOUBLE_ID: case JavaTokenContext.FLOAT_ID: case JavaTokenContext.INT_ID: case JavaTokenContext.LONG_ID: case JavaTokenContext.SHORT_ID: case JavaTokenContext.VOID_ID: typeStartPos = pos; arrayDepth = 0; typeEndPos = pos + tokenLen; state = AFTER_TYPE; break; case JavaTokenContext.DOT_ID: switch (state) { case AFTER_TYPE: // allowed only inside type state = AFTER_DOT; typeEndPos = pos + tokenLen; break; case AFTER_EQUAL: case AFTER_VARIABLE: break; default: state = INIT; break; } break; case JavaTokenContext.ELLIPSIS_ID: switch (state) { case AFTER_TYPE: arrayDepth++; break; default: state = INIT; break; } break; case JavaTokenContext.LBRACKET_ID: switch (state) { case AFTER_TYPE: state = AFTER_TYPE_LSB; arrayDepth++; break; case AFTER_MATCHING_VARIABLE: state = AFTER_MATCHING_VARIABLE_LSB; decArrayDepth++; break; case AFTER_EQUAL: break; default: state = INIT; break; } break; case JavaTokenContext.RBRACKET_ID: switch (state) { case AFTER_TYPE_LSB: state = AFTER_TYPE; break; case AFTER_MATCHING_VARIABLE_LSB: state = AFTER_MATCHING_VARIABLE; break; case AFTER_EQUAL: break; default: state = INIT; break; } break; // both in type and varName case JavaTokenContext.LPAREN_ID: parenthesisCounter++; if (state != AFTER_EQUAL) { state = INIT; } break; case JavaTokenContext.RPAREN_ID: if (state == AFTER_MATCHING_VARIABLE) { processDeclaration(); } if (parenthesisCounter > 0) { parenthesisCounter--; } if (state != AFTER_EQUAL) { state = INIT; } break; case JavaTokenContext.LBRACE_ID: case JavaTokenContext.RBRACE_ID: if (parenthesisCounter > 0) { parenthesisCounter--; // to tolerate opened parenthesis } state = INIT; break; case JavaTokenContext.COMMA_ID: if (parenthesisCounter > 0) { // comma is declaration separator in parenthesis if (parenthesisCounter == 1 && state == AFTER_MATCHING_VARIABLE) { processDeclaration(); } if (state != AFTER_EQUAL) { state = INIT; } } else { // not in parenthesis switch (state) { case AFTER_MATCHING_VARIABLE: processDeclaration(); // let it flow to AFTER_VARIABLE case AFTER_VARIABLE: case AFTER_EQUAL: state = AFTER_COMMA; break; default: state = INIT; break; } } break; case JavaTokenContext.NEW_ID: if (state != AFTER_EQUAL) { state = INIT; } break; case JavaTokenContext.EQ_ID: switch (state) { case AFTER_MATCHING_VARIABLE: processDeclaration(); // flow to AFTER_VARIABLE case AFTER_VARIABLE: state = AFTER_EQUAL; break; case AFTER_EQUAL: break; default: state = INIT; } break; case JavaTokenContext.SEMICOLON_ID: if (state == AFTER_MATCHING_VARIABLE) { processDeclaration(); } state = INIT; break; case JavaTokenContext.IDENTIFIER_ID: switch (state) { case AFTER_TYPE: case AFTER_COMMA: if (varName == null || Analyzer.equals(varName, buffer, tokenOffset, tokenLen)) { decArrayDepth = arrayDepth; decVarNameOffset = tokenOffset; decVarNameLen = tokenLen; state = AFTER_MATCHING_VARIABLE; } else { state = AFTER_VARIABLE; } break; case AFTER_VARIABLE: // error state = INIT; break; case AFTER_EQUAL: break; case AFTER_DOT: typeEndPos = pos + tokenLen; state = AFTER_TYPE; break; case INIT: typeStartPos = pos; arrayDepth = 0; typeEndPos = pos + tokenLen; state = AFTER_TYPE; break; default: state = INIT; break; } break; case JavaTokenContext.WHITESPACE_ID: // whitespace ignored break; case JavaTokenContext.COLON_ID: // 1.5 enhanced for loop sysntax processDeclaration(); case JavaTokenContext.INSTANCEOF_ID: default: state = INIT; } return true; } public int eot(int offset) { return 0; } public void nextBuffer(char[] buffer, int offset, int len, int startPos, int preScan, boolean lastBuffer) { this.buffer = buffer; bufferStartPos = startPos - offset; } } }
... 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.