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

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.*;
import java.io.Reader;
import java.io.InputStream;
import java.io.IOException;

import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;

import javax.swing.text.Segment;
import javax.swing.text.StyledDocument;
import javax.swing.text.BadLocationException;
import org.netbeans.api.java.classpath.ClassPath;

import org.openide.cookies.SourceCookie;
import org.openide.filesystems.FileObject;
import org.openide.nodes.Node;
import org.openide.src.*;
import org.openide.text.CloneableEditorSupport;
import org.openide.util.RequestProcessor;
import org.openide.util.Task;
import org.openide.util.TaskListener;
import org.openide.util.Utilities;

import org.netbeans.modules.java.bridge.ElementImpl;
import org.netbeans.modules.java.bridge.DefaultLangModel;
import org.netbeans.modules.java.bridge.WrapperFactory;
import org.netbeans.modules.java.bridge.LangModel;
import org.netbeans.modules.java.bridge.CommitListener;
import org.netbeans.modules.java.bridge.SrcElementImpl;

import org.netbeans.modules.java.codegen.DocumentBinding;
import org.netbeans.modules.java.codegen.SourceText;
import org.netbeans.modules.java.ParserEngine;
import org.netbeans.modules.java.ElementFactory;
import org.netbeans.modules.java.ErrConsumer;
import org.netbeans.modules.java.JavaDataObject;
import org.netbeans.modules.java.Parsing;

import org.netbeans.modules.java.JavaDataObject;

/**
 * This class serves as a default implementation of JavaParser interface and coordinates
 * one-way interaction between a LangModel and the underlying StyledDocument. The other
 * way is implemented through various bindings attached to individual model elements.
 * The parser is responsible for maintaining `dirty' status for the model data, with respect
 * to changes made to the document, parsing when the client asks to parse and updating
 * the model contents when appropriate.
 *
 * @author  sdedic
 * @version 
 */
public class ParsingSupport implements JavaParser {
    
    private static final int READ_THRESHOLD = 2048;
    
    private PropertyChangeSupport  propSupport;
    
    public static final String PROP_STATUS = "status"; // NOI18N
    
    private static final boolean DEBUG = false;
    
    /** Initially true. Set to false when the Support is no longer valid.
     */
    private boolean valid;
    
    /**
     * True during the model update operation. When true, the refImplementation
     * cannot be cleared, otherwise the updater thread may lock up.
     */
    private boolean updating;

    private boolean clean;
    
    private Env parsingEnv;
    
    /**
     * Parser engine this support is working with.
     */
    private ParserEngine    engine;

    /** IOException saved (?) from the last parsing process. If no change is
     * reported to the underlying document, the exception is immediately
     * thrown without attempting to parse.
     */
    private SourceException     savedException;
    
    /**
     * SourceElement that is bound to the proxy. The proxy, if needed constructs the
     * real Impl. The element, once constructed, is not freed unless the ParsingSupport
     * dies as well.
     */
    private SourceElement   src;
    
    /**
     * The request object that is filed to the parsing queue. Since the request has not
     * yet touched by the RequestProcessor, it is safe to change paramterers for it.
     */
    private Processor       currentRequest;
    
    /** The request that is currently running, null if none. This request is currently
     * being serviced by the RequestProcessor.
     */
    private Processor       runningRequest;

    /**
     * Language model interface.
     */
    LangModel           model;

    /**
     * Extended interface for updating the language model.
     */
    LangModel.Updater   updater;

    /**
     * A Weak/Soft reference to the real implementation.
     */
    private Reference       refImplementation;
    
    /**
     * Parsing status of the source - it is one of SourceElement.STATUS_* symbolic
     * constatnts.
     */
    private int             status;
    
    /**
     * Collection of ChangeListeners registered with this Support.
     */
    Collection              changeList;
    
    DocumentBinding         docBinding;
    
    JavaDataObject jdo;
    
    /**
     * Creates a ParsingSupport and initializes it with an environment, binding,
     * updater interface and the model.
     */
    public ParsingSupport(Env parsingEnv, JavaDataObject jdo, DocumentBinding docBinding,
        LangModel.Updater updater, LangModel model) {
        this.parsingEnv = parsingEnv;
        this.jdo = jdo;
        this.docBinding = docBinding;
        this.updater = updater;
        this.model = model;
        this.valid = true;
    }
    
    public void addChangeListener(ChangeListener l) {
        if (changeList == null) {
            synchronized (this) {
                if (changeList == null) 
                    changeList = new LinkedList();
            }
        }
        synchronized (changeList) {
            changeList.add(l);
        }
    }
    
