diff -r 0343ed0a18d1 settings/manifest.mf --- a/settings/manifest.mf Tue Mar 05 12:31:51 2013 +0100 +++ b/settings/manifest.mf Tue Mar 05 21:40:46 2013 +0100 @@ -3,5 +3,5 @@ OpenIDE-Module-Layer: org/netbeans/modules/settings/resources/mf-layer.xml OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/settings/resources/Bundle.properties AutoUpdate-Essential-Module: true -OpenIDE-Module-Specification-Version: 1.39 +OpenIDE-Module-Specification-Version: 1.40 diff -r 0343ed0a18d1 settings/src/org/netbeans/api/settings/FactoryMethod.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/settings/src/org/netbeans/api/settings/FactoryMethod.java Tue Mar 05 21:40:46 2013 +0100 @@ -0,0 +1,69 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2013 Sun Microsystems, Inc. + */ +package org.netbeans.api.settings; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.netbeans.spi.settings.Convertor; + +/** Specifies an alternative factory method to use (rather than constructor) + * to create the instance. Use in orchestration with + * {@link ConvertAsProperties} or only any class that is processed by serializing + * {@link Convertor}s. + * + * @since 1.40 + * @author Jaroslav Tulach + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface FactoryMethod { + /** + * Name of factory method to use instead of default constructor. Sometimes, for + * example when dealing with singletons, it may be desirable to control how + * an instance of given class is created. In such case one can create a + * factory method (takes no arguments and returns instance of desired type) + * in the class annotated by {@link ConvertAsProperties} annotation. + */ + String value(); +} diff -r 0343ed0a18d1 settings/src/org/netbeans/modules/settings/convertors/XMLPropertiesConvertor.java --- a/settings/src/org/netbeans/modules/settings/convertors/XMLPropertiesConvertor.java Tue Mar 05 12:31:51 2013 +0100 +++ b/settings/src/org/netbeans/modules/settings/convertors/XMLPropertiesConvertor.java Tue Mar 05 21:40:46 2013 +0100 @@ -225,7 +225,7 @@ Class c = getInstanceClass(); try { - return c.newInstance(); + return XMLSettingsSupport.newInstance(c); } catch (Exception ex) { // IllegalAccessException, InstantiationException IOException ioe = new IOException("Cannot create instance of " + c.getName()); // NOI18N ioe.initCause(ex); diff -r 0343ed0a18d1 settings/src/org/netbeans/modules/settings/convertors/XMLSettingsSupport.java --- a/settings/src/org/netbeans/modules/settings/convertors/XMLSettingsSupport.java Tue Mar 05 12:31:51 2013 +0100 +++ b/settings/src/org/netbeans/modules/settings/convertors/XMLSettingsSupport.java Tue Mar 05 21:40:46 2013 +0100 @@ -46,10 +46,12 @@ import java.io.*; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; +import org.netbeans.api.settings.FactoryMethod; import org.openide.filesystems.*; import org.openide.modules.ModuleInfo; @@ -78,6 +80,19 @@ /** Logging for events in XML settings system. */ static final Logger err = Logger.getLogger(XMLSettingsSupport.class.getName()); // NOI18N + + static Object newInstance(Class clazz) throws IllegalArgumentException, + InstantiationException, NoSuchMethodException, InvocationTargetException, + IllegalAccessException { + FactoryMethod fm = clazz.getAnnotation(FactoryMethod.class); + if (fm != null) { + return clazz.getMethod(fm.value()).invoke(null); + } + Constructor c = clazz.getDeclaredConstructor(); + c.setAccessible(true); + return c.newInstance(); + } + /** Store instanceof elements. * @param classes everything what class extends or implements @@ -603,9 +618,7 @@ } } else { try { - Constructor c = clazz.getDeclaredConstructor(); - c.setAccessible(true); - inst = c.newInstance(); + inst = newInstance(clazz); } catch (Exception ex) { IOException ioe = new IOException(); ioe.initCause(ex); diff -r 0343ed0a18d1 settings/test/unit/src/org/netbeans/modules/settings/convertors/SerialDataConvertorTest.java --- a/settings/test/unit/src/org/netbeans/modules/settings/convertors/SerialDataConvertorTest.java Tue Mar 05 12:31:51 2013 +0100 +++ b/settings/test/unit/src/org/netbeans/modules/settings/convertors/SerialDataConvertorTest.java Tue Mar 05 21:40:46 2013 +0100 @@ -53,9 +53,12 @@ import org.openide.modules.ModuleInfo; import org.openide.util.*; import java.io.*; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; import java.util.*; import junit.framework.Test; import junit.framework.TestSuite; +import org.netbeans.api.settings.FactoryMethod; import org.netbeans.junit.*; @@ -657,4 +660,36 @@ assertNotNull("Missing InstanceCookie", ic); assertNotNull("the persisted object cannot be read", ic.instanceCreate()); } + + public void testFactoryMethod() throws IOException, ClassNotFoundException { + DataFolder df = DataFolder.findFolder(FileUtil.getConfigRoot().createFolder("testFactoryMethod")); + FileObject fo = df.getPrimaryFile().createData("test.settings"); + OutputStream os = fo.getOutputStream(); + os.write(( +"\n" + +"\n" + +"\n" + +" \n" + +"\n" + ).getBytes("UTF-8")); + os.close(); + + InstanceCookie ido = DataObject.find(fo).getCookie(InstanceCookie.class); + FactoryBase fb = (FactoryBase) ido.instanceCreate(); + assertNotNull("Re-created OK!", fb); + } + + @FactoryMethod("create") + public static class FactoryBase implements Serializable { + private FactoryBase() { + throw new IllegalStateException("Don't call my default constructor"); + } + + FactoryBase(boolean ok) { + } + + public static FactoryBase create() { + return new FactoryBase(true); + } + } } diff -r 0343ed0a18d1 settings/test/unit/src/org/netbeans/modules/settings/convertors/XMLPropertiesConvertorTest.java --- a/settings/test/unit/src/org/netbeans/modules/settings/convertors/XMLPropertiesConvertorTest.java Tue Mar 05 12:31:51 2013 +0100 +++ b/settings/test/unit/src/org/netbeans/modules/settings/convertors/XMLPropertiesConvertorTest.java Tue Mar 05 21:40:46 2013 +0100 @@ -45,6 +45,13 @@ package org.netbeans.modules.settings.convertors; import java.io.*; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.Properties; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import org.netbeans.api.settings.ConvertAsProperties; +import org.netbeans.api.settings.FactoryMethod; import org.netbeans.junit.NbTestCase; import org.netbeans.junit.RandomlyFails; @@ -58,6 +65,7 @@ import org.openide.filesystems.FileObject; import org.openide.filesystems.FileSystem; import org.openide.filesystems.FileUtil; +import org.openide.filesystems.Repository; import org.openide.filesystems.XMLFileSystem; import org.openide.loaders.*; import org.openide.modules.ModuleInfo; @@ -352,6 +360,50 @@ assertEquals("Listener not deregistered", 0, obj.getListenerCount()); assertNull(filename + ".settings was not deleted!", root.getFileObject(filename)); } + + public void testFactoryMethod() throws Exception { + FileObject dtdFO = Repository.getDefault().getDefaultFileSystem(). + findResource("/xml/lookups/abc/x.instance"); + assertNotNull("Provider not found", dtdFO); + Convertor c = XMLPropertiesConvertor.create(dtdFO); + assertNotNull("Convertor created", c); + + DataFolder folder = DataFolder.findFolder(root); + + FactoryBase inst = FactoryBase.create(); + InstanceDataObject ido = InstanceDataObject.create(folder, null, inst, null); + + assertSame("Instance is there", inst, ido.instanceCreate()); + + Reference ref = new WeakReference(inst); + inst = null; + + assertGC("Instance can disappear", ref); + + Object obj = ido.instanceCreate(); + assertEquals("One can re-create it without default constructor", FactoryBase.class, obj.getClass()); + } + + @ConvertAsProperties(dtd = "-//abc/x") + @FactoryMethod("create") + public static class FactoryBase implements Serializable { + public FactoryBase() { + throw new IllegalStateException("Don't call my default constructor"); + } + + FactoryBase(boolean ok) { + } + + public static FactoryBase create() { + return new FactoryBase(true); + } + + void readProperties(Properties p) { + } + + void writeProperties(Properties p) { + } + } public void testModuleDisabling() throws Exception { FileObject dtd = FileUtil.getConfigFile("xml/lookups/NetBeans_org_netbeans_modules_settings_testModuleDisabling/DTD_XML_FooSetting_1_0.instance");