diff --git a/core.startup/src/org/netbeans/core/startup/layers/BinaryFS.java b/core.startup/src/org/netbeans/core/startup/layers/BinaryFS.java --- a/core.startup/src/org/netbeans/core/startup/layers/BinaryFS.java +++ b/core.startup/src/org/netbeans/core/startup/layers/BinaryFS.java @@ -367,6 +367,11 @@ public Object getAttribute(String attrName) { initialize(); AttrImpl attr = attrs.get(attrName); + if (attr == null && attrName.startsWith("class:")) { + attr = attrs.get(attrName.substring(6)); + return attr == null ? null : attr.getType(this); + } + return (attr != null) ? attr.getValue(this, attrName) : null; } @@ -482,7 +487,7 @@ case 9: // urlvalue return new URL(value); case 10: // methodvalue - return methodValue (value,foProvider,attrName); + return methodValue(value, foProvider, attrName).invoke(); case 11: // newvalue Class cls = findClass (value); // special support for singletons @@ -507,13 +512,70 @@ return null; // problem getting the value... } - /** Constructs new attribute as Object. Used for dynamic creation: methodvalue. */ - private Object methodValue(String method, BFSBase foProvider, String attr) throws Exception { + public Class getType( BFSBase foProvider) { + try { + switch(index) { + case 0: return Byte.class; + case 1: return Short.class; + case 2: return Integer.class; + case 3: return Long.class; + case 4: return Float.class; + case 5: return Double.class; + case 6: return Boolean.class; + case 7: return Character.class; + case 8: return String.class; + case 9: return URL.class; + case 10: // methodvalue + return methodValue(value, foProvider, null).getMethod().getReturnType(); + case 11: // newvalue + return findClass (value); + case 12: // serialvalue + return null; + case 13: // bundle value + return String.class; + default: + throw new IllegalStateException("Bad index: " + index); // NOI18N + } + } catch (Exception exc) { + Exceptions.attachMessage(exc, "value = " + value + " from " + foProvider.getPath()); //NOI18N + Logger.getLogger(BinaryFS.class.getName()).log(Level.WARNING, null, exc); + } + return null; // problem getting the value... + } + + /** Used to store Method and its parameters. */ + private static class MethodAndParams { + private Method method; + private Object[] params; + + MethodAndParams(Method method, Object[] params) { + this.method = method; + this.params = params; + } + + public Object invoke() throws Exception { + method.setAccessible(true); //otherwise cannot invoke private + return method.invoke(null, params); + } + + public Method getMethod() { + return method; + } + + public Object[] getParams() { + return params; + } + } + + /** Constructs new attribute as Object. Used for dynamic creation: methodvalue. + * @return MethodAndParams object or throws InstantiationException if method is not found + */ + private MethodAndParams methodValue(String method, BFSBase foProvider, String attr) throws Exception { int i = method.lastIndexOf('.'); if (i != -1) { // Cf. XMLMapAttr.Attr.methodValue: - Class cls = findClass(value.substring(0, i)); - String methodName = value.substring(i + 1); + Class cls = findClass(method.substring(0, i)); + String methodName = method.substring(i + 1); Class[][] paramArray = { {FileObject.class, String.class}, {String.class, FileObject.class}, {FileObject.class}, {String.class}, {}, @@ -537,12 +599,11 @@ values[j] = wrapToMap(foProvider.getFileObjectForAttr()); } } - m.setAccessible(true); //otherwise cannot invoke private - return m.invoke(null, values); + return new MethodAndParams(m, values); } } // Some message to logFile - throw new InstantiationException (value); + throw new InstantiationException(method); } private Object decodeValue(String value) throws Exception { diff --git a/openide.filesystems/apichanges.xml b/openide.filesystems/apichanges.xml --- a/openide.filesystems/apichanges.xml +++ b/openide.filesystems/apichanges.xml @@ -46,6 +46,30 @@ Filesystems API + + + XMLFileSystem attributes can be queried for instance class + + + + + +

