/******************************************************************************* * Copyright (c) 2000, 2013 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.debug.core.sourcelookup.containers; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.sourcelookup.ISourceContainerType; import org.eclipse.debug.internal.core.sourcelookup.SourceLookupMessages; import org.eclipse.debug.internal.core.sourcelookup.SourceLookupUtils; import com.ibm.icu.text.MessageFormat; /** * An archive in the local file system. Returns instances * of <code>ZipEntryStorage</code> as source elements. * <p> * Clients may instantiate this class. * </p> * @since 3.0 * @noextend This class is not intended to be subclassed by clients. */ public class ExternalArchiveSourceContainer extends AbstractSourceContainer { private boolean fDisposed; private boolean fDetectRoots; private Set<String> fPotentialRoots; private List<String> fRoots = new ArrayList<String>(); private String fArchivePath; /** * Unique identifier for the external archive source container type * (value <code>org.eclipse.debug.core.containerType.externalArchive</code>). */ public static final String TYPE_ID = DebugPlugin.getUniqueIdentifier() + ".containerType.externalArchive"; //$NON-NLS-1$ /** * Creates an archive source container on the archive at the * specified location in the local file system. * * @param archivePath path to the archive in the local file system * @param detectRootPaths whether root container paths should be detected. When * <code>true</code>, searching is performed relative to a root path * within the archive based on fully qualified file names. A root * path is automatically determined for when the first * successful search is performed. For example, when searching for a file * named <code>a/b/c.d</code>, and an entry in the archive named * <code>r/a/b/c.d</code> exists, a root path is set to <code>r</code>. * When searching for an unqualified file name, root containers are not * considered. * When <code>false</code>, searching is performed by * matching file names as suffixes to the entries in the archive. */ public ExternalArchiveSourceContainer(String archivePath, boolean detectRootPaths) { fArchivePath = archivePath; fDetectRoots = detectRootPaths; } @SuppressWarnings("resource") @Override public Object[] findSourceElements(String name) throws CoreException { String newname = name.replace('\\', '/'); ZipFile file = getArchive(); if (file == null) { return EMPTY; } // NOTE: archive can be closed between get (above) and synchronized block (below) synchronized (file) { boolean isQualfied = newname.indexOf('/') > 0; if (fDetectRoots && isQualfied) { ZipEntry entry = searchRoots(file, newname); if (entry != null) { return new Object[]{new ZipEntryStorage(file, entry)}; } } else { // try exact match ZipEntry entry = null; try { entry = file.getEntry(newname); } catch (IllegalStateException e) { // archive was closed between retrieving and locking throw new CoreException(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), e.getMessage(), e)); } if (entry != null) { // can't be any duplicates if there is an exact match return new Object[]{new ZipEntryStorage(file, entry)}; } // search Enumeration<? extends ZipEntry> entries = file.entries(); List<ZipEntryStorage> matches = null; while (entries.hasMoreElements()) { entry = entries.nextElement(); String entryName = entry.getName(); if (entryName.endsWith(newname)) { if (isQualfied || entryName.length() == newname.length() || entryName.charAt(entryName.length() - newname.length() - 1) == '/') { if (isFindDuplicates()) { if (matches == null) { matches = new ArrayList<ZipEntryStorage>(); } matches.add(new ZipEntryStorage(file, entry)); } else { return new Object[]{new ZipEntryStorage(file, entry)}; } } } } if (matches != null) { return matches.toArray(); } } } return EMPTY; } /** * Returns the root path in this archive for the given file name, based * on its type, or <code>null</code> if none. Detects a root if a root has * not yet been detected for the given file type. * * @param file zip file to search in * @param name file name * @return the {@link ZipEntry} with the given name or <code>null</code> * @exception CoreException if an exception occurs while detecting the root */ private synchronized ZipEntry searchRoots(ZipFile file, String name) throws CoreException { if (fDisposed) { return null; } if (fPotentialRoots == null) { fPotentialRoots = new HashSet<String>(); fPotentialRoots.add(""); //$NON-NLS-1$ // all potential roots are the directories try { Enumeration<? extends ZipEntry> entries = file.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); if (entry.isDirectory()) { fPotentialRoots.add(entry.getName()); } else { String entryName = entry.getName(); int index = entryName.lastIndexOf("/"); //$NON-NLS-1$ while (index > 0) { if (fPotentialRoots.add(entryName.substring(0, index + 1))) { entryName = entryName.substring(0, index); index = entryName.lastIndexOf("/"); //$NON-NLS-1$ } else { break; } } } } } catch (IllegalStateException e) { // archive was closed between retrieving and locking throw new CoreException(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), e.getMessage(), e)); } } int i = 0; while (i < fRoots.size()) { String root = fRoots.get(i); ZipEntry entry = file.getEntry(root+name); if (entry != null) { return entry; } i++; } if (!fPotentialRoots.isEmpty()) { for (String root : fPotentialRoots) { ZipEntry entry = file.getEntry(root + name); if (entry != null) { if (root != null) { fRoots.add(root); fPotentialRoots.remove(root); // remove any roots that begin with the new root, as // roots // cannot be nested Iterator<String> rs = fPotentialRoots.iterator(); while (rs.hasNext()) { String r = rs.next(); if (r.startsWith(root)) { rs.remove(); } } } return entry; } } } return null; } /** * Returns the archive to search in. * @return the {@link ZipFile} to search in * * @throws CoreException if unable to access the archive */ private synchronized ZipFile getArchive() throws CoreException { if (fDisposed) { return null; } try { return SourceLookupUtils.getZipFile(fArchivePath); } catch (IOException e) { File file = new File(fArchivePath); if (file.exists()) { abort(MessageFormat.format(SourceLookupMessages.ExternalArchiveSourceContainer_2, new Object[] { fArchivePath }), e); } else { warn(MessageFormat.format(SourceLookupMessages.ExternalArchiveSourceContainer_1, new Object[] { fArchivePath }), e); } } return null; } @Override public String getName() { return fArchivePath; } @Override public ISourceContainerType getType() { return getSourceContainerType(TYPE_ID); } /** * Returns whether root paths are automatically detected in this * archive source container. * * @return whether root paths are automatically detected in this * archive source container */ public boolean isDetectRoot() { return fDetectRoots; } @Override public boolean equals(Object obj) { return obj instanceof ExternalArchiveSourceContainer && ((ExternalArchiveSourceContainer)obj).getName().equals(getName()); } @Override public int hashCode() { return getName().hashCode(); } @Override public synchronized void dispose() { super.dispose(); if (fPotentialRoots != null) { fPotentialRoots.clear(); } fRoots.clear(); fDisposed = true; } }