# HG changeset patch # Parent 894b65304badf5d3fa9495d021902667ea644be3 diff --git a/api.templates/apichanges.xml b/api.templates/apichanges.xml --- a/api.templates/apichanges.xml +++ b/api.templates/apichanges.xml @@ -54,6 +54,22 @@ + + + Support for composition + + + + + + TemplateHandler may need to create additional files, using the same parameters for creation as a base. + Attribute providers may need to be processed in a custom way, which requires to create and manage CreateDescriptor + by the client. This change allows to clone information from CreateDescriptor into an independent FileBuilder, and + to create a Descriptor out of a Builder. + + + + Support for Technology Ids diff --git a/api.templates/manifest.mf b/api.templates/manifest.mf --- a/api.templates/manifest.mf +++ b/api.templates/manifest.mf @@ -2,5 +2,5 @@ AutoUpdate-Show-In-Client: false OpenIDE-Module: org.netbeans.api.templates OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/templates/Bundle.properties -OpenIDE-Module-Specification-Version: 1.4 +OpenIDE-Module-Specification-Version: 1.5 OpenIDE-Module-Recommends: org.netbeans.templates.IndentEngine diff --git a/api.templates/src/org/netbeans/api/templates/CreateFromTemplateImpl.java b/api.templates/src/org/netbeans/api/templates/CreateFromTemplateImpl.java --- a/api.templates/src/org/netbeans/api/templates/CreateFromTemplateImpl.java +++ b/api.templates/src/org/netbeans/api/templates/CreateFromTemplateImpl.java @@ -92,6 +92,11 @@ return impl.build(); } + static void collectAttributes(FileBuilder flb) { + CreateFromTemplateImpl impl = new CreateFromTemplateImpl(flb); + flb.withParameters(impl.findTemplateParameters()); + } + List build() throws IOException { // side effects: replaces the map in CreateDescriptor try { @@ -108,7 +113,7 @@ } // also modifies desc.getParameters, result not needed. findTemplateParameters(); - computeEffectiveName(); + computeEffectiveName(desc); List pf = null; for (CreateFromTemplateHandler h : Lookup.getDefault().lookupAll(CreateFromTemplateHandler.class)) { @@ -129,7 +134,7 @@ } } - private void computeEffectiveName() { + /* package private */ static void computeEffectiveName(CreateDescriptor desc) { String name = desc.getName(); if (name == null) { // name is not set - try to check parameters, if some template attribute handler diff --git a/api.templates/src/org/netbeans/api/templates/FileBuilder.java b/api.templates/src/org/netbeans/api/templates/FileBuilder.java --- a/api.templates/src/org/netbeans/api/templates/FileBuilder.java +++ b/api.templates/src/org/netbeans/api/templates/FileBuilder.java @@ -56,6 +56,7 @@ import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.MapFormat; +import org.openide.util.Parameters; /** * Fluent interface for file creation. The Builder is first parametrized. After @@ -101,6 +102,29 @@ } /** + * Creates a Builder based on the CreateDescriptor. The FileBuilder inherits + * all parameters of the original {@link CreateDescriptor}. The client may change the attributes. + * The method may be useful when creating secondary files; for example target and all attributes + * are retained. During {@link #build()}, attributes may be redefined as needed for the + * additional file, just like in normal Builder operation. + *