    public void removeChangeListener(ChangeListener l) {
        if (changeList == null)
            return;
        synchronized (changeList) {
            changeList.remove(l);
        }
    }

    /**
     * Adds a property change listener to this object.
     */
    public void addPropertyChangeListener(PropertyChangeListener l) {
        if (propSupport == null) {
            synchronized (this) {
                if (propSupport == null)
                    propSupport = new PropertyChangeSupport(this);
            }
        }
        propSupport.addPropertyChangeListener(l);
    }
    
    /**
     * Removes the change listener.
     */
    public void removePropertyChangeListener(PropertyChangeListener l) {
        if (propSupport == null)
            return;
        propSupport.removePropertyChangeListener(l);
    }
    
    /** Schedules a refresh/reparse of the associated document. The request is given
     * the passed priority. If there are some errors during parsing and acceptErrors
     * is false, the hierarchy is not updated. After the parsing task finishes,
     * the hiearchy will reflect the state of the document at the time parse() was called,
     * or a more recent state, if there were multiple parse() requests before the task
     * was processed.
     * @return Task that can be tested for completion or can be listened on
     */
    public Task parse(int priority, boolean ignoreClean, boolean acceptErrors) {
        Thread.dumpStack();
        // create a shallow request (no line annotation)
//        ParsableObjectRequest req = new ParseSourceRequest();
//        return parse(priority, ignoreClean, acceptErrors, req);
        return Task.EMPTY;
    }

    public Task parse(int priority, boolean ignoreClean, boolean acceptErrors, ParsableObjectRequest req) {
        
        if (req.getParserType () == JavaParser.MDR_PARSER) {
            // System.out.println("MDR_PARSER used !!!");
            // Thread.dumpStack ();
            return new FinishedTask(null);
        }
        
        Processor immediate;
        SourceElement.Impl i = null;
        // #20100 - order locks properly: getEditorSupport() is likely to 
        // access/lock some CookieSet, and cookies tend to call/synchronize
        // on the parser support.
        CloneableEditorSupport editSupport = docBinding.getEditorSupport();
        synchronized (this) {
            if (DEBUG) {
                System.err.println("Got parse request, prio = " + priority + " ignoreClean = " + ignoreClean + " acceptErrs = " + acceptErrors); // NOI18N
                i = getSourceImpl();
                System.err.println("Data = " + i + " clean = " + this.clean); // NOI18N
                System.err.println("Parsing task = " + currentRequest + "/" + runningRequest); // NOI18N
            }
            if (currentRequest != null) {
                // if THIS is the parsing thread, then -- well, it's bad.
                // temporal resolution: take out the request IMMEDIATELY.
                if (PARSING_RP.isRequestProcessorThread()) {
                    if (DEBUG)
                        System.err.println("Running in parsing thread!"); // NOI18N
                    immediate = currentRequest;
                } else {                
                    currentRequest.enableErrors(acceptErrors);
                    currentRequest.setPriority(priority);
                    if (DEBUG)
                        System.err.println("Returning task from current request " + currentRequest); // NOI18N
                    return currentRequest.getClientTask();
                }
            } else {
                i = getSourceImpl();
                if (i != null && this.clean && !ignoreClean) {
                    if (DEBUG)
                        System.err.println("Returning finished task"); // NOI18N
                    return new FinishedTask(i);
                }
                Processor proc = new Processor(priority, parsingEnv, editSupport,req);
                proc.enableErrors(acceptErrors);
                if (PARSING_RP != null && PARSING_RP.isRequestProcessorThread()) {
                    immediate = proc;
                } else {
                    addRequest(proc, priority);
                    return proc.getClientTask();
                }
            }
        }
        immediate.run();
        i = getSourceImpl();
        return new FinishedTask(i);
    }    
    public synchronized Task getCurrentTask() {
        if (currentRequest!=null)
            return currentRequest.getClientTask();
        return new FinishedTask(null);
    }

    private static class FinishedTask extends Task {
        private Object hook;
        
        public FinishedTask(Object o) {
            super(null);
            hook = o;
        }
    }
    
    public void fireElementPropertyChange(Element source, PropertyChangeEvent evt) {
        if (source == getSource()) {
            ((SrcElementImpl) getSourceImpl ()).propertyChange(evt);
        } else {
            updater.firePropertyChange(source, evt);
        }
    }
    
    /**
     * Returns the parser engine used by this Support.
     */
    public ParserEngine getParserEngine() {
        return this.engine;
    }
    
