? loaders/src/org/openide/text/TestProvider.java ? test/unit/src/META-INF/services/org.openide.text.AnnotationProvider Index: openide-spec-vers.properties =================================================================== RCS file: /cvs/openide/openide-spec-vers.properties,v retrieving revision 1.137 diff -u -r1.137 openide-spec-vers.properties --- openide-spec-vers.properties 13 Apr 2004 13:34:10 -0000 1.137 +++ openide-spec-vers.properties 29 Apr 2004 15:15:02 -0000 @@ -4,4 +4,4 @@ # Must always be numeric (numbers separated by '.', e.g. 4.11). # See http://openide.netbeans.org/versioning-policy.html for more. -openide.specification.version=4.29 +openide.specification.version=4.30 Index: api/doc/changes/apichanges.xml =================================================================== RCS file: /cvs/openide/api/doc/changes/apichanges.xml,v retrieving revision 1.192 diff -u -r1.192 apichanges.xml --- api/doc/changes/apichanges.xml 13 Apr 2004 13:34:11 -0000 1.192 +++ api/doc/changes/apichanges.xml 29 Apr 2004 15:15:04 -0000 @@ -116,6 +116,25 @@ + + Support for AnnotationProvider interface + + + + + +
    +
  • Added interface org.openide.text.AnnotationProvider
  • +
  • Call all instances of the interface found in the global lookup + when going to visualize given Annotatable. +
  • +