+ * The new FileBuilder instance is completely indepenent of the original Descriptor. If the CreateDescriptor + * supports additional properties in the future, using this method guarantees that they will be + * transferred to the FileBuilder copy. + * + * @param desc the original descriptor + * @return new FileBuilder + * @since 1.5 + */ + public static @NonNull FileBuilder fromDescriptor(@NonNull CreateDescriptor desc) { + Parameters.notNull("desc", desc); + return new FileBuilder(desc.getTemplate(), desc.getTarget()). + name(desc.getProposedName()). + useLocale(desc.getLocale()). + withParameters(desc.getParameters()); + } + + /** * Creates a new FileBuilder for a specific template and target folder. * @param template the template to use. * @param target the target folder; must already exist. @@ -230,6 +254,29 @@ return descriptor; } + /** + * Creates a descriptor from the current Builder's state. + * If `collectAttributes' is false, the descriptor + * will have no additional parameters set from {@link CreateFromTemplateAttributes} providers; + * the caller must process the providers, if it wishes to get additional attributes. + * The Descriptor can be used to collect information from attribute providers or manually + * trigger file creation in template handler. + *

+ * The operation changes the FileBuilder state. + * + * @param collectAttributes if true, attribute providers are asked to add their attributes + * to the builder/descriptor. + * @return descriptor + * @since 1.5 + */ + public @NonNull CreateDescriptor createDescriptor(boolean collectAttributes) { + if (collectAttributes) { + CreateFromTemplateImpl.collectAttributes(this); + } + CreateFromTemplateImpl.computeEffectiveName(descriptor); + return descriptor; + } + private final CreateDescriptor descriptor; @SuppressWarnings("PackageVisibleField") diff --git a/projectuiapi/nbproject/project.xml b/projectuiapi/nbproject/project.xml --- a/projectuiapi/nbproject/project.xml +++ b/projectuiapi/nbproject/project.xml @@ -80,7 +80,7 @@ - 1.3 + 1.5 diff --git a/projectuiapi/src/org/netbeans/modules/project/uiapi/ProjectTemplateAttributesProvider.java b/projectuiapi/src/org/netbeans/modules/project/uiapi/ProjectTemplateAttributesProvider.java --- a/projectuiapi/src/org/netbeans/modules/project/uiapi/ProjectTemplateAttributesProvider.java +++ b/projectuiapi/src/org/netbeans/modules/project/uiapi/ProjectTemplateAttributesProvider.java @@ -45,6 +45,8 @@ package org.netbeans.modules.project.uiapi; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.URI; import java.nio.charset.Charset; import java.util.Collection; @@ -56,6 +58,7 @@ import org.netbeans.api.queries.FileEncodingQuery; import org.netbeans.api.templates.CreateDescriptor; import org.netbeans.api.templates.CreateFromTemplateAttributes; +import org.netbeans.api.templates.FileBuilder; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.loaders.CreateFromTemplateAttributesProvider; @@ -84,7 +87,7 @@ FileObject targetF = desc.getTarget(); String name = desc.getProposedName(); Project prj = FileOwnerQuery.getOwner(targetF); - Map all = null; + Map all = new HashMap(); if (prj != null) { // call old providers Collection oldProvs = prj.getLookup().lookupAll(CreateFromTemplateAttributesProvider.class); @@ -97,9 +100,6 @@ for (CreateFromTemplateAttributesProvider attrs : oldProvs) { Map m = attrs.attributesFor(template, target, name); if (m != null) { - if (all == null) { - all = new HashMap(); - } all.putAll(m); } } @@ -110,12 +110,14 @@ } } // call new providers last, so they can override anything old providers could screw up. + // new providers should get all attributes collected from previous (new-style) CFTAs incl. attributes provided + // by [deprecated] CFTAPs accumulated above. + FileBuilder bld = FileBuilder.fromDescriptor(desc); + // temporary: for (CreateFromTemplateAttributes attrs : prj.getLookup().lookupAll(CreateFromTemplateAttributes.class)) { - Map m = attrs.attributesFor(desc); + CreateDescriptor childDesc = bld.withParameters(all).createDescriptor(false); + Map m = attrs.attributesFor(childDesc); if (m != null) { - if (all == null) { - all = new HashMap(); - } all.putAll(m); } } diff --git a/projectuiapi/test/unit/src/org/netbeans/modules/project/uiapi/ProjectTemplateAttributesProviderTest.java b/projectuiapi/test/unit/src/org/netbeans/modules/project/uiapi/ProjectTemplateAttributesProviderTest.java --- a/projectuiapi/test/unit/src/org/netbeans/modules/project/uiapi/ProjectTemplateAttributesProviderTest.java +++ b/projectuiapi/test/unit/src/org/netbeans/modules/project/uiapi/ProjectTemplateAttributesProviderTest.java @@ -42,16 +42,30 @@ package org.netbeans.modules.project.uiapi; +import java.io.IOException; import java.nio.charset.Charset; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectManager; import org.netbeans.api.project.TestUtil; import org.netbeans.api.queries.FileEncodingQuery; +import org.netbeans.api.templates.CreateDescriptor; +import org.netbeans.api.templates.CreateFromTemplateAttributes; +import org.netbeans.api.templates.FileBuilder; import org.netbeans.junit.NbTestCase; +import org.netbeans.spi.project.ProjectFactory; +import org.netbeans.spi.project.ProjectState; import org.netbeans.spi.queries.FileEncodingQueryImplementation; import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.loaders.CreateFromTemplateAttributesProvider; +import org.openide.loaders.DataFolder; +import org.openide.loaders.DataObject; +import org.openide.util.Lookup; import org.openide.util.NbCollections; +import org.openide.util.lookup.Lookups; import org.openide.util.test.MockLookup; /** @@ -62,6 +76,7 @@ private FileObject scratch; private FileObject folder; + private FileObject projdir; public ProjectTemplateAttributesProviderTest(String testName) { super(testName); @@ -72,10 +87,41 @@ super.setUp(); scratch = TestUtil.makeScratchDir(this); folder = scratch.createFolder("folder"); - MockLookup.setInstances(new FEQImpl()); + + projdir = scratch.createFolder("proj"); + + createProject(projdir); + + MockLookup.setInstances(new FEQImpl(), new TestProjectFactory()); assertEquals(FEQImpl.ENCODING, FileEncodingQuery.getEncoding(folder).name()); } + private void createProject(FileObject projdir) throws Exception { + TestUtil.createFileFromContent(ProjectTemplateAttributesProviderTest.class.getResource("data/test.txt"), projdir, "nbproject/test.txt"); + TestUtil.createFileFromContent(ProjectTemplateAttributesProviderTest.class.getResource("data/test.txt"), projdir, "src/test/test.txt"); + } + + /** + * Checks that the attribute providers execute in the correct order and see other provider's data. + * Legacy providers should execute first. New providers should execute after that. Each new-style + * provider should see all attributes defined by previous providers (legacy or new). + * + * @throws Exception + */ + public void testProjectAttributeProviders() throws Exception { + Project prj = ProjectManager.getDefault().findProject(projdir); + FileObject folder = projdir.getFileObject("nbproject"); + FileObject template = FileUtil.toFileObject(getDataDir()).getFileObject("file.txt"); + Map init = new HashMap(); + init.put("mama", "se raduje"); + FileObject result = FileBuilder.createFromTemplate(template, folder, "honza", init, FileBuilder.Mode.FORMAT); + + assertEquals( + "Jedna, 2, Honza jde. Nese 2 pytle s brouky. Mama se raduje, ze bude pect vdolky.\n", + result.asText()); + } + + public void testCheckProjectAttrs() throws Exception { Map checked = ProjectTemplateAttributesProvider.checkProjectAttrs(null, folder); assertAttribute("default", checked, "license"); @@ -128,4 +174,80 @@ return null; } } + + private static final class AttrProv1 implements CreateFromTemplateAttributes { + + @Override + public Map attributesFor(CreateDescriptor desc) { + Map m = new HashMap(); + m.put("jedna", 2); // used by Prov2 + m.put("dve", "Honza jde"); + return m; + } + + } + + private static final class AttrProv2 implements CreateFromTemplateAttributes { + @Override + public Map attributesFor(CreateDescriptor desc) { + String s = desc.getValue("pytel"); + s += " brouky"; + Map m = new HashMap(); + m.put("pytel", s); // replace /append to legacy-provided value + m.put("nese", desc.getValue("jedna")); // copy previous value + return m; + } + } + + private static final class AttrProvLegacy implements CreateFromTemplateAttributesProvider { + @Override + public Map attributesFor(DataObject template, DataFolder target, String name) { + Map m = new HashMap(); + m.put("pytel", "s"); // appended by Prov2 + m.put("bude", "bude pect vdolky"); + return m; + } + } + + private static final class TestProject implements Project { + + private final Lookup l; + private final FileObject projectDirectory; + + TestProject(FileObject projectDirectory) throws IOException { + l = Lookups.fixed(new AttrProv1(), new AttrProv2(), new AttrProvLegacy()); + this.projectDirectory = projectDirectory; + } + + public FileObject getProjectDirectory() { + return projectDirectory; + } + + public Lookup getLookup() { + return l; + } + + public String toString() { + return "TestAntBasedProject[" + getProjectDirectory() + "]"; + } + + } + + public static class TestProjectFactory implements ProjectFactory { + + public boolean isProject(FileObject projectDirectory) { + return projectDirectory.getFileObject("nbproject") != null; + } + + public Project loadProject(FileObject projectDirectory, ProjectState state) throws IOException { + if (isProject(projectDirectory)) + return new TestProject(projectDirectory); + + return null; + } + + public void saveProject(Project project) throws IOException, ClassCastException { + } + + } }