    /** Replaces the parser engine with the given implementation. All
     * parse requests issued from now on will use the new engine impl.
     */
    public void setParserEngine(ParserEngine eng) {
        this.engine = eng;
    }

    /**
     * Prepares data for the Java hierarchy from the source. If some data already
     * exists, they are not refreshed, and the Task is reaturned in already finished
     * state.
     * @return task object that is set to completed after the data is prepared.
     */
    public Task prepare() {
        return Task.EMPTY; //parse(PRIORITY_BACKGROUND, false, false);
    }
    
    public SourceElement getSource() {
        if (src == null)
            src = jdo.getSource ();
        return src;
        /*
        synchronized (this) {
            if (src != null)
                return src;
            createSourceProxy ();
            return src = new SourceElement (new org.netbeans.modules.java.bridge.SrcElementImpl (jdo));
        }
         */
    }
    
    /**
     * Invalidates contents of the parser support. This method should be called
     * when the original data source is discarded. The parser support is not
     * expecting to be valid ever again after this call.
     */
    public void invalidate() {
        SourceElement.Impl impl;
        synchronized (this) {
            if (!valid)
                return;
            impl = getSourceImpl();
            if (impl == null)
                return;
            valid = false;
        }
        updater.invalidateModel(getSource());
        synchronized (this) {
            if (!updating)
                refImplementation = null;
        }
        changeStatus(SourceElement.STATUS_NOT);
    }
        
    
    protected void changeStatus(int newStatus) {
        int oldStatus = status;
        status = newStatus;
        if (propSupport != null && propSupport.hasListeners(null))
            propSupport.firePropertyChange(PROP_STATUS, oldStatus, newStatus);
        fireStateChange();
    }
    
    private void fireParsingEvent(final Collection messages) {
        final Object hook = getSourceImpl();
        
        EVENT_RP.post(new Runnable() {
            public void run() {
                Parsing.fireEvent(jdo, messages);
            }
        });
    }
    
    protected void fireStateChange() {
        if (changeList == null)
            return;
        Collection copy;
        
        synchronized (changeList) {
            if (changeList.isEmpty())
                return;
            copy = new ArrayList(changeList);
        }
        ChangeEvent e = new ChangeEvent(this);
        
        for (Iterator it = copy.iterator(); it.hasNext(); ) {
            try {
                ((ChangeListener)it.next()).stateChanged(e);
            } catch (RuntimeException x) {
                org.openide.ErrorManager.getDefault().notify(org.openide.ErrorManager.WARNING, x);
            }
        }
    }
    
    public SourceElement.Impl getSourceImpl() {
        SourceElement sourceElem = getSource ();
        if (sourceElem == null)
            return null;
        return (SourceElement.Impl) sourceElem.getCookie (SourceElement.Impl.class);        
    }
    
    public LangModel getModel() {
        return this.model;
    }
    
    public SourceElement.Impl findSourceImpl() throws SourceException {
        synchronized (this) {
            SourceElement.Impl impl = getSourceImpl();
            if (impl != null)
                return impl;
            Util.log("impl = null"); // NOI18N
            if (savedException != null)
                throw savedException;
        }
        throw new SourceException("Cannot acquire source"); // NOI18N
    }
    
    /**
     * Invalidates the data in the specified region (bounds inclusive).
     * @return true, if validity pattern of the parsed data changes.
     */
    public void sourceChanged(int from, int to) {
        Processor req = currentRequest;
        
        clean = false;
        if (req != null)
            req.sourceChanged();
    }
    
    /**
     * Retrieves the Throwable that caused the parsing to abort. This will be
     * mainly an IOException.
     * @return Throwable that aborted the last most recent parse request.
     */
    public SourceException getErrorCause() {
        return savedException;
    }

    /*--------------------------------------------------------------------------------*/
    
    Node.Cookie findCookieForSource(Class type) {
        if (src == null)
            return null;
        return parsingEnv.findCookie(getSource(), type);
    }
    
    /**
     * Retrieves the parsing status of the source. The value is one of the symbolic
     * SourceElement.STATUS_* constants.
     */
    public int getStatus() {
        int s = status;
        if (s != SourceElement.STATUS_OK)
            return s;
        SourceElement.Impl impl = getSourceImpl();
        if (impl == null)
            changeStatus(SourceElement.STATUS_NOT);
        return status;
    }
    
    public Task addParsingRunnable(Runnable r, int priority) {
        return PARSING_RP.post(r, 0, priority);
    }
    

    static final Runnable EMPTY_RUNNABLE = new Runnable() {
        public void run() {}
    };
    
