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

import java.util.LinkedList;
import java.util.HashMap;
import org.netbeans.editor.BaseDocument;
import org.netbeans.editor.DrawLayerFactory;
import javax.swing.text.BadLocationException;
import java.util.Iterator;
import java.util.ArrayList;
import javax.swing.event.DocumentListener;
import org.netbeans.editor.BaseDocumentEvent;
import javax.swing.event.DocumentEvent;
import javax.swing.event.EventListenerList;
import java.util.EventListener;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.util.Comparator;
import org.netbeans.editor.AnnotationDesc;
import org.netbeans.editor.AnnotationTypes;
import javax.swing.JPopupMenu;
import javax.swing.Action;
import java.util.Map;
import java.util.TreeSet;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.SwingUtilities;
import org.netbeans.editor.ext.ExtKit;
import org.openide.util.RequestProcessor;

/** Annotations class act as data model containing all annotations attached
 * to one document. Class uses instances of private class LineAnnotations for 
 * grouping of added annotations by line. These objects (LineAnnotations) are
 * referenced from two collections. First one is Map where the key is Mark. 
 * It is used during the drawing in DrawLayerFactory.AnnotationLayer - when
 * the mark appears in mark change, the LineAnnotations instance is found for 
 * it and the active annotation on the line can be queried.
 * Second is List where the LineAnnotations are sorted by line number. This
 * list is used for drawing the annotations in the gutter when the sequential
 * order is important.
 * 
 * The class also listen on document. It need to know how many lines where
 * removed or added to refresh the LineAnnotations.line property.
 *
 * @author David Konecny
 * @since 07/2001
 */
public class Annotations implements DocumentListener {
    
    /** Map of [Mark, LineAnnotations] */
    private HashMap lineAnnotationsByMark;
    
    /** List of [LineAnnotations] which is ordered by line number */
    private ArrayList lineAnnotationsArray;

    /** Drawing layer for drawing of annotations */
    private DrawLayerFactory.AnnotationLayer drawLayer;

    /** Reference to document */
    private BaseDocument doc;

    /** List of listeners on AnnotationsListener*/
    private EventListenerList listenerList;

    /** Property change listener on annotation type changes */
    private PropertyChangeListener l;
    
    /** Property change listener on AnnotationTypes changes */
    private PropertyChangeListener annoTypesListener;

    /** Whether the column with glyph icons is visible */
    private boolean glyphColumn = false;
    
    /** Whether the column with cycling button is visible*/
    private boolean glyphButtonColumn = false;
    
    /** Whether the gutter popup menu has been initialized */
    private boolean menuInitialized = false;

    /** Sorts the subMenu items */
    public static final Comparator MENU_COMPARATOR = new MenuComparator();    

    public Annotations(BaseDocument doc) {
        lineAnnotationsByMark = new HashMap(30);
        lineAnnotationsArray = new ArrayList(20);
        listenerList =  new EventListenerList();
        
        drawLayer = null;
        this.doc = doc;

        // add annotation drawing layer
        doc.addLayer(new DrawLayerFactory.AnnotationLayer(doc),
                DrawLayerFactory.ANNOTATION_LAYER_VISIBILITY);

        // listener on document changes
        this.doc.addDocumentListener(this);
        
        l = new PropertyChangeListener() {
            public void propertyChange (PropertyChangeEvent evt) {
                if (evt.getPropertyName() == AnnotationDesc.PROP_ANNOTATION_TYPE) {
                    AnnotationDesc anno = (AnnotationDesc)evt.getSource();
                    LineAnnotations lineAnnos = (LineAnnotations)lineAnnotationsByMark.get(anno.getMark());
                    lineAnnos.refreshAnnotations();
                    refreshLine(lineAnnos.getLine());
                }
                if (evt.getPropertyName() == AnnotationDesc.PROP_MOVE_TO_FRONT) {
                    AnnotationDesc anno = (AnnotationDesc)evt.getSource();
                    frontAnnotation(anno);
                }
            }
        };

        AnnotationTypes.getTypes().addPropertyChangeListener( annoTypesListener = new PropertyChangeListener() {
            public void propertyChange (PropertyChangeEvent evt) {
                if (evt.getPropertyName() == AnnotationTypes.PROP_COMBINE_GLYPHS) {
                    LineAnnotations lineAnnos;
                    for( Iterator it = lineAnnotationsArray.iterator(); it.hasNext(); ) {
                        lineAnnos = (LineAnnotations)it.next();
                        lineAnnos.refreshAnnotations();
                    }
                }
                if (evt.getPropertyName() == AnnotationTypes.PROP_ANNOTATION_TYPES) {
                    LineAnnotations lineAnnos;
                    for( Iterator it = lineAnnotationsArray.iterator(); it.hasNext(); ) {
                        lineAnnos = (LineAnnotations)it.next();
                        for( Iterator it2 = lineAnnos.getAnnotations(); it2.hasNext(); ) {
                            AnnotationDesc anno = (AnnotationDesc)it2.next();
                            anno.updateAnnotationType();
                        }
                    }
                }
                fireChangedAll();
            }
        });
        
    }

