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.universe;
0043:
0044: import java.beans.PropertyChangeListener;
0045: import java.beans.PropertyChangeSupport;
0046: import java.io.File;
0047: import java.io.FileNotFoundException;
0048: import java.io.IOException;
0049: import java.io.InputStream;
0050: import java.net.MalformedURLException;
0051: import java.net.URI;
0052: import java.net.URL;
0053: import java.text.MessageFormat;
0054: import java.util.ArrayList;
0055: import java.util.Arrays;
0056: import java.util.Collection;
0057: import java.util.Collections;
0058: import java.util.HashSet;
0059: import java.util.List;
0060: import java.util.Locale;
0061: import java.util.Map;
0062: import java.util.Properties;
0063: import java.util.Set;
0064: import java.util.SortedSet;
0065: import java.util.TreeSet;
0066: import java.util.jar.JarFile;
0067: import java.util.zip.ZipEntry;
0068: import org.netbeans.api.project.ProjectManager;
0069: import org.netbeans.modules.apisupport.project.ManifestManager;
0070: import org.netbeans.modules.apisupport.project.Util;
0071: import org.netbeans.spi.project.support.ant.EditableProperties;
0072: import org.netbeans.spi.project.support.ant.PropertyUtils;
0073: import org.openide.ErrorManager;
0074: import org.openide.filesystems.FileUtil;
0075: import org.openide.modules.InstalledFileLocator;
0076: import org.openide.modules.SpecificationVersion;
0077: import org.openide.util.Mutex;
0078: import org.openide.util.MutexException;
0079: import org.openide.util.NbBundle;
0080:
0081: /**
0082: * Represents one NetBeans platform, i.e. installation of the NB platform or IDE
0083: * or some derivative product.
0084: * Has a code id and can have associated sources and Javadoc, just like e.g. Java platforms.
0085: *
0086: * @author Jesse Glick
0087: */
0088: public final class NbPlatform {
0089:
0090: private static final String PLATFORM_PREFIX = "nbplatform."; // NOI18N
0091: private static final String PLATFORM_DEST_DIR_SUFFIX = ".netbeans.dest.dir"; // NOI18N
0092: private static final String PLATFORM_LABEL_SUFFIX = ".label"; // NOI18N
0093: private static final String PLATFORM_SOURCES_SUFFIX = ".sources"; // NOI18N
0094: private static final String PLATFORM_JAVADOC_SUFFIX = ".javadoc"; // NOI18N
0095: private static final String PLATFORM_HARNESS_DIR_SUFFIX = ".harness.dir"; // NOI18N
0096: public static final String PLATFORM_ID_DEFAULT = "default"; // NOI18N
0097:
0098: public static final String PROP_SOURCE_ROOTS = "sourceRoots"; // NOI18N
0099:
0100: private static Set<NbPlatform> platforms;
0101:
0102: private final PropertyChangeSupport pcs = new PropertyChangeSupport(
0103: this );
0104:
0105: // should proceed in chronological order so we can do compatibility tests with >=
0106: /** Unknown version - platform might be invalid, or just predate any 5.0 release version. */
0107: public static final int HARNESS_VERSION_UNKNOWN = 0;
0108: /** Harness version found in 5.0. */
0109: public static final int HARNESS_VERSION_50 = 1;
0110: /** Harness version found in 5.0 update 1 and 5.5. */
0111: public static final int HARNESS_VERSION_50u1 = 2;
0112: /** Harness version found in 5.5 update 1. */
0113: public static final int HARNESS_VERSION_55u1 = 3;
0114: /** Harness version found in 6.0. */
0115: public static final int HARNESS_VERSION_60 = 4;
0116: /** Harness version found in 6.1. */
0117: public static final int HARNESS_VERSION_61 = 5;
0118:
0119: /**
0120: * Reset cached info so unit tests can start from scratch.
0121: */
0122: public static void reset() {
0123: platforms = null;
0124: }
0125:
0126: /**
0127: * Get a set of all registered platforms.
0128: */
0129: public static synchronized Set<NbPlatform> getPlatforms() {
0130: return new HashSet<NbPlatform>(getPlatformsInternal());
0131: }
0132:
0133: private static Set<NbPlatform> getPlatformsInternal() {
0134: if (platforms == null) {
0135: platforms = new HashSet<NbPlatform>();
0136: Map<String, String> p = PropertyUtils
0137: .sequentialPropertyEvaluator(null,
0138: PropertyUtils.globalPropertyProvider())
0139: .getProperties();
0140: if (p == null) { // #115909
0141: p = Collections.emptyMap();
0142: }
0143: boolean foundDefault = false;
0144: for (Map.Entry<String, String> entry : p.entrySet()) {
0145: String key = entry.getKey();
0146: if (key.startsWith(PLATFORM_PREFIX)
0147: && key.endsWith(PLATFORM_DEST_DIR_SUFFIX)) {
0148: String id = key
0149: .substring(PLATFORM_PREFIX.length(), key
0150: .length()
0151: - PLATFORM_DEST_DIR_SUFFIX.length());
0152: String label = p.get(PLATFORM_PREFIX + id
0153: + PLATFORM_LABEL_SUFFIX);
0154: String destdir = entry.getValue();
0155: String harnessdir = p.get(PLATFORM_PREFIX + id
0156: + PLATFORM_HARNESS_DIR_SUFFIX);
0157: String sources = p.get(PLATFORM_PREFIX + id
0158: + PLATFORM_SOURCES_SUFFIX);
0159: String javadoc = p.get(PLATFORM_PREFIX + id
0160: + PLATFORM_JAVADOC_SUFFIX);
0161: File destdirF = FileUtil.normalizeFile(new File(
0162: destdir));
0163: File harness;
0164: if (harnessdir != null) {
0165: harness = FileUtil.normalizeFile(new File(
0166: harnessdir));
0167: } else {
0168: harness = findHarness(destdirF);
0169: }
0170: platforms.add(new NbPlatform(id, label, destdirF,
0171: harness, findURLs(sources),
0172: findURLs(javadoc)));
0173: foundDefault |= id.equals(PLATFORM_ID_DEFAULT);
0174: }
0175: }
0176: if (!foundDefault) {
0177: File loc = defaultPlatformLocation();
0178: if (loc != null) {
0179: platforms.add(new NbPlatform(PLATFORM_ID_DEFAULT,
0180: null, loc, findHarness(loc), new URL[0],
0181: new URL[0]));
0182: }
0183: }
0184: if (Util.err.isLoggable(ErrorManager.INFORMATIONAL)) {
0185: Util.err.log("NbPlatform initial list: " + platforms);
0186: }
0187: }
0188: return platforms;
0189: }
0190:
0191: /**
0192: * Get the default platform.
0193: * @return the default platform, if there is one (usually should be)
0194: */
0195: public static NbPlatform getDefaultPlatform() {
0196: return NbPlatform.getPlatformByID(PLATFORM_ID_DEFAULT);
0197: }
0198:
0199: /**
0200: * Get the location of the default platform, or null.
0201: */
0202: public static File defaultPlatformLocation() {
0203: // XXX cache the result?
0204: // Semi-arbitrary platform* component.
0205: File bootJar = InstalledFileLocator.getDefault().locate(
0206: "core/core.jar", "org.netbeans.core.startup", false); // NOI18N
0207: if (bootJar == null) {
0208: if (Util.err.isLoggable(ErrorManager.INFORMATIONAL)) {
0209: Util.err.log("no core/core.jar");
0210: }
0211: return null;
0212: }
0213: // Semi-arbitrary harness component.
0214: File harnessJar = InstalledFileLocator.getDefault().locate(
0215: "modules/org-netbeans-modules-apisupport-harness.jar",
0216: "org.netbeans.modules.apisupport.harness", false); // NOI18N
0217: if (harnessJar == null) {
0218: ErrorManager
0219: .getDefault()
0220: .log(
0221: ErrorManager.WARNING,
0222: "Cannot resolve default platform. " + // NOI18N
0223: "Probably either \"org.netbeans.modules.apisupport.harness\" module is missing or is corrupted."); // NOI18N
0224: return null;
0225: }
0226: File loc = harnessJar.getParentFile().getParentFile()
0227: .getParentFile();
0228: try {
0229: if (!loc.getCanonicalFile().equals(
0230: bootJar.getParentFile().getParentFile()
0231: .getParentFile().getCanonicalFile())) {
0232: // Unusual installation structure, punt.
0233: if (Util.err.isLoggable(ErrorManager.INFORMATIONAL)) {
0234: Util.err
0235: .log("core.jar & harness.jar locations do not match: "
0236: + bootJar + " vs. " + harnessJar);
0237: }
0238: return null;
0239: }
0240: } catch (IOException x) {
0241: ErrorManager.getDefault().notify(
0242: ErrorManager.INFORMATIONAL, x);
0243: }
0244: // Looks good.
0245: return FileUtil.normalizeFile(loc);
0246: }
0247:
0248: /**
0249: * Get any sources which should by default be associated with the default platform.
0250: */
0251: private static URL[] defaultPlatformSources(File loc) {
0252: if (loc.getName().equals("netbeans")
0253: && loc.getParentFile().getName().equals("nbbuild")) { // NOI18N
0254: try {
0255: return new URL[] { loc.getParentFile().getParentFile()
0256: .toURI().toURL() };
0257: } catch (MalformedURLException e) {
0258: assert false : e;
0259: }
0260: }
0261: return new URL[0];
0262: }
0263:
0264: /**
0265: * Get any Javadoc which should by default be associated with the default platform.
0266: */
0267: private static URL[] defaultPlatformJavadoc() {
0268: File apidocsZip = InstalledFileLocator.getDefault().locate(
0269: "docs/NetBeansAPIs.zip",
0270: "org.netbeans.modules.apisupport.apidocs", true); // NOI18N
0271: if (apidocsZip != null) {
0272: return new URL[] { Util.urlForJar(apidocsZip) };
0273: } else {
0274: return new URL[0];
0275: }
0276: }
0277:
0278: /**
0279: * Find a platform by its ID.
0280: * @param id an ID (as in {@link #getID})
0281: * @return the platform with that ID, or null
0282: */
0283: public static synchronized NbPlatform getPlatformByID(String id) {
0284: for (NbPlatform p : getPlatformsInternal()) {
0285: if (p.getID().equals(id)) {
0286: return p;
0287: }
0288: }
0289: return null;
0290: }
0291:
0292: /**
0293: * Find a platform by its installation directory.
0294: * If there is a registered platform for that directory, returns it.
0295: * Otherwise will create an anonymous platform ({@link #getID} will be null).
0296: * An anonymous platform might have sources associated with it;
0297: * currently this will be true in case the dest dir is nbbuild/netbeans/ inside a netbeans.org checkout.
0298: * @param the installation directory (as in {@link #getDestDir})
0299: * @return the platform with that destination directory
0300: */
0301: public static synchronized NbPlatform getPlatformByDestDir(
0302: File destDir) {
0303: for (NbPlatform p : getPlatformsInternal()) {
0304: if (p.getDestDir().equals(destDir)) {
0305: return p;
0306: }
0307: }
0308: URL[] sources = new URL[0];
0309: if (destDir.getName().equals("netbeans")) { // NOI18N
0310: File parent = destDir.getParentFile();
0311: if (parent != null && parent.getName().equals("nbbuild")) { // NOI18N
0312: File super parent = parent.getParentFile();
0313: if (super parent != null
0314: && ModuleList.isNetBeansOrg(super parent)) {
0315: sources = new URL[] { Util.urlForDir(super parent) };
0316: }
0317: }
0318: }
0319: // XXX might also check OpenProjectList for NbModuleProject's and/or SuiteProject's with a matching
0320: // dest dir and look up property 'sources' to use; TBD whether Javadoc could also be handled in a
0321: // similar way
0322: return new NbPlatform(null, null, destDir,
0323: findHarness(destDir), sources, new URL[0]);
0324: }
0325:
0326: /**
0327: * Find the location of the harness inside a platform.
0328: * Guaranteed to be a child directory (but might not exist yet).
0329: */
0330: private static File findHarness(File destDir) {
0331: File[] kids = destDir.listFiles();
0332: if (kids != null) {
0333: for (int i = 0; i < kids.length; i++) {
0334: if (isHarness(kids[i])) {
0335: return kids[i];
0336: }
0337: }
0338: }
0339: return new File(destDir, "harness"); // NOI18N
0340: }
0341:
0342: /**
0343: * Check whether a given directory is really a valid harness.
0344: */
0345: public static boolean isHarness(File dir) {
0346: return new File(dir, "modules" + File.separatorChar
0347: + "org-netbeans-modules-apisupport-harness.jar")
0348: .isFile(); // NOI18N
0349: }
0350:
0351: /**
0352: * Returns whether the platform within the given directory is already
0353: * registered.
0354: */
0355: public static synchronized boolean contains(File destDir) {
0356: boolean contains = false;
0357: for (NbPlatform p : getPlatformsInternal()) {
0358: if (p.getDestDir().equals(destDir)) {
0359: contains = true;
0360: break;
0361: }
0362: }
0363: return contains;
0364: }
0365:
0366: /**
0367: * Register a new platform.
0368: * @param id unique ID string for the platform
0369: * @param destdir destination directory (i.e. top-level directory beneath which there are clusters)
0370: * @param label display label
0371: * @return the created platform
0372: * @throws IOException in case of problems (e.g. destination directory does not exist)
0373: */
0374: public static NbPlatform addPlatform(final String id,
0375: final File destdir, final String label) throws IOException {
0376: return addPlatform(id, destdir, findHarness(destdir), label);
0377: }
0378:
0379: /**
0380: * Register a new platform.
0381: * @param id unique ID string for the platform
0382: * @param destdir destination directory (i.e. top-level directory beneath which there are clusters)
0383: * @param harness harness directory
0384: * @param label display label
0385: * @return the created platform
0386: * @throws IOException in case of problems (e.g. destination directory does not exist)
0387: */
0388: public static NbPlatform addPlatform(final String id,
0389: final File destdir, final File harness, final String label)
0390: throws IOException {
0391: try {
0392: ProjectManager.mutex().writeAccess(
0393: new Mutex.ExceptionAction<Void>() {
0394: public Void run() throws IOException {
0395: if (getPlatformByID(id) != null) {
0396: throw new IOException("ID " + id
0397: + " already taken");
0398: }
0399: EditableProperties props = PropertyUtils
0400: .getGlobalProperties();
0401: String plafDestDir = PLATFORM_PREFIX + id
0402: + PLATFORM_DEST_DIR_SUFFIX;
0403: props.setProperty(plafDestDir, destdir
0404: .getAbsolutePath());
0405: if (!destdir.isDirectory()) {
0406: throw new FileNotFoundException(destdir
0407: .getAbsolutePath());
0408: }
0409: storeHarnessLocation(id, destdir, harness,
0410: props);
0411: props.setProperty(PLATFORM_PREFIX + id
0412: + PLATFORM_LABEL_SUFFIX, label);
0413: PropertyUtils.putGlobalProperties(props);
0414: return null;
0415: }
0416: });
0417: } catch (MutexException e) {
0418: throw (IOException) e.getException();
0419: }
0420: NbPlatform plaf = new NbPlatform(id, label, FileUtil
0421: .normalizeFile(destdir), harness, findURLs(null),
0422: findURLs(null));
0423: synchronized (NbPlatform.class) {
0424: getPlatformsInternal().add(plaf);
0425: }
0426: if (Util.err.isLoggable(ErrorManager.INFORMATIONAL)) {
0427: Util.err.log("NbPlatform added: " + plaf);
0428: }
0429: return plaf;
0430: }
0431:
0432: private static void storeHarnessLocation(String id, File destdir,
0433: File harness, EditableProperties props) {
0434: String harnessDirKey = PLATFORM_PREFIX + id
0435: + PLATFORM_HARNESS_DIR_SUFFIX;
0436: if (harness.equals(findHarness(destdir))) {
0437: // Common case.
0438: String plafDestDir = PLATFORM_PREFIX + id
0439: + PLATFORM_DEST_DIR_SUFFIX;
0440: props.setProperty(harnessDirKey, "${" + plafDestDir + "}/"
0441: + harness.getName()); // NOI18N
0442: } else if (getDefaultPlatform() != null
0443: && harness.equals(getDefaultPlatform()
0444: .getHarnessLocation())) {
0445: // Also common.
0446: props.setProperty(harnessDirKey, "${" + PLATFORM_PREFIX
0447: + PLATFORM_ID_DEFAULT + PLATFORM_HARNESS_DIR_SUFFIX
0448: + "}"); // NOI18N
0449: } else {
0450: // Some random location.
0451: props.setProperty(harnessDirKey, harness.getAbsolutePath());
0452: }
0453: }
0454:
0455: public static void removePlatform(final NbPlatform plaf)
0456: throws IOException {
0457: try {
0458: ProjectManager.mutex().writeAccess(
0459: new Mutex.ExceptionAction<Void>() {
0460: public Void run() throws IOException {
0461: EditableProperties props = PropertyUtils
0462: .getGlobalProperties();
0463: props.remove(PLATFORM_PREFIX + plaf.getID()
0464: + PLATFORM_DEST_DIR_SUFFIX);
0465: props.remove(PLATFORM_PREFIX + plaf.getID()
0466: + PLATFORM_HARNESS_DIR_SUFFIX);
0467: props.remove(PLATFORM_PREFIX + plaf.getID()
0468: + PLATFORM_LABEL_SUFFIX);
0469: props.remove(PLATFORM_PREFIX + plaf.getID()
0470: + PLATFORM_SOURCES_SUFFIX);
0471: props.remove(PLATFORM_PREFIX + plaf.getID()
0472: + PLATFORM_JAVADOC_SUFFIX);
0473: PropertyUtils.putGlobalProperties(props);
0474: return null;
0475: }
0476: });
0477: } catch (MutexException e) {
0478: throw (IOException) e.getException();
0479: }
0480: synchronized (NbPlatform.class) {
0481: getPlatformsInternal().remove(plaf);
0482: }
0483: if (Util.err.isLoggable(ErrorManager.INFORMATIONAL)) {
0484: Util.err.log("NbPlatform removed: " + plaf);
0485: }
0486: }
0487:
0488: private final String id;
0489: private String label;
0490: private File nbdestdir;
0491: private File harness;
0492: private URL[] sourceRoots;
0493: private URL[] javadocRoots;
0494: private List<ModuleList> listsForSources;
0495: private int harnessVersion = -1;
0496:
0497: private NbPlatform(String id, String label, File nbdestdir,
0498: File harness, URL[] sources, URL[] javadoc) {
0499: this .id = id;
0500: this .label = label;
0501: this .nbdestdir = nbdestdir;
0502: this .harness = harness;
0503: this .sourceRoots = sources;
0504: this .javadocRoots = javadoc;
0505: }
0506:
0507: static URL[] findURLs(final String path) {
0508: if (path == null) {
0509: return new URL[0];
0510: }
0511: String[] pieces = PropertyUtils.tokenizePath(path);
0512: URL[] urls = new URL[pieces.length];
0513: for (int i = 0; i < pieces.length; i++) {
0514: // XXX perhaps also support http: URLs somehow?
0515: urls[i] = Util.urlForDirOrJar(FileUtil
0516: .normalizeFile(new File(pieces[i])));
0517: }
0518: return urls;
0519: }
0520:
0521: /**
0522: * Get a unique ID for this platform.
0523: * Used e.g. in <code>nbplatform.active</code> in <code>platform.properties</code>.
0524: * @return a unique ID, or <code>null</code> for <em>anonymous</em>
0525: * platforms (see {@link #getPlatformByDestDir}).
0526: */
0527: public String getID() {
0528: return id;
0529: }
0530:
0531: /**
0532: * Check if this is the default platform.
0533: * @return true for the one default platform
0534: */
0535: public boolean isDefault() {
0536: return PLATFORM_ID_DEFAULT.equals(id);
0537: }
0538:
0539: /**
0540: * Get a display label suitable for the user.
0541: * If not set, {@link #computeDisplayName} is used.
0542: * The {@link #isDefault default platform} is specially marked.
0543: * @return a display label
0544: */
0545: public String getLabel() {
0546: if (label == null) {
0547: try {
0548: label = isValid() ? computeDisplayName(nbdestdir)
0549: : NbBundle.getMessage(NbPlatform.class,
0550: "MSG_InvalidPlatform", // NOI18N
0551: getDestDir().getAbsolutePath());
0552: } catch (IOException e) {
0553: Util.err.notify(ErrorManager.INFORMATIONAL, e);
0554: label = nbdestdir.getAbsolutePath();
0555: }
0556: }
0557: if (isDefault()) {
0558: return NbBundle.getMessage(NbPlatform.class,
0559: "LBL_default_platform", label);
0560: } else {
0561: return label;
0562: }
0563: }
0564:
0565: /**
0566: * Get the installation directory.
0567: * @return the installation directory
0568: */
0569: public File getDestDir() {
0570: return nbdestdir;
0571: }
0572:
0573: public void setDestDir(File destdir) {
0574: this .nbdestdir = destdir;
0575: // XXX write build.properties too
0576: }
0577:
0578: /**
0579: * Get associated source roots for this platform.
0580: * Each root could be a netbeans.org source checkout or a module suite project directory.
0581: * @return a list of source root URLs (may be empty but not null)
0582: */
0583: public URL[] getSourceRoots() {
0584: if (sourceRoots.length == 0 && isDefault()) {
0585: return defaultPlatformSources(getDestDir());
0586: } else {
0587: return sourceRoots;
0588: }
0589: }
0590:
0591: private void maybeUpdateDefaultPlatformSources() {
0592: if (sourceRoots.length == 0 && isDefault()) {
0593: sourceRoots = defaultPlatformSources(getDestDir());
0594: pcs.firePropertyChange(PROP_SOURCE_ROOTS, null, null);
0595: }
0596: }
0597:
0598: /**
0599: * Add given source root to the current source root list and save the
0600: * result into the global properties in the <em>userdir</em> (see {@link
0601: * PropertyUtils#putGlobalProperties})
0602: */
0603: public void addSourceRoot(URL root) throws IOException {
0604: maybeUpdateDefaultPlatformSources();
0605: URL[] newSourceRoots = new URL[sourceRoots.length + 1];
0606: System.arraycopy(sourceRoots, 0, newSourceRoots, 0,
0607: sourceRoots.length);
0608: newSourceRoots[sourceRoots.length] = root;
0609: setSourceRoots(newSourceRoots);
0610: }
0611:
0612: /**
0613: * Remove given source roots from the current source root list and save the
0614: * result into the global properties in the <em>userdir</em> (see {@link
0615: * PropertyUtils#putGlobalProperties})
0616: */
0617: public void removeSourceRoots(URL[] urlsToRemove)
0618: throws IOException {
0619: maybeUpdateDefaultPlatformSources();
0620: Collection<URL> newSources = new ArrayList<URL>(Arrays
0621: .asList(sourceRoots));
0622: newSources.removeAll(Arrays.asList(urlsToRemove));
0623: URL[] sources = new URL[newSources.size()];
0624: setSourceRoots(newSources.toArray(sources));
0625: }
0626:
0627: public void moveSourceRootUp(int indexToUp) throws IOException {
0628: maybeUpdateDefaultPlatformSources();
0629: if (indexToUp <= 0) {
0630: return; // nothing needs to be done
0631: }
0632: URL[] newSourceRoots = new URL[sourceRoots.length];
0633: System.arraycopy(sourceRoots, 0, newSourceRoots, 0,
0634: sourceRoots.length);
0635: newSourceRoots[indexToUp - 1] = sourceRoots[indexToUp];
0636: newSourceRoots[indexToUp] = sourceRoots[indexToUp - 1];
0637: setSourceRoots(newSourceRoots);
0638: }
0639:
0640: public void moveSourceRootDown(int indexToDown) throws IOException {
0641: maybeUpdateDefaultPlatformSources();
0642: if (indexToDown >= (sourceRoots.length - 1)) {
0643: return; // nothing needs to be done
0644: }
0645: URL[] newSourceRoots = new URL[sourceRoots.length];
0646: System.arraycopy(sourceRoots, 0, newSourceRoots, 0,
0647: sourceRoots.length);
0648: newSourceRoots[indexToDown + 1] = sourceRoots[indexToDown];
0649: newSourceRoots[indexToDown] = sourceRoots[indexToDown + 1];
0650: setSourceRoots(newSourceRoots);
0651: }
0652:
0653: public void setSourceRoots(URL[] roots) throws IOException {
0654: putGlobalProperty(PLATFORM_PREFIX + getID()
0655: + PLATFORM_SOURCES_SUFFIX, urlsToAntPath(roots));
0656: sourceRoots = roots;
0657: pcs.firePropertyChange(PROP_SOURCE_ROOTS, null, null);
0658: listsForSources = null;
0659: }
0660:
0661: /**
0662: * Get associated Javadoc roots for this platform.
0663: * Each root may contain some Javadoc sets in the usual format as subdirectories,
0664: * where the subdirectory is named acc. to the code name base of the module it
0665: * is documenting (using '-' in place of '.').
0666: * @return a list of Javadoc root URLs (may be empty but not null)
0667: */
0668: public URL[] getJavadocRoots() {
0669: if (javadocRoots.length == 0 && isDefault()) {
0670: return defaultPlatformJavadoc();
0671: } else {
0672: return javadocRoots;
0673: }
0674: }
0675:
0676: private void maybeUpdateDefaultPlatformJavadoc() {
0677: if (javadocRoots.length == 0 && isDefault()) {
0678: javadocRoots = defaultPlatformJavadoc();
0679: }
0680: }
0681:
0682: /**
0683: * Add given javadoc root to the current javadoc root list and save the
0684: * result into the global properties in the <em>userdir</em> (see {@link
0685: * PropertyUtils#putGlobalProperties})
0686: */
0687: public void addJavadocRoot(URL root) throws IOException {
0688: maybeUpdateDefaultPlatformJavadoc();
0689: URL[] newJavadocRoots = new URL[javadocRoots.length + 1];
0690: System.arraycopy(javadocRoots, 0, newJavadocRoots, 0,
0691: javadocRoots.length);
0692: newJavadocRoots[javadocRoots.length] = root;
0693: setJavadocRoots(newJavadocRoots);
0694: }
0695:
0696: /**
0697: * Remove given javadoc roots from the current javadoc root list and save
0698: * the result into the global properties in the <em>userdir</em> (see
0699: * {@link PropertyUtils#putGlobalProperties})
0700: */
0701: public void removeJavadocRoots(URL[] urlsToRemove)
0702: throws IOException {
0703: maybeUpdateDefaultPlatformJavadoc();
0704: Collection<URL> newJavadocs = new ArrayList<URL>(Arrays
0705: .asList(javadocRoots));
0706: newJavadocs.removeAll(Arrays.asList(urlsToRemove));
0707: URL[] javadocs = new URL[newJavadocs.size()];
0708: setJavadocRoots(newJavadocs.toArray(javadocs));
0709: }
0710:
0711: public void moveJavadocRootUp(int indexToUp) throws IOException {
0712: maybeUpdateDefaultPlatformJavadoc();
0713: if (indexToUp <= 0) {
0714: return; // nothing needs to be done
0715: }
0716: URL[] newJavadocRoots = new URL[javadocRoots.length];
0717: System.arraycopy(javadocRoots, 0, newJavadocRoots, 0,
0718: javadocRoots.length);
0719: newJavadocRoots[indexToUp - 1] = javadocRoots[indexToUp];
0720: newJavadocRoots[indexToUp] = javadocRoots[indexToUp - 1];
0721: setJavadocRoots(newJavadocRoots);
0722: }
0723:
0724: public void moveJavadocRootDown(int indexToDown) throws IOException {
0725: maybeUpdateDefaultPlatformJavadoc();
0726: if (indexToDown >= (javadocRoots.length - 1)) {
0727: return; // nothing needs to be done
0728: }
0729: URL[] newJavadocRoots = new URL[javadocRoots.length];
0730: System.arraycopy(javadocRoots, 0, newJavadocRoots, 0,
0731: javadocRoots.length);
0732: newJavadocRoots[indexToDown + 1] = javadocRoots[indexToDown];
0733: newJavadocRoots[indexToDown] = javadocRoots[indexToDown + 1];
0734: setJavadocRoots(newJavadocRoots);
0735: }
0736:
0737: public void setJavadocRoots(URL[] roots) throws IOException {
0738: putGlobalProperty(PLATFORM_PREFIX + getID()
0739: + PLATFORM_JAVADOC_SUFFIX, urlsToAntPath(roots));
0740: javadocRoots = roots;
0741: }
0742:
0743: /**
0744: * Test whether this platform is valid or not. See
0745: * {@link #isPlatformDirectory}
0746: */
0747: public boolean isValid() {
0748: return NbPlatform.isPlatformDirectory(getDestDir());
0749: }
0750:
0751: static String urlsToAntPath(final URL[] urls) {
0752: StringBuffer path = new StringBuffer();
0753: for (int i = 0; i < urls.length; i++) {
0754: if (urls[i].getProtocol().equals("jar")) { // NOI18N
0755: path.append(urlToAntPath(FileUtil
0756: .getArchiveFile(urls[i])));
0757: } else {
0758: path.append(urlToAntPath(urls[i]));
0759: }
0760: if (i != urls.length - 1) {
0761: path.append(':'); // NOI18N
0762: }
0763: }
0764: return path.toString();
0765: }
0766:
0767: private static String urlToAntPath(final URL url) {
0768: return new File(URI.create(url.toExternalForm()))
0769: .getAbsolutePath();
0770: }
0771:
0772: private void putGlobalProperty(final String key, final String value)
0773: throws IOException {
0774: try {
0775: ProjectManager.mutex().writeAccess(
0776: new Mutex.ExceptionAction<Void>() {
0777: public Void run() throws IOException {
0778: EditableProperties props = PropertyUtils
0779: .getGlobalProperties();
0780: if ("".equals(value)) { // NOI18N
0781: props.remove(key);
0782: } else {
0783: props.setProperty(key, value);
0784: }
0785: PropertyUtils.putGlobalProperties(props);
0786: return null;
0787: }
0788: });
0789: } catch (MutexException e) {
0790: throw (IOException) e.getException();
0791: }
0792: }
0793:
0794: /**
0795: * Find sources for a module JAR file contained in this destination directory.
0796: * @param jar a JAR file in the destination directory
0797: * @return the directory of sources for this module (a project directory), or null
0798: */
0799: public File getSourceLocationOfModule(File jar) {
0800: if (listsForSources == null) {
0801: listsForSources = new ArrayList<ModuleList>();
0802: for (URL u : getSourceRoots()) {
0803: if (!u.getProtocol().equals("file")) { // NOI18N
0804: continue;
0805: }
0806: File dir = new File(URI.create(u.toExternalForm()));
0807: if (dir.isDirectory()) {
0808: try {
0809: if (ModuleList.isNetBeansOrg(dir)) {
0810: listsForSources
0811: .add(ModuleList
0812: .findOrCreateModuleListFromNetBeansOrgSources(dir));
0813: } else {
0814: listsForSources
0815: .add(ModuleList
0816: .findOrCreateModuleListFromSuiteWithoutBinaries(dir));
0817: }
0818: } catch (IOException e) {
0819: Util.err.notify(ErrorManager.INFORMATIONAL, e);
0820: }
0821: }
0822: }
0823: }
0824: for (ModuleList l : listsForSources) {
0825: for (ModuleEntry entry : l.getAllEntriesSoft()) {
0826: // XXX should be more strict (e.g. compare also clusters)
0827: if (!entry.getJarLocation().getName().equals(
0828: jar.getName())) {
0829: continue;
0830: }
0831: File src = entry.getSourceLocation();
0832: if (src != null && src.isDirectory()) {
0833: return src;
0834: }
0835: }
0836: for (ModuleEntry entry : l.getAllEntries()) {
0837: if (!entry.getJarLocation().getName().equals(
0838: jar.getName())) {
0839: continue;
0840: }
0841: File src = entry.getSourceLocation();
0842: if (src != null && src.isDirectory()) {
0843: return src;
0844: }
0845: }
0846: }
0847: return null;
0848: }
0849:
0850: /**
0851: * Returns (naturally sorted) array of all module entries pertaining to
0852: * <code>this</code> NetBeans platform. This is just a convenient delegate
0853: * to the {@link ModuleList#findOrCreateModuleListFromBinaries}.
0854: */
0855: public ModuleEntry[] getModules() {
0856: try {
0857: SortedSet<ModuleEntry> set = new TreeSet<ModuleEntry>(
0858: ModuleList.findOrCreateModuleListFromBinaries(
0859: getDestDir()).getAllEntriesSoft());
0860: ModuleEntry[] entries = new ModuleEntry[set.size()];
0861: set.toArray(entries);
0862: return entries;
0863: } catch (IOException e) {
0864: Util.err.notify(e);
0865: return new ModuleEntry[0];
0866: }
0867: }
0868:
0869: /**
0870: * Gets a module from the platform by name.
0871: */
0872: public ModuleEntry getModule(String cnb) {
0873: try {
0874: return ModuleList.findOrCreateModuleListFromBinaries(
0875: getDestDir()).getEntry(cnb);
0876: } catch (IOException e) {
0877: Util.err.notify(e);
0878: return null;
0879: }
0880: }
0881:
0882: private static File findCoreJar(File destdir) {
0883: File[] subdirs = destdir.listFiles();
0884: if (subdirs != null) {
0885: for (int i = 0; i < subdirs.length; i++) {
0886: if (!subdirs[i].isDirectory()) {
0887: continue;
0888: }
0889: if (!subdirs[i].getName().startsWith("platform")) { // NOI18N
0890: continue;
0891: }
0892: File coreJar = new File(subdirs[i], "core"
0893: + File.separatorChar + "core.jar"); // NOI18N
0894: if (coreJar.isFile()) {
0895: return coreJar;
0896: }
0897: }
0898: }
0899: return null;
0900: }
0901:
0902: /**
0903: * Check whether a given directory on disk is a valid destdir as per {@link #getDestDir}.
0904: * @param destdir a candidate directory
0905: * @return true if it can be used as a platform
0906: */
0907: public static boolean isPlatformDirectory(File destdir) {
0908: return findCoreJar(destdir) != null;
0909: }
0910:
0911: public static boolean isSupportedPlatform(File destdir) {
0912: boolean valid = false;
0913: File coreJar = findCoreJar(destdir);
0914: if (coreJar != null) {
0915: String platformDir = coreJar.getParentFile()
0916: .getParentFile().getName();
0917: assert platformDir.startsWith("platform"); // NOI18N
0918: int version = Integer.parseInt(platformDir.substring(8)); // 8 == "platform".length
0919: valid = version >= 6;
0920: }
0921: return valid;
0922: }
0923:
0924: /**
0925: * Find a display name for a NetBeans platform on disk.
0926: * @param destdir a dir passing {@link #isPlatformDirectory}
0927: * @return a display name
0928: * @throws IllegalArgumentException if {@link #isPlatformDirectory} was false
0929: * @throws IOException if its labelling info could not be read
0930: */
0931: public static String computeDisplayName(File destdir)
0932: throws IOException {
0933: File coreJar = findCoreJar(destdir);
0934: if (coreJar == null) {
0935: throw new IllegalArgumentException(destdir
0936: .getAbsolutePath());
0937: }
0938: String currVer, implVers;
0939: JarFile jf = new JarFile(coreJar);
0940: try {
0941: currVer = findCurrVer(jf, "");
0942: if (currVer == null) {
0943: throw new IOException(coreJar.getAbsolutePath());
0944: }
0945: implVers = jf.getManifest().getMainAttributes().getValue(
0946: "OpenIDE-Module-Implementation-Version"); // NOI18N
0947: if (implVers == null) {
0948: throw new IOException(coreJar.getAbsolutePath());
0949: }
0950: } finally {
0951: jf.close();
0952: }
0953: // Also check in localizing bundles for 'currentVersion', since it may be branded.
0954: // We do not know what the runtime branding will be, so look for anything.
0955: File[] clusters = destdir.listFiles();
0956: BRANDED_CURR_VER: if (clusters != null) {
0957: for (int i = 0; i < clusters.length; i++) {
0958: File coreLocaleDir = new File(clusters[i], "core"
0959: + File.separatorChar + "locale"); // NOI18N
0960: if (!coreLocaleDir.isDirectory()) {
0961: continue;
0962: }
0963: String[] kids = coreLocaleDir.list();
0964: if (kids != null) {
0965: for (int j = 0; j < kids.length; j++) {
0966: String name = kids[j];
0967: String prefix = "core"; // NOI18N
0968: String suffix = ".jar"; // NOI18N
0969: if (!name.startsWith(prefix)
0970: || !name.endsWith(suffix)) {
0971: continue;
0972: }
0973: String infix = name.substring(prefix.length(),
0974: name.length() - suffix.length());
0975: int uscore = infix.lastIndexOf('_');
0976: if (uscore == -1) {
0977: // Malformed.
0978: continue;
0979: }
0980: String lastPiece = infix.substring(uscore + 1);
0981: if (Arrays.asList(Locale.getISOCountries())
0982: .contains(lastPiece)
0983: || (!lastPiece.equals("nb") && Arrays
0984: .asList(
0985: Locale
0986: .getISOLanguages())
0987: .contains(lastPiece))) { // NOI18N
0988: // Probably a localization, not a branding... so skip it. (We want to show English only.)
0989: // But hardcode support for branding 'nb' since this is also Norwegian Bokmal, apparently!
0990: // XXX should this try to use Locale.getDefault() localization if possible?
0991: continue;
0992: }
0993: jf = new JarFile(new File(coreLocaleDir, name));
0994: try {
0995: String brandedCurrVer = findCurrVer(jf,
0996: infix);
0997: if (brandedCurrVer != null) {
0998: currVer = brandedCurrVer;
0999: break BRANDED_CURR_VER;
1000: }
1001: } finally {
1002: jf.close();
1003: }
1004: }
1005: }
1006: }
1007: }
1008: return MessageFormat.format(currVer, new Object[] { implVers });
1009: }
1010:
1011: private static String findCurrVer(JarFile jar, String infix)
1012: throws IOException {
1013: // first try to find the Bundle for 5.0+ (after openide split)
1014: ZipEntry bundle = jar
1015: .getEntry("org/netbeans/core/startup/Bundle" + infix
1016: + ".properties"); // NOI18N
1017: if (bundle == null) {
1018: // might be <5.0 (before openide split)
1019: bundle = jar.getEntry("org/netbeans/core/Bundle" + infix
1020: + ".properties"); // NOI18N
1021: }
1022: if (bundle == null) {
1023: return null;
1024: }
1025: Properties props = new Properties();
1026: InputStream is = jar.getInputStream(bundle);
1027: try {
1028: props.load(is);
1029: } finally {
1030: is.close();
1031: }
1032: return props.getProperty("currentVersion"); // NOI18N
1033: }
1034:
1035: /**
1036: * Returns whether the given label (see {@link #getLabel}) is valid.
1037: * <em>Valid</em> label must be non-null and must not be used by any
1038: * already defined platform.
1039: */
1040: public static boolean isLabelValid(String supposedLabel) {
1041: if (supposedLabel == null) {
1042: return false;
1043: }
1044: for (NbPlatform p : NbPlatform.getPlatforms()) {
1045: String label = p.getLabel();
1046: if (supposedLabel.equals(label)) {
1047: return false;
1048: }
1049: }
1050: return true;
1051: }
1052:
1053: public @Override
1054: String toString() {
1055: return "NbPlatform[" + getID() + ":" + getDestDir()
1056: + ";sources=" + Arrays.asList(getSourceRoots())
1057: + ";javadoc=" + Arrays.asList(getJavadocRoots()) + "]"; // NOI18N;
1058: }
1059:
1060: /**
1061: * Get the version of this platform's harness.
1062: */
1063: public int getHarnessVersion() {
1064: if (harnessVersion != -1) {
1065: return harnessVersion;
1066: }
1067: if (!isValid()) {
1068: return harnessVersion = HARNESS_VERSION_UNKNOWN;
1069: }
1070: File harnessJar = new File(harness, "modules"
1071: + File.separatorChar
1072: + "org-netbeans-modules-apisupport-harness.jar"); // NOI18N
1073: if (harnessJar.isFile()) {
1074: try {
1075: JarFile jf = new JarFile(harnessJar);
1076: try {
1077: String spec = jf
1078: .getManifest()
1079: .getMainAttributes()
1080: .getValue(
1081: ManifestManager.OPENIDE_MODULE_SPECIFICATION_VERSION);
1082: if (spec != null) {
1083: SpecificationVersion v = new SpecificationVersion(
1084: spec);
1085: if (v
1086: .compareTo(new SpecificationVersion(
1087: "1.11")) >= 0) { // NOI18N
1088: return harnessVersion = HARNESS_VERSION_61;
1089: } else if (v
1090: .compareTo(new SpecificationVersion(
1091: "1.10")) >= 0) { // NOI18N
1092: return harnessVersion = HARNESS_VERSION_60;
1093: } else if (v
1094: .compareTo(new SpecificationVersion(
1095: "1.9")) >= 0) { // NOI18N
1096: return harnessVersion = HARNESS_VERSION_55u1;
1097: } else if (v
1098: .compareTo(new SpecificationVersion(
1099: "1.7")) >= 0) { // NOI18N
1100: return harnessVersion = HARNESS_VERSION_50u1;
1101: } else if (v
1102: .compareTo(new SpecificationVersion(
1103: "1.6")) >= 0) { // NOI18N
1104: return harnessVersion = HARNESS_VERSION_50;
1105: } // earlier than beta2? who knows...
1106: }
1107: } finally {
1108: jf.close();
1109: }
1110: } catch (IOException e) {
1111: Util.err.notify(ErrorManager.INFORMATIONAL, e);
1112: } catch (NumberFormatException e) {
1113: Util.err.notify(ErrorManager.INFORMATIONAL, e);
1114: }
1115: }
1116: return harnessVersion = HARNESS_VERSION_UNKNOWN;
1117: }
1118:
1119: /**
1120: * Get the current location of this platform's harness
1121: */
1122: public File getHarnessLocation() {
1123: return harness;
1124: }
1125:
1126: /**
1127: * Get the location of the harness bundled with this platform.
1128: */
1129: public File getBundledHarnessLocation() {
1130: return findHarness(nbdestdir);
1131: }
1132:
1133: /**
1134: * Set a new location for this platform's harness.
1135: */
1136: public void setHarnessLocation(final File harness)
1137: throws IOException {
1138: if (harness.equals(this .harness)) {
1139: return;
1140: }
1141: try {
1142: ProjectManager.mutex().writeAccess(
1143: new Mutex.ExceptionAction<Void>() {
1144: public Void run() throws IOException {
1145: EditableProperties props = PropertyUtils
1146: .getGlobalProperties();
1147: storeHarnessLocation(id, nbdestdir,
1148: harness, props);
1149: PropertyUtils.putGlobalProperties(props);
1150: return null;
1151: }
1152: });
1153: } catch (MutexException e) {
1154: throw (IOException) e.getException();
1155: }
1156: this .harness = harness;
1157: harnessVersion = -1;
1158: }
1159:
1160: /**
1161: * Gets a quick display name for the <em>version</em> of a harness.
1162: * @param {@link #HARNESS_VERSION_50} etc.
1163: * @return a short display name
1164: */
1165: public static String getHarnessVersionDisplayName(int version) {
1166: switch (version) {
1167: case HARNESS_VERSION_50:
1168: return NbBundle.getMessage(NbPlatform.class,
1169: "LBL_harness_version_5.0");
1170: case HARNESS_VERSION_50u1:
1171: return NbBundle.getMessage(NbPlatform.class,
1172: "LBL_harness_version_5.0u1");
1173: case HARNESS_VERSION_55u1:
1174: return NbBundle.getMessage(NbPlatform.class,
1175: "LBL_harness_version_5.5u1");
1176: case HARNESS_VERSION_60:
1177: return NbBundle.getMessage(NbPlatform.class,
1178: "LBL_harness_version_6.0");
1179: case HARNESS_VERSION_61:
1180: return NbBundle.getMessage(NbPlatform.class,
1181: "LBL_harness_version_6.1");
1182: default:
1183: assert version == HARNESS_VERSION_UNKNOWN;
1184: return NbBundle.getMessage(NbPlatform.class,
1185: "LBL_harness_version_unknown");
1186: }
1187: }
1188:
1189: public void addPropertyChangeListener(
1190: PropertyChangeListener listener) {
1191: pcs.addPropertyChangeListener(listener);
1192: }
1193:
1194: public void removePropertyChangeListener(
1195: PropertyChangeListener listener) {
1196: pcs.removePropertyChangeListener(listener);
1197: }
1198:
1199: }
|