    /**
     * Helper class that is put to the parsing RequestProcessor to carry out 
     * a specific ParseRequest.
     */
    class Processor extends Object implements
        Runnable, CommitListener, ParseObjectRequest {
        Processor             chained;
        int                   priority;
        RequestProcessor.Task ownTask;
        boolean               errorsOK;
        //SourceElement.Impl    implHook;
        int                   stage;
        int                   resultStatus;
        ParsableObjectRequest request;
        T                     task;
        
        Processor(int priority, Env env, CloneableEditorSupport supp,ParsableObjectRequest req) {
            request=req;
            task = new T();
            request.setEnvironment(env); 
            request.setEditorSupport(supp); 
        }
        
        /**
         * If run() is directly invoked on the request processor task, it means
         * that the request processor thread itself needs to complete the task before
         * executing. We have to evaluate the task at once.
         */
        protected void directRun() {
            do {
                run();
            } while (stage >= 0);
        }
        
        public void setPriority(int prior) {
            if (this.priority > prior)
                return;
            priority = prior;
            // if the task cannot be cancelled, it is being processed right now,
            // or it was already completed.
            if (ownTask.cancel()) {
                addRequest(this, prior); 
            }
        }
        
        public void enableErrors(boolean enable) {
            errorsOK |= enable;
        }
        
        public void chainRequest(Processor other) {
            chained = other;
        }
        
        public void setProcessorTask(RequestProcessor.Task t) {
            ownTask = t;
        }
                
        public void run() {
            Util.log("processing request " + this + " stage " + stage); // NOI18N
            try {
                switch (stage++) {
                    case 0:
                        if (DEBUG) {
                            System.err.println("Starting request " + this); // NOI18N
                        }
                        parseLockModel();
                        break;
                    case 1:
                        break;
                }
            } catch (SourceException.IO e) {
            // the exception is expected - I/O in reading sources. Mark the source as errorenous.
                resultStatus = SourceElement.STATUS_ERROR;
            } catch (Throwable e) {
                savedException = new SourceException(e.getLocalizedMessage());
                parsingEnv.annotateThrowable(savedException, e);
                parsingEnv.annotateThrowable(savedException, "Parser error", false); // NOI18N
		        org.openide.ErrorManager.getDefault().notify(e);
                resultStatus = SourceElement.STATUS_ERROR;
            } finally {
                stage--;
            }
            Util.log("request " + this + " stage " + (stage + 1) + " end"); // NOI18N
            if (stage > 0)
                return;
            
            if (resultStatus != -1) {
                complete();
            } else {
                // it's a pity -- reschedule the request.
                request.notifyReschedule();
                Util.log("Rescheduling request"); // NOI18N
                stage = 0;
                addRequest(this, priority);
            }
        }
        
        private void updateModel() throws SourceException {
            model.removePreCommitListener(this);
            if (getSyntaxErrors() > 0 && !errorsOK) {
                // don't update the model at all.
                resultStatus = SourceElement.STATUS_PARTIAL;
                return;
            }
            if (isValid()) {
                // we have the write lock, noone changed the model
                // since we started.

                // SUCCESS (?)
                resultStatus = getSyntaxErrors() > 0 ?
                    SourceElement.STATUS_PARTIAL : SourceElement.STATUS_OK;
            }
        }
        
        private void parseLockModel() throws SourceException {
            model.addPreCommitListener(this);
            resultStatus = -1;
            synchronized (ParsingSupport.this) {
                runningRequest = this;
                Util.log("Running request = " + this); // NOI18N
            }
            savedException = null;
            try {
                runningRequest = this;
                process(getParserEngine());
                if (isValid()) {
                    Util.log("Request " + this + " processed. Still valid"); // NOI18N
                    stage = 1;
                    Util.log("trying to run update"); // NOI18N
//                    ParsingSupport.this.updater.runUpdate(this, true);
                    resultStatus = SourceElement.STATUS_OK;
                }
            } catch (IOException ex) {
                savedException = new SourceException.IO(ex);
                parsingEnv.annotateThrowable(savedException, ex);
                // notify listeners that we are finished.
                resultStatus = SourceElement.STATUS_ERROR;
            } catch (InternalError er) {
                // the parser sometimes issues this one -- just issue an error
                savedException = new SourceException(er.getMessage());
                parsingEnv.annotateThrowable(savedException, er);
                resultStatus = SourceElement.STATUS_ERROR;
            } finally {
                model.removePreCommitListener(this);
            }
            runningRequest = null;
        }
        
        public void complete() {
            Collection messages;
            
            synchronized (ParsingSupport.this) {
                if (currentRequest == this)
                    currentRequest = null;
                messages = request.getMessages();
            }
            changeStatus(resultStatus);
            fireParsingEvent(messages);
            task.complete();
            if (chained != null)
                chained.complete();
            // signal -- processing is finally over.
            stage = -1;
        }
        
        public void changesCommited(Set created, Set removed, Map changed) {
            request.modelChanged();
        }

        /**
         * Causes the request to be passed immediately to the parsing engine.
         * If the request is already processed, it returns immediately.
         */
        public void process(ParserEngine eng) throws IOException {
            eng.process(this);
        }

        /**
         * Returns a task clients can wait for. This is *NOT* a task which processes the
         * request, since the request can be rescheduled several times before it is
         * completed (if it is completed).
         */
        public Task getClientTask() {
            return task;
        }

        /**
         * Notifies the request that the source text has been changed. This causes
         * cancellation of the request in some cases.
         */
        public void sourceChanged() {
            request.sourceChanged();
        }

        /* ParseObjectRequest method is delegated to request object */
        public void setSyntaxErrors(int errors) {
            request.setSyntaxErrors(errors);
        }

        public void setSemanticErrors(int errors) {
            request.setSemanticErrors(errors);
        }

        public void notifyStart() {
            request.notifyStart();
        }

        public void notifyComplete() {
            request.notifyComplete();
        }

        public boolean isValid() {
            return request.isValid();
        }

        public boolean needsProcessing() {
            return request.needsProcessing();
        }

        public int getSyntaxErrors() {
            return request.getSyntaxErrors();
        }

        public Collection getMessages() {
            return request.getMessages();
        }

        public ElementFactory getFactory() {
            return request.getFactory();
        }

        public char[] getSource() throws java.io.IOException {
            clean = true;
            return request.getSource();
        }

        public InputStream findCompiledClass(String className) {
            return request.findCompiledClass(className);
        }

        public Object getParserType() {
            return request.getParserType();
        }

        public ErrConsumer getErrConsumer() {
            return request.getErrConsumer();
        }
        
        public String getSourceName() {
            return request.getSourceName();
        }
        
        public ClassPath getSourcePath() {
            return request.getSourcePath();
        }
        
        public ClassPath getLibraryPath() {
            return request.getLibraryPath();
        }
        
        public ClassPath getBootClassPath() {
            return request.getBootClassPath();
        }
        
        /* --------------------------------------------------------------------*/

        /** The class is NOT static intentionally - it holds a hard reference
         * to the request, keeping its data alive.
         */
        private class T extends org.openide.util.Task {
            T() {
                super(EMPTY_RUNNABLE);
            }

            public void run() {
                Processor.this.directRun();
            }

            protected void complete() {
                super.notifyFinished();
            }
        }
    }
    
