/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat, Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.search.util.impl;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
/**
* Utility class for file and directory operations, like synchronisation and reading from class path.
*
* @author Emmanuel Bernard
* @author Sanne Grinovero
* @author Hardy Ferentschik
*/
public abstract class FileHelper {
private static final Log log = LoggerFactory.make();
private static final int FAT_PRECISION = 2000;
public static final long DEFAULT_COPY_BUFFER_SIZE = 16 * 1024 * 1024; // 16 MB
public static boolean areInSync(File source, File destination) throws IOException {
if ( source.isDirectory() ) {
if ( !destination.exists() ) {
return false;
}
else if ( !destination.isDirectory() ) {
throw new IOException(
"Source and Destination not of the same type:"
+ source.getCanonicalPath() + " , " + destination.getCanonicalPath()
);
}
String[] sources = source.list();
Set<String> srcNames = new HashSet<String>( Arrays.asList( sources ) );
String[] dests = destination.list();
// check for files in destination and not in source
for ( String fileName : dests ) {
if ( !srcNames.contains( fileName ) ) {
return false;
}
}
boolean inSync = true;
for ( String fileName : sources ) {
File srcFile = new File( source, fileName );
File destFile = new File( destination, fileName );
if ( !areInSync( srcFile, destFile ) ) {
inSync = false;
break;
}
}
return inSync;
}
else {
if ( destination.exists() && destination.isFile() ) {
long sts = source.lastModified() / FAT_PRECISION;
long dts = destination.lastModified() / FAT_PRECISION;
return sts == dts;
}
else {
return false;
}
}
}
public static void synchronize(File source, File destination, boolean smart) throws IOException {
synchronize( source, destination, smart, DEFAULT_COPY_BUFFER_SIZE );
}
public static void synchronize(File source, File destination, boolean smart, long chunkSize) throws IOException {
if ( chunkSize <= 0 ) {
log.checkSizeMustBePositive();
chunkSize = DEFAULT_COPY_BUFFER_SIZE;
}
if ( source.isDirectory() ) {
if ( !destination.exists() ) {
if ( !destination.mkdirs() ) {
throw new IOException( "Could not create path " + destination );
}
}
else if ( !destination.isDirectory() ) {
throw new IOException(
"Source and Destination not of the same type:"
+ source.getCanonicalPath() + " , " + destination.getCanonicalPath()
);
}
String[] sources = source.list();
Set<String> srcNames = new HashSet<String>( Arrays.asList( sources ) );
String[] dests = destination.list();
//delete files not present in source
for ( String fileName : dests ) {
if ( !srcNames.contains( fileName ) ) {
delete( new File( destination, fileName ) );
}
}
//copy each file from source
for ( String fileName : sources ) {
File srcFile = new File( source, fileName );
File destFile = new File( destination, fileName );
synchronize( srcFile, destFile, smart, chunkSize );
}
}
else {
if ( destination.exists() && destination.isDirectory() ) {
delete( destination );
}
if ( destination.exists() ) {
long sts = source.lastModified() / FAT_PRECISION;
long dts = destination.lastModified() / FAT_PRECISION;
//do not copy if smart and same timestamp and same length
if ( !smart || sts == 0 || sts != dts || source.length() != destination.length() ) {
copyFile( source, destination, chunkSize );
}
}
else {
copyFile( source, destination, chunkSize );
}
}
}
private static void copyFile(File srcFile, File destFile, long chunkSize) throws IOException {
FileInputStream is = null;
FileOutputStream os = null;
try {
is = new FileInputStream( srcFile );
FileChannel iChannel = is.getChannel();
os = new FileOutputStream( destFile, false );
FileChannel oChannel = os.getChannel();
long doneBytes = 0L;
long todoBytes = srcFile.length();
while ( todoBytes != 0L ) {
long iterationBytes = Math.min( todoBytes, chunkSize );
long transferredLength = oChannel.transferFrom( iChannel, doneBytes, iterationBytes );
if ( iterationBytes != transferredLength ) {
throw new IOException(
"Error during file transfer: expected "
+ iterationBytes + " bytes, only " + transferredLength + " bytes copied."
);
}
doneBytes += transferredLength;
todoBytes -= transferredLength;
}
}
finally {
if ( is != null ) {
is.close();
}
if ( os != null ) {
os.close();
}
}
boolean successTimestampOp = destFile.setLastModified( srcFile.lastModified() );
if ( !successTimestampOp ) {
log.notChangeTimestamp( destFile );
}
}
/**
* Attempts to delete a file. If the file is a directory delete recursive all content.
*
* @param file the file or Directory to be deleted
* @return false if it wasn't possible to delete it or all of the contents. A common problem on Windows systems.
*/
public static boolean delete(File file) {
boolean allok = true;
if ( file.isDirectory() ) {
for ( File subFile : file.listFiles() ) {
boolean deleted = delete( subFile );
allok = allok && deleted;
}
}
if ( allok && file.exists() ) {
if ( !file.delete() ) {
log.notDeleted( file );
return false;
}
}
return allok;
}
/**
* Reads the provided input stream into a string
*
* @param inputStream the input stream to read from
* @return the content of the input stream as string
* @throws java.io.IOException in case an error occurs reading from the input stream
*/
public static String readInputStream(InputStream inputStream) throws IOException {
Writer writer = new StringWriter();
try {
char[] buffer = new char[1000];
Reader reader = new BufferedReader( new InputStreamReader( inputStream, "UTF-8" ) );
int r = reader.read( buffer );
while ( r != -1 ) {
writer.write( buffer, 0, r );
r = reader.read( buffer );
}
return writer.toString();
}
finally {
closeResource( writer );
}
}
/**
* Load a resource from a specific classLoader
*
* @param resourceName the name of the resource
* @param classLoader the classloader to use, or null to try the ContextClassloader first or the loading one second.
* @return the resource contents as a String
*/
public static String readResourceAsString(String resourceName, ClassLoader classLoader) {
InputStream in;
if ( classLoader != null ) {
in = classLoader.getResourceAsStream( resourceName );
}
else {
in = openResource( resourceName );
}
if ( in == null ) {
throw log.unableToLoadResource( resourceName );
}
String s;
try {
s = FileHelper.readInputStream( in );
}
catch ( IOException e ) {
throw log.unableToReadFile( resourceName, e );
}
finally {
closeResource( in );
}
return s;
}
public static InputStream openResource(String resourceName) {
//try loading from application context first:
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream resource = classLoader.getResourceAsStream( resourceName );
if ( resource != null ) {
return resource;
}
else {
classLoader = FileHelper.class.getClassLoader();
return classLoader.getResourceAsStream( resourceName );
}
}
/**
* Closes a resource without throwing IOExceptions
*
* @param resource the resource to close
*/
public static void closeResource(Closeable resource) {
if ( resource != null ) {
try {
resource.close();
}
catch ( IOException e ) {
//we don't really care if we can't close
log.couldNotCloseResource( e );
}
}
}
}