diff --git a/platform/o.n.bootstrap/src/org/netbeans/Archive.java b/platform/o.n.bootstrap/src/org/netbeans/Archive.java index 7d4615d52af2..dc3f9c4d3a48 100644 --- a/platform/o.n.bootstrap/src/org/netbeans/Archive.java +++ b/platform/o.n.bootstrap/src/org/netbeans/Archive.java @@ -34,11 +34,11 @@ * File format of the archive: * [Header] * ([Source entry]|[File entry])* - * + * * Header: * 8B [Magic] * 8B [timestamp] - * + * * Source entry (describes a data source for following entries): * 1B 0x01 type identifier * xB id utf8 String identifier of the source (file name) @@ -49,7 +49,7 @@ * 4B len length of the data (or -1 for no such file for source) * xB name utf8 String name of the file * lenB data file content - * + * * Utf8 string * 2B len length of the following String in bytes * lenB data utf8 encoded string @@ -58,37 +58,38 @@ */ class Archive implements Stamps.Updater { // increment on format change - private static final long magic = 6836742066851800321l; + private static final long MAGIC = 6836742066851800321l; private static final Logger LOG = Logger.getLogger(Archive.class.getName()); - + private volatile boolean saved; private final boolean prepopulated; - + private volatile boolean gathering; private final Object gatheringLock = new Object(); // these two collections are guarded either by the gatheringLock // or by the "gathering" volatile flag transitions - private Map requests = new LinkedHashMap(); - private Map knownSources = new HashMap(); + private Map requests = new LinkedHashMap<>(); + private Map knownSources = new HashMap<>(); private volatile boolean active; // These two collections are guarded by the "active" volatile flag transition. // They are modified from a single thread only, when "active" flag is false - private Map sources = new HashMap(); - private Map entries = new HashMap(); - + private Map sources = new HashMap<>(); + private Map entries = new HashMap<>(); + public Archive() { gathering = false; active = false; prepopulated = false; } - Archive(boolean prep) { + @SuppressWarnings("unused") // used by org.netbeans.core.startup.UpdateAllResourcesTest via reflection + private Archive(boolean prep) { gathering = false; active = false; prepopulated = prep; } - + /** Creates a new instance of Archive that reads data from given cache */ Archive(Stamps cache) { @@ -104,44 +105,41 @@ public Archive() { sources.clear(); entries.clear(); } - prepopulated = entries.size() > 0; - + prepopulated = !entries.isEmpty(); + active = true; gathering = true; } - final boolean isActive() { - return active; - } - /** * Sweep through the master buffer and remember all the entries */ private void parse(ByteBuffer master, long after) throws Exception { if (master.remaining() < 16) throw new IllegalStateException("Cache invalid"); - if (master.getLong() != magic) throw new IllegalStateException("Wrong format"); + if (master.getLong() != MAGIC) throw new IllegalStateException("Wrong format"); if (master.getLong() < after) throw new IllegalStateException("Cache outdated"); - + int srcCounter = 0; while (master.remaining() > 0) { int type = master.get(); switch (type) { - case 1: // source header + case 1 -> { + // source header String name = parseString(master); - sources.put(name, srcCounter++); - break; - case 2: + sources.put(name, srcCounter); + srcCounter++; + } + case 2 -> { Entry en = new Entry(master); // shifts the buffer entries.put(en, en); - break; - default: - throw new IllegalStateException("Cache invalid"); + } + default -> throw new IllegalStateException("Cache invalid"); } } master.rewind(); } - + private static String parseString(ByteBuffer src) { int len = src.getChar(); byte data[] = new byte[len]; @@ -152,13 +150,13 @@ private static String parseString(ByteBuffer src) { throw new InternalError(); // UTF8 must be supported } } - + private static void writeString(DataOutputStream dos, String str) throws UnsupportedEncodingException, IOException { byte[] data = str.getBytes("UTF8"); dos.writeChar(data.length); dos.write(data); } - + @SuppressWarnings("element-type-mismatch") public byte[] getData(ArchiveResources source, String name) throws IOException { Entry e = null; @@ -191,13 +189,13 @@ public byte[] getData(ArchiveResources source, String name) throws IOException { return e.getContent(); } - + public void stopGathering() { synchronized (gatheringLock) { gathering = false; } } - + public void stopServing() { active = false; // thread-safe, the only place using the field after @@ -205,7 +203,7 @@ public void stopServing() { // and this free happens-after clearing the flag entries = null; } - + public void save(Stamps cache) throws IOException { if (saved) { return; @@ -216,42 +214,43 @@ public void save(Stamps cache) throws IOException { @Override public void flushCaches(DataOutputStream dos) throws IOException { - stopGathering(); - stopServing(); - - assert !gathering; - assert !active; - - if (!prepopulated) { // write header - dos.writeLong(magic); - dos.writeLong(System.currentTimeMillis()); - } - - // no need to really synchronize on this collection, gathering flag - // is already cleared - for (String s:requests.keySet()) { - String[] parts = s.split("(?<=!/)", 2); - String name = parts.length == 2 ? parts[1] : ""; - ArchiveResources src = knownSources.get(parts[0]); - assert src != null : "Could not find " + s + " in " + knownSources; - byte[] data = src.resource(name); - Integer srcId = sources.get(parts[0]); - if (srcId == null) { - srcId = sources.size(); - sources.put(parts[0], srcId); - dos.write(1); - writeString(dos, parts[0]); + try (dos) { + stopGathering(); + stopServing(); + + assert !gathering; + assert !active; + + if (!prepopulated) { // write header + dos.writeLong(MAGIC); + dos.writeLong(System.currentTimeMillis()); } - - dos.write(2); - dos.writeChar(srcId); - dos.writeInt(data == null ? -1 : data.length); // store a marker to avoid openning - writeString(dos, name); - if (data != null) { - dos.write(data); + + // no need to really synchronize on this collection, gathering flag + // is already cleared + for (String s:requests.keySet()) { + String[] parts = s.split("(?<=!/)", 2); + String name = parts.length == 2 ? parts[1] : ""; + ArchiveResources src = knownSources.get(parts[0]); + assert src != null : "Could not find " + s + " in " + knownSources; + byte[] data = src.resource(name); + Integer srcId = sources.get(parts[0]); + if (srcId == null) { + srcId = sources.size(); + sources.put(parts[0], srcId); + dos.write(1); + writeString(dos, parts[0]); + } + + dos.write(2); + dos.writeChar(srcId); + dos.writeInt(data == null ? -1 : data.length); // store a marker to avoid openning + writeString(dos, name); + if (data != null) { + dos.write(data); + } } } - dos.close(); if (LOG.isLoggable(Level.FINER)) { for (Object r : requests.keySet()) { @@ -282,11 +281,11 @@ final boolean isPopulated() { * 8 xB name utf8 String name of the file *x+8 yB data file content */ - + private static class Entry { private final int offset; private final ByteBuffer master; - + Entry(ByteBuffer m) { master = m; offset = master.position(); @@ -301,11 +300,11 @@ String getName() { my.position(offset+6); return parseString(my); } - + int getSource() { return master.getChar(offset); } - + byte[] getContent() { int fLen = master.getInt(offset+2); int nLen = master.getChar(offset+6); @@ -317,14 +316,17 @@ byte[] getContent() { clone.get(content); return content; } - - public @Override int hashCode() { + + @Override + public int hashCode() { ByteBuffer clone = master.duplicate(); - clone.position(offset+8); - clone.limit(offset+8+master.getChar(offset+6)); + clone.position(offset + 8); + clone.limit(offset + 8 + master.getChar(offset + 6)); - int code = 53*master.getChar(offset); - while (clone.hasRemaining()) code = code*53 + clone.get(); + int code = 53 * master.getChar(offset); + while (clone.hasRemaining()) { + code = code * 53 + clone.get(); + } return code; } @@ -332,18 +334,18 @@ byte[] getContent() { if (obj instanceof Template) return obj.equals(this); return obj == this; } - + public @Override String toString() { return "#" + getSource() + ":" + getName() + "=[" + offset + "]"; } } - + // template private static class Template { private int source; private byte[] utf; - - Template(int src, String name) { + + Template(int src, String name) { try { this.source = src; utf = name.getBytes("UTF8"); @@ -351,23 +353,23 @@ private static class Template { throw new InternalError(); } } - + public @Override boolean equals(Object o) { if (! (o instanceof Entry)) return false; Entry e = (Entry)o; - + if (source != e.master.getChar(e.offset)) return false; if (utf.length != e.master.getChar(e.offset+6)) return false; ByteBuffer clone = e.master.duplicate(); clone.position(((Entry)o).offset+8); - - + + for (byte b : utf) if (b != clone.get()) return false; return true; } - + public @Override int hashCode() { int code = 53*source; for (byte b : utf) code = code*53 + b; diff --git a/platform/o.n.bootstrap/src/org/netbeans/JarClassLoader.java b/platform/o.n.bootstrap/src/org/netbeans/JarClassLoader.java index ef998f05a104..5e71e96ccc74 100644 --- a/platform/o.n.bootstrap/src/org/netbeans/JarClassLoader.java +++ b/platform/o.n.bootstrap/src/org/netbeans/JarClassLoader.java @@ -30,6 +30,7 @@ import java.lang.instrument.IllegalClassFormatException; import java.lang.ref.Reference; import java.lang.ref.SoftReference; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.JarURLConnection; import java.net.MalformedURLException; @@ -55,8 +56,6 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; -import java.util.Vector; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; @@ -84,26 +83,27 @@ public class JarClassLoader extends ProxyClassLoader { // platform/netbinox/src/org/netbeans/modules/netbinox/JarBundleFile.java // should also be adjusted. At least the multi-release handling is similar. // - + private static Stamps cache; private static final String META_INF = "META-INF/"; private static final Name MULTI_RELEASE = new Name("Multi-Release"); private static final int BASE_VERSION = 8; private static final int RUNTIME_VERSION = Runtime.version().feature(); - - static Archive archive = new Archive(); + + @SuppressWarnings("PackageVisibleField") // used by NetigsoFramework + static Archive archive = new Archive(); static void initializeCache() { cache = Stamps.getModulesJARs(); archive = new Archive(cache); PackageAttrsCache.initialize(); } - + /** * Creates a new archive or updates existing archive with the necessary * resources gathered so far. It also stops gatheing and serving * additional request, if it was still doing so. - */ + */ public static void saveArchive() { if (cache != null) { try { @@ -116,44 +116,55 @@ public static void saveArchive() { archive.stopServing(); } } - - /** Check whether the archive has already been populated during + + /** Check whether the archive has already been populated during * previous executions. - * + * * @return true, if the archive is ready and non-empty * @since 2.61 */ public static boolean isArchivePopulated() { return archive != null && archive.isPopulated(); } - + static { ProxyURLStreamHandlerFactory.register(); } - + private static final Logger LOGGER = Logger.getLogger(JarClassLoader.class.getName()); - private Source[] sources; - private Module module; - - /** Creates new JarClassLoader. + private final Source[] sources; + private final Module module; + + /** + * Creates new JarClassLoader. * Gives transitive flag as true. + * + * @param files + * @param parents */ public JarClassLoader(List files, ClassLoader[] parents) { this(files, parents, true, null); } - + public JarClassLoader(List files, ClassLoader[] parents, boolean transitive) { this(files, parents, transitive, null); } - /** Creates new JarClassLoader. + + /** + * Creates new JarClassLoader. + * + * @param files + * @param parents + * @param transitive + * @param mod * @since org.netbeans.core/1 > 1.6 * @see ProxyClassLoader#ProxyClassLoader(ClassLoader[],boolean) */ public JarClassLoader(List files, ClassLoader[] parents, boolean transitive, Module mod) { super(parents, transitive); this.module = mod; - List l = new ArrayList(files.size()); + List l = new ArrayList<>(files.size()); try { for (File file : files) { l.add(Source.create(file, this)); @@ -166,28 +177,13 @@ public JarClassLoader(List files, ClassLoader[] parents, boolean transitiv addCoveredPackages(getCoveredPackages(module, sources)); } - final void addURL(URL location) throws IOException, URISyntaxException { - File f = BaseUtilities.toFile(location.toURI()); - assert f.exists() : "URL must be existing local file: " + location; - - List arr = new ArrayList(Arrays.asList(sources)); - arr.add(new JarSource(f)); - - synchronized (sources) { - sources = arr.toArray(Source[]::new); - } - - // overlaps with old packages doesn't matter,PCL uses sets. - addCoveredPackages(getCoveredPackages(module, sources)); - } - /** Allows to specify the right permissions, OneModuleClassLoader does it differently. */ - protected PermissionCollection getPermissions( CodeSource cs ) { - return Policy.getPolicy().getPermissions(cs); - } - - + protected PermissionCollection getPermissions( CodeSource cs ) { + return Policy.getPolicy().getPermissions(cs); + } + + protected Package definePackage(String name, Manifest man, URL url) throws IllegalArgumentException { if (man == null) { @@ -200,28 +196,16 @@ protected Package definePackage(String name, Manifest man, URL url) return definePackage(name, arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], sealBase); } - + /** * Bytecode patching helper */ private PatchByteCode patchingBytecode; - - byte[] getClassData(String name) { - String path = name.replace('.', '/').concat(".class"); // NOI18N - for( int i=0; i doLoadClass(String pkgName, String name) { String path = name.replace('.', '/').concat(".class"); // NOI18N - + // look up the Sources and return a class based on their content for( int i=0; i doLoadClass(String pkgName, String name) { } try { data = patchingBytecode.apply(name, data); - } catch (Exception x) { + } catch (IOException | RuntimeException x) { LOGGER.log(Level.INFO, "Could not bytecode-patch " + name, x); } - + // Note that we assume that if we are defining a class in this package, // we should also define the package! Thus recurse==false. // However special packages might be defined in a parent and then we want @@ -256,7 +240,7 @@ protected Class doLoadClass(String pkgName, String name) { } else { class DelayedManifest extends Manifest { private Manifest delegate; - + private Manifest delegate() { if (delegate == null) { Manifest m; @@ -269,7 +253,7 @@ private Manifest delegate() { } return delegate; } - + @Override public Attributes getMainAttributes() { return delegate().getMainAttributes(); @@ -300,7 +284,7 @@ public Map getEntries() { LOGGER.log(Level.WARNING, "Problems patching" + name, ex); } return defineClass (name, data, 0, data.length, src.getProtectionDomain()); - } + } return null; } // look up the jars and return a resource based on a content of jars @@ -315,16 +299,16 @@ public URL findResource(String name) { @Override public Enumeration findResources(String name) { - Vector v = new Vector(3); + ArrayList v = new ArrayList<>(3); // look up the jars and return a resource based on a content of jars for( int i=0; i sources = new HashMap(); + private static Map sources = new HashMap<>(); private Boolean multiRelease; - + public Source(URL url) { this.url = url; } - + public final URL getURL() { return url; } public abstract String getPath(); - + public final ProtectionDomain getProtectionDomain() { if (pd == null) { CodeSource cs = new CodeSource(url, (Certificate[])null); @@ -370,19 +354,19 @@ public final ProtectionDomain getProtectionDomain() { } return pd; } - + public final URL getResource(String name) { try { return doGetResource(name); - } catch (Exception e) { + } catch (IOException | RuntimeException e) { // can't get the resource. E.g. already closed JarFile LOGGER.log(Level.FINE, null, e); } return null; } - + protected abstract URL doGetResource(String name) throws IOException; - + public final byte[] getClassData(String path) { try { return readClass(path); @@ -398,14 +382,14 @@ public Manifest getManifest() { return null; } - protected abstract void listCoveredPackages(Set known, StringBuffer save); - + protected abstract void listCoveredPackages(Set known, StringBuilder save); + protected void destroy() throws IOException { // relatively slow (millis instead of micros), // but rare enough to not matter sources.values().remove(this); } - + static Source create(File f, JarClassLoader jcl) throws IOException { boolean directory; if (f.getName().endsWith("jar")) { @@ -446,16 +430,16 @@ protected boolean isMultiRelease() { } } - + static void dumpFiles(File f, int retry) { - for (;;) { - if (f == null) { + for (File checkFile = f;; checkFile = checkFile.getParentFile()) { + if (checkFile == null) { LOGGER.log(Level.INFO, "file {0} is null. # of retries {1}", new Object[]{f, retry}); // NOI18N break; } - if (f.exists()) { + if (checkFile.exists()) { LOGGER.log(Level.INFO, "file {0} exists. # of retries {1}", new Object[]{f, retry}); // NOI18N - if (f.isDirectory()) { + if (checkFile.isDirectory()) { LOGGER.log(Level.INFO, "{0} is directory and contains: {1}", new Object[]{f, Arrays.toString(f.list())}); // NOI18N } else { LOGGER.log(Level.INFO, "{0} isDirectory: {1}, isFile: {2} size: {3}", new Object[]{f, f.isDirectory(), f.isFile(), f.length()}); // NOI18N @@ -463,11 +447,10 @@ static void dumpFiles(File f, int retry) { break; } LOGGER.log(Level.INFO, "{0} does not exist, # of retries {1}", new Object[]{f, retry}); // NOI18N - f = f.getParentFile(); } } - static class JarSource extends Source implements ArchiveResources { + private static class JarSource extends Source implements ArchiveResources { private String resPrefix; private File file; @@ -478,14 +461,14 @@ static class JarSource extends Source implements ArchiveResources { private volatile int[] versions; private volatile Reference manifest; /** #141110: expensive to repeatedly look for them */ - private final Set nonexistentResources = Collections.synchronizedSet(new HashSet()); - private final Set warnedFiles = Collections.synchronizedSet(new HashSet()); // #183696 - + private final Set nonexistentResources = Collections.synchronizedSet(new HashSet<>()); + private final Set warnedFiles = Collections.synchronizedSet(new HashSet<>()); // #183696 + JarSource(File file) throws IOException { this(file, toURI(file)); } private JarSource(File file, String resPrefix) throws IOException { - super(new URL(resPrefix)); // NOI18N + super(URI.create(resPrefix).toURL()); // NOI18N this.resPrefix = resPrefix; // NOI18N; this.file = file; } @@ -515,6 +498,7 @@ public File getAbsoluteFile() { } @Override + @SuppressWarnings("NestedAssignment") public Manifest getManifest() { { Manifest man; @@ -528,14 +512,14 @@ public Manifest getManifest() { return null; } final Manifest man = new Manifest(new ByteArrayInputStream(arr)); - manifest = new SoftReference(man); + manifest = new SoftReference<>(man); return man; } catch (IOException ex) { LOGGER.log(Level.WARNING, "Cannot read manifest for " + getPath(), ex); return null; } } - + JarFile getJarFile(final String forWhat) throws IOException { FutureTask init = null; synchronized(sources) { @@ -544,53 +528,51 @@ JarFile getJarFile(final String forWhat) throws IOException { if (fjar == null) { fjar = sources.get(this); if (fjar == null) { - fjar = init = new FutureTask(new Callable() { - @Override - public JarFile call() throws IOException { - int retry = 0; - for (;;) { + init = new FutureTask<>(() -> { + int retry = 0; + for (;;) { + try { + long now = System.currentTimeMillis(); + JarFile ret; try { - long now = System.currentTimeMillis(); - JarFile ret; - try { - ret = new JarFile(file, false); - } catch (FileNotFoundException | NoSuchFileException ex) { - throw (ZipException)new ZipException(ex.getMessage()).initCause(ex); - } - long took = System.currentTimeMillis() - now; - opened(JarClassLoader.JarSource.this, forWhat); - if (took > 500) { - LOGGER.log(Level.WARNING, "Opening {0} took {1} ms", new Object[]{file, took}); // NOI18N - } - return ret; - } catch (ZipException zip) { - if (file.exists() && retry++ < 3) { - LOGGER.log(Level.WARNING, "Error opening " + file + " (exists=" + file.exists() + ") retry: " + retry, zip); // NOI18N - opened(JarClassLoader.JarSource.this, "ziperror"); - continue; - } - dumpFiles(file, retry); - throw zip; + ret = new JarFile(file, false); + } catch (FileNotFoundException | NoSuchFileException ex) { + throw (ZipException)new ZipException(ex.getMessage()).initCause(ex); + } + long took = System.currentTimeMillis() - now; + opened(JarClassLoader.JarSource.this, forWhat); + if (took > 500) { + LOGGER.log(Level.WARNING, "Opening {0} took {1} ms", new Object[]{file, took}); // NOI18N + } + return ret; + } catch (ZipException zip) { + if (file.exists() && retry++ < 3) { + LOGGER.log(Level.WARNING, "Error opening " + file + " (exists=" + file.exists() + ") retry: " + retry, zip); // NOI18N + opened(JarClassLoader.JarSource.this, "ziperror"); + continue; } + dumpFiles(file, retry); + throw zip; } } }); - sources.put(this, fjar); + fjar = init; + sources.put(this, init); } } } if (init != null) init.run(); return callGet(); } - + private void releaseJarFile() { synchronized(sources) { assert used > 0; used--; } } - - + + @Override protected URL doGetResource(String name) throws IOException { byte[] buf = archive.getData(this, name); @@ -604,7 +586,7 @@ protected URL doGetResource(String name) throws IOException { throw new IOException(name + " in " + resPrefix + ": " + x.toString(), x); } } - + @Override protected byte[] readClass(String path) throws IOException { try { @@ -645,7 +627,8 @@ private int[] getVersions() { int[] ret = new int[vers.size()]; int i = 0; for (Integer ver : vers) { - ret[i++] = ver; + ret[i] = ver; + i++; } versions = ret; return ret; @@ -683,7 +666,7 @@ public byte[] resource(String path) throws IOException { if (LOGGER.isLoggable(Level.FINER)) { LOGGER.log(Level.FINER, "Loading {0} from {1}", new Object[] {path, file.getPath()}); } - + int len = (int)ze.getSize(); byte[] data = new byte[len]; InputStream is = jf.getInputStream(ze); @@ -699,7 +682,7 @@ public byte[] resource(String path) throws IOException { @Override - protected void listCoveredPackages(Set known, StringBuffer save) { + protected void listCoveredPackages(Set known, StringBuilder save) { try { JarFile src = getJarFile("pkg"); @@ -717,26 +700,32 @@ protected void listCoveredPackages(Set known, StringBuffer save) { } continue; } - if (itm.startsWith("META-INF/")) { + if (itm.startsWith("META-INF/versions/")) { + int endOfVersion = itm.indexOf("/", 18) + 1; + if (endOfVersion > 0 && endOfVersion < itm.length() && endOfVersion < slash) { + String pkg = itm.substring(endOfVersion, slash).replace('/', '.'); + if (known.add(pkg)) { + save.append(pkg).append(','); + } + } + continue; + } else if (itm.startsWith("META-INF/")) { String res = itm.substring(8); // "/services/pkg.Service" if (known.add(res)) save.append(res).append(','); continue; } - String pkg = slash > 0 ? itm.substring(0, slash).replace('/','.') : ""; - if (known.add(pkg)) save.append(pkg).append(','); + String pkg = slash > 0 ? itm.substring(0, slash).replace('/', '.') : ""; + if (known.add(pkg)) { + save.append(pkg).append(','); + } } } - } catch (ZipException x) { // Unix + } catch (ZipException | FileNotFoundException x) { // Unix if (warnedFiles.add(file)) { LOGGER.log(Level.INFO, "Cannot open " + file, x); dumpFiles(file, -1); } - } catch (FileNotFoundException x) { // Windows - if (warnedFiles.add(file)) { - LOGGER.log(Level.INFO, "Cannot open " + file, x); - dumpFiles(file, -1); - } - } catch (IOException ioe) { + } catch (IOException ioe) { // Windows if (warnedFiles.add(file)) { LOGGER.log(Level.WARNING, "problems with " + file, ioe); dumpFiles(file, -1); @@ -746,14 +735,14 @@ protected void listCoveredPackages(Set known, StringBuffer save) { } } - + @Override protected void destroy() throws IOException { super.destroy(); if (dead) { return; } - + File orig = file; if (!orig.isFile()) { @@ -765,7 +754,7 @@ protected void destroy() throws IOException { // See comment in Module.cleanup. return; } - + String name = orig.getName(); String prefix, suffix; int idx = name.lastIndexOf('.'); @@ -776,15 +765,17 @@ protected void destroy() throws IOException { prefix = name.substring(0, idx); suffix = name.substring(idx); } - - while (prefix.length() < 3) prefix += "x"; // NOI18N + + while (prefix.length() < 3) { + prefix += "x"; // NOI18N + } File temp = Files.createTempFile(prefix, suffix).toFile(); temp.deleteOnExit(); try (InputStream is = new FileInputStream(orig); OutputStream os = new FileOutputStream(temp)) { is.transferTo(os); } - + doCloseJar(); file = temp; dead = true; @@ -792,7 +783,7 @@ protected void destroy() throws IOException { LOGGER.log(Level.FINE, "#21114: replacing {0} with {1}", new Object[] {orig, temp}); } } - + private JarFile callGet() throws IOException { boolean interrupted = false; JarFile ret; @@ -825,7 +816,7 @@ private void doCloseJar() throws IOException { if (fjar != null) { jar = callGet(); if (sources.remove(this) == null) { - LOGGER.warning("Can't remove " + this); + LOGGER.log(Level.WARNING, "Can''t remove {0}", this); } LOGGER.log(Level.FINE, "Closing JAR {0}", jar.getName()); fjar = null; @@ -841,7 +832,7 @@ private void doCloseJar() throws IOException { @Override protected void finalize() throws Throwable { super.finalize(); - + doCloseJar(); if (dead) { @@ -853,7 +844,7 @@ protected void finalize() throws Throwable { } // JarFile pool tracking - private static final Map> sources = new HashMap>(); + private static final Map> sources = new HashMap<>(); private static int LIMIT = Integer.getInteger("org.netbeans.JarClassLoader.limit_fd", 300); static void opened(JarSource source, String forWhat) { @@ -874,26 +865,26 @@ static void opened(JarSource source, String forWhat) { } } - // called under lock(sources) + // called under lock(sources) private static JarSource toClose(JarSource notThisOne) { assert Thread.holdsLock(sources); - - int min = Integer.MAX_VALUE; - JarSource candidate = null; + + int min = Integer.MAX_VALUE; + JarSource candidate = null; for (JarSource act : sources.keySet()) { // aging: slight exponential decay of all opened sources? act.requests = 5*act.requests/6; - + if (act.used > 0) continue; - if (act.requests < min) { - min = act.requests; - candidate = act; - } - } - + if (act.requests < min) { + min = act.requests; + candidate = act; + } + } + assert candidate != null; assert candidate != notThisOne : "Closing just opened JarSource: " + notThisOne; - return candidate; + return candidate; } @Override @@ -913,10 +904,10 @@ public String getIdentifier() { } } - static class DirSource extends Source { + private static class DirSource extends Source { File dir; Manifest manifest; - + DirSource(File file) throws MalformedURLException { super(BaseUtilities.toURI(file).toURL()); dir = file; @@ -937,9 +928,10 @@ public Manifest getManifest() { Exceptions.printStackTrace(ex); } } - return manifest = mf; + manifest = mf; + return mf; } - + @Override public String getPath() { return dir.getPath(); @@ -950,23 +942,23 @@ protected URL doGetResource(String name) throws MalformedURLException { File resFile = new File(dir, name); return resFile.exists() ? BaseUtilities.toURI(resFile).toURL() : null; } - + @Override protected byte[] readClass(String path) throws IOException { File clsFile = new File(dir, path.replace('/', File.separatorChar)); if (!clsFile.exists()) return null; - + try (InputStream is = new FileInputStream(clsFile)) { return is.readAllBytes(); } } - + @Override - protected void listCoveredPackages(Set known, StringBuffer save) { + protected void listCoveredPackages(Set known, StringBuilder save) { appendAllChildren(known, save, dir, ""); } - - private static void appendAllChildren(Set known, StringBuffer save, File dir, String prefix) { + + private static void appendAllChildren(Set known, StringBuilder save, File dir, String prefix) { boolean populated = false; for (File f : dir.listFiles()) { if (f.isDirectory()) { @@ -994,7 +986,7 @@ private static void appendAllChildren(Set known, StringBuffer save, File } } } - + private static Iterable getCoveredPackages(Module mod, Source[] sources) { if (mod != null) { Set ret = mod.getCoveredPackages(); @@ -1002,10 +994,11 @@ private static Iterable getCoveredPackages(Module mod, Source[] sources) return ret; } } - - Set known = new HashSet(); + + Set known = new HashSet<>(); Manifest m = mod == null ? null : mod.getManifest(); if (m != null) { + assert mod != null; Attributes attr = m.getMainAttributes(); String pack = attr.getValue("Covered-Packages"); // NOI18N if (pack != null) { @@ -1014,9 +1007,9 @@ private static Iterable getCoveredPackages(Module mod, Source[] sources) return known; } } - + // not precomputed/cached, analyze - StringBuffer save = new StringBuffer(); + StringBuilder save = new StringBuilder(); for (Source s : sources) s.listCoveredPackages(known, save); if (save.length() > 0) save.setLength(save.length()-1); @@ -1025,12 +1018,13 @@ private static Iterable getCoveredPackages(Module mod, Source[] sources) } return known; } - + + @SuppressWarnings("PackageVisibleInnerClass") // Testing static class JarURLStreamHandler extends URLStreamHandler { private static final URLStreamHandler fallback = new URLStreamHandler() { protected @Override URLConnection openConnection(URL u) throws IOException { - return new URL(u.toString()).openConnection(); + return URI.create(u.toString()).toURL().openConnection(); } }; @@ -1089,31 +1083,31 @@ protected JarURLConnection openConnection(URL u) throws IOException { LOGGER.log(Level.FINER, "Calling original {0} yields {1}", new Object[]{originalJarHandler, ret}); } return ret; - } catch (Exception e) { - throw (IOException) new IOException(e.toString()).initCause(e); + } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException | RuntimeException e) { + throw new IOException(e.toString(), e); } } String _name = url.substring(bang + 2); try { _name = new URI(_name).getPath(); } catch (URISyntaxException x) { - throw (IOException) new IOException("Decoding " + u + ": " + x).initCause(x); + throw new IOException("Decoding " + u + ": " + x.getMessage(), x); } if (LOGGER.isLoggable(Level.FINER)) { LOGGER.log(Level.FINER, "creating NbJarURLConnection({0},{1},{2})", new Object[]{u, _src, _name}); } return new NbJarURLConnection (u, _src, _name, loader); } - + @Override protected void parseURL(URL u, String spec, int start, int limit) { if (spec.startsWith("/")) { setURL( - u, "jar", u.getHost(), u.getPort(), - u.getAuthority(), u.getUserInfo(), + u, "jar", u.getHost(), u.getPort(), + u.getAuthority(), u.getUserInfo(), u.getFile().replaceFirst("!/.*$", "!" + spec), // NOI18N u.getQuery(), u.getRef() - ); + ); } else { super.parseURL(u, spec, start, limit); } @@ -1125,7 +1119,7 @@ protected void parseURL(URL u, String spec, int start, int limit) { * */ private static class NbJarURLConnection extends JarURLConnection { - private JarSource src; + private final JarSource src; private final String name; private byte[] data; private InputStream iStream; diff --git a/platform/o.n.bootstrap/src/org/netbeans/ProxyClassPackages.java b/platform/o.n.bootstrap/src/org/netbeans/ProxyClassPackages.java index eb26dfa5697e..7296a965706d 100644 --- a/platform/o.n.bootstrap/src/org/netbeans/ProxyClassPackages.java +++ b/platform/o.n.bootstrap/src/org/netbeans/ProxyClassPackages.java @@ -41,12 +41,12 @@ private ProxyClassPackages() { static void addCoveredPackages( ProxyClassLoader loader, Iterable coveredPackages) { synchronized(packageCoverage) { for (String pkg : coveredPackages) { - Set delegates = ProxyClassPackages.packageCoverage.get(pkg); - if (delegates == null) { + Set delegates = ProxyClassPackages.packageCoverage.get(pkg); + if (delegates == null) { delegates = Collections.singleton(loader); ProxyClassPackages.packageCoverage.put(pkg, delegates); } else if (delegates.size() == 1) { - delegates = new HashSet(delegates); + delegates = new HashSet<>(delegates); ProxyClassPackages.packageCoverage.put(pkg, delegates); delegates.add(loader); } else { @@ -55,7 +55,7 @@ static void addCoveredPackages( ProxyClassLoader loader, Iterable covere } } } - + static void removeCoveredPakcages(ProxyClassLoader loader) { synchronized (packageCoverage) { packageCoverage.values().removeIf( (Set set) -> { @@ -74,5 +74,5 @@ static Set findCoveredPkg(String pkg) { return v; }); } - + } diff --git a/platform/o.n.bootstrap/src/org/netbeans/StandardModule.java b/platform/o.n.bootstrap/src/org/netbeans/StandardModule.java index 9eb0b47f2f1b..7901fb719993 100644 --- a/platform/o.n.bootstrap/src/org/netbeans/StandardModule.java +++ b/platform/o.n.bootstrap/src/org/netbeans/StandardModule.java @@ -70,21 +70,21 @@ class StandardModule extends Module { /** if reloadable, temporary JAR file actually loaded from */ private File physicalJar; private Manifest manifest; - + /** Simple registry of JAR files used as modules. * Used only for debugging purposes, so that we can be sure * that no one is using Class-Path to refer to other modules. */ - private static final Set moduleJARs = new HashSet(); + private static final Set moduleJARs = new HashSet<>(); /** Patches added at the front of the classloader (or null). * Files are assumed to be JARs; directories are themselves. */ private Set patches; - + /** localized properties, only non-null if requested from disabled module */ private Properties localizedProps; - + /** Use ModuleManager.create as a factory. */ public StandardModule(ModuleManager mgr, Events ev, File jar, Object history, boolean reloadable, boolean autoload, boolean eager) throws IOException { super(mgr, ev, history, reloadable, autoload, eager); @@ -116,7 +116,7 @@ ModuleData createData(DataInput in, Manifest mf) throws IOException { public @Override void releaseManifest() { manifest = null; } - + /** Get a localized attribute. * First, if OpenIDE-Module-Localizing-Bundle was given, the specified * bundle file (in all locale JARs as well as base JAR) is searched for @@ -150,13 +150,14 @@ public Object getLocalizedAttribute(String attr) { } } catch (MissingResourceException mre) { String resource = basename.replace('.', '/') + ".properties"; - Exceptions.attachMessage(mre, "#149833: failed to find " + basename + + Exceptions.printStackTrace( + Exceptions.attachMessage(mre, "#149833: failed to find " + basename + " in locale " + Locale.getDefault() + " in " + classloader + " for " + jar + - "; resource lookup of " + resource + " -> " + classloader.getResource(resource)); - Exceptions.printStackTrace(mre); + "; resource lookup of " + resource + " -> " + classloader.getResource(resource)) + ); } } else { - Util.err.warning("cannot efficiently load non-*.properties OpenIDE-Module-Localizing-Bundle: " + locb); + Util.err.log(Level.WARNING, "cannot efficiently load non-*.properties OpenIDE-Module-Localizing-Bundle: {0}", locb); } } if (!usingLoader) { @@ -200,12 +201,12 @@ public Object getLocalizedAttribute(String attr) { } } } - + @Override public boolean isFixed() { return false; } - + /** Get the JAR this module is packaged in. * May be null for modules installed specially, e.g. * automatically from the classpath. @@ -214,7 +215,7 @@ public boolean isFixed() { public @Override File getJarFile() { return jar; } - + /** Create a temporary test JAR if necessary. * This is primarily necessary to work around a Java bug, * #4405789, which is marked as fixed so might be obsolete. @@ -228,21 +229,21 @@ private void destroyPhysicalJar() { if (physicalJar != null) { if (physicalJar.isFile()) { if (! physicalJar.delete()) { - Util.err.warning("temporary JAR " + physicalJar + " not currently deletable."); + Util.err.log(Level.WARNING, "temporary JAR {0} not currently deletable.", physicalJar); } else { - Util.err.fine("deleted: " + physicalJar); + Util.err.log(Level.FINE, "deleted: {0}", physicalJar); } } physicalJar = null; } else { - Util.err.fine("no physicalJar to delete for " + this); + Util.err.log(Level.FINE, "no physicalJar to delete for {0}", this); } } - + /** Open the JAR, load its manifest, and do related things. */ private void loadManifest() throws IOException { if (Util.err.isLoggable(Level.FINE)) { - Util.err.fine("loading manifest of " + jar); + Util.err.log(Level.FINE, "loading manifest of {0}", jar); } File jarBeingOpened = null; // for annotation purposes try { @@ -262,14 +263,15 @@ private void loadManifest() throws IOException { } } catch (IOException e) { if (jarBeingOpened != null) { - Exceptions.attachMessage(e, + throw Exceptions.attachMessage(e, "While loading manifest from: " + jarBeingOpened); // NOI18N } throw e; } } - + + @SuppressWarnings("ReturnOfCollectionOrArrayField") // collection is only accessed in StandardModule private Set findPatches() { if (patches == null) { // #9273: load any modules/patches/this-code-name/*.jar files first: @@ -280,12 +282,12 @@ private Set findPatches() { if (jars != null) { for (File patchJar : jars) { if (patches == null) { - patches = new HashSet(5); + patches = new HashSet<>(5); } patches.add(patchJar); } } else { - Util.err.warning("Could not search for patches in " + patchdir); + Util.err.log(Level.WARNING, "Could not search for patches in {0}", patchdir); } } // The following system property is used @@ -300,7 +302,7 @@ private Set findPatches() { File fileElement = new File(element); if (fileElement.exists()) { if (patches == null) { - patches = new HashSet(15); + patches = new HashSet<>(15); } patches.add(fileElement); } @@ -318,11 +320,11 @@ private Set findPatches() { patches = Collections.emptySet(); } } - + return patches; } - - + + /** Check if there is any need to load localized properties. * If so, try to load them. Throw an exception if they cannot * be loaded for some reason. Uses an open JAR file for the @@ -391,7 +393,7 @@ private void loadLocalizedProps(JarFile jarFile, Manifest m) throws IOException */ } } - + /** Get all JARs loaded by this module. * Includes the module itself, any locale variants of the module, * any extensions specified with Class-Path, any locale variants @@ -405,7 +407,7 @@ private void loadLocalizedProps(JarFile jarFile, Manifest m) throws IOException */ @Override public List getAllJars() { - List l = new ArrayList(); + List l = new ArrayList<>(); Set ptchs = findPatches(); if (ptchs != null) l.addAll(ptchs); if (physicalJar != null) { @@ -432,7 +434,7 @@ public void setReloadable(boolean r) { getManager().fireReloadable(this); } } - + /** Reload this module. Access from ModuleManager. * If an exception is thrown, the module is considered * to be in an invalid state. @@ -452,7 +454,7 @@ public void reload() throws IOException { throw new InvalidException("Code name base changed during reload: " + codeNameBase1 + " -> " + codeNameBase2); // NOI18N } } - + // Access from ModuleManager: /** Turn on the classloader. Passed a list of parent modules to use. * The parents should already have had their classloaders initialized. @@ -460,10 +462,10 @@ public void reload() throws IOException { @Override protected void classLoaderUp(Set parents) throws IOException { if (Util.err.isLoggable(Level.FINE)) { - Util.err.fine("classLoaderUp on " + this + " with parents " + parents); + Util.err.log(Level.FINE, "classLoaderUp on {0} with parents {1}", new Object[]{this, parents}); } // Find classloaders for dependent modules and parent to them. - List loaders = new ArrayList(parents.size() + 1); + List loaders = new ArrayList<>(parents.size() + 1); // This should really be the base loader created by org.nb.Main for loading openide etc.: loaders.add(Module.class.getClassLoader()); for (Module parent: parents) { @@ -494,7 +496,7 @@ protected void classLoaderUp(Set parents) throws IOException { } loaders.add(l); } - List classp = new ArrayList(3); + List classp = new ArrayList<>(3); Set ptchs = findPatches(); if (ptchs != null) classp.addAll(ptchs); @@ -507,12 +509,12 @@ protected void classLoaderUp(Set parents) throws IOException { } else { classp.add(jar); } - + ((StandardModuleData)data()).addCp(classp); // possibly inject some patches getManager().refineModulePath(this, classp); - + // #27853 ClassLoader cld = getManager().refineClassLoader(this, loaders); // the classloader may be shared, if this module is a fragment @@ -527,12 +529,12 @@ protected void classLoaderUp(Set parents) throws IOException { } } } - + /** Setup a new module with the given class path and the set of parent * class loaders. */ protected ClassLoader createNewClassLoader(List classp, List parents) { - return new OneModuleClassLoader(classp, parents.toArray(new ClassLoader[0])); + return new OneModuleClassLoader(classp, parents.toArray(ClassLoader[]::new)); } /** Get the class loader of a particular parent module. */ @@ -556,20 +558,20 @@ protected void cleanup() { // XXX should this rather be done when the classloader is collected? destroyPhysicalJar(); } - + /** Notify the module that it is being deleted. */ @Override public void destroy() { moduleJARs.remove(jar); } - + /** String representation for debugging. */ public @Override String toString() { String s = "StandardModule:" + getCodeNameBase() + " jarFile: " + jar.getAbsolutePath(); // NOI18N if (!isValid()) s += "[invalid]"; // NOI18N return s; } - + /** PermissionCollection with an instance of AllPermission. */ private static PermissionCollection modulePermissions; /** @return initialized @see #modulePermission */ @@ -581,7 +583,7 @@ private static synchronized PermissionCollection getAllPermission() { } return modulePermissions; } - + static boolean isModuleJar(File f) { return moduleJARs.contains(f); } @@ -590,7 +592,7 @@ static boolean isModuleJar(File f) { /** Class loader to load a single module. * Auto-localizing, multi-parented, permission-granting, the works. */ - class OneModuleClassLoader extends JarClassLoader implements Util.ModuleProvider { + private class OneModuleClassLoader extends JarClassLoader implements Util.ModuleProvider { /** Create a new loader for a module. * @param classp the List of all module jars of code directories; * includes the module itself, its locale variants, @@ -601,12 +603,12 @@ class OneModuleClassLoader extends JarClassLoader implements Util.ModuleProvider public OneModuleClassLoader(List classp, ClassLoader[] parents) throws IllegalArgumentException { super(classp, parents, false, StandardModule.this); } - + @Override public Module getModule() { return StandardModule.this; } - + /** Inherited. * @param cs is ignored * @return PermissionCollection with an AllPermission instance @@ -614,7 +616,7 @@ public Module getModule() { protected @Override PermissionCollection getPermissions(CodeSource cs) { return getAllPermission(); } - + /** * Look up a native library as described in modules documentation. * @see http://bits.netbeans.org/dev/javadoc/org-openide-modules/org/openide/modules/doc-files/api.html#jni @@ -643,7 +645,7 @@ public Module getModule() { CL_LOG.log(Level.FINE, "found {0}", lib); return lib.getAbsolutePath(); } - + if( BaseUtilities.isMac() ) { String jniMapped = mapped.replaceFirst("\\.dylib$",".jnilib"); lib = ifl.locate("modules/lib/" + jniMapped, getCodeNameBase(), false); // NOI18N @@ -682,7 +684,7 @@ public Module getModule() { } return getManager().shouldDelegateResource(StandardModule.this, other, pkg, parent); } - + public @Override String toString() { return "ModuleCL@" + Integer.toHexString(System.identityHashCode(this)) + "[" + getCodeNameBase() + "]"; // NOI18N } diff --git a/platform/o.n.bootstrap/test/unit/src/org/netbeans/JarClassLoaderTest.java b/platform/o.n.bootstrap/test/unit/src/org/netbeans/JarClassLoaderTest.java index d7c2740bdf24..ab4488d625ec 100644 --- a/platform/o.n.bootstrap/test/unit/src/org/netbeans/JarClassLoaderTest.java +++ b/platform/o.n.bootstrap/test/unit/src/org/netbeans/JarClassLoaderTest.java @@ -18,7 +18,6 @@ */ package org.netbeans; -import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileOutputStream; @@ -33,6 +32,7 @@ import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; @@ -64,7 +64,7 @@ */ public class JarClassLoaderTest extends NbTestCase { - private static Logger LOGGER = Logger.getLogger(ProxyClassLoader.class.getName()); + private static final Logger LOGGER = Logger.getLogger(ProxyClassLoader.class.getName()); public JarClassLoaderTest(String name) { @@ -83,7 +83,7 @@ public void testCanLoadFromDefaultPackage() throws Exception { File jar = new File(getWorkDir(), "default-package-resource.jar"); TestFileUtils.writeZipFile(jar, "resource.txt:content", "package/resource.txt:content"); JarClassLoader jcl = new JarClassLoader(Collections.singletonList(jar), new ProxyClassLoader[0]); - + assertStreamContent(jcl.getResourceAsStream("package/resource.txt"), "content"); assertStreamContent(jcl.getResourceAsStream("/package/resource.txt"), "content"); assertStreamContent(jcl.getResourceAsStream("resource.txt"), "content"); @@ -105,9 +105,10 @@ public void testKnowsWhichJarsHaveDefaultPackage() throws Exception { TestFileUtils.writeZipFile(a2, "A.txt:A2", "package/resourceA2.txt:content"); final File b = new File(getWorkDir(), "b.jar"); TestFileUtils.writeZipFile(b, "B.txt:B", "package/resourceB.txt:content"); - - + + class CntJCL extends JarClassLoader { + @SuppressWarnings("PackageVisibleField") int queried; public CntJCL(List files, ClassLoader[] parents) { @@ -125,7 +126,7 @@ public Enumeration findResources(String name) { queried++; return super.findResources(name); } - + } final CntJCL[] arr = new CntJCL[] { new CntJCL(Collections.singletonList(nothing), new ClassLoader[0]), @@ -133,23 +134,23 @@ public Enumeration findResources(String name) { new CntJCL(Collections.singletonList(a2), new ClassLoader[0]), new CntJCL(Collections.singletonList(b), new ClassLoader[0]), }; - + ProxyClassLoader pcl = new ProxyClassLoader(arr, true); - + assertURLsContent(pcl.getResources("A.txt"), "A", "A2"); - + assertEquals("No query to nothing.jar", 0, arr[0].queried); assertEquals("One query to a1.jar", 1, arr[1].queried); assertEquals("One query to a2.jar", 1, arr[2].queried); assertEquals("No query to b.jar", 0, arr[3].queried); - + assertURLsContent(pcl.getResources("B.txt"), "B"); - + assertEquals("No query to nothing.jar", 0, arr[0].queried); assertEquals("Still One query to a1.jar", 1, arr[1].queried); assertEquals("Still One query to a2.jar", 1, arr[2].queried); assertEquals("One query to b.jar now", 1, arr[3].queried); - + } public void testCanLoadFromDefaultPackageCachedOldFormat() throws Exception { doCanLoadCached("META-INF,/MANIFEST.MF,package"); @@ -157,28 +158,29 @@ public void testCanLoadFromDefaultPackageCachedOldFormat() throws Exception { public void testCanLoadFromDefaultPackageCached() throws Exception { doCanLoadCached("META-INF,/MANIFEST.MF,package,default/resource.txt"); } - + private void doCanLoadCached(String covPkg) throws Exception { final File jar = new File(getWorkDir(), "default-package-resource-cached.jar"); - TestFileUtils.writeZipFile(jar, "resource.txt:content", "package/resource.txt:content", + TestFileUtils.writeZipFile(jar, "resource.txt:content", "package/resource.txt:content", "META-INF/MANIFEST.MF:OpenIDE-Module: x.y.z\nCovered-Packages: " + covPkg + ",\n" ); MockModuleInstaller inst = new MockModuleInstaller(); MockEvents ev = new MockEvents(); ModuleManager mm = new ModuleManager(inst, ev); - + Module fake = new Module(mm, null, null, null) { - public List getAllJars() {throw new UnsupportedOperationException();} - public void setReloadable(boolean r) { throw new UnsupportedOperationException();} - public void reload() throws IOException { throw new UnsupportedOperationException();} - protected void classLoaderUp(Set parents) throws IOException {throw new UnsupportedOperationException();} - protected void classLoaderDown() { throw new UnsupportedOperationException();} - protected void cleanup() { throw new UnsupportedOperationException();} - protected void destroy() { throw new UnsupportedOperationException("Not supported yet.");} - public boolean isFixed() { throw new UnsupportedOperationException("Not supported yet.");} - public Object getLocalizedAttribute(String attr) { throw new UnsupportedOperationException("Not supported yet.");} + @Override public List getAllJars() {throw new UnsupportedOperationException();} + @Override public void setReloadable(boolean r) { throw new UnsupportedOperationException();} + @Override public void reload() throws IOException { throw new UnsupportedOperationException();} + @Override protected void classLoaderUp(Set parents) throws IOException {throw new UnsupportedOperationException();} + @Override protected void classLoaderDown() { throw new UnsupportedOperationException();} + @Override protected void cleanup() { throw new UnsupportedOperationException();} + @Override protected void destroy() { throw new UnsupportedOperationException("Not supported yet.");} + @Override public boolean isFixed() { throw new UnsupportedOperationException("Not supported yet.");} + @Override public Object getLocalizedAttribute(String attr) { throw new UnsupportedOperationException("Not supported yet.");} + @Override public Manifest getManifest() { try { return new JarFile(jar, false).getManifest(); @@ -190,7 +192,7 @@ public Manifest getManifest() { }; JarClassLoader jcl = new JarClassLoader(Collections.singletonList(jar), new ProxyClassLoader[0], false, fake); - + assertStreamContent(jcl.getResourceAsStream("package/resource.txt"), "content"); assertStreamContent(jcl.getResourceAsStream("/package/resource.txt"), "content"); assertStreamContent(jcl.getResourceAsStream("resource.txt"), "content"); @@ -208,7 +210,7 @@ public void testCanLoadFromDefaultPackageDirs() throws Exception { TestFileUtils.writeFile(new File(dir, "package/resource.txt"), "content"); TestFileUtils.writeFile(new File(dir, "META-INF/services/resource.txt"), "content"); JarClassLoader jcl = new JarClassLoader(Collections.singletonList(dir), new ProxyClassLoader[0]); - + assertStreamContent(jcl.getResourceAsStream("package/resource.txt"), "content"); assertStreamContent(jcl.getResourceAsStream("/package/resource.txt"), "content"); assertStreamContent(jcl.getResourceAsStream("resource.txt"), "content"); @@ -238,23 +240,6 @@ public void testJarURLConnection() throws Exception { assertEquals(jar.getAbsolutePath(), jconn.getJarFile().getName()); } - public void testAddURLMethod() throws Exception { - File jar = new File(getWorkDir(), "default-package-resource.jar"); - TestFileUtils.writeZipFile(jar, "META-INF/MANIFEST.MF:Manifest-Version: 1.0\nfoo: bar\n\n", "package/re source++.txt:content"); - JarClassLoader jcl = new JarClassLoader(Collections.emptyList(), new ProxyClassLoader[0]); - jcl.addURL(Utilities.toURI(jar).toURL()); - URL url = jcl.getResource("package/re source++.txt"); - assertTrue(url.toString(), url.toString().endsWith("default-package-resource.jar!/package/re%20source++.txt")); - URLConnection conn = url.openConnection(); - assertEquals(7, conn.getContentLength()); - assertTrue(conn instanceof JarURLConnection); - JarURLConnection jconn = (JarURLConnection) conn; - assertEquals("package/re source++.txt", jconn.getEntryName()); - assertEquals(Utilities.toURI(jar).toURL(), jconn.getJarFileURL()); - assertEquals("bar", jconn.getMainAttributes().getValue("foo")); - assertEquals(jar.getAbsolutePath(), jconn.getJarFile().getName()); - } - public void testResourceDefinition() throws Exception { // #196595 File jar = new File(getWorkDir(), "some.jar"); TestFileUtils.writeZipFile(jar, "package/resource.txt:content"); @@ -294,25 +279,25 @@ public Enumeration getResources(String name) throws IOException { } } ClassLoader jdkonly = new JDKOnly(); - + File jar = new File(getWorkDir(), "some.jar"); TestFileUtils.writeZipFile( - jar, + jar, "META-INF/services/java.io.Serializable:org.netbeans.MetaInfServicesToken" ); URL url = MetaInfServicesToken.class.getProtectionDomain().getCodeSource().getLocation(); URLClassLoader one = new URLClassLoader(new URL[] { url }, jdkonly); URLClassLoader two = new URLClassLoader(new URL[] { url }, jdkonly); - + final String name = MetaInfServicesToken.class.getName(); Class cOne = one.loadClass(name); Class cTwo = two.loadClass(name); - + if (cOne == cTwo) { fail("Classes should be different, not loaded by: " + cOne.getClassLoader()); } - + ClassLoader cl = new JarClassLoader(Collections.singletonList(jar), new ClassLoader[] { two }); ProxyClassLoader all = new ProxyClassLoader(new ClassLoader[] { one, cl }, false); Object res = Lookups.metaInfServices(all).lookup(Serializable.class); @@ -327,15 +312,12 @@ private void assertURLsContent(Enumeration urls, String ... contents) throw } assertFalse("Too many entries", urls.hasMoreElements()); } - + private void assertStreamContent(InputStream str, String content) throws IOException { assertNotNull("Resource found", str); byte[] data = new byte[content.length()]; - DataInputStream dis = new DataInputStream(str); - try { + try (DataInputStream dis = new DataInputStream(str)) { dis.readFully(data); - } finally { - dis.close(); } assertEquals(new String(data), content); } @@ -355,8 +337,9 @@ public void interruptImpl(int toInterrupt) throws Exception { BlockingSecurityManager.initialize(jar.toString(), readSemaphore); class Tester extends Thread { - int slot; + private final int slot; + @SuppressWarnings("CallToThreadStartDuringObjectConstruction") Tester(int slot) throws Exception { this.slot = slot; start(); @@ -374,7 +357,7 @@ public void run() { results[slot] = t; } } - }; + } Thread[] threads = new Thread[] { new Tester(0), new Tester(1) }; // threads[0] has reached the blocking point while opening the jar @@ -403,7 +386,7 @@ public void testCanInterruptWaitingThread() throws Exception { private static class BlockingSecurityManager extends SecurityManager { private static String path; private static Semaphore sync; - + public static void initialize(String path, Semaphore sync) { BlockingSecurityManager.path = path; BlockingSecurityManager.sync = sync; @@ -413,7 +396,7 @@ public static void initialize(String path, Semaphore sync) { System.setSecurityManager(new BlockingSecurityManager()); } } - + public @Override void checkRead(String file) { if (file.equals(path)) { sync.acquireUninterruptibly(); @@ -424,7 +407,7 @@ public static void initialize(String path, Semaphore sync) { checkRead(file); } - + public @Override void checkPermission(Permission perm) {} public @Override void checkPermission(Permission perm, Object ctx) {} @@ -433,38 +416,7 @@ public static void initialize(String path, Semaphore sync) { public void testMultiReleaseJar() throws Exception { clearWorkDir(); - // Prepare multi-release jar file - File classes = new File(getWorkDir(), "classes"); - classes.mkdirs(); - ToolProvider.getSystemJavaCompiler() - .getTask(null, null, d -> { throw new IllegalStateException(d.toString()); }, Arrays.asList("-d", classes.getAbsolutePath(), "-proc:none"), null, - Arrays.asList(new SourceFileObject("test/Impl.java", "package test; public class Impl { public static String get() { return \"base\"; } }"), - new SourceFileObject("api/API.java", "package api; public class API { public static String run() { return test.Impl.get(); } }"))) - .call(); - File classes9 = new File(new File(new File(classes, "META-INF"), "versions"), "9"); - classes9.mkdirs(); - ToolProvider.getSystemJavaCompiler() - .getTask(null, null, d -> { throw new IllegalStateException(d.toString()); }, Arrays.asList("-d", classes9.getAbsolutePath(), "-classpath", classes.getAbsolutePath(), "-proc:none"), null, - Arrays.asList(new SourceFileObject("test/Impl.java", "package test; public class Impl { public static String get() { return \"9\"; } }"))) - .call(); - Map jarContent = new LinkedHashMap<>(); - jarContent.put("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\nMulti-Release: true\n\n".getBytes()); - Path classesPath = classes.toPath(); - Files.walk(classesPath) - .filter(p -> Files.isRegularFile(p)) - .forEach(p -> { - try { - jarContent.put(classesPath.relativize(p).toString(), TestFileUtils.readFileBin(p.toFile())); - } catch (IOException ex) { - throw new IllegalStateException(ex); - } - }); - jarContent.put("test/dummy.txt", "base".getBytes(UTF_8)); - jarContent.put("META-INF/versions/9/test/dummy.txt", "9".getBytes(UTF_8)); - File jar = new File(getWorkDir(), "multi-release.jar"); - try (OutputStream out = new FileOutputStream(jar)) { - TestFileUtils.writeZipFile(out, jarContent); - } + File jar = prepareMultireleaseTestJar(); // Check multi release class loading JarClassLoader jcl = new JarClassLoader(Arrays.asList(jar), new ProxyClassLoader[0]); @@ -482,18 +434,72 @@ public void testMultiReleaseJar() throws Exception { // Check multi release resource loading try(InputStream is = jcl.getResourceAsStream("test/dummy.txt")) { - assertEquals(expected, loadUTF8(is)); + assertEquals(expected, new String(is.readAllBytes(), StandardCharsets.UTF_8)); } } - private static String loadUTF8(InputStream is) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] buffer = new byte[2048]; - int read; - while ((read = is.read(buffer)) > 0) { - baos.write(buffer, 0, read); + public void testLoadClassPackageOnlyVersioned() throws IOException, URISyntaxException, ClassNotFoundException { + clearWorkDir(); + + File jar = prepareMultireleaseTestJar(); + + // try to load a class that is in a package, that is only present in + // a package that only exists the versioned tree of the JAR. + // META-INF/versions/ + JarClassLoader jcl = new JarClassLoader(Arrays.asList(jar), new ProxyClassLoader[0]); + Class loadedClass = jcl.loadClass("internal.Demo"); + assertNotNull(loadedClass); + } + + public File prepareMultireleaseTestJar() throws URISyntaxException, IOException { + // Prepare multi-release jar file + File classes = new File(getWorkDir(), "classes"); + classes.mkdirs(); + ToolProvider.getSystemJavaCompiler() + .getTask( + null, + null, + d -> { throw new IllegalStateException(d.toString()); }, + Arrays.asList("-d", classes.getAbsolutePath(), "-proc:none"), + null, + Arrays.asList( + new SourceFileObject("test/Impl.java", "package test; public class Impl { public static String get() { return \"base\"; } }"), + new SourceFileObject("api/API.java", "package api; public class API { public static String run() { return test.Impl.get(); } }") + )) + .call(); + File classes9 = new File(new File(new File(classes, "META-INF"), "versions"), "9"); + classes9.mkdirs(); + ToolProvider.getSystemJavaCompiler() + .getTask( + null, + null, + d -> { throw new IllegalStateException(d.toString()); }, + Arrays.asList("-d", classes9.getAbsolutePath(), "-classpath", classes.getAbsolutePath(), "-proc:none"), + null, + Arrays.asList( + new SourceFileObject("test/Impl.java", "package test; public class Impl { public static String get() { return \"9\"; } }"), + new SourceFileObject("internal/Demo.java", "package internal; public class Demo { public static String get() { return \"9\"; } }") + )) + .call(); + Map jarContent = new LinkedHashMap<>(); + jarContent.put("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\nMulti-Release: true\n\n".getBytes()); + Path classesPath = classes.toPath(); + Files.walk(classesPath) + .filter(p -> Files.isRegularFile(p)) + .forEach(p -> { + try { + jarContent.put(classesPath.relativize(p).toString(), TestFileUtils.readFileBin(p.toFile())); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + }); + jarContent.put("test/dummy.txt", "base".getBytes(UTF_8)); + jarContent.put("META-INF/versions/9/test/dummy.txt", "9".getBytes(UTF_8)); + File jar = new File(getWorkDir(), "multi-release.jar"); + try (OutputStream out = new FileOutputStream(jar)) { + TestFileUtils.writeZipFile(out, jarContent); } - return baos.toString("UTF-8"); + return jar; } private static final class SourceFileObject extends SimpleJavaFileObject {