001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans;
043:
044: import java.io.*;
045: import java.util.*;
046: import java.util.logging.Level;
047: import java.util.logging.Logger;
048: import org.openide.util.*;
049: import org.openide.modules.*;
050:
051: /** Static utility methods for use within this package.
052: * @author Jesse Glick
053: */
054: public abstract class Util {
055:
056: // Prevent accidental subclassing.
057: private Util() {
058: }
059:
060: /** Log everything happening in the module system. */
061: public static final Logger err = Logger
062: .getLogger("org.netbeans.core.modules"); // NOI18N
063:
064: /**
065: * Make a temporary copy of a JAR file.
066: */
067: static File makeTempJar(File moduleFile) throws IOException {
068: String prefix = moduleFile.getName();
069: if (prefix.endsWith(".jar") || prefix.endsWith(".JAR")) { // NOI18N
070: prefix = prefix.substring(0, prefix.length() - 4);
071: }
072: if (prefix.length() < 3)
073: prefix += '.';
074: if (prefix.length() < 3)
075: prefix += '.';
076: if (prefix.length() < 3)
077: prefix += '.';
078: String suffix = "-test.jar"; // NOI18N
079: File physicalModuleFile = File.createTempFile(prefix, suffix);
080: physicalModuleFile.deleteOnExit();
081: InputStream is = new FileInputStream(moduleFile);
082: try {
083: OutputStream os = new FileOutputStream(physicalModuleFile);
084: try {
085: byte[] buf = new byte[4096];
086: int i;
087: while ((i = is.read(buf)) != -1) {
088: os.write(buf, 0, i);
089: }
090: } finally {
091: os.close();
092: }
093: } finally {
094: is.close();
095: }
096: err.fine("Made " + physicalModuleFile);
097: return physicalModuleFile;
098: }
099:
100: /**
101: * Find existing locale variants of f, in search order.
102: */
103: static List<File> findLocaleVariantsOf(File f) {
104: List<FileWithSuffix> result = findLocaleVariantsWithSuffixesOf(f);
105: List<File> l = new ArrayList<File>(result.size());
106: for (FileWithSuffix fws : result) {
107: l.add(fws.file);
108: }
109: return l;
110: }
111:
112: static final class FileWithSuffix {
113: public final File file;
114: public final String suffix;
115:
116: FileWithSuffix(File file, String suffix) {
117: this .file = file;
118: this .suffix = suffix;
119: }
120: }
121:
122: /**
123: * Find existing locale variants of f, in search order.
124: */
125: static List<FileWithSuffix> findLocaleVariantsWithSuffixesOf(File f) {
126: if (!f.isFile()) {
127: return Collections.emptyList();
128: }
129: String logicalDir = null;
130: {
131: // #34069: we have to consider that e.g. modules/locale/foo_branding.jar might be
132: // located in a different root of ${netbeans.dirs}, so need to use IFL. Here the
133: // logical path would be "modules/foo.jar" for the base module.
134: String logicalPath = findLogicalPath(f);
135: if (logicalPath != null) {
136: int slash = logicalPath.lastIndexOf('/');
137: if (slash != -1) {
138: logicalDir = logicalPath.substring(0, slash + 1)
139: + "locale/"; // NOI18N
140: } else {
141: logicalDir = "locale/"; // NOI18N
142: }
143: }
144: }
145: List<FileWithSuffix> l = new ArrayList<FileWithSuffix>(7);
146: String nameExt = f.getName();
147: int idx = nameExt.lastIndexOf('.'); // NOI18N
148: String name, ext;
149: if (idx != -1) {
150: name = nameExt.substring(0, idx);
151: ext = nameExt.substring(idx);
152: } else {
153: name = nameExt;
154: ext = ""; // NOI18N
155: }
156: if (logicalDir != null) {
157: for (String suffix : getLocalizingSuffixesFast()) {
158: String path = logicalDir + name + suffix + ext;
159: File v = InstalledFileLocator.getDefault().locate(path,
160: null, false);
161: if (v != null) {
162: l.add(new FileWithSuffix(v, suffix));
163: }
164: }
165: } else {
166: File dir = new File(f.getParentFile(), "locale"); // NOI18N
167: if (dir.exists()) {
168: for (String suffix : getLocalizingSuffixesFast()) {
169: File v = new File(dir, name + suffix + ext);
170: if (v.isFile()) {
171: l.add(new FileWithSuffix(v, suffix));
172: }
173: }
174: }
175: }
176: return l;
177: }
178:
179: /** Similar to {@link NbBundle#getLocalizingSuffixes} but optimized.
180: * @since JST-PENDING: Called from InstalledFileLocatorImpl
181: */
182: public static synchronized String[] getLocalizingSuffixesFast() {
183: if (suffixes == null
184: || Locale.getDefault() != lastLocale
185: || !Utilities.compareObjects(NbBundle.getBranding(),
186: lastBranding)) {
187: List<String> _suffixes = new ArrayList<String>();
188: Iterator<String> it = NbBundle.getLocalizingSuffixes();
189: while (it.hasNext()) {
190: _suffixes.add(it.next());
191: }
192: suffixes = _suffixes.toArray(new String[_suffixes.size()]);
193: lastLocale = Locale.getDefault();
194: lastBranding = NbBundle.getBranding();
195: }
196: return suffixes;
197: }
198:
199: private static String[] suffixes = null;
200: private static Locale lastLocale = null;
201: private static String lastBranding = null;
202:
203: /**
204: * Find a path such that InstalledFileLocator.getDefault().locate(path, null, false)
205: * yields the given file. Only guaranteed to work in case the logical path is a suffix of
206: * the file's absolute path (after converting path separators); otherwise there is no
207: * general way to invert locate(...) so this heuristic may fail. However for the IFL
208: * implementation used in a plain NB installation (i.e.
209: * org.netbeans.core.modules.InstalledFileLocatorImpl), this condition will in fact hold.
210: * @return the inverse of locate(...), or null if there is no such path
211: * @see "#34069"
212: */
213: private static String findLogicalPath(File f) {
214: InstalledFileLocator l = InstalledFileLocator.getDefault();
215: String path = f.getName();
216: File parent = f.getParentFile();
217: while (parent != null) {
218: File probe = l.locate(path, null, false);
219: //System.err.println("Util.fLP: f=" + f + " parent=" + parent + " probe=" + probe + " f.equals(probe)=" + f.equals(probe));
220: if (f.equals(probe)) {
221: return path;
222: }
223: path = parent.getName() + '/' + path;
224: parent = parent.getParentFile();
225: }
226: return null;
227: }
228:
229: // XXX ought to be some way to get localized messages for these...
230:
231: /** Check whether a simple dependency is met.
232: * Only applicable to Java dependencies.
233: */
234: static boolean checkJavaDependency(Dependency dep)
235: throws IllegalArgumentException {
236: // Note that "any" comparison is not possible for this type.
237: if (dep.getType() == Dependency.TYPE_JAVA) {
238: if (dep.getName().equals(Dependency.JAVA_NAME)) {
239: if (dep.getComparison() == Dependency.COMPARE_SPEC) {
240: return new SpecificationVersion(dep.getVersion())
241: .compareTo(Dependency.JAVA_SPEC) <= 0;
242: } else {
243: return dep.getVersion()
244: .equals(Dependency.JAVA_IMPL);
245: }
246: } else {
247: if (dep.getComparison() == Dependency.COMPARE_SPEC) {
248: return new SpecificationVersion(dep.getVersion())
249: .compareTo(Dependency.VM_SPEC) <= 0;
250: } else {
251: return dep.getVersion().equals(Dependency.VM_IMPL);
252: }
253: }
254: } else {
255: throw new IllegalArgumentException();
256: }
257: }
258:
259: /** Check whether a package dependency is met.
260: * A classloader must be supplied to check in.
261: */
262: static boolean checkPackageDependency(Dependency dep, ClassLoader cl)
263: throws IllegalArgumentException {
264: if (dep.getType() != Dependency.TYPE_PACKAGE) {
265: throw new IllegalArgumentException(
266: "Not a package dependency"); // NOI18N
267: }
268: if (!(cl instanceof Util.PackageAccessibleClassLoader)
269: && cl != Util.class.getClassLoader()) {
270: throw new IllegalArgumentException(
271: "Not a package-accessible classloader: " + cl); // NOI18N
272: }
273: String name = dep.getName();
274: String version = dep.getVersion();
275: int comparison = dep.getComparison();
276: String packageName, sampleName;
277: int idx = name.indexOf('[');
278: if (idx == -1) {
279: packageName = name;
280: sampleName = null;
281: } else if (idx == 0) {
282: packageName = null;
283: sampleName = name.substring(1, name.length() - 1);
284: } else {
285: packageName = name.substring(0, idx);
286: sampleName = name.substring(idx + 1, name.length() - 1);
287: if (sampleName.indexOf('.') == -1) {
288: // Unqualified class name; prefix it automatically.
289: sampleName = packageName + '.' + sampleName;
290: }
291: }
292: if (sampleName != null) {
293: try {
294: cl.loadClass(sampleName);
295: } catch (ClassNotFoundException cnfe) {
296: if (packageName == null) {
297: // This was all we were relying on, so it is an error.
298: err.log(Level.WARNING, null, cnfe);
299: err.fine("Probed class could not be found");
300: return false;
301: }
302: // Else let the regular package check take care of it;
303: // this was only run to enforce that the package defs were loaded.
304: } catch (RuntimeException e) {
305: // SecurityException, etc. Package exists but is corrupt.
306: err.log(Level.WARNING, null, e);
307: err.fine("Assuming package " + packageName
308: + " is corrupt");
309: return false;
310: } catch (LinkageError le) {
311: // NoClassDefFoundError, etc. Package exists but is corrupt.
312: err.log(Level.WARNING, null, le);
313: err.fine("Assuming package " + packageName
314: + " is corrupt");
315: return false;
316: }
317: }
318: if (packageName != null) {
319: Package pkg;
320: if (cl instanceof Util.PackageAccessibleClassLoader) {
321: pkg = ((Util.PackageAccessibleClassLoader) cl)
322: .getPackageAccessibly(packageName);
323: } else {
324: pkg = Package.getPackage(packageName);
325: }
326: if (pkg == null) {
327: err.fine("No package with the name " + packageName
328: + " found");
329: return false;
330: }
331: if (comparison == Dependency.COMPARE_ANY) {
332: return true;
333: } else if (comparison == Dependency.COMPARE_SPEC) {
334: if (pkg.getSpecificationVersion() == null) {
335: err.fine("Package " + packageName
336: + " did not give a specification version");
337: return false;
338: } else {
339: try {
340: SpecificationVersion versionSpec = new SpecificationVersion(
341: version);
342: SpecificationVersion pkgSpec = new SpecificationVersion(
343: pkg.getSpecificationVersion().trim());
344: if (versionSpec.compareTo(pkgSpec) <= 0) {
345: return true;
346: } else {
347: err.fine("Loaded package " + packageName
348: + " was only of version " + pkgSpec
349: + " but " + versionSpec
350: + " was requested");
351: return false;
352: }
353: } catch (NumberFormatException nfe) {
354: err.log(Level.WARNING, null, nfe);
355: err
356: .fine("Will not honor a dependency on non-numeric package spec version");
357: return false;
358: }
359: }
360: } else {
361: // COMPARE_IMPL
362: if (pkg.getImplementationVersion() == null) {
363: err.fine("Package " + packageName
364: + " had no implementation version");
365: return false;
366: } else if (!pkg.getImplementationVersion().trim()
367: .equals(version)) {
368: err.fine("Package " + packageName
369: + " had the wrong impl version: "
370: + pkg.getImplementationVersion());
371: return false;
372: } else {
373: return true;
374: }
375: }
376: } else {
377: // Satisfied sample class.
378: return true;
379: }
380: }
381:
382: /**
383: * Interface to permit a couple of methods in ClassLoader to be made public.
384: * @since 2.1
385: */
386: public interface PackageAccessibleClassLoader {
387: /** @see ClassLoader#getPackage */
388: Package getPackageAccessibly(String name);
389:
390: /** @see ClassLoader#getPackages */
391: Package[] getPackagesAccessibly();
392: }
393:
394: /**
395: * Interface for a classloader to declare that it comes from a module.
396: * @since 2.1
397: */
398: public interface ModuleProvider {
399: Module getModule();
400: }
401:
402: /**
403: * Enumerate (direct) interdependencies among a set of modules.
404: * If used in a topological sort, the result will be a reverse-order
405: * list of modules (suitable for disabling; reverse for enabling).
406: * @param modules some modules
407: * @param modulesByName map from module cnbs to modules (may contain unrelated modules)
408: * @param providersOf map from tokens to sets of modules providing them (may mention unrelated modules)
409: * @return a map from modules to lists of modules they depend on
410: * @see Utilities#topologicalSort
411: * JST-PENDING needed from tests
412: */
413: public static Map<Module, List<Module>> moduleDependencies(
414: Collection<Module> modules,
415: Map<String, Module> modulesByName,
416: Map<String, Set<Module>> _providersOf) {
417: Set<Module> modulesSet = (modules instanceof Set) ? (Set<Module>) modules
418: : new HashSet<Module>(modules);
419: Map<String, List<Module>> providersOf = new HashMap<String, List<Module>>(
420: _providersOf.size() * 2 + 1);
421: for (Map.Entry<String, Set<Module>> entry : _providersOf
422: .entrySet()) {
423: Set<Module> providers = entry.getValue();
424: if (providers != null) {
425: List<Module> availableProviders = new LinkedList<Module>(
426: providers);
427: availableProviders.retainAll(modulesSet);
428: if (!availableProviders.isEmpty()) {
429: providersOf.put(entry.getKey(), availableProviders);
430: }
431: }
432: }
433: Map<Module, List<Module>> m = new HashMap<Module, List<Module>>();
434: for (Module m1 : modules) {
435: List<Module> l = null;
436: for (Dependency dep : m1.getDependenciesArray()) {
437: if (dep.getType() == Dependency.TYPE_REQUIRES) {
438: List<Module> providers = providersOf.get(dep
439: .getName());
440:
441: if (providers != null) {
442: if (l == null) {
443: l = new LinkedList<Module>();
444: }
445: l.addAll(providers);
446: }
447: } else if (dep.getType() == Dependency.TYPE_MODULE) {
448: String cnb = (String) parseCodeName(dep.getName())[0];
449: Module m2 = modulesByName.get(cnb);
450:
451: if (m2 != null && modulesSet.contains(m2)) {
452: if (l == null) {
453: l = new LinkedList<Module>();
454: }
455: l.add(m2);
456: }
457: }
458: }
459: if (l != null) {
460: m.put(m1, l);
461: }
462: }
463: return m;
464: }
465:
466: /**
467: * Get dependencies forward or backwards starting from one module.
468: * @see #moduleDependencies
469: * @see ModuleManager#getModuleInterdependencies
470: */
471: static Set<Module> moduleInterdependencies(Module m,
472: boolean reverse, boolean transitive, Set<Module> modules,
473: Map<String, Module> modulesByName,
474: Map<String, Set<Module>> providersOf) {
475: // XXX these algorithms could surely be made faster using standard techniques
476: // for now the speed is not critical however
477: if (reverse) {
478: Set<Module> s = new HashSet<Module>();
479: for (Module m2 : modules) {
480: if (m2 == m) {
481: continue;
482: }
483: if (moduleInterdependencies(m2, false, transitive,
484: modules, modulesByName, providersOf)
485: .contains(m)) {
486: s.add(m2);
487: }
488: }
489: return s;
490: } else {
491: Set<Module> s = new HashSet<Module>();
492: for (Dependency dep : m.getDependenciesArray()) {
493: if (dep.getType() == Dependency.TYPE_REQUIRES
494: || dep.getType() == Dependency.TYPE_NEEDS) {
495: Set<Module> providers = providersOf.get(dep
496: .getName());
497: if (providers != null) {
498: s.addAll(providers);
499: }
500: } else if (dep.getType() == Dependency.TYPE_MODULE) {
501: String cnb = (String) parseCodeName(dep.getName())[0];
502: Module m2 = modulesByName.get(cnb);
503: if (m2 != null) {
504: s.add(m2);
505: }
506: }
507: }
508: s.remove(m);
509: if (transitive) {
510: Set<Module> toAdd;
511: do {
512: toAdd = new HashSet<Module>();
513: for (Module m2 : s) {
514: Set<Module> s2 = moduleInterdependencies(m2,
515: false, false, modules, modulesByName,
516: providersOf);
517: s2.remove(m);
518: s2.removeAll(s);
519: toAdd.addAll(s2);
520: }
521: s.addAll(toAdd);
522: } while (!toAdd.isEmpty());
523: }
524: return s;
525: }
526: }
527:
528: /** Find the most human-presentable message present in an exception.
529: * At worst, the detail message, but preferably a localized message
530: * if different, or the first localized annotation found.
531: * If returning the detail message is not OK, returns null instead.
532: * @since JST-PENDING: used from NbProblemDisplayer
533: */
534: public static String findLocalizedMessage(Throwable t,
535: boolean detailOK) {
536: String locmsg = t.getLocalizedMessage();
537: if (Utilities.compareObjects(locmsg, t.getMessage())) {
538: locmsg = Exceptions.findLocalizedMessage(t);
539: if (!detailOK) {
540: return null;
541: }
542: }
543: return locmsg;
544: }
545:
546: /** Get a filter for JAR files. */
547: static FilenameFilter jarFilter() {
548: return new JarFilter();
549: }
550:
551: private static final class JarFilter implements FilenameFilter {
552: JarFilter() {
553: }
554:
555: public boolean accept(File dir, String name) {
556: String n = name.toLowerCase(Locale.US);
557: return n.endsWith(".jar"); // NOI18N
558: }
559: }
560:
561: /** Convert a class file name to a resource name suitable for Beans.instantiate.
562: * @param name resource name of class file
563: * @return class name without the <code>.class</code>/<code>.ser</code> extension, and using dots as package separator
564: * @throws IllegalArgumentException if the name did not have a valid extension, or originally contained dots outside the extension, etc.
565: * @since JST-PENDING: used from NbInstaller
566: */
567: public static String createPackageName(String name)
568: throws IllegalArgumentException {
569: String clExt = ".class"; // NOI18N
570: if (!name.endsWith(clExt)) {
571: // try different extension
572: clExt = ".ser"; // NOI18N
573: }
574: if (name.endsWith(clExt)) {
575: String bareName = name.substring(0, name.length()
576: - clExt.length());
577: if (bareName.length() == 0) { // ".class" // NOI18N
578: throw new IllegalArgumentException(
579: "Bad class file name: " + name); // NOI18N
580: }
581: if (bareName.charAt(0) == '/') { // "/foo/bar.class" // NOI18N
582: throw new IllegalArgumentException(
583: "Bad class file name: " + name); // NOI18N
584: }
585: if (bareName.charAt(bareName.length() - 1) == '/') { // "foo/bar/.class" // NOI18N
586: throw new IllegalArgumentException(
587: "Bad class file name: " + name); // NOI18N
588: }
589: if (bareName.indexOf('.') != -1) { // "foo.bar.class" // NOI18N
590: throw new IllegalArgumentException(
591: "Bad class file name: " + name); // NOI18N
592: }
593: return bareName.replace('/', '.'); // NOI18N
594: } else { // "foo/bar" or "foo.bar" // NOI18N
595: throw new IllegalArgumentException("Bad class file name: "
596: + name); // NOI18N
597: }
598: }
599:
600: /** A lookup implementation specialized for modules.
601: * Its primary advantage over e.g. AbstractLookup is that
602: * it is possible to add modules to the set at one time and
603: * fire changes in the set of modules later on. ModuleManager
604: * uses this to add modules immediately in create() and destroy(),
605: * but only fire lookup events later and asynchronously, from the
606: * read mutex.
607: */
608: static final class ModuleLookup extends Lookup {
609: ModuleLookup() {
610: }
611:
612: private final Set<Module> modules = new HashSet<Module>(100);
613: private final Set<ModuleResult> results = new WeakSet<ModuleResult>(
614: 10);
615:
616: /** Add a module to the set. */
617: public void add(Module m) {
618: synchronized (modules) {
619: modules.add(m);
620: }
621: }
622:
623: /** Remove a module from the set. */
624: public void remove(Module m) {
625: synchronized (modules) {
626: modules.remove(m);
627: }
628: }
629:
630: /** Fire changes to all result listeners. */
631: public void changed() {
632: synchronized (results) {
633: Iterator it = results.iterator();
634: while (it.hasNext()) {
635: ((ModuleResult) it.next()).changed();
636: }
637: }
638: }
639:
640: public <T> T lookup(Class<T> clazz) {
641: if ((clazz == Module.class || clazz == ModuleInfo.class
642: || clazz == Object.class || clazz == null)
643: && !modules.isEmpty()) {
644: synchronized (modules) {
645: return clazz.cast(modules.iterator().next());
646: }
647: } else {
648: return null;
649: }
650: }
651:
652: @SuppressWarnings("unchecked")
653: public <T> Lookup.Result<T> lookup(Lookup.Template<T> t) {
654: Class<T> clazz = t.getType();
655: if (clazz == Module.class || clazz == ModuleInfo.class
656: || clazz == Object.class || clazz == null) {
657: return (Lookup.Result<T>) (Object) new ModuleResult(
658: (Lookup.Template<Module>) t);
659: } else {
660: return Lookup.EMPTY.lookup(t);
661: }
662: }
663:
664: public @Override
665: String toString() {
666: synchronized (modules) {
667: return "ModuleLookup" + modules; // NOI18N
668: }
669: }
670:
671: private final class ModuleResult extends Lookup.Result<Module> {
672: private final Lookup.Template<? super Module> t;
673: private final Set<LookupListener> listeners = new HashSet<LookupListener>(
674: 10);
675:
676: public ModuleResult(Lookup.Template<? super Module> t) {
677: this .t = t;
678: synchronized (results) {
679: results.add(this );
680: }
681: }
682:
683: public void addLookupListener(LookupListener l) {
684: synchronized (listeners) {
685: listeners.add(l);
686: }
687: }
688:
689: public void removeLookupListener(LookupListener l) {
690: synchronized (listeners) {
691: listeners.remove(l);
692: }
693: }
694:
695: public void changed() {
696: LookupListener[] _listeners;
697: synchronized (listeners) {
698: if (listeners.isEmpty()) {
699: return;
700: }
701: _listeners = listeners
702: .toArray(new LookupListener[listeners
703: .size()]);
704: }
705: LookupEvent ev = new LookupEvent(this );
706: for (int i = 0; i < _listeners.length; i++) {
707: _listeners[i].resultChanged(ev);
708: }
709: }
710:
711: public Collection<Module> allInstances() {
712: synchronized (modules) {
713: String id = t.getId();
714: Object inst = t.getInstance();
715: if (id != null) {
716: Iterator<Module> it = modules.iterator();
717: while (it.hasNext()) {
718: Module m = it.next();
719: if (id.equals(ModuleItem.PREFIX
720: + m.getCodeNameBase())) {
721: if (inst == null || inst == m) {
722: return Collections
723: .<Module> singleton(m);
724: }
725: }
726: }
727: return Collections.<Module> emptySet();
728: } else if (inst != null) {
729: return modules.contains(inst) ? Collections
730: .<Module> singleton(Module.class
731: .cast(inst)) : Collections
732: .<Module> emptySet();
733: } else {
734: // Regular lookup based on type.
735: return new HashSet<Module>(modules);
736: }
737: }
738: }
739:
740: public @Override
741: Set<Class<? extends Module>> allClasses() {
742: return Collections
743: .<Class<? extends Module>> singleton(Module.class);
744: }
745:
746: public @Override
747: Collection<? extends Lookup.Item<Module>> allItems() {
748: Collection<Module> insts = allInstances();
749: ArrayList<ModuleItem> list = new ArrayList<ModuleItem>(
750: Math.max(1, insts.size()));
751: for (Module m : insts) {
752: list.add(new ModuleItem(m));
753: }
754: return list;
755: }
756:
757: public @Override
758: String toString() {
759: return "ModuleResult:" + t; // NOI18N
760: }
761: }
762:
763: private static final class ModuleItem extends
764: Lookup.Item<Module> {
765: public static final String PREFIX = "Module["; // NOI18N
766: private final Module item;
767:
768: public ModuleItem(Module item) {
769: this .item = item;
770: }
771:
772: public Module getInstance() {
773: return item;
774: }
775:
776: public Class<? extends Module> getType() {
777: return Module.class;
778: }
779:
780: public String getId() {
781: return PREFIX + item.getCodeNameBase();
782: }
783:
784: public String getDisplayName() {
785: return item.getDisplayName();
786: }
787: }
788: }
789:
790: // OK to not release this memory; module deletion is rare: holds 45kB for 173 modules (June 2005)
791: private static final Map<String, Object[]> codeNameParseCache = new HashMap<String, Object[]>(
792: 200); // Map<String,[String,int]>
793:
794: /** Find the code name base and major release version from a code name.
795: * Caches these parses. Thread-safe (i.e. OK from read mutex).
796: * @return an array consisting of the code name base (String) followed by the release version (Integer or null)
797: * followed by another end-range version (Integer or null)
798: * @throws NumberFormatException if the release version is mangled
799: * @since JST-PENDING: used from NbInstaller
800: */
801: public static Object[] parseCodeName(String cn)
802: throws NumberFormatException {
803: synchronized (codeNameParseCache) {
804: Object[] r = codeNameParseCache.get(cn);
805: if (r == null) {
806: r = new Object[3];
807: int i = cn.lastIndexOf('/');
808: if (i == -1) {
809: r[0] = cn;
810: } else {
811: r[0] = cn.substring(0, i).intern();
812: String end = cn.substring(i + 1);
813: int j = end.indexOf('-');
814: if (j == -1) {
815: r[1] = new Integer(end);
816: } else {
817: r[1] = new Integer(end.substring(0, j));
818: r[2] = new Integer(end.substring(j + 1));
819: }
820: }
821: codeNameParseCache.put(cn.intern(), r);
822: }
823: return r;
824: }
825: }
826:
827: /** Get API module dependency, if any, for a module.
828: * @param dependencies module dependencies
829: * @param cnb code name base of API module
830: * @return a fake spec version (0.x.y if x.y w/ no major release, else r.x.y); or null if no dep
831: * @since JST-PENDING: used from NbInstaller
832: */
833: public static SpecificationVersion getModuleDep(
834: Set<Dependency> dependencies, String cnb) {
835: for (Dependency d : dependencies) {
836: if (d.getType() == Dependency.TYPE_MODULE
837: && d.getComparison() == Dependency.COMPARE_SPEC) {
838: try {
839: Object[] p = parseCodeName(d.getName());
840: if (!p[0].equals(cnb)) {
841: continue;
842: }
843: int rel = ((Integer) p[1]).intValue(); // ignore any end range, consider only start
844: if (rel == -1)
845: rel = 0; // XXX will this lead to incorrect semantics?
846: return new SpecificationVersion("" + rel + "."
847: + d.getVersion()); // NOI18N
848: } catch (NumberFormatException nfe) {
849: Util.err.log(Level.WARNING, null, nfe);
850: return null;
851: }
852: }
853: }
854: return null;
855: }
856:
857: /**
858: * Transitively fill out a set of modules with all of its module dependencies.
859: * Dependencies on missing modules are silently ignored, but dependencies
860: * on present but uninstallable (problematic) modules are included.
861: * @param mgr the manager
862: * @param modules a mutable set of modules
863: * @since JST-PENDING: used from NbInstaller
864: */
865: public static void transitiveClosureModuleDependencies(
866: ModuleManager mgr, Set<Module> modules) {
867: Set<Module> nue = null; // Set of newly appended modules
868: while (nue == null || !nue.isEmpty()) {
869: nue = new HashSet<Module>();
870: for (Module m : modules) {
871: for (Dependency dep : m.getDependenciesArray()) {
872: if (dep.getType() != Dependency.TYPE_MODULE) {
873: continue;
874: }
875: Module other = mgr.get((String) parseCodeName(dep
876: .getName())[0]);
877: if (other != null && !modules.contains(other)) {
878: nue.add(other);
879: }
880: }
881: }
882: modules.addAll(nue);
883: }
884: }
885:
886: }
|