/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2010 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]" * * Contributor(s): * * 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. * * 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. */ package org.netbeans.modules.ruby.spi.project.support.rake; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectManager; import org.netbeans.api.project.ProjectUtils; import org.netbeans.modules.ruby.api.project.rake.RakeArtifact; import org.netbeans.modules.ruby.api.project.rake.RakeArtifactQuery; import org.netbeans.api.queries.CollocationQuery; import org.netbeans.modules.ruby.modules.project.rake.RakeBasedProjectFactorySingleton; import org.netbeans.spi.project.AuxiliaryConfiguration; import org.netbeans.spi.project.SubprojectProvider; import org.openide.ErrorManager; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.EditableProperties; import org.openide.util.Mutex; import org.openide.util.NbCollections; import org.openide.xml.XMLUtil; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; // XXX need a method to update non-key data in references e.g. during projectOpened() /** * Helps manage inter-project references. * Normally you would create an instance of this object and keep it in your * project object in order to support {@link SubprojectProvider} and various * operations that change settings which might refer to build artifacts from * other projects: e.g. when changing the classpath for a Java-based project * you would want to use this helper to scan potential classpath entries for * JARs coming from other projects that you would like to be able to build * as dependencies before your project is built. * <p> * You probably only need the higher-level methods such as {@link #addReference} * and {@link #removeReference(String,String)}; the lower-level methods such as {@link #addRawReference} * are provided for completeness, but typical client code should not need them. * <p> * Only deals with references needed to support build artifacts coming from * foreign projects. If for some reason you wish to store other kinds of * references to foreign projects, you do not need this class; just store * them however you wish, and be sure to create an appropriate {@link SubprojectProvider}. * <p> * Modification methods (add, remove) mark the project as modified but do not save it. * @author Jesse Glick */ public final class ReferenceHelper { private static final Logger LOGGER = Logger.getLogger(ReferenceHelper.class.getName()); /** * XML element name used to store references in <code>project.xml</code>. */ static final String REFS_NAME = "references"; // NOI18N /** * XML element name used to store one reference in <code>project.xml</code>. */ static final String REF_NAME = "reference"; // NOI18N /** * XML namespace used to store references in <code>project.xml</code>. */ static final String REFS_NS = "http://www.netbeans.org/ns/rake-project-references/1"; // NOI18N /** * Newer version of {@link #REFS_NS} supporting Properties and with changed semantics of <script>. */ static final String REFS_NS2 = "http://www.netbeans.org/ns/rake-project-references/2"; // NOI18N /** Set of property names which values can be used as additional base * directories. */ private Set<String> extraBaseDirectories = new HashSet<String>(); private final RakeProjectHelper h; final PropertyEvaluator eval; private final AuxiliaryConfiguration aux; /** * Create a new reference helper. * It needs an {@link RakeProjectHelper} object in order to update references * in <code>project.xml</code>, * as well as set project or private properties referring to the locations * of foreign projects on disk. * <p> * The property evaluator may be used in {@link #getForeignFileReferenceAsArtifact}, * {@link ReferenceHelper.RawReference#toRakeArtifact}, or * {@link #createSubprojectProvider}. Typically this would * be {@link RakeProjectHelper#getStandardPropertyEvaluator}. You can substitute * a custom evaluator but be warned that this helper class assumes that * {@link RakeProjectHelper#PROJECT_PROPERTIES_PATH} and {@link RakeProjectHelper#PRIVATE_PROPERTIES_PATH} * have their customary meanings; specifically that they are both used when evaluating * properties (such as the location of a foreign project) and that private properties * can override public properties. * @param helper an Ant project helper object representing this project's configuration * @param aux an auxiliary configuration provider needed to store references * @param eval a property evaluator */ public ReferenceHelper(RakeProjectHelper helper, AuxiliaryConfiguration aux, PropertyEvaluator eval) { h = helper; this.aux = aux; this.eval = eval; } /** * Load <references> from project.xml. * @return can return null if there are no references stored yet */ private Element loadReferences() { assert ProjectManager.mutex().isReadAccess() || ProjectManager.mutex().isWriteAccess(); Element references = aux.getConfigurationFragment(REFS_NAME, REFS_NS2, true); if (references == null) { references = aux.getConfigurationFragment(REFS_NAME, REFS_NS, true); } return references; } /** * Store <references> to project.xml (i.e. to memory and mark project modified). */ private void storeReferences(Element references) { assert ProjectManager.mutex().isWriteAccess(); assert references != null && references.getLocalName().equals(REFS_NAME) && (REFS_NS.equals(references.getNamespaceURI()) || REFS_NS2.equals(references.getNamespaceURI())); aux.putConfigurationFragment(references, true); } private void removeOldReferences() { assert ProjectManager.mutex().isWriteAccess(); aux.removeConfigurationFragment(REFS_NAME, REFS_NS, true); } /** * Add a reference to an artifact coming from a foreign project. * <p> * For more info see {@link #addReference(RakeArtifact, URI)}. * @param artifact the artifact to add * @return true if a reference or some property was actually added or modified, * false if everything already existed and was not modified * @throws IllegalArgumentException if the artifact is not associated with a project * @deprecated to add reference use {@link #addReference(RakeArtifact, URI)}; * to check whether reference exist or not use {@link #isReferenced(RakeArtifact, URI)}. * This method creates reference for the first artifact location only. */ @Deprecated public boolean addReference(final RakeArtifact artifact) throws IllegalArgumentException { Object ret[] = addReference0(artifact, artifact.getArtifactLocations()[0]); return ((Boolean)ret[0]).booleanValue(); } // @return array of two elements: [Boolean - any modification, String - reference] private Object[] addReference0(final RakeArtifact artifact, final URI location) throws IllegalArgumentException { return ProjectManager.mutex().writeAccess(new Mutex.Action<Object[]>() { public Object[] run() { int index = findLocationIndex(artifact, location); Project forProj = artifact.getProject(); if (forProj == null) { throw new IllegalArgumentException("No project associated with " + artifact); // NOI18N } // Set up the raw reference. File forProjDir = FileUtil.toFile(forProj.getProjectDirectory()); assert forProjDir != null : forProj.getProjectDirectory(); String projName = getUsableReferenceID(ProjectUtils.getInformation(forProj).getName()); String forProjName = findReferenceID(projName, "project.", forProjDir.getAbsolutePath()); if (forProjName == null) { forProjName = generateUniqueID(projName, "project.", forProjDir.getAbsolutePath()); } RawReference ref; File scriptFile = artifact.getScriptLocation(); if (canUseVersion10(artifact, forProjDir)) { String rel = PropertyUtils.relativizeFile(forProjDir, scriptFile); URI scriptLocation; try { scriptLocation = new URI(null, null, rel, null); } catch (URISyntaxException ex) { scriptLocation = forProjDir.toURI().relativize(scriptFile.toURI()); } ref = new RawReference(forProjName, artifact.getType(), scriptLocation, artifact.getTargetName(), artifact.getCleanTargetName(), artifact.getID()); } else { String scriptLocation; if (scriptFile.getAbsolutePath().startsWith(forProjDir.getAbsolutePath())) { String rel = PropertyUtils.relativizeFile(forProjDir, scriptFile); assert rel != null : "Relativization must succeed for files: "+forProjDir+ " "+scriptFile; scriptLocation = "${project."+forProjName+"}/"+rel; } else { scriptLocation = "build.script.reference." + forProjName; setPathProperty(forProjDir, scriptFile, scriptLocation); scriptLocation = "${"+scriptLocation+"}"; } ref = new RawReference(forProjName, artifact.getType(), scriptLocation, artifact.getTargetName(), artifact.getCleanTargetName(), artifact.getID(), artifact.getProperties()); } boolean success = addRawReference0(ref); // Set up ${project.whatever}. FileObject myProjDirFO = RakeBasedProjectFactorySingleton.getProjectFor(h).getProjectDirectory(); File myProjDir = FileUtil.toFile(myProjDirFO); if (setPathProperty(myProjDir, forProjDir, "project." + forProjName)) { success = true; } // Set up ${reference.whatever.whatever}. String propertiesFile; String forProjPathProp = "project." + forProjName; // NOI18N URI artFile = location; String refPath; if (artFile.isAbsolute()) { refPath = new File(artFile).getAbsolutePath(); propertiesFile = RakeProjectHelper.PRIVATE_PROPERTIES_PATH; } else { refPath = "${" + forProjPathProp + "}/" + artFile.getPath(); // NOI18N propertiesFile = RakeProjectHelper.PROJECT_PROPERTIES_PATH; } EditableProperties props = h.getProperties(propertiesFile); String refPathProp = "reference." + forProjName + '.' + getUsableReferenceID(artifact.getID()); // NOI18N if (index > 0) { refPathProp += "."+index; } if (!refPath.equals(props.getProperty(refPathProp))) { props.put(refPathProp, refPath); h.putProperties(propertiesFile, props); success = true; } return new Object[] {success, "${" + refPathProp + "}"}; // NOI18N } }); } private int findLocationIndex(final RakeArtifact artifact, final URI location) throws IllegalArgumentException { if (location == null) { throw new IllegalArgumentException("location cannot be null"); } URI uris[] = artifact.getArtifactLocations(); for (int i=0; i<uris.length; i++) { if (uris[i].equals(location)) { return i; } } throw new IllegalArgumentException("location ("+location+") must be in RakeArtifact's locations ("+artifact+")"); } /** * Test whether the artifact can be stored as /1 artifact or not. */ private static boolean canUseVersion10(RakeArtifact aa, File projectDirectory) { // is there multiple outputs? if (aa.getArtifactLocations().length > 1) { return false; } // has some properties? if (aa.getProperties().keySet().size() > 0) { return false; } // does Ant script lies under project directory? if (!aa.getScriptLocation().getAbsolutePath().startsWith(projectDirectory.getAbsolutePath())) { return false; } return true; } /** * Helper method which checks collocation status of two files and based on * that it will in private or project properties file set up property with * the given name and with absolute or relative path value. * @return was there any change or not */ private boolean setPathProperty(File base, File path, String propertyName) { String[] values; String[] propertiesFiles; String relativePath = relativizeFileToExtraBaseFolders(path); // try relativize against external base dirs if (relativePath != null) { propertiesFiles = new String[] { RakeProjectHelper.PROJECT_PROPERTIES_PATH }; values = new String[] { relativePath }; } else if (CollocationQuery.areCollocated(base, path)) { // Fine, using a relative path to subproject. relativePath = PropertyUtils.relativizeFile(base, path); assert relativePath != null : "These dirs are not really collocated: " + base + " & " + path; values = new String[] { relativePath, path.getAbsolutePath() }; propertiesFiles = new String[] { RakeProjectHelper.PROJECT_PROPERTIES_PATH, RakeProjectHelper.PRIVATE_PROPERTIES_PATH, }; } else { // use an absolute path. // mkleint: when the AlwaysRelativeCollocationQueryImplementation gets removed // this code gets called more frequently // to get the previous behaviour replace CollocationQuery.areCollocated(base, path) // with PropertyUtils.relativizeFile(base, path) != null propertiesFiles = new String[] { RakeProjectHelper.PRIVATE_PROPERTIES_PATH }; values = new String[] { path.getAbsolutePath() }; } boolean metadataChanged = false; for (int i=0; i<propertiesFiles.length; i++) { EditableProperties props = h.getProperties(propertiesFiles[i]); if (!values[i].equals(props.getProperty(propertyName))) { props.put(propertyName, values[i]); h.putProperties(propertiesFiles[i], props); metadataChanged = true; } } if (propertiesFiles.length == 1) { // check presence of this property in opposite property file and // remove it if necessary String propertiesFile = (propertiesFiles[0] == RakeProjectHelper.PROJECT_PROPERTIES_PATH ? RakeProjectHelper.PRIVATE_PROPERTIES_PATH : RakeProjectHelper.PROJECT_PROPERTIES_PATH); EditableProperties props = h.getProperties(propertiesFile); if (props.remove(propertyName) != null) { h.putProperties(propertiesFile, props); } } return metadataChanged; } /** * Add a reference to an artifact's location coming from a foreign project. * <p> * Records the name of the foreign project. * Normally the foreign project name is that project's code name, * but it may be uniquified if that name is already taken to refer * to a different project with the same code name. * <p> * Adds a project property if necessary to refer to its location of the foreign * project - a shared property if the foreign project * is {@link CollocationQuery collocated} with this one, else a private property. * This property is named <samp>project.<i>foreignProjectName</i></samp>. * Example: <samp>project.mylib=../mylib</samp> * <p> * Adds a project property to refer to the artifact's location. * This property is named <samp>reference.<i>foreignProjectName</i>.<i>targetName</i></samp> * and will use <samp>${project.<i>foreignProjectName</i>}</samp> and be a shared * property - unless the artifact location is an absolute URI, in which case the property * will also be private. * Example: <samp>reference.mylib.jar=${project.mylib}/dist/mylib.jar</samp> * <p> * Also records the artifact type, (relative) script path, and build and * clean target names. * <p> * If the reference already exists (keyed by foreign project object * and target name), nothing is done, unless some other field (script location, * clean target name, or artifact type) needed to be updated, in which case * the new information replaces the old. Similarly, the artifact location * property is updated if necessary. * <p> * Acquires write access. * @param artifact the artifact to add * @param location the artifact's location to create reference to * @return name of reference which was created or already existed * @throws IllegalArgumentException if the artifact is not associated with a project * or if the location is not artifact's location * @since 1.5 */ public String addReference(final RakeArtifact artifact, URI location) throws IllegalArgumentException { Object ret[] = addReference0(artifact, location); return (String)ret[1]; } /** * Tests whether reference for artifact's location was already created by * {@link #addReference(RakeArtifact, URI)} for this project or not. This * method returns false also in case when reference exist but needs to be * updated. * <p> * Acquires read access. * @param artifact the artifact to add * @param location the artifact's location to create reference to * @return true if already referenced * @throws IllegalArgumentException if the artifact is not associated with a project * or if the location is not artifact's location * @since 1.5 */ public boolean isReferenced(final RakeArtifact artifact, final URI location) throws IllegalArgumentException { return ProjectManager.mutex().readAccess(new Mutex.Action<Boolean>() { public Boolean run() { int index = findLocationIndex(artifact, location); Project forProj = artifact.getProject(); if (forProj == null) { throw new IllegalArgumentException("No project associated with " + artifact); // NOI18N } File forProjDir = FileUtil.toFile(forProj.getProjectDirectory()); assert forProjDir != null : forProj.getProjectDirectory(); String projName = getUsableReferenceID(ProjectUtils.getInformation(forProj).getName()); String forProjName = findReferenceID(projName, "project.", forProjDir.getAbsolutePath()); if (forProjName == null) { return false; } RawReference ref = getRawReference(forProjName, getUsableReferenceID(artifact.getID())); if (ref == null) { return false; } File script = h.resolveFile(eval.evaluate(ref.getScriptLocationValue())); if (!artifact.getType().equals(ref.getArtifactType()) || !artifact.getID().equals(ref.getID()) || !artifact.getScriptLocation().equals(script) || !artifact.getProperties().equals(ref.getProperties()) || !artifact.getTargetName().equals(ref.getTargetName()) || !artifact.getCleanTargetName().equals(ref.getCleanTargetName())) { return false; } String reference = "reference." + forProjName + '.' + getUsableReferenceID(artifact.getID()); // NOI18N if (index > 0) { reference += "."+index; } return eval.getProperty(reference) != null; } }); } /** * Add a raw reference to a foreign project artifact. * Does not check if such a project already exists; does not create a project * property to refer to it; does not do any backreference usage notifications. * <p> * If the reference already exists (keyed by foreign project name and target name), * nothing is done, unless some other field (script location, clean target name, * or artifact type) needed to be updated, in which case the new information * replaces the old. * <p> * Note that since {@link RawReference} is just a descriptor, it is not guaranteed * that after adding one {@link #getRawReferences} or {@link #getRawReference} * would return the identical object. * <p> * Acquires write access. * @param ref a raw reference descriptor * @return true if a reference was actually added or modified, * false if it already existed and was not modified */ public boolean addRawReference(final RawReference ref) { return ProjectManager.mutex().writeAccess(new Mutex.Action<Boolean>() { public Boolean run() { try { return addRawReference0(ref); } catch (IllegalArgumentException e) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); return false; } } }); } private boolean addRawReference0(final RawReference ref) throws IllegalArgumentException { Element references = loadReferences(); if (references == null) { references = XMLUtil.createDocument("ignore", null, null, null).createElementNS(ref.getNS(), REFS_NAME); // NOI18N } boolean modified = false; if (references.getNamespaceURI().equals(REFS_NS) && ref.getNS().equals(REFS_NS2)) { // upgrade all references to version /2 here: references = upgradeTo20(references); removeOldReferences(); modified = true; } modified = updateRawReferenceElement(ref, references); if (modified) { storeReferences(references); } return modified; } private Element upgradeTo20(Element references) { Element references20 = XMLUtil.createDocument("ignore", null, null, null).createElementNS(REFS_NS2, REFS_NAME); // NOI18N RawReference rr[] = getRawReferences(references); for (int i=0; i<rr.length; i++) { rr[i].upgrade(); updateRawReferenceElement(rr[i], references20); } return references20; } private static boolean updateRawReferenceElement(RawReference ref, Element references) throws IllegalArgumentException { // Linear search; always keeping references sorted first by foreign project // name, then by target name. Element nextRefEl = null; Iterator<Element> it = XMLUtil.findSubElements(references).iterator(); while (it.hasNext()) { Element testRefEl = it.next(); RawReference testRef = RawReference.create(testRefEl); if (testRef.getForeignProjectName().compareTo(ref.getForeignProjectName()) > 0) { // gone too far, go back nextRefEl = testRefEl; break; } if (testRef.getForeignProjectName().equals(ref.getForeignProjectName())) { if (testRef.getID().compareTo(ref.getID()) > 0) { // again, gone too far, go back nextRefEl = testRefEl; break; } if (testRef.getID().equals(ref.getID())) { // Key match, check if it needs to be updated. if (testRef.getArtifactType().equals(ref.getArtifactType()) && testRef.getScriptLocationValue().equals(ref.getScriptLocationValue()) && testRef.getProperties().equals(ref.getProperties()) && testRef.getTargetName().equals(ref.getTargetName()) && testRef.getCleanTargetName().equals(ref.getCleanTargetName())) { // Match on other fields. Return without changing anything. return false; } // Something needs updating. // Delete the old ref and set nextRef to the next item in line. references.removeChild(testRefEl); if (it.hasNext()) { nextRefEl = it.next(); } else { nextRefEl = null; } break; } } } // Need to insert a new record before nextRef. Element newRefEl = ref.toXml(references.getNamespaceURI(), references.getOwnerDocument()); // Note: OK if nextRefEl == null, that means insert as last child. references.insertBefore(newRefEl, nextRefEl); return true; } /** * Remove a reference to an artifact coming from a foreign project. * <p> * The property giving the location of the artifact is removed if it existed. * <p> * If this was the last reference to the foreign project, its location * property is removed as well. * <p> * If the reference does not exist, nothing is done. * <p> * Acquires write access. * @param foreignProjectName the local name of the foreign project * (usually its code name) * @param id the ID of the build artifact (usually build target name) * @return true if a reference or some property was actually removed, * false if the reference was not there and no property was removed * @deprecated use {@link #destroyReference} instead; was unused anyway */ @Deprecated public boolean removeReference(final String foreignProjectName, final String id) { return removeReference(foreignProjectName, id, false, null); } /** * Checks whether this is last reference and therefore the artifact can * be removed from project.xml or not */ private boolean isLastReference(String ref) { Object ret[] = findArtifactAndLocation(ref); if (ret[0] == null || ret[1] == null) { return true; } RakeArtifact aa = (RakeArtifact)ret[0]; URI uri = (URI)ret[1]; URI uris[] = aa.getArtifactLocations(); boolean lastReference = true; // are there any other referenced jars or not: for (int i=0; i<uris.length; i++) { if (uris[i].equals(uri)) { continue; } if (isReferenced(aa, uris[i])) { lastReference = false; break; } } return lastReference; } private boolean removeReference(final String foreignProjectName, final String id, final boolean escaped, final String reference) { return ProjectManager.mutex().writeAccess(new Mutex.Action<Boolean>() { public Boolean run() { boolean success = false; try { if (isLastReference("${"+reference+"}")) { success = removeRawReference0(foreignProjectName, id, escaped); } } catch (IllegalArgumentException e) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); return false; } // Note: try to delete obsoleted properties from both project.properties // and private.properties, just in case. String[] PROPS_PATHS = { RakeProjectHelper.PROJECT_PROPERTIES_PATH, RakeProjectHelper.PRIVATE_PROPERTIES_PATH, }; // if raw reference was removed then try to clean also project reference property: if (success) { // Check whether there are any other references using foreignProjectName. // If not, we can delete ${project.foreignProjectName}. RawReference[] refs = new RawReference[0]; Element references = loadReferences(); if (references != null) { refs = getRawReferences(references); } boolean deleteProjProp = true; for (int i = 0; i < refs.length; i++) { if (refs[i].getForeignProjectName().equals(foreignProjectName)) { deleteProjProp = false; break; } } if (deleteProjProp) { String projProp = "project." + foreignProjectName; // NOI18N for (int i = 0; i < PROPS_PATHS.length; i++) { EditableProperties props = h.getProperties(PROPS_PATHS[i]); if (props.containsKey(projProp)) { props.remove(projProp); h.putProperties(PROPS_PATHS[i], props); success = true; } } } } String refProp = reference; if (refProp == null) { refProp = "reference." + foreignProjectName + '.' + getUsableReferenceID(id); // NOI18N } // remove also build script property if exist any: String buildScriptProperty = "build.script.reference." + foreignProjectName; for (String path : PROPS_PATHS) { EditableProperties props = h.getProperties(path); if (props.containsKey(refProp)) { props.remove(refProp); h.putProperties(path, props); success = true; } if (props.containsKey(buildScriptProperty)) { props.remove(buildScriptProperty); h.putProperties(path, props); success = true; } } return success; } }); } /** * Remove reference to a file. * <p> * If the reference does not exist, nothing is done. * <p> * Acquires write access. * @param fileReference file reference as created by * {@link #createForeignFileReference(File, String)} * @return true if the reference was actually removed; otherwise false * @deprecated use {@link #destroyReference} instead; was unused anyway */ @Deprecated public boolean removeReference(final String fileReference) { return removeFileReference(fileReference); } private boolean removeFileReference(final String fileReference) { return ProjectManager.mutex().writeAccess(new Mutex.Action<Boolean>() { public Boolean run() { boolean success = false; // Note: try to delete obsoleted properties from both project.properties // and private.properties, just in case. String[] PROPS_PATHS = { RakeProjectHelper.PROJECT_PROPERTIES_PATH, RakeProjectHelper.PRIVATE_PROPERTIES_PATH, }; String refProp = fileReference; if (refProp.startsWith("${") && refProp.endsWith("}")) { refProp = refProp.substring(2, refProp.length()-1); } for (String path : PROPS_PATHS) { EditableProperties props = h.getProperties(path); if (props.containsKey(refProp)) { props.remove(refProp); h.putProperties(path, props); success = true; } } return success; } }); } /** * Remove a raw reference to an artifact coming from a foreign project. * Does not attempt to manipulate backreferences in the foreign project * nor project properties. * <p> * If the reference does not exist, nothing is done. * <p> * Acquires write access. * @param foreignProjectName the local name of the foreign project * (usually its code name) * @param id the ID of the build artifact (usually build target name) * @return true if a reference was actually removed, false if it was not there */ public boolean removeRawReference(final String foreignProjectName, final String id) { return ProjectManager.mutex().writeAccess(new Mutex.Action<Boolean>() { public Boolean run() { try { return removeRawReference0(foreignProjectName, id, false); } catch (IllegalArgumentException e) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); return false; } } }); } private boolean removeRawReference0(final String foreignProjectName, final String id, boolean escaped) throws IllegalArgumentException { Element references = loadReferences(); if (references == null) { return false; } boolean success = removeRawReferenceElement(foreignProjectName, id, references, escaped); if (success) { storeReferences(references); } return success; } private static boolean removeRawReferenceElement(String foreignProjectName, String id, Element references, boolean escaped) throws IllegalArgumentException { // As with addRawReference, do a linear search through. for (Element testRefEl : XMLUtil.findSubElements(references)) { RawReference testRef = RawReference.create(testRefEl); String refID = testRef.getID(); String refName = testRef.getForeignProjectName(); if (escaped) { refID = getUsableReferenceID(testRef.getID()); refName = getUsableReferenceID(testRef.getForeignProjectName()); } if (refName.compareTo(foreignProjectName) > 0) { // searched past it return false; } if (refName.equals(foreignProjectName)) { if (refID.compareTo(id) > 0) { // again, searched past it return false; } if (refID.equals(id)) { // Key match, remove it. references.removeChild(testRefEl); return true; } } } // Searched through to the end and did not find it. return false; } /** * Get a list of raw references from this project to others. * If necessary, you may use {@link RawReference#toRakeArtifact} to get * live information from each reference, such as its associated project. * <p> * Acquires read access. * @return a (possibly empty) list of raw references from this project */ public RawReference[] getRawReferences() { return ProjectManager.mutex().readAccess(new Mutex.Action<RawReference[]>() { public RawReference[] run() { Element references = loadReferences(); if (references != null) { try { return getRawReferences(references); } catch (IllegalArgumentException e) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } } return new RawReference[0]; } }); } private static RawReference[] getRawReferences(Element references) throws IllegalArgumentException { List<Element> subEls = XMLUtil.findSubElements(references); List<RawReference> refs = new ArrayList<RawReference>(subEls.size()); for (Element subEl : subEls) { refs.add(RawReference.create(subEl)); } return refs.toArray(new RawReference[refs.size()]); } /** * Get a particular raw reference from this project to another. * If necessary, you may use {@link RawReference#toRakeArtifact} to get * live information from each reference, such as its associated project. * <p> * Acquires read access. * @param foreignProjectName the local name of the foreign project * (usually its code name) * @param id the ID of the build artifact (usually the build target name) * @return the specified raw reference from this project, * or null if none such could be found */ public RawReference getRawReference(final String foreignProjectName, final String id) { return getRawReference(foreignProjectName, id, false); } // not private only to allow unit testing RawReference getRawReference(final String foreignProjectName, final String id, final boolean escaped) { return ProjectManager.mutex().readAccess(new Mutex.Action<RawReference>() { public RawReference run() { Element references = loadReferences(); if (references != null) { try { return getRawReference(foreignProjectName, id, references, escaped); } catch (IllegalArgumentException e) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } } return null; } }); } private static RawReference getRawReference(String foreignProjectName, String id, Element references, boolean escaped) throws IllegalArgumentException { for (Element subEl : XMLUtil.findSubElements(references)) { RawReference ref = RawReference.create(subEl); String refID = ref.getID(); String refName = ref.getForeignProjectName(); if (escaped) { refID = getUsableReferenceID(ref.getID()); refName = getUsableReferenceID(ref.getForeignProjectName()); } if (refName.equals(foreignProjectName) && refID.equals(id)) { return ref; } } return null; } /** * Create an Ant-interpretable string referring to a file on disk. * If the file refers to a known Ant artifact according to * {@link RakeArtifactQuery#findArtifactFromFile}, of the expected type * and associated with a particular project, * the behavior is identical to {@link #createForeignFileReference(RakeArtifact)}. * Otherwise, a reference for the file is created. The file path will * be relative in case {@link CollocationQuery#areCollocated} says that * the file is collocated with this project's main directory, else it * will be an absolute path. * <p> * Acquires write access. * @param file a file to refer to (need not currently exist) * @param expectedArtifactType the required {@link RakeArtifact#getType} * @return a string which can refer to that file somehow */ public String createForeignFileReference(final File file, final String expectedArtifactType) { if (!file.equals(FileUtil.normalizeFile(file))) { throw new IllegalArgumentException("Parameter file was not "+ // NOI18N "normalized. Was "+file+" instead of "+FileUtil.normalizeFile(file)); // NOI18N } return ProjectManager.mutex().writeAccess(new Mutex.Action<String>() { public String run() { RakeArtifact art = RakeArtifactQuery.findArtifactFromFile(file); if (art != null && art.getType().equals(expectedArtifactType) && art.getProject() != null) { try { return createForeignFileReference(art); } catch (IllegalArgumentException iae) { throw new AssertionError(iae); } } else { String propertiesFile; String path; File myProjDir = FileUtil.toFile(RakeBasedProjectFactorySingleton.getProjectFor(h).getProjectDirectory()); String fileID = file.getName(); // if the file is folder then add to ID string also parent folder name, // i.e. if external source folder name is "src" the ID will // be a bit more selfdescribing, e.g. project-src in case // of ID for ant/project/src directory. if (file.isDirectory() && file.getParentFile() != null) { fileID = file.getParentFile().getName()+"-"+file.getName(); } fileID = PropertyUtils.getUsablePropertyName(fileID); String prop = findReferenceID(fileID, "file.reference.", file.getAbsolutePath()); // NOI18N if (prop == null) { prop = generateUniqueID(fileID, "file.reference.", file.getAbsolutePath()); // NOI18N } setPathProperty(myProjDir, file, "file.reference." + prop); return "${file.reference." + prop + '}'; // NOI18N } } }); } /** * Test whether file does not lie under an extra base folder and if it does * then return string in form of "${extra.base}/remaining/path"; or null. */ private String relativizeFileToExtraBaseFolders(File f) { File base = FileUtil.toFile(h.getProjectDirectory()); String fileToRelativize = f.getAbsolutePath(); for (String prop : extraBaseDirectories) { String path = eval.getProperty(prop); File extraBase = PropertyUtils.resolveFile(base, path); path = extraBase.getAbsolutePath(); if (!path.endsWith(File.separator)) { path += File.separator; } if (fileToRelativize.startsWith(path)) { return "${"+prop+"}/"+fileToRelativize.substring(path.length()).replace('\\', '/'); // NOI18N } } return null; } /** * Add extra folder which can be used as base directory (in addition to * project base folder) for creating references. Duplicate property names * are not allowed. Any newly created reference to a file lying under an * extra base directory will be based on that property and will be stored in * shared project properties. * <p>Acquires write access. * @param propertyName property name which value is path to folder which * can be used as alternative project's base directory; cannot be null; * property must exist * @throws IllegalArgumentException if propertyName is null or such a * property does not exist * @since 1.4 */ public void addExtraBaseDirectory(final String propertyName) { if (propertyName == null || eval.getProperty(propertyName) == null) { throw new IllegalArgumentException("propertyName is null or such a property does not exist: "+propertyName); // NOI18N } ProjectManager.mutex().writeAccess(new Runnable() { public void run() { if (!extraBaseDirectories.add(propertyName)) { throw new IllegalArgumentException("Already extra base directory property: "+propertyName); // NOI18N } } }); } /** * Remove extra base directory. The base directory property had to be added * by {@link #addExtraBaseDirectory} method call. At the time when this * method is called the property must still exist and must be valid. This * method will replace all references of the extra base directory property * with its current value and if needed it may move such a property from * shared project properties into the private properties. * <p>Acquires write access. * @param propertyName property name which was added by * {@link #addExtraBaseDirectory} method. * @throws IllegalArgumentException if given property is not extra base * directory * @since 1.4 */ public void removeExtraBaseDirectory(final String propertyName) { ProjectManager.mutex().writeAccess(new Runnable() { public void run() { if (!extraBaseDirectories.remove(propertyName)) { throw new IllegalArgumentException("Non-existing extra base directory property: "+propertyName); // NOI18N } // substitute all references of removed extra base folder property with its value String tag = "${"+propertyName+"}"; // NOI18N // was extra base property defined in shared file or not: boolean shared = h.getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH).containsKey(propertyName); String value = eval.getProperty(propertyName); EditableProperties propProj = h.getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH); EditableProperties propPriv = h.getProperties(RakeProjectHelper.PRIVATE_PROPERTIES_PATH); boolean modifiedProj = false; boolean modifiedPriv = false; Iterator<Map.Entry<String,String>> it = propProj.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String,String> entry = it.next(); String val = entry.getValue(); int index; if ((index = val.indexOf(tag)) != -1) { val = val.substring(0, index) +value + val.substring(index+tag.length()); if (shared) { // substitute extra base folder property with its value entry.setValue(val); } else { // move property to private properties file it.remove(); propPriv.put(entry.getKey(), val); modifiedPriv = true; } modifiedProj = true; } } if (modifiedProj) { h.putProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH, propProj); } if (modifiedPriv) { h.putProperties(RakeProjectHelper.PRIVATE_PROPERTIES_PATH, propPriv); } } }); } /** * Find reference ID (e.g. something you can then pass to RawReference * as foreignProjectName) for the given property base name, prefix and path. * @param property project name or jar filename * @param prefix prefix used for reference, i.e. "project." for project * reference or "file.reference." for file reference * @param path absolute filename the reference points to * @return found reference ID or null */ private String findReferenceID(String property, String prefix, String path) { Map<String,String> m = h.getStandardPropertyEvaluator().getProperties(); for (Map.Entry<String,String> e : m.entrySet()) { String key = e.getKey(); if (key.startsWith(prefix+property)) { String v = h.resolvePath(e.getValue()); if (path.equals(v)) { return key.substring(prefix.length()); } } } return null; } /** * Generate unique reference ID for the given property base name, prefix * and path. See also {@link #findReferenceID(String, String, String)}. * @param property project name or jar filename * @param prefix prefix used for reference, i.e. "project." for project * reference or "file.reference." for file reference * @param path absolute filename the reference points to * @return generated unique reference ID */ private String generateUniqueID(String property, String prefix, String value) { PropertyEvaluator pev = h.getStandardPropertyEvaluator(); if (pev.getProperty(prefix+property) == null) { return property; } int i = 1; while (pev.getProperty(prefix+property+"-"+i) != null) { i++; } return property+"-"+i; } /** * Create an Ant-interpretable string referring to a known build artifact file. * Simply calls {@link #addReference} and returns an Ant string which will * refer to that artifact correctly. * <p> * Acquires write access. * @param artifact a known build artifact to refer to * @return a string which can refer to that artifact file somehow * @throws IllegalArgumentException if the artifact is not associated with a project * @deprecated use {@link #addReference(RakeArtifact, URI)} instead */ @Deprecated public String createForeignFileReference(RakeArtifact artifact) throws IllegalArgumentException { Object ret[] = addReference0(artifact, artifact.getArtifactLocations()[0]); return (String)ret[1]; } /** * Project reference ID cannot contain dot character. * File reference can. */ private static String getUsableReferenceID(String ID) { return PropertyUtils.getUsablePropertyName(ID).replace('.', '_'); } private static final Pattern FOREIGN_FILE_REFERENCE = Pattern.compile("\\$\\{reference\\.([^.${}]+)\\.([^.${}]+)\\.([\\d&&[^.${}]]+)\\}"); // NOI18N private static final Pattern FOREIGN_FILE_REFERENCE_OLD = Pattern.compile("\\$\\{reference\\.([^.${}]+)\\.([^.${}]+)\\}"); // NOI18N private static final Pattern FOREIGN_PLAIN_FILE_REFERENCE = Pattern.compile("\\$\\{file\\.reference\\.([^${}]+)\\}"); // NOI18N /** * Try to find an <code>RakeArtifact</code> object corresponding to a given * foreign file reference. * If the supplied string is not a recognized reference to a build * artifact, returns null. * <p>Acquires read access. * @param reference a reference string as present in an Ant property * @return a corresponding Ant artifact object if there is one, else null * @deprecated use {@link #findArtifactAndLocation} instead */ @Deprecated public RakeArtifact getForeignFileReferenceAsArtifact(final String reference) { Object ret[] = findArtifactAndLocation(reference); return (RakeArtifact)ret[0]; } /** * Try to find an <code>RakeArtifact</code> object and location corresponding * to a given reference. If the supplied string is not a recognized * reference to a build artifact, returns null. * <p> * Acquires read access. * @param reference a reference string as present in an Ant property and as * created by {@link #addReference(RakeArtifact, URI)} * @return always returns array of two items. The items may be null. First * one is instance of RakeArtifact and second is instance of URI and is * RakeArtifact's location * @since 1.5 */ public Object[] findArtifactAndLocation(final String reference) { return ProjectManager.mutex().readAccess(new Mutex.Action<Object[]>() { public Object[] run() { RakeArtifact aa = null; Matcher m = FOREIGN_FILE_REFERENCE.matcher(reference); boolean matches = m.matches(); int index = 0; if (!matches) { m = FOREIGN_FILE_REFERENCE_OLD.matcher(reference); matches = m.matches(); } else { try { index = Integer.parseInt(m.group(3)); } catch (NumberFormatException ex) { ErrorManager.getDefault().log(ErrorManager.INFORMATIONAL, "Could not parse reference ("+reference+") for the jar index. " + // NOI18N "Expected number: "+m.group(3)); // NOI18N matches = false; } } if (matches) { RawReference ref = getRawReference(m.group(1), m.group(2), true); if (ref != null) { aa = ref.toRakeArtifact(ReferenceHelper.this); } } if (aa == null) { return new Object[] {null, null}; } if (index >= aa.getArtifactLocations().length) { // #55413: we no longer have that many items...treat it as dead. return new Object[] {null, null}; } URI uri = aa.getArtifactLocations()[index]; return new Object[] {aa, uri}; } }); } /** * Remove a reference to a foreign file from the project. * See {@link #destroyReference} for more information. * @param reference an Ant-interpretable foreign file reference as created e.g. * by {@link #createForeignFileReference(File,String)} or * by {@link #createForeignFileReference(RakeArtifact)} * @deprecated use {@link #destroyReference} instead which does exactly * the same but has more appropriate name */ @Deprecated public void destroyForeignFileReference(String reference) { destroyReference(reference); } /** * Remove a reference to a foreign file from the project. * If the passed string consists of an Ant property reference corresponding to * a known inter-project reference created by * {@link #addReference(RakeArtifact, URI)} or file reference created by * {@link #createForeignFileReference(File, String)}, that reference is removed. * Since this would break any other identical foreign * file references present in the project, you should first confirm that this * reference was the last one of its kind (by string match). * <p> * If the passed string is anything else (i.e. a plain file path, relative or * absolute), nothing is done. * <p> * Acquires write access. * @param reference an Ant-interpretable foreign file reference as created e.g. * by {@link #createForeignFileReference(File,String)} or * by {@link #createForeignFileReference(RakeArtifact)} * @return true if reference was really destroyed or not * @since 1.5 */ public boolean destroyReference(String reference) { Matcher m = FOREIGN_FILE_REFERENCE.matcher(reference); boolean matches = m.matches(); if (!matches) { m = FOREIGN_FILE_REFERENCE_OLD.matcher(reference); matches = m.matches(); } if (matches) { String forProjName = m.group(1); String id = m.group(2); return removeReference(forProjName, id, true, reference.substring(2, reference.length()-1)); } m = FOREIGN_PLAIN_FILE_REFERENCE.matcher(reference); if (m.matches()) { return removeFileReference(reference); } return false; } /** * Create an object permitting this project to represent subprojects. * Would be placed into the project's lookup. * @return a subproject provider object suitable for the project lookup * @see Project#getLookup */ public SubprojectProvider createSubprojectProvider() { return new SubprojectProviderImpl(this); } /** * Access from SubprojectProviderImpl. */ RakeProjectHelper getRakeProjectHelper() { return h; } /**Tries to fix references after copy/rename/move operation on the project. * Handles relative/absolute paths. * * @param originalPath the project folder of the original project * @see org.netbeans.spi.project.CopyOperationImplementation * @see org.netbeans.spi.project.MoveOperationImplementation * @since 1.9 */ public void fixReferences(File originalPath) { LOGGER.fine("Fixing refs for: " + originalPath); String[] prefixesToFix = new String[] {"file.reference.", "project."}; EditableProperties pub = h.getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH); EditableProperties priv = h.getProperties(RakeProjectHelper.PRIVATE_PROPERTIES_PATH); File projectDir = FileUtil.toFile(h.getProjectDirectory()); List<String> pubRemove = new ArrayList<String>(); List<String> privRemove = new ArrayList<String>(); Map<String,String> pubAdd = new HashMap<String,String>(); Map<String,String> privAdd = new HashMap<String,String>(); for (Map.Entry<String,String> e : pub.entrySet()) { String key = e.getKey(); boolean cont = false; for (String prefix : prefixesToFix) { if (key.startsWith(prefix)) { cont = true; break; } } if (!cont) continue; // #151648: do not try to fix references defined via property String value = e.getValue(); if (value.startsWith("${")) { // NOI18N continue; } File absolutePath = FileUtil.normalizeFile(PropertyUtils.resolveFile(originalPath, value)); //TODO: extra base dir relativization: //mkleint: removed CollocationQuery.areCollocated() reference // when AlwaysRelativeCQI gets removed the condition resolves to false more frequently. // that might not be desirable. String rel = PropertyUtils.relativizeFile(projectDir, absolutePath); if (rel == null) { pubRemove.add(key); privAdd.put(key, absolutePath.getAbsolutePath()); } } for (Map.Entry<String,String> e : pub.entrySet()) { String key = e.getKey(); boolean cont = false; for (String prefix : prefixesToFix) { if (key.startsWith(prefix)) { cont = true; break; } } if (!cont) continue; // #151648: do not try to fix references defined via property String value = e.getValue(); if (value.startsWith("${")) { // NOI18N continue; } File absolutePath = FileUtil.normalizeFile(PropertyUtils.resolveFile(originalPath, value)); if (absolutePath.getAbsolutePath().startsWith(originalPath.getAbsolutePath())) { //#65141: in private.properties, a full path into originalPath may be given, fix: String relative = PropertyUtils.relativizeFile(originalPath, absolutePath); absolutePath = FileUtil.normalizeFile(new File(projectDir, relative)); LOGGER.fine("Removing " + key + ", " + "path: " + absolutePath.getAbsolutePath() + ", " + "original path: " + originalPath.getAbsolutePath()); privRemove.add(key); privAdd.put(key, absolutePath.getAbsolutePath()); } //TODO: extra base dir relativization: //mkleint: removed CollocationQuery.areCollocated() reference // when AlwaysRelativeCQI gets removed the condition resolves to false more frequently. // that might not be desirable. String rel = PropertyUtils.relativizeFile(projectDir, absolutePath); if (rel != null) { pubAdd.put(key, rel); } } for (String s : pubRemove) { pub.remove(s); } for (String s : privRemove) { priv.remove(s); } pub.putAll(pubAdd); priv.putAll(privAdd); h.putProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH, pub); h.putProperties(RakeProjectHelper.PRIVATE_PROPERTIES_PATH, priv); } /** * Copies the given properties to private.properties. * @param toCopy the properties to copy; may be <code>null</code> in which * case nothing is copied. */ public void copyToPrivateProperties(EditableProperties toCopy) { if (toCopy == null) { return; } EditableProperties priv = h.getProperties(RakeProjectHelper.PRIVATE_PROPERTIES_PATH); if (priv == null) { return; } for (Entry<String, String> each : toCopy.entrySet()) { priv.put(each.getKey(), each.getValue()); } h.putProperties(RakeProjectHelper.PRIVATE_PROPERTIES_PATH, priv); } /** * A raw reference descriptor representing a link to a foreign project * and some build artifact used from it. * This class corresponds directly to what it stored in <code>project.xml</code> * to refer to a target in a foreign project. * See {@link RakeArtifact} for the precise meaning of several of the fields in this class. */ public static final class RawReference { private final String foreignProjectName; private final String artifactType; private URI scriptLocation; // introduced in /2 version private String newScriptLocation; private final String targetName; private final String cleanTargetName; private final String artifactID; private final Properties props; /** * Create a raw reference descriptor. * As this is basically just a struct, does no real work. * @param foreignProjectName the name of the foreign project (usually its code name) * @param artifactType the {@link RakeArtifact#getType type} of the build artifact * @param scriptLocation the relative URI to the build script from the project directory * @param targetName the Ant target name * @param cleanTargetName the Ant clean target name * @param artifactID the {@link RakeArtifact#getID ID} of the build artifact * @throws IllegalArgumentException if the script location is given an absolute URI */ public RawReference(String foreignProjectName, String artifactType, URI scriptLocation, String targetName, String cleanTargetName, String artifactID) throws IllegalArgumentException { this(foreignProjectName, artifactType, scriptLocation, null, targetName, cleanTargetName, artifactID, new Properties()); } /** * Create a raw reference descriptor. * As this is basically just a struct, does no real work. * @param foreignProjectName the name of the foreign project (usually its code name) * @param artifactType the {@link RakeArtifact#getType type} of the build artifact * @param newScriptLocation absolute path to the build script; can contain Ant-like properties * @param targetName the Ant target name * @param cleanTargetName the Ant clean target name * @param artifactID the {@link RakeArtifact#getID ID} of the build artifact * @param props optional properties to be used for target execution; never null * @throws IllegalArgumentException if the script location is given an absolute URI * @since 1.5 */ public RawReference(String foreignProjectName, String artifactType, String newScriptLocation, String targetName, String cleanTargetName, String artifactID, Properties props) throws IllegalArgumentException { this(foreignProjectName, artifactType, null, newScriptLocation, targetName, cleanTargetName, artifactID, props); } private RawReference(String foreignProjectName, String artifactType, URI scriptLocation, String newScriptLocation, String targetName, String cleanTargetName, String artifactID, Properties props) throws IllegalArgumentException { this.foreignProjectName = foreignProjectName; this.artifactType = artifactType; if (scriptLocation != null && scriptLocation.isAbsolute()) { throw new IllegalArgumentException("Cannot use an absolute URI " + scriptLocation + " for script location"); // NOI18N } this.scriptLocation = scriptLocation; this.newScriptLocation = newScriptLocation; this.targetName = targetName; this.cleanTargetName = cleanTargetName; this.artifactID = artifactID; this.props = props; } private static final List/*<String>*/ SUB_ELEMENT_NAMES = Arrays.asList(new String[] { "foreign-project", // NOI18N "artifact-type", // NOI18N "script", // NOI18N "target", // NOI18N "clean-target", // NOI18N "id", // NOI18N }); /** * Create a RawReference by parsing an XML <reference> fragment. * @throws IllegalArgumentException if anything is missing or duplicated or malformed etc. */ static RawReference create(Element xml) throws IllegalArgumentException { if (REFS_NS.equals(xml.getNamespaceURI())) { return create1(xml); } else { return create2(xml); } } private static RawReference create1(Element xml) throws IllegalArgumentException { if (!REF_NAME.equals(xml.getLocalName()) || !REFS_NS.equals(xml.getNamespaceURI())) { throw new IllegalArgumentException("bad element name: " + xml); // NOI18N } NodeList nl = xml.getElementsByTagNameNS("*", "*"); // NOI18N if (nl.getLength() != 6) { throw new IllegalArgumentException("missing or extra data: " + xml); // NOI18N } String[] values = new String[nl.getLength()]; for (int i = 0; i < nl.getLength(); i++) { Element el = (Element)nl.item(i); if (!REFS_NS.equals(el.getNamespaceURI())) { throw new IllegalArgumentException("bad subelement ns: " + el); // NOI18N } String elName = el.getLocalName(); int idx = SUB_ELEMENT_NAMES.indexOf(elName); if (idx == -1) { throw new IllegalArgumentException("bad subelement name: " + elName); // NOI18N } String val = XMLUtil.findText(el); if (val == null) { throw new IllegalArgumentException("empty subelement: " + el); // NOI18N } if (values[idx] != null) { throw new IllegalArgumentException("duplicate " + elName + ": " + values[idx] + " and " + val); // NOI18N } values[idx] = val; } assert !Arrays.asList(values).contains(null); URI scriptLocation = URI.create(values[2]); // throws IllegalArgumentException return new RawReference(values[0], values[1], scriptLocation, values[3], values[4], values[5]); } private static RawReference create2(Element xml) throws IllegalArgumentException { if (!REF_NAME.equals(xml.getLocalName()) || !REFS_NS2.equals(xml.getNamespaceURI())) { throw new IllegalArgumentException("bad element name: " + xml); // NOI18N } List nl = XMLUtil.findSubElements(xml); if (nl.size() < 6) { throw new IllegalArgumentException("missing or extra data: " + xml); // NOI18N } String[] values = new String[6]; for (int i = 0; i < 6; i++) { Element el = (Element)nl.get(i); if (!REFS_NS2.equals(el.getNamespaceURI())) { throw new IllegalArgumentException("bad subelement ns: " + el); // NOI18N } String elName = el.getLocalName(); int idx = SUB_ELEMENT_NAMES.indexOf(elName); if (idx == -1) { throw new IllegalArgumentException("bad subelement name: " + elName); // NOI18N } String val = XMLUtil.findText(el); if (val == null) { throw new IllegalArgumentException("empty subelement: " + el); // NOI18N } if (values[idx] != null) { throw new IllegalArgumentException("duplicate " + elName + ": " + values[idx] + " and " + val); // NOI18N } values[idx] = val; } Properties props = new Properties(); if (nl.size() == 7) { Element el = (Element)nl.get(6); if (!REFS_NS2.equals(el.getNamespaceURI())) { throw new IllegalArgumentException("bad subelement ns: " + el); // NOI18N } if (!"properties".equals(el.getLocalName())) { // NOI18N throw new IllegalArgumentException("bad subelement. expected 'properties': " + el); // NOI18N } for (Element el2 : XMLUtil.findSubElements(el)) { String key = el2.getAttribute("name"); String value = XMLUtil.findText(el2); // #53553: NPE if (value == null) { value = ""; // NOI18N } props.setProperty(key, value); } } assert !Arrays.asList(values).contains(null); return new RawReference(values[0], values[1], values[2], values[3], values[4], values[5], props); } /** * Write a RawReference as an XML <reference> fragment. */ Element toXml(String namespace, Document ownerDocument) { Element el = ownerDocument.createElementNS(namespace, REF_NAME); String[] values = { foreignProjectName, artifactType, newScriptLocation != null ? newScriptLocation : scriptLocation.toString(), targetName, cleanTargetName, artifactID, }; for (int i = 0; i < 6; i++) { Element subel = ownerDocument.createElementNS(namespace, (String)SUB_ELEMENT_NAMES.get(i)); subel.appendChild(ownerDocument.createTextNode(values[i])); el.appendChild(subel); } if (props.keySet().size() > 0) { assert namespace.equals(REFS_NS2) : "can happen only in /2"; // NOI18N Element propEls = ownerDocument.createElementNS(namespace, "properties"); // NOI18N el.appendChild(propEls); for (String key : new TreeSet<String>(NbCollections.checkedSetByFilter(props.keySet(), String.class, true))) { Element propEl = ownerDocument.createElementNS(namespace, "property"); // NOI18N propEl.appendChild(ownerDocument.createTextNode(props.getProperty(key))); propEl.setAttribute("name", key); // NOI18N propEls.appendChild(propEl); } } return el; } private String getNS() { if (newScriptLocation != null) { return REFS_NS2; } else { return REFS_NS; } } /** * Get the name of the foreign project as referred to from this project. * Usually this will be the code name of the foreign project, but it may * instead be a uniquified name. * The name can be used in project properties and the build script to refer * to the foreign project from among subprojects. * @return the foreign project name */ public String getForeignProjectName() { return foreignProjectName; } /** * Get the type of the foreign project's build artifact. * For example, <a href="@org-netbeans-modules-java-project@/org/netbeans/modules/gsfpath/api/project/JavaProjectConstants.html#ARTIFACT_TYPE_JAR"><code>JavaProjectConstants.ARTIFACT_TYPE_JAR</code></a>. * @return the artifact type */ public String getArtifactType() { return artifactType; } /** * Get the location of the foreign project's build script relative to the * project directory. * This is the script which would be called to build the desired artifact. * @return the script location * @deprecated use {@link #getScriptLocationValue} instead; may return null now */ @Deprecated public URI getScriptLocation() { return scriptLocation; } /** * Get absolute path location of the foreign project's build script. * This is the script which would be called to build the desired artifact. * @return absolute path possibly containing Ant properties */ public String getScriptLocationValue() { if (newScriptLocation != null) { return newScriptLocation; } else { return "${project."+foreignProjectName+"}/"+scriptLocation.toString(); } } /** * Get the Ant target name to build the artifact. * @return the target name */ public String getTargetName() { return targetName; } /** * Get the Ant target name to clean the artifact. * @return the clean target name */ public String getCleanTargetName() { return cleanTargetName; } /** * Get the ID of the foreign project's build artifact. * See also {@link RakeArtifact#getID}. * @return the artifact identifier */ public String getID() { return artifactID; } public Properties getProperties() { return props; } /** * Attempt to convert this reference to a live artifact object. * This involves finding the referenced foreign project on disk * (among standard project and private properties) and asking it * for the artifact named by the given target. * Given that object, you can find important further information * such as the location of the actual artifact on disk. * <p> * Note that non-key attributes of the returned artifact (i.e. * type, script location, and clean target name) might not match * those in this raw reference. * <p> * Acquires read access. * @param helper an associated reference helper used to resolve the foreign * project location * @return the actual Ant artifact object, or null if it could not be located */ public RakeArtifact toRakeArtifact(final ReferenceHelper helper) { return ProjectManager.mutex().readAccess(new Mutex.Action<RakeArtifact>() { public RakeArtifact run() { RakeProjectHelper h = helper.h; String path = helper.eval.getProperty("project." + foreignProjectName); // NOI18N if (path == null) { // Undefined foreign project. return null; } FileObject foreignProjectDir = h.resolveFileObject(path); if (foreignProjectDir == null) { // Nonexistent foreign project dir. return null; } Project p; try { p = ProjectManager.getDefault().findProject(foreignProjectDir); } catch (IOException e) { // Could not load it. ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); return null; } if (p == null) { // Was not a project dir. return null; } return RakeArtifactQuery.findArtifactByID(p, artifactID); } }); } private void upgrade() { assert newScriptLocation == null && scriptLocation != null : "was already upgraded "+this; newScriptLocation = "${project."+foreignProjectName+"}/" +scriptLocation.toString(); // NOI18N scriptLocation = null; } public String toString() { return "ReferenceHelper.RawReference<" + foreignProjectName + "," + artifactType + "," + newScriptLocation != null ? newScriptLocation : scriptLocation + "," + targetName + "," + cleanTargetName + "," + artifactID + ">"; // NOI18N } } }