/* * Copyright 2003-2011 JetBrains s.r.o. * * Licensed 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 jetbrains.mps.vfs.impl; import jetbrains.mps.util.FileUtil; import jetbrains.mps.util.annotation.Hack; import jetbrains.mps.vfs.IFile; import jetbrains.mps.vfs.path.Path; import jetbrains.mps.vfs.path.UniPath; import jetbrains.mps.vfs.ex.IFileEx; import jetbrains.mps.vfs.openapi.FileSystem; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Todo rewrite using {@link Path} */ public class JarEntryFile implements IFileEx { private static final Logger LOG = LogManager.getLogger(JarEntryFile.class); public static final String JAR = "jar"; private static final String DOT = "."; public static final String DOT_JAR = DOT + JAR; private static final IoFileSystem FS = IoFileSystem.INSTANCE; private final AbstractJarFileData myJarFileData; private final File myJarFile; private final String myEntryPath; private final IoFileSystem myFileSystem; JarEntryFile(AbstractJarFileData jarFileData, File jarFile, String path, IoFileSystem fileSystem) { myJarFileData = jarFileData; myJarFile = jarFile; myEntryPath = FileUtil.normalize(path); myFileSystem = fileSystem; } @NotNull @Override public FileSystem getFileSystem() { return FS; } @NotNull @Override public String getName() { String result = myEntryPath; int index = result.lastIndexOf('/'); if (index != -1) { result = result.substring(index + 1); } if (myEntryPath.isEmpty()) { return "/"; } return result; } @Override public IFile getParent() { if (myEntryPath.isEmpty()) { return null; } else { return new JarEntryFile(myJarFileData, myJarFile, myJarFileData.getParentDirectory(myEntryPath), myFileSystem); } } @Override public List<IFile> getChildren() { if (!isDirectory()) return Collections.emptyList(); List<IFile> result = new ArrayList<IFile>(); for (String e : myJarFileData.getSubdirectories(myEntryPath)) { result.add(new JarEntryFile(myJarFileData, myJarFile, e, myFileSystem)); } for (String e : myJarFileData.getFiles(myEntryPath)) { result.add(new JarEntryFile(myJarFileData, myJarFile, myEntryPath.length() > 0 ? myEntryPath + "/" + e : e, myFileSystem)); } return result; } @Override public boolean isArchive() { return true; } @Override public boolean isInArchive() { return true; } @Override @NotNull public IFile getDescendant(@NotNull String suffix) { String path = myEntryPath.length() > 0 ? myEntryPath + "/" + suffix : suffix; return new JarEntryFile(myJarFileData, myJarFile, path, myFileSystem); } @Override public boolean isDirectory() { return myJarFileData != null && myJarFileData.isDirectory(myEntryPath); } @NotNull @Override public String getPath() { return myJarFile.getAbsolutePath() + "!/" + myEntryPath; } @NotNull @Override public UniPath toPath() { return UniPath.fromString(getPath()); } @Override public long lastModified() { return myJarFile.lastModified(); } @Override public boolean exists() { return myJarFileData != null && myJarFileData.exists(myEntryPath); } @Override public boolean createNewFile() { return false; } @Override public boolean mkdirs() { return false; } @Override public boolean delete() { return false; } @Override public boolean rename(String newName) { return false; } @Override public boolean move(IFile newParent) { return false; } @Override public InputStream openInputStream() throws IOException { if (myJarFileData == null) { throw new IOException("File is not found " + getPath()); } return myJarFileData.openStream(myEntryPath); } @Override public OutputStream openOutputStream() throws IOException { throw new UnsupportedOperationException(); } @Override public boolean isReadOnly() { return true; } @Override public long length() { if (myJarFileData == null) { return -1; } return myJarFileData.getLength(myEntryPath); } @NotNull @Override public String toString() { return getPath(); } @Override public IFile getBundleHome() { return new IoFile(myJarFile, myFileSystem); } @Override public boolean setTimeStamp(long time) { return false; } /** * The problem here is with jar URLs. * JDK does not allow us to work efficiently with jar files via URI. * 0. I want to create URL which can be converted to URI. * 1. Unfortunately that means that we need to escape space characters. * (unescaped URL cannot be converted to the URI due to the #toURI method contract) * 2. We are urged to use multi-argument <code>URI</code> constructor in order to properly escape all characters (spaces for instance). * Moreover it is a recommended way to comply to all URI protocol conventions. * 3. URI multi-arg constructor also asks for its path to be absolute (which -- in his understanding -- means a path starting with '/') * 4. In jdk a default URL for a jar-file looks like 'jar:file://a.jar!/b.txt' and calling URL#getPath returns * 'file://a.jar!/b.txt' which does not start with the slash and hence is not absolute and therefore cannot be simply passed to the URI constructor. * 5. That is why we are compelled to use such a hack in constructor arguments which namely passes * a jar:file scheme and passes an honest absolute path '/a.jar!/b.txt'. * * [AP: I would rather return URLs which can be converted to URIs than the ones which can not] * Hopefully this hell will be replaced by the upcoming <code>vfs.Path</code> features. */ @Hack @Override public URL getUrl() throws MalformedURLException { // try { // return new URI("jar:file", "", myJarFile.getAbsolutePath() + "!/" + myEntryPath, null, null).toURL(); // } catch (URISyntaxException e) { // throw new RuntimeException(e); // } return new URL("jar:file://" + myJarFile.getAbsolutePath() + "!/" + myEntryPath); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; JarEntryFile that = (JarEntryFile) o; if (myEntryPath != null ? !myEntryPath.equals(that.myEntryPath) : that.myEntryPath != null) return false; if (myJarFile != null ? !myJarFile.equals(that.myJarFile) : that.myJarFile != null) return false; return true; } @Override public int hashCode() { int result = myJarFile != null ? myJarFile.hashCode() : 0; result = 31 * result + (myEntryPath != null ? myEntryPath.hashCode() : 0); return result; } }