/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* JarFileManager.java
* Creation date: Jan 25, 2006.
* By: Joseph Wong
*/
package org.openquark.cal.services;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
/**
* This class implements a manager for a {@link JarFile}. In particular, it caches information about
* the entries in the jar file for performance, and provides methods for querying the jar file about
* particular subdirectories within the jar without enumerating through all entries in the jar.
*
* @author Joseph Wong
*/
public class JarFileManager {
/**
* The JarFile encapsulated by this manager.
*/
private final JarFile jarFile;
/**
* A Map mapping a folder name to a Set of the names of the files contained directly within that folder.
*
* It starts out as null, and is lazily built on first access.
*/
private Map<String, Set<String>> folderNameToFileNames;
/**
* A Map mapping a folder name to a Set of the paths of all (direct and indirect) descendent folders within that folder.
*
* It starts out as null, and is lazily built on first access.
*/
private Map<String, Set<String>> folderNameToAllDescendentFolderPaths;
/**
* Constructs a JarFileManager, encapsulating the given JarFile.
* @param jarFile the jar file to be managed by the manager.
*/
public JarFileManager(JarFile jarFile) {
if (jarFile == null) {
throw new NullPointerException();
}
this.jarFile = jarFile;
}
/**
* Ensures that the underlying jar file is closed.
*/
@Override
protected void finalize() throws IOException {
jarFile.close();
}
/**
* @return the JarFile encapsulated by this manager.
*/
public JarFile getJarFile() {
return jarFile;
}
/**
* If the cache is not initialized, enumerate through the jar file and build up the cache.
*/
private synchronized void ensureCacheInitialized() {
if (folderNameToFileNames == null || folderNameToAllDescendentFolderPaths == null) {
Map<String, Set<String>> fileNamesMap = new HashMap<String, Set<String>>();
Map<String, Set<String>> directoryPathsMap = new HashMap<String, Set<String>>();
// loop through all the entries in the jar, building up the (folder |-> {children files}) map
// and the (folder |-> {all descendant folders}) map.
for (Enumeration<JarEntry> en = jarFile.entries(); en.hasMoreElements(); ) {
ZipEntry entry = en.nextElement();
String entryName = entry.getName();
if (!entry.isDirectory()) {
// build the file entries cache
int lastIndexOfSlash = entryName.lastIndexOf('/');
String relativePath;
String fileName;
if (lastIndexOfSlash != -1) {
relativePath = entryName.substring(0, lastIndexOfSlash);
fileName = entryName.substring(lastIndexOfSlash + 1);
} else {
relativePath = ""; // the 'root' folder as a relative path is the empty string
fileName = entryName;
}
Set<String> fileNames = fileNamesMap.get(relativePath);
if (fileNames == null) {
fileNames = new HashSet<String>();
fileNamesMap.put(relativePath, fileNames);
}
fileNames.add(fileName);
} else {
// build the directory entries cache
String directoryPath = entryName;
addDirectoryPathToCachingMap(directoryPathsMap, directoryPath);
}
}
// a Car file that we write out ourselves may not have explicit subdirectory entries,
// so we will need to loop through the parent directories of the file entries in the Car,
// i.e. the keys in the fileNamesMap
for (final String directoryPath : fileNamesMap.keySet()) {
addDirectoryPathToCachingMap(directoryPathsMap, directoryPath);
}
// wrap all inner sets with unmodifiable wrappers
Map<String, Set<String>> fileNamesMapWithUnmodifiableInnerSets = new HashMap<String, Set<String>>();
for (final Map.Entry<String, Set<String>> mapEntry : fileNamesMap.entrySet()) {
fileNamesMapWithUnmodifiableInnerSets.put(mapEntry.getKey(), Collections.unmodifiableSet(mapEntry.getValue()));
}
Map<String, Set<String>> directoryPathsMapWithUnmodifiableInnerSets = new HashMap<String, Set<String>>();
for (final Map.Entry<String, Set<String>> mapEntry : directoryPathsMap.entrySet()) {
directoryPathsMapWithUnmodifiableInnerSets.put(mapEntry.getKey(), Collections.unmodifiableSet(mapEntry.getValue()));
}
// lock the outer maps down with unmodifiable wrappers
folderNameToFileNames = Collections.unmodifiableMap(fileNamesMapWithUnmodifiableInnerSets);
folderNameToAllDescendentFolderPaths = Collections.unmodifiableMap(directoryPathsMapWithUnmodifiableInnerSets);
}
}
/**
* Processes a directory path and adds appropriate entries for it to the (folder |-> {all descendant folders}) map.
* @param directoryPathsMap the caching map.
* @param directoryPath the directory path to be cached in the given map.
*/
private void addDirectoryPathToCachingMap(Map<String, Set<String>> directoryPathsMap, String directoryPath) {
Set<String> allPaths = new HashSet<String>();
allPaths.add(directoryPath);
String currentPath = directoryPath;
// we loop through all the ancestor folders of this entry
// and associate this entry with each of its ancestors
do {
/// let currentPath = parent of currentPath
//
int lastIndexOfSlash = currentPath.lastIndexOf('/');
if (lastIndexOfSlash != -1) {
currentPath = currentPath.substring(0, lastIndexOfSlash);
} else {
currentPath = ""; // the 'root' folder as a relative path is the empty string
}
Set<String> subfolderPathsOfCurrentPath = directoryPathsMap.get(currentPath);
if (subfolderPathsOfCurrentPath == null) {
subfolderPathsOfCurrentPath = new HashSet<String>();
directoryPathsMap.put(currentPath, subfolderPathsOfCurrentPath);
}
subfolderPathsOfCurrentPath.addAll(allPaths);
allPaths.add(currentPath);
} while (currentPath.length() > 0);
}
/**
* Returns a Set of the names for the files in the specified folder.
* @param folderName the name of the folder.
* @return a Set of the names for the files in the specified folder, or null if the jar file has
* no entries in the specified folder. The returned Set is not modifiable.
*/
public Set<String> getFileNamesInFolder(String folderName) {
ensureCacheInitialized();
return folderNameToFileNames.get(folderName);
}
/**
* Returns a Set of the paths for all (direct and indirect) descendent folders in the specified folder.
* @param folderName the name of the folder.
* @return a Set of the paths for all (direct and indirect) descendent folders in the specified folder,
* or null if the jar file has no subfolders in the specified folder. The returned Set is not modifiable.
*/
public Set<String> getAllDescendentFolderPathsInFolder(String folderName) {
ensureCacheInitialized();
return folderNameToAllDescendentFolderPaths.get(folderName);
}
}