blob: 077ccf2a8bfc656e4a053f485c4e92a01e55ccc5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.netbinox;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.jar.Attributes.Name;
import java.util.jar.Manifest;
import java.util.logging.Level;
import org.eclipse.osgi.baseadaptor.BaseData;
import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
import org.eclipse.osgi.baseadaptor.bundlefile.DirBundleFile;
import org.eclipse.osgi.baseadaptor.bundlefile.MRUBundleFileList;
import org.eclipse.osgi.baseadaptor.bundlefile.ZipBundleFile;
import org.netbeans.core.netigso.spi.BundleContent;
import org.netbeans.core.netigso.spi.NetigsoArchive;
import org.openide.modules.ModuleInfo;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
/** This is fake bundle. It is created by the Netbinox infrastructure to
* use the {@link NetigsoArchive} to get cached data and speed up the start.
*
* @author Jaroslav Tulach <jtulach@netbeans.org>
*/
final class JarBundleFile extends BundleFile implements BundleContent {
//
// When making changes to this file, check if
// platform/o.n.bootstrap/src/org/netbeans/JarClassLoader.java (JarClassLoader/JarSource)
// should also be adjusted. At least the multi-release handling is similar.
//
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;
private static Map<Long,File> usedIds;
static {
int version;
try {
Object runtimeVersion = Runtime.class.getMethod("version").invoke(null);
version = (int) runtimeVersion.getClass().getMethod("major").invoke(runtimeVersion);
} catch (ReflectiveOperationException ex) {
version = BASE_VERSION;
}
RUNTIME_VERSION = version;
}
private BundleFile delegate;
private final MRUBundleFileList mru;
private final BaseData data;
private final NetigsoArchive archive;
private int[] versions;
private Boolean isMultiRelease;
JarBundleFile(
File base, BaseData data, NetigsoArchive archive,
MRUBundleFileList mru, boolean isBase
) {
super(base);
long id;
if (isBase) {
id = data.getBundleID();
} else {
id = 100000 + base.getPath().hashCode();
}
boolean assertOn = false;
assert assertOn = true;
if (assertOn) {
if (usedIds == null) {
usedIds = new HashMap<Long, File>();
}
File prev = usedIds.put(id, base);
if (prev != null && !prev.equals(base)) {
NetbinoxFactory.LOG.log(
Level.WARNING,
"same id: {0} for {1} and {2}", // NOI18N
new Object[]{id, base, prev}
);
}
}
this.archive = archive.forBundle(id, this);
this.data = data;
this.mru = mru;
}
private synchronized BundleFile delegate(String who, String what) {
if (delegate == null) {
NetbinoxFactory.LOG.log(Level.FINE, "opening {0} because of {1} needing {2}", new Object[]{data.getLocation(), who, what});
try {
delegate = new ZipBundleFile(getBaseFile(), data, mru) {
@Override
protected boolean checkedOpen() {
try {
return getZipFile() != null;
} catch (IOException ex) {
final File bf = new File(getBaseFile().getPath());
if (bf.isDirectory()) {
try {
delegate = new DirBundleFile(bf, false);
return false;
} catch (IOException dirEx) {
NetbinoxFactory.LOG.log(Level.WARNING,
"Cannot create DirBundleFile for " + bf,
dirEx
);
}
}
NetbinoxFactory.LOG.log(Level.WARNING, "Cannot open bundle delegate {0}", bf);
if (!bf.isFile() || !bf.canRead()) {
delegate = EmptyBundleFile.EMPTY;
return false;
}
}
// no optimizations
return super.checkedOpen();
}
};
} catch (IOException ex) {
NetbinoxFactory.LOG.log(Level.WARNING, "Error creating delegate for {0} because of {1}", new Object[] { getBaseFile(), data.getLocation() });
delegate = EmptyBundleFile.EMPTY;
}
}
return delegate;
}
@Override
public File getBaseFile() {
final File file = super.getBaseFile();
class VFile extends File {
public VFile() {
super(file.getPath());
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public boolean isFile() {
return true;
}
@Override
public boolean exists() {
return true;
}
@Override
public File getAbsoluteFile() {
return this;
}
@Override
public long lastModified() {
return data.getLastModified();
}
}
return new VFile();
}
@Override
public File getFile(String file, boolean bln) {
if (((! file.startsWith(META_INF)) ) && isMultiRelease()) {
for (int version : getVersions()) {
File f = getFile0("META-INF/versions/" + version + "/" + file, bln);
if (f != null) {
return f;
}
}
}
return getFile0(file, bln);
}
private File getFile0(String file, boolean bln) {
byte[] exists = getCachedEntry(file);
if (exists == null) {
return null;
}
BundleFile d = delegate("getFile", file);
return d == null ? null : d.getFile(file, bln);
}
@Override
public byte[] resource(String name) throws IOException {
if ((! name.startsWith(META_INF)) && isMultiRelease()) {
for (int version : getVersions()) {
byte[] b = resource0("META-INF/versions/" + version + "/" + name);
if (b != null) {
return b;
}
}
}
return resource0(name);
}
private byte[] resource0(String name) throws IOException {
BundleEntry u = findEntry("resource", name);
if (u == null) {
return null;
}
InputStream is = u.getInputStream();
if (is == null) {
return new byte[0];
}
byte[] arr = null;
try {
arr = new byte[is.available()];
int pos = 0;
for (;;) {
int toRead = arr.length - pos;
if (toRead == 0) {
break;
}
int len = is.read(arr, pos, toRead);
if (len == -1) {
break;
}
pos += len;
}
if (pos != arr.length) {
throw new IOException("Not read enough: " + pos + " should have been: " + arr.length); // NOI18N
}
} finally {
is.close();
}
NetbinoxFactory.LOG.log(Level.FINE, "Loaded {1} bytes for {0}", new Object[] { name, arr.length }); // NOI18N
return arr;
}
private BundleEntry findEntry(String why, final String name) {
if (!name.equals("META-INF/MANIFEST.MF") && // NOI18N
data != null &&
data.getLocation() != null &&
data.getLocation().startsWith("netigso://") // NOI18N
) {
String cnb = data.getLocation().substring(10);
for (ModuleInfo mi : Lookup.getDefault().lookupAll(ModuleInfo.class)) {
if (mi.getCodeNameBase().equals(cnb)) {
if (!mi.isEnabled()) {
break;
}
final URL url = mi.getClassLoader().getResource(name);
if (url != null) {
return new ModuleEntry(url, name);
} else {
break;
}
}
}
}
if ("/".equals(name)) { // NOI18N
return new RootEntry(this); // NOI18N
}
BundleEntry u;
for (;;) {
BundleFile d = delegate(why, name);
u = d.getEntry(name);
if (u != null || d == delegate) {
break;
}
}
return u;
}
private byte[] getCachedEntry(String name) {
try {
return archive.fromArchive(name);
} catch (IOException ex) {
return null;
}
}
@Override
public BundleEntry getEntry(final String name) {
if ((! name.startsWith(META_INF)) && isMultiRelease()) {
for (int version : getVersions()) {
BundleEntry be = getEntry0("META-INF/versions/" + version + "/" + name);
if(be != null) {
return be;
}
}
}
return getEntry0(name);
}
private BundleEntry getEntry0(final String name) {
if (!archive.isActive()) {
return delegate("inactive", name).getEntry(name); // NOI18N
}
final byte[] arr = getCachedEntry(name);
if (arr == null && !name.equals("/")) {
return null;
}
return new CachingEntry(arr, name);
}
@Override
public Enumeration<String> getEntryPaths(String prefix) {
BundleFile d = delegate("getEntryPaths", prefix);
if (d == null) {
return Collections.enumeration(Collections.<String>emptyList());
}
return d.getEntryPaths(prefix);
}
@Override
public synchronized void close() throws IOException {
if (delegate != null) {
delegate.close();
}
}
@Override
public void open() throws IOException {
if (delegate != null) {
delegate.open();
}
}
@Override
public boolean containsDir(String path) {
return path.endsWith("/") && getEntry(path) != null;
}
private class CachingEntry extends BundleEntry {
private final String name;
private final int size;
private final Reference<byte[]> arr;
public CachingEntry(byte[] arr, String name) {
this.size = arr.length;
this.name = name;
this.arr = new SoftReference<byte[]>(arr);
}
@Override
public InputStream getInputStream() throws IOException {
byte[] data = arr.get();
// once used, let the array go...
arr.clear();
if (data == null) {
data = getCachedEntry(name);
}
if (data == null) {
throw new FileNotFoundException();
}
return new ByteArrayInputStream(data);
}
@Override
public long getSize() {
return size;
}
@Override
public String getName() {
return name;
}
@Override
public long getTime() {
return getBaseFile().lastModified();
}
@Override
public URL getLocalURL() {
return findEntry("getLocalURL", name).getLocalURL(); // NOI18N
}
@Override
public URL getFileURL() {
return findEntry("getFileURL", name).getFileURL(); // NOI18N
}
}
/**
* @return versions for which a {@code META-INF/versions/NUMBER} entry exists.
* The order is from largest version to lowest. Only versions supported by
* the runtime VM are reported.
*/
private int[] getVersions() {
if (versions != null) {
return versions;
}
Set<Integer> vers = new TreeSet<>(Collections.reverseOrder());
for(int i = BASE_VERSION; i <= RUNTIME_VERSION; i++) {
String directory = "META-INF/versions/" + i;
BundleEntry be = delegate("getVersions", directory).getEntry(directory);
if (be != null) {
vers.add(i);
}
}
int[] ret = new int[vers.size()];
int i = 0;
for (Integer ver : vers) {
ret[i++] = ver;
}
versions = ret;
return versions;
}
private boolean isMultiRelease() {
if(isMultiRelease != null) {
return isMultiRelease;
}
BundleEntry be = delegate("isMultiRelease", "META-INF/MANIFEST.MF").getEntry("META-INF/MANIFEST.MF");
if(be == null) {
isMultiRelease = false;
} else {
try {
Manifest manifest = new Manifest(be.getInputStream());
isMultiRelease = Boolean.valueOf(manifest.getMainAttributes().getValue(MULTI_RELEASE));
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
return isMultiRelease;
}
}