    /** Finds the drawing layer for annotations */
    public synchronized DrawLayerFactory.AnnotationLayer getLayer() {
        if (drawLayer == null)
            drawLayer = (DrawLayerFactory.AnnotationLayer)doc.findLayer(DrawLayerFactory.ANNOTATION_LAYER_NAME);
        return drawLayer;
    }
    
    /** Add annotation */
    public void addAnnotation(AnnotationDesc anno) {

        // Should always be run in EQ
        if (!SwingUtilities.isEventDispatchThread()) {
            throw new IllegalStateException("Must be run in EQ"); // NOI18N
        }

        // create mark for this annotation. One mark can be shared by more annotations
        MarkChain chain = getLayer().getMarkChain();
        try {
            /* Always adds a fresh mark. It helps to behave well
             * in the following scenario:
             * 1. add annotaion at line e.g. 3.
             * 2. add annotation at line 4.
             * 3. goto begining of line 2.
             * 4. select lines 2,3,4,5 by pressing down arrow.
             * 5. remove selected lines.
             * 6. Undo by ctrl-z.
             * If reusing marks this will result into line selection
             * being at line 2 but gutter marking of annotations
             * being at line 4.
             * It happens because although DocumentLine.updatePositionRef()
             * removes and adds the annotations on modified lines, it does that
             * sequentially for each line so it removes 
             * first of the two DocumentLine's annotations
             * and tries to readd it to begining of removed block
             * but it finds there an existing mark
             * from second DocumentLine's annotation.
             * It reuses the mark but as the mark was inside
             * the removed block originally the undo restores its position
             * inside the block.
             * Creation of fresh marks fixes that problem.
             */
            chain.addMark(anno.getOffset(), true);

        } catch (BadLocationException e) {
            /*
             * This is problematic point.
             * Once this place is reached the annotation desc
             * will have null mark and will
             * not in fact be actively present
             * in the document.
             * Such annotation will then throw NPE
             * in removeAnnotation().
             * Hopefully causes of getting to this place
             * were eliminated.
             */
            throw new IllegalStateException("offset=" + anno.getOffset() // NOI18N
                + ", docLen=" + doc.getLength()); // NOI18N
        }
        // attach created mark to annotation
        MarkFactory.ChainDrawMark annoMark = chain.getAddedMark();
        if (annoMark == null) {
            throw new NullPointerException();
        }
        anno.setMark(annoMark);

        // #33165 - different strategy - first trying to search
        // by the mark in the map [mark, lineAnnos]. That should
        // eliminate problems when a mark is tried to be removed
        // from the mark chain a second time.
        // Hopefully this will not cause any other sorts of problems.
        
        LineAnnotations lineAnnos = (LineAnnotations)lineAnnotationsByMark.get(annoMark);
        if (lineAnnos == null) {
            // fine LineAnnotations instance corresponding to the line of this annotation
            // or create new LineAnnotations if this is first annotation on this line
            lineAnnos = getLineAnnotations(anno.getLine());
            if (lineAnnos == null) {
                lineAnnos = new LineAnnotations();
                lineAnnos.addAnnotation(anno);
                if (lineAnnotationsByMark.put(anno.getMark(), lineAnnos) != null) {
                    throw new IllegalStateException("Mark already in the map."); // NOI18N
                }

                // insert newly created LineAnnotations into sorted array
                boolean inserted = false;
                for (int i=0; i < lineAnnotationsArray.size(); i++) {
                    if (((LineAnnotations)lineAnnotationsArray.get(i)).getLine() > lineAnnos.getLine()) {
                        lineAnnotationsArray.add(i, lineAnnos);
                        inserted = true;
                        break;
                    }
                }
                if (!inserted)
                        lineAnnotationsArray.add(lineAnnos);

            }
            else {
                lineAnnos.addAnnotation(anno);
                // check whether this mark is in lineAnnotationsByMark Map
                // it is possible that Line.Part annotations will have more marks
                // for one line
                if (lineAnnotationsByMark.get(anno.getMark()) == null)
                    lineAnnotationsByMark.put(anno.getMark(), lineAnnos);
            }
            
        } else { // mark was already in lineAnnotationsByMark map
            lineAnnos.addAnnotation(anno);
        }

        // add listener on changes of annotation type
        anno.addPropertyChangeListener(l);

        // ignore annotation types with default icon
        if (anno.isVisible() && (!anno.isDefaultGlyph() || (anno.isDefaultGlyph() && lineAnnos.getCount() > 1))) {
            glyphColumn = true;
        }
        
        if (lineAnnos.getCount() > 1)
            glyphButtonColumn = true;

        // notify view that it must be redrawn
        refreshLine(lineAnnos.getLine());

//        System.out.println("AFTER ADD:\n" + dumpLineAnnotationsArray());
    }

