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.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.io.*;
import java.lang.ref.WeakReference;
import java.nio.CharBuffer;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.*;
import javax.swing.Timer;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.jmi.reflect.JmiException;

import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.jmi.javamodel.*;
import org.netbeans.jmi.javamodel.Element;
import org.netbeans.modules.java.parser.JavaParser;
import org.netbeans.modules.java.settings.JavaSettings;
import org.netbeans.modules.java.ui.nodes.SourceNodeFactory;
import org.netbeans.modules.java.ui.nodes.SourceNodes;
import org.netbeans.modules.javacore.JMManager;
import org.netbeans.modules.javacore.RepositoryUpdater;
import org.netbeans.modules.javacore.RequestPoster;
import org.netbeans.modules.javacore.internalapi.JavaMetamodel;
import org.netbeans.modules.javacore.internalapi.ParsingListener;
import org.netbeans.modules.javacore.internalapi.UndoManager;
import org.openide.awt.UndoRedo;
import org.openide.cookies.LineCookie;
import org.openide.loaders.DataObject;
import org.openide.loaders.MultiDataObject;
import org.openide.nodes.Node;
import org.openide.src.SourceElement;
import org.openide.text.*;
import org.openide.text.Annotation;
import org.openide.util.*;
import org.openide.util.actions.SystemAction;
import org.openide.windows.*;
import org.openide.ErrorManager;


/*
* TODO:
* 1) undo support
*/
/** Java source-file extension for handling the Editor.
* The main purpose of this class is to manage guarded sections.
*
*
* @author Petr Hamernik
*/
public class JavaEditor extends EditorSupport implements PropertyChangeListener {
    /** The prefix of all magic strings */
    final static String MAGIC_PREFIX = "//GEN-"; // NOI18N

    /** Magic strings - special comments which are inserted during saving
     * and are removed during loading.
     */
    private static String[] SECTION_MAGICS;
    private static final int LONGEST_ITEM = 10;

    static {

        StringBuffer sb = new StringBuffer(MAGIC_PREFIX);
        int size = sb.length();

        SECTION_MAGICS = new String[7];
        SECTION_MAGICS[0] = sb.append("LINE:").toString(); // NOI18N
        sb.setLength(size);
        SECTION_MAGICS[1] = sb.append("BEGIN:").toString(); // NOI18N
        sb.setLength(size);
        SECTION_MAGICS[2] = sb.append("END:").toString(); // NOI18N
        sb.setLength(size);
        SECTION_MAGICS[3] = sb.append("HEADER:").toString(); // NOI18N
        sb.setLength(size);
        SECTION_MAGICS[4] = sb.append("HEADEREND:").toString(); // NOI18N
        sb.setLength(size);
        SECTION_MAGICS[5] = sb.append("FIRST:").toString(); // NOI18N
        sb.setLength(size);
        SECTION_MAGICS[6] = sb.append("LAST:").toString(); // NOI18N
    }

    /** Types of the magic comments. */
    private final static int T_LINE      = 0;
    private final static int T_BEGIN     = 1;
    private final static int T_END       = 2;
    private final static int T_HEADER    = 3;
    private final static int T_HEADEREND = 4;
    // obsoleted - only for backward compatibility
    private final static int T_FIRST     = 5;
    private final static int T_LAST      = 6;

    /** Table of the guarded sections. Keys are the names of the sections
    * and values are the GuardedSection classes. The table is null till
    * while document is not in the memory.
    */
    HashMap sections = null; 

    /** Timer which countdowns the auto-reparsing time. */
    Timer timer;

    /** New lines in this file was delimited by '\n' */
    static final byte NEW_LINE_N = 0;

    /** New lines in this file was delimited by '\r' */
    static final byte NEW_LINE_R = 1;

    /** New lines in this file was delimited by '\r\n' */
    static final byte NEW_LINE_RN = 2;

    /** The type of new lines */
    byte newLineType;

    private transient boolean hasAnnotations = false;

    /**
     * Helper variable for document reloading support. If the loadFromStream is called
     * and reloading is true, source reparsing is forced.
     */
    private boolean reloading = false;

    private static final Comparator SECTION_COMPARATOR = new GuardedPositionComparator();

    /* List of error annotations attached to this document */
    private ArrayList errorAnnotations=new ArrayList();

    /** queue processing error annotations */
    private static final RequestProcessor ERROR_ANNOTATION_QUEUE = 
            new RequestProcessor("Error Annotation Queue", 1); // NOI18N 

    private boolean parsingAttached;

    private ParsingListener wParsingL;
    
    private OverrideAnnotationSupport overriddensSupport;

    /** Classpaths associated with this file.
     *  Never use this fields directly, may not be initialized.
     *  Use the corresponging gethers.  */
    private ClassPath sourceClasspath;
    private ClassPath librariesClasspath;
    private ClassPath bootClasspath;

    private UndoRedo.Manager undoRedo = null;
    private boolean undoRedoPrecreated = false;

    private transient String resourceMofId = null;
    private transient WeakReference resource = null;

    private final ParsingListener listener = new ParsingListener() {
        public void resourceParsed(final Resource resource) {
            JavaMetamodel.getDefaultRepository().beginTrans(false);
            try {
                if (resource == getResource()) {
                    notifyParsingDone();
                }
            } finally {
                JavaMetamodel.getDefaultRepository().endTrans();
            }
        }
    };

    /** Create a new Editor support for the given Java source.
    * @param entry the (primary) file entry representing the Java source file
    */
    public JavaEditor(MultiDataObject.Entry entry) {
        super(entry);
        // add change listener
        addChangeListener(new JavaEditorChangeListener());
        JavaMetamodel.getUndoManager().addPropertyChangeListener(new UndoManagerListener(this));
    }

    /** Finds data object the editor belongs to.
    * @return data object or null
    */
    DataObject getDataObject() {
        return findDataObject();
    }

    synchronized UndoRedo.Manager getUndoRedo() {
        if (undoRedo == null) {
            undoRedo = super.createUndoRedoManager();
            undoRedoPrecreated = true;
        }
        return undoRedo;
    }
    
    protected UndoRedo.Manager createUndoRedoManager() {
        if (undoRedoPrecreated) {
            undoRedoPrecreated = false;
            return undoRedo;
        }
        undoRedo = super.createUndoRedoManager();
        return undoRedo;
    }
    
    private void changeTimeoutElapsed() {
        parseSource(false, true);
    }

    private void parseSource(boolean force, final boolean refreshAnnotations) {
        if (force) {
            JavaMetamodel.getManager().addModified((JavaDataObject) findDataObject());
        }
        ERROR_ANNOTATION_QUEUE.post(new Runnable() {
            public void run() {
                //beginTrans(writeAccess=true) cause reparse of all registered data objects
                JavaMetamodel.getDefaultRepository().beginTrans(true);
                JavaMetamodel.getDefaultRepository().endTrans(false);
                if (refreshAnnotations)
                    refreshAnnotations();
            }
        });
    }

    private void classpathChanged() {
        parseSource(true, true);
    }

