/****************************************************************************
* Copyright (C) 2012 ecsec GmbH.
* All rights reserved.
* Contact: ecsec GmbH (info@ecsec.de)
*
* This file is part of the Open eCard App.
*
* GNU General Public License Usage
* This file may be used under the terms of the GNU General Public
* License version 3.0 as published by the Free Software Foundation
* and appearing in the file LICENSE.GPL included in the packaging of
* this file. Please review the following information to ensure the
* GNU General Public License version 3.0 requirements will be met:
* http://www.gnu.org/copyleft/gpl.html.
*
* Other Usage
* Alternatively, this file may be used in accordance with the terms
* and conditions contained in a signed written agreement between
* you and ecsec GmbH.
*
***************************************************************************/
package org.openecard.common.util;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.openecard.common.io.LimitedInputStream;
/**
*
* @author Moritz Horsch <horsch@cdc.informatik.tu-darmstadt.de>
* @author Tobias Wich <tobias.wich@ecsec.de>
*/
public class FileUtils {
/**
* Finds and returns the user specific config directory.
* The config directory is defined as '$HOME/.openecard'. This function evaluates the system property 'user.home'
* and simply appends '.openecard'. <br/>
* The config directory can be used to save logging configs, logs, certificates and anything else one might think
* of.
*
* @return File object pointing to the config directory.
* @throws IOException In case the directory can not be found.
* @throws SecurityException In case the directory and/or one of its parents could not be created.
*/
public static File getHomeConfigDir() throws IOException, SecurityException {
final String dirName = "openecard";
final String home = System.getProperty("user.home");
if (home != null) {
String pathname = home + File.separator + "." + dirName;
File path = new File(pathname);
path.mkdirs();
return path;
} else {
throw new IOException("Home path can not be determined.");
}
}
/**
* Reads a file.
*
* @param file File
* @return File content as a byte array
* @throws FileNotFoundException
* @throws IOException
*/
public static byte[] toByteArray(File file) throws FileNotFoundException, IOException {
BufferedInputStream is = new BufferedInputStream(new FileInputStream(file));
return toByteArray(is);
}
/**
* Reads a file.
*
* @param file File
* @param limit Limit of bytes to be read
* @return File content as a byte array
* @throws FileNotFoundException
* @throws IOException
*/
public static byte[] toByteArray(File file, int limit) throws FileNotFoundException, IOException {
BufferedInputStream is = new BufferedInputStream(new LimitedInputStream(new FileInputStream(file)), limit);
return toByteArray(is);
}
public static byte[] toByteArray(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[4*1024]; // disks use 4k nowadays
int i;
while ((i = is.read(buffer)) != -1) {
baos.write(buffer, 0, i);
}
return baos.toByteArray();
}
/**
* Reads a file.
*
* @param file File
* @return File content as a String
* @throws FileNotFoundException
* @throws IOException
* @throws UnsupportedEncodingException
*/
public static String toString(File file) throws FileNotFoundException, IOException, UnsupportedEncodingException {
return toString(new FileInputStream(file));
}
/**
* Reads a file.
*
* @param file File
* @param charset Charset
* @return File content as a String
* @throws FileNotFoundException
* @throws IOException
* @throws UnsupportedEncodingException
*/
public static String toString(File file, String charset) throws FileNotFoundException, IOException, UnsupportedEncodingException {
return toString(new FileInputStream(file), charset);
}
public static String toString(InputStream in) throws UnsupportedEncodingException, IOException {
return toString(in, "UTF-8");
}
public static String toString(InputStream in, String charset) throws UnsupportedEncodingException, IOException {
return new String(toByteArray(in), charset);
}
/**
* List directory contents for a resource folder. This is basically a brute-force implementation.
* Works for regular files and also JARs. <p>Taken from
* {@link http://www.uofr.net/~greg/java/get-resource-listing.html} and modified for our needs.</p>
*
* @author Greg Briggs
* @param clazz Any java class that lives in the same place as the resources you want.
* @param path Should end with "/", but not start with one.
* @return List of URLs pointing to all subentries including the specified one.
* @throws URISyntaxException
* @throws IOException
*/
public static Map<String,URL> getResourceListing(Class clazz, String path) throws URISyntaxException, IOException {
URL dirURL = clazz.getClassLoader().getResource(path);
if (dirURL != null && dirURL.getProtocol().equals("file")) {
File dirFile = new File(dirURL.toURI());
return getSubdirFileListing(dirFile, dirURL.toExternalForm());
}
// TODO: I think this code is not needed (at least on linux), revise on windows and remove if possible
if (dirURL == null) {
// In case of a jar file, we can't actually find a directory.
// Have to assume the same jar as clazz.
String me = clazz.getName().replace(".", "/") + ".class";
dirURL = clazz.getClassLoader().getResource(me);
}
if (dirURL.getProtocol().equals("jar")) {
// a JAR path
String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf("!")); //strip out only the JAR file
String jarUrl = dirURL.toExternalForm().substring(0, dirURL.toExternalForm().indexOf("!"));
JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
TreeMap<String,URL> result = new TreeMap<String,URL>(); //avoid duplicates in case it is a subdirectory
while (entries.hasMoreElements()) {
JarEntry nextEntry = entries.nextElement();
// skip directory entries
if (! nextEntry.isDirectory()) {
String name = nextEntry.getName();
if (name.startsWith(path)) { //filter according to the path
String entryPath = jarUrl + "!/" + name;
String prefix = "/" + name.substring(path.length());
result.put(prefix, new URL(entryPath));
}
}
}
return result;
}
throw new UnsupportedOperationException("Cannot list files for URL " + dirURL);
}
private static TreeMap<String,URL> getSubdirFileListing(File dir, String base) throws MalformedURLException {
TreeMap<String,URL> resultList = new TreeMap<String,URL>();
for (File next : dir.listFiles()) {
if (next.canRead() && next.isDirectory()) {
resultList.putAll(getSubdirFileListing(next, base));
} else if (next.canRead() && next.isFile()) {
// generate prefix
URL fileURL = next.toURI().toURL();
String prefix = fileURL.toExternalForm().substring(base.length()-1);
resultList.put(prefix, fileURL);
}
}
return resultList;
}
/**
* Map list of files to resource URLs.
* The list file must itself be present in the classpath and contain unix style path values separated by colons (:).
* These path values must be relative to the classpath. The map key is path without the given prefix.<br/>
* E.g.
* <code>/www/index.html</code> becomes <code>/index.html</code> -> <code>some-url-to-the-file</code>.
*
* @param clazz Base for the {@link java.lang.Class#getResource()} operation.
* @param prefix Prefix common to all path entries.
* @param listFile File with the path entries.
* @return Mapping of all files without the classpath prefix to their respective URLs.
* @throws UnsupportedEncodingException
* @throws IOException
*/
public static Map<String,URL> getResourceFileListing(Class clazz, String prefix, String listFile) throws UnsupportedEncodingException, IOException {
InputStream fileStream = resolveResourceAsStream(clazz, listFile);
String fileValue = toString(fileStream);
String[] files = fileValue.split(":");
TreeMap<String,URL> result = new TreeMap<String,URL>();
for (String file : files) {
URL fileUrl = resolveResourceAsURL(clazz, file);
if (fileUrl != null) {
result.put(file.substring(prefix.length()), fileUrl);
}
}
return result;
}
/**
* Same as {@link java.lang.Class#getResourceAsStream()} but works with and without jars reliably.
* In fact the resource is tried to be loaded with and without / in front of the path.
*
* @param clazz Base for the <code>getResource()</code> operation.
* @param name Name of the resource.
* @return Open stream to the resource or null if none found.
* @throws IOException
*/
public static InputStream resolveResourceAsStream(Class clazz, String name) throws IOException {
URL url = resolveResourceAsURL(clazz, name);
if (url != null) {
return url.openStream();
}
return null;
}
/**
* Same as {@link java.lang.Class#getResource()} but works with and without jars reliably.
* In fact the resource is tried to be loaded with and without / in front of the path.
*
* @param clazz Base for the <code>getResource()</code> operation.
* @param name name of the resource.
* @return URL to the resource or null if none found.
* @throws IOException
*/
public static URL resolveResourceAsURL(Class clazz, String name) {
URL url = clazz.getResource(name);
if (url == null) {
name = name.startsWith("/") ? name.substring(1) : "/" + name;
url = clazz.getResource(name);
}
return url;
}
}