    /** Remove annotation */
    public void removeAnnotation(AnnotationDesc anno) {

        // Should always be run in EQ
        if (!SwingUtilities.isEventDispatchThread()) {
            throw new IllegalStateException("Must be run in EQ"); // NOI18N
        }

        // find LineAnnotations for the mark
        MarkFactory.ChainDrawMark annoMark = (MarkFactory.ChainDrawMark)anno.getMark();
        if (annoMark == null) {
            throw new NullPointerException();
        }

        LineAnnotations lineAnnos = (LineAnnotations)lineAnnotationsByMark.get(annoMark);

        int line = lineAnnos.getLine();
        // remove annotation from the line
        lineAnnos.removeAnnotation(anno);
        
        // check if this mark is referenced or not. If not, remove it
        if (!lineAnnos.isMarkStillReferenced(annoMark)) {
            if (lineAnnotationsByMark.remove(annoMark) != lineAnnos) {
                throw new IllegalStateException();
            }

            MarkChain chain = getLayer().getMarkChain();
            // Partial fix of #33165 - rather remove mark explicitly than through its offset
            //chain.removeMark(anno.getOffset());
            if (!chain.removeMark(annoMark)) {
                throw new IllegalStateException("Mark not removed"); // NOI18N
            }
        }
        
        // if there is no more annotations on the line, remove LineAnnotations
        if (lineAnnos.getCount() == 0) {
            lineAnnotationsArray.remove(lineAnnotationsArray.indexOf(lineAnnos));
        }

        // clear the mark from annotation
        anno.setMark(null);

        // remove listener on changes of annotation type
        anno.removePropertyChangeListener(l);

        // notify view that must be redrawn
        refreshLine(line);
//        System.out.println("AFTER REMOVE:\n" + dumpLineAnnotationsArray());
    }
    
    /** Finds active annotation for the Mark. It is called from DrawLayer 
     * when it found the DrawMark */
    public AnnotationDesc getActiveAnnotation(Mark mark) {
        LineAnnotations annos;
        annos = (LineAnnotations)lineAnnotationsByMark.get(mark);
        if (annos == null) {
            return null;
        }
        AnnotationDesc anno = annos.getActive();
        // it is possible that some other mark on the line (means
        // some other annotations) is active
        if (anno==null || anno.getMark() != mark) {
            return null;
        }
        return anno;
    }

    /** Returns the active annotation for the line given by Mark. */
    AnnotationDesc getLineActiveAnnotation(Mark mark) {
        LineAnnotations annos;
        annos = (LineAnnotations)lineAnnotationsByMark.get(mark);
        if (annos == null) {
            return null;
        }
        AnnotationDesc anno = annos.getActive();
        return anno;
    }
    
    /** Finds LineAnnotations for the given line number */
    protected LineAnnotations getLineAnnotations(int line) {
        LineAnnotations annos;
        // since fix of #33165 it is possible to use binary search here
        int low = 0;
        int high = lineAnnotationsArray.size() - 1;
        while (low <= high) {
            int mid = (low + high) / 2;
            annos = (LineAnnotations)lineAnnotationsArray.get(mid);
            int annosLine = annos.getLine();
            if (line < annosLine) {
                high = mid - 1;
            } else if (line > annosLine) {
                low = mid + 1;
            } else {
                return annos;
            }
        }
        return null;
    }

    /** Returns the active annotation for the given line number.
     * It is called from the glyph gutter*/
    public AnnotationDesc getActiveAnnotation(int line) {
        LineAnnotations annos = getLineAnnotations(line);
        if (annos == null)
            return null;
        return annos.getActive();
    }

    /** Move annotation in front of others. The activated annotation
     * is moved in front of other annotations on the same line */
    public void frontAnnotation(AnnotationDesc anno) {
        int line = anno.getLine();
        LineAnnotations annos = getLineAnnotations(line);
        if (annos == null)
            return;
        annos.activate(anno);
        refreshLine(line);
    }

    /** Activate next annotation on the line. Used for cycling 
     * through the annotations */
    public AnnotationDesc activateNextAnnotation(int line) {
        LineAnnotations annos = getLineAnnotations(line);
        if (annos == null)
            return null;
        AnnotationDesc aa = annos.activateNext();
        refreshLine(line);
        return aa;
    }

