PageRenderTime 23ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/projects/netbeans-7.3/project.ant/src/org/netbeans/api/project/ant/AntBuildExtender.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 458 lines | 298 code | 34 blank | 126 comment | 54 complexity | 84c9d6aaf8f3c18fcc4039f2e27c575d MD5 | raw file
  1. /*
  2. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  3. *
  4. * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
  5. *
  6. * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
  7. * Other names may be trademarks of their respective owners.
  8. *
  9. * The contents of this file are subject to the terms of either the GNU
  10. * General Public License Version 2 only ("GPL") or the Common
  11. * Development and Distribution License("CDDL") (collectively, the
  12. * "License"). You may not use this file except in compliance with the
  13. * License. You can obtain a copy of the License at
  14. * http://www.netbeans.org/cddl-gplv2.html
  15. * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
  16. * specific language governing permissions and limitations under the
  17. * License. When distributing the software, include this License Header
  18. * Notice in each file and include the License file at
  19. * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
  20. * particular file as subject to the "Classpath" exception as provided
  21. * by Oracle in the GPL Version 2 section of the License file that
  22. * accompanied this code. If applicable, add the following below the
  23. * License Header, with the fields enclosed by brackets [] replaced by
  24. * your own identifying information:
  25. * "Portions Copyrighted [year] [name of copyright owner]"
  26. *
  27. * Contributor(s):
  28. *
  29. * The Original Software is NetBeans. The Initial Developer of the Original
  30. * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
  31. * Microsystems, Inc. All Rights Reserved.
  32. *
  33. * If you wish your version of this file to be governed by only the CDDL
  34. * or only the GPL Version 2, indicate your decision by adding
  35. * "[Contributor] elects to include this software in this distribution
  36. * under the [CDDL or GPL Version 2] license." If you do not indicate a
  37. * single choice of license, a recipient has the option to distribute
  38. * your version of this file under either the CDDL, the GPL Version 2 or
  39. * to extend the choice of license to its licensees as provided above.
  40. * However, if you add GPL Version 2 code and therefore, elected the GPL
  41. * Version 2 license, then the option applies only if the new code is
  42. * made subject to such option by the copyright holder.
  43. */
  44. package org.netbeans.api.project.ant;
  45. import java.io.IOException;
  46. import java.io.InputStream;
  47. import java.io.OutputStream;
  48. import java.util.ArrayList;
  49. import java.util.Arrays;
  50. import java.util.Collection;
  51. import java.util.Collections;
  52. import java.util.HashMap;
  53. import java.util.HashSet;
  54. import java.util.List;
  55. import java.util.Map;
  56. import java.util.Set;
  57. import java.util.TreeMap;
  58. import java.util.TreeSet;
  59. import java.util.logging.Level;
  60. import java.util.logging.Logger;
  61. import javax.xml.parsers.DocumentBuilder;
  62. import javax.xml.parsers.DocumentBuilderFactory;
  63. import javax.xml.parsers.ParserConfigurationException;
  64. import org.netbeans.api.project.FileOwnerQuery;
  65. import org.netbeans.api.project.ProjectManager;
  66. import org.netbeans.api.project.ProjectUtils;
  67. import org.netbeans.api.project.libraries.Library;
  68. import org.netbeans.modules.project.ant.AntBuildExtenderAccessor;
  69. import org.netbeans.spi.project.AuxiliaryConfiguration;
  70. import org.netbeans.spi.project.ant.AntBuildExtenderImplementation;
  71. import org.netbeans.spi.project.support.ant.AntProjectHelper;
  72. import org.netbeans.spi.project.support.ant.EditableProperties;
  73. import org.netbeans.spi.project.support.ant.ReferenceHelper;
  74. import org.openide.filesystems.FileObject;
  75. import org.openide.filesystems.FileUtil;
  76. import org.openide.util.Exceptions;
  77. import org.openide.util.Mutex;
  78. import org.openide.util.MutexException;
  79. import org.openide.util.Parameters;
  80. import org.w3c.dom.Document;
  81. import org.w3c.dom.Element;
  82. import org.w3c.dom.NodeList;
  83. /**
  84. * Allows extending the project's build script with 3rd party additions.
  85. * Check the Project's lookup to see if the feature is supported by a given Ant project type.
  86. * Typical usage:
  87. * <ul>
  88. * <li>Lookup the instance of AntBuildExtender in the project at hand</li>
  89. * <li>Create the external build script file with your targets and configuration</li>
  90. * <li>Use the AntBuildExtender to wire your script and targets into the main build lifecycle</li>
  91. * <li>Call {@link org.netbeans.api.project.ProjectManager#saveProject} to persist the changes and
  92. * regenerate the main build script</li>
  93. * </ul>
  94. *
  95. * Please note that it's easy to break the build script functionality and any script extensions
  96. * shall be done with care. A few rules to follow:
  97. * <ul>
  98. * <li>Pick a reasonably unique extension id</li>
  99. * <li>Prefix target names and properties you define in your extension with the extension id to prevent clashes.</li>
  100. * </ul>
  101. * @author mkleint
  102. * @since org.netbeans.modules.project.ant 1.16
  103. */
  104. public final class AntBuildExtender {
  105. private HashMap<String, Extension> extensions;
  106. private AntBuildExtenderImplementation implementation;
  107. private ReferenceHelper refHelper;
  108. public static final String ANT_CUSTOMTASKS_LIBS_PROPNAME = "ant.customtasks.libs";
  109. static {
  110. AntBuildExtenderAccessorImpl.createAccesor();
  111. }
  112. AntBuildExtender(AntBuildExtenderImplementation implementation) {
  113. this.implementation = implementation;
  114. }
  115. AntBuildExtender(AntBuildExtenderImplementation implementation, ReferenceHelper refHlpr) {
  116. this.implementation = implementation;
  117. this.refHelper = refHlpr;
  118. }
  119. /**
  120. * Get a list of target names in the main build script that are allowed to be
  121. * extended by adding the "depends" attribute definition to them.
  122. * @return list of target names
  123. */
  124. public List<String> getExtensibleTargets() {
  125. List<String> targets = new ArrayList<String>();
  126. targets.addAll(implementation.getExtensibleTargets());
  127. targets = Collections.unmodifiableList(targets);
  128. return targets;
  129. }
  130. /**
  131. * Adds a new build script extension.
  132. * @param id identification of the extension
  133. * @param extensionXml fileobject referencing the build script for the extension,
  134. * needs to be located in nbproject directory or below.
  135. * @return the newly created extension.
  136. */
  137. public synchronized Extension addExtension(String id, FileObject extensionXml) {
  138. assert extensionXml != null;
  139. assert extensionXml.isValid() && extensionXml.isData();
  140. //TODO assert the owner is the same as the owner of this instance of entender.
  141. assert FileOwnerQuery.getOwner(extensionXml) == implementation.getOwningProject();
  142. FileObject nbproj = implementation.getOwningProject().getProjectDirectory().getFileObject(AntProjectHelper.PROJECT_XML_PATH).getParent();
  143. assert FileUtil.isParentOf(nbproj, extensionXml);
  144. if (extensions == null) {
  145. readProjectMetadata();
  146. }
  147. if (extensions.get(id) != null) {
  148. throw new IllegalStateException("Extension with id '" + id + "' already exists.");
  149. }
  150. Extension ex = new Extension(id, extensionXml, FileUtil.getRelativePath(nbproj, extensionXml));
  151. extensions.put(id, ex);
  152. updateProjectMetadata();
  153. return ex;
  154. }
  155. /**
  156. * Remove an existing build script extension. Make sure to remove the extension's script file
  157. * before/after removing the extension.
  158. * @param id identification of the extension
  159. */
  160. public synchronized void removeExtension(String id) {
  161. if (extensions == null) {
  162. readProjectMetadata();
  163. }
  164. if (extensions.get(id) == null) {
  165. // oh well, just ignore.
  166. return;
  167. }
  168. extensions.remove(id);
  169. updateProjectMetadata();
  170. }
  171. /**
  172. * Get an extension by the id.
  173. * @param id identification token
  174. * @return Extention with the given id or null if not found.
  175. */
  176. public synchronized Extension getExtension(String id) {
  177. if (extensions == null) {
  178. readProjectMetadata();
  179. }
  180. return extensions.get(id);
  181. }
  182. synchronized Set<Extension> getExtensions() {
  183. Set<Extension> ext = new HashSet<Extension>();
  184. if (extensions == null) {
  185. readProjectMetadata();
  186. }
  187. ext.addAll(extensions.values());
  188. return ext;
  189. }
  190. /**
  191. * Copies global library to the shared library folder of the project if the
  192. * project is sharable and adds library name to the list of libraries needed
  193. * to run Ant script. In the case of non-sharable project only the name is
  194. * added to the list and the library is copied when the project is made sharable.
  195. *
  196. * @param library global library to be copied to shared library folder of the project
  197. * @throws java.io.IOException exception thrown when properties cannot be loaded or saved
  198. * @since org.netbeans.modules.project.ant/1 1.23
  199. */
  200. public void addLibrary(Library library) throws IOException {
  201. Parameters.notNull("library", library);
  202. setValueOfProperty(ANT_CUSTOMTASKS_LIBS_PROPNAME, library.getName(), true);
  203. if (refHelper != null && refHelper.getProjectLibraryManager() != null) {
  204. if (refHelper.getProjectLibraryManager().getLibrary(library.getName()) == null) {
  205. try {
  206. refHelper.copyLibrary(library);
  207. } catch (IOException ex) {
  208. Exceptions.printStackTrace(ex);
  209. }
  210. }
  211. }
  212. }
  213. /**
  214. * Removes library name from the list of libraries needed to run Ant script
  215. *
  216. * @param library either global or shared library to be removed from list of
  217. * libraries needed for running Ant script; cannot be null
  218. * @throws java.io.IOException exception thrown when properties cannot be loaded or saved
  219. * @since org.netbeans.modules.project.ant/1 1.23
  220. */
  221. public void removeLibrary(Library library) throws IOException {
  222. Parameters.notNull("library", library);
  223. setValueOfProperty(ANT_CUSTOMTASKS_LIBS_PROPNAME, library.getName(), false);
  224. }
  225. private void setValueOfProperty(final String propName, final String value, final boolean add) throws IOException {
  226. try {
  227. final FileObject projPropsFO = implementation.getOwningProject().getProjectDirectory().getFileObject(AntProjectHelper.PROJECT_PROPERTIES_PATH);
  228. final InputStream is = projPropsFO.getInputStream();
  229. ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void>() {
  230. public @Override Void run() throws Exception {
  231. EditableProperties editableProps = new EditableProperties(true);
  232. try {
  233. editableProps.load(is);
  234. } finally {
  235. if (is != null) {
  236. is.close();
  237. }
  238. }
  239. String libIDs[] = new String[0];
  240. String savedPropVal = editableProps.getProperty(propName);
  241. if (savedPropVal != null) {
  242. libIDs = savedPropVal.split(",");
  243. }
  244. Set<String> libIDSet = new TreeSet<String>(Arrays.asList(libIDs));
  245. if (add) {
  246. libIDSet.add(value);
  247. } else {
  248. libIDSet.remove(value);
  249. }
  250. String newLibIDs[] = libIDSet.toArray(new String[libIDSet.size()]);
  251. StringBuilder propValue = new StringBuilder();
  252. for (String newLibID : newLibIDs) {
  253. propValue.append(newLibID);
  254. propValue.append(",");
  255. }
  256. propValue.delete(propValue.length() - 1, propValue.length());
  257. editableProps.setProperty(propName, propValue.toString());
  258. OutputStream os = projPropsFO.getOutputStream();
  259. try {
  260. editableProps.store(os);
  261. } finally {
  262. os.close();
  263. }
  264. return null;
  265. }
  266. });
  267. } catch (MutexException mux) {
  268. throw (IOException) mux.getException();
  269. }
  270. }
  271. private static final DocumentBuilder db;
  272. static {
  273. try {
  274. db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
  275. } catch (ParserConfigurationException e) {
  276. throw new AssertionError(e);
  277. }
  278. }
  279. private static Document createNewDocument() {
  280. // #50198: for thread safety, use a separate document.
  281. // Using XMLUtil.createDocument is much too slow.
  282. synchronized (db) {
  283. return db.newDocument();
  284. }
  285. }
  286. private void updateProjectMetadata() {
  287. Document doc = createNewDocument();
  288. Element root = doc.createElementNS(AntBuildExtenderAccessor.AUX_NAMESPACE, AntBuildExtenderAccessor.ELEMENT_ROOT);
  289. if (extensions != null) {
  290. FileObject nbproj = implementation.getOwningProject().getProjectDirectory().getFileObject(AntProjectHelper.PROJECT_XML_PATH).getParent();
  291. for (Extension ext : extensions.values()) {
  292. Element child = doc.createElementNS(AntBuildExtenderAccessor.AUX_NAMESPACE, AntBuildExtenderAccessor.ELEMENT_EXTENSION);
  293. child.setAttribute(AntBuildExtenderAccessor.ATTR_ID, ext.id);
  294. String relPath = FileUtil.getRelativePath(nbproj, ext.file);
  295. assert relPath != null;
  296. child.setAttribute(AntBuildExtenderAccessor.ATTR_FILE, relPath);
  297. root.appendChild(child);
  298. for (String target : ext.dependencies.keySet()) {
  299. for (String depTarget : ext.dependencies.get(target)) {
  300. Element dep = doc.createElementNS(AntBuildExtenderAccessor.AUX_NAMESPACE, AntBuildExtenderAccessor.ELEMENT_DEPENDENCY);
  301. dep.setAttribute(AntBuildExtenderAccessor.ATTR_TARGET, target);
  302. dep.setAttribute(AntBuildExtenderAccessor.ATTR_DEPENDSON, depTarget);
  303. child.appendChild(dep);
  304. }
  305. }
  306. }
  307. }
  308. AuxiliaryConfiguration config = ProjectUtils.getAuxiliaryConfiguration(implementation.getOwningProject());
  309. config.putConfigurationFragment(root, true);
  310. }
  311. private void readProjectMetadata() {
  312. AuxiliaryConfiguration config = ProjectUtils.getAuxiliaryConfiguration(implementation.getOwningProject());
  313. Element cfgEl = config.getConfigurationFragment(AntBuildExtenderAccessor.ELEMENT_ROOT, AntBuildExtenderAccessor.AUX_NAMESPACE, true);
  314. extensions = new HashMap<String, Extension>();
  315. FileObject projectXMLFO = implementation.getOwningProject().getProjectDirectory().getFileObject(AntProjectHelper.PROJECT_XML_PATH);
  316. if (projectXMLFO == null) { // #192915
  317. return;
  318. }
  319. FileObject nbproj = projectXMLFO.getParent();
  320. if (cfgEl != null) {
  321. String namespace = cfgEl.getNamespaceURI();
  322. NodeList roots = cfgEl.getElementsByTagNameNS(namespace, AntBuildExtenderAccessor.ELEMENT_EXTENSION);
  323. for (int i=0; i <roots.getLength(); i++) {
  324. Element root = (Element) roots.item(i);
  325. String id = root.getAttribute(AntBuildExtenderAccessor.ATTR_ID);
  326. assert id.length() > 0 : "Illegal project.xml";
  327. String value = root.getAttribute(AntBuildExtenderAccessor.ATTR_FILE);
  328. FileObject script = nbproj.getFileObject(value);
  329. if (script == null) {
  330. //#144658 avoid assert here, for sake of manually edited project files..
  331. Logger.getLogger(AntBuildExtender.class.getName()).log(Level.SEVERE, "Missing file {0} for build script extension {1}. The extension is skipped.", new Object[] {value, id});
  332. continue;
  333. }
  334. Extension ext = new Extension(id, script, value);
  335. extensions.put(id, ext);
  336. NodeList deps = root.getElementsByTagNameNS(namespace, AntBuildExtenderAccessor.ELEMENT_DEPENDENCY);
  337. for (int j = 0; j < deps.getLength(); j++) {
  338. Element dep = (Element)deps.item(j);
  339. String target = dep.getAttribute(AntBuildExtenderAccessor.ATTR_TARGET);
  340. String dependsOn = dep.getAttribute(AntBuildExtenderAccessor.ATTR_DEPENDSON);
  341. assert target != null;
  342. assert dependsOn != null;
  343. ext.loadDependency(target, dependsOn);
  344. }
  345. }
  346. }
  347. }
  348. /**
  349. * Describes and allows to manipulate the build script extension and it's links to the main build script
  350. * of the project.
  351. */
  352. public final class Extension {
  353. String id;
  354. FileObject file;
  355. String path;
  356. TreeMap<String, Collection<String>> dependencies;
  357. Extension(String id, FileObject script, String relPath) {
  358. this.id = id;
  359. file = script;
  360. path = relPath;
  361. dependencies = new TreeMap<String, Collection<String>>();
  362. }
  363. String getPath() {
  364. return path;
  365. }
  366. /**
  367. * Add a dependency of a main build script target on the target in the extension's script.
  368. * @param mainBuildTarget name of target in the main build script (see {@link org.netbeans.api.project.ant.AntBuildExtender#getExtensibleTargets})
  369. * @param extensionTarget name of target in the extension script
  370. */
  371. public void addDependency(String mainBuildTarget, String extensionTarget) {
  372. assert implementation.getExtensibleTargets().contains(mainBuildTarget) :
  373. "The target '" + mainBuildTarget + "' is not designated by the project type as extensible.";
  374. synchronized (this) {
  375. if (loadDependency(mainBuildTarget, extensionTarget)) {
  376. updateProjectMetadata();
  377. }
  378. }
  379. }
  380. private synchronized boolean loadDependency(String mainBuildTarget, String extensionTarget) {
  381. Collection<String> tars = dependencies.get(mainBuildTarget);
  382. boolean changed = false;
  383. if (tars == null) {
  384. tars = new ArrayList<String>();
  385. dependencies.put(mainBuildTarget, tars);
  386. changed = true;
  387. }
  388. if (!tars.contains(extensionTarget)) {
  389. tars.add(extensionTarget);
  390. changed = true;
  391. } else {
  392. //log?
  393. }
  394. return changed;
  395. }
  396. /**
  397. * Remove a dependency of a main build script target on the target in the extension's script.
  398. *
  399. * @param mainBuildTarget name of target in the main build script (see {@link org.netbeans.api.project.ant.AntBuildExtender#getExtensibleTargets})
  400. * @param extensionTarget name of target in the extension script
  401. */
  402. public void removeDependency(String mainBuildTarget, String extensionTarget) {
  403. Collection<String> str = dependencies.get(mainBuildTarget);
  404. if (str != null) {
  405. if (str.remove(extensionTarget)) {
  406. updateProjectMetadata();
  407. }
  408. } else {
  409. //oh well, just ignore, nothing to update anyway..
  410. }
  411. }
  412. Map<String, Collection<String>> getDependencies() {
  413. TreeMap<String, Collection<String>> toRet = new TreeMap<String, Collection<String>>();
  414. synchronized (this) {
  415. for (String str : dependencies.keySet()) {
  416. ArrayList<String> col = new ArrayList<String>();
  417. col.addAll(dependencies.get(str));
  418. toRet.put(str, col);
  419. }
  420. }
  421. return toRet;
  422. }
  423. }
  424. }