+ If you are interested just in the class of an attribute, but + without creating its instance, use fileObject.getAttribute("class:attrName"). + This instructs the XMLFileSystem + to scan its XML files for definition of attrName + attribute and guess its class. The guessing is + usually easy, just for methodvalue types, the system + needs to use some kind of heuristic: it locates the + appropriate factory method and returns its return type. This may + not be the actual type of the returned object at the end, but + it seems as the best guess without instantiating it. +

+
+ + +
Declarative MIME resolvers now available in standalone mode diff --git a/openide.filesystems/nbproject/project.properties b/openide.filesystems/nbproject/project.properties --- a/openide.filesystems/nbproject/project.properties +++ b/openide.filesystems/nbproject/project.properties @@ -43,4 +43,4 @@ javadoc.main.page=org/openide/filesystems/doc-files/api.html javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml -spec.version.base=7.11.0 +spec.version.base=7.12.0 diff --git a/openide.filesystems/src/org/openide/filesystems/XMLFileSystem.java b/openide.filesystems/src/org/openide/filesystems/XMLFileSystem.java --- a/openide.filesystems/src/org/openide/filesystems/XMLFileSystem.java +++ b/openide.filesystems/src/org/openide/filesystems/XMLFileSystem.java @@ -150,7 +150,16 @@ * * where Value can be any java type. * - * + *

