blob: 1bc3f5f1ddb27dfa2e962e3a6894673eade6449f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.filesystem.zip;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.eclipse.core.filesystem.*;
import org.eclipse.core.filesystem.provider.FileInfo;
import org.eclipse.core.filesystem.provider.FileStore;
import org.eclipse.core.internal.filesystem.Messages;
import org.eclipse.core.internal.filesystem.Policy;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.NLS;
/**
* File store implementation representing a file or directory inside
* a zip file.
*/
public class ZipFileStore extends FileStore {
/**
* The path of this store within the zip file.
*/
private IPath path;
/**
* The file store that represents the actual zip file.
*/
private IFileStore rootStore;
/**
* Creates a new zip file store.
* @param rootStore
* @param path
*/
public ZipFileStore(IFileStore rootStore, IPath path) {
this.rootStore = rootStore;
this.path = path.makeRelative();
}
private ZipEntry[] childEntries(IProgressMonitor monitor) throws CoreException {
HashMap entries = new HashMap();
ZipInputStream in = new ZipInputStream(rootStore.openInputStream(EFS.NONE, monitor));
String myName = path.toString();
try {
ZipEntry current;
while ((current = in.getNextEntry()) != null) {
final String currentPath = current.getName();
if (isParent(myName, currentPath))
entries.put(currentPath, current);
else if (isAncestor(myName, currentPath)) {
int myNameLength = myName.length() + 1;
int nameEnd = currentPath.indexOf('/', myNameLength);
String dirName = nameEnd == -1 ? currentPath : currentPath.substring(0, nameEnd+1);
if (!entries.containsKey(dirName))
entries.put(dirName, new ZipEntry(dirName));
}
}
} catch (IOException e) {
Policy.error(EFS.ERROR_READ, NLS.bind(Messages.couldNotRead, rootStore.toString()), e);
} finally {
try {
if (in != null)
in.close();
} catch (IOException e) {
//ignore
}
}
return (ZipEntry[]) entries.values().toArray(new ZipEntry[entries.size()]);
}
public IFileInfo[] childInfos(int options, IProgressMonitor monitor) throws CoreException {
ZipEntry[] entries = childEntries(monitor);
int entryCount = entries.length;
IFileInfo[] infos = new IFileInfo[entryCount];
for (int i = 0; i < entryCount; i++)
infos[i] = convertZipEntryToFileInfo(entries[i]);
return infos;
}
public String[] childNames(int options, IProgressMonitor monitor) throws CoreException {
ZipEntry[] entries = childEntries(monitor);
int entryCount = entries.length;
String[] names = new String[entryCount];
for (int i = 0; i < entryCount; i++)
names[i] = computeName(entries[i]);
return names;
}
/**
* Computes the simple file name for a given zip entry.
*/
private String computeName(ZipEntry entry) {
//the entry name is a relative path, with an optional trailing separator
//We need to strip off the trailing slash, and then take everything after the
//last separator as the name
String name = entry.getName();
int end = name.length()-1;
if (name.charAt(end) == '/')
end--;
return name.substring(name.lastIndexOf('/', end) + 1, end+1);
}
/**
* Creates a file info object corresponding to a given zip entry
*
* @param entry the zip entry
* @return The file info for a zip entry
*/
private IFileInfo convertZipEntryToFileInfo(ZipEntry entry) {
FileInfo info = new FileInfo(computeName(entry));
info.setLastModified(entry.getTime());
info.setExists(true);
info.setDirectory(entry.isDirectory());
info.setLength(entry.getSize());
return info;
}
public IFileInfo fetchInfo(int options, IProgressMonitor monitor) throws CoreException {
ZipInputStream in = new ZipInputStream(rootStore.openInputStream(EFS.NONE, monitor));
try {
String myPath = path.toString();
ZipEntry current;
while ((current = in.getNextEntry()) != null) {
String currentPath = current.getName();
if (myPath.equals(currentPath))
return convertZipEntryToFileInfo(current);
//directories don't always have their own entry, but it is implied by the existence of a child
if (isAncestor(myPath, currentPath))
return createDirectoryInfo(getName());
}
} catch (IOException e) {
Policy.error(EFS.ERROR_READ, NLS.bind(Messages.couldNotRead, rootStore.toString()), e);
} finally {
try {
if (in != null)
in.close();
} catch (IOException e) {
//ignore
}
}
//does not exist
return new FileInfo(getName());
}
/**
* @return A directory info for this file store
*/
private IFileInfo createDirectoryInfo(String name) {
FileInfo result = new FileInfo(name);
result.setExists(true);
result.setDirectory(true);
return result;
}
/**
* Finds the zip entry with the given name in this zip file. Returns the
* entry and leaves the input stream open positioned at the beginning of
* the bytes of that entry. Returns null if the entry could not be found.
*/
private ZipEntry findEntry(String name, ZipInputStream in) throws IOException {
ZipEntry current;
while ((current = in.getNextEntry()) != null) {
if (current.getName().equals(name))
return current;
}
return null;
}
public IFileStore getChild(String name) {
return new ZipFileStore(rootStore, path.append(name));
}
public String getName() {
String name = path.lastSegment();
return name == null ? "" : name; //$NON-NLS-1$
}
public IFileStore getParent() {
if (path.segmentCount() > 0)
return new ZipFileStore(rootStore, path.removeLastSegments(1));
//the root entry has no parent
return null;
}
/**
* Returns whether ancestor is a parent of child.
* @param ancestor the potential ancestor
* @param child the potential child
* @return <code>true</code> or <code>false</code>
*/
private boolean isAncestor(String ancestor, String child) {
//children will start with myName and have no child path
int ancestorLength = ancestor.length();
if (ancestorLength == 0)
return true;
return child.startsWith(ancestor) && child.length() > ancestorLength && child.charAt(ancestorLength) == '/';
}
/**
* Returns whether parent is the immediate parent of child.
* @param parent the potential parent
* @param child the potential child
* @return <code>true</code> or <code>false</code>
*/
private boolean isParent(String parent, String child) {
//children will start with myName and have no child path
int chop = parent.length() + 1;
return child.startsWith(parent) && child.length() > chop && child.substring(chop).indexOf('/') == -1;
}
public InputStream openInputStream(int options, IProgressMonitor monitor) throws CoreException {
ZipInputStream in = new ZipInputStream(rootStore.openInputStream(EFS.NONE, monitor));
try {
ZipEntry entry = findEntry(path.toString(), in);
if (entry == null)
Policy.error(EFS.ERROR_READ, NLS.bind(Messages.fileNotFound, toString()), null);
if (entry.isDirectory())
Policy.error(EFS.ERROR_READ, NLS.bind(Messages.notAFile, toString()), null);
return in;
} catch (IOException e) {
try {
if (in != null)
in.close();
} catch (IOException e1) {
//ignore secondary failure
}
Policy.error(EFS.ERROR_READ, NLS.bind(Messages.couldNotRead, rootStore.toString()), e);
}
//can't get here
return null;
}
public URI toURI() {
try {
return new URI(ZipFileSystem.SCHEME_ZIP, null, path.makeAbsolute().toString(), rootStore.toURI().toString(), null); //$NON-NLS-1$
} catch (URISyntaxException e) {
//should not happen
throw new RuntimeException(e);
}
}
}