Index: ant/project/apichanges.xml =================================================================== RCS file: /cvs/ant/project/apichanges.xml,v --- ant/project/apichanges.xml 2 Mar 2007 03:34:19 -0000 1.15 +++ ant/project/apichanges.xml 6 Apr 2007 13:50:17 -0000 @@ -81,6 +81,26 @@ + + + + Support for externally extending the project's build script + + + + + +

+ Add framework for extending the project's build script with 3rd party snippets, + allowing automated extensions to the build process. +

+
+ + + + + +
Index: ant/project/manifest.mf =================================================================== RCS file: /cvs/ant/project/manifest.mf,v --- ant/project/manifest.mf 2 Mar 2007 03:34:19 -0000 1.19 +++ ant/project/manifest.mf 6 Apr 2007 13:50:17 -0000 @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.project.ant/1 -OpenIDE-Module-Specification-Version: 1.15 +OpenIDE-Module-Specification-Version: 1.16 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/project/ant/Bundle.properties OpenIDE-Module-Install: org/netbeans/modules/project/ant/AntProjectModule.class Index: ant/project/nbproject/project.xml =================================================================== RCS file: /cvs/ant/project/nbproject/project.xml,v --- ant/project/nbproject/project.xml 27 Mar 2007 00:01:23 -0000 1.21 +++ ant/project/nbproject/project.xml 6 Apr 2007 13:50:17 -0000 @@ -165,5 +165,6 @@ build/tasks.jar + Index: ant/project/src/org/netbeans/api/project/ant/AntBuildExtender.java =================================================================== RCS file: ant/project/src/org/netbeans/api/project/ant/AntBuildExtender.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ant/project/src/org/netbeans/api/project/ant/AntBuildExtender.java 6 Apr 2007 13:50:18 -0000 @@ -0,0 +1,304 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.api.project.ant; + +import org.netbeans.spi.project.ant.AntBuildExtenderImplementation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.spi.project.ant.AntBuildExtenderImplementation; +import org.netbeans.spi.project.support.ant.AntProjectHelper; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +/** + * Allows extending the project's build script with 3rd party additions. + * Check the Project's lookup to see if the feature is supported by a given Ant project type. + * Typical usage: + *
    + *
  • Lookup the instance of AntBuildExtender in the project at hand
  • + *
  • Create the external build script file with your targets and configuration
  • + *
  • Use the AntBuildExtender to wire your script and targets into the main build lifecycle
  • + *
  • Call {@link org.netbeans.api.project.ProjectManager#saveProject} to persist the changes and + * regenerate the main build script
  • + *
+ * + * Please note that it's easy to break the build script functionality and any script extensions + * shall be done with care. A few rules to follow: + *
    + *
  • Pick a reasonably unique extension id
  • + *
  • Prefix target names and properties you define in your extension with the extension id to prevent clashes.
  • + *
+ * @author mkleint + * @since org.netbeans.modules.project.ant 1.16 + */ +public final class AntBuildExtender { + private HashMap extensions; + private AntBuildExtenderImplementation implementation; + + static { + AntBuildExtenderAccessorImpl.createAccesor(); + } + + AntBuildExtender(AntBuildExtenderImplementation implementation) { + this.implementation = implementation; + } + + /** + * Get a list of target names in the main build script that are allowed to be + * extended by adding the "depends" attribute definition to them. + * @return list of target names + */ + public List getExtendableTargets() { + List targets = new ArrayList(); + targets.addAll(implementation.getExtendableTargets()); + targets = Collections.unmodifiableList(targets); + return targets; + } + + /** + * Adds a new build script extension. + * @param id identification of the extension + * @param extensionXml fileobject referencing the build script for the extension, + * needs to be located in nbproject directory or below. + * @return the newly created extension. + */ + public synchronized Extension addExtension(String id, FileObject extensionXml) { + assert extensionXml != null; + assert extensionXml.isValid() && extensionXml.isData(); + //TODO assert the owner is the same as the owner of this instance of entender. + assert FileOwnerQuery.getOwner(extensionXml) == implementation.getOwningProject(); + FileObject nbproj = implementation.getOwningProject().getProjectDirectory().getFileObject(AntProjectHelper.PROJECT_XML_PATH).getParent(); + assert FileUtil.isParentOf(nbproj, extensionXml); + if (extensions == null) { + readProjectMetadata(); + } + if (extensions.get(id) != null) { + throw new IllegalStateException("Extension with id '" + id + "' already exists."); + } + Extension ex = new Extension(id, extensionXml, FileUtil.getRelativePath(nbproj, extensionXml)); + extensions.put(id, ex); + updateProjectMetadata(); + return ex; + } + /** + * Remove an existing build script extension. Make sure to remove the extension's script file + * before/after removing the extension. + * @param id identification of the extension + */ + public synchronized void removeExtension(String id) { + if (extensions == null) { + readProjectMetadata(); + } + if (extensions.get(id) == null) { + // oh well, just ignore. + return; + } + extensions.remove(id); + updateProjectMetadata(); + } + + /** + * Get an extension by the id. + * @param id identification token + * @return Extention with the given id or null if not found. + */ + public synchronized Extension getExtension(String id) { + if (extensions == null) { + readProjectMetadata(); + } + return extensions.get(id); + } + + + synchronized Set getExtensions() { + Set ext = new HashSet(); + if (extensions == null) { + readProjectMetadata(); + } + ext.addAll(extensions.values()); + return ext; + } + + private static final DocumentBuilder db; + static { + try { + db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new AssertionError(e); + } + } + private static Document createNewDocument() { + // #50198: for thread safety, use a separate document. + // Using XMLUtil.createDocument is much too slow. + synchronized (db) { + return db.newDocument(); + } + } + + + private void updateProjectMetadata() { + Document doc = createNewDocument(); + Element root = doc.createElement(AntBuildExtenderImplementation.ELEMENT_ROOT); + if (extensions != null) { + FileObject nbproj = implementation.getOwningProject().getProjectDirectory().getFileObject(AntProjectHelper.PROJECT_XML_PATH).getParent(); + for (Extension ext : extensions.values()) { + Element child = doc.createElement(AntBuildExtenderImplementation.ELEMENT_EXTENSION); + child.setAttribute(AntBuildExtenderImplementation.ATTR_ID, ext.id); + String relPath = FileUtil.getRelativePath(nbproj, ext.file); + assert relPath != null; + child.setAttribute(AntBuildExtenderImplementation.ATTR_FILE, relPath); + root.appendChild(child); + for (String target : ext.dependencies.keySet()) { + for (String depTarget : ext.dependencies.get(target)) { + Element dep = doc.createElement(AntBuildExtenderImplementation.ELEMENT_DEPENDENCY); + dep.setAttribute(AntBuildExtenderImplementation.ATTR_TARGET, target); + dep.setAttribute(AntBuildExtenderImplementation.ATTR_DEPENDSON, depTarget); + child.appendChild(dep); + } + } + } + } + implementation.updateBuildExtensionMetadata(root); + } + + private void readProjectMetadata() { + Element cfgEl = implementation.getBuildExtensionMetadata(); + List rootIds = new ArrayList(); + FileObject nbproj = implementation.getOwningProject().getProjectDirectory().getFileObject(AntProjectHelper.PROJECT_XML_PATH).getParent(); + extensions = new HashMap(); + if (cfgEl != null) { + String namespace = cfgEl.getNamespaceURI(); + NodeList roots = cfgEl.getElementsByTagNameNS(namespace, AntBuildExtenderImplementation.ELEMENT_EXTENSION); + for (int i=0; i 0 : "Illegal project.xml"; + String value = root.getAttribute(AntBuildExtenderImplementation.ATTR_FILE); + FileObject script = nbproj.getFileObject(value); + assert script != null : "Missing file " + script; + Extension ext = new Extension(id, script, value); + extensions.put(id, ext); + NodeList deps = root.getElementsByTagNameNS(namespace, AntBuildExtenderImplementation.ELEMENT_DEPENDENCY); + for (int j = 0; j < deps.getLength(); j++) { + Element dep = (Element)deps.item(j); + String target = dep.getAttribute(AntBuildExtenderImplementation.ATTR_TARGET); + String dependsOn = dep.getAttribute(AntBuildExtenderImplementation.ATTR_DEPENDSON); + assert target != null; + assert dependsOn != null; + ext.loadDependency(target, dependsOn); + } + } + } + } + + /** + * Describes and allows to manipulate the build script extension and it's links to the main build script + * of the project. + */ + public final class Extension { + String id; + FileObject file; + String path; + TreeMap> dependencies; + + Extension(String id, FileObject script, String relPath) { + this.id = id; + file = script; + path = relPath; + dependencies = new TreeMap>(); + } + + String getPath() { + return path; + } + + /** + * Add a dependency of a main build script target on the target in the extension's script. + * @param mainBuildTarget name of target in the main build script (see {@link org.netbeans.api.project.ant.AntBuildExtender#getExtendableTargets}) + * @param extensionTarget name of target in the extention script + */ + public void addDependency(String mainBuildTarget, String extensionTarget) { + assert implementation.getExtendableTargets().contains(mainBuildTarget) : + "The target '" + mainBuildTarget + "' is not designated by the project type as extensible."; + synchronized (AntBuildExtender.class) { + loadDependency(mainBuildTarget, extensionTarget); + updateProjectMetadata(); + } + } + + private void loadDependency(String mainBuildTarget, String extensionTarget) { + synchronized (AntBuildExtender.class) { + Collection tars = dependencies.get(mainBuildTarget); + if (tars == null) { + tars = new ArrayList(); + dependencies.put(mainBuildTarget, tars); + } + if (!tars.contains(extensionTarget)) { + tars.add(extensionTarget); + } else { + //log? + } + } + } + + + /** + * Remove a dependency of a main build script target on the target in the extension's script. + * + * @param mainBuildTarget name of target in the main build script (see {@link org.netbeans.api.project.ant.AntBuildExtender#getExtendableTargets}) + * @param extensionTarget name of target in the extention script + */ + public void removeDependency(String mainBuildTarget, String extensionTarget) { + Collection str = dependencies.get(mainBuildTarget); + if (str != null) { + str.remove(extensionTarget); + updateProjectMetadata(); + } else { + //oh well, just ignore, nothing to update anyway.. + } + } + + Map> getDependencies() { + TreeMap> toRet = new TreeMap>(); + synchronized (AntBuildExtender.class) { + for (String str : dependencies.keySet()) { + ArrayList col = new ArrayList(); + col.addAll(dependencies.get(str)); + toRet.put(str, col); + } + } + return toRet; + } + } +} Index: ant/project/src/org/netbeans/api/project/ant/AntBuildExtenderAccessorImpl.java =================================================================== RCS file: ant/project/src/org/netbeans/api/project/ant/AntBuildExtenderAccessorImpl.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ant/project/src/org/netbeans/api/project/ant/AntBuildExtenderAccessorImpl.java 6 Apr 2007 13:50:18 -0000 @@ -0,0 +1,60 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ +package org.netbeans.api.project.ant; + +import org.netbeans.spi.project.ant.AntBuildExtenderImplementation; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import org.netbeans.api.project.ant.AntBuildExtender.Extension; +import org.netbeans.modules.project.ant.AntBuildExtenderAccessor; +import org.netbeans.spi.project.ant.AntBuildExtenderImplementation; + +/** + * + * @author mkleint + */ +class AntBuildExtenderAccessorImpl extends AntBuildExtenderAccessor { + + static void createAccesor() { + if (DEFAULT == null) { + DEFAULT= new AntBuildExtenderAccessorImpl(); + } + } + + private AntBuildExtenderAccessorImpl() { + } + + + public AntBuildExtender createExtender(AntBuildExtenderImplementation impl) { + return new AntBuildExtender(impl); + } + + public Set getExtensions(AntBuildExtender ext) { + return ext.getExtensions(); + } + + public String getPath(Extension extension) { + return extension.getPath(); + } + + public Map> getDependencies(Extension extension) { + return extension.getDependencies(); + } + +} Index: ant/project/src/org/netbeans/modules/project/ant/AntBuildExtenderAccessor.java =================================================================== RCS file: ant/project/src/org/netbeans/modules/project/ant/AntBuildExtenderAccessor.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ant/project/src/org/netbeans/modules/project/ant/AntBuildExtenderAccessor.java 6 Apr 2007 13:50:18 -0000 @@ -0,0 +1,57 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.modules.project.ant; + +import org.netbeans.spi.project.ant.AntBuildExtenderImplementation; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import org.netbeans.api.project.ant.AntBuildExtender; +import org.netbeans.spi.project.ant.AntBuildExtenderImplementation; + + +/** + * @author mkleint + */ +public abstract class AntBuildExtenderAccessor { + + public static AntBuildExtenderAccessor DEFAULT = null; + + static { + // invokes static initializer of Item.class + // that will assign value to the DEFAULT field above + Class c = AntBuildExtender.class; + try { + Class.forName(c.getName(), true, c.getClassLoader()); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public abstract AntBuildExtender createExtender(AntBuildExtenderImplementation impl); + + public abstract Set getExtensions(AntBuildExtender ext); + + public abstract String getPath(AntBuildExtender.Extension extension); + + public abstract Map> getDependencies(AntBuildExtender.Extension extension); + + +} Index: ant/project/src/org/netbeans/spi/project/ant/AntBuildExtenderImplementation.java =================================================================== RCS file: ant/project/src/org/netbeans/spi/project/ant/AntBuildExtenderImplementation.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ant/project/src/org/netbeans/spi/project/ant/AntBuildExtenderImplementation.java 6 Apr 2007 13:50:18 -0000 @@ -0,0 +1,104 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.spi.project.ant; + +import java.util.List; +import org.netbeans.api.project.Project; +import org.w3c.dom.Element; + +/** + * A project type's spi for {@link org.netbeans.api.project.ant.AntBuildExtender}'s wiring. + * A typical setup in the project type includes: + *
    + *
  • Enhance the Project's metadata schema with the build extensibility content. + *
  • Provide lookup and storage of the metadata content by implementing this interface + *
  • Provide an instance of {@link org.netbeans.api.project.ant.AntBuildExtender} in project's lookup for use by 3rd + * party modules. + *
  • Use the new {@link org.netbeans.spi.project.support.ant.GeneratedFilesHelper#GeneratedFilesHelper(AntProjectHelper,AntBuildExtender)} constructor to + * create the helper for generating build related files. + *
+ * @author mkleint + * @since org.netbeans.modules.project.ant 1.16 + */ +public interface AntBuildExtenderImplementation { + + /** + * + */ + public static final String ELEMENT_ROOT = "buildExtensions"; //NOI18N + + /** + * + */ + public static final String ELEMENT_EXTENSION = "extension"; //NOI18N + + /** + * + */ + public static final String ELEMENT_DEPENDENCY = "dependency"; //NOI18N + + /** + * + */ + public static final String ATTR_TARGET = "target"; + /** + * + */ + public static final String ATTR_DEPENDSON = "dependsOn"; + /** + * + */ + public static final String ATTR_ID = "id"; + /** + * + */ + public static final String ATTR_FILE = "file"; + + + /** + * A declarative list of targets that are intended by the project type to be used + * for extensions to plug into. + * @return list of target names + */ + List getExtendableTargets(); + + /** + * Requests an update of build extensibility metadata by the implementation. + * A usual implementation takes the content of root parameter and + * copies it into the correct place in the primary project configuration space in project.xml + * + * @param root the root element for extensibility metadata (See {@link org.netbeans.spi.project.ant.AntBuildExtenderImplementation#ELEMENT_ROOT}) */ + void updateBuildExtensionMetadata(Element root); + + /** + * Returns the project build script extensibility configuration from a known location in project.xml. + * (As defined by the project type's schema). Usual implementation will use the primary project configuration + * space in project.xml. + * @return project metadata element, the root for project extensions (See {@link org.netbeans.spi.project.ant.AntBuildExtenderImplementation#ELEMENT_ROOT}), or null if not + * configured. + */ + Element getBuildExtensionMetadata(); + + /** + * Returns Ant Project instance. + * @return The project that this instance of AntBuildExtenderImplementation describes + */ + Project getOwningProject(); +} Index: ant/project/src/org/netbeans/spi/project/support/ant/AntBuildExtenderSupport.java =================================================================== RCS file: ant/project/src/org/netbeans/spi/project/support/ant/AntBuildExtenderSupport.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ant/project/src/org/netbeans/spi/project/support/ant/AntBuildExtenderSupport.java 6 Apr 2007 13:50:18 -0000 @@ -0,0 +1,46 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.spi.project.support.ant; + +import org.netbeans.spi.project.ant.AntBuildExtenderImplementation; +import org.netbeans.api.project.ant.AntBuildExtender; +import org.netbeans.modules.project.ant.AntBuildExtenderAccessor; + +/** + * Factory class for creation of AntBuildExtender instances + * @author mkleint + * @since org.netbeans.modules.project.ant 1.16 + */ +public final class AntBuildExtenderSupport { + + /** Creates a new instance of AntBuildExtenderSupport */ + private AntBuildExtenderSupport() { + } + + /** + * + * @param implementation + * @return + */ + public static AntBuildExtender createAntExtender(AntBuildExtenderImplementation implementation) { + return AntBuildExtenderAccessor.DEFAULT.createExtender(implementation); + } + +} Index: ant/project/src/org/netbeans/spi/project/support/ant/GeneratedFilesHelper.java =================================================================== RCS file: /cvs/ant/project/src/org/netbeans/spi/project/support/ant/GeneratedFilesHelper.java,v --- ant/project/src/org/netbeans/spi/project/support/ant/GeneratedFilesHelper.java 3 Aug 2006 19:21:06 -0000 1.17 +++ ant/project/src/org/netbeans/spi/project/support/ant/GeneratedFilesHelper.java 6 Apr 2007 13:50:20 -0000 @@ -29,6 +29,7 @@ import java.io.OutputStream; import java.net.URI; import java.net.URL; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -40,17 +41,30 @@ import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.netbeans.api.project.ProjectManager; +import org.netbeans.api.project.ant.AntBuildExtender; +import org.netbeans.api.project.ant.AntBuildExtender.Extension; +import org.netbeans.modules.project.ant.AntBuildExtenderAccessor; import org.netbeans.modules.project.ant.UserQuestionHandler; import org.openide.ErrorManager; import org.openide.filesystems.FileLock; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileSystem; import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; import org.openide.util.Mutex; import org.openide.util.MutexException; import org.openide.util.NbBundle; import org.openide.util.UserQuestionException; import org.openide.util.Utilities; +import org.openide.xml.XMLUtil; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; /** * Helps a project type (re-)generate, and manage the state and versioning of, @@ -160,6 +174,8 @@ /** Project directory. */ private final FileObject dir; + private AntBuildExtender extender; + /** * Create a helper based on the supplied project helper handle. * @param h an Ant-based project helper supplied to the project type provider @@ -168,6 +184,19 @@ this.h = h; dir = h.getProjectDirectory(); } + + /** + * Create a helper based on the supplied project helper handle. The created + * instance is capable of extending the generated files with 3rd party content. + * @param h an Ant-based project helper supplied to the project type provider + * @param ex build extensibility support + * @since org.netbeans.modules.project.ant 1.16 + * + */ + public GeneratedFilesHelper(AntProjectHelper h, AntBuildExtender ex) { + this(h); + extender = ex; + } /** * Create a helper based only on a project directory. @@ -255,7 +284,11 @@ new ByteArrayInputStream(projectXmlData), projectXmlF.toURI().toString()); ByteArrayOutputStream result = new ByteArrayOutputStream(); t.transform(projectXmlSource, new StreamResult(result)); - resultData = result.toByteArray(); + if (BUILD_IMPL_XML_PATH.equals(path)) { + resultData = applyBuildExtensions(result.toByteArray(), extender); + } else { + resultData = result.toByteArray(); + } } catch (TransformerException e) { throw (IOException)new IOException(e.toString()).initCause(e); } @@ -369,6 +402,83 @@ } } + private byte[] applyBuildExtensions(byte[] resultData, AntBuildExtender ext) { + if (ext == null) { + return resultData; + } + try { + ByteArrayInputStream in2 = new ByteArrayInputStream(resultData); + InputSource is = new InputSource(in2); + + Document doc = XMLUtil.parse(is, false, true, null, null); + Element el = doc.getDocumentElement(); + Node firstSubnode = el.getFirstChild(); + //TODO check if first one is text and use it as indentation.. + for (Extension extension : AntBuildExtenderAccessor.DEFAULT.getExtensions(ext)) { + Text after = doc.createTextNode("\n"); + el.insertBefore(after, firstSubnode); + Element imp = createImportElement(AntBuildExtenderAccessor.DEFAULT.getPath(extension), doc); + el.insertBefore(imp, after); + Text before = doc.createTextNode(" "); + el.insertBefore(before, imp); + firstSubnode = before; + NodeList nl = el.getElementsByTagName("target"); + Map> deps = AntBuildExtenderAccessor.DEFAULT.getDependencies(extension); + for (String targetName : deps.keySet()) { + Element targetEl = null; + for (int i = 0; i < nl.getLength(); i++) { + Element elem = (Element)nl.item(i); + String at = elem.getAttribute("name"); + if (at != null && at.equals(targetName)) { + targetEl = elem; + break; + } + } +// System.out.println("target name=" + targetName); +// System.out.println("target elem=" + targetEl); + if (targetEl != null) { + Attr depend = targetEl.getAttributeNode("depends"); + if (depend == null) { + depend = doc.createAttribute("depends"); + depend.setValue(""); + targetEl.setAttributeNode(depend); + } + String oldVal = depend.getValue(); + for (String targ : deps.get(targetName)) { + oldVal = oldVal + "," + targ; + } + if (oldVal.startsWith(",")) { + oldVal = oldVal.substring(1); + } + depend.setValue(oldVal); + } else { + //TODO log?? + } + } + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + XMLUtil.write(doc, out, "UTF-8"); + return out.toByteArray(); + } + catch (IOException ex) { + ex.printStackTrace(); + Exceptions.printStackTrace(ex); + return resultData; + } + catch (SAXException ex) { + ex.printStackTrace(); + Exceptions.printStackTrace(ex); + return resultData; + } + } + + private Element createImportElement(String path, Document doc) { + Element el = doc.createElement("import"); + el.setAttribute("file", path); + return el; + } + /** * Load data from a stream into a buffer. */ Index: ant/project/src/org/netbeans/spi/project/support/ant/package.html =================================================================== RCS file: /cvs/ant/project/src/org/netbeans/spi/project/support/ant/package.html,v --- ant/project/src/org/netbeans/spi/project/support/ant/package.html 30 Jun 2006 18:05:51 -0000 1.3 +++ ant/project/src/org/netbeans/spi/project/support/ant/package.html 6 Apr 2007 13:50:20 -0000 @@ -130,6 +130,10 @@ {@link org.netbeans.spi.project.support.ant.ProjectXmlSavedHook} to be told when to recreate them.

+

To allow 3rd party extensions to build scripts, use {@link org.netbeans.spi.project.ant.AntBuildExtenderImplementation} and +{@link org.netbeans.spi.project.support.ant.AntBuildExtenderSupport}. +

+

{@link org.netbeans.spi.project.support.ant.EditableProperties} is a VCS-friendly alternative to {@link java.util.Properties}. {@link org.netbeans.spi.project.support.ant.PropertyUtils} also provides various Index: ant/project/test/unit/src/org/netbeans/spi/project/support/ant/AntBasedTestUtil.java =================================================================== RCS file: /cvs/ant/project/test/unit/src/org/netbeans/spi/project/support/ant/AntBasedTestUtil.java,v --- ant/project/test/unit/src/org/netbeans/spi/project/support/ant/AntBasedTestUtil.java 27 Mar 2007 00:01:31 -0000 1.21 +++ ant/project/test/unit/src/org/netbeans/spi/project/support/ant/AntBasedTestUtil.java 6 Apr 2007 13:50:21 -0000 @@ -19,6 +19,7 @@ package org.netbeans.spi.project.support.ant; +import org.netbeans.spi.project.ant.AntBuildExtenderImplementation; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; @@ -56,6 +57,7 @@ import org.netbeans.spi.project.AuxiliaryConfiguration; import org.netbeans.api.project.ProjectInformation; import org.netbeans.api.project.ProjectManager; +import org.netbeans.api.project.ant.AntBuildExtender; import org.netbeans.spi.project.ant.AntArtifactProvider; import org.netbeans.spi.queries.CollocationQueryImplementation; import org.openide.filesystems.FileObject; @@ -100,6 +102,9 @@ return new TestAntBasedProjectType(); } + public static AntBasedProjectType testAntBasedProjectType(AntBuildExtenderImplementation extender) { + return new TestAntBasedProjectType(extender); + } /** * You can adjust which artifacts are supplied. */ @@ -108,15 +113,20 @@ } private static final class TestAntBasedProjectType implements AntBasedProjectType { + private AntBuildExtenderImplementation ext; TestAntBasedProjectType() {} + TestAntBasedProjectType(AntBuildExtenderImplementation ext) { + this.ext = ext; + } + public String getType() { return "test"; } public Project createProject(AntProjectHelper helper) throws IOException { - return new TestAntBasedProject(helper); + return new TestAntBasedProject(helper, ext); } public String getPrimaryConfigurationDataElementName(boolean shared) { @@ -136,14 +146,22 @@ private final GeneratedFilesHelper genFilesHelper; private final Lookup l; - TestAntBasedProject(AntProjectHelper helper) throws IOException { + TestAntBasedProject(AntProjectHelper helper, AntBuildExtenderImplementation ext) throws IOException { if (helper.getProjectDirectory().getFileObject("nbproject/broken") != null) { throw new IOException("broken"); } this.helper = helper; AuxiliaryConfiguration aux = helper.createAuxiliaryConfiguration(); refHelper = new ReferenceHelper(helper, aux, helper.getStandardPropertyEvaluator()); - genFilesHelper = new GeneratedFilesHelper(helper); + Object extContent; + if (ext !=null) { + AntBuildExtender e = AntBuildExtenderSupport.createAntExtender(ext); + genFilesHelper = new GeneratedFilesHelper(helper, e); + extContent = e; + } else { + genFilesHelper = new GeneratedFilesHelper(helper); + extContent = new Object(); + } l = Lookups.fixed(new Object[] { new TestInfo(), helper, @@ -166,6 +184,7 @@ } }, "hello", + extContent }); } @@ -218,6 +237,7 @@ public void removePropertyChangeListener(PropertyChangeListener listener) {} } + private final class TestAntArtifactProvider implements AntArtifactProviderMutable { Index: ant/project/test/unit/src/org/netbeans/spi/project/support/ant/AntBuildExtenderTest.java =================================================================== RCS file: ant/project/test/unit/src/org/netbeans/spi/project/support/ant/AntBuildExtenderTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ant/project/test/unit/src/org/netbeans/spi/project/support/ant/AntBuildExtenderTest.java 6 Apr 2007 13:50:21 -0000 @@ -0,0 +1,135 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.spi.project.support.ant; + +import org.netbeans.spi.project.ant.AntBuildExtenderImplementation; +import java.util.Collections; +import java.util.List; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectManager; +import org.netbeans.api.project.TestUtil; +import org.netbeans.api.project.ant.AntBuildExtender; +import org.netbeans.junit.NbTestCase; +import org.openide.filesystems.FileObject; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +/** + * Test functionality of AntBuildExtender. + * @author mkleint + */ +public class AntBuildExtenderTest extends NbTestCase { + + public AntBuildExtenderTest(String name) { + super(name); + } + + private FileObject scratch; + private FileObject projdir; + private FileObject extension1; + private ProjectManager pm; + private Project p; + private AntProjectHelper h; + private GeneratedFilesHelper gfh; + private ExtImpl extenderImpl; + + protected void setUp() throws Exception { + super.setUp(); + scratch = TestUtil.makeScratchDir(this); + projdir = scratch.createFolder("proj"); + TestUtil.createFileFromContent(GeneratedFilesHelperTest.class.getResource("data/project.xml"), projdir, "nbproject/project.xml"); + extension1 = TestUtil.createFileFromContent(GeneratedFilesHelperTest.class.getResource("data/extension1.xml"), projdir, "nbproject/extension1.xml"); + extenderImpl = new ExtImpl(); + TestUtil.setLookup(new Object[] { + AntBasedTestUtil.testAntBasedProjectType(extenderImpl), + }); + pm = ProjectManager.getDefault(); + p = pm.findProject(projdir); + extenderImpl.project = p; + h = p.getLookup().lookup(AntProjectHelper.class); + gfh = p.getLookup().lookup(GeneratedFilesHelper.class); + assertNotNull(gfh); + } + + public void testGetExtendableTargets() { + AntBuildExtender instance = p.getLookup().lookup(AntBuildExtender.class); + + List result = instance.getExtendableTargets(); + + assertEquals(1, result.size()); + assertEquals("all", result.get(0)); + } + + public void testAddExtension() { + AntBuildExtender instance = p.getLookup().lookup(AntBuildExtender.class); + instance.addExtension("milos", extension1); + Element el = extenderImpl.newElement; + assertNotNull(el); + NodeList nl = el.getElementsByTagName(AntBuildExtenderImplementation.ELEMENT_EXTENSION); + assertEquals(1, nl.getLength()); + Element extens = (Element) nl.item(0); + assertEquals("milos", extens.getAttribute(AntBuildExtenderImplementation.ATTR_ID)); + assertEquals("extension1.xml", extens.getAttribute(AntBuildExtenderImplementation.ATTR_FILE)); + } + + public void testRemoveExtension() { + AntBuildExtender instance = p.getLookup().lookup(AntBuildExtender.class); + testAddExtension(); + Element el = extenderImpl.newElement; + extenderImpl.oldElement = el; + instance.removeExtension("milos"); + el = extenderImpl.newElement; + assertNotNull(el); + NodeList nl = el.getElementsByTagName(AntBuildExtenderImplementation.ELEMENT_EXTENSION); + assertEquals(0, nl.getLength()); + } + + public void testGetExtension() { + AntBuildExtender instance = p.getLookup().lookup(AntBuildExtender.class); + testAddExtension(); + AntBuildExtender.Extension ext = instance.getExtension("milos"); + assertNotNull(ext); + } + + private class ExtImpl implements AntBuildExtenderImplementation { + Project project; + Element newElement; + Element oldElement; + List targets = Collections.singletonList("all"); + + public List getExtendableTargets() { + return targets; + } + + public void updateBuildExtensionMetadata(Element element) { + newElement = element; + } + + public Element getBuildExtensionMetadata() { + return oldElement; + } + + public Project getOwningProject() { + return project; + } + + } + +} Index: ant/project/test/unit/src/org/netbeans/spi/project/support/ant/GeneratedFilesHelperTest.java =================================================================== RCS file: /cvs/ant/project/test/unit/src/org/netbeans/spi/project/support/ant/GeneratedFilesHelperTest.java,v --- ant/project/test/unit/src/org/netbeans/spi/project/support/ant/GeneratedFilesHelperTest.java 3 Aug 2006 19:21:08 -0000 1.9 +++ ant/project/test/unit/src/org/netbeans/spi/project/support/ant/GeneratedFilesHelperTest.java 6 Apr 2007 13:50:21 -0000 @@ -19,14 +19,18 @@ package org.netbeans.spi.project.support.ant; +import org.netbeans.spi.project.ant.AntBuildExtenderImplementation; import java.io.ByteArrayInputStream; import java.io.File; import java.net.URL; +import java.util.Collections; +import java.util.List; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectManager; import org.netbeans.api.project.TestUtil; import org.netbeans.junit.NbTestCase; import org.netbeans.modules.project.ant.Util; +import org.netbeans.spi.project.AuxiliaryConfiguration; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.Utilities; @@ -46,21 +50,26 @@ private FileObject scratch; private FileObject projdir; + private FileObject extension1; private ProjectManager pm; private Project p; private AntProjectHelper h; private GeneratedFilesHelper gfh; + private ExtImpl extenderImpl; protected void setUp() throws Exception { super.setUp(); scratch = TestUtil.makeScratchDir(this); projdir = scratch.createFolder("proj"); TestUtil.createFileFromContent(GeneratedFilesHelperTest.class.getResource("data/project.xml"), projdir, "nbproject/project.xml"); + extension1 = TestUtil.createFileFromContent(GeneratedFilesHelperTest.class.getResource("data/extension1.xml"), projdir, "nbproject/extension1.xml"); + extenderImpl = new ExtImpl(); TestUtil.setLookup(new Object[] { - AntBasedTestUtil.testAntBasedProjectType(), + AntBasedTestUtil.testAntBasedProjectType(extenderImpl), }); pm = ProjectManager.getDefault(); p = pm.findProject(projdir); + extenderImpl.project = p; h = p.getLookup().lookup(AntProjectHelper.class); gfh = p.getLookup().lookup(GeneratedFilesHelper.class); assertNotNull(gfh); @@ -198,6 +207,36 @@ } assertTrue("generated file has platform line endings", ok); } + } + + private class ExtImpl implements AntBuildExtenderImplementation { + Project project; + Element newElement; + Element oldElement; + + public List getExtendableTargets() { + return Collections.singletonList("all"); + } + + public void updateBuildExtensionMetadata(Element element) { + newElement = element; + } + + public Element getBuildExtensionMetadata() { + Element el = project.getLookup().lookup(AuxiliaryConfiguration.class).getConfigurationFragment(ELEMENT_ROOT, "urn:test:extension", true); + if (el != null) { + NodeList nl = el.getElementsByTagName(AntBuildExtenderImplementation.ELEMENT_ROOT); + if (nl.getLength() == 1) { + return (Element) nl.item(0); + } + } + return null; + } + + public Project getOwningProject() { + return project; + } + } } Index: ant/project/test/unit/src/org/netbeans/spi/project/support/ant/data/extension1.xml =================================================================== RCS file: ant/project/test/unit/src/org/netbeans/spi/project/support/ant/data/extension1.xml --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ant/project/test/unit/src/org/netbeans/spi/project/support/ant/data/extension1.xml 6 Apr 2007 13:50:21 -0000 @@ -0,0 +1,13 @@ + + + + + + +