+ * If you are interested just in the class of an attribute, but + * without creating its instance, use fileObject.getAttribute("class:attrName"). + * This instructs the XMLFileSystem to scan its XML files for definition of attrName + * attribute and guess its class. The guessing is usually easy, + * just for methodvalue types, the system needs to use + * some kind of heuristic: it locates the appropriate factory method and returns + * its return type. This may not be the actual type of the returned object at the end, + * but it seems as the best guess without instantiating it. + * @author Radek Matous */ public final class XMLFileSystem extends AbstractFileSystem { diff --git a/openide.filesystems/src/org/openide/filesystems/XMLMapAttr.java b/openide.filesystems/src/org/openide/filesystems/XMLMapAttr.java --- a/openide.filesystems/src/org/openide/filesystems/XMLMapAttr.java +++ b/openide.filesystems/src/org/openide/filesystems/XMLMapAttr.java @@ -190,12 +190,16 @@ } Object retVal = null; - - try { - retVal = (attr == null) ? attr : attr.get(params); - } catch (Exception e) { - ExternalUtil.annotate(e, "attrName = " + attrName); //NOI18N - throw e; + if (attr == null && origAttrName.startsWith("class:")) { // NOI18N + attr = (Attr) map.get(origAttrName.substring(6)); + retVal = attr.getType(params); + } else { + try { + retVal = (attr == null) ? attr : attr.get(params); + } catch (Exception e) { + ExternalUtil.annotate(e, "attrName = " + attrName); //NOI18N + throw e; + } } if (retVal instanceof ModifiedAttribute) { @@ -733,6 +737,47 @@ } } + final Class getType(Object[] params) { + try { + if (obj != null) { + return obj.getClass(); + } + switch (keyIndex) { + case 0: + return Byte.class; + case 1: + return Short.class; + case 2: + return Integer.class; + case 3: + return Long.class; + case 4: + return Float.class; + case 5: + return Double.class; + case 6: + return Boolean.class; + case 7: + return Character.class; + case 8: + return value.getClass(); + case 9: + return methodValue(value, params).getMethod().getReturnType(); + case 10: + return null; // return decodeValue(value); + case 11: + return URL.class; + case 12: + return ExternalUtil.findClass(Utilities.translate(value)); + case 13: + return String.class; + } + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + } + return null; + } + /** Returns class name of value object.*/ final String getClassName() { if (obj != null) { @@ -870,7 +915,7 @@ return value; case 9: - return methodValue(value, params); + return methodValue(value, params).invoke(); case 10: return decodeValue(value); @@ -903,14 +948,36 @@ throw new InstantiationException(value); } + /** Used to store Method and its parameters. */ + private static class MethodAndParams { + private Method method; + private Object[] params; + + MethodAndParams(Method method, Object[] params) { + this.method = method; + this.params = params; + } + + public Object invoke() throws Exception { + method.setAccessible(true); //otherwise cannot invoke private + return method.invoke(null, params); + } + + public Method getMethod() { + return method; + } + + public Object[] getParams() { + return params; + } + } + /** Constructs new attribute as Object. Used for dynamic creation: methodvalue . * @param params only 2 parametres will be used - * @return Object or null + * @return MethodAndParams object or throws InstantiationException if method is not found */ - private final Object methodValue(String value, Object[] params) - throws Exception { + private final MethodAndParams methodValue(String value, Object[] params) throws Exception { int sepIdx = value.lastIndexOf('.'); - if (sepIdx != -1) { String methodName = value.substring(sepIdx + 1); Class cls = ExternalUtil.findClass(value.substring(0, sepIdx)); @@ -932,41 +999,27 @@ { FileObject.class }, { String.class }, { }, { Map.class, String.class }, { Map.class }, }; - - // XXX clearer and more efficient version now lives in BinaryFS: - boolean both = ((fo != null) && (attrName != null)); - Object[] objectsList = new Object[7]; - objectsList[0] = (both) ? new Object[] { fo, attrName } : null; - objectsList[1] = (both) ? new Object[] { attrName, fo } : null; - objectsList[2] = (fo != null) ? new Object[] { fo } : null; - objectsList[3] = (attrName != null) ? new Object[] { attrName } : null; - objectsList[4] = new Object[] { }; - - Map fileMap = wrapToMap(fo); - objectsList[5] = attrName != null ? new Object[] { fileMap, attrName } : null; - objectsList[6] = new Object[] { fileMap }; - - for (int i = 0; i < paramArray.length; i++) { - Object[] objArray = (Object[]) objectsList[i]; - - if (objArray == null) { + for (Class[] paramTypes : paramArray) { + Method m; + try { + m = cls.getDeclaredMethod(methodName, paramTypes); + } catch (NoSuchMethodException x) { continue; } - - try { - Method method = cls.getDeclaredMethod(methodName, paramArray[i]); - - if (method != null) { - method.setAccessible(true); - - return method.invoke(null, objArray); + Object[] values = new Object[paramTypes.length]; + for (int j = 0; j < paramTypes.length; j++) { + if (paramTypes[j] == FileObject.class) { + values[j] = fo; + } else if (paramTypes[j] == String.class) { + values[j] = attrName; + } else { + assert paramTypes[j] == Map.class; + values[j] = wrapToMap(fo); } - } catch (NoSuchMethodException nsmExc) { - continue; } + return new MethodAndParams(m, values); } } - throw new InstantiationException(value); } diff --git a/openide.filesystems/test/unit/src/org/openide/filesystems/XMLFileSystemTestHid.java b/openide.filesystems/test/unit/src/org/openide/filesystems/XMLFileSystemTestHid.java --- a/openide.filesystems/test/unit/src/org/openide/filesystems/XMLFileSystemTestHid.java +++ b/openide.filesystems/test/unit/src/org/openide/filesystems/XMLFileSystemTestHid.java @@ -296,6 +296,135 @@ } + + public void testNoInstanceCreatedWithNewValue() throws Exception { + Count.cnt = 0; + File f = writeFile("layer.xml", + "\n" + + "\n" + + "" + + " " + + "\n" + + "\n" + + "\n" + ); + + xfs = FileSystemFactoryHid.createXMLSystem(getName(), this, f.toURL()); + FileObject fo = xfs.findResource ("TestModule/sample.txt"); + assertNotNull(fo); + + Object clazz = fo.getAttribute("class:instanceCreate"); + assertEquals("No instance of Count created", 0, Count.cnt); + assertEquals("Yet right class guessed", Count.class, clazz); + Object instance = fo.getAttribute("instanceCreate"); + assertEquals("One instance of Count created", 1, Count.cnt); + assertNotNull("Returned", instance); + assertEquals("Right class", Count.class, instance.getClass()); + } + + public void testNoInstanceCreatedWithMethodValue1() throws Exception { + Count.cnt = 0; + File f = writeFile("layer.xml", + "\n" + + "\n" + + "" + + " " + + "\n" + + "\n" + + "\n" + ); + + xfs = FileSystemFactoryHid.createXMLSystem(getName(), this, f.toURL()); + FileObject fo = xfs.findResource ("TestModule/sample.txt"); + assertNotNull(fo); + + Object clazz = fo.getAttribute("class:instanceCreate"); + assertEquals("No instance of Count created", 0, Count.cnt); + assertEquals("Yet right class guessed", Count.class, clazz); + Object instance = fo.getAttribute("instanceCreate"); + assertEquals("One instance of Count created", 1, Count.cnt); + assertNotNull("Returned", instance); + assertEquals("Right class", Count.class, instance.getClass()); + } + + public void testNoInstanceCreatedWithMethodValue2() throws Exception { + Count.cnt = 0; + File f = writeFile("layer.xml", + "\n" + + "\n" + + "" + + " " + + "\n" + + "\n" + + "\n" + ); + + xfs = FileSystemFactoryHid.createXMLSystem(getName(), this, f.toURL()); + FileObject fo = xfs.findResource ("TestModule/sample.txt"); + assertNotNull(fo); + + Object clazz = fo.getAttribute("class:instanceCreate"); + assertEquals("No instance of Count created", 0, Count.cnt); + assertEquals("Only Runnable guessed as that is the return type of the method", Runnable.class, clazz); + Object instance = fo.getAttribute("instanceCreate"); + assertEquals("One instance of Count created", 1, Count.cnt); + assertNotNull("Returned", instance); + assertEquals("Right class", Count.class, instance.getClass()); + } + + public void testClassBoolean() throws Exception { + doPrimitiveTypeTest("boolvalue='true'", Boolean.class); + } + + public void testClassByte() throws Exception { + doPrimitiveTypeTest("bytevalue='1'", Byte.class); + } + + public void testClassInt() throws Exception { + doPrimitiveTypeTest("intvalue='1'", Integer.class); + } + + public void testClassShort() throws Exception { + doPrimitiveTypeTest("shortvalue='1'", Short.class); + } + + public void testClassLong() throws Exception { + doPrimitiveTypeTest("longvalue='1'", Long.class); + } + public void testClassDouble() throws Exception { + doPrimitiveTypeTest("doublevalue='1.0'", Double.class); + } + public void testClassFloat() throws Exception { + doPrimitiveTypeTest("floatvalue='1.0'", Float.class); + } + public void testClassString() throws Exception { + doPrimitiveTypeTest("stringvalue='1'", String.class); + } + public void testClassURL() throws Exception { + doPrimitiveTypeTest("urlvalue='http://www.netbeans.org'", URL.class); + } + + private void doPrimitiveTypeTest(String value, Class expClass) throws Exception { + File f = writeFile("layer.xml", + "\n" + + "\n" + + "" + + " " + + "\n" + + "\n" + + "\n" + ); + + xfs = FileSystemFactoryHid.createXMLSystem(getName(), this, f.toURL()); + FileObject fo = xfs.findResource ("TestModule/sample.txt"); + assertNotNull(fo); + + Object clazz = fo.getAttribute("class:instanceCreate"); + assertEquals("Only Runnable guessed as that is the return type of the method", expClass, clazz); + Object instance = fo.getAttribute("instanceCreate"); + assertNotNull("Returned", instance); + assertEquals("Right class", expClass, instance.getClass()); + } private File writeFile(String name, String content) throws IOException { diff --git a/openide.loaders/src/org/openide/loaders/InstanceDataObject.java b/openide.loaders/src/org/openide/loaders/InstanceDataObject.java --- a/openide.loaders/src/org/openide/loaders/InstanceDataObject.java +++ b/openide.loaders/src/org/openide/loaders/InstanceDataObject.java @@ -1147,11 +1147,19 @@ private static String getClassName(FileObject fo) { // first of all try "instanceClass" property of the primary file Object attr = fo.getAttribute (EA_INSTANCE_CLASS); + if (attr instanceof Class) { + return ((Class)attr).getName(); + } if (attr instanceof String) { return Utilities.translate((String) attr); } else if (attr != null) { err.warning( "instanceClass was a " + attr.getClass().getName()); // NOI18N + } + + attr = fo.getAttribute ("class:" + EA_INSTANCE_CREATE); + if (attr instanceof Class) { + return ((Class)attr).getName(); } attr = fo.getAttribute (EA_INSTANCE_CREATE); @@ -1191,6 +1199,7 @@ /** Uses cache to remember list of classes to them this object is * assignable. */ + @Override public Class instanceClass() throws IOException, ClassNotFoundException { return super.instanceClass (customClassLoader); } @@ -1198,6 +1207,7 @@ /** Uses the cache to answer this question without loading the class itself, if the * cache exists. */ + @Override public boolean instanceOf (Class type) { // try the life object if any FileObject fo = entry ().getFile (); diff --git a/openide.loaders/test/unit/src/org/openide/awt/MenuBarTest.java b/openide.loaders/test/unit/src/org/openide/awt/MenuBarTest.java --- a/openide.loaders/test/unit/src/org/openide/awt/MenuBarTest.java +++ b/openide.loaders/test/unit/src/org/openide/awt/MenuBarTest.java @@ -41,11 +41,16 @@ package org.openide.awt; +import java.awt.event.ActionEvent; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.URL; import java.util.ArrayList; import java.util.logging.Level; +import javax.swing.AbstractAction; import javax.swing.JMenu; import org.netbeans.junit.Log; import org.netbeans.junit.NbTestCase; @@ -56,8 +61,10 @@ import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.filesystems.Repository; +import org.openide.filesystems.XMLFileSystem; import org.openide.nodes.Node; import org.openide.util.HelpCtx; +import org.openide.util.Utilities; import org.openide.util.actions.CallbackSystemAction; /** @@ -75,11 +82,17 @@ super(testName); } + @Override protected Level logLevel() { return Level.FINE; } + @Override protected void setUp() throws Exception { + CreateOnlyOnceAction.instancesCount = 0; + CreateOnlyOnceAction.w = new StringWriter(); + CreateOnlyOnceAction.pw = new PrintWriter(CreateOnlyOnceAction.w); + FileObject fo = FileUtil.createFolder( Repository.getDefault().getDefaultFileSystem().getRoot(), "Folder" + getName() @@ -89,6 +102,7 @@ mb.waitFinished(); } + @Override protected void tearDown() throws Exception { } @@ -239,7 +253,38 @@ fail("There were warnings about the use of invalid nodes: " + seq); } } - + + public void testActionIsCreatedOnlyOnce_13195() throws Exception { + doActionIsCreatedOnlyOnce_13195("Menu"); + } + + public void testActionIsCreatedOnlyOnceWithNewValue() throws Exception { + doActionIsCreatedOnlyOnce_13195("MenuWithNew"); + } + + private void doActionIsCreatedOnlyOnce_13195(String name) throws Exception { + // crate XML FS from data + String[] stringLayers = new String [] { "/org/openide/awt/data/testActionOnlyOnce.xml" }; + URL[] layers = new URL[stringLayers.length]; + + for (int cntr = 0; cntr < layers.length; cntr++) { + layers[cntr] = Utilities.class.getResource(stringLayers[cntr]); + } + + XMLFileSystem system = new XMLFileSystem(); + system.setXmlUrls(layers); + + // build menu + DataFolder dataFolder = DataFolder.findFolder(system.findResource(name)); + MenuBar menuBar = new MenuBar(dataFolder); + menuBar.waitFinished(); + + if (CreateOnlyOnceAction.instancesCount != 1) { + // ensure that only one instance of action was created + fail("Action created only once, but was: " + CreateOnlyOnceAction.instancesCount + "\n" + CreateOnlyOnceAction.w); + } + } + public void componentAdded(ContainerEvent e) { add++; } @@ -265,4 +310,27 @@ } + + public static class CreateOnlyOnceAction extends AbstractAction { + + static int instancesCount = 0; + static StringWriter w; + static PrintWriter pw; + + public static synchronized CreateOnlyOnceAction create() { + return new CreateOnlyOnceAction(); + } + + public void actionPerformed(ActionEvent e) { + // no op + } + + public CreateOnlyOnceAction() { + new Exception("created for " + (++instancesCount) + " time").printStackTrace(pw); + putValue(NAME, "TestAction"); + } + + } + + }