|
What this is
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 |
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.