    /* --------------------------------------------------------------------*/
    static RequestProcessor    PARSING_RP;

    /** processor dedicated for parser events. */
    private static RequestProcessor EVENT_RP;
    
    private void addRequest(Processor proc, int priority) {
        synchronized (this) {
            if (PARSING_RP == null) {
                PARSING_RP = new RequestProcessor("Java source parsing"); // NOI18N
                EVENT_RP = new RequestProcessor("Java Parser Event Queue"); // NOI18N
            }
            if (currentRequest != proc) {
                if (currentRequest != null) 
                    proc.chainRequest(currentRequest);
                currentRequest = proc;
            }
            proc.setProcessorTask(PARSING_RP.post(proc, 0, priority));
        }
    }
    
    protected void registerData(SourceElement.Impl data) {
        Reference newRef;

        synchronized (ParsingSupport.class) {
            newRef = new DataFinalizer(this, data, Utilities.activeReferenceQueue());
        }
        synchronized (this) {
            refImplementation = newRef;
        }
    }
    
    protected void notifyFinalized(Reference refImpl) {
        int oldStatus;
        
        synchronized (this) {
            if (refImplementation != refImpl)
                return;
            refImplementation = null;
        }
        changeStatus(SourceElement.STATUS_NOT);
    }
    
    private static class DataFinalizer extends WeakReference implements Runnable {
        Reference   refSupp;

        DataFinalizer(ParsingSupport supp, Object data, ReferenceQueue rqueue) {
            super(data, rqueue);
            refSupp = new WeakReference(supp);
        }
        
        public void run() {
            Reference f;

            ParsingSupport supp = (ParsingSupport)refSupp.get();
            if (supp != null)
                supp.notifyFinalized(this);
        }
    }
}
... 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.