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

package org.netbeans.modules.java;

import java.lang.reflect.Modifier;
import java.util.*;
import javax.swing.SwingUtilities;
import javax.swing.text.StyledDocument;
import javax.jmi.reflect.InvalidObjectException;
import org.netbeans.api.mdr.MDRObject;
import org.netbeans.api.mdr.events.AttributeEvent;
import org.netbeans.api.mdr.events.MDRChangeEvent;
import org.netbeans.api.mdr.events.MDRChangeListener;
import org.netbeans.jmi.javamodel.*;
import org.netbeans.modules.java.settings.JavaSettings;
import org.netbeans.modules.javacore.internalapi.JavaMetamodel;
import org.netbeans.modules.javacore.JMManager;
import org.openide.ErrorManager;
import org.openide.cookies.LineCookie;
import org.openide.text.Annotation;
import org.openide.text.Line;
import org.openide.util.RequestProcessor;

/** 
 * The main purpose of this class is to manage annotations of overridden and implemented methods for a particular
 * JavaEditor. Unused instances should release resources by {@link #suspend}. The suspended instance should not be
 * reused.
 *
 * @author Jan Pokorsky, Tomas Zezula
 */
final class OverrideAnnotationSupport {
    
    private final JavaEditor editor;
    
    private WMDRChangeListener overriddenListener;
    
    private Request currentRequest;
    
    /* List of override attached to this document */
    private List overrideAnnotations = new ArrayList ();
    
    /** model was cleared so do not try to do anything with it */
    private boolean isSuspended = false;

    private static final RequestProcessor QUEUE = new RequestProcessor("Overriddens Queue", 1); // NOI18N
        
    public OverrideAnnotationSupport(JavaEditor editor) {
        this.editor = editor;
        this.overriddenListener = new WMDRChangeListener(this);
    }
    
    /** computes annotations and attaches them. */
    public void processOverriddenAnnotation() {
        if (!isEnabled()) return;
        synchronized(this) {
            if (isSuspended) return;
            if (currentRequest != null && !currentRequest.cancel()) {
                currentRequest.followMe = true;
                return;
            }
            currentRequest = new Request();
            // schedule the request with a delay to prevent excessive cpu consumption in case of flood of requests
            QUEUE.post(currentRequest, 200);
        }
    }
    
    /** stops processing, unregisters listeners, detaches annotations */
    public void suspend() {
//        boolean isRunning = false;
        synchronized(this) {
            if (this.isSuspended) return;
            this.isSuspended = true;
            if (this.currentRequest != null && !this.currentRequest.cancel()) {
//                isRunning = true;
                this.currentRequest.followMe = false;
            }
        }
        
        Request clean = new Request(Request.CLEAN);
//        if (isRunning) { // do not block
        // MaM - isRunning commented out to always perform this in the QUEUE to prevent deadlocks (#46115)
            QUEUE.post(clean);
//        } else {
//            clean.run();
//        }
    }
    
    private void dispose() {
        if (this.overriddenListener != null) {
            this.overriddenListener.removeAllElements();
        }
        detachAnnotations(this.overrideAnnotations);
        this.overrideAnnotations.clear();
    }
    
    /** show annotations? */
    private boolean isEnabled() {
        return JavaSettings.getDefault().getShowOverriding();
    }
        