    private void parsingErrorsChanged(PropertyChangeEvent evt) {
        int errors=JavaSettings.getDefault().getParsingErrors();
        Integer old=(Integer)evt.getOldValue();
        int oldErrors=JavaSettings.DEFAULT_PARSING_ERRORS;

        if (old!=null) {
            oldErrors=old.intValue();
        }
        if (oldErrors==errors) // no change
            return;
        if (errors==0 && !errorAnnotations.isEmpty()) { // dettach all annotations
            detachAnnotations(errorAnnotations);
            errorAnnotations.clear();
            return;
        }
        if (oldErrors==errorAnnotations.size() || errors 0) {
                GuardedWriter writer = new GuardedWriter(os, list, encoding);
                kit.write(writer, doc, 0, doc.getLength());
                return;
            }
        }
        Writer w;
        if (encoding == null)
            w = new OutputStreamWriter(os);
        else
            w = new OutputStreamWriter(os, encoding);
        kit.write(w, doc, 0, doc.getLength());
    }

    /** Reload the document when changed externally */
    protected Task reloadDocumentTask () {
	StyledDocument doc = null;
	    // PENDING: do not openDocument() to obtain doc, as all text is removed from the
	    // document, guarded sections are (probably) erased anyway. This causes two passes
	    // through loadFromStreamToKit!
            try {
                doc = openDocument();
            }
            catch (IOException e) {
                // no reload performed in this case
            }

            if (doc != null) {
                clearSections();
                NbDocument.unmarkGuarded(doc, 0, doc.getLength());
		reloading = true;
                return super.reloadDocumentTask ();
            }
            else
                return null;
    }

    /** Save the document in this thread and start reparsing it.
    * @exception IOException on I/O error
    */
    public void saveDocument () throws IOException {
        saveDocument(true);
    }

    /** Save the document in this thread.
    * @param parse true if the parser should be started, otherwise false
    * @exception IOException on I/O error
    */
    protected void saveDocumentIfNecessary(boolean parse) throws IOException {
        saveDocument(false);
    }

    /** Save the document in this thread.
    * @param forceSave if true save always, otherwise only when is modified
    * @exception IOException on I/O error
    */
    private void saveDocument(boolean forceSave) throws IOException {
        if (forceSave || isModified()) {
            RepositoryUpdater.getDefault().addFileObjectToSave(findDataObject().getPrimaryFile());
            super.saveDocument();
        }
    }

    private void processAnnotations(List errors) {
        ArrayList added,removed,unchanged;
        Collection newAnnotations;
        newAnnotations = getAnnotations(errors);
        added=new ArrayList(newAnnotations);
        added.removeAll(errorAnnotations);
        unchanged=new ArrayList(errorAnnotations);
        unchanged.retainAll(newAnnotations);
        removed=errorAnnotations;
        removed.removeAll(newAnnotations);
        detachAnnotations(removed);
        if (!added.isEmpty() && isDocumentLoaded()) {

            // Partial fix of #33165 - document read-locking
            final ArrayList finalAdded = added;
            StyledDocument doc = getDocument();
            Runnable docRenderer = new Runnable() {
                public void run() {
                    LineCookie cookie = (LineCookie)findDataObject().getCookie(LineCookie.class);
                    Line.Set lines = cookie.getLineSet();

                    for (Iterator i=finalAdded.iterator();i.hasNext();) {
                        ParserAnnotation ann=(ParserAnnotation)i.next();

                        ann.attachToLineSet(lines);
                    }
                }
            };

            if (doc != null) {
                doc.render(docRenderer);
            } else {
                docRenderer.run();
            }
        }

        errorAnnotations=unchanged;
        errorAnnotations.addAll(added);
    }


    /** @return annotations for the given list of errors */
    private Collection getAnnotations(List errors) {
        HashMap map = new HashMap(2*errors.size());
        int maxErrors = JavaSettings.getDefault().getParsingErrors();
        for (Iterator it = errors.iterator(); it.hasNext();) {
            ErrorInfo err = (ErrorInfo) it.next();
            int line = err.getLineNumber();

            if (line>0) {  // annotate only errors with positive line number
                int column = err.getColumn();
                String message = err.getDescription();
                ParserAnnotation anno = new ParserAnnotation(line, column, err.getSeverity(), message);

                // This is trying to ensure that annotations on the same
                // line are "chained" (so we get a single annotation for
                // multiple errors on a line).
                // If we knew the errors were sorted by file & line number,
                // this would be easy (and we wouldn't need to do the hashmap
                // "sort"
                Integer lineInt = new Integer(line);
                ParserAnnotation prev = (ParserAnnotation)map.get(lineInt);
                if (prev != null) {
                    prev.chain(anno);
                } else if (map.size() < maxErrors) {
                    map.put(lineInt, anno);
                }
            }
        }
        return map.values();
    }

    private static void detachAnnotations(Collection anns) {
        Iterator i;

        for (i=anns.iterator();i.hasNext();) {
            Annotation ann=(Annotation)i.next();
            if (ann.getAttachedAnnotatable() != null) {
                ann.detach();
            }
        }
    }

    private ClassPath getBootClassPath () {
        if (this.bootClasspath == null) {
            this.bootClasspath = ClassPath.getClassPath (this.entry.getFile(), ClassPath.BOOT);
        }
        return this.bootClasspath;
    }

    private ClassPath getLibrariesPath () {
        if (this.librariesClasspath == null) {
            this.librariesClasspath = ClassPath.getClassPath (this.entry.getFile(), ClassPath.COMPILE);
        }
        return this.librariesClasspath;
    }

    private ClassPath getSourcePath () {
        if (this.sourceClasspath == null) {
            this.sourceClasspath = ClassPath.getClassPath (this.entry.getFile(), ClassPath.SOURCE);
        }
        return this.sourceClasspath;
    }

    // ==================== SourceCookie.Editor methods =================

    /** Returns a source element describing the hierarchy of the source.
    * @return the element
    * @deprecated Please use DataObject services to obtain java hierarchy.
    */
    public SourceElement getSource() {
        return ((JavaDataObject)findDataObject()).getSource();
    }

    /** Translate a source element to text.
    *
    * @param element an element from the source hierarchy
    * @return a text element
    * @deprecated Please use DataObject's cookies to translate swing <-> org.openide.src
    */
    public javax.swing.text.Element sourceToText(org.openide.src.Element element) {
        return null;
    }

    /** Translate a text element to a source element, if it is possible to do so.
    *
    * @param element a text element
    * @return the element from the source hierarchy
    * @exception NoSuchElementException if the text element doesn't match
    *  any element from the source hierarchy
    * @deprecated Please use DataObject's cookies to translate swing <-> org.openide.src
    */
    public org.openide.src.Element textToSource(javax.swing.text.Element element) throws NoSuchElementException {
        throw new NoSuchElementException();
    }

    /** Find the element at the specified offset in the document.
    * @param offset The position of the element
    * @return the element at the position.
    * @deprecated Please use DataObject's cookies to translate swing <-> org.openide.src
    */
    public org.openide.src.Element findElement(int offset) {
        return null;
    }

    // ==================== Guarded sections public methods =================

    /**
     * Creates an empty simple section at the given position.
     * The position must not be within any existing guarded section or
     * Java Element and the passed name must not be registered to other
     * already existing section. The created section will initially contain
     * one space and a newline.
     * @return SimpleSection instance that can be used for generating text into
     * the protected region
     * @throws IllegalArgumentException if either the name has been already used, or
     * the position is inside another section or Java Element.
     * @throws BadLocationException if pos is outside of document's scope, or
     * the document does not permit creating the guarded section.
     */
    public SimpleSection createSimpleSection(PositionRef pos, String name)
        throws IllegalArgumentException, BadLocationException {
        checkOverlap(pos);
        return doCreateSimpleSection(pos, name);
    }

    /*
     * Creates a simple section over the given bounds in the source text.
     * The bounds must not overlap with any existing section or Java Element
     * and the passed name must not be registered to other
     * already existing section. The section will then contain all the text
     * inside the passed PositionBounds.
     * @return SimpleSection instance that can be used for generating text into
     * the protected region
     * @throw IllegalArgumentException if either the name has been already used, or
     * the bounds overlap with another section or Java Element.
     * @throw BadLocationException if pos is outside of document's scope, or
     * the document does not permit creating the guarded section.
     */
    public SimpleSection createSimpleSection(PositionBounds bounds, String name)
        throws IllegalArgumentException, BadLocationException {
        checkOverlap(bounds);
        return doCreateSimpleSection(bounds, name);
    }

    private void checkOverlap(PositionRef pos) {
        Iterator it = sections.values().iterator();
        while (it.hasNext()) {
            GuardedSection s = (GuardedSection)it.next();
            if (s.contains(pos, false)) {
                throw new IllegalArgumentException("Sections overlap"); // NOI18N
            }
        }
    }

    /*
     * Tests if bounds overlap with any existing guarded section.
     * @return true if bounds does not overlap with any section, otherwise false
     */
    public boolean testOverlap(PositionBounds bounds) {
        try {
            StyledDocument loadedDoc = openDocument();
        }
        catch (IOException e) {
            throw new IllegalArgumentException();
        }
        try {
            checkOverlap(bounds,true);
        } catch (IllegalArgumentException e){
            return false;
        }
        return true;
    }

    private void checkOverlap(PositionBounds bounds) throws IllegalArgumentException {
        checkOverlap(bounds,false);
    }

    private void checkOverlap(PositionBounds bounds,boolean allowHoles) throws IllegalArgumentException {
        Collection c = new TreeSet(new GuardedPositionComparator());
        c.addAll(sections.values());

        Iterator it = c.iterator();
        PositionRef begin = bounds.getBegin();
        PositionRef end = bounds.getEnd();
        int beginOffset = begin.getOffset();
        int endOffset = end.getOffset();
        GuardedSection starting = null;
        boolean overlapOK = false;

        while (it.hasNext()) {
            GuardedSection s = (GuardedSection)it.next();
            if (s.contains(begin, allowHoles) || s.contains(end, allowHoles))
                throw new IllegalArgumentException("Sections overlap"); // NOI18N
            if (s.getBegin().getOffset() > beginOffset) {
                if (s.getBegin().getOffset() < endOffset) {
                    throw new IllegalArgumentException("Sections overlap"); // NOI18N
                }
                break;
            }
        }
    }

    private SimpleSection doCreateSimpleSection(final PositionBounds bounds, final String name)
    throws IllegalArgumentException, BadLocationException  {
        StyledDocument loadedDoc = null;
        try {
            loadedDoc = openDocument();
        }
        catch (IOException e) {
            throw new IllegalArgumentException("Cannot load document"); // NOI18N
        }
        final StyledDocument doc = loadedDoc;
        final SimpleSection[] sect = new SimpleSection[] { null };

        Util.ExceptionRunnable run = new Util.ExceptionRunnable() {
            public void run() throws Exception {
                sect[0] = new SimpleSection(name,
                createBounds(bounds.getBegin().getOffset(), bounds.getEnd().getOffset(), false)
                );
                sections.put(sect[0].getName(), sect[0]);
                sect[0].markGuarded(doc);
            }
        };
        try {
            Util.runAtomic(doc, run);
	    notifyModified();
        }
        catch (Exception e) {
            if (e instanceof BadLocationException)
                throw (BadLocationException) e;
            else
                throw new IllegalArgumentException();
        }
        return sect[0];
    }

    private SimpleSection doCreateSimpleSection(final PositionRef pos, final String name)
        throws IllegalArgumentException, BadLocationException  {
        StyledDocument loadedDoc = null;
        try {
            loadedDoc = openDocument();
        }
        catch (IOException e) {
            throw new IllegalArgumentException();
        }
        final StyledDocument doc = loadedDoc;
        final SimpleSection[] sect = new SimpleSection[] { null };

        Util.ExceptionRunnable run = new Util.ExceptionRunnable() {
                                         public void run() throws Exception {
                                             int where = pos.getOffset();
                                             doc.insertString(where, "\n \n", null); // NOI18N
                                             sect[0] = new SimpleSection(name,
                                                                         createBounds(where + 1, where + 3, false)
                                                                        );
                                             sections.put(sect[0].getName(), sect[0]);
                                             sect[0].markGuarded(doc);
                                         }
                                     };
        try {
            Util.runAtomic(doc, run);
	    notifyModified();
        }
        catch (Exception e) {
            if (e instanceof BadLocationException)
                throw (BadLocationException) e;
            else
                throw new IllegalArgumentException();
        }
        return sect[0];
    }

    /**
     * Create new simple guarded section at a specified place.
     * @param previous section to create the new one after
     * @param name the name of the new section
     * @exception IllegalArgumentException if the name is already in use
     * @exception BadLocationException if it is not possible to create a
     *            new guarded section here
     */
    public SimpleSection createSimpleSectionAfter(final GuardedSection previous,
    final String name)
    throws IllegalArgumentException, BadLocationException {
        PositionBounds bounds;
        if (previous instanceof SimpleSection)
            bounds = ((SimpleSection) previous).bounds;
        else
            bounds = ((InteriorSection) previous).bottom;
        if ((previous == null) || (!previous.valid))
            throw new IllegalArgumentException("Invalid guarded block"); // NOI18N

        return doCreateSimpleSection(bounds.getEnd(), name);
    }

    public InteriorSection createInteriorSection(PositionRef pos, String name)
    throws IllegalArgumentException, BadLocationException {
        checkOverlap(pos);
        return doCreateInteriorSection(pos, name);
    }

    public InteriorSection createInteriorSectionAfter(GuardedSection previous,
        String name) throws IllegalArgumentException, BadLocationException {
            PositionBounds bounds;
        if (previous instanceof SimpleSection)
            bounds = ((SimpleSection) previous).bounds;
        else
            bounds = ((InteriorSection) previous).bottom;
        if ((previous == null) || (!previous.valid))
            throw new IllegalArgumentException("Invalid guarded block"); // NOI18N
        return doCreateInteriorSection(bounds.getEnd(), name);
    }

    public InteriorSection createInteriorSection(PositionBounds bounds, PositionBounds interior,
        String name) throws IllegalArgumentException, BadLocationException {
        checkOverlap(bounds);
        if (bounds.getBegin().getOffset() > interior.getEnd().getOffset() ||
            bounds.getEnd().getOffset() < interior.getEnd().getOffset())
            throw new IllegalArgumentException("Interior is not nested."); // NOI18N
        return doCreateInteriorSection(bounds, interior, name);
    }

    private InteriorSection doCreateInteriorSection(final PositionBounds bounds,
    final PositionBounds interiorBounds, final String name)
    throws IllegalArgumentException, BadLocationException {
        StyledDocument loadedDoc = null;
        try {
            loadedDoc = openDocument();
        }
        catch (IOException e) {
            throw new IllegalArgumentException("Cannot load document"); // NOI18N
        }

        final StyledDocument doc = loadedDoc;
        final InteriorSection[] sect = new InteriorSection[] { null };

        Util.ExceptionRunnable run = new Util.ExceptionRunnable() {
            public void run() {
                sect[0] = new InteriorSection(name,
                createBounds(bounds.getBegin().getOffset(), interiorBounds.getBegin().getOffset(), false),
                createBounds(interiorBounds.getBegin().getOffset(), interiorBounds.getEnd().getOffset(), true),
                createBounds(interiorBounds.getEnd().getOffset(), bounds.getEnd().getOffset(), false)
                );
                sections.put(sect[0].getName(), sect[0]);
                sect[0].markGuarded(doc);
            }
        };
        try {
            Util.runAtomic(doc, run);
	    notifyModified();
        }
        catch (Exception e) {
            if (e instanceof BadLocationException)
                throw (BadLocationException) e;
            else
                throw new IllegalArgumentException();
        }
        return sect[0];
    }

    /** Create new interior guarded section at a specified place.
    * @param pos section to create the new one after
    * @param name the name of the new section
    * @exception IllegalArgumentException if the name is already in use
    * @exception BadLocationException if it is not possible to create a
    *            new guarded section here
    */
    private InteriorSection doCreateInteriorSection(final PositionRef pos,
            final String name)
    throws IllegalArgumentException, BadLocationException {
        StyledDocument loadedDoc = null;
        try {
            loadedDoc = openDocument();
        }
        catch (IOException e) {
            throw new IllegalArgumentException();
        }

        final StyledDocument doc = loadedDoc;
        final InteriorSection[] sect = new InteriorSection[] { null };

        Util.ExceptionRunnable run = new Util.ExceptionRunnable() {
            public void run() throws Exception {
                int where = pos.getOffset();
                doc.insertString(where, "\n \n \n \n", null); // NOI18N
                sect[0] = new InteriorSection(name,
                createBounds(where + 1, where + 3, false),
                createBounds(where + 3, where + 5, true),
                createBounds(where + 5, where + 7, false)
                );
                sections.put(sect[0].getName(), sect[0]);
                sect[0].markGuarded(doc);
            }
        };
        try {
            Util.runAtomic(doc, run);
	    notifyModified();
        }
        catch (Exception e) {
            if (e instanceof BadLocationException)
                throw (BadLocationException) e;
            else
                throw new IllegalArgumentException();
        }
        return sect[0];
    }

    /** Try to find the simple section of the given name.
    * @param name the name of the requested section
    * @return the found guarded section or null if there is no section
    *         of the given name
    */
    public SimpleSection findSimpleSection(String name) {
        GuardedSection s = findSection(name);
        return (s instanceof SimpleSection) ? (SimpleSection) s : null;
    }

    /** Try to find the interior section of the given name.
    * @param name the name of the looked-for section
    * @return the found guarded section or null if there is no section
    *         of the given name
    */
    public InteriorSection findInteriorSection(String name) {
        GuardedSection s = findSection(name);
        return (s instanceof InteriorSection) ? (InteriorSection) s : null;
    }

    /** Try to find the section of the given name.
    * @param name the name of the looked-for section
    * @return the found guarded section or null if there is no section
    *         of the given name
    */
    public GuardedSection findSection(String name) {
        try {
            StyledDocument doc = openDocument ();
            synchronized (this) {
                if (sections != null)
                    return (GuardedSection) sections.get(name);
            }
        }
        catch (IOException e) {
        }
        return null;
    }

    /** Get all sections.
    * @return an iterator of {@link JavaEditor.GuardedSection}s
    */
    public Iterator getGuardedSections() {
        try {
            StyledDocument doc = openDocument ();
            synchronized (this) {
                if (sections != null)
                    return ((HashMap)sections.clone()).values().iterator();
            }
        }
        catch (IOException e) {
        }
        return Collections.EMPTY_SET.iterator();
    }

    /** Get all section names.
    * @return an iterator of {@link String}s
    */
    public Iterator getGuardedSectionNames() {
        try {
            StyledDocument doc = openDocument ();
            synchronized (this) {
                if (sections != null)
                    return ((HashMap)sections.clone()).keySet().iterator();
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        return Collections.EMPTY_SET.iterator();
    }

    /**
     * Finds a position not obscured by a guarded block that is inside the given
     * bounds. Favors positions at the beginning of the passed bounds. Returns null,
     * if the document cannot be loaded.
     * @param bnds bounds to search within
     * @return PositionRef that can be safely written into.
     */
    public PositionRef findFreePosition(PositionBounds bnds) {
        StyledDocument doc;
        try {
            doc = openDocument();
        } catch (IOException ex) {
            return null;
        }

        PositionRef beginPos = bnds.getBegin();
        int startOffs = beginPos.getOffset();

        TreeSet set = new TreeSet(SECTION_COMPARATOR);
        set.addAll(this.sections.values());
        for (Iterator it = set.iterator(); it.hasNext(); ) {
            GuardedSection s = (GuardedSection)it.next();
            PositionRef start = s.getBegin();
            if (start.getOffset() > startOffs) {
                // no section at the start.
                break;
            }
            if (s.contains(beginPos, false)) {
                // got guarded block that contains
                PositionRef after = s.getPositionAfter();
                if (after.getOffset() > bnds.getEnd().getOffset()) {
                    return null;
                }
                return after;
            }
        }
        return beginPos;
    }

    // ==================== Misc not-public methods ========================

    /* A method to create a new component. Overridden in subclasses.
    * @return the {@link Editor} for this support
    */
    protected CloneableTopComponent createCloneableTopComponent () {
        prepareDocument();
        return createJavaEditorComponent();
    }

    /** Method for creation of the java editor component
    * - accessible from the innerclass.
    */
    JavaEditorComponent createJavaEditorComponent() {
        DataObject obj = findDataObject ();
        JavaEditorComponent editor = new JavaEditorComponent(obj);
        editor.setIcon(obj.getNodeDelegate().getIcon(java.beans.BeanInfo.ICON_COLOR_16x16));

        // dock into editor mode if possible
        Mode editorMode = WindowManager.getDefault().findMode(EDITOR_MODE);
        if (editorMode != null)
            editorMode.dockInto(editor);

        return editor;
    }

    /** Set all sections as invalid. It is called from closeLast method
    * of the JavaEditorComponent.
    */
    synchronized void clearSections() {
        if (sections != null) {
            Iterator it = ((HashMap)sections.clone()).values().iterator();
            while (it.hasNext()) {
                GuardedSection sect = (GuardedSection) it.next();
                sect.valid = false;
            }
            sections = null;
        }
    }

    PositionRef findUnguarded(PositionRef fromWhere, boolean allowHoles, boolean after) {
	Iterator it = getGuardedSections();

	while (it.hasNext()) {
	    GuardedSection sect = (GuardedSection)it.next();
	    if (sect.contains(fromWhere, allowHoles)) {
		if (after) {
		    return sect.getPositionAfter();
		} else {
		    return sect.getPositionBefore();
		}
	    }
	}
	return fromWhere;
    }

    /** The real component of the Java editor.
    * Subclasses should not attempt to work with this;
    * if they require special editing support, separate windows
    * should be created by overriding (e.g.) {@link EditorSupport#open}.
    */
    public static class JavaEditorComponent extends EditorSupport.Editor {
        /** Default delay between cursor movement and updating selected element nodes. */
        static final int SELECTED_NODES_DELAY = 1000;

        /** Timer which countdowns the "update selected element node" time. */ // NOI18N
        Timer timerSelNodes;

        /** The support, subclass of EditorSupport */
        JavaEditor support;

        /** Listener on caret movements */
        CaretListener caretListener;

        /**
         * Toolbar that is displayed at the top of the editor window.
         * Lazily initialized in enableToolBar / createToolBar.
         */
        java.awt.Component toolBar;

        /** The last caret offset position. */
        int lastCaretOffset = -1;

        static final long serialVersionUID =6223349196427270209L;

        /** Only for externalization */
        public JavaEditorComponent () {
            super();
        }

        /** Creates new editor */
        public JavaEditorComponent (DataObject obj) {
            super(obj);
            initialize();
        }

        private transient org.openide.util.RequestProcessor.Task selectionTask = null;

        private final RequestPoster elementSelectionPoster = new RequestPoster();
    
        /** Selects element at the given position. */
        void selectElementsAtOffset(final int offset) {
            elementSelectionPoster.post(new Runnable() {
                public void run() {
                    final DataObject d = support.findDataObject();
                    if (!isActiveTC() || d == null || !d.isValid()) {
                      return;
                    }
                    
                    final Node n;
                    final Node[] ns = new Node[1];
                    org.openide.nodes.Children.MUTEX.readAccess(new Runnable() {
                        public void run() {
                            ns[0] = createNode(offset, d);
                        }
                    });
                    n = ns[0];
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            setActivatedNodes((n != null) ? new Node[] { n } : new Node[] {} );
                        }
                    });
                }
            });
        }
        
        private static Node createNode(int offset, DataObject d) {
            SourceNodeFactory factory = SourceNodes.getExplorerFactory();
            Node n = null;
            Element element;
            try {
                JMManager.getTransactionMutex().addPriorityThread();
                JavaMetamodel.getDefaultRepository().beginTrans(false);
                try {
                    Resource res = JavaMetamodel.getManager().getResource(d.getPrimaryFile());
                    element = res == null? null: findElement(res, offset);
                    
                    if (element instanceof Field) {
                        n = factory.createFieldNode((Field) element);
                    } else if (element instanceof Attribute) {
                        n = factory.createAnnotationTypeMethodNode((Attribute) element);
                    } else if (element instanceof AnnotationType) {
                        n = factory.createAnnotationTypeNode((AnnotationType) element);
                    } else if (element instanceof Constructor) {
                        n = factory.createConstructorNode((Constructor) element);
                    } else if (element instanceof EnumConstant) {
                        n = factory.createEnumConstantNode((EnumConstant) element);
                    } else if (element instanceof Initializer) {
                        n = factory.createInitializerNode((Initializer) element);
                    } else if (element instanceof Method) {
                        n = factory.createMethodNode((Method) element);
                    } else if (element instanceof JavaEnum) {
                        n = factory.createEnumNode((JavaEnum) element);
                    } else if (element instanceof JavaClass) {
                        n = factory.createClassNode((JavaClass) element);
                    } else if (element instanceof Resource) {
                        n = d.getNodeDelegate();
                    } else {
                        n = null;
                    }
                } finally {
                    JavaMetamodel.getDefaultRepository().endTrans();
                }
            } catch (JmiException e) {
                ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
            }
            
            return n;
        }
        
        /**
         * finds class member on particular offset. It works around the issue
         * of body parsing
         * @param r resource to look up
         * @param offset offset
         * @return the class member
         */ 
        private static Element findElement(Resource r, int offset) {
            Iterator cit = r.getClassifiers().iterator();
            Element el = r;
            while (cit.hasNext()) {
                JavaClass jc = (JavaClass) cit.next();
                PositionBounds bounds = JavaMetamodel.getManager().getElementPosition(jc);
                if (bounds != null && offset >= bounds.getBegin().getOffset() &&
                        offset <= bounds.getEnd().getOffset()) {
                    el = findElement(jc, offset);
                    break;
                }
            }
            return el;
        }
        
        /**
         * @see #findElement(org.netbeans.jmi.javamodel.Resource, int)
         */ 
        private static Element findElement(JavaClass jc, int offset) {
            Iterator classIt = jc.getFeatures().iterator();
            Element el = jc;
            while (classIt.hasNext()) {
                ClassMember cm = (ClassMember) classIt.next();
                PositionBounds bounds = JavaMetamodel.getManager().getElementPosition(cm);
                if (offset >= bounds.getBegin().getOffset() &&
                        offset <= bounds.getEnd().getOffset()) {
                    if (cm instanceof JavaClass) {
                        el = findElement((JavaClass) cm, offset);
                    } else {
                        el = cm;
                    }
                    break;
                }
            }
            
            return el;
        }

        protected boolean isActiveTC() {
            return getRegistry().getActivated() == JavaEditorComponent.this;
        }

        protected void notifyParsingDone() {
//            if (lastCaretOffset != -1) {
//                selectElementsAtOffset(lastCaretOffset);
//            }
        }

        /**
         * Refreshes the activated node immediately. Provides system actions
         * based on the node activated at the time of popu invoke.
         */
        public SystemAction[] getSystemActions() {
            selectElementsAtOffset(lastCaretOffset);
            timerSelNodes.stop();
            return super.getSystemActions();
        }

        /** Obtain a support for this component */
        private void initialize () {
            support = (JavaEditor)obj.getCookie(JavaEditor.class);
            assert support != null : "jdo.getCookie(JavaEditor.class) returned null for JDO " + obj.getPrimaryFile().getPath(); // NOI18N

            timerSelNodes = new Timer(200, new java.awt.event.ActionListener() {
                                          public void actionPerformed(java.awt.event.ActionEvent e) {
                                              if (lastCaretOffset == -1 && getEditorPane() != null) {
                                                  Caret caret = getEditorPane().getCaret();
                                                  if (caret != null)
                                                    lastCaretOffset = caret.getDot();
                                              }
                                              selectElementsAtOffset(lastCaretOffset);
                                          }
                                      });
            timerSelNodes.setInitialDelay(200);
            timerSelNodes.setRepeats(false);
            caretListener = new CaretListener() {
                                public void caretUpdate(CaretEvent e) {
                                    support.restartTimer(true);
                                    restartTimerSelNodes(e.getDot());
                                }
                            };
            timerSelNodes.restart();
        }

        /** Restart the timer which updates the selected nodes after the specified delay from
        * last caret movement.
        */
        void restartTimerSelNodes(int pos) {
            timerSelNodes.setInitialDelay(SELECTED_NODES_DELAY);
            timerSelNodes.restart();
            lastCaretOffset = pos;
        }


        /* Is called from the clone method to create new component from this one.
        * This implementation only clones the object by calling super.clone method.
        * @return the copy of this object
        */
        protected CloneableTopComponent createClonedObject () {
            return support.createJavaEditorComponent();
        }

        /* This method is called when parent window of this component has focus,
        * and this component is preferred one in it.
        */
        protected void componentActivated () {
            JEditorPane p = getEditorPane();
            if (p != null)
                p.addCaretListener(caretListener);
            super.componentActivated ();
            // give focus to the editor pane, rather than the toolbar:
            if (p != null)
                p.requestFocusInWindow();
            if (!support.hasAnnotations) { // if we don't have annotations, schedule parsing
                ERROR_ANNOTATION_QUEUE.post(new Runnable() {
                    public void run() {
                        support.refreshAnnotations();
                        if (support.overriddensSupport != null) { //#43491
                            support.overriddensSupport.processOverriddenAnnotation();
                        }
                    }
                }, 1000, Thread.MIN_PRIORITY);
            }
            support.attachParsingListener();
        }

        public void requestFocus() {
            super.requestFocus();
            JEditorPane p = getEditorPane();
            if (p != null) {
                p.requestFocus();
            }
        }
        
        public boolean requestFocusInWindow() {
            super.requestFocusInWindow();
            JEditorPane p = getEditorPane();
            if (p != null) {
                return p.requestFocusInWindow();
            } else {
                return false;
            }
        }

        /*
        * This method is called when parent window of this component losts focus,
        * or when this component losts preferrence in the parent window.
        */
        protected void componentDeactivated () {
            JEditorPane p = getEditorPane();
            if (p != null)
                p.removeCaretListener(caretListener);
            support.removeParsingListener();
            synchronized (this) {
                if (selectionTask != null) {
                    selectionTask.cancel();
                    selectionTask = null;
                }
            }
            super.componentDeactivated ();
        }

        /** Deserialize this top component.
        * @param in the stream to deserialize from
        */
        public void readExternal (ObjectInput in)
        throws IOException, ClassNotFoundException {
            super.readExternal(in);
            initialize();
        }

    } // end of JavaEditorComponent inner class

    /** Takes the section descriptors from the GuardedReader and
    * fills the table 'sections', also marks as guarded all sections
    * in the given document.
    * @param is Where to take the guarded section descriptions.
    * @param doc Where to mark guarded.
    */
    private void fillSections(GuardedReader is, StyledDocument doc) {
        JavaEditor.SectionDesc descBegin = null;

        Iterator it = is.list.iterator();
        while (it.hasNext()) {
            SectionDesc descCurrent = (SectionDesc) it.next();
            GuardedSection sect = null;
            switch (descCurrent.type) {
            case T_LINE:
                sect = new SimpleSection(descCurrent.name,
                                         createBounds(descCurrent.begin,
                                                      descCurrent.end, false));
                break;

            case T_BEGIN:
            case T_HEADER:
            case T_FIRST:
                descBegin = descCurrent;
                break;

            case T_HEADEREND:
                if ((descBegin != null) &&
                        ((descBegin.type == T_HEADER) || (descBegin.type == T_FIRST)) &&
                        (descCurrent.name.equals(descBegin.name))
                   ) {
                    descBegin.end = descCurrent.end;
                }
                else {
                    //SYNTAX ERROR - ignore it.
                    descBegin = null;
                }
                break;

            case T_END:
            case T_LAST:
                if ((descBegin != null) && (descBegin.name.equals(descCurrent.name))) {
                    if ((descBegin.type == T_BEGIN) && (descCurrent.type == T_END)) {
                        // simple section
                        sect = new SimpleSection(descCurrent.name,
                                                 createBounds(descBegin.begin,
                                                              descCurrent.end, false));
                        break;
                    }
                    if (((descBegin.type == T_FIRST) && (descCurrent.type == T_LAST)) ||
                            ((descBegin.type == T_HEADER) && (descCurrent.type == T_END))) {
                        // interior section
                        sect = new InteriorSection(descCurrent.name,
                                                   createBounds(descBegin.begin, descBegin.end, false),
                                                   createBounds(descBegin.end, descCurrent.begin, true),
                                                   createBounds(descCurrent.begin, descCurrent.end, false)
                                                  );
                        break;
                    }
                }
                //SYNTAX ERROR - ignore it.
                descBegin = null;
                break;
            }

            if (sect != null) {
                sections.put(sect.getName(), sect);
                descBegin = null;
                sect.markGuarded(doc);
            }
        }
    }

    /** Simple creates the bounds for the two offsets. */
    public PositionBounds createBounds(int begin, int end, boolean dir) {
        if (dir) {
            return new PositionBounds(
                       createPositionRef(begin, Position.Bias.Forward),
                       createPositionRef(end, Position.Bias.Backward)
                   );
        }
        else {
            return new PositionBounds(
                       createPositionRef(begin, Position.Bias.Backward),
                       createPositionRef(end, Position.Bias.Forward)
                   );
        }
    }

    public void propertyChange(PropertyChangeEvent evt) {
        UndoManager undo = JavaMetamodel.getUndoManager();
        if (undo.isUndoAvailable() || undo.isRedoAvailable()) {
            getUndoRedo().discardAllEdits();
        }
    }

    // ==================== Public inner classes ========================

    /** Represents one guarded section.
    */
    public abstract class GuardedSection extends Object {
        /** Name of the section. */
        String name;

        /** If the section is valid or if it was removed. */
        boolean valid;

        /** Get the name of the section.
        * @return the name
        */
        public String getName() {
            return name;
        }

        /** Creates new section.
        * @param name Name of the new section.
        */
        GuardedSection(String name) {
            this.name = name;
            valid = true;
        }

        /** Set the name of the section.
        * @param name the new name
        * @exception PropertyVetoException if the new name is already in use
        */
        public void setName(String name) throws PropertyVetoException {
            if (!this.name.equals(name)) {
                synchronized (JavaEditor.this) {
                    if (valid) {
                        if (sections.get(name) != null)
                            throw new PropertyVetoException("", new PropertyChangeEvent(this, "name", this.name, name)); // NOI18N
                        sections.remove(this.name);
                        this.name = name;
                        sections.put(name, this);
                    }
                }
            }
        }

        /** Deletes the text of the section and
        * removes it from the table. The section will then be invalid
        * and it will be impossible to use its methods.
        *
        * @return true if the operation was successful, otherwise false
        */
        public boolean deleteSection() {
            synchronized (JavaEditor.this) {
                if (valid) {
                    try {
                        sections.remove(name);
                        // get document should always return the document, when section
                        // is deleted, because it is still valid (and valid is only
                        // when document is loaded.
                        unmarkGuarded(getDocument());
                        deleteText();
                        valid = false;
                        return true;
                    }
                    catch (BadLocationException e) {
                    }
                    catch (IOException e) {
                    }
                }
                return false;
            }
        }

        /**
         * Tests if the section is still valid - it is not removed from the
         * source.
         */
        public boolean isValid() {
            return valid;
        }

        /**
         * Removes the section from the Document, but retains the text contained
         * within. The method should be used to unprotect a region of code
         * instead of calling NbDocument.
         * @return true if the operation succeeded.
         */
        public boolean removeSection() {
            synchronized (JavaEditor.this) {
                if (!valid)
                    return false;
                sections.remove(name);
                // get document should always return the document, when section
                // is deleted, because it is still valid (and valid is only
                // when document is loaded.
                unmarkGuarded(getDocument());
                valid = false;
                return true;
            }
        }

        /** Delete one new-line character before the specified offset.
        * This method is used when guarded blocks are deleted. When new guarded block is created,
        * there is added one more new-line before it, so this method remove this char in the end of
        * guarded block life cycle.
        * It works only when there is "\n" char before the offset and no problem occured (IOException...)
        * @param offset The begin of removed guarded block.
        */
        void deleteNewLineBeforeBlock(int offset) {
            if (offset > 1) {
                try {
                    PositionBounds b = createBounds(offset - 1, offset, true);
                    String s = b.getText();
                    if (s.equals("\n")) { // NOI18N
                        b.setText(""); // NOI18N
                    }
                }
                catch (IOException e) {
                }
                catch (BadLocationException e) {
                }
            }
        }

        /** Opens the editor and set cursor to this guarded section.
        */
        public void openAt() {
            JavaEditor.this.openAt(getBegin());
        }

        /** Set the text contained in this section.
        * Newlines are automatically added to all text segments handled,
        * unless there was already one.
        * All guarded blocks must consist of entire lines.
        * This applies to the contents of specific guard types as well.
        * @param bounds the bounds indicating where the text should be set
        * @param text the new text
        * @param minLen If true the text has to have length more than 2 chars.
        * @return true if the operation was successful, otherwise false
        */
        protected boolean setText(PositionBounds bounds, String text, boolean minLen) {
            if (!valid)
                return false;

            // modify the text - has to end with new line and the length
            // has to be more then 2 characters
            if (minLen) {
                if (text.length() == 0)
                    text = " \n"; // NOI18N
                else if (text.length() == 1)
                    text = text.equals("\n") ? " \n" : text + "\n"; // NOI18N
            }

            if (!text.endsWith("\n")) // NOI18N
                text = text + "\n"; // NOI18N

            try {
                bounds.setText(text);
                return true;
            }
            catch (BadLocationException e) {
            }
            catch (IOException e) {
            }
            return false;
        }

        /** Marks or unmarks the section as guarded.
        * @param doc The styled document where this section placed in.
        * @param bounds The rangeof text which should be marked or unmarked.
        * @param mark true means mark, false unmark.
        */
        void markGuarded(StyledDocument doc, PositionBounds bounds, boolean mark) {
            int begin = bounds.getBegin().getOffset();
            int end = bounds.getEnd().getOffset();
            if (mark)
                NbDocument.markGuarded(doc, begin, end - begin);
            else
                NbDocument.unmarkGuarded(doc, begin, end - begin);
        }

        /** Marks the section as guarded.
        * @param doc The styled document where this section placed in.
        */
        abstract void markGuarded(StyledDocument doc);

        /** Unmarks the section as guarded.
        * @param doc The styled document where this section placed in.
        */
        abstract void unmarkGuarded(StyledDocument doc);

        /** Deletes the text in the section.
        * @exception BadLocationException
        * @exception IOException
        */
        abstract void deleteText() throws BadLocationException, IOException;

        /** Gets the begin of section. To this position is set the cursor
        * when section is open in the editor.
        */
        public abstract PositionRef getBegin();

        /** Gets the text contained in the section.
        * @return The text contained in the section.
        */
        public abstract String getText();

        /** Assures that a position is not inside the guarded section. Complex guarded sections
         * that contain portions of editable text can return true if the tested position is
         * inside one of such portions provided that permitHoles is true.
         * @param pos position in question
         * @param permitHoles if false, guarded section is taken as a monolithic block
         * without any holes in it regadless of its complexity.
 */
        public abstract boolean contains(PositionRef pos,boolean permitHoles);
        /** Returns a position after the whole guarded block that is safe for insertions.
 */
        public abstract PositionRef getPositionAfter();
        /** Returns position before the whole guarded block that is safe for insertions.
 */
        public abstract PositionRef getPositionBefore();
    }

    /** Represents a simple guarded section.
    * It consists of one contiguous block.
    */
    public final class SimpleSection extends GuardedSection {
        /** Text range of the guarded section. */
        PositionBounds bounds;

        /** Creates new section.
        * @param name Name of the new section.
        * @param bounds The range of the section.
        */
        SimpleSection(String name, PositionBounds bounds) {
            super(name);
            this.bounds = bounds;
        }

        /** Set the text of the section.
        * @param text the new text
        * @return true if the operation was successful, otherwise false
        * @see JavaEditor.GuardedSection#setText
        */
        public boolean setText(String text) {
            return setText(bounds, text, true);
        }

        /** Deletes the text in the section.
        * @exception BadLocationException
        * @exception IOException
        */
        void deleteText() throws BadLocationException, IOException {
            bounds.setText(""); // NOI18N
            deleteNewLineBeforeBlock(bounds.getBegin().getOffset());
        }

        /** Marks the section as guarded.
        * @param doc The styled document where this section placed in.
        */
        void markGuarded(StyledDocument doc) {
            markGuarded(doc, bounds, true);
        }

        /** Unmarks the section as guarded.
        * @param doc The styled document where this section placed in.
        */
        void unmarkGuarded(StyledDocument doc) {
            markGuarded(doc, bounds, false);
            JavaEditor.this.notifyModified();
        }

        /** Gets the begin of section. To this position is set the cursor
        * when section is open in the editor.
        * @return the begin position of section.
        */
        public PositionRef getBegin() {
            return bounds.getBegin();
        }

        /** Gets the text contained in the section.
        * @return The text contained in the section.
        */
        public String getText() {
            StringBuffer buf = new StringBuffer();
            try {
                buf.append(bounds.getText());
            }
            catch (Exception e) {
            }
            return buf.toString();
        }

        /*
        public String toString() {
          StringBuffer buf = new StringBuffer("SimpleSection:"+name); // NOI18N
          buf.append("\"");
          try {
            buf.append(bounds.getText());
          }
          catch (Exception e) {
            buf.append("EXCEPTION:"); // NOI18N
            buf.append(e.getMessage());
          }
          buf.append("\"");
          return buf.toString();
    }*/

        public PositionRef getPositionAfter() {
	    return createPositionRef(bounds.getEnd().getOffset(), Position.Bias.Backward);
	}

        public boolean contains(PositionRef pos,boolean allowHoles) {
	    return bounds.getBegin().getOffset() <= pos.getOffset() &&
		bounds.getEnd().getOffset() >= pos.getOffset();
	}

        public PositionRef getPositionBefore() {
            return createPositionRef(bounds.getBegin().getOffset(), Position.Bias.Forward);
	}
    }

    /** Represents an advanced guarded block.
    * It consists of three pieces: a header, body, and footer.
    * The header and footer are guarded but the body is not.
    */
    public final class InteriorSection extends GuardedSection {
        /** Text range of the header. */
        PositionBounds header;

        /** Text range of the header. */
        PositionBounds body;

        /** Text range of the bottom. */
        PositionBounds bottom;

        /** Creates new section.
        * @param name Name of the new section.
        */
        InteriorSection(String name, PositionBounds header, PositionBounds body, PositionBounds bottom) {
            super(name);
            this.header = header;
            this.body = body;
            this.bottom = bottom;
        }

        /** Set the text of the body.
        * @param text the new text
        * @return true if the operation was successful, otherwise false
        * @see JavaEditor.GuardedSection#setText
        */
        public boolean setBody(String text) {
            return setText(body, text, false);
        }

        /** Set the text of the header.
        * @param text the new text
        * @return true if the operation was successful, otherwise false
        * @see JavaEditor.GuardedSection#setText
        */
        public boolean setHeader(String text) {
            return setText(header, text, true);
        }

        /**
         * Returns the contents of the header part of the section. If the
         * section is invalid the method returns null.
         * @return contents of the header or null, if the section is not valid.
         */
        public String getHeader() {
            if (!isValid())
                return null;
            try {
                return header.getText();
            } catch (IOException ex) {
                // should not be never reached.
            } catch (BadLocationException ex) {
                // should not happen.
            }
            return null;
        }

        /** Set the text of the bottom.
        * Note that the bottom of the section must have exactly one line.
        * So, all interior newline characters will be replaced by spaces.
        *
        * @param text the new text
        * @return true if the operation was successful, otherwise false
        * @see JavaEditor.GuardedSection#setText
        */
        public boolean setBottom(String text) {
            boolean endsWithEol = text.endsWith("\n"); // NOI18N
            int firstEol = text.indexOf('\n');
            int lastEol = text.lastIndexOf('\n');

            if ((firstEol != lastEol) || (endsWithEol && (firstEol != -1))) {
                if (endsWithEol) {
                    text = text.substring(0, text.length() - 1);
                }
                text = text.replace('\n', ' ');
            }
            return setText(bottom, text, true);
        }

        /**
         * Returns the contents of the bottom part of the guarded section.
         * The method will return null, if the section is not valid.
         * @return contents of the bottom part, or null if the section is not valid.
         */
        public String getBottom() throws IOException, BadLocationException {
            if (!isValid())
                return null;
            try {
                return bottom.getText();
            } catch (IOException ex) {
                // should not be never reached.
            } catch (BadLocationException ex) {
                // should not happen.
            }
            return null;
        }

        /** Gets the begin of section. To this position is set the cursor
        * when section is open in the editor.
        * @return the begin position of the body section - the place where
        *         is possible to edit.
        */
        public PositionRef getBegin() {
            return body.getBegin();
        }

        /** Deletes the text in the section.
        * @exception BadLocationException
        * @exception IOException
        */
        void deleteText() throws BadLocationException, IOException {
            header.setText(""); // NOI18N
            body.setText(""); // NOI18N
            bottom.setText(""); // NOI18N
            deleteNewLineBeforeBlock(header.getBegin().getOffset());
        }

        /** Marks the section as guarded.
        * @param doc The styled document where this section placed in.
        */
        void markGuarded(StyledDocument doc) {
            markGuarded(doc, header, true);
            markGuarded(doc, bottom, true);
        }

        /** Unmarks the section as guarded.
        * @param doc The styled document where this section placed in.
        */
        void unmarkGuarded(StyledDocument doc) {
            markGuarded(doc, header, false);
            markGuarded(doc, bottom, false);
            JavaEditor.this.notifyModified();
        }

        /** Gets the text contained in the section.
        * @return The text contained in the section.
        */
        public String getText() {
            StringBuffer buf = new StringBuffer();
            try {
                buf.append(header.getText());
                buf.append(body.getText());
                buf.append(bottom.getText());
            }
            catch (Exception e) {
            }
            return buf.toString();
        }

        /*
        public String toString() {
          StringBuffer buf = new StringBuffer("InteriorSection:"+name); // NOI18N
          try {
            buf.append("HEADER:\""); // NOI18N
            buf.append(header.getText());
            buf.append("\"");
            buf.append("BODY:\""); // NOI18N
            buf.append(body.getText());
            buf.append("\"");
            buf.append("BOTTOM:\""); // NOI18N
            buf.append(bottom.getText());
            buf.append("\"");
          }
          catch (Exception e) {
            buf.append("EXCEPTION:"); // NOI18N
            buf.append(e.getMessage());
          }
          return buf.toString();
    }*/
        public boolean contains(PositionRef pos,boolean allowHoles) {
	    if (!allowHoles) {
    		return header.getBegin().getOffset() <= pos.getOffset() &&
		    bottom.getEnd().getOffset() >= pos.getOffset();
	    } else {
		if (header.getBegin().getOffset() <= pos.getOffset() &&
		    header.getEnd().getOffset() >= pos.getOffset()) {
		    return true;
		}
		return bottom.getBegin().getOffset() <= pos.getOffset() &&
		    bottom.getEnd().getOffset() >= pos.getOffset();
	    }
	}

        public PositionRef getPositionBefore() {
	    return createPositionRef(header.getBegin().getOffset(), Position.Bias.Forward);
	}

        public PositionRef getPositionAfter() {
	    return createPositionRef(bottom.getEnd().getOffset(), Position.Bias.Backward);
	}
    }

    // ==================== Private inner classes ===========================

    private class JavaEditorChangeListener implements ChangeListener {
        private PropertyChangeListener classpathListener;
        private PropertyChangeListener settingListener;

        public void stateChanged(ChangeEvent ev) {
            JavaSettings js=JavaSettings.getDefault();

            // create classpath listener
            if (classpathListener == null) {
                classpathListener = new PropertyChangeListener() {
                    public void propertyChange(PropertyChangeEvent evt) {
                        if (ClassPath.PROP_ROOTS.equals(evt.getPropertyName())) {
                            classpathChanged();
                        }
                    }
                };
            }
            if (settingListener==null)
                settingListener=new PropertyChangeListener() {
                                    public void propertyChange(PropertyChangeEvent evt) {
                                        if (JavaSettings.PROP_PARSING_ERRORS.equals(evt.getPropertyName())) {
                                            parsingErrorsChanged(evt);
                                        }
                                        if (JavaSettings.PROP_SHOW_OVERRIDING.equals(evt.getPropertyName())) {
                                            showOverridingChanged (evt);
                                        }
                                    }
                                };
            ClassPath sourceClasspath = getSourcePath();
            ClassPath librariesClasspath = getLibrariesPath();
            ClassPath bootClassPath = getBootClassPath();
            if (sourceClasspath != null) {
                sourceClasspath.removePropertyChangeListener(classpathListener);
            }
            if (librariesClasspath != null) {
                librariesClasspath.removePropertyChangeListener(classpathListener);
            }
            if (bootClassPath != null) {
                bootClassPath.removePropertyChangeListener(classpathListener);
            }
            js.removePropertyChangeListener(settingListener);
            if (isDocumentLoaded()) {
                if (sourceClasspath != null) {
                    sourceClasspath.addPropertyChangeListener(classpathListener);
                }
                if (librariesClasspath != null) {
                    librariesClasspath.addPropertyChangeListener (classpathListener);
                }
                if (bootClassPath != null) {
                    bootClassPath.addPropertyChangeListener(classpathListener);
                }
                js.addPropertyChangeListener(settingListener);
            }
        }

    }
    /** Comparator of the guarded sections. It compares the begin position
    * of the sections.
    */
    private static class GuardedPositionComparator implements Comparator {
        /** Compare two objects. Both have to be either SimpleSection
        * either InteriorSection instance.
        */
        public int compare(Object o1, Object o2) {
            return getOffset(o1) - getOffset(o2);
        }

        /** Computes the offset of the begin of the section. */
        private int getOffset(Object o) {
            if (o instanceof SimpleSection) {
                return ((SimpleSection)o).bounds.getBegin().getOffset();
            }
            else {
                return ((InteriorSection)o).header.getBegin().getOffset();
            }
        }
    }

    /** Class for holding information about the one special (guarded)
    * comment. It is created by GuardedReader and used by
    * JavaEditor to creating the guarded sections.
    */
    private static class SectionDesc {
        /** Type - one of T_XXX constant */
        int type;

        /** Name of the section comment */
        String name;

        /** offset of the begin */
        int begin;

        /** offset of the end */
        int end;

        /** Simple constructor */
        SectionDesc(int type) {
            this.type = type;
            name = null;
            begin = 0;
            end = 0;
        }
    }

    /** This stream is able to filter special guarded comments.
     * Holding this information is optional and depends on the construction
     * of this stream - the reason of this feature is that
     * GuardedReader is used also for parser (and it doesn't require
     * the storing the guarded block information - just filter the comments).
     */
    static class GuardedReader extends Reader {
        /** Encapsulated reader */
        Reader reader;

        /** Character buffer */
        char[] charBuff;
        char[] readBuff;
        int howmany;
        Pattern magicsAsRE;

        /** The flag determining if this stream should store the guarded
         * block information (list of the SectionDesc).
         */
        boolean justFilter;

        /** The position at the current line. */
        int position;

        /** The list of the SectionsDesc. */
        LinkedList list;

        /** The count of types new line delimiters used in the file */
        final int[] newLineTypes;

        /** Creates new stream.
         * @param is encapsulated input stream.
         * @param justFilter The flag determining if this stream should
         *        store the guarded block information. True means just filter,
         *        false means store the information.
         */
        GuardedReader(InputStream is, boolean justFilter) throws IOException {
            this(is, justFilter, null);
        }

        GuardedReader(InputStream is, boolean justFilter, String encoding) throws IOException {
            if (encoding == null)
                reader = new InputStreamReader(is);
            else
                reader = new InputStreamReader(is, encoding);
            this.justFilter = justFilter;
            position = 0;
            list = new LinkedList();
            newLineTypes = new int[] { 0, 0, 0 };
        }

        /** Read the array of chars */
        public int read(char[] cbuf, int off, int len) throws IOException {

            if (charBuff == null) {
                readCharBuff();
                translateToCharBuff();
            }

            if (howmany <= 0) {
                return -1;
            } else {
                int min = Math.min(len, howmany);
                System.arraycopy(charBuff, position, cbuf, off, min);
                howmany -= min;
                position += min;
                return min;
            }
        }

        /** Reads readBuff */
        final void readCharBuff() throws IOException {

            char[] tmp = new char[2048];
            int read;
            ArrayList buffs = new ArrayList(20);

            for (;;) {
                read = readFully(tmp);
                buffs.add(tmp);
                if (read < 2048) {
                    break;
                } else {
                    tmp = new char[2048];
                }
            }

            int listsize = buffs.size() - 1;
            int size = listsize * 2048 + read;
            readBuff = new char[size];
            charBuff = new char[size];
            int copy = 0;

            for (int i = 0; i < listsize; i++) {
                char[] tmp2 = (char[]) buffs.get(i);
                System.arraycopy(tmp2, 0, readBuff, copy, 2048);
                copy += 2048;
            }
            System.arraycopy(tmp, 0, readBuff, copy, read);
        }

        /** reads fully given buffer */
        final int readFully(final char[] buff) throws IOException {
            int read = 0;
            int sum = 0;

            do {
                read = reader.read(buff, sum, buff.length - sum);
                sum += read;
            } while ((sum < buff.length) && (read > 0));

            return sum + 1;
        }

        /** Called after raw filling from an underlying reader */
        final void translateToCharBuff() {
            position = 0;

            // points to first unused cell in charBuff
            int charBuffPtr = 0;
            int stop = readBuff.length - 1;

            // read char
            int c;
            // ptr to first not processed char in readBuff
            int i = 0;
            // points to a character right after a newline
            int lastNewLine = 0;

            // final automata
            int fatpos = 0;
            final int MAGICLEN = MAGIC_PREFIX.length();


            //process newlines so only '\n' appears in the charBuff
            //count all kinds of newlines - most used will be used on save
            while (i < stop) {
                c = readBuff[i];
                switch (c) {
                case (int)'\n':
                    newLineTypes[NEW_LINE_N]++;
                    charBuff[charBuffPtr++] = '\n';
                    lastNewLine = charBuffPtr;
                    i++;
                    break;
                case (int)'\r':
                    int c2 = readBuff[i + 1];
                    if (c2 != (int)'\n') {
                        newLineTypes[NEW_LINE_R]++;
                        i++;
                    } else {
                        i +=2;
                        newLineTypes[NEW_LINE_RN]++;
                    }
                    charBuff[charBuffPtr++] = '\n';
                    lastNewLine = charBuffPtr;
                    break;

                default:
                    charBuff[charBuffPtr++] = readBuff[i++];
                }

                switch (fatpos) {
                case 0:
                    if (c == '/') {
                        fatpos++;
                    } else {
                        fatpos = 0;
                    }
                    break;

                case 1:
                    if (c == '/') {
                        fatpos++;
                    } else {
                        fatpos = 0;
                    }
                    break;

                case 2:
                    if (c == 'G') {
                        fatpos++;
		    } else if (c == '/') {
			fatpos = 2; // what if /////GEN-xxx?
                    } else {
                        fatpos = 0;
                    }
                    break;

                case 3:
                    if (c == 'E') {
                        fatpos++;
                    } else {
                        fatpos = 0;
                    }
                    break;

                case 4:
                    if (c == 'N') {
                        fatpos++;
                    } else {
                        fatpos = 0;
                    }
                    break;

                case 5:
                    if (c == '-') {
                        fatpos++;
                    } else {
                        fatpos = 0;
                    }
                    break;

                default:
                    fatpos = 0;
                }

                // "//GEN-" was reached at this time
                if(fatpos == MAGICLEN) {
                    fatpos = 0;
                    Pattern magics = getMagicsAsRE();
                    int searchLen = Math.min(LONGEST_ITEM, readBuff.length - i);
                    CharBuffer chi = CharBuffer.wrap(readBuff, i, searchLen);
                    Matcher matcher = magics.matcher(chi);
                    if (matcher.find()) {
                        String match = matcher.group();

                        charBuffPtr -= MAGICLEN;
                        i += match.length();
                        int toNl = toNewLine(i);
                        int sectionSize=MAGICLEN+match.length()+toNl;
                        
                        if (!justFilter) {
                            int type = string2Type(match);
                            SectionDesc desc = new SectionDesc(type);
                            desc.begin = lastNewLine;
                            desc.end = charBuffPtr + sectionSize + 1;
                            desc.name = new String(readBuff, i, toNl);
                            list.add(desc);
                        }
                        i += toNl;
                        Arrays.fill(charBuff,charBuffPtr,charBuffPtr+sectionSize,' ');
                        charBuffPtr+=sectionSize;
                    }
                }
            }

            if (i == stop) {
                c = readBuff[i];
                switch (c) {
                case (int)'\n':
                    newLineTypes[NEW_LINE_N]++;
                    charBuff[charBuffPtr++] = '\n';
                    break;
                case (int)'\r':
                    newLineTypes[NEW_LINE_R]++;
                    charBuff[charBuffPtr++] = '\n';
                    break;

                default:
                    charBuff[charBuffPtr++] = readBuff[i++];
                }
            }

            // repair last SectionDesc
            if (!justFilter && (list.size() > 0)) {
                SectionDesc desc = (SectionDesc) list.getLast();
                if (desc.end > charBuffPtr) {
                    desc.end = charBuffPtr;
                }
            }

            howmany = charBuffPtr;
            readBuff = null;
        }

        /** Translates a String (//GEN-XXX) to its number */
        static int string2Type(String match) {
            StringBuffer sb = new StringBuffer(MAGIC_PREFIX);
            sb.append(match);
            match = sb.toString();

            final int len = SECTION_MAGICS.length;

            for (int i = 0; i < len; i++) {
                if (match.equals(SECTION_MAGICS[i])) {
                    return i;
                }
            }
            return -1;
        }

        /** Searches for newline from i */
        final int toNewLine(int i) {
            int c;
            int counter = i;
            final int len = readBuff.length;
            while (counter < len) {
                c = readBuff[counter++];
                if (c == '\r' || c == '\n') {
                    counter--;
                    break;
                }
            }

            return counter - i;
        }

        /** @return searching engine for magics */
        final Pattern getMagicsAsRE() {
            if (magicsAsRE == null) {
                magicsAsRE = Pattern.compile(makeOrRegexp());
            }
            return magicsAsRE;
        }

        /** Makes or regular expression for magics */
        final String makeOrRegexp() {
            StringBuffer sb = new StringBuffer(100);
            final int len = MAGIC_PREFIX.length();
            final int slen = SECTION_MAGICS.length - 1;
            for (int i = 0; i < slen; i++) {
                sb.append(SECTION_MAGICS[i].substring(len));
                sb.append('|');
            }
            sb.append(SECTION_MAGICS[slen].substring(len));
            return sb.toString();
        }

        /** @return most frequently type of new line delimiter */
        byte getNewLineType() {
            // special case: an empty file (all newline types equal)
            if (newLineTypes[0] == newLineTypes[1] &&
                newLineTypes[1] == newLineTypes[2]) {

                String s = System.getProperty("line.separator");
                if ("\r".equals(s)) // NOI18N
                    return NEW_LINE_R;
                else if ("\r\n".equals(s)) // NOI18N
                    return NEW_LINE_RN;
                else
                    return NEW_LINE_N;
            }
            if (newLineTypes[0] > newLineTypes[1]) {
                return (newLineTypes[0] > newLineTypes[2]) ? (byte) 0 : 2;
            }
            else {
                return (newLineTypes[1] > newLineTypes[2]) ? (byte) 1 : 2;
            }
        }

        /** Close underlayed writer. */
        public void close() throws IOException {
            reader.close();
        }
    }

    /** This stream is able to insert special guarded comments.
    */
    static class GuardedWriter extends Writer {
        /** Encapsulated writer. */
        BufferedWriter writer;

        /** From this iterator is possible to obtain all section
        * descriptions during writing the document.
        */
        Iterator sections;

        /** Current section from the previous iterator. For filling this
        * field is used method nextSection.
        */
        SectionDesc current;

        /** Current offset in the original document (NOT in the encapsulated
        * output stream.
        */
        int offsetCounter;

        /** This flag is used during writing. It is complicated to explain. */
        boolean wasNewLine;

        /** number of consecutive spaces */
        int spaces;
        
        /** Creates new GuardedWriter.
        * @param os Encapsulated output stream.
        * @param list The list of the guarded sections.
        */
        GuardedWriter(OutputStream os, ArrayList list, String encoding) throws IOException {
            if (encoding == null)
                writer = new BufferedWriter(new OutputStreamWriter(os));
            else
                writer = new BufferedWriter(new OutputStreamWriter(os, encoding));
            offsetCounter = 0;
            sections = prepareSections(list);
            nextSection();
            wasNewLine = false;
        }

        /** Writes chars to underlayed writer */
        public void write(char[] cbuf, int off, int len) throws IOException {
            for (int i = 0; i < len; i++) {
                writeOneChar(cbuf[i + off]);
            }
        }

        /** Calls underlayed writer flush */
        public void close() throws IOException {
            writer.flush();
        }

        /** Calls underlayed writer flush */
        public void flush() throws IOException {
            writer.flush();
        }

        /** This method prepares the iterator of the SectionDesc classes
        * @param list The list of the GuardedSection classes.
        * @return iterator of the SectionDesc
        */
        private Iterator prepareSections(ArrayList list) {
            LinkedList dest = new LinkedList();
            Collections.sort(list, new GuardedPositionComparator());

            Iterator it = list.iterator();
            while (it.hasNext()) {
                GuardedSection o = (GuardedSection) it.next();
                if (o instanceof SimpleSection) {
                    SectionDesc desc = new SectionDesc(T_LINE);
                    desc.name = o.name;
                    desc.begin = ((SimpleSection)o).bounds.getBegin().getOffset();
                    desc.end = ((SimpleSection)o).bounds.getEnd().getOffset();
                    dest.add(desc);
                }
                else {
                    SectionDesc desc = new SectionDesc(T_HEADER);
                    desc.begin = (((InteriorSection)o).header).getBegin().getOffset();
                    desc.end = (((InteriorSection)o).header).getEnd().getOffset();
                    desc.name = o.name;
                    dest.add(desc);

                    desc = new SectionDesc(T_END);
                    desc.begin = (((InteriorSection)o).bottom).getBegin().getOffset();
                    desc.end = (((InteriorSection)o).bottom).getEnd().getOffset();
                    desc.name = o.name;
                    dest.add(desc);
                }
            }
            return dest.iterator();
        }

        /** Write one character. If there is a suitable place,
        * some special comments are written to the underlaying stream.
        * @param b char to write.
        */
        void writeOneChar(int b) throws IOException {
            if (b == '\r')
                return;

            if (current != null) {
                if (offsetCounter == current.begin) {
                    wasNewLine = false;
                }
                if ((b == '\n') && (current.begin <= offsetCounter)) {
                    switch (current.type) {
                    case T_LINE:
                        if (!wasNewLine) {
                            if (offsetCounter + 1 >= current.end) {
                                writeMagic(T_LINE, current.name);
                                nextSection();
                            }
                            else {
                                writeMagic(T_BEGIN, current.name);
                                wasNewLine = true;
                            }
                        }
                        else {
                            if (offsetCounter + 1 >= current.end) {
                                writeMagic(T_END, current.name);
                                nextSection();
                            }
                        }
                        break;

                    case T_HEADER:
                        if (!wasNewLine) {
                            if (offsetCounter + 1 >= current.end) {
                                writeMagic(T_FIRST, current.name);
                                nextSection();
                            }
                            else {
                                writeMagic(T_FIRST, current.name);
                                wasNewLine = true;
                            }
                        }
                        else {
                            if (offsetCounter + 1 >= current.end) {
                                writeMagic(T_HEADEREND, current.name);
                                nextSection();
                            }
                        }
                        break;

                    case T_END:
                        writeMagic(T_LAST, current.name);
                        nextSection();
                        break;
                    }
                }
            }
            if (b==' ')
                spaces++;
            else {
                char[] sp=new char[spaces];
                
                Arrays.fill(sp,' ');
                writer.write(sp);
                writer.write(b);
                spaces=0;
            }
            offsetCounter++;
        }

        /** Try to get next sectionDesc from the 'sections'
        * If there is no more section the 'current' will be set to null.
        */
        private void nextSection() {
            current = (SectionDesc) (sections.hasNext() ? sections.next() : null);
        }

        /** Writes the magic to the underlaying stream.
        * @param type The type of the magic section - T_XXX constant.
        * @param name name of the section.
        */
        private void writeMagic(int type, String name) throws IOException {
            spaces=0;
            writer.write(SECTION_MAGICS[type], 0, SECTION_MAGICS[type].length());
            writer.write(name, 0, name.length());
        }
    }

    /** This stream is used for changing the new line delimiters.
    * It replaces the '\n' by '\n', '\r' or "\r\n"
    */
    private static class NewLineOutputStream extends OutputStream {
        /** Underlaying stream. */
        OutputStream stream;

        /** The type of new line delimiter */
        byte newLineType;

        /** Creates new stream.
        * @param stream Underlaying stream
        * @param newLineType The type of new line delimiter
        */
        public NewLineOutputStream(OutputStream stream, byte newLineType) {
            this.stream = stream;
            this.newLineType = newLineType;
        }

        /** Write one character.
        * @param b char to write.
        */
        public void write(int b) throws IOException {
            if (b == '\n') {
                switch (newLineType) {
                case NEW_LINE_R:
                    stream.write('\r');
                    break;
                case NEW_LINE_RN:
                    stream.write('\r');
                case NEW_LINE_N:
                    stream.write('\n');
                    break;
                }
            }
            else {
                stream.write(b);
            }
        }
    }

    static class WParsingListener extends java.lang.ref.WeakReference implements ParsingListener, Runnable {
        WParsingListener(ParsingListener orig) {
            super(orig, org.openide.util.Utilities.activeReferenceQueue());
        }

        public void run() {
            JavaMetamodel.removeParsingListener(this);
        }

        ParsingListener getListener() {
            Object o = get();
            if (o == null) {
                JavaMetamodel.removeParsingListener(this);
            }
            return (ParsingListener) o;
        }

        public void resourceParsed(Resource rsc) {
            ParsingListener l = getListener();
            if (l != null)
                l.resourceParsed(rsc);
        }
    }

    private static class UndoManagerListener implements PropertyChangeListener {
        
        private WeakReference ref;
        
        UndoManagerListener(JavaEditor editor) {
            ref = new WeakReference(editor);
        }
        
        public void propertyChange(PropertyChangeEvent evt) {
            UndoManager undo = JavaMetamodel.getUndoManager();
            JavaEditor editor = (JavaEditor) ref.get();            
            if (editor == null) {
                undo.removePropertyChangeListener(this);
                return;
            }
            if (undo.isUndoAvailable() || undo.isRedoAvailable()) {
                editor.getUndoRedo().discardAllEdits();
            }
        }

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