Removed
Link Here
|
1 |
/* |
2 |
* Sun Public License Notice |
3 |
* |
4 |
* The contents of this file are subject to the Sun Public License |
5 |
* Version 1.0 (the "License"). You may not use this file except in |
6 |
* compliance with the License. A copy of the License is available at |
7 |
* http://www.sun.com/ |
8 |
* |
9 |
* The Original Code is NetBeans. The Initial Developer of the Original |
10 |
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2002 Sun |
11 |
* Microsystems, Inc. All Rights Reserved. |
12 |
*/ |
13 |
|
14 |
package org.netbeans.core.modules; |
15 |
|
16 |
// THIS CLASS OUGHT NOT USE NbBundle NOR org.openide CLASSES |
17 |
// OUTSIDE OF openide-util.jar! UI AND FILESYSTEM/DATASYSTEM |
18 |
// INTERACTIONS SHOULD GO ELSEWHERE. |
19 |
// (NbBundle.getLocalizedValue is OK here.) |
20 |
|
21 |
import org.openide.modules.ModuleInfo; |
22 |
import org.openide.ErrorManager; |
23 |
import java.io.*; |
24 |
import java.util.*; |
25 |
import java.util.jar.*; |
26 |
import java.net.URL; |
27 |
import java.security.*; |
28 |
import org.openide.util.NbBundle; |
29 |
import java.util.zip.ZipEntry; |
30 |
import org.openide.modules.SpecificationVersion; |
31 |
import org.openide.modules.Dependency; |
32 |
|
33 |
/** Object representing one module, possibly installed. |
34 |
* Responsible for opening of module JAR file; reading |
35 |
* manifest; parsing basic information such as dependencies; |
36 |
* and creating a classloader for use by the installer. |
37 |
* Methods not defined in ModuleInfo must be called from within |
38 |
* the module manager's read mutex as a rule. |
39 |
* @author Jesse Glick |
40 |
*/ |
41 |
public final class Module extends ModuleInfo { |
42 |
|
43 |
public static final String PROP_RELOADABLE = "reloadable"; // NOI18N |
44 |
public static final String PROP_CLASS_LOADER = "classLoader"; // NOI18N |
45 |
public static final String PROP_MANIFEST = "manifest"; // NOI18N |
46 |
public static final String PROP_VALID = "valid"; // NOI18N |
47 |
public static final String PROP_PROBLEMS = "problems"; // NOI18N |
48 |
|
49 |
/** manager which owns this module */ |
50 |
private final ModuleManager mgr; |
51 |
/** event logging (should not be much here) */ |
52 |
private final Events ev; |
53 |
/** associated history object |
54 |
* @see ModuleHistory |
55 |
*/ |
56 |
private final Object history; |
57 |
/** JAR file holding the module */ |
58 |
private final File jar; |
59 |
/** if reloadable, temporary JAR file actually loaded from */ |
60 |
private File physicalJar = null; |
61 |
/** true if currently enabled; manipulated by ModuleManager */ |
62 |
private boolean enabled; |
63 |
/** whether it is supposed to be easily reloadable */ |
64 |
private boolean reloadable; |
65 |
/** whether it is supposed to be automatically loaded when required */ |
66 |
private final boolean autoload; |
67 |
/** if true, this module is eagerly turned on whenever it can be */ |
68 |
private final boolean eager; |
69 |
/** module manifest */ |
70 |
private Manifest manifest; |
71 |
/** code name base (no slash) */ |
72 |
private String codeNameBase; |
73 |
/** code name release, or -1 if undefined */ |
74 |
private int codeNameRelease; |
75 |
/** provided tokens */ |
76 |
private String[] provides; |
77 |
/** set of dependencies parsed from manifest */ |
78 |
private Set dependencies; |
79 |
/** specification version parsed from manifest, or null */ |
80 |
private SpecificationVersion specVers; |
81 |
/** currently active module classloader */ |
82 |
private ClassLoader classloader = null; |
83 |
/** localized properties, or null to use only manifest */ |
84 |
private Properties localizedProps; |
85 |
|
86 |
/** Map from extension JARs to sets of JAR that load them via Class-Path. |
87 |
* Used only for debugging purposes, so that a warning is printed if two |
88 |
* different modules try to load the same extension (which would cause them |
89 |
* to both load their own private copy, which may not be intended). |
90 |
*/ |
91 |
private static final Map extensionOwners = new HashMap(); // Map<File,Set<File>> |
92 |
|
93 |
/** Set of locale-variants JARs for this module. |
94 |
* Added explicitly to classloader, and can be used by execution engine. |
95 |
*/ |
96 |
private final Set localeVariants = new HashSet(); // Set<File> |
97 |
/** Set of extension JARs that this module loads via Class-Path. |
98 |
* Can be used e.g. by execution engine. (#9617) |
99 |
*/ |
100 |
private final Set plainExtensions = new HashSet(); // Set<File> |
101 |
/** Set of localized extension JARs derived from plainExtensions. |
102 |
* Used to add these to the classloader. (#9348) |
103 |
* Can be used e.g. by execution engine. |
104 |
*/ |
105 |
private final Set localeExtensions = new HashSet(); // Set<File> |
106 |
|
107 |
/** Use ModuleManager.create as a factory. */ |
108 |
Module(ModuleManager mgr, Events ev, File jar, Object history, boolean reloadable, boolean autoload, boolean eager) throws IOException { |
109 |
if (autoload && eager) throw new IllegalArgumentException("A module may not be both autoload and eager"); // NOI18N |
110 |
this.mgr = mgr; |
111 |
this.ev = ev; |
112 |
this.jar = jar; |
113 |
this.history = history; |
114 |
this.reloadable = reloadable; |
115 |
this.autoload = autoload; |
116 |
this.eager = eager; |
117 |
enabled = false; |
118 |
loadManifest(); |
119 |
parseManifest(); |
120 |
} |
121 |
|
122 |
|
123 |
/** hack !!! - old (release33) constructor */ |
124 |
Module(ModuleManager mgr, Events ev, File jar, Object history, boolean reloadable, boolean autoload) throws IOException { |
125 |
this(mgr,ev,jar,history,reloadable,autoload,false); |
126 |
} |
127 |
|
128 |
|
129 |
|
130 |
/** Create a special-purpose "fixed" JAR. */ |
131 |
Module(ModuleManager mgr, Events ev, Manifest manifest, Object history, ClassLoader classloader) throws InvalidException { |
132 |
this.mgr = mgr; |
133 |
this.ev = ev; |
134 |
this.manifest = manifest; |
135 |
this.history = history; |
136 |
this.classloader = classloader; |
137 |
jar = null; |
138 |
reloadable = false; |
139 |
autoload = false; |
140 |
eager = false; |
141 |
enabled = false; |
142 |
parseManifest(); |
143 |
} |
144 |
|
145 |
/** Get the associated module manager. */ |
146 |
public ModuleManager getManager() { |
147 |
return mgr; |
148 |
} |
149 |
|
150 |
public boolean isEnabled() { |
151 |
return enabled; |
152 |
} |
153 |
|
154 |
// Access from ModuleManager: |
155 |
void setEnabled(boolean enabled) { |
156 |
/* #13647: actually can happen if loading of bootstrap modules is rolled back: |
157 |
if (isFixed() && ! enabled) throw new IllegalStateException("Cannot disable a fixed module: " + this); // NOI18N |
158 |
*/ |
159 |
this.enabled = enabled; |
160 |
} |
161 |
|
162 |
/** Normally a module once created and managed is valid |
163 |
* (that is, either installed or not, but at least managed). |
164 |
* If it is deleted any remaining references to it become |
165 |
* invalid. |
166 |
*/ |
167 |
public boolean isValid() { |
168 |
return mgr.get(getCodeNameBase()) == this; |
169 |
} |
170 |
|
171 |
/** Is this module automatically loaded? |
172 |
* If so, no information about its state is kept |
173 |
* permanently beyond the existence of its JAR file; |
174 |
* it is enabled when some real module needs it to be, |
175 |
* and disabled when this is no longer the case. |
176 |
* @see <a href="http://www.netbeans.org/issues/show_bug.cgi?id=9779">#9779</a> |
177 |
*/ |
178 |
public boolean isAutoload() { |
179 |
return autoload; |
180 |
} |
181 |
|
182 |
/** Is this module eagerly enabled? |
183 |
* If so, no information about its state is kept permanently. |
184 |
* It is turned on whenever it can be, i.e. whenever it meets all of |
185 |
* its dependencies. This may be used to implement "bridge" modules with |
186 |
* simple functionality that just depend on two normal modules. |
187 |
* A module may not be simultaneously eager and autoload. |
188 |
* @see <a href="http://www.netbeans.org/issues/show_bug.cgi?id=17501">#17501</a> |
189 |
* @since org.netbeans.core/1 1.3 |
190 |
*/ |
191 |
public boolean isEager() { |
192 |
return eager; |
193 |
} |
194 |
|
195 |
/** Get an associated arbitrary attribute. |
196 |
* Right now, simply provides the main attributes of the manifest. |
197 |
* In the future some of these could be suppressed (if only of dangerous |
198 |
* interest, e.g. Class-Path) or enhanced with other information available |
199 |
* from the core (if needed). |
200 |
*/ |
201 |
public Object getAttribute(String attr) { |
202 |
return getManifest().getMainAttributes().getValue(attr); |
203 |
} |
204 |
|
205 |
/** Get a localized attribute. |
206 |
* First, if OpenIDE-Module-Localizing-Bundle was given, the specified |
207 |
* bundle file (in all locale JARs as well as base JAR) is searched for |
208 |
* a key of the specified name. |
209 |
* Otherwise, the manifest's main attributes are searched for an attribute |
210 |
* with the specified name, possibly with a locale suffix. |
211 |
* If the attribute name contains a slash, and there is a manifest section |
212 |
* named according to the part before the last slash, then this section's attributes |
213 |
* are searched instead of the main attributes, and for the attribute listed |
214 |
* after the slash. Currently this would only be useful for localized filesystem |
215 |
* names. E.g. you may request the attribute org/foo/MyFileSystem.class/Display-Name. |
216 |
* In the future certain attributes known to be dangerous could be |
217 |
* explicitly suppressed from this list; should only be used for |
218 |
* documented localizable attributes such as OpenIDE-Module-Name etc. |
219 |
*/ |
220 |
public Object getLocalizedAttribute(String attr) { |
221 |
if (localizedProps != null) { |
222 |
// Permit localized attributes to be defined in a separate bundle. |
223 |
String val = localizedProps.getProperty(attr); |
224 |
if (val != null) { |
225 |
return val; |
226 |
} |
227 |
} |
228 |
int idx = attr.lastIndexOf('/'); // NOI18N |
229 |
if (idx == -1) { |
230 |
// Simple main attribute. |
231 |
return NbBundle.getLocalizedValue(getManifest().getMainAttributes(), new Attributes.Name(attr)); |
232 |
} else { |
233 |
// Attribute of a manifest section. |
234 |
String section = attr.substring(0, idx); |
235 |
String realAttr = attr.substring(idx + 1); |
236 |
Attributes attrs = getManifest().getAttributes(section); |
237 |
if (attrs != null) { |
238 |
return NbBundle.getLocalizedValue(attrs, new Attributes.Name(realAttr)); |
239 |
} else { |
240 |
return null; |
241 |
} |
242 |
} |
243 |
} |
244 |
|
245 |
public String getCodeName() { |
246 |
return (String)getAttribute("OpenIDE-Module"); // NOI18N |
247 |
} |
248 |
|
249 |
public String getCodeNameBase() { |
250 |
return codeNameBase; |
251 |
} |
252 |
|
253 |
public int getCodeNameRelease() { |
254 |
return codeNameRelease; |
255 |
} |
256 |
|
257 |
public String[] getProvides() { |
258 |
return provides; |
259 |
} |
260 |
/** Test whether the module provides a given token or not. */ |
261 |
final boolean provides(String token) { |
262 |
for (int i = 0; i < provides.length; i++) { |
263 |
if (provides[i].equals(token)) { |
264 |
return true; |
265 |
} |
266 |
} |
267 |
return false; |
268 |
} |
269 |
|
270 |
public Set getDependencies() { |
271 |
return dependencies; |
272 |
} |
273 |
// Just for binary compatibility, this is not the real impl in core: |
274 |
final Dependency[] getDependenciesArray() { |
275 |
return (Dependency[])dependencies.toArray(new Dependency[dependencies.size()]); |
276 |
} |
277 |
|
278 |
public SpecificationVersion getSpecificationVersion() { |
279 |
return specVers; |
280 |
} |
281 |
|
282 |
public boolean owns(Class clazz) { |
283 |
ClassLoader cl = clazz.getClassLoader(); |
284 |
if (cl instanceof Util.ModuleProvider) { |
285 |
return ((Util.ModuleProvider) cl).getModule() == this; |
286 |
} |
287 |
return false; |
288 |
|
289 |
} |
290 |
|
291 |
/** Original parseManifest - will decide whether to call |
292 |
* parseManifest_New for trunk release or parseManifest_Old |
293 |
* for release 33 |
294 |
*/ |
295 |
private void parseManifest() throws InvalidException { |
296 |
// have to use reflection and try to obtain |
297 |
// ModuleManager.refineDependencies() |
298 |
// if any problem is encountered with reflection -- fallback to |
299 |
// the new version of parseManifest |
300 |
try { |
301 |
Class parameterTypes[] = new Class[2]; |
302 |
parameterTypes[0] = this.getClass(); |
303 |
parameterTypes[1] = Set.class; |
304 |
java.lang.reflect.Method aMethod = mgr.getClass().getDeclaredMethod("refineDependencies",parameterTypes); |
305 |
// good call parseManifest_New() - in finally blockk; |
306 |
} catch (NoSuchMethodException nsme) { |
307 |
// using old parseManifest |
308 |
//System.out.println("XTest hack in org.netbeans.core.modules.Module.java: using old parseManifest()"); |
309 |
parseManifest_Old(); |
310 |
return; |
311 |
} catch (Throwable t) { |
312 |
// any problem - use new parseManifest |
313 |
//System.out.println("XTest hack in org.netbeans.core.modules.Module.java: using new parseManifest() cannot get info on ModuleManager"); |
314 |
parseManifest_New(); |
315 |
} |
316 |
//System.out.println("XTest hack in org.netbeans.core.modules.Module.java: using new parseManifest()"); |
317 |
parseManifest_New(); |
318 |
|
319 |
} |
320 |
|
321 |
|
322 |
/** Parse information from the current manifest. |
323 |
* Includes code name, specification version, and dependencies. |
324 |
* If anything is in an invalid format, throws an exception with |
325 |
* some kind of description of the problem. |
326 |
*/ |
327 |
private void parseManifest_New() throws InvalidException { |
328 |
Attributes attr = manifest.getMainAttributes(); |
329 |
// Code name |
330 |
String codeName = attr.getValue("OpenIDE-Module"); // NOI18N |
331 |
if (codeName == null) throw new InvalidException("Not a module: no OpenIDE-Module tag in manifest of " + /* #17629: important! */jar); // NOI18N |
332 |
try { |
333 |
// This has the side effect of checking syntax: |
334 |
if (codeName.indexOf(',') != -1) { |
335 |
throw new InvalidException("Illegal code name syntax parsing OpenIDE-Module: " + codeName); // NOI18N |
336 |
} |
337 |
Dependency.create(Dependency.TYPE_MODULE, codeName); |
338 |
int idx = codeName.lastIndexOf('/'); // NOI18N |
339 |
if (idx == -1) { |
340 |
codeNameBase = codeName; |
341 |
codeNameRelease = -1; |
342 |
} else { |
343 |
codeNameBase = codeName.substring(0, idx); |
344 |
codeNameRelease = Integer.parseInt(codeName.substring(idx + 1)); |
345 |
} |
346 |
// Spec vers |
347 |
String specVersS = attr.getValue("OpenIDE-Module-Specification-Version"); // NOI18N |
348 |
if (specVersS != null) { |
349 |
try { |
350 |
specVers = new SpecificationVersion(specVersS); |
351 |
} catch (NumberFormatException nfe) { |
352 |
InvalidException ie = new InvalidException("While parsing OpenIDE-Module-Specification-Version: " + nfe.toString()); // NOI18N |
353 |
Util.err.annotate(ie, nfe); |
354 |
throw ie; |
355 |
} |
356 |
} else { |
357 |
specVers = null; |
358 |
} |
359 |
// Token provides |
360 |
String providesS = attr.getValue("OpenIDE-Module-Provides"); // NOI18N |
361 |
if (providesS == null) { |
362 |
provides = new String[] {}; |
363 |
} else { |
364 |
StringTokenizer tok = new StringTokenizer(providesS, ", "); // NOI18N |
365 |
provides = new String[tok.countTokens()]; |
366 |
for (int i = 0; i < provides.length; i++) { |
367 |
String provide = tok.nextToken(); |
368 |
if (provide.indexOf(',') != -1) { |
369 |
throw new InvalidException("Illegal code name syntax parsing OpenIDE-Module-Provides: " + provide); // NOI18N |
370 |
} |
371 |
Dependency.create(Dependency.TYPE_MODULE, provide); |
372 |
if (provide.lastIndexOf('/') != -1) throw new IllegalArgumentException("Illegal OpenIDE-Module-Provides: " + provide); |
373 |
provides[i] = provide; |
374 |
} |
375 |
if (new HashSet(Arrays.asList(provides)).size() < provides.length) { |
376 |
throw new IllegalArgumentException("Duplicate entries in OpenIDE-Module-Provides: " + providesS); |
377 |
} |
378 |
} |
379 |
// Dependencies |
380 |
Set dependencies = new HashSet(20); // Set<Dependency> |
381 |
dependencies.addAll(Dependency.create(Dependency.TYPE_IDE, attr.getValue("OpenIDE-Module-IDE-Dependencies"))); // NOI18N |
382 |
dependencies.addAll(Dependency.create(Dependency.TYPE_JAVA, attr.getValue("OpenIDE-Module-Java-Dependencies"))); // NOI18N |
383 |
dependencies.addAll(Dependency.create(Dependency.TYPE_MODULE, attr.getValue("OpenIDE-Module-Module-Dependencies"))); // NOI18N |
384 |
dependencies.addAll(Dependency.create(Dependency.TYPE_PACKAGE, attr.getValue("OpenIDE-Module-Package-Dependencies"))); // NOI18N |
385 |
dependencies.addAll(Dependency.create(Dependency.TYPE_REQUIRES, attr.getValue("OpenIDE-Module-Requires"))); // NOI18N |
386 |
// Permit the concrete installer to make some changes: |
387 |
mgr.refineDependencies(this, dependencies); |
388 |
// Now copy (avoid synch problems): |
389 |
this.dependencies = Collections.unmodifiableSet(dependencies); |
390 |
} catch (IllegalArgumentException iae) { |
391 |
InvalidException ie = new InvalidException("While parsing a dependency attribute: " + iae.toString()); // NOI18N |
392 |
Util.err.annotate(ie, iae); |
393 |
throw ie; |
394 |
} |
395 |
} |
396 |
|
397 |
/** Old version of parse manifest - for release33 compatibility |
398 |
* Use at your own risk |
399 |
*/ |
400 |
private void parseManifest_Old() throws InvalidException { |
401 |
Attributes attr = manifest.getMainAttributes(); |
402 |
// Code name |
403 |
String codeName = attr.getValue("OpenIDE-Module"); // NOI18N |
404 |
if (codeName == null) throw new InvalidException("Not a module: no OpenIDE-Module tag"); // NOI18N |
405 |
try { |
406 |
// This has the side effect of checking syntax: |
407 |
if (codeName.indexOf(',') != -1) { |
408 |
throw new InvalidException("Illegal code name syntax: " + codeName); // NOI18N |
409 |
} |
410 |
Dependency.create(Dependency.TYPE_MODULE, codeName); |
411 |
int idx = codeName.lastIndexOf('/'); // NOI18N |
412 |
if (idx == -1) { |
413 |
codeNameBase = codeName; |
414 |
codeNameRelease = -1; |
415 |
} else { |
416 |
codeNameBase = codeName.substring(0, idx); |
417 |
codeNameRelease = Integer.parseInt(codeName.substring(idx + 1)); |
418 |
} |
419 |
// Spec vers |
420 |
String specVersS = attr.getValue("OpenIDE-Module-Specification-Version"); // NOI18N |
421 |
if (specVersS != null) { |
422 |
try { |
423 |
specVers = new SpecificationVersion(specVersS); |
424 |
} catch (NumberFormatException nfe) { |
425 |
InvalidException ie = new InvalidException(nfe.toString()); |
426 |
Util.err.annotate(ie, nfe); |
427 |
throw ie; |
428 |
} |
429 |
} else { |
430 |
specVers = null; |
431 |
} |
432 |
// Dependencies |
433 |
Set dependencies = new HashSet(20); // Set<Dependency> |
434 |
dependencies.addAll(Dependency.create(Dependency.TYPE_IDE, attr.getValue("OpenIDE-Module-IDE-Dependencies"))); // NOI18N |
435 |
dependencies.addAll(Dependency.create(Dependency.TYPE_JAVA, attr.getValue("OpenIDE-Module-Java-Dependencies"))); // NOI18N |
436 |
dependencies.addAll(Dependency.create(Dependency.TYPE_MODULE, attr.getValue("OpenIDE-Module-Module-Dependencies"))); // NOI18N |
437 |
dependencies.addAll(Dependency.create(Dependency.TYPE_PACKAGE, attr.getValue("OpenIDE-Module-Package-Dependencies"))); // NOI18N |
438 |
// Now copy (avoid synch problems): |
439 |
this.dependencies = Collections.unmodifiableSet(dependencies); |
440 |
} catch (IllegalArgumentException iae) { |
441 |
InvalidException ie = new InvalidException(iae.toString()); |
442 |
Util.err.annotate(ie, iae); |
443 |
throw ie; |
444 |
} |
445 |
} |
446 |
|
447 |
|
448 |
|
449 |
/** Get the JAR this module is packaged in. |
450 |
* May be null for modules installed specially, e.g. |
451 |
* automatically from the classpath. |
452 |
* @see #isFixed |
453 |
*/ |
454 |
public File getJarFile() { |
455 |
return jar; |
456 |
} |
457 |
|
458 |
/** Check if this is a "fixed" module. |
459 |
* Fixed modules are installed automatically (e.g. based on classpath) |
460 |
* and cannot be uninstalled or manipulated in any way. |
461 |
*/ |
462 |
public boolean isFixed() { |
463 |
return jar == null; |
464 |
} |
465 |
|
466 |
/** Create a temporary test JAR if necessary. |
467 |
* This is primarily necessary to work around a Java bug, |
468 |
* #4405789, which might be fixed in 1.4--check up on this. |
469 |
*/ |
470 |
private void ensurePhysicalJar() throws IOException { |
471 |
if (reloadable && physicalJar == null) { |
472 |
physicalJar = Util.makeTempJar(jar); |
473 |
} |
474 |
} |
475 |
private void destroyPhysicalJar() { |
476 |
if (physicalJar != null) { |
477 |
if (! physicalJar.delete()) { |
478 |
Util.err.log(ErrorManager.WARNING, "Warning: temporary JAR " + physicalJar + " not currently deletable."); |
479 |
} else { |
480 |
Util.err.log("deleted: " + physicalJar); |
481 |
} |
482 |
physicalJar = null; |
483 |
} else { |
484 |
Util.err.log("no physicalJar to delete for " + this); |
485 |
} |
486 |
} |
487 |
|
488 |
/** Open the JAR, load its manifest, and do related things. */ |
489 |
private void loadManifest() throws IOException { |
490 |
Util.err.log("loading manifest of " + jar); |
491 |
JarFile jarFile; |
492 |
if (reloadable) { |
493 |
ensurePhysicalJar(); |
494 |
jarFile = new JarFile(physicalJar); |
495 |
} else { |
496 |
jarFile = new JarFile(jar); |
497 |
} |
498 |
try { |
499 |
Manifest m = jarFile.getManifest(); |
500 |
findExtensionsAndVariants(m); |
501 |
loadLocalizedProps(jarFile, m); |
502 |
manifest = m; |
503 |
} finally { |
504 |
jarFile.close(); |
505 |
} |
506 |
} |
507 |
|
508 |
/** Find any extensions loaded by the module, as well as any localized |
509 |
* variants of the module or its extensions. |
510 |
*/ |
511 |
private void findExtensionsAndVariants(Manifest m) { |
512 |
localeVariants.clear(); |
513 |
localeVariants.addAll(Util.findLocaleVariantsOf(jar, false)); |
514 |
plainExtensions.clear(); |
515 |
localeExtensions.clear(); |
516 |
String classPath = m.getMainAttributes().getValue(Attributes.Name.CLASS_PATH); |
517 |
if (classPath != null) { |
518 |
StringTokenizer tok = new StringTokenizer(classPath); |
519 |
while (tok.hasMoreTokens()) { |
520 |
String ext = tok.nextToken(); |
521 |
File extfile = new File(jar.getParentFile(), ext.replace('/', File.separatorChar)); |
522 |
if (! extfile.exists()) { |
523 |
// Ignore unloadable extensions. |
524 |
continue; |
525 |
} |
526 |
synchronized (extensionOwners) { |
527 |
Set owners = (Set)extensionOwners.get(extfile); |
528 |
if (owners == null) { |
529 |
owners = new HashSet(2); |
530 |
owners.add(jar); |
531 |
extensionOwners.put(extfile, owners); |
532 |
} else if (! owners.contains(jar)) { |
533 |
owners.add(jar); |
534 |
ev.log(Events.EXTENSION_MULTIPLY_LOADED, extfile, owners); |
535 |
} // else already know about it (OK or warned) |
536 |
} |
537 |
plainExtensions.add(extfile); |
538 |
localeExtensions.addAll(Util.findLocaleVariantsOf(extfile, false)); |
539 |
} |
540 |
} |
541 |
Util.err.log("localeVariants of " + jar + ": " + localeVariants); |
542 |
Util.err.log("plainExtensions of " + jar + ": " + plainExtensions); |
543 |
Util.err.log("localeExtensions of " + jar + ": " + localeExtensions); |
544 |
} |
545 |
|
546 |
/** Check if there is any need to load localized properties. |
547 |
* If so, try to load them. Throw an exception if they cannot |
548 |
* be loaded for some reason. Uses an open JAR file for the |
549 |
* base module at least, though may also open locale variants |
550 |
* as needed. |
551 |
* @see <a href="http://www.netbeans.org/issues/show_bug.cgi?id=12549">#12549</a> |
552 |
*/ |
553 |
private void loadLocalizedProps(JarFile jarFile, Manifest m) throws IOException { |
554 |
String locbundle = m.getMainAttributes().getValue("OpenIDE-Module-Localizing-Bundle"); // NOI18N |
555 |
if (locbundle != null) { |
556 |
// Something requested, read it in. |
557 |
// locbundle is a resource path. |
558 |
{ |
559 |
ZipEntry bundleFile = jarFile.getEntry(locbundle); |
560 |
// May not be present in base JAR: might only be in e.g. default locale variant. |
561 |
if (bundleFile != null) { |
562 |
localizedProps = new Properties(); |
563 |
InputStream is = jarFile.getInputStream(bundleFile); |
564 |
try { |
565 |
localizedProps.load(is); |
566 |
} finally { |
567 |
is.close(); |
568 |
} |
569 |
} |
570 |
} |
571 |
{ |
572 |
// Check also for localized variant JARs and load in anything from them as needed. |
573 |
// Note we need to go in the reverse of the usual search order, so as to |
574 |
// overwrite less specific bundles with more specific. |
575 |
int idx = locbundle.lastIndexOf('.'); // NOI18N |
576 |
String name, ext; |
577 |
if (idx == -1) { |
578 |
name = locbundle; |
579 |
ext = ""; // NOI18N |
580 |
} else { |
581 |
name = locbundle.substring(0, idx); |
582 |
ext = locbundle.substring(idx); |
583 |
} |
584 |
List pairs = Util.findLocaleVariantsOf(jar, true); |
585 |
Collections.reverse(pairs); |
586 |
Iterator it = pairs.iterator(); |
587 |
while (it.hasNext()) { |
588 |
Object[] pair = (Object[])it.next(); |
589 |
File localeJar = (File)pair[0]; |
590 |
String suffix = (String)pair[1]; |
591 |
String rsrc = name + suffix + ext; |
592 |
JarFile localeJarFile = new JarFile(localeJar); |
593 |
try { |
594 |
ZipEntry bundleFile = localeJarFile.getEntry(rsrc); |
595 |
// Need not exist in all locale variants. |
596 |
if (bundleFile != null) { |
597 |
if (localizedProps == null) { |
598 |
localizedProps = new Properties(); |
599 |
} // else append and overwrite base-locale values |
600 |
InputStream is = localeJarFile.getInputStream(bundleFile); |
601 |
try { |
602 |
localizedProps.load(is); |
603 |
} finally { |
604 |
is.close(); |
605 |
} |
606 |
} |
607 |
} finally { |
608 |
localeJarFile.close(); |
609 |
} |
610 |
} |
611 |
} |
612 |
if (localizedProps == null) { |
613 |
// We should have loaded from at least some bundle in there... |
614 |
throw new IOException("Could not find localizing bundle: " + locbundle); // NOI18N |
615 |
} |
616 |
/* Don't log; too large and annoying: |
617 |
if (Util.err.isLoggable(ErrorManager.UNKNOWN)) { |
618 |
Util.err.log("localizedProps=" + localizedProps); |
619 |
} |
620 |
*/ |
621 |
} |
622 |
} |
623 |
|
624 |
/** Get all JARs loaded by this module. |
625 |
* Includes the module itself, any locale variants of the module, |
626 |
* any extensions specified with Class-Path, any locale variants |
627 |
* of those extensions. |
628 |
* If patches are implemented (#9273), the list will be in classpath order (patches first). |
629 |
* Currently the temp JAR is provided in the case of test modules, to prevent |
630 |
* sporadic ZIP file exceptions when background threads (like Java parsing) tries |
631 |
* to open libraries found in the library path. |
632 |
* JARs already present in the classpath are <em>not</em> listed. |
633 |
* @return a <code>List<File></code> of JARs |
634 |
*/ |
635 |
public List getAllJars() { |
636 |
if (jar == null) { |
637 |
// Classpath module. |
638 |
return Collections.EMPTY_LIST; |
639 |
} |
640 |
List l = new ArrayList (); // List<File> |
641 |
l.add (reloadable ? physicalJar : jar); |
642 |
l.addAll (plainExtensions); |
643 |
l.addAll (localeVariants); |
644 |
l.addAll (localeExtensions); |
645 |
return l; |
646 |
} |
647 |
|
648 |
/** Is this module supposed to be easily reloadable? |
649 |
* If so, it is suitable for testing inside the IDE. |
650 |
* Controls whether a copy of the JAR file is made before |
651 |
* passing it to the classloader, which can affect locking |
652 |
* and refreshing of the JAR. |
653 |
*/ |
654 |
public boolean isReloadable() { |
655 |
return reloadable; |
656 |
} |
657 |
|
658 |
/** Set whether this module is supposed to be reloadable. |
659 |
* Has no immediate effect, only impacts what happens the |
660 |
* next time it is enabled (after having been disabled if |
661 |
* necessary). |
662 |
*/ |
663 |
public void setReloadable(boolean r) { |
664 |
if (isFixed()) throw new IllegalStateException(); |
665 |
if (reloadable != r) { |
666 |
reloadable = r; |
667 |
mgr.fireReloadable(this); |
668 |
} |
669 |
} |
670 |
|
671 |
/** Used as a flag to tell if this module was really successfully released. |
672 |
* Currently does not work, so if it cannot be made to work, delete it. |
673 |
* (Someone seems to be holding a strong reference to the classloader--who?!) |
674 |
*/ |
675 |
private transient boolean released; |
676 |
/** Count which release() call is really being checked. */ |
677 |
private transient int releaseCount = 0; |
678 |
|
679 |
/** Reload this module. Access from ModuleManager. |
680 |
* If an exception is thrown, the module is considered |
681 |
* to be in an invalid state. |
682 |
*/ |
683 |
void reload() throws IOException { |
684 |
if (isFixed()) throw new IllegalStateException(); |
685 |
// Probably unnecessary but just in case: |
686 |
destroyPhysicalJar(); |
687 |
String codeNameBase1 = getCodeNameBase(); |
688 |
localizedProps = null; |
689 |
loadManifest(); |
690 |
parseManifest(); |
691 |
String codeNameBase2 = getCodeNameBase(); |
692 |
if (! codeNameBase1.equals(codeNameBase2)) { |
693 |
throw new InvalidException("Code name base changed during reload: " + codeNameBase1 + " -> " + codeNameBase2); // NOI18N |
694 |
} |
695 |
} |
696 |
|
697 |
/** Get the classloader capable of retrieving |
698 |
* things from this module. |
699 |
* If the module is disabled, this will be null (unless this module is fixed). |
700 |
* If it is enabled, it must not be null. |
701 |
* It is not guaranteed that change events will be fired |
702 |
* for changes in this property. |
703 |
*/ |
704 |
public ClassLoader getClassLoader() { |
705 |
return classloader; |
706 |
} |
707 |
// Access from ModuleManager: |
708 |
/** Turn on the classloader. Passed a list of parent modules to use. |
709 |
* The parents should already have had their classloaders initialized. |
710 |
*/ |
711 |
void classLoaderUp(Set parents) throws IOException { |
712 |
if (isFixed()) return; // no need |
713 |
Util.err.log("classLoaderUp on " + this + " with parents " + parents); |
714 |
// Find classloaders for dependent modules and parent to them. |
715 |
ClassLoader[] loaders = new ClassLoader[parents.size()]; |
716 |
Iterator it = parents.iterator(); |
717 |
int i = 0; |
718 |
while (it.hasNext()) { |
719 |
Module parent = (Module)it.next(); |
720 |
loaders[i++] = parent.getClassLoader(); |
721 |
} |
722 |
List classp = new ArrayList(3); // List<File|JarFile> |
723 |
// #9273: load any modules/patches/this-code-name/*.jar files first: |
724 |
File patchdir = new File(new File(jar.getParentFile(), "patches"), // NOI18N |
725 |
getCodeNameBase().replace('.', '-')); // NOI18N |
726 |
if (patchdir.isDirectory()) { |
727 |
File[] jars = patchdir.listFiles(Util.jarFilter()); |
728 |
if (jars != null) { |
729 |
for (int j = 0; j < jars.length; j++) { |
730 |
ev.log(Events.PATCH, jars[j]); |
731 |
classp.add(new JarFile(jars[j])); |
732 |
} |
733 |
} else { |
734 |
Util.err.log(ErrorManager.WARNING, "Could not search for patches in " + patchdir); |
735 |
} |
736 |
} |
737 |
|
738 |
// load tests by the same classloader as module classes |
739 |
// this classpath was formerly mounted to repository |
740 |
String testcp = System.getProperty("tbag.classpath"); |
741 |
String xclname = System.getProperty("xtest.useclassloader"); |
742 |
if ( xclname != null && xclname.equals ( getCodeNameBase() ) ) { |
743 |
if ( testcp != null ) { |
744 |
StringTokenizer stok = new StringTokenizer( testcp, System.getProperty("path.separator") ); |
745 |
while ( stok.hasMoreElements() ) { |
746 |
String testp = (String) stok.nextElement(); |
747 |
File testdir = new File ( testp ); |
748 |
if ( testdir.isDirectory() ) { |
749 |
Util.err.log(ErrorManager.WARNING, "---> Adding test dir: " + testdir + " for " + getCodeNameBase()); |
750 |
File [] jars = testdir.listFiles( Util.jarFilter() ); |
751 |
if ( jars != null ) { |
752 |
for ( int j = 0; j < jars.length; j++ ) { |
753 |
// add all jar files |
754 |
classp.add( new JarFile( jars[j] ) ); |
755 |
} |
756 |
} |
757 |
// add dir itself |
758 |
classp.add ( (Object) testdir ); |
759 |
} |
760 |
} |
761 |
} |
762 |
} |
763 |
|
764 |
if (reloadable) { |
765 |
ensurePhysicalJar(); |
766 |
classp.add(new JarFile(physicalJar)); |
767 |
} else { |
768 |
classp.add(new JarFile(jar)); |
769 |
} |
770 |
// URLClassLoader would not otherwise find these, so: |
771 |
for (it = localeVariants.iterator(); it.hasNext(); ) { |
772 |
classp.add(new JarFile((File)it.next())); |
773 |
} |
774 |
for (it = localeExtensions.iterator(); it.hasNext(); ) { |
775 |
File act = (File)it.next(); |
776 |
classp.add(act.isDirectory() ? (Object)act : new JarFile(act)); |
777 |
} |
778 |
for( it = plainExtensions.iterator(); it.hasNext(); ) { |
779 |
File act = (File)it.next(); |
780 |
classp.add(act.isDirectory() ? (Object)act : new JarFile(act)); |
781 |
} |
782 |
try { |
783 |
classloader = new OneModuleClassLoader(classp, loaders); |
784 |
} catch (IllegalArgumentException iae) { |
785 |
// Should not happen, but just in case. |
786 |
IOException ioe = new IOException(iae.toString()); |
787 |
Util.err.annotate(ioe, iae); |
788 |
throw ioe; |
789 |
} |
790 |
} |
791 |
/** Turn off the classloader and release all resources. */ |
792 |
void classLoaderDown() { |
793 |
if (isFixed()) return; // don't touch it |
794 |
if (classloader instanceof ProxyClassLoader) { |
795 |
// Remove references to it from other places, i.e. ModuleClassLoader: |
796 |
((ProxyClassLoader)classloader).destroy(); |
797 |
} |
798 |
classloader = null; |
799 |
Util.err.log("classLoaderDown on " + this + ": releaseCount=" + releaseCount + " released=" + released); |
800 |
released = false; |
801 |
System.gc(); // hope OneModuleClassLoader.finalize() is called... |
802 |
// but probably it won't be. See #4405807. |
803 |
if (! released) { |
804 |
Util.err.log("Warning: not all resources associated with module " + jar + " were successfully released."); |
805 |
released = true; |
806 |
} else { |
807 |
Util.err.log ("All resources associated with module " + jar + " were successfully released."); |
808 |
} |
809 |
destroyPhysicalJar(); |
810 |
} |
811 |
/** For compat only! */ |
812 |
void cleanup() {} |
813 |
void destroy() {} |
814 |
|
815 |
/** Get the JAR manifest. |
816 |
* Should never be null, even if disabled. |
817 |
* Might change if a module is reloaded. |
818 |
* It is not guaranteed that change events will be fired |
819 |
* for changes in this property. |
820 |
*/ |
821 |
public Manifest getManifest() { |
822 |
return manifest; |
823 |
} |
824 |
|
825 |
/** Get a set of {@link org.openide.modules.Dependency} objects representing missed dependencies. |
826 |
* This module is examined to see |
827 |
* why it would not be installable. |
828 |
* If it is enabled, there are no problems. |
829 |
* If it is in fact installable (possibly only |
830 |
* by also enabling some other managed modules which are currently disabled), and |
831 |
* all of its non-module dependencies are met, the returned set will be empty. |
832 |
* Otherwise it will contain a list of reasons why this module cannot be installed: |
833 |
* non-module dependencies which are not met; and module dependencies on modules |
834 |
* which either do not exist in the managed set, or are the wrong version, |
835 |
* or themselves cannot be installed |
836 |
* for some reason or another (which may be separately examined). |
837 |
* Note that in the (illegal) situation of two or more modules forming a cyclic |
838 |
* dependency cycle, none of them will be installable, and the missing dependencies |
839 |
* for each will be stated as the dependencies on the others. Again other modules |
840 |
* dependent on modules in the cycle will list failed dependencies on the cyclic modules. |
841 |
* Missing package dependencies are not guaranteed to be reported unless an install |
842 |
* of the module has already been attempted, and failed due to them. |
843 |
* The set may also contain {@link InvalidException}s representing known failures |
844 |
* of the module to be installed, e.g. due to classloader problems, missing runtime |
845 |
* resources, or failed ad-hoc dependencies. Again these are not guaranteed to be |
846 |
* reported unless an install has already been attempted and failed due to them. |
847 |
*/ |
848 |
public Set getProblems() { |
849 |
if (! isValid()) throw new IllegalStateException("Not valid: " + this); // NOI18N |
850 |
if (isEnabled()) return Collections.EMPTY_SET; |
851 |
return Collections.unmodifiableSet(mgr.missingDependencies(this)); |
852 |
} |
853 |
|
854 |
// Access from ChangeFirer: |
855 |
final void firePropertyChange0(String prop, Object old, Object nue) { |
856 |
if (Util.err.isLoggable(ErrorManager.UNKNOWN)) { |
857 |
Util.err.log("Module.propertyChange: " + this + " " + prop + ": " + old + " -> " + nue); |
858 |
} |
859 |
firePropertyChange(prop, old, nue); |
860 |
} |
861 |
|
862 |
/** Get the history object representing what has happened to this module before. |
863 |
* @see ModuleHistory |
864 |
*/ |
865 |
public final Object getHistory() { |
866 |
return history; |
867 |
} |
868 |
|
869 |
/** String representation for debugging. */ |
870 |
public String toString() { |
871 |
String s = "Module[" + getCodeNameBase() + "]"; // NOI18N |
872 |
if (!isValid()) s += "[invalid]"; // NOI18N |
873 |
return s; |
874 |
} |
875 |
|
876 |
/** PermissionCollection with an instance of AllPermission. */ |
877 |
private static PermissionCollection modulePermissions; |
878 |
/** @return initialized @see #modulePermission */ |
879 |
private static synchronized PermissionCollection getAllPermission() { |
880 |
if (modulePermissions == null) { |
881 |
modulePermissions = new Permissions(); |
882 |
modulePermissions.add(new AllPermission()); |
883 |
modulePermissions.setReadOnly(); |
884 |
} |
885 |
return modulePermissions; |
886 |
} |
887 |
|
888 |
/** Class loader to load a single module. |
889 |
* Auto-localizing, multi-parented, permission-granting, the works. |
890 |
*/ |
891 |
private class OneModuleClassLoader extends JarClassLoader implements Util.ModuleProvider, Util.PackageAccessibleClassLoader { |
892 |
private int rc; |
893 |
/** Create a new loader for a module. |
894 |
* @param classp the List of all module jars of code directories; |
895 |
* includes the module itself, its locale variants, |
896 |
* variants of extensions and Class-Path items from Manifest. |
897 |
* The items are JarFiles for jars and Files for directories |
898 |
* @param parents a set of parent classloaders (from other modules) |
899 |
*/ |
900 |
public OneModuleClassLoader(List classp, ClassLoader[] parents) throws IllegalArgumentException { |
901 |
super(classp, parents); |
902 |
rc = releaseCount++; |
903 |
} |
904 |
|
905 |
public Module getModule() { |
906 |
return Module.this; |
907 |
} |
908 |
|
909 |
/** Inherited. |
910 |
* @param cs is ignored |
911 |
* @return PermissionCollection with an AllPermission instance |
912 |
*/ |
913 |
protected PermissionCollection getPermissions(CodeSource cs) { |
914 |
return getAllPermission(); |
915 |
} |
916 |
/** look for JNI libraries also in modules/bin/ */ |
917 |
protected String findLibrary(String libname) { |
918 |
String mapped = System.mapLibraryName(libname); |
919 |
File lib = new File(new File(jar.getParentFile(), "bin"), mapped); // NOI18N |
920 |
if (lib.isFile()) { |
921 |
return lib.getAbsolutePath(); |
922 |
} else { |
923 |
return null; |
924 |
} |
925 |
} |
926 |
public String toString() { |
927 |
return super.toString() + "[" + getCodeNameBase() + "]"; // NOI18N |
928 |
} |
929 |
|
930 |
protected void finalize() throws Throwable { |
931 |
super.finalize(); |
932 |
Util.err.log("Finalize for " + this + ": rc=" + rc + " releaseCount=" + releaseCount + " released=" + released); // NOI18N |
933 |
if (rc == releaseCount) { |
934 |
// Hurrah! release() worked. |
935 |
released = true; |
936 |
} else { |
937 |
Util.err.log("Now resources for " + getCodeNameBase() + " have been released."); // NOI18N |
938 |
} |
939 |
} |
940 |
} |
941 |
|
942 |
} |