    private void processOverriddenAnnotation (Resource rsc) {
//        System.err.println("### DO RECOMPUTE OVERRIDENS: " + editor.getDataObject().getPrimaryFile() + ", " + Thread.currentThread());
        if (JMManager.PERF_DEBUG) Thread.dumpStack();
        final List originalAnnotations = copyAnnotations();
        final List overrideAnnotations = this.findOverriddenMethods(rsc);
        final List addedOverrideAnnotations = new ArrayList (overrideAnnotations);
        addedOverrideAnnotations.removeAll (originalAnnotations);
        final List removedOverrideAnnotations = new ArrayList (originalAnnotations);
        removedOverrideAnnotations.removeAll (overrideAnnotations);
        final List unchangedOverrideAnnotations = new ArrayList (originalAnnotations);
        unchangedOverrideAnnotations.retainAll (overrideAnnotations);
        if (isSuspended) return;
        detachAnnotations (removedOverrideAnnotations);
        
        if (editor.isDocumentLoaded() && !addedOverrideAnnotations.isEmpty()) {
            StyledDocument doc = editor.getDocument();
            Runnable docRenderer = new Runnable() {
                public void run() {
                    LineCookie cookie = (LineCookie) editor.getDataObject().getCookie(LineCookie.class);
                    Line.Set lines = cookie.getLineSet();
                    for (Iterator it = addedOverrideAnnotations.iterator(); it.hasNext();) {
                        OverrideAnnotation ann = (OverrideAnnotation) it.next ();
                        ann.attachToLineSet (lines);
                    }
                }
            };
            if (doc != null) {
                JavaMetamodel.getDefaultRepository().beginTrans(false);
                try {
                    doc.render(docRenderer);
                } finally {
                    JavaMetamodel.getDefaultRepository().endTrans();
                }
            } else {
                SwingUtilities.invokeLater (docRenderer);
            }
        }
        List computedAnnotations = unchangedOverrideAnnotations;
        computedAnnotations.addAll(addedOverrideAnnotations);
        syncAnnotations(computedAnnotations);
    }
        
    private synchronized List copyAnnotations() {
        return new ArrayList(this.overrideAnnotations);
    }
        
    private synchronized void syncAnnotations(List l) {
        this.overrideAnnotations = l;
    }

    private void processOverriddenAnnotationImpl() {
        Resource rsc = editor.getResource();
        if (rsc != null) {
            processOverriddenAnnotation(rsc);
        }
    }

    private List findOverriddenMethods(JavaClass cls, Map methods) {
        if (methods.isEmpty()) return Collections.EMPTY_LIST;
        JavaClass parent;
        List result = new ArrayList ();
        List interfaces = new ArrayList ();
        interfaces.addAll(cls.getInterfaces());
        parent = cls.getSuperClass();
        while (parent != null) {
            if (isSuspended) return Collections.EMPTY_LIST;
            if (Modifier.isFinal (parent.getModifiers())) {
                break;
            }
            if (findOverridenMethods(parent, interfaces, methods, result)) return result;
            cls = parent;
            parent = cls.getSuperClass();
        }
        Set visited = new HashSet();
        while (!interfaces.isEmpty()) {
            JavaClass ifc = (JavaClass) interfaces.remove(0);
            if (visited.add(ifc)) {
                if (findOverridenMethods(ifc, interfaces, methods, result)) return result;
            }
        }
        return result;
    }