    /** Get next line number with some annotation*/
    public int getNextLineWithAnnotation(int line) {
        LineAnnotations annos;

        // since fix of #33165 it is possible to use binary search here
        int low = 0;
        int high = lineAnnotationsArray.size() - 1;
        while (low <= high) {
            int mid = (low + high) / 2;
            annos = (LineAnnotations)lineAnnotationsArray.get(mid);
            int annosLine = annos.getLine();
            if (line < annosLine) {
                high = mid - 1;
            } else if (line > annosLine) {
                low = mid + 1;
            } else {
                return annosLine;
            }
        }
        
        // not found annotation on exact line, follow the search with simple searching
        for (int i=low; i= line){
                return annosLine;
            }
        }
        
        return -1;
    }

    /** Get next line number with some annotation*/
    public AnnotationDesc getAnnotation(int line, String type) {
        return null;
    }
    
    /** Return list of pasive annotations which should be drawn on the backgorund */
    public AnnotationDesc[] getPasiveAnnotations(int line) {
        LineAnnotations annos = getLineAnnotations(line);
        if (annos == null)
            return null;
        if (annos.getCount() <= 1)
            return null;
        return annos.getPasive();
    }
    
    /** Return list of passive annotations attached on the line of given offset */
    public AnnotationDesc[]  getPassiveAnnotations(int offset){
        int lineIndex = doc.getDefaultRootElement().getElementIndex(offset);
        return (lineIndex>=0) ? getPasiveAnnotations(lineIndex) : null;
    }

    /** Returns number of visible annotations on the line*/
    public int getNumberOfAnnotations(int line) {
        LineAnnotations annos = getLineAnnotations(line);
        if (annos == null)
            return 0;
        return annos.getCount();
    }
    

    /** Notify view that it is necessary to redraw the line of the document  */
    protected void refreshLine(int line) {
        fireChangedLine(line);
        int start = Utilities.getRowStartFromLineOffset(doc, line);
        int end = Utilities.getRowStartFromLineOffset(doc, line+1);
        if (end == -1)
            end = doc.getLength();
        doc.repaintBlock(start, end);
    }
    
    /** Checks the number of removed lines and recalculate
     * LineAnnotations.line property */
    public void removeUpdate(DocumentEvent e) {
        BaseDocumentEvent be = (BaseDocumentEvent)e;
        int countOfDeletedLines = be.getLFCount();
        if (countOfDeletedLines == 0)
            return;
        
        int changedLine = be.getLine();

/* #33165 - line in line-annotations handled in a different way
        LineAnnotations annos;
        for (int i=0; i changedLine && annos.getLine() < changedLine+countOfDeletedLines)
                annos.setLine(changedLine);
            if (annos.getLine() > changedLine)
                annos.setLine(annos.getLine()-countOfDeletedLines);
        }
 */
        // fire event to AnnotationsListeners that everything should be redraw
        fireChangedAll();
    }
    
    /** Checks the number of inserted lines and recalculate
     * LineAnnotations.line property */
    public void insertUpdate(DocumentEvent e) {
        BaseDocumentEvent be = (BaseDocumentEvent)e;
        int countOfInsertedLines = be.getLFCount();
        if (countOfInsertedLines == 0)
            return;
        
/* #33165 - line in line-annotations handled in a different way
        int changedLine = be.getLine();

        LineAnnotations annos;
        LineAnnotations current = null;
        for (int i=0; i e.getOffset())
                current = annos;
            if (annos.getLine() > changedLine)
                annos.setLine(annos.getLine()+countOfInsertedLines);
        }
        if (current != null)
            current.setLine(current.getLine()+countOfInsertedLines);
 */
        
        // fire event to AnnotationsListeners that everything should be redraw
        fireChangedAll();
    }
    
    /**Gives notification that an attribute or set of attributes changed.*/
    public void changedUpdate(DocumentEvent e) {
    }
    
    /** Add AnnotationsListener listener */
    public void addAnnotationsListener(AnnotationsListener listener) {
	listenerList.add(AnnotationsListener.class, listener);
    }

    /** Remove AnnotationsListener listener */
    public void removeAnnotationsListener(AnnotationsListener listener) {
	listenerList.remove(AnnotationsListener.class, listener);
    }

    /** Fire AnnotationsListener.ChangedLine change*/
    protected void fireChangedLine(int line) {
	// Guaranteed to return a non-null array
	Object[] listeners = listenerList.getListenerList();
	// Process the listeners last to first, notifying
	// those that are interested in this event
	for (int i = listeners.length-2; i>=0; i-=2) {
	    if (listeners[i]==AnnotationsListener.class) {
		// Lazily create the event:
		// if (e == null)
		// e = new ListSelectionEvent(this, firstIndex, lastIndex);
		((AnnotationsListener)listeners[i+1]).changedLine(line);
	    }	       
	}
    }
   
    /** Fire AnnotationsListener.ChangedAll change*/
    protected void fireChangedAll() {
	// Guaranteed to return a non-null array
	Object[] listeners = listenerList.getListenerList();
	// Process the listeners last to first, notifying
	// those that are interested in this event
	for (int i = listeners.length-2; i>=0; i-=2) {
	    if (listeners[i]==AnnotationsListener.class) {
		// Lazily create the event:
		// if (e == null)
		// e = new ListSelectionEvent(this, firstIndex, lastIndex);
		((AnnotationsListener)listeners[i+1]).changedAll();
	    }	       
	}
    }

    /** Return whether this document has or had any glyph icon attached.
     * This method is called from glyph gutter to check whether the glyph column
     * should be drawn or not. */
    public boolean isGlyphColumn() {
        return glyphColumn;
    }
    
    /** Return whether this document has or had more annotations on one line.
     * This method is called from glyph gutter to check whether the glyph cycling 
     * column should be drawn or not. */
    public boolean isGlyphButtonColumn() {
        return glyphButtonColumn;
    }

    
    private void addAcceleretors(Action a, JMenuItem item, BaseKit kit){
        // Try to get the accelerator
        javax.swing.text.JTextComponent target = Utilities.getFocusedComponent();
        if (target == null) return;
        javax.swing.text.Keymap km = target.getKeymap();
        if (km != null) {
            javax.swing.KeyStroke[] keys = km.getKeyStrokesForAction(a);
            if (keys != null && keys.length > 0) {
                item.setAccelerator(keys[0]);
            }else{
                // retrieve via actionName
                String actionName = (String)a.getValue(Action.NAME);
                if (actionName == null) return;
                BaseAction action = (BaseAction)kit.getActionByName(actionName);
                if (action == null) return;
                keys = km.getKeyStrokesForAction(action);
                if (keys != null && keys.length > 0) {
                    item.setAccelerator(keys[0]);
                }                        
            }
        }
    }
    
    /** Creates menu item for the given action. It must handle the BaseActions, which
     * have localized name stored not in Action.NAME property. */
    private JMenuItem createMenuItem(Action action, BaseKit kit) {
        if (action instanceof BaseAction) {
            JMenuItem item = new JMenuItem( ((BaseAction)action).getPopupMenuText(null) );
            item.addActionListener(action);
            addAcceleretors(action, item, kit);
            return item;
        } else {
            JMenuItem item = new JMenuItem( (String)action.getValue(Action.NAME) );
            item.addActionListener(action);
            addAcceleretors(action, item, kit);
            return item;
        }
    }

    /** Creates popup menu with all actions for the given line. */
    public JPopupMenu createPopupMenu(BaseKit kit, int line) {
        return createMenu(kit, line, false).getPopupMenu();
    }
    
    private void initMenu(JMenu pm, BaseKit kit, int line){
        LineAnnotations annos = getLineAnnotations(line);
        Map types = new HashMap(AnnotationTypes.getTypes().getVisibleAnnotationTypeNamesCount() * 4/3);

        Action[] actions;
        boolean separator = false;
        boolean added = false;
        JMenu subMenu;
        TreeSet orderedSubMenus = new TreeSet(MENU_COMPARATOR);

        if (annos != null) {
            
            // first, add actions for active annotation
            AnnotationDesc anno = annos.getActive();
            if (anno != null) {
                actions = anno.getActions();
                if (actions != null) {
                    subMenu = new JMenu(anno.getAnnotationTypeInstance().getDescription());
                    for (int j=0; j 0){
                            //pm.add(subMenu);
                            orderedSubMenus.add(subMenu);
                            added = true;
                        }
                        types.put(pasiveAnnos[i].getAnnotationType(), pasiveAnnos[i].getAnnotationType());
                    }
                }
                if (added)
                    separator = true;
            }
        }

        // third, add all remaining possible actions to the end of the list
        added = false;
        AnnotationType type;
        for (Iterator i = AnnotationTypes.getTypes().getAnnotationTypeNames(); i.hasNext(); ) {
            type = AnnotationTypes.getTypes().getType((String)i.next());
            if (type == null || !type.isVisible())
                continue;
            if (types.get(type.getName()) != null)
                continue;
            actions = type.getActions();
            if (actions != null) {
                subMenu = new JMenu(type.getDescription());
                for (int j=0; j 0){
                    //pm.add(subMenu);
                    orderedSubMenus.add(subMenu);
                    added = true;
                }
            }
        }
        
        if (added)
            separator = true;

        /*
        if (separator)
            pm.addSeparator();
         */
        
        if (!orderedSubMenus.isEmpty()){
            Iterator iter = orderedSubMenus.iterator();
            while(iter.hasNext()){
                subMenu = (JMenu) iter.next();
                pm.add(subMenu);
            }
            pm.addSeparator();
        }
        
        
        // add checkbox for enabling/disabling of line numbers
        BaseAction action = (BaseAction)kit.getActionByName(BaseKit.toggleLineNumbersAction);
        pm.add(action.getPopupMenuItem(null));
        
        BaseAction action2 = (BaseAction)kit.getActionByName(ExtKit.toggleToolbarAction);
        if (action2 != null){
            pm.add(action2.getPopupMenuItem(null));
        }
        menuInitialized = true;
    }

    private static class DelayedMenu extends JMenu{
        
        RequestProcessor.Task task;
        
        public DelayedMenu(String s){
            super(s);
        }
        
        public JPopupMenu getPopupMenu() {
            RequestProcessor.Task t = task;
            if (t!=null && !t.isFinished()){
                t.waitFinished();
            }
            return super.getPopupMenu();
        }
        
        void setTask(RequestProcessor.Task task){
            this.task = task;
        }
        
        void clearTask() {
            this.task = null; // clear the finished task to avoid leaking
        }
    }
    
    private JMenu createMenu(BaseKit kit, int line, boolean backgroundInit){
        final DelayedMenu pm = new DelayedMenu(LocaleSupport.getString("generate-gutter-popup"));
        final BaseKit fKit = kit;
        final int fLine = line;

        if (backgroundInit){
            RequestProcessor rp = RequestProcessor.getDefault();
            RequestProcessor.Task task = rp.create(new Runnable(){
                public void run(){
                    initMenu(pm, fKit, fLine);
                    pm.clearTask(); // clear the finished task reference to avoid leaking
                }
            });
            pm.setTask(task); // set before task execution so that always cleaned properly
            task.schedule(0);
        }else{
            initMenu(pm, fKit, fLine);
        }

        return pm;
    }
    
    /** Creates popup menu with all actions for the given line. */
    public JMenu createMenu(BaseKit kit, int line) {
        boolean bkgInit = menuInitialized;
        menuInitialized = true;
        return createMenu(kit, line, !bkgInit);
    }
    
    private String dumpAnnotaionDesc(AnnotationDesc ad) {
        return "offset=" + ad.getOffset() // NOI18N
            + "(ls=" + doc.getParagraphElement(ad.getOffset()).getStartOffset() // NOI18N
            + "), line=" + ad.getLine() // NOI18N
            + ", type=" + ad.getAnnotationType(); // NOI18N
    }

    private String dumpLineAnnotationsArray() {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < lineAnnotationsArray.size(); i++) {
            LineAnnotations la = (LineAnnotations)lineAnnotationsArray.get(i);
            LinkedList annos = la.annos;
            sb.append("[" + i + "]: line=" + la.getLine() // NOI18N
                + ", anos:"); // NOI18N
            for (int j = 0; j < annos.size(); j++) {
                sb.append("\n    [" + j + "]: " + dumpAnnotaionDesc((AnnotationDesc)annos.get(j))); // NOI18N
            }
            sb.append('\n');
        }
        return sb.toString();
    }
            
    
    /** Manager of all annotations attached to one line. Class stores
     * the references to all annotations from one line in List and also
     * stores which annotation is active, count of visible annotations 
     * and line number. */
    static public class LineAnnotations extends Object {

        /** List with all annotations in this LineAnnotations */
        private LinkedList annos;

        /** List with all visible annotations in this LineAnnotations */
        private LinkedList annosVisible;
        
        /** Active annotation. Used only in case there is more than one
         * annotation on the line */
        private AnnotationDesc active;
        
        /** Line number */
//        private int lineNumber;
        
        protected LineAnnotations() {
            annos = new LinkedList();
            annosVisible = new LinkedList();
//            lineNumber = -1;
        }

        /** Add annotation to this line and activate it. */
        public void addAnnotation(AnnotationDesc anno) {
//            if (lineNumber == -1)
//                lineNumber = anno.getLine();
            annos.add(anno);
            if (anno.isVisible()) {
                active = anno;
            }
            refreshAnnotations();
        }
        
        /** Remove annotation from this line. Refresh the active one
         * and count of visible. */
        public void removeAnnotation(AnnotationDesc anno) {
            if (anno == active)
                activateNext();
            annos.remove(anno);
            if (active == anno)
                active = null;
            refreshAnnotations();
        }

        /** Return the active line annotation. */
        public AnnotationDesc getActive() {
            return active;
        }

        /** Getter for the line number property */
        public int getLine() {
            // #33165 - delegating of getting of the line number to first anno
            return (annos.size() > 0)
                ? ((AnnotationDesc)annos.get(0)).getLine()
                : 0;
//            return lineNumber;
        }

        /** Setter for the line number property */
        public void setLine(int line) {
//            lineNumber = line;
            throw new IllegalStateException("Setting of line number not allowed"); // NOI18N
        }

        /** Gets the array of all pasive and visible annotations */
        public AnnotationDesc[] getPasive() {
            AnnotationDesc[] pasives = new AnnotationDesc[getCount()-1];
            int startIndex = annosVisible.indexOf(getActive());
            int index = startIndex;
            int i=0;
            while (true) {
                index++;
                if (index >= annosVisible.size())
                    index = 0;
                if (index == startIndex)
                    break;

                pasives[i] = (AnnotationDesc)annosVisible.get(index);
                i++;
            }
            return pasives;
        }

        /** Make the given annotation active. */
        public boolean activate(AnnotationDesc anno) {
            
            int i,j;
            i = annosVisible.indexOf(anno);

            if (i == -1) {
                // was anno combined by some type ??
                for(j=0; j < annosVisible.size(); j++) {
                    if (annosVisible.get(j) instanceof AnnotationCombination) {
                        if (((AnnotationCombination)annosVisible.get(j)).isAnnotationCombined(anno)) {
                            i = j;
                            anno = (AnnotationCombination)annosVisible.get(j);
                            break;
                        }
                    }
                }
            }
            
            if (i == -1)
                return false;
            
            if (annosVisible.get(i) == null)
                return false;
            
            if (anno == active || !anno.isVisible())
                return false;
            
            active = anno;
            
            return true;
        }

        /** Get count of visible annotations on the line */
        public int getCount() {
            return annosVisible.size();
        }

        /** Activate next annoation on the line. Used during the cycling. */
        public AnnotationDesc activateNext() {
            if (getCount() <= 1)
                return active;
            
            int current = annosVisible.indexOf(active);
            current++;
            if (current >= getCount())
                current = 0;
            active = (AnnotationDesc)annosVisible.get(current);
            return active;
        }
        
        /** Searches all combination annotation type and sort them
         * by getCombinationOrder into combTypes array
         * which is passed as paramter. */
        private void fillInCombinationsAndOrderThem(LinkedList combTypes) {
            AnnotationType type;
            AnnotationType.CombinationMember[] combs;
            
            for (Iterator it = AnnotationTypes.getTypes().getAnnotationTypeNames(); it.hasNext(); ) {
                type = AnnotationTypes.getTypes().getType((String)it.next());
                if (type == null)
                    continue;
                combs = type.getCombinations();
                if (combs != null && type.isWholeLine() &&
                    (combs.length >= 2 || (combs.length == 1 && combs[0].isAbsorbAll())) ) {
                    if (type.getCombinationOrder() == 0) {
                        combTypes.add(type);
                    } else {
                        boolean inserted = false;
                        for (int i=0; i < combTypes.size(); i++) {
                            if ( ((AnnotationType)combTypes.get(i)).getCombinationOrder() > type.getCombinationOrder()) {
                                combTypes.add(i, type);
                                inserted = true;
                                break;
                            }
                        }
                        if (!inserted)
                            combTypes.add(type);
                    }
                }
            }
        }

        /** For the given combination annotation type and list of annotations
         * it finds all annotations which are combined by this combination
         * and inserts into list of annotations new combined annotation which
         * wraps combined annotations. The result list of annotations can
         * contain null values for annotations which were combined. */
        private boolean combineType(AnnotationType combType, LinkedList annosDupl) {

            int i, j, k;
            boolean matchedType;
            int countOfAnnos = 0;
            int valid_optional_count = 0;
            
            LinkedList combinedAnnos = new LinkedList();

            AnnotationType.CombinationMember[] combs = combType.getCombinations();
            
            // check that there is match between line annos & all types specified in combination
            boolean matchedComb = true;
            AnnotationType.CombinationMember comb;
            AnnotationDesc anno;
            for (i=0; i < combs.length; i++) {

                comb = combs[i];
                matchedType = false;
                
                // check that for one specified combination type there exist some annotation
                for (j=0; j < annosDupl.size(); j++) {
                    
                    anno = (AnnotationDesc)annosDupl.get(j);
                    
                    if (anno == null)
                        continue;

                    // check whether this annotation matches the specified combination type
                    if (comb.getName().equals( anno.getAnnotationType() )) {
                        countOfAnnos++;

                        // now check if the combination has specified some minimum count of annos
                        if (comb.getMinimumCount() == 0) {
                            matchedType = true;
                            countOfAnnos++;
                            combinedAnnos.add(anno);
                            if (!comb.isAbsorbAll())
                                break;
                        } else {
                            int requiredCount = comb.getMinimumCount() - 1;
                            for (k=j+1; (k < annosDupl.size()) && (requiredCount > 0); k++) {
                                if (annosDupl.get(k) == null)
                                    continue;
                                if (comb.getName().equals( ((AnnotationDesc)annosDupl.get(k)).getAnnotationType() )) {
                                    requiredCount--;
                                }
                            }
                            if (requiredCount == 0) {
                                matchedType = true;
                                
                                combinedAnnos.add(anno);
                                for (k=j+1; k < annosDupl.size(); k++) {
                                    if (annosDupl.get(k) == null)
                                        continue;
                                    if (comb.getName().equals( ((AnnotationDesc)annosDupl.get(k)).getAnnotationType() )) {
                                        countOfAnnos++;
                                        combinedAnnos.add(annosDupl.get(k));
                                    }
                                }
                            }
                            break;
                        }
                        
                    }
                    
                }
                
                if (matchedType) {
                    if (comb.isOptional())
                        valid_optional_count++;
                } else {
                    if (!comb.isOptional()) {
                        matchedComb = false;
                        break;
                    }
                }
                
            }
            if (combType.getMinimumOptionals() > valid_optional_count)
                matchedComb = false;

            AnnotationCombination annoComb = null;
            if (matchedComb) {

                boolean activateComb = false;
                
                for (i=0; i 0)
                    active = (AnnotationDesc)annosVisible.get(0);
                else
                    active = null;
            }
        }

        /** Is this given mark still referenced by some annotation or it
         * can be removed from the draw mark chain */
        public boolean isMarkStillReferenced(Mark mark) {
            AnnotationDesc anno;
            for( Iterator it = annos.listIterator(); it.hasNext(); ) {
                anno = (AnnotationDesc)it.next();
                if (anno.getMark() == mark)
                   return true;
            }
            return false;
        }
        
        public Iterator getAnnotations() {
            return annos.iterator();
        }
        
    }
    

    /** Listener for listening on changes in Annotations object.*/
    public interface AnnotationsListener extends EventListener {

        /** This method is fired when annotations on the line are changed - 
         * annotation was added, removed, changed, etc. */
        public void changedLine(int Line);

        /** It is not possible to trace what have changed and so the listeners
         * are only informed that something has changed and that the change
         * must be reflected somehow (most probably by complete redraw). */
        public void changedAll();

    }

    /** Annotation which is used for representation of combined annotations. 
     * Some basic operations like getLine etc. are delegated to one of the
     * annotations which are representd by this combined annotation. The only
     * added functionality is for tooltip text and annotation type.
     */
    private static class AnnotationCombination extends AnnotationDesc {
        
        /** Delegate annotaiton */
        private AnnotationDesc delegate;

        /** Annotation type */
        private String type;
        
        /** List of annotations which are combined */
        private LinkedList list;
        
        public AnnotationCombination(String type, AnnotationDesc delegate) {
            super(delegate.getOffset(), delegate.getLength());
            this.delegate = delegate;
            this.type = type;
            updateAnnotationType();
            list = new LinkedList();
            list.add(delegate);
        }
        
        /** Getter for offset of this annotation  */
        public int getOffset() {
            return delegate.getOffset();
        }
        
        /** Getter for line number of this annotation  */
        public int getLine() {
            return delegate.getLine();
        }
        
        /** Getter for localized tooltip text for this annotation  */
        public String getShortDescription() {
            return getAnnotationTypeInstance().getDescription();
        }
        
        /** Getter for annotation type name  */
        public String getAnnotationType() {
            return type;
        }

        /** Add the annotation to this combination */
        public void addCombinedAnnotation(AnnotationDesc anno) {
            list.add(anno);
        }

        /** Is the given annotation part of this combination */
        public boolean isAnnotationCombined(AnnotationDesc anno) {
            if (list.indexOf(anno) == -1)
                return false;
            else
                return true;
        }

        /** Get Mark which represent this annotation in document */
        Mark getMark() {
            return delegate.getMark();
        }
        
    }

    public static final class MenuComparator implements Comparator {

       
        public MenuComparator() {
        }
        
        public int compare(Object o1, Object o2) {
            JMenu menuOne = (JMenu)o1;
            JMenu menuTwo = (JMenu)o2;
            if (menuTwo == null || menuOne == null) return 0;
            String menuOneText = menuOne.getText();
            String menuTwoText = menuTwo.getText();
            if (menuTwoText == null || menuOneText == null) return 0;
            return menuOneText.compareTo(menuTwoText);
        }

    }
    
}
... 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.