org.openide.filesystems.JarFileSystem Maven / Gradle / Ivy
/*
* 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.openide.filesystems;
import java.beans.PropertyVetoException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.management.ManagementFactory;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipException;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.util.RequestProcessor.Task;
import org.openide.util.BaseUtilities;
/** A virtual filesystem based on a JAR archive.
* For historical reasons many AbstractFileSystem.* methods are implemented
* as protected in this class. Do not call them! Subclasses might override
* them, or (better) use delegation.
*
Most module code should never create an instance of this class directly.
* Use {@link FileUtil#getArchiveRoot(FileObject)} instead.
* @author Jan Jancura, Jaroslav Tulach, Petr Hamernik, Radek Matous
*/
public class JarFileSystem extends AbstractFileSystem {
/** generated Serialized Version UID */
static final long serialVersionUID = -98124752801761145L;
/** One request proccesor shared for all instances of JarFileSystem*/
private static final RequestProcessor req = new RequestProcessor("JarFs - modification watcher", 1, false, false); // NOI18N
/** Controlls the LocalFileSystem's automatic refresh.
* If the refresh time interval is set from the System.property, than this value is used.
* Otherwise, the refresh time interval is set to 0, which means the refresh is disabled. */
private static final int REFRESH_TIME = Integer.getInteger("org.openide.filesystems.JarFileSystem.REFRESH_TIME", 0)
.intValue(); // NOI18N
/** maxsize for passing ByteArrayInputStream*/
private static final long MEM_STREAM_SIZE = 100000;
/** Maximal time for which closing of jar is postponed. */
private static final int CLOSE_DELAY_MAX = 5000;
/** Mininal time for which closing of jar is postponed. */
private static final int CLOSE_DELAY_MIN = 300;
/**
* Opened zip file of this filesystem is stored here or null.
*/
private transient JarFile jar;
/** Manifest file for jar
*/
private transient Manifest manifest;
/** Archive file.1
*/
private File root = new File("."); // NOI18N
/** Watches modification on root file */
private transient volatile RequestProcessor.Task watcherTask = null;
private transient volatile RequestProcessor.Task closeTask = null;
private transient long lastModification = 0;
private static final Logger LOGGER = Logger.getLogger(JarFileSystem.class.getName());
/*Should help to prevent closing JarFile if anybody has InputStream. Also this variable
is used as object for synchronization: synchronized(closeSync)*/
private transient Object closeSync = new Object();
private int checkTime = REFRESH_TIME;
/** number of FileObjects in using. If no one is used then the cached data
* is freed */
private transient long aliveCount = 0;
/** Cached image of JarFile capable of answering queries on type and children.
* There is a strong reference held while there is a living FileObject
* and a SoftReference for caching after all FOs are freed.*/
private transient Cache strongCache;
/** The soft part of the cache reference. For simplicity never null*/
private transient Reference softCache = new SoftReference(null);
private transient FileObject foRoot;
private transient FileChangeListener fcl;
/** Actual time for which closing of jar is postponed. */
private transient int closeDelay = CLOSE_DELAY_MIN;
/** Time of request for opening of jar. */
private transient long openRequestTime = 0;
/**
* Default constructor.
* Most module code should never create an instance of this class directly.
* Use {@link FileUtil#getArchiveRoot(FileObject)} instead.
*/
public JarFileSystem() {
Impl impl = new Impl(this);
this.list = impl;
this.info = impl;
this.change = impl;
this.attr = impl;
}
/** Creates new JAR for a given JAR file. This constructor
* behaves basically like:
*
* JarFileSystem fs = new JarFileSystem();
* fs.setJarFile(jar);
*
* but it is more effective in some situations. It does not open and
* read the content of the jar file immediately. Instead
* it waits until somebody asks for resources from inside the JAR.
*
* @param jar location of the JAR file
* @since 7.34
*/
public JarFileSystem(File jar) throws IOException {
this();
try {
setJarFile(jar, true, false);
} catch (PropertyVetoException ex) {
// cannot happen, setSystemName can throw the exception only
// if the filesystem is already in Repository, which this one
// is not.
throw (IOException)new IOException().initCause(ex);
}
}
/* Creates Reference. In FileSystem, which subclasses AbstractFileSystem, you can overload method
* createReference(FileObject fo) to achieve another type of Reference (weak, strong etc.)
* @param fo is FileObject. It`s reference yourequire to get.
* @return Reference to FileObject
*/
@Override
protected Reference createReference(T fo) {
aliveCount++;
if ((checkTime > 0) && (watcherTask == null)) {
watcherTask = req.post(watcherTask(), checkTime);
}
return new Ref(fo);
}
private void freeReference() {
aliveCount--;
// Nobody uses this JarFileSystem => stop watcher, close JarFile and throw away cache.
if (aliveCount == 0) {
Task w = watcherTask;
if (w != null) {
w.cancel();
watcherTask = null;
}
strongCache = null; // no more active FO, keep only soft ref
closeCurrentRoot(false);
}
}
/** Get the JAR manifest.
* It will be lazily initialized.
* @return parsed manifest file for this archive
*/
public Manifest getManifest() {
if (manifest == null) {
try {
synchronized (closeSync) {
JarFile j = reOpenJarFile();
manifest = (j == null) ? null : j.getManifest();
manifest = (manifest == null) ? null : new Manifest(manifest);
}
} catch (IOException ex) {
} finally {
closeCurrentRoot(false);
}
if (manifest == null) {
manifest = new Manifest();
}
}
return manifest;
}
/**
* Set name of the ZIP/JAR file.
* @param aRoot path to new ZIP or JAR file
* @throws IOException if the file is not valid
* @throws IllegalArgumentException if the file is {@link FileUtil#normalizeFile(java.io.File) not normalized}
*/
public void setJarFile(final File aRoot) throws IOException, PropertyVetoException {
setJarFile(aRoot, true, true);
}
@SuppressWarnings("deprecation") // need to set it for compat
private void _setSystemName(String s) throws PropertyVetoException {
setSystemName(s);
}
private void setJarFile(final File aRoot, boolean refreshRoot, boolean openJar)
throws IOException, PropertyVetoException {
if (!aRoot.equals(FileUtil.normalizeFile(aRoot))) {
throw new IllegalArgumentException(
"Parameter aRoot was not " + // NOI18N
"normalized. Was " + aRoot + " instead of " + FileUtil.normalizeFile(aRoot)
); // NOI18N
}
FileObject newRoot = null;
String oldDisplayName = getDisplayName();
if (getRefreshTime() > 0) {
setRefreshTime(0);
}
if (aRoot == null) {
throw new FSException(NbBundle.getMessage(JarFileSystem.class, "EXC_NotValidFile", aRoot));
}
if (!aRoot.exists()) {
throw new FSException(NbBundle.getMessage(JarFileSystem.class, "EXC_FileNotExists", aRoot.getAbsolutePath()));
}
if (!aRoot.canRead()) {
throw new FSException(NbBundle.getMessage(JarFileSystem.class, "EXC_CanntRead", aRoot.getAbsolutePath()));
}
if (!aRoot.isFile()) {
throw new FSException(NbBundle.getMessage(JarFileSystem.class, "EXC_NotValidFile", aRoot.getAbsolutePath()));
}
String s;
s = aRoot.getAbsolutePath();
s = s.intern();
JarFile tempJar = null;
if (openJar) {
try {
tempJar = new JarFile(s, false);
LOGGER.log(Level.FINE, "opened: "+ System.currentTimeMillis()+ " " + s);//NOI18N
} catch (ZipException e) {
throw new FSException(NbBundle.getMessage(JarFileSystem.class, "EXC_NotValidJarFile2", e.getLocalizedMessage(), s));
}
}
synchronized (closeSync) {
_setSystemName(s);
closeCurrentRoot(false);
setJar(tempJar);
openRequestTime = System.currentTimeMillis();
root = new File(s);
if (refreshRoot) {
strongCache = null;
softCache.clear();
aliveCount = 0;
newRoot = refreshRoot();
manifest = null;
lastModification = 0;
if (newRoot != null) {
firePropertyChange("root", null, newRoot); // NOI18N
}
}
}
firePropertyChange(PROP_DISPLAY_NAME, oldDisplayName, getDisplayName());
foRoot = FileUtil.toFileObject(root);
if ((foRoot != null) && (fcl == null)) {
fcl = new FileChangeAdapter() {
@Override
public void fileChanged(FileEvent fe) {
if (watcherTask == null) {
parse(true);
}
}
@Override
public void fileRenamed(FileRenameEvent fe) {
File f = FileUtil.toFile(fe.getFile());
if ((f != null) && !f.equals(aRoot)) {
try {
setJarFile(f, false, true);
} catch (IOException iex) {
ExternalUtil.exception(iex);
} catch (PropertyVetoException pvex) {
ExternalUtil.exception(pvex);
}
}
}
@Override
public void fileDeleted(FileEvent fe) {
Enumeration extends FileObject> en = existingFileObjects(getRoot());
while (en.hasMoreElements()) {
AbstractFolder fo = (AbstractFolder) en.nextElement();
fo.validFlag = false;
fo.fileDeleted0(new FileEvent(fo));
}
refreshRoot();
}
};
if (refreshRoot) {
foRoot.addFileChangeListener(FileUtil.weakFileChangeListener(fcl, foRoot));
}
}
}
/** Get the file path for the ZIP or JAR file.
* @return the file path
*/
public File getJarFile() {
return root;
}
/*
* Provides name of the system that can be presented to the user.
* @return user presentable name of the filesystem
*/
public String getDisplayName() {
return root != null ? root.getAbsolutePath() : NbBundle.getMessage(JarFileSystem.class, "JAR_UnknownJar");
}
/** This filesystem is read-only.
* @return true
*/
public boolean isReadOnly() {
return true;
}
/* Closes associated JAR file on cleanup, if possible. */
@Override
public void removeNotify() {
closeCurrentRoot(true);
}
/* initialization of jar variable, that is necessary after JarFileSystem was removed from Repository */
// public void addNotify () {
// super.addNotify ();
// }
//
// List
//
protected String[] children(String name) {
Cache cache = getCache();
return cache.getChildrenOf(name);
}
//
// Change
//
protected void createFolder(String name) throws java.io.IOException {
throw new IOException();
}
protected void createData(String name) throws IOException {
throw new IOException();
}
protected void rename(String oldName, String newName)
throws IOException {
throw new IOException();
}
protected void delete(String name) throws IOException {
throw new IOException();
}
//
// Info
//
protected Date lastModified(String name) {
long t;
if (name.length() == 0) {
t = getJarFile().lastModified();
} else {
try {
t = getEntry(name).getTime();
} finally {
closeCurrentRoot(false);
}
}
return new Date(t);
}
protected boolean folder(String name) {
if ("".equals(name)) {
return true; // NOI18N
}
Cache cache = getCache();
return cache.isFolder(name);
}
protected boolean readOnly(String name) {
return true;
}
protected String mimeType(String name) {
return null;
}
protected long size(String name) {
long retVal = getEntry(name).getSize();
closeCurrentRoot(false);
return (retVal == -1) ? 0 : retVal;
}
private InputStream getMemInputStream(JarFile jf, JarEntry je)
throws IOException {
InputStream is = getInputStream4336753(jf, je);
ByteArrayOutputStream os = new ByteArrayOutputStream(is.available());
try {
FileUtil.copy(is, os);
} finally {
os.close();
}
return new ByteArrayInputStream(os.toByteArray());
}
private InputStream getTemporaryInputStream(JarFile jf, JarEntry je, boolean forceRecreate)
throws IOException {
String filePath = jf.getName();
String entryPath = je.getName();
StringBuffer jarCacheFolder = new StringBuffer("jarfscache"); //NOI18N
jarCacheFolder.append(System.getProperty("user.name")).append("/"); //NOI18N
File jarfscache = new File(System.getProperty("java.io.tmpdir"), jarCacheFolder.toString()); //NOI18N
if (!jarfscache.exists()) {
jarfscache.mkdirs();
}
File f = new File(jarfscache, temporaryName(filePath, entryPath));
boolean createContent = !f.exists();
if (createContent) {
f.createNewFile();
} else {
forceRecreate |= (Math.abs((System.currentTimeMillis() - f.lastModified())) > 10000);
}
if (createContent || forceRecreate) {
// JDK 1.3 contains bug #4336753
//is = j.getInputStream (je);
try (InputStream is = getInputStream4336753(jf, je); OutputStream os = Files.newOutputStream(f.toPath())) {
FileUtil.copy(is, os);
} catch (InvalidPathException ex) {
throw new IOException(ex);
}
}
f.deleteOnExit();
return Files.newInputStream(f.toPath());
}
private static String temporaryName(String filePath, String entryPath) {
String fileHash = String.valueOf(filePath.hashCode());
String entryHash = String.valueOf(entryPath.hashCode());
StringBuffer sb = new StringBuffer();
sb.append("f").append(fileHash).append("e").append(entryHash);
return sb.toString().replace('-', 'x'); //NOI18N
}
protected InputStream inputStream(String name) throws java.io.FileNotFoundException {
InputStream is = null;
try {
synchronized (closeSync) {
JarFile j = reOpenJarFile();
if (j != null) {
JarEntry je = j.getJarEntry(name);
if (je != null) {
if (je.getSize() < MEM_STREAM_SIZE) {
is = getMemInputStream(j, je);
} else {
is = getTemporaryInputStream(j, je, (strongCache != null));
}
}
}
}
} catch (java.io.FileNotFoundException e) {
throw e;
} catch (IOException e) {
FileNotFoundException fnfe = new FileNotFoundException(root.getAbsolutePath());
fnfe.initCause(e);
throw fnfe;
} catch (RuntimeException e) {
FileNotFoundException fnfe = new FileNotFoundException(root.getAbsolutePath());
fnfe.initCause(e);
throw fnfe;
} finally {
closeCurrentRoot(false);
}
if (is == null) {
throw new java.io.FileNotFoundException(name);
}
return is;
}
// 4336753 workaround
private InputStream getInputStream4336753(JarFile j, JarEntry je)
throws IOException {
InputStream in = null;
while (in == null) {
try {
in = j.getInputStream(je);
break;
} catch (NullPointerException ex) {
// ignore, it occured during reseting reused Inflanter
// try again until there will be no Inflanter to reuse
}
}
return in;
}
protected OutputStream outputStream(String name) throws java.io.IOException {
throw new IOException();
}
protected void lock(String name) throws IOException {
throw new FSException(NbBundle.getMessage(JarFileSystem.class, "EXC_CannotLock_JAR", name, root));
}
protected void unlock(String name) {
}
protected void markUnimportant(String name) {
}
protected Object readAttribute(String name, String attrName) {
if ("java.io.File".equals(attrName)) {
return null;
}
Attributes attr1 = getManifest().getAttributes(name);
try {
return (attr1 == null) ? null : attr1.getValue(attrName);
} catch (IllegalArgumentException iax) {
return null;
}
}
protected void writeAttribute(String name, String attrName, Object value)
throws IOException {
throw new IOException("Setting attribute not allowed for JarFileSystem [" + this.getDisplayName() + "!" + name + " <- " + attrName + "=" + value + "]"); //NOI18N
}
protected Enumeration attributes(String name) {
Attributes attr1 = getManifest().getAttributes(name);
if (attr1 != null) {
class ToString implements org.openide.util.Enumerations.Processor
© 2015 - 2025 Weber Informatics LLC | Privacy Policy