    private boolean findOverridenMethods(JavaClass parent, List interfaces, Map methods, List result) {
        this.overriddenListener.addElement(parent);
        interfaces.addAll(parent.getInterfaces());
        for (Iterator it = parent.getContents().iterator(); it.hasNext();) {
            ClassMember tmp = (ClassMember) it.next();
            if (tmp instanceof Method) {
                int modifiers = tmp.getModifiers();
                if (!Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers) && !Modifier.isPrivate(modifiers)) {
                    Method m = (Method) methods.get(tmp);
                    if (m != null) {
                        methods.remove (m);
                        result.add (new OverrideAnnotation.Descriptor((Method) tmp,m));
                        if (methods.isEmpty()) {
                            //Fully covered
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private Map createMethodMap (JavaClass cls, Set classes) {
        Map methods = new TreeMap (new Comparator() {
            public int compare(Object o1, Object o2) {
                Method m1 = (Method) o1, m2 = (Method) o2;
                int result = m1.getName() == null ? -1 : m1.getName().compareTo(m2.getName());
                if (result == 0) {
                    List p1 = m1.getParameters(), p2 = m2.getParameters();
                    Iterator it2 = p2.iterator();
                    for (Iterator it1 = p1.iterator(); it1.hasNext() && result == 0;) {
                        Type param1 = ((Parameter) it1.next()).getType();
                        if (it2.hasNext()) {
                            Type param2 = ((Parameter) it2.next()).getType();
                            result = param1 == null || param1.getName() == null ? -1 : (param2 == null ? 1 : param1.getName().compareTo(param2.getName()));
                        } else {
                            result = -1;
                        }
                    }
                    if (result == 0 && it2.hasNext()) {
                        result = 1;
                    }
                }
                if (result == 0) {
                    result = m1.getType() == null || m1.getType().getName() == null ? -1 : (m2.getType() == null ? 1 : m1.getType().getName().compareTo(m2.getType().getName()));
                }
                return result;
            }
        });
        for (Iterator it = cls.getContents().iterator(); it.hasNext();) {
            ClassMember tmp = (ClassMember) it.next();
            if (tmp instanceof Method) {
                int modifiers = tmp.getModifiers();
                if (!Modifier.isStatic(modifiers) && !Modifier.isPrivate(modifiers)) {
                    methods.put (tmp, tmp);
                }
            } else if (tmp instanceof JavaClass) {
                classes.add(tmp);
            }
        }
        return methods;
    }

    private List findOverriddenMethods (Resource rsc) {
        JavaMetamodel.getDefaultRepository().beginTrans(false);
        try {
            JavaMetamodel.getManager().setClassPath(rsc);
            Set classes = new HashSet(rsc.getClassifiers());
            List result = new ArrayList ();
            while (!classes.isEmpty()) {
                Iterator tmp = classes.iterator();
                JavaClass cls = (JavaClass) tmp.next();
                tmp.remove();
                this.overriddenListener.addElement(cls);
                List methodsDescriptor = findOverriddenMethods(cls, createMethodMap(cls, classes));
                if (isSuspended) return result;
                for (Iterator it = methodsDescriptor.iterator(); it.hasNext();) {
                    OverrideAnnotation.Descriptor descriptor = (OverrideAnnotation.Descriptor) it.next ();
                    result.add (OverrideAnnotation.forDescriptor (descriptor));
                }
            }
            return result;
        } catch (InvalidObjectException e) {
            // ignore
            return Collections.EMPTY_LIST;
        } finally {
            JavaMetamodel.getDefaultRepository().endTrans();
        }
    }

    private static void detachAnnotations(Collection anns) {
        for (Iterator i = anns.iterator(); i.hasNext();) {
            Annotation ann = (Annotation) i.next();
            try {
                ann.detach();
            } catch (Exception e) {
                ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
            }
        }
    }
        
    /** represents sheduled reqest to process overridden annotations */
    private class Request implements Runnable {
        private boolean isCanceled = false;
        private boolean isRunning = false;
        /** follow current reqest with a new one when it is finished */
        private boolean followMe = false;
        /** request type */
        private final int type;
        private static final int DEFAULT = 0;
        private static final int CLEAN = 1;
            
        public Request() {
            this(DEFAULT);
        }
        
        public Request(int type) {
            this.type = type;
        }
        
        public void run() {
            switch (type) {
                case DEFAULT:
                    computeAnnotations();
                    break;
                case CLEAN:
                    dispose();
                    break;
                default: assert false: "Invalid request type: " + type; // NOI18N
            }
        }
        
        private void computeAnnotations() {
            if (isCanceled) return;
            try {
                isRunning = true;
                processOverriddenAnnotationImpl();
            } finally {
                isRunning = false;
            }
                
            synchronized(OverrideAnnotationSupport.this) {
                if (followMe) {
                    followMe = false;
                    processOverriddenAnnotation();
                }
            }
        }
            
        /**
         * @return true if canceled
         */ 
        public boolean cancel() {
            isCanceled = true;
            return !isRunning;
        }
            
    }
    
    /** listens to changes of class, superclasses, interfaces and all their methods */
    private static class WMDRChangeListener implements MDRChangeListener {

        OverrideAnnotationSupport support;

        //Map>
        private Map containers;

        public WMDRChangeListener (OverrideAnnotationSupport support) {
            this.support = support;
        }

        public synchronized void addElement (JavaClass cls) {
            if (this.containers == null || support.isSuspended) {
                this.containers = new HashMap ();
            }
            List methods = (List) this.containers.get(cls);
            if (methods == null) {
                methods = new ArrayList();
                for (Iterator it = cls.getContents().iterator(); it.hasNext();) {
                    Object tmp = it.next();
                    if (tmp instanceof Method) {
                        methods.add(tmp);
                        ((MDRObject) tmp).addListener(this, AttributeEvent.EVENTMASK_ATTRIBUTE);
                    }
                }
                this.containers.put(cls, methods);
                ((MDRObject) cls).addListener(this, AttributeEvent.EVENTMASK_ATTRIBUTE);
            }
        }
        
        private void updateMethods(JavaClass cls) {
            JavaMetamodel.getDefaultRepository().beginTrans(false);
            try {
                synchronized (this) {
                    if (this.containers == null || support.isSuspended)
                        return;
                    List methods = (List) this.containers.get(cls);
                    if (methods != null) {
                        List toAdd = new ArrayList();
                        List toRemove = new ArrayList(methods);
                        for (Iterator it = cls.getContents().iterator(); it.hasNext();) {
                            Object tmp = it.next();
                            toRemove.remove(tmp);
                            if (tmp instanceof Method && !methods.contains(tmp)) {
                                toAdd.add(tmp);
                                ((MDRObject) tmp).addListener(this, AttributeEvent.EVENTMASK_ATTRIBUTE);
                            }
                        }
                        methods.addAll(toAdd);
                        methods.removeAll(toRemove);
                        for (Iterator it = toRemove.iterator(); it.hasNext();) {
                            MDRObject me = (MDRObject) it.next();
                            me.removeListener(this);
                        }
                    }
                }
            } finally {
                JavaMetamodel.getDefaultRepository().endTrans();
            }
        }

        public void removeAllElements () {
            JavaMetamodel.getDefaultRepository().beginTrans(false);
            try {
                synchronized (this) {
                    if (this.containers == null)
                        return;

                    for (Iterator it = this.containers.entrySet().iterator(); it.hasNext();) {
                        Map.Entry entry = (Map.Entry) it.next();
                        MDRObject cls = (MDRObject) entry.getKey();
                        List methods = (List) entry.getValue();
                        cls.removeListener(this);
                        for (Iterator jt = methods.iterator(); jt.hasNext();) {
                            MDRObject m = (MDRObject) jt.next();
                            m.removeListener(this);
                        }
                        methods.clear();
                    }
                    this.containers.clear();
                }
            } finally {
                JavaMetamodel.getDefaultRepository().endTrans();
            }
        }

        public void change(MDRChangeEvent e) {
            if (support.isSuspended) return;
            if ((e.getSource() instanceof Element) && !((Element) e.getSource()).isValid()) {
                return ;
            }

            boolean recompute = false;
            AttributeEvent event = (AttributeEvent) e;
            String attrName = event.getAttributeName();

            if (event.getSource() instanceof JavaClass) {
                if ("contents".equals(attrName) && ((event.getNewElement() instanceof Method) || (event.getOldElement() instanceof Method))) { // NOI18N
                    updateMethods((JavaClass) event.getSource());
                    recompute = true;
                } else if ("superClassName".equals(attrName) || "interfaceNames".equals(attrName)) { // NOI18N
                    if (this.containers.containsKey(event.getSource())) {
                        this.removeAllElements();
                    }
                    recompute = true;
                }
            } else if (event.getSource() instanceof Method) {
                if ("name".equals(attrName) || "modifiers".equals(attrName) || "parameters".equals(attrName) || "typeName".equals(attrName)) { // NOI18N
                    recompute = true;
                }
            }
            // todo: handle the situation when a parameter type is changed

            if (recompute) {
                support.processOverriddenAnnotation();
            }
        }
    }
}
... 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.