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

import java.lang.ref.*;
import java.util.*;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import java.io.IOException;

import org.openide.util.RequestProcessor;
import org.openide.cookies.ConnectionCookie;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;

import org.openide.src.*;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;

import org.netbeans.modules.java.JavaConnections;
import org.netbeans.modules.java.bridge.LangModel;

/**
 * The class creates a liaison between a source element and external dependencies.
 * It's main task is to create and maintain connections to source(s) and classes this
 * source depends on. Then, it is used for listening on those sources.
 * @author  sdedic
 * @version 
 */
public abstract class SourceConnectionSupport 
    implements PropertyChangeListener, Registrar, ConnectionCookie.Listener,
    SynchronizeCodeCookie {
            
    /**
     * List of prefixes that should be skipped when connecting object. This effectively
     * prevents connection to most of r/o JDK classes.
     */
    private static final String[] SKIP_REGISTRATION_PREFIXES = { 
            "java.", "javax."  // NOI18N
    };
    
    private static final boolean DEBUG = false;        

    public static final int CONNECT_NOT = 0;
    public static final int CONNECT_CONFIRM = 1;
    public static final int CONNECT_AUTO = 2;

    public static final int CONNECT_DEFAULT = CONNECT_CONFIRM;

    /**
     * Holds the source element we are managing connections for.
     */
    SourceElement src;

    /**
     *  Project context
     */
    private FileObject context;

    /**
     * Implementation of the class dependency management.
     */
    ClassDependencyImpl classDeps;

    /**
     * Map of dependencies -- keyed by the external entity that is depended on,
     * values are collections of local entities that depend on the external one.
     */
    Map dependencies;

    /**
     * Node that serves as a listener for ConnectionCookie.Events
     */
    Node listenerNode;
    
    /**
     * True, if the connection support has linked its listeners to the classes in
     * the source.
     */
    boolean initialized;
    
    /**
     * Suspended counter.
     */
    int suspended;
    
    /**
     * For logging and debugging purposes
     */
    String supportID;
    
    /**
     * Model of the source.
     */
    LangModel   model;
    
    /**
     * Synchronization model that should be used for the source. Can be one of the
     * CONNECT_XXX manifested constants.
     */
    int syncMode = -1;

    /**
     * If true, the source will attempt to refresh links as soon as it is parsed.
     */
    boolean relinkEnabled;
    
    /**
     * True, if a full refresh already took place.
     */
    boolean fullRefresh;
    
    /**
     * List of classes seen in the source, can be null if the support was
     * not refreshed.
     */
    Collection classList;

    /**
     * Type of the connection used to listen on superclasses and superinterfaces.
     */
    public static final ConnectionCookie.Type DEPENDENCY_IMPLEMENTATION = JavaConnections.IMPLEMENTS;
    
    ClassElement[]  NO_CLASSES = new ClassElement[] {};
    
    public SourceConnectionSupport(SourceElement src, LangModel model, String id) {
        this.src = src;
        this.context = ((DataObject)this.src.getCookie (DataObject.class)).getPrimaryFile();
        this.supportID = id;
        this.model = model;
        this.suspended = 1;
    }
    
    /* package-private */ static String getString(String key) {
        return org.openide.util.NbBundle.getMessage(SourceConnectionSupport.class, key);
    }
    
    private void initialize() {
        classDeps = new ClassDependencyImpl(this);
        
        //model.addPostCommitListener(classDeps); 
        
        // watch out source element property changes.
        src.addPropertyChangeListener(this);
    }
    
    protected void checkState() {
        loadModel();
        if (!fullRefresh) {
            RequestProcessor.postRequest(new Runnable() {
                public void run() {
                    refreshLinks(false);
                }
            });
        }
    }

    /**
     * Suspends the dependency management - it si required for certain operations.
     * If the management is restored later, full refresh is done just like when the
     * source is parsed for the first time.
     */
    public void suspend() {
        boolean s;
        ClassElement[] cls;
        synchronized (this) {
            s = suspended == 0;
            suspended++;
            if (!s || !initialized)
                return;
            if (DEBUG)
                System.err.println(getName() + " suspended"); // NOI18N
            if (classList != null) {
                for (Iterator it = classList.iterator(); it.hasNext(); ) {
                    ClassElement c = (ClassElement)((Reference)it.next()).get();
                    if (DEBUG)
                        System.err.println(getName() + " detaching from " + // NOI18N
                            c.getName().getFullName());
                    if (c != null)
                        c.removePropertyChangeListener(classDeps);
                }
                classList = null;
            }
            src.removePropertyChangeListener(this);
        }
    }

    /**
     * Resumes the connection management. Does full refresh on the connections, if
     * refresh is true. Otherwise it postpones the refresh after next parse.
     */
    public synchronized void resume(boolean refresh) {
        if (--suspended == 0) {
            if (DEBUG)
                System.err.println(getName() + " resumed"); // NOI18N
            initialize();
            if (refresh && src.getStatus() == SourceElement.STATUS_OK)
                refreshLinks(true);
            else
                initialized = false;
        }
    }
    
    public void syncConnections() {
        boolean old = relinkEnabled;
        
        relinkEnabled = true;
        if (!old) {
            RequestProcessor.postRequest(new Runnable() {
                public void run() {
                    refreshLinks(false);
                }
            });
        }
    }
    
    protected boolean isSuspended() {
        return suspended > 0;
    }
    
    /**
     * This method should save dependencies on the external entity "fqn" and 
     */
    protected abstract void saveDependencies(String fqn, String[] classIds);
    
    /**
     * This method should read a list of external dependency names from somewhere.
     */
    protected abstract String[] readDependencyList();
    
    /**
     * This method should be implemented to read the local dependents for the named
     * dependency.
     */
    protected abstract String[] readDependencies(String fqn);
    
    /**
     * The descendant need to add a reasonable semantic for this method. The handle is
     * required to construct the Support and call restore() on it.
     */
    protected abstract Node.Handle createListenerHandle(Node n);

    /**
     * Restores the dependency information. 
     */
    private void loadModel() {
        if (dependencies != null)
            // already initialized
            return;
        
        if (DEBUG) {
            System.err.println("reading dependencies for " + supportID); // NOI18N
        }
        String[] dependencyNames = readDependencyList();
        int hashSize;

        if (DEBUG)
            System.err.println(getName() + " loading deps"); // NOI18N
        if (dependencyNames == null || dependencyNames.length == 0)
            hashSize = 7;
        else
            hashSize = dependencyNames.length * 4 / 3;
        Map m = new HashMap(hashSize);
        for (int i = 0; i < dependencyNames.length; i++) {
            if (DEBUG) {
                System.err.println("- loading dependencies from " + dependencyNames[i]); // NOI18N
            }
            String[] deps = readDependencies(dependencyNames[i]);
            if (deps == null || deps.length == 0)
                continue;
            if (DEBUG) {
                for (int j = 0; j < deps.length; j++) {
                    System.err.println("-- " + deps[j]);
                }
            }
            Identifier srcId = Identifier.create(dependencyNames[i]);
            Collection c = new HashSet(deps.length * 4 / 3);
            c.addAll(Arrays.asList(deps));
            m.put(srcId, c);
        }
        this.dependencies = m;
    }

    /**
     * Overridable -- returns node that should serve as the connecting entity.
     * The node must support serialization via {@link org.openide.nodes.Node.Handle}.
     */
    public Node getListenerNode() {
        if (listenerNode != null)
            return listenerNode;
        synchronized (this) {
            if (listenerNode != null)
                return listenerNode;
            listenerNode = new ListenerNode();
        }
        return listenerNode;
    }
    
    public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getSource() != src)
            return;
        String propName = evt.getPropertyName();
        if (propName == null)
            return;
        if (SourceElement.PROP_STATUS.equals(propName)) {
            handleStatusChange(
            ((Integer)evt.getOldValue()).intValue(),
            ((Integer)evt.getNewValue()).intValue());
            return;
        }
    }
    
    protected String getName() {
        return "[" + this.supportID + "] "; // NOI18N
    }
    
    private void refreshLinks(boolean first) {
        if (fullRefresh)
            return;
        
        loadModel();

        if (DEBUG) {
            System.err.println(getName() + "Refreshing stored information:"); // NOI18N
            System.err.println("First flag is " + first); // NOI18N
        }
        
        fullRefresh |= !first;
        
        // do a refresh of all classes in the source - we became valid again!
        ClassElement[] allClasses = src.getAllClasses();
        
        // listener attach must be synchronized, otherwise we could detach
        // a listener :-) accidentally.
        List l = new ArrayList(allClasses.length);
        synchronized (this) {
            for (int i = 0; i < allClasses.length; i++) {
                ClassElement c = allClasses[i];
                l.add(new WeakReference(c));
            }
            this.classList = l;
        }
        for (int i = 0; i < allClasses.length; i++) {
            ClassElement c = allClasses[i];
            classDeps.refreshClass(c, first);
        }
        
        // now do the reverse check: see if all dependencies are really needed:
        Collection entryList = new Vector(dependencies.entrySet());
        // this will eventually hold dependencies that should be cleaned up.
        Collection cleanDeps = null;
        
        for (Iterator entries = entryList.iterator(); entries.hasNext(); ) {
            Map.Entry en = (Map.Entry)entries.next();
            Identifier srcIdent = (Identifier)en.getKey();
            Collection deps = new ArrayList((Collection)en.getValue());
            Collection removed = null;
            
            for (Iterator depClasses = deps.iterator(); depClasses.hasNext(); ) {
                String relName = (String)depClasses.next();

                ClassElement cl = findRelativeClass(relName);
                if (cl == null || !classDeps.dependsOn(cl, srcIdent)) {
                    if (removed == null)
                        removed = new LinkedList();
                    if (DEBUG)
                        System.err.println(getName() + "Scheduling removal of dependency " + relName + " -> " +  // NOI18N
                            srcIdent.getFullName());
                    removed.add(relName);
                }
            }
            if (removed != null) {
                if (cleanDeps == null) 
                    cleanDeps = new LinkedList();
                cleanDeps.add(srcIdent);
                cleanDeps.add(removed);
            }
        }
        if (cleanDeps == null)
            return;
        
        for (Iterator clean = cleanDeps.iterator(); clean.hasNext(); ) {
            Identifier source = (Identifier)clean.next();
            if (DEBUG)
                System.err.println(getName() + "Removing deps from " + source.getFullName()); // NOI18N
            for (Iterator rel = ((Collection)clean.next()).iterator(); rel.hasNext(); ) {
                removeDependency((String)rel.next(), source);
            }
        }
    }
    
    private void handleStatusChange(int oldStatus, int newStatus) {
        if (initialized || isSuspended() || newStatus != SourceElement.STATUS_OK)
            return;
        if (DEBUG) {
            System.err.println("[ " + supportID + " ]: Status changed from " + oldStatus + " to " + newStatus); // NOI18N
        } 
        fullRefresh = false;
        refreshLinks(oldStatus == SourceElement.STATUS_NOT);
        initialized = true;
    }

    protected synchronized Node createListenerNode() {
        if (listenerNode != null)
            return listenerNode;
        return listenerNode = new ListenerNode();
    }

    private class ListenerNode extends AbstractNode {
        private Node.Handle     myHandle;
        
        public ListenerNode() {
            super(Children.LEAF);
        }
        
        public Node.Handle getHandle() {
            if (myHandle != null)
                return myHandle;
            synchronized (this) {
                if (myHandle == null) {
                    myHandle = createListenerHandle(this);
                }
            }
            return myHandle;
        }
        
        public Node.Cookie getCookie(Class clazz) {
            Node.Cookie x = super.getCookie(clazz);
            if (x != null)
                return x;
            if (clazz.isInstance(SourceConnectionSupport.this))
                return SourceConnectionSupport.this;
            return null;
        }
    }
    
    //////////////////////// Registrar implementation ///////////////////////////

    private ClassElement findClass(Identifier className) {
        if (!relinkEnabled)
            return null;
        ClassElement cls = ClassElement.forName(className.getFullName(), this.context);
        return cls;
    }
    
    private boolean eligibleToRegistration(Identifier id) {
        String fn = id.getFullName();
        
        for (int i = 0; i < SKIP_REGISTRATION_PREFIXES.length; i++) {
            if (fn.startsWith(SKIP_REGISTRATION_PREFIXES[i]))
                return false;
        }
        return true;
    }

    protected void registerDependency(ClassElement source) {
        if (source == null || isSuspended())
            return;
        ConnectionCookie ck = (ConnectionCookie)source.getCookie(ConnectionCookie.class);
        if (ck == null)
            return;
        try {
            ck.register(DEPENDENCY_IMPLEMENTATION, getListenerNode());
        } catch (IOException ex) {
            // swallow
        }
    }
    
    protected void unregisterDependency(ClassElement source) {
        if (source == null || isSuspended())
            return;
        ConnectionCookie ck = (ConnectionCookie)source.getCookie(ConnectionCookie.class);
        if (ck == null) {
            return;
        }
        try {
            ck.unregister(DEPENDENCY_IMPLEMENTATION, getListenerNode());
        } catch (IOException ex) {
            // swallow
        }
    }
    
    /**
     * Returns an Enumeration of the dependencies bound to the specified source.
     * If no dependency is known, the returned enumeration is empty.
     * The Enumeration contains ClassElements.
     */
    public Enumeration getDependencies(Identifier source) {
        Collection c;
        
        synchronized (this) {
            c = (Collection)dependencies.get(source);
            if (c != null) {
                c = new ArrayList(c);
            }
        }
        if (c == null) {
            return Collections.enumeration(Collections.EMPTY_LIST);
        }
        
        Collection els = new ArrayList(c.size());
        for (Iterator it = c.iterator(); it.hasNext(); ) {
            String relName = (String)it.next();
            if (DEBUG) {
                System.err.println("*dependency " + relName + " on " + source); // NOI18N
            }
            ClassElement clel = findRelativeClass(relName);
            els.add(clel);
        }
        return Collections.enumeration(els);
    }
    
    /**
     * Maps addDependency between a target class and the source thing as a link
     * to the source's file.
     */
    public boolean addDependency(ClassElement target, Identifier source) {
        String relName = findRelativeName(target);
        boolean changed = false;
        Collection c;

        checkState();
        synchronized (this) {
            c = (Collection)dependencies.get(source);
            if (c == null) {
                c = new HashSet(7);
                dependencies.put(source, c);
            }
            changed = c.add(relName);
            if (DEBUG) {
                System.err.println("added dependency from " + source + " to " + relName); // NOI18N
                System.err.println("change is " + changed); // NOI18N
            }
        }
        if (changed) {
            saveDependencies(source.getFullName(), 
                (String[])c.toArray(new String[c.size()]));
        }
        
        // try to add the dependency link to the source:
        if (eligibleToRegistration(source))
            registerDependency(findClass(source));
        return changed;
    }
    
    private void removeDependency(String relName, Identifier source) {
        boolean changed = false;
        Collection c;
        
        checkState();
        synchronized (this) {
            if (DEBUG) 
                System.err.println("RemoveDependency: relName = " + relName + " source = " + source); // NOI18N
            c = (Collection)dependencies.get(source);
            if (c == null) {
                if (DEBUG)
                    System.err.println("Source deps not found."); // NOI18N
            } else if (!c.remove(relName)) {
                if (DEBUG) {
                    System.err.println("Dep from source to relName not found. Current list follows:"); // NOI18N
                    for (Iterator it = c.iterator(); it.hasNext(); )
                        System.err.println(it.next());
                    System.err.println("- end of list"); // NOI18N
                }
            } else {
                changed = true;
            }
        }
        if (changed)
            saveDependencies(source.getFullName(), c.isEmpty() ? null : 
                (String[])c.toArray(new String[c.size()]));
        if (eligibleToRegistration(source)) {
            unregisterDependency(findClass(source));
        }
    }

    public void removeDependency(ClassElement target, Identifier source) {
        String relName = findRelativeName(target);
        removeDependency(relName, source);
    }

    /**
     * Attempts to find the class named by "relName". It traverses through top-level
     * classes and inner classes.
     */
    private ClassElement findRelativeClass(String relName) {
        // #36160: NoSuchElementException
        if ((relName == null) || (relName.length () == 0))
            return null;
        
        StringTokenizer tok = new StringTokenizer(relName, "."); // NOI18N
        String name = tok.nextToken();
        Identifier id = Identifier.create(name);
        ClassElement cls = src.getClass(id);
        
        while (cls != null && tok.hasMoreTokens()) {
            id = Identifier.create(tok.nextToken());
            cls = cls.getClass(id);
        }
        return cls;
    }
    
    /**
     * Creates a class name relative to the source. It strips the package name
     * from the class one. It is done by traversing to outer classes rather than
     * obtaining the package declaration to ensure better consistency.
     */
    private String findRelativeName(ClassElement el) {
        String fullName = el.getName().getFullName();
        ClassElement outer;

        if (fullName == null || fullName.length() == 0)
            return ""; // NOI18N
        
        for (outer = el.getDeclaringClass(); outer != null; outer = el.getDeclaringClass()) {
            el = outer;
        }
        String outerQualifier = el.getName().getQualifier();
        if ("".equals(outerQualifier))
            return fullName;

        return fullName.substring(outerQualifier.length() + 1);
    }
    
    private void classChanged(ClassElement oldState, ClassElement newState,
        Collection searchFor, Collection replaceWith) {

        Identifier[] oldIts = oldState.getInterfaces();
        Identifier[] newIts = newState.getInterfaces();
    }

    
    /**
     * The main entry for all JavaConnections received. This method will distribute
     * the changes to their proper recipients acting like a demultiplexor. It silently
     * discards all events for which it does not register a recpient.
     */
    public void notify(ConnectionCookie.Event ev) {
        if (!(ev instanceof JavaConnections.Event))
            // ignore non-JavaConnections events
            return;
        JavaConnections.Change[] changes = ((JavaConnections.Event)ev).getChanges();

        // load the model & check dependencies, if not already done:
        loadModel();
        if (!fullRefresh)
            refreshLinks(false);
        
        if (DEBUG) {
            System.err.println("[connections " + supportID + " ]: got event " + ev +  // NOI18N
                "with " + changes.length + " changes"); // NOI18N
        }
        Map newItems;
        Map changeEvents;
        
        newItems = new HashMap(17);
        changeEvents = new HashMap(17);
        
        for (int i = 0; i < changes.length; i++) {
            JavaConnections.Change ch = changes[i];
            int type = ch.getChangeType();
            
            if (type == JavaConnections.TYPE_METHODS_ADD) {
                // I need to split the collection according to the connection 
                // event real recipients. One event may have more than one recipient.
                if (DEBUG) {
                    System.err.println("- including " + ch.getElements().length + " new items"); // NOI18N
                }
                splitNewMethods(ch.getElements(), newItems);
            } else if (type == JavaConnections.TYPE_METHODS_CHANGE) {
                splitMethodChange(ch, changeEvents);
                if (DEBUG) {
                    System.err.println("+1 change "); // NOI18N
                }
            }
        }
        ClassElement[] allClasses = src.getAllClasses();
        
        Collection result = null;
        
        for (int i = 0; i < allClasses.length; i++) {
            ClassElement recipient = allClasses[i];
            Collection added = (Collection)newItems.get(recipient);
            Collection changeEvs = (Collection)changeEvents.get(recipient);
            MemberElement me = null;
            
            if (added != null && !added.isEmpty()) {
                me = (MemberElement)added.iterator().next();
            } else if (changeEvs != null && !changeEvs.isEmpty()) {
                me = (MemberElement)
                    ((JavaConnections.Change)changeEvs.iterator().next()).getNewElement();
            }
            if (me == null)
                continue;
                
            if (DEBUG) {
                System.err.println("Dispatching to " + recipient.getName()); // NOI18N
                if (added == null)
                    System.err.println("- no additions"); // NOI18N
                else
                    System.err.println("- " + added.size() + " additions"); // NOI18N
                if (changeEvs == null)
                    System.err.println("- no changes"); // NOI18N
                else
                    System.err.println("- " + changeEvs.size() + " changes"); // NOI18N
            }
            // handle the notification.
            classDeps.connectionNotify(recipient, 
                me.getDeclaringClass(),
                added == null ? null :Collections.enumeration(added), 
                changeEvs == null ? null : Collections.enumeration(changeEvs)
                );
        }
    }
    
    private void splitMethodChange(JavaConnections.Change ch, Map dist) {
        if (!(ch.getNewElement() instanceof MethodElement))
            return;
        
        MethodElement m = (MethodElement)ch.getNewElement();
        ClassElement src = m.getDeclaringClass();
        Enumeration deps = getDependencies(src.getName());
        while (deps.hasMoreElements()) {
            ClassElement target = (ClassElement)deps.nextElement();
            Collection c = (Collection)dist.get(target);
            if (c == null) {
                c = new LinkedList();
                dist.put(target, c);
            }
            c.add(ch);
        }
    }
    
    /**
     * Splits the element array into the map according to the actual recipients of those
     * added methods. The result map is keyed by a ClassElement instance; valus are collections
     * of methods that should be added to the class.
     */
    private void splitNewMethods(Element[] els, Map targetMap) {
        for (int i = 0; i < els.length; i++) {
            if (!(els[i] instanceof MethodElement)) {
                // discarding something that is not a MethodElement - should not
                // happen, if the connection source is working OK.
                continue;
            }
            MethodElement m = (MethodElement)els[i];
            ClassElement source = m.getDeclaringClass();
            if (source == null) {
                // discard something that is not based on a class - should not happen,
                // if the connection source is healthy.
                continue;
            }
            Identifier srcID = source.getName();
            Enumeration deps = getDependencies(srcID);
            
            while (deps.hasMoreElements()) {
                ClassElement c = (ClassElement)deps.nextElement();
                if (DEBUG) {
                    System.err.println("adding new item to " + c.getName()); // NOI18N
                }
                Collection col = (Collection)targetMap.get(c);
                if (col == null) {
                    col = new LinkedList();
                    targetMap.put(c, col);
                }
                col.add(m);
            }
        }
    }
    
    protected void storeSynchronizationMode(int mode) {
    }
    
    protected int readSynchronizationMode() {
        return CONNECT_DEFAULT;
    }
    
    public void setSynchronizationMode(ClassElement t, int mode) {
        storeSynchronizationMode(mode);
        syncMode = mode;
    }
    
    public int getSynchronizationMode(ClassElement t) {
        if (syncMode == -1)
            return syncMode = readSynchronizationMode();
        else
            return syncMode;
    }
    
    /**
     * Synchronizes the whole source.
     */
    public void synchronize() throws SourceException {
        checkState();
        
        ClassElement[] classes = src.getAllClasses();
        for (int i = 0; i < classes.length; i++) {
            classDeps.synchronizeClass(classes[i]);
        }
    }
    
    public SynchronizeCodeCookie createClassSyncCookie(ClassElement cls) {
        if (cls.isClassOrInterface())
            return new ClassSyncImpl(cls);
        return null;    // do not synchronize interfaces
    }
    
    private class ClassSyncImpl implements SynchronizeCodeCookie {
        ClassElement target;
        
        ClassSyncImpl(ClassElement target) {
            this.target = target;
        }
        
        public void synchronize() throws SourceException {
            classDeps.synchronizeClass(target);
        }
    }
}
... 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.