/*
* Copyright (C) 2011 Laurent Caillette
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.novelang;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.MissingResourceException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ClassUtils;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.fonts.EmbedFontInfo;
import org.novelang.configuration.ConfigurationTools;
import org.novelang.configuration.ContentConfiguration;
import org.novelang.configuration.DaemonConfiguration;
import org.novelang.configuration.FopFontStatus;
import org.novelang.configuration.ProducerConfiguration;
import org.novelang.configuration.RenderingConfiguration;
import org.novelang.configuration.RenditionKinematic;
import org.novelang.logger.Logger;
import org.novelang.logger.LoggerFactory;
import org.novelang.outfit.DefaultCharset;
import org.novelang.outfit.loader.ClasspathResourceLoader;
import org.novelang.outfit.loader.CompositeResourceLoader;
import org.novelang.outfit.loader.ResourceLoader;
import org.novelang.outfit.loader.ResourceName;
import org.novelang.outfit.loader.UrlResourceLoader;
/**
* Utility class for dealing with test-dedicated resources.
* Some tests require files on the filesystem, while they are resources that are
* only accessible from a classloader. So we have boring stuff with URLs, byte arrays
* and so on.
*
* @author Laurent Caillette
*/
public final class ResourceTools {
private static final Logger LOGGER = LoggerFactory.getLogger( ResourceTools.class );
private static final ExecutorService EXECUTOR_SERVICE =
Executors.newSingleThreadExecutor( ConfigurationTools.getExecutorThreadFactory() ) ;
private ResourceTools() { }
public static URL getResourceUrl( final Class owningClass, final String resourceName ) {
final String fullName ;
if( resourceName.startsWith( "/" ) ) {
fullName = resourceName ;
} else {
fullName =
"/" +
ClassUtils. getPackageName( owningClass ). replace( '.', '/' ) +
"/" +
resourceName
;
}
return owningClass.getResource( fullName ) ;
}
public static byte[] readResource( final Class owningClass, final String resourceName ) {
final URL url = getResourceUrl( owningClass, resourceName ) ;
if( null == url ) {
throw new MissingResourceException(
owningClass.toString() + " key '" + resourceName + "'",
owningClass.toString(),
resourceName
) ;
}
LOGGER.info( "Loading resource '",
url.toExternalForm(),
"' from ",
owningClass
) ;
final InputStream inputStream;
try {
inputStream = url.openStream();
} catch( IOException e ) {
throw new RuntimeException( "Should not happpen", e );
}
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream() ;
try {
IOUtils.copy( inputStream, outputStream ) ;
} catch( IOException e ) {
throw new RuntimeException( "Should not happpen", e );
}
return outputStream.toByteArray() ;
}
/**
* Copy a resource into given directory, creating subdirectories if resource name includes
* a directory.
*/
public static File copyResourceToDirectory(
final Class owningClass,
final ResourceName resourceName,
final File destinationDir
) {
return copyResourceToDirectory( owningClass, "/" + resourceName.getName(), destinationDir ) ;
}
/**
* Copy a resource into given directory, creating subdirectories if resource name includes
* a directory.
*/
public static File copyResourceToDirectory(
final Class owningClass,
final String resourceName,
final File destinationDir
) {
final byte[] resourceBytes = readResource( owningClass, resourceName ) ;
final ByteArrayInputStream inputStream =
new ByteArrayInputStream( resourceBytes );
final File expandedDestinationDir =
new File( destinationDir, FilenameUtils.getPath( resourceName ) ) ;
final File destinationFile = new File( destinationDir, resourceName ) ;
final FileOutputStream fileOutputStream ;
try {
expandedDestinationDir.mkdirs() ;
fileOutputStream = new FileOutputStream( destinationFile ) ;
} catch( FileNotFoundException e ) {
throw new RuntimeException( e );
}
try {
IOUtils.copy( inputStream, fileOutputStream ) ;
} catch( IOException e ) {
throw new RuntimeException( e );
}
return destinationFile ;
}
/**
* Copy a resource into given directory, creating no directory above target file.
*/
public static File copyResourceToDirectoryFlat(
final Class owningClass,
final ResourceName resourceName,
final File destinationDir
) {
return copyResourceToDirectoryFlat( owningClass, "/" + resourceName.getName(), destinationDir ) ;
}
/**
* Copy a resource into given directory, creating no directory above target file.
*/
public static File copyResourceToDirectoryFlat(
final Class owningClass,
final String resourceName,
final File destinationDir
) {
final byte[] resourceBytes = readResource( owningClass, resourceName ) ;
final ByteArrayInputStream inputStream =
new ByteArrayInputStream( resourceBytes );
final File destinationFile = new File( destinationDir, FilenameUtils.getName( resourceName ) ) ;
final FileOutputStream fileOutputStream ;
try {
fileOutputStream = new FileOutputStream( destinationFile ) ;
} catch( FileNotFoundException e ) {
throw new RuntimeException( e );
}
try {
IOUtils.copy( inputStream, fileOutputStream ) ;
} catch( IOException e ) {
throw new RuntimeException( e );
}
return destinationFile ;
}
public static File createDirectory( final File parent, final String name ) {
final File directory = new File( parent, name ) ;
if( ! directory.exists() ) {
directory.mkdirs() ;
}
org.junit.Assert.assertTrue(
"Could not create: '" + directory.getAbsolutePath() + "'",
FileUtils.waitFor( directory, 1 )
) ;
return directory ;
}
public static ProducerConfiguration createProducerConfiguration(
final File contentDirectory,
final ResourceLoader resourceLoader,
final Charset renderingCharset,
final RenditionKinematic renditionKinematic
) {
return new ProducerConfiguration() {
@Override
public RenderingConfiguration getRenderingConfiguration() {
return new RenderingConfiguration() {
@Override
public ResourceLoader getResourceLoader() {
return resourceLoader ;
}
@Override
public FopFactory getFopFactory() {
return FopFactory.newInstance() ;
}
@Override
public FopFontStatus getCurrentFopFontStatus() {
final Iterable<EmbedFontInfo> fontInfo = ImmutableList.of() ;
final ImmutableSet< String > failedFonts = ImmutableSet.of() ;
return new FopFontStatus(
fontInfo,
failedFonts
) ;
}
@Override
public Charset getDefaultCharset() {
return renderingCharset ;
}
@Override
public RenditionKinematic getRenderingKinematic() {
return renditionKinematic ;
}
} ;
}
@Override
public ContentConfiguration getContentConfiguration() {
return new ContentConfiguration() {
@Override
public File getContentRoot() {
return contentDirectory;
}
@Override
public Charset getSourceCharset() {
return DefaultCharset.SOURCE ;
}
} ;
}
@Override
public ExecutorService getExecutorService() {
return EXECUTOR_SERVICE ;
}
} ;
}
public static ExecutorService getExecutorService() {
return EXECUTOR_SERVICE ;
}
public static ProducerConfiguration createProducerConfiguration(
final File contentDirectory,
final Charset renderingCharset,
final RenditionKinematic renderingKinematic
) {
return doCreateProducerConfiguration(
contentDirectory,
null,
true,
renderingCharset,
renderingKinematic
) ;
}
/**
* @deprecated silly semantics because of the support of multiple style directories.
*/
private static ProducerConfiguration doCreateProducerConfiguration(
final File contentDirectory,
final File styleDirectory,
final boolean shouldAddClasspathResourceLoader,
final Charset renderingCharset,
RenditionKinematic renderingKinematic ) {
final CompositeResourceLoader resourceLoader ;
final CompositeResourceLoader customResourceLoader ;
if( styleDirectory == null ) {
resourceLoader = new CompositeResourceLoader(
new ClasspathResourceLoader( ConfigurationTools.BUNDLED_STYLE_DIR ) ) ;
} else {
try {
customResourceLoader = new CompositeResourceLoader(
new UrlResourceLoader( styleDirectory.toURI().toURL() ) ) ;
} catch( MalformedURLException e ) {
throw new Error( e ) ;
}
if( shouldAddClasspathResourceLoader ) {
resourceLoader = new CompositeResourceLoader(
customResourceLoader,
new ClasspathResourceLoader( ConfigurationTools.BUNDLED_STYLE_DIR )
) ;
} else {
resourceLoader = customResourceLoader ;
}
}
return createProducerConfiguration(
contentDirectory, resourceLoader, renderingCharset, renderingKinematic ) ;
}
public static DaemonConfiguration createDaemonConfiguration(
final int httpDaemonPort,
final File contentDirectory,
final Charset renderingCharset
) {
final ProducerConfiguration producerConfiguration = createProducerConfiguration(
contentDirectory,
renderingCharset,
RenditionKinematic.DAEMON
) ;
return new DaemonConfiguration() {
@Override
public int getPort() {
return httpDaemonPort ;
}
@Override
public ProducerConfiguration getProducerConfiguration() {
return producerConfiguration ;
}
@Override
public boolean getServeRemotes() {
return true ;
}
} ;
}
public static DaemonConfiguration createDaemonConfiguration(
final int httpDaemonPort,
final File contentDirectory,
final ResourceLoader resourceLoader
) {
final ProducerConfiguration producerConfiguration = createProducerConfiguration(
contentDirectory,
resourceLoader,
DefaultCharset.RENDERING,
RenditionKinematic.DAEMON
) ;
return new DaemonConfiguration() {
@Override
public int getPort() {
return httpDaemonPort ;
}
@Override
public ProducerConfiguration getProducerConfiguration() {
return producerConfiguration ;
}
@Override
public boolean getServeRemotes() {
return true ;
}
} ;
}
}