0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.modules.apisupport.project;
0043:
0044: import org.netbeans.modules.apisupport.project.spi.NbModuleProvider;
0045: import java.beans.PropertyChangeEvent;
0046: import java.beans.PropertyChangeListener;
0047: import java.io.File;
0048: import java.io.IOException;
0049: import java.io.InputStream;
0050: import java.io.OutputStream;
0051: import java.net.MalformedURLException;
0052: import java.net.URL;
0053: import java.text.Collator;
0054: import java.util.ArrayList;
0055: import java.util.Collection;
0056: import java.util.Collections;
0057: import java.util.Comparator;
0058: import java.util.Enumeration;
0059: import java.util.HashMap;
0060: import java.util.HashSet;
0061: import java.util.Iterator;
0062: import java.util.LinkedHashSet;
0063: import java.util.List;
0064: import java.util.Locale;
0065: import java.util.Map;
0066: import java.util.Set;
0067: import java.util.SortedSet;
0068: import java.util.StringTokenizer;
0069: import java.util.TreeSet;
0070: import java.util.jar.JarFile;
0071: import java.util.jar.Manifest;
0072: import java.util.zip.ZipEntry;
0073: import javax.swing.event.ChangeEvent;
0074: import javax.swing.event.ChangeListener;
0075: import org.netbeans.api.java.project.JavaProjectConstants;
0076: import org.netbeans.api.project.Project;
0077: import org.netbeans.api.project.ProjectInformation;
0078: import org.netbeans.api.project.ProjectManager;
0079: import org.netbeans.api.project.ProjectUtils;
0080: import org.netbeans.api.project.SourceGroup;
0081: import org.netbeans.api.project.Sources;
0082: import org.netbeans.api.queries.VisibilityQuery;
0083: import org.netbeans.modules.apisupport.project.ui.customizer.ModuleDependency;
0084: import org.netbeans.modules.apisupport.project.universe.LocalizedBundleInfo;
0085: import org.netbeans.modules.apisupport.project.universe.ModuleEntry;
0086: import org.netbeans.modules.apisupport.project.universe.NbPlatform;
0087: import org.netbeans.spi.project.support.ant.EditableProperties;
0088: import org.netbeans.spi.project.support.ant.PropertyEvaluator;
0089: import org.netbeans.spi.project.support.ant.PropertyProvider;
0090: import org.netbeans.spi.project.support.ant.PropertyUtils;
0091: import org.openide.ErrorManager;
0092: import org.openide.filesystems.FileLock;
0093: import org.openide.filesystems.FileObject;
0094: import org.openide.filesystems.FileUtil;
0095: import org.openide.filesystems.URLMapper;
0096: import org.openide.modules.SpecificationVersion;
0097: import org.openide.util.ChangeSupport;
0098: import org.openide.util.NbBundle;
0099: import org.openide.util.Utilities;
0100: import org.openide.util.WeakListeners;
0101: import org.w3c.dom.Element;
0102: import org.w3c.dom.NamedNodeMap;
0103: import org.w3c.dom.Node;
0104: import org.w3c.dom.NodeList;
0105: import org.w3c.dom.Text;
0106:
0107: /**
0108: * Utility methods for the module.
0109: *
0110: * @author Jesse Glick, Martin Krauskopf
0111: */
0112: public final class Util {
0113:
0114: private Util() {
0115: }
0116:
0117: public static final ErrorManager err = ErrorManager.getDefault()
0118: .getInstance("org.netbeans.modules.apisupport.project"); // NOI18N
0119:
0120: private static final String SFS_VALID_PATH_RE = "(\\p{Alnum}|\\/|_)+"; // NOI18N
0121:
0122: // COPIED FROM org.netbeans.modules.project.ant:
0123: // (except for namespace == null support in findElement)
0124: // (and support for comments in findSubElements)
0125:
0126: /**
0127: * Search for an XML element in the direct children of a parent.
0128: * DOM provides a similar method but it does a recursive search
0129: * which we do not want. It also gives a node list and we want
0130: * only one result.
0131: * @param parent a parent element
0132: * @param name the intended local name
0133: * @param namespace the intended namespace (or null)
0134: * @return the one child element with that name, or null if none or more than one
0135: */
0136: public static Element findElement(Element parent, String name,
0137: String namespace) {
0138: Element result = null;
0139: NodeList l = parent.getChildNodes();
0140: for (int i = 0; i < l.getLength(); i++) {
0141: if (l.item(i).getNodeType() == Node.ELEMENT_NODE) {
0142: Element el = (Element) l.item(i);
0143: if ((namespace == null && name.equals(el.getTagName()))
0144: || (namespace != null
0145: && name.equals(el.getLocalName()) && namespace
0146: .equals(el.getNamespaceURI()))) {
0147: if (result == null) {
0148: result = el;
0149: } else {
0150: return null;
0151: }
0152: }
0153: }
0154: }
0155: return result;
0156: }
0157:
0158: /**
0159: * Extract nested text from an element.
0160: * Currently does not handle coalescing text nodes, CDATA sections, etc.
0161: * @param parent a parent element
0162: * @return the nested text, or null if none was found
0163: */
0164: public static String findText(Element parent) {
0165: NodeList l = parent.getChildNodes();
0166: for (int i = 0; i < l.getLength(); i++) {
0167: if (l.item(i).getNodeType() == Node.TEXT_NODE) {
0168: Text text = (Text) l.item(i);
0169: return text.getNodeValue();
0170: }
0171: }
0172: return null;
0173: }
0174:
0175: /**
0176: * Find all direct child elements of an element.
0177: * More useful than {@link Element#getElementsByTagNameNS} because it does
0178: * not recurse into recursive child elements.
0179: * Children which are all-whitespace text nodes or comments are ignored; others cause
0180: * an exception to be thrown.
0181: * @param parent a parent element in a DOM tree
0182: * @return a list of direct child elements (may be empty)
0183: * @throws IllegalArgumentException if there are non-element children besides whitespace
0184: */
0185: public static List<Element> findSubElements(Element parent)
0186: throws IllegalArgumentException {
0187: NodeList l = parent.getChildNodes();
0188: List<Element> elements = new ArrayList<Element>(l.getLength());
0189: for (int i = 0; i < l.getLength(); i++) {
0190: Node n = l.item(i);
0191: if (n.getNodeType() == Node.ELEMENT_NODE) {
0192: elements.add((Element) n);
0193: } else if (n.getNodeType() == Node.TEXT_NODE) {
0194: String text = ((Text) n).getNodeValue();
0195: if (text.trim().length() > 0) {
0196: throw new IllegalArgumentException(
0197: "non-ws text encountered in " + parent
0198: + ": " + text); // NOI18N
0199: }
0200: } else if (n.getNodeType() == Node.COMMENT_NODE) {
0201: // OK, ignore
0202: } else {
0203: throw new IllegalArgumentException(
0204: "unexpected non-element child of " + parent
0205: + ": " + n); // NOI18N
0206: }
0207: }
0208: return elements;
0209: }
0210:
0211: /**
0212: * Convert an XML fragment from one namespace to another.
0213: */
0214: public static Element translateXML(Element from, String namespace) {
0215: Element to = from.getOwnerDocument().createElementNS(namespace,
0216: from.getLocalName());
0217: NodeList nl = from.getChildNodes();
0218: int length = nl.getLength();
0219: for (int i = 0; i < length; i++) {
0220: Node node = nl.item(i);
0221: Node newNode;
0222: if (node.getNodeType() == Node.ELEMENT_NODE) {
0223: newNode = translateXML((Element) node, namespace);
0224: } else {
0225: newNode = node.cloneNode(true);
0226: }
0227: to.appendChild(newNode);
0228: }
0229: NamedNodeMap m = from.getAttributes();
0230: for (int i = 0; i < m.getLength(); i++) {
0231: Node attr = m.item(i);
0232: to.setAttribute(attr.getNodeName(), attr.getNodeValue());
0233: }
0234: return to;
0235: }
0236:
0237: // CANDIDATES FOR FileUtil (#59311):
0238:
0239: /**
0240: * Creates a URL for a directory on disk.
0241: * Works correctly even if the directory does not currently exist.
0242: */
0243: public static URL urlForDir(File dir) {
0244: try {
0245: URL u = FileUtil.normalizeFile(dir).toURI().toURL();
0246: String s = u.toExternalForm();
0247: if (s.endsWith("/")) { // NOI18N
0248: return u;
0249: } else {
0250: return new URL(s + "/"); // NOI18N
0251: }
0252: } catch (MalformedURLException e) {
0253: throw new AssertionError(e);
0254: }
0255: }
0256:
0257: /**
0258: * Creates a URL for the root of a JAR on disk.
0259: */
0260: public static URL urlForJar(File jar) {
0261: try {
0262: return FileUtil.getArchiveRoot(FileUtil.normalizeFile(jar)
0263: .toURI().toURL());
0264: } catch (MalformedURLException e) {
0265: throw new AssertionError(e);
0266: }
0267: }
0268:
0269: /**
0270: * Creates a URL for a directory on disk or the root of a JAR.
0271: * Works correctly whether or not the directory or JAR currently exists.
0272: * Detects whether the file is supposed to be a directory or a JAR.
0273: */
0274: public static URL urlForDirOrJar(File location) {
0275: try {
0276: URL u = FileUtil.normalizeFile(location).toURI().toURL();
0277: if (FileUtil.isArchiveFile(u)) {
0278: u = FileUtil.getArchiveRoot(u);
0279: } else {
0280: String us = u.toExternalForm();
0281: if (!us.endsWith("/")) { // NOI18N
0282: u = new URL(us + "/"); // NOI18N
0283: }
0284: }
0285: return u;
0286: } catch (MalformedURLException e) {
0287: throw new AssertionError(e);
0288: }
0289: }
0290:
0291: /**
0292: * Tries to find {@link Project} in the given directory. If succeeds
0293: * delegates to {@link ProjectInformation#getDisplayName}. Returns {@link
0294: * FileUtil#getFileDisplayName} otherwise.
0295: */
0296: public static String getDisplayName(FileObject projectDir) {
0297: if (projectDir.isFolder()) {
0298: try {
0299: Project p = ProjectManager.getDefault().findProject(
0300: projectDir);
0301: if (p != null) {
0302: return ProjectUtils.getInformation(p)
0303: .getDisplayName();
0304: }
0305: } catch (IOException e) {
0306: // ignore
0307: }
0308: }
0309: return FileUtil.getFileDisplayName(projectDir);
0310: }
0311:
0312: /**
0313: * Normalizes the given value to a regular dotted code name base.
0314: * @param value to be normalized
0315: */
0316: public static String normalizeCNB(String value) {
0317: StringTokenizer tk = new StringTokenizer(value
0318: .toLowerCase(Locale.ENGLISH), ".", true); // NOI18N
0319: StringBuffer normalizedCNB = new StringBuffer();
0320: boolean delimExpected = false;
0321: while (tk.hasMoreTokens()) {
0322: String namePart = tk.nextToken();
0323: if (!delimExpected) {
0324: if (namePart.equals(".")) { //NOI18N
0325: continue;
0326: }
0327: for (int i = 0; i < namePart.length(); i++) {
0328: char c = namePart.charAt(i);
0329: if (i == 0) {
0330: if (!Character.isJavaIdentifierStart(c)) {
0331: continue;
0332: }
0333: } else {
0334: if (!Character.isJavaIdentifierPart(c)) {
0335: continue;
0336: }
0337: }
0338: normalizedCNB.append(c);
0339: }
0340: } else {
0341: if (namePart.equals(".")) { //NOI18N
0342: normalizedCNB.append(namePart);
0343: }
0344: }
0345: delimExpected = !delimExpected;
0346: }
0347: // also be sure there is no '.' left at the end of the cnb
0348: return normalizedCNB.toString().replaceAll("\\.$", ""); // NOI18N
0349: }
0350:
0351: /**
0352: * Check whether a given name can serve as a legal <ol>
0353: * <li>Java class name
0354: * <li>Java package name
0355: * <li>NB module code name base
0356: * </ol>
0357: */
0358: public static boolean isValidJavaFQN(String name) {
0359: if (name.length() == 0) {
0360: return false;
0361: }
0362: StringTokenizer tk = new StringTokenizer(name, ".", true); //NOI18N
0363: boolean delimExpected = false;
0364: while (tk.hasMoreTokens()) {
0365: String namePart = tk.nextToken();
0366: if (delimExpected ^ namePart.equals(".")) { // NOI18N
0367: return false;
0368: }
0369: if (!delimExpected && !Utilities.isJavaIdentifier(namePart)) {
0370: return false;
0371: }
0372: delimExpected = !delimExpected;
0373: }
0374: return delimExpected;
0375: }
0376:
0377: /**
0378: * Search for an appropriate localized bundle (i.e.
0379: * OpenIDE-Module-Localizing-Bundle) entry in the given
0380: * <code>manifest</code> taking into account branding and localization
0381: * (using {@link NbBundle#getLocalizingSuffixes}) and returns an
0382: * appropriate <em>valid</em> {@link LocalizedBundleInfo} instance. By
0383: * <em>valid</em> it's meant that a found localized bundle contains at
0384: * least a display name. If <em>valid</em> bundle is not found
0385: * <code>null</code> is returned.
0386: *
0387: * @param sourceDir source directory to be used for as a <em>searching
0388: * path</em> for the bundle
0389: * @param manifest manifest the bundle's path should be extracted from
0390: * @return localized bundle info for the given project or <code>null</code>
0391: */
0392: public static LocalizedBundleInfo findLocalizedBundleInfo(
0393: FileObject sourceDir, Manifest manifest) {
0394: String locBundleResource = ManifestManager.getInstance(
0395: manifest, false).getLocalizingBundle();
0396: try {
0397: if (locBundleResource != null) {
0398: List<FileObject> bundleFOs = new ArrayList<FileObject>();
0399: for (Iterator it = getPossibleResources(locBundleResource); it
0400: .hasNext();) {
0401: String resource = (String) it.next();
0402: FileObject bundleFO = sourceDir
0403: .getFileObject(resource);
0404: if (bundleFO != null) {
0405: bundleFOs.add(bundleFO);
0406: }
0407: }
0408: if (!bundleFOs.isEmpty()) {
0409: Collections.reverse(bundleFOs);
0410: return LocalizedBundleInfo.load(bundleFOs
0411: .toArray(new FileObject[bundleFOs.size()]));
0412: }
0413: }
0414: } catch (IOException e) {
0415: Util.err.notify(ErrorManager.INFORMATIONAL, e);
0416: }
0417: return null;
0418: }
0419:
0420: /**
0421: * Actually deletages to {@link #findLocalizedBundleInfo(FileObject, Manifest)}.
0422: */
0423: public static LocalizedBundleInfo findLocalizedBundleInfo(
0424: File projectDir) {
0425: FileObject projectDirFO = FileUtil.toFileObject(projectDir);
0426: if (projectDirFO == null) {
0427: return null;
0428: }
0429: NbModuleProject p;
0430: try {
0431: p = (NbModuleProject) ProjectManager.getDefault()
0432: .findProject(projectDirFO);
0433: } catch (IOException e) {
0434: return null;
0435: }
0436: if (p == null) {
0437: return null;
0438: }
0439: String src = p.evaluator().getProperty("src.dir"); // NOI18N
0440: assert src != null : "Cannot evaluate src.dir property for "
0441: + p;
0442: File srcF = FileUtil.normalizeFile(new File(projectDir, src));
0443: FileObject sourceDir = FileUtil.toFileObject(srcF);
0444: FileObject manifestFO = FileUtil.toFileObject(FileUtil
0445: .normalizeFile(new File(projectDir, "manifest.mf"))); // NOI18N
0446:
0447: LocalizedBundleInfo locInfo = null;
0448: Manifest mf = getManifest(manifestFO);
0449: if (sourceDir != null && mf != null) {
0450: locInfo = findLocalizedBundleInfo(sourceDir, mf);
0451: }
0452: return locInfo;
0453: }
0454:
0455: /**
0456: * The same as {@link #findLocalizedBundleInfo(FileObject, Manifest)} but
0457: * searching in the given JAR representing a NetBeans module.
0458: */
0459: public static LocalizedBundleInfo findLocalizedBundleInfoFromJAR(
0460: File binaryProject) {
0461: try {
0462: JarFile main = new JarFile(binaryProject);
0463: try {
0464: Manifest mf = main.getManifest();
0465: String locBundleResource = ManifestManager.getInstance(
0466: mf, false).getLocalizingBundle();
0467: if (locBundleResource != null) {
0468: List<InputStream> bundleISs = new ArrayList<InputStream>();
0469: Collection<JarFile> extraJarFiles = new ArrayList<JarFile>();
0470: try {
0471: // Look for locale variant JARs too.
0472: // XXX the following could be simplified with #29580:
0473: String name = binaryProject.getName();
0474: int dot = name.lastIndexOf('.');
0475: if (dot == -1) {
0476: dot = name.length();
0477: }
0478: String base = name.substring(0, dot);
0479: String suffix = name.substring(dot);
0480: Iterator<String> it = NbBundle
0481: .getLocalizingSuffixes();
0482: while (it.hasNext()) {
0483: String infix = it.next();
0484: File variant = new File(binaryProject
0485: .getParentFile(), "locale"
0486: + File.separatorChar + base + infix
0487: + suffix); // NOI18N
0488: if (variant.isFile()) {
0489: JarFile jf = new JarFile(variant);
0490: extraJarFiles.add(jf);
0491: addBundlesFromJar(jf, bundleISs,
0492: locBundleResource);
0493: }
0494: }
0495: // Add main last, since we are about to reverse it:
0496: addBundlesFromJar(main, bundleISs,
0497: locBundleResource);
0498: if (!bundleISs.isEmpty()) {
0499: Collections.reverse(bundleISs);
0500: return LocalizedBundleInfo.load(bundleISs
0501: .toArray(new InputStream[bundleISs
0502: .size()]));
0503: }
0504: } finally {
0505: for (InputStream bundleIS : bundleISs) {
0506: bundleIS.close();
0507: }
0508: for (JarFile jarFile : extraJarFiles) {
0509: jarFile.close();
0510: }
0511: }
0512: }
0513: } finally {
0514: main.close();
0515: }
0516: } catch (IOException e) {
0517: Util.err.notify(ErrorManager.INFORMATIONAL, e);
0518: }
0519: return null;
0520: }
0521:
0522: private static void addBundlesFromJar(JarFile jf,
0523: List<InputStream> bundleISs, String locBundleResource)
0524: throws IOException {
0525: for (Iterator it = getPossibleResources(locBundleResource); it
0526: .hasNext();) {
0527: String resource = (String) it.next();
0528: ZipEntry entry = jf.getEntry(resource);
0529: if (entry != null) {
0530: InputStream bundleIS = jf.getInputStream(entry);
0531: bundleISs.add(bundleIS);
0532: }
0533: }
0534: }
0535:
0536: /**
0537: * Convenience method for loading {@link EditableProperties} from a {@link
0538: * FileObject}. New items will alphabetizied by key.
0539: *
0540: * @param propsFO file representing properties file
0541: * @exception FileNotFoundException if the file represented by the given
0542: * FileObject does not exists, is a folder rather than a regular
0543: * file or is invalid. i.e. as it is thrown by {@link
0544: * FileObject#getInputStream()}.
0545: */
0546: public static EditableProperties loadProperties(FileObject propsFO)
0547: throws IOException {
0548: InputStream propsIS = propsFO.getInputStream();
0549: EditableProperties props = new EditableProperties(true);
0550: try {
0551: props.load(propsIS);
0552: } finally {
0553: propsIS.close();
0554: }
0555: return props;
0556: }
0557:
0558: /**
0559: * Convenience method for storing {@link EditableProperties} into a {@link
0560: * FileObject}.
0561: *
0562: * @param propsFO file representing where properties will be stored
0563: * @param props properties to be stored
0564: * @exception IOException if properties cannot be written to the file
0565: */
0566: public static void storeProperties(FileObject propsFO,
0567: EditableProperties props) throws IOException {
0568: FileLock lock = propsFO.lock();
0569: try {
0570: OutputStream os = propsFO.getOutputStream(lock);
0571: try {
0572: props.store(os);
0573: } finally {
0574: os.close();
0575: }
0576: } finally {
0577: lock.releaseLock();
0578: }
0579: }
0580:
0581: /**
0582: * Convenience method for loading {@link EditableManifest} from a {@link
0583: * FileObject}.
0584: *
0585: * @param manifestFO file representing manifest
0586: * @exception FileNotFoundException if the file represented by the given
0587: * FileObject does not exists, is a folder rather than a regular
0588: * file or is invalid. i.e. as it is thrown by {@link
0589: * FileObject#getInputStream()}.
0590: */
0591: public static EditableManifest loadManifest(FileObject manifestFO)
0592: throws IOException {
0593: InputStream mfIS = manifestFO.getInputStream();
0594: try {
0595: return new EditableManifest(mfIS);
0596: } finally {
0597: mfIS.close();
0598: }
0599: }
0600:
0601: /**
0602: * Convenience method for storing {@link EditableManifest} into a {@link
0603: * FileObject}.
0604: *
0605: * @param manifestFO file representing where manifest will be stored
0606: * @param em manifest to be stored
0607: * @exception IOException if manifest cannot be written to the file
0608: */
0609: public static void storeManifest(FileObject manifestFO,
0610: EditableManifest em) throws IOException {
0611: FileLock lock = manifestFO.lock();
0612: try {
0613: OutputStream os = manifestFO.getOutputStream(lock);
0614: try {
0615: em.write(os);
0616: } finally {
0617: os.close();
0618: }
0619: } finally {
0620: lock.releaseLock();
0621: }
0622: }
0623:
0624: /**
0625: * Find Javadoc URL for NetBeans.org modules. May return <code>null</code>.
0626: */
0627: public static URL findJavadocForNetBeansOrgModules(
0628: final ModuleDependency dep) {
0629: ModuleEntry entry = dep.getModuleEntry();
0630: File destDir = entry.getDestDir();
0631: File nbOrg = null;
0632: if (destDir.getParent() != null) {
0633: nbOrg = destDir.getParentFile().getParentFile();
0634: }
0635: if (nbOrg == null) {
0636: throw new IllegalArgumentException("ModuleDependency "
0637: + dep + // NOI18N
0638: " doesn't represent nb.org module"); // NOI18N
0639: }
0640: File builtJavadoc = new File(nbOrg, "nbbuild/build/javadoc"); // NOI18N
0641: URL[] javadocURLs = null;
0642: if (builtJavadoc.exists()) {
0643: File[] javadocs = builtJavadoc.listFiles();
0644: javadocURLs = new URL[javadocs.length];
0645: for (int i = 0; i < javadocs.length; i++) {
0646: javadocURLs[i] = Util.urlForDirOrJar(javadocs[i]);
0647: }
0648: }
0649: return javadocURLs == null ? null : findJavadocURL(dep
0650: .getModuleEntry().getCodeNameBase().replace('.', '-'),
0651: javadocURLs);
0652: }
0653:
0654: /**
0655: * Find Javadoc URL for the given module dependency using Javadoc roots of
0656: * the given platform. May return <code>null</code>.
0657: */
0658: public static URL findJavadoc(final ModuleDependency dep,
0659: final NbPlatform platform) {
0660: String cnbdashes = dep.getModuleEntry().getCodeNameBase()
0661: .replace('.', '-');
0662: URL[] roots = platform.getJavadocRoots();
0663: return roots == null ? null : findJavadocURL(cnbdashes, roots);
0664: }
0665:
0666: public static boolean isValidSFSPath(final String path) {
0667: return path.matches(SFS_VALID_PATH_RE);
0668: }
0669:
0670: /**
0671: * Delegates to {@link #addDependency(NbModuleProject, String)}.
0672: */
0673: public static boolean addDependency(final NbModuleProject target,
0674: final NbModuleProject dependency) throws IOException {
0675: return addDependency(target, dependency.getCodeNameBase());
0676: }
0677:
0678: /**
0679: * Delegates to {@link Util#addDependency(NbModuleProject, String, String,
0680: * SpecificationVersion, boolean)}.
0681: */
0682: public static boolean addDependency(final NbModuleProject target,
0683: final String codeNameBase) throws IOException {
0684: return Util.addDependency(target, codeNameBase, null, null,
0685: true);
0686: }
0687:
0688: /**
0689: * Makes <code>target</code> project to be dependend on the given
0690: * <code>dependency</code> project. I.e. adds new <module-dependency>
0691: * element into target's <em>project.xml</em>. If such a dependency already
0692: * exists the method does nothing. If the given code name base cannot be
0693: * found in the module's universe the method logs informational message and
0694: * does nothing otherwise.
0695: * <p>
0696: * Note that the method does <strong>not</strong> save the
0697: * <code>target</code> project. You need to do so explicitly (see {@link
0698: * ProjectManager#saveProject}).
0699: *
0700: * @param codeNameBase codename base.
0701: * @param releaseVersion release version, if <code>null</code> will be taken from the
0702: * entry found in platform.
0703: * @param version {@link SpecificationVersion specification version}, if
0704: * <code>null</code>, will be taken from the entry found in the
0705: * module's target platform.
0706: * @param useInCompiler whether this this module needs a
0707: * <code>dependency</code> module at a compile time.
0708: * @return true if a dependency was successfully added; false otherwise
0709: * (e.g. when such dependency already exists)
0710: */
0711: public static boolean addDependency(final NbModuleProject target,
0712: final String codeNameBase, final String releaseVersion,
0713: final SpecificationVersion version,
0714: final boolean useInCompiler) throws IOException {
0715: ModuleEntry me = target.getModuleList().getEntry(codeNameBase);
0716: if (me == null) { // ignore semi-silently (#72611)
0717: Util.err.log(ErrorManager.INFORMATIONAL, "Trying to add "
0718: + codeNameBase + // NOI18N
0719: " which cannot be found in the module's universe."); // NOI18N
0720: return false;
0721: }
0722:
0723: ProjectXMLManager pxm = new ProjectXMLManager(target);
0724:
0725: // firstly check if the dependency is already not there
0726: Set currentDeps = pxm.getDirectDependencies();
0727: for (Iterator it = currentDeps.iterator(); it.hasNext();) {
0728: ModuleDependency md = (ModuleDependency) it.next();
0729: if (codeNameBase.equals(md.getModuleEntry()
0730: .getCodeNameBase())) {
0731: Util.err.log(ErrorManager.INFORMATIONAL, codeNameBase
0732: + " already added"); // NOI18N
0733: return false;
0734: }
0735: }
0736:
0737: ModuleDependency md = new ModuleDependency(me,
0738: (releaseVersion == null) ? me.getReleaseVersion()
0739: : releaseVersion,
0740: version == null ? me.getSpecificationVersion()
0741: : version.toString(), useInCompiler, false);
0742: pxm.addDependency(md);
0743: return true;
0744: }
0745:
0746: private static URL findJavadocURL(final String cnbdashes,
0747: final URL[] roots) {
0748: URL indexURL = null;
0749: for (int i = 0; i < roots.length; i++) {
0750: URL root = roots[i];
0751: try {
0752: indexURL = Util.normalizeURL(new URL(root, cnbdashes
0753: + "/index.html")); // NOI18N
0754: if (indexURL == null
0755: && (root.toExternalForm().indexOf(cnbdashes) != -1)) {
0756: indexURL = Util.normalizeURL(new URL(root,
0757: "index.html")); // NOI18N
0758: }
0759: } catch (MalformedURLException ex) {
0760: // ignore - let the indexURL == null
0761: }
0762: if (indexURL != null) {
0763: break;
0764: }
0765: }
0766: return indexURL;
0767: }
0768:
0769: private static URL normalizeURL(URL url) {
0770: // not sure - in some private tests it seems that input
0771: // jar:file:/home/..../NetBeansAPIs.zip!/..../index.html result in:
0772: // http://localhost:8082/..../index.html
0773: // URL resolvedURL = null;
0774: // FileObject fo = URLMapper.findFileObject(url);
0775: // if (fo != null) {
0776: // resolvedURL = URLMapper.findURL(fo, URLMapper.EXTERNAL);
0777: // }
0778: return URLMapper.findFileObject(url) == null ? null : url;
0779: }
0780:
0781: private static Iterator getPossibleResources(
0782: String locBundleResource) {
0783: String locBundleResourceBase, locBundleResourceExt;
0784: int idx = locBundleResource.lastIndexOf('.');
0785: if (idx != -1 && idx > locBundleResource.lastIndexOf('/')) {
0786: locBundleResourceBase = locBundleResource.substring(0, idx);
0787: locBundleResourceExt = locBundleResource.substring(idx);
0788: } else {
0789: locBundleResourceBase = locBundleResource;
0790: locBundleResourceExt = "";
0791: }
0792: Collection<String> resources = new LinkedHashSet<String>();
0793: for (Iterator<String> it = NbBundle.getLocalizingSuffixes(); it
0794: .hasNext();) {
0795: String suffix = it.next();
0796: String resource = locBundleResourceBase + suffix
0797: + locBundleResourceExt;
0798: resources.add(resource);
0799: resources.add(resource);
0800: }
0801: return resources.iterator();
0802: }
0803:
0804: public static Manifest getManifest(FileObject manifestFO) {
0805: if (manifestFO != null) {
0806: try {
0807: InputStream is = manifestFO.getInputStream();
0808: try {
0809: return new Manifest(is);
0810: } finally {
0811: is.close();
0812: }
0813: } catch (IOException e) {
0814: Util.err.notify(ErrorManager.INFORMATIONAL, e);
0815: }
0816: }
0817: return null;
0818: }
0819:
0820: /**
0821: * Property provider which computes one or more properties based on some properties coming
0822: * from an intermediate evaluator, and is capable of firing changes correctly.
0823: */
0824: public static abstract class ComputedPropertyProvider implements
0825: PropertyProvider, PropertyChangeListener {
0826: private final PropertyEvaluator eval;
0827: private final ChangeSupport cs = new ChangeSupport(this );
0828:
0829: protected ComputedPropertyProvider(PropertyEvaluator eval) {
0830: this .eval = eval;
0831: eval.addPropertyChangeListener(WeakListeners
0832: .propertyChange(this , eval));
0833: }
0834:
0835: /** get properties based on the incoming properties */
0836: protected abstract Map<String, String> getProperties(
0837: Map<String, String> inputPropertyValues);
0838:
0839: /** specify interesting input properties */
0840: protected abstract Set<String> inputProperties();
0841:
0842: public final Map<String, String> getProperties() {
0843: Map<String, String> vals = new HashMap<String, String>();
0844: for (String k : inputProperties()) {
0845: vals.put(k, eval.getProperty(k));
0846: }
0847: return getProperties(vals);
0848: }
0849:
0850: public final void addChangeListener(ChangeListener l) {
0851: cs.addChangeListener(l);
0852: }
0853:
0854: public final void removeChangeListener(ChangeListener l) {
0855: cs.removeChangeListener(l);
0856: }
0857:
0858: public final void propertyChange(PropertyChangeEvent evt) {
0859: String p = evt.getPropertyName();
0860: if (p != null && !inputProperties().contains(p)) {
0861: return;
0862: }
0863: cs.fireChange();
0864: }
0865: }
0866:
0867: public static final class UserPropertiesFileProvider implements
0868: PropertyProvider, PropertyChangeListener, ChangeListener {
0869: private final PropertyEvaluator eval;
0870: private final File basedir;
0871: private final ChangeSupport changeSupport = new ChangeSupport(
0872: this );
0873: private final ChangeListener listener = WeakListeners.change(
0874: this , null);
0875: private PropertyProvider delegate;
0876:
0877: public UserPropertiesFileProvider(PropertyEvaluator eval,
0878: File basedir) {
0879: this .eval = eval;
0880: this .basedir = basedir;
0881: eval.addPropertyChangeListener(WeakListeners
0882: .propertyChange(this , eval));
0883: computeDelegate();
0884: }
0885:
0886: private void computeDelegate() {
0887: if (delegate != null) {
0888: delegate.removeChangeListener(listener);
0889: }
0890: String buildS = eval.getProperty("user.properties.file"); // NOI18N
0891: if (buildS != null) {
0892: delegate = PropertyUtils
0893: .propertiesFilePropertyProvider(PropertyUtils
0894: .resolveFile(basedir, buildS));
0895: } else {
0896: /* XXX what should we do?
0897: delegate = null;
0898: */
0899: delegate = PropertyUtils.globalPropertyProvider();
0900: }
0901: delegate.addChangeListener(listener);
0902: }
0903:
0904: public Map<String, String> getProperties() {
0905: if (delegate != null) {
0906: return delegate.getProperties();
0907: } else {
0908: return Collections.emptyMap();
0909: }
0910: }
0911:
0912: public void addChangeListener(ChangeListener l) {
0913: changeSupport.addChangeListener(l);
0914: }
0915:
0916: public void removeChangeListener(ChangeListener l) {
0917: changeSupport.removeChangeListener(l);
0918: }
0919:
0920: public void propertyChange(PropertyChangeEvent evt) {
0921: String p = evt.getPropertyName();
0922: if (p == null || p.equals("user.properties.file")) { // NOI18N
0923: computeDelegate();
0924: changeSupport.fireChange();
0925: }
0926: }
0927:
0928: public void stateChanged(ChangeEvent e) {
0929: changeSupport.fireChange();
0930: }
0931: }
0932:
0933: /**
0934: * Order projects by display name.
0935: */
0936: public static Comparator<Project> projectDisplayNameComparator() {
0937: return new Comparator<Project>() {
0938: private final Collator LOC_COLLATOR = Collator
0939: .getInstance();
0940:
0941: public int compare(Project o1, Project o2) {
0942: ProjectInformation i1 = ProjectUtils.getInformation(o1);
0943: ProjectInformation i2 = ProjectUtils.getInformation(o2);
0944: int result = LOC_COLLATOR.compare(i1.getDisplayName(),
0945: i2.getDisplayName());
0946: if (result != 0) {
0947: return result;
0948: } else {
0949: result = i1.getName().compareTo(i2.getName());
0950: if (result != 0) {
0951: return result;
0952: } else {
0953: return System.identityHashCode(o1)
0954: - System.identityHashCode(o2);
0955: }
0956: }
0957: }
0958: };
0959: }
0960:
0961: /**
0962: * Returns {@link NbModuleProvider.NbModuleType} from a project's lookup.
0963: */
0964: public static NbModuleProvider.NbModuleType getModuleType(
0965: final Project project) {
0966: NbModuleProvider provider = project.getLookup().lookup(
0967: NbModuleProvider.class);
0968: assert provider != null : "has NbModuleProvider in the lookup";
0969: return provider.getModuleType();
0970: }
0971:
0972: /**
0973: * Finds all available packages in a given project directory. Found entries
0974: * are in the form of a regular java package (x.y.z).
0975: *
0976: * @param prjDir directory containing project to be scanned
0977: * @return a set of found packages
0978: */
0979: public static SortedSet<String> scanProjectForPackageNames(
0980: final File prjDir) {
0981: NbModuleProject project = null;
0982: // find all available public packages in classpath extensions
0983: FileObject source = FileUtil.toFileObject(prjDir);
0984: if (source != null) { // ??
0985: try {
0986: project = (NbModuleProject) ProjectManager.getDefault()
0987: .findProject(source);
0988: } catch (IOException e) {
0989: Util.err.notify(ErrorManager.INFORMATIONAL, e);
0990: }
0991: }
0992:
0993: if (project == null) {
0994: return new TreeSet<String>(Collections.<String> emptySet());
0995: }
0996:
0997: SortedSet<String> availablePublicPackages = new TreeSet<String>();
0998: // find all available public packages in a source root
0999: Set<FileObject> pkgs = new HashSet<FileObject>();
1000: FileObject srcDirFO = project.getSourceDirectory();
1001: Util.scanForPackages(pkgs, srcDirFO, "java"); // NOI18N
1002: for (FileObject pkg : pkgs) {
1003: if (srcDirFO.equals(pkg)) { // default package #71532
1004: continue;
1005: }
1006: String pkgS = PropertyUtils.relativizeFile(FileUtil
1007: .toFile(srcDirFO), FileUtil.toFile(pkg));
1008: availablePublicPackages.add(pkgS.replace('/', '.'));
1009: }
1010:
1011: String[] libsPaths = new ProjectXMLManager(project)
1012: .getBinaryOrigins();
1013: for (int i = 0; i < libsPaths.length; i++) {
1014: scanJarForPackageNames(availablePublicPackages, project
1015: .getHelper().resolveFile(libsPaths[i]));
1016: }
1017:
1018: // #72669: remove invalid packages.
1019: Iterator it = availablePublicPackages.iterator();
1020: while (it.hasNext()) {
1021: String pkg = (String) it.next();
1022: if (!Util.isValidJavaFQN(pkg)) {
1023: it.remove();
1024: }
1025: }
1026: return availablePublicPackages;
1027: }
1028:
1029: /**
1030: * Scans a given jar file for all packages which contains at least one
1031: * .class file. Found entries are in the form of a regular java package
1032: * (x.y.z).
1033: *
1034: * @param jarFile jar file to be scanned
1035: * @param packages a set into which found packages will be added
1036: */
1037: public static void scanJarForPackageNames(
1038: final Set<String> packages, final File jarFile) {
1039: FileObject jarFileFO = FileUtil.toFileObject(jarFile);
1040: if (jarFileFO == null) {
1041: // Broken classpath entry, perhaps.
1042: return;
1043: }
1044: FileObject root = FileUtil.getArchiveRoot(jarFileFO);
1045: if (root == null) {
1046: // Not really a JAR?
1047: return;
1048: }
1049: Set<FileObject> pkgs = new HashSet<FileObject>();
1050: Util.scanForPackages(pkgs, root, "class"); // NOI18N
1051: for (FileObject pkg : pkgs) {
1052: if (root.equals(pkg)) { // default package #71532
1053: continue;
1054: }
1055: String pkgS = pkg.getPath();
1056: packages.add(pkgS.replace('/', '.'));
1057: }
1058: }
1059:
1060: /**
1061: * Scan recursively through all folders in the given <code>dir</code> and
1062: * add every directory/package, which contains at least one file with the
1063: * given extension (probably class or java), into the given
1064: * <code>validPkgs</code> set. Added entries are in the form of regular java
1065: * package (x.y.z)
1066: */
1067: private static void scanForPackages(
1068: final Set<FileObject> validPkgs, final FileObject dir,
1069: final String ext) {
1070: if (dir == null) {
1071: return;
1072: }
1073: for (Enumeration en1 = dir.getFolders(false); en1
1074: .hasMoreElements();) {
1075: FileObject subDir = (FileObject) en1.nextElement();
1076: if (VisibilityQuery.getDefault().isVisible(subDir)) {
1077: scanForPackages(validPkgs, subDir, ext);
1078: }
1079: }
1080: for (Enumeration en2 = dir.getData(false); en2
1081: .hasMoreElements();) {
1082: FileObject kid = (FileObject) en2.nextElement();
1083: if (kid.hasExt(ext)
1084: && Utilities.isJavaIdentifier(kid.getName())) {
1085: // at least one class inside directory -> valid package
1086: validPkgs.add(dir);
1087: break;
1088: }
1089: }
1090: }
1091:
1092: /**
1093: * when ever there is need for non-java files creation or lookup,
1094: * use this method to get the right location for all projects.
1095: * Eg. maven places resources not next to the java files.
1096: */
1097: public static FileObject getResourceDirectory(Project prj) {
1098: Sources srcs = ProjectUtils.getSources(prj);
1099: SourceGroup[] grps = srcs
1100: .getSourceGroups(JavaProjectConstants.SOURCES_TYPE_RESOURCES);
1101: if (grps != null && grps.length > 0) {
1102: return grps[0].getRootFolder();
1103: }
1104: // fallback to sources..
1105: NbModuleProvider prov = prj.getLookup().lookup(
1106: NbModuleProvider.class);
1107: assert prov != null;
1108: return prov.getSourceDirectory();
1109: }
1110:
1111: }
|