+
+ + +
+ + There should exist just one FileObject for one resource (java.io.File or URL). Index: src/org/openide/text/AnnotationProvider.java =================================================================== RCS file: src/org/openide/text/AnnotationProvider.java diff -N src/org/openide/text/AnnotationProvider.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/openide/text/AnnotationProvider.java 29 Apr 2004 15:15:04 -0000 @@ -0,0 +1,39 @@ +/* + * 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.openide.text; + +import org.openide.util.Lookup; + +/** + * A provider of annotations for given context. + * + * Implementations of this interface are looked up in the global lookup + * and called to let them attach annotations to the lines in the set. + * The call is performed during opening of given context. + * + * @author Petr Nejedly + * @since 4.30 + */ +public interface AnnotationProvider { + + /** + * Attach annotations to the Line.Set for given context. + * + * @param set the Line.Set to attach annotations to. + * @param context a Lookup describing the context for the Line.Set. + * it shall contain the FileObject the LineSet is associated with. + */ + public void annotate (Line.Set set, Lookup context); + +} Index: src/org/openide/text/CloneableEditor.java =================================================================== RCS file: /cvs/openide/src/org/openide/text/CloneableEditor.java,v retrieving revision 1.69 diff -u -r1.69 CloneableEditor.java --- src/org/openide/text/CloneableEditor.java 13 Feb 2004 10:20:29 -0000 1.69 +++ src/org/openide/text/CloneableEditor.java 29 Apr 2004 15:15:04 -0000 @@ -212,6 +212,8 @@ caret.setDot(cursorPosition); } } + + support.ensureAnnotationsLoaded(); } protected CloneableTopComponent createClonedObject() { Index: src/org/openide/text/CloneableEditorSupport.java =================================================================== RCS file: /cvs/openide/src/org/openide/text/CloneableEditorSupport.java,v retrieving revision 1.120 diff -u -r1.120 CloneableEditorSupport.java --- src/org/openide/text/CloneableEditorSupport.java 15 Mar 2004 16:11:25 -0000 1.120 +++ src/org/openide/text/CloneableEditorSupport.java 29 Apr 2004 15:15:05 -0000 @@ -286,6 +286,20 @@ return positionManager; } + + private boolean annotationsLoaded; + + void ensureAnnotationsLoaded() { + if (!annotationsLoaded) { + annotationsLoaded = true; + Line.Set lines = getLineSet(); + Lookup.Result result = Lookup.getDefault().lookup(new Lookup.Template(AnnotationProvider.class)); + for (Iterator it = result.allInstances().iterator(); it.hasNext(); ) { + AnnotationProvider act = (AnnotationProvider)it.next(); + act.annotate(lines, lookup); + } + } + } /** Overrides superclass method, first processes document preparation. * @see #prepareDocument */ Index: loaders/src/org/openide/text/DataEditorSupport.java =================================================================== RCS file: /cvs/openide/loaders/src/org/openide/text/DataEditorSupport.java,v retrieving revision 1.14 diff -u -r1.14 DataEditorSupport.java --- loaders/src/org/openide/text/DataEditorSupport.java 10 Mar 2004 13:48:36 -0000 1.14 +++ loaders/src/org/openide/text/DataEditorSupport.java 29 Apr 2004 15:15:05 -0000 @@ -33,6 +33,8 @@ import org.openide.util.Mutex; import org.openide.windows.*; import org.openide.util.NbBundle; +import org.openide.util.Lookup; +import org.openide.util.lookup.*; /** Support for associating an editor and a Swing {@link Document} to a data object. * @@ -51,7 +53,7 @@ * @param env environment to pass to */ public DataEditorSupport (DataObject obj, CloneableEditorSupport.Env env) { - super (env, org.openide.util.lookup.Lookups.singleton (obj)); + super (env, createLookup(obj)); this.obj = obj; } @@ -588,5 +590,28 @@ } } // end of DataNodeListener + + /* Create a special lookup implementation that contains a DataObject and its + * primary fileobject. If the file is moved, the FileObject is replaced, + * while the DataObject keeps the identity. + */ + private static Lookup createLookup(final DataObject dobj) { + final InstanceContent ic = new InstanceContent(); + Lookup l = new AbstractLookup(ic); + dobj.addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent ev) { + String propName = ev.getPropertyName(); + if (propName == null || propName == DataObject.PROP_PRIMARY_FILE) { + updateLookup(dobj, ic); + } + } + }); + updateLookup(dobj,ic); + return l; + } + + private static void updateLookup(DataObject d, InstanceContent ic) { + ic.set(Arrays.asList(new Object[] { d, d.getPrimaryFile() }), null); + } } Index: test/unit/src/org/openide/text/AnnotationProviderTest.java =================================================================== RCS file: test/unit/src/org/openide/text/AnnotationProviderTest.java diff -N test/unit/src/org/openide/text/AnnotationProviderTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ test/unit/src/org/openide/text/AnnotationProviderTest.java 29 Apr 2004 15:15:06 -0000 @@ -0,0 +1,453 @@ +/* + * 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-2001 Sun + * Microsystems, Inc. All Rights Reserved. + */ + + +package org.openide.text; + + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Collections; +import javax.swing.SwingUtilities; +import javax.swing.text.BadLocationException; +import javax.swing.text.Position; +import javax.swing.text.StyledDocument; + +import junit.textui.TestRunner; + +import org.netbeans.junit.NbTestCase; +import org.netbeans.junit.NbTestSuite; +import org.openide.actions.*; +import org.openide.cookies.CloseCookie; +import org.openide.cookies.EditCookie; + +import org.openide.cookies.EditorCookie; +import org.openide.cookies.OpenCookie; +import org.openide.cookies.PrintCookie; +import org.openide.cookies.SaveCookie; +import org.openide.filesystems.FileLock; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileStateInvalidException; +import org.openide.filesystems.FileSystem; +import org.openide.filesystems.Repository; +import org.openide.filesystems.TestUtilHid; +import org.openide.loaders.DataNode; +import org.openide.loaders.DataObject; +import org.openide.loaders.DataObjectExistsException; +import org.openide.loaders.ExtensionList; +import org.openide.loaders.MultiDataObject; +import org.openide.loaders.MultiFileLoader; +import org.openide.loaders.UniFileLoader; +import org.openide.nodes.Children; +import org.openide.nodes.CookieSet; +import org.openide.nodes.Node; +import org.openide.text.CloneableEditorSupport; +import org.openide.util.HelpCtx; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.actions.SystemAction; +import org.openide.windows.CloneableOpenSupport; +import org.openide.windows.WindowManager; + + +/** + */ +public class AnnotationProviderTest extends NbTestCase { + + public AnnotationProviderTest(String s) { + super(s); + } + + public static void main(String[] args) { + TestRunner.run(new NbTestSuite(AnnotationProviderTest.class)); + } + + private FileSystem fs; + + protected void setUp() throws Exception { + System.setProperty("org.openide.util.Lookup", "org.openide.text.AnnotationProviderTest$Lkp"); + + TestUtilHid.destroyLocalFileSystem (getName()); + fs = TestUtilHid.createLocalFileSystem (getName(), new String[] {}); + } + + protected void tearDown() throws Exception { + } + + public void testLookupIsConsistent() throws Exception { + Object o = Lookup.getDefault().lookup(AnnotationProvider.class); + if(o == null) { + fail("No annotation provider found"); + } + + FileObject fo = fs.getRoot().createData("test", "txt"); + + DataObject data = DataObject.find(fo); + + EditorCookie ec = (EditorCookie)data.getCookie(EditorCookie.class); + + if(!(ec instanceof CloneableEditorSupport)) { + fail("Bad editor cookie type"); + } + + CloneableEditorSupport ces = (CloneableEditorSupport)ec; + + ConsistencyCheckProvider.called = 0; + ces.open(); + + assertEquals("Provider called exactly once", 1, ConsistencyCheckProvider.called); + assertEquals("Consistent lookup content", data.getPrimaryFile(), ConsistencyCheckProvider.inLkp); + + assertEquals("Exactly one annotation attached", 1, ces.getLineSet().getCurrent(0).getAnnotationCount()); + + ces.close(); + assertEquals("Exactly one annotation attached after close", 1, ces.getLineSet().getCurrent(0).getAnnotationCount()); + + ConsistencyCheckProvider.called = 0; + ces.open(); + assertEquals("Provider called exactly once during reopen", 1, ConsistencyCheckProvider.called); + assertEquals("Exactly one annotation attached after reopen", 1, ces.getLineSet().getCurrent(0).getAnnotationCount()); + + } + public static class ConsistencyCheckProvider implements AnnotationProvider { + + private static int called; + private static FileObject inLkp; + + public void annotate(org.openide.text.Line.Set set, org.openide.util.Lookup context) { + inLkp= (FileObject)context.lookup(FileObject.class); + called++; + + set.getCurrent(0).addAnnotation(new MyAnnotation()); + } + + } + + + // below is only irrelevant support stuff + + private static class MyAnnotation extends Annotation { + + public String getAnnotationType() { + return "nowhere"; + } + + public String getShortDescription() { + return "Test annotation"; + } + + } + + protected boolean runInEQ() { + return true; + } + + // + // Code from text module + // + + + public static final class TXTDataLoader extends UniFileLoader { + + /** Generated serial version UID. */ + static final long serialVersionUID =-3658061894653334886L; + + /** file attribute which forces a file to be considered a text file */ + static final String ATTR_IS_TEXT_FILE = "org.netbeans.modules.text.IsTextFile"; // NOI18N + + + /** Creates new TXTDataLoader. */ + public TXTDataLoader() { + super("org.netbeans.modules.text.TXTDataObject"); // NOI18N + } + + /** Does initialization. Initializes extension list. */ + protected void initialize () { + super.initialize(); + + ExtensionList ext = new ExtensionList(); + ext.addExtension("txt"); // NOI18N + ext.addExtension("doc"); // NOI18N + ext.addExtension("me"); // for read.me files // NOI18N + ext.addExtension("policy"); // NOI18N + ext.addExtension("mf"); // for manifest.mf files // NOI18N + ext.addExtension("MF"); // -""- // NOI18N + ext.addExtension("log"); // log files are nice to be readable // NOI18N + setExtensions(ext); + } + + /** Gets default display name. Overrides superclass method. */ + protected String defaultDisplayName() { + return NbBundle.getBundle(TXTDataLoader.class).getString("PROP_TXTLoader_Name"); + } + + /** Gets default system actions. Overrides superclass method. */ + protected SystemAction[] defaultActions() { + return new SystemAction[] { + SystemAction.get(OpenAction.class), + SystemAction.get (FileSystemAction.class), + null, + SystemAction.get(CutAction.class), + SystemAction.get(CopyAction.class), + SystemAction.get(PasteAction.class), + null, + SystemAction.get(DeleteAction.class), + SystemAction.get(RenameAction.class), + null, + SystemAction.get(SaveAsTemplateAction.class), + null, + SystemAction.get(ToolsAction.class), + SystemAction.get(PropertiesAction.class), + }; + } + + /** Check whether a file is recognized. + * It will be if the extension matches, or if it is marked to be a text file. */ + protected FileObject findPrimaryFile (FileObject fo) { + boolean isSysFile; + try { + isSysFile = fo.getFileSystem () == Repository.getDefault ().getDefaultFileSystem (); + } catch (FileStateInvalidException fsie) { + // Never mind. + isSysFile = false; + } + if (! isSysFile && Boolean.TRUE.equals (fo.getAttribute (ATTR_IS_TEXT_FILE))) + return fo; + return super.findPrimaryFile (fo); + } + + /** Creates new TXTDataObject for specified FileObject. + * @param fo FileObject + * @return new TXTDataObject + */ + protected MultiDataObject createMultiObject(final FileObject fo) + throws IOException { + return new TXTDataObject(fo, this); + } + + } // end of TXTDataLoader + + + public static final class TXTDataObject extends MultiDataObject implements CookieSet.Factory { + + /** Generated Serialized Version UID */ + static final long serialVersionUID = 4795737295255253334L; + + /** Editor support for text data object. */ + private transient TXTEditorSupport editorSupport; + + + /** Constructor. */ + public TXTDataObject(final FileObject obj, final MultiFileLoader loader) throws DataObjectExistsException { + super(obj, loader); + + getCookieSet().add(TXTEditorSupport.class, this); + } + + + /** Implements CookieSet.Factory interface. */ + public Node.Cookie createCookie(Class clazz) { + if(clazz.isAssignableFrom(TXTEditorSupport.class)) + return getEditorSupport(); + else + return null; + } + + // Accessibility from TXTEditorSupport: + CookieSet getCookieSet0() { + return getCookieSet(); + } + + /** Gets editor support for this data object. */ + private TXTEditorSupport getEditorSupport() { + if(editorSupport == null) { + synchronized(this) { + if(editorSupport == null) + editorSupport = new TXTEditorSupport(this); + } + } + + return editorSupport; + } + + /** Provides node that should represent this data object. When a node for representation + * in a parent is requested by a call to getNode (parent) it is the exact copy of this node + * with only parent changed. This implementation creates instance DataNode. + *

+ * This method is called only once. + * + * @return the node representation for this data object + * @see DataNode + */ + protected Node createNodeDelegate () { + return new TXTNode(this); + } + + /** Help context for this object. + * @return help context + */ + public HelpCtx getHelpCtx () { + return new HelpCtx (TXTDataObject.class); + } + + + /** Text node implementation. + * Leaf node, default action opens editor or instantiates template. + * Icons redefined. + */ + public static final class TXTNode extends DataNode { + /** Icon base for the TXTNode node */ + private static final String TXT_ICON_BASE = "org/netbeans/modules/text/txtObject"; // NOI18N + + /** Constructs node. */ + public TXTNode (final DataObject dataObject) { + super(dataObject, Children.LEAF); + setIconBase(TXT_ICON_BASE); + } + + /** Overrides default action from DataNode. */ + public SystemAction getDefaultAction () { + SystemAction result = super.getDefaultAction(); + return result == null ? SystemAction.get(OpenAction.class) : result; + } + } // End of nested class TXTNode. + + } // TXTDataObject + + + public static final class TXTEditorSupport extends DataEditorSupport + implements OpenCookie, EditCookie, EditorCookie.Observable, PrintCookie, CloseCookie { + + /** SaveCookie for this support instance. The cookie is adding/removing + * data object's cookie set depending on if modification flag was set/unset. */ + private final SaveCookie saveCookie = new SaveCookie() { + /** Implements SaveCookie interface. */ + public void save() throws IOException { + TXTEditorSupport.this.saveDocument(); + TXTEditorSupport.this.getDataObject().setModified(false); + } + }; + + + /** Constructor. */ + TXTEditorSupport(TXTDataObject obj) { + super(obj, new Environment(obj)); + + setMIMEType("text/plain"); // NOI18N + } + + /** + * Overrides superclass method. Adds adding of save cookie if the document has been marked modified. + * @return true if the environment accepted being marked as modified + * or false if it has refused and the document should remain unmodified + */ + protected boolean notifyModified () { + if (!super.notifyModified()) + return false; + + addSaveCookie(); + + return true; + } + + /** Overrides superclass method. Adds removing of save cookie. */ + protected void notifyUnmodified () { + super.notifyUnmodified(); + + removeSaveCookie(); + } + + /** Helper method. Adds save cookie to the data object. */ + private void addSaveCookie() { + TXTDataObject obj = (TXTDataObject)getDataObject(); + + // Adds save cookie to the data object. + if(obj.getCookie(SaveCookie.class) == null) { + obj.getCookieSet0().add(saveCookie); + obj.setModified(true); + } + } + + /** Helper method. Removes save cookie from the data object. */ + private void removeSaveCookie() { + TXTDataObject obj = (TXTDataObject)getDataObject(); + + // Remove save cookie from the data object. + Node.Cookie cookie = obj.getCookie(SaveCookie.class); + + if(cookie != null && cookie.equals(saveCookie)) { + obj.getCookieSet0().remove(saveCookie); + obj.setModified(false); + } + } + + + /** Nested class. Environment for this support. Extends + * DataEditorSupport.Env abstract class. + */ + + private static class Environment extends DataEditorSupport.Env + { + private static final long serialVersionUID = 3499855082262173256L; + + /** Constructor. */ + public Environment(TXTDataObject obj) { + super(obj); + } + + + /** Implements abstract superclass method. */ + protected FileObject getFile() { + return getDataObject().getPrimaryFile(); + } + + /** Implements abstract superclass method.*/ + protected FileLock takeLock() throws IOException { + return ((TXTDataObject)getDataObject()).getPrimaryEntry().takeLock(); + } + + /** + * Overrides superclass method. + * @return text editor support (instance of enclosing class) + */ + public CloneableOpenSupport findCloneableOpenSupport() { + return (TXTEditorSupport)getDataObject().getCookie(TXTEditorSupport.class); + } + } // End of nested Environment class. + + } // TXTEditorSupport + + + private static class MyPool extends org.openide.loaders.DataLoaderPool { + protected java.util.Enumeration loaders() { + return Collections.enumeration(Collections.singleton( + TXTDataLoader.getLoader(TXTDataLoader.class) + )); + } + + } + + public static class Lkp extends org.openide.util.lookup.AbstractLookup { + public Lkp () { + this (new org.openide.util.lookup.InstanceContent ()); + } + + private Lkp (org.openide.util.lookup.InstanceContent ic) { + super (ic); + + ic.add (new MyPool ()); + ic.add (new ConsistencyCheckProvider ()); + } + } +}