Applied-Energistics-2-tiler.../src/main/java/appeng/recipes/loader/RecipeResourceCopier.java
thatsIch 888b3e5600 Closes #1899, Fixed #1898: Adds an easy way to export interesting information into CSV format
Mostly used for the recipe system, but can also be used for debugging purposes. Debug options needs to be ticked to use the full information gain. Recipes only require the normal localization and the specific name plus metadata.

Shifted the recipes into a recipes folder where the CSV will also reside. This will also elevate the copying of the readme to the user directory since it can reside in the recipes folder.

Fixed a bug where the copier would copy the would also copy empty folders
2015-12-23 14:32:53 +01:00

266 lines
8.6 KiB
Java

/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2015, AlgorithmX2, All rights reserved.
*
* Applied Energistics 2 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.
*
* Applied Energistics 2 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 Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.recipes.loader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import org.apache.commons.io.FileUtils;
import com.google.common.base.Preconditions;
/**
* copies recipes in jars onto file system includes the readme, needs to be modified if other files needs to be handled
*
* @author thatsIch
* @version rv3 - 11.05.2015
* @since rv3 11.05.2015
*/
public class RecipeResourceCopier
{
/**
* Most expected size of recipes found
*/
private static final int INITIAL_RESOURCE_CAPACITY = 20;
private static final Pattern DOT_COMPILE_PATTERN = Pattern.compile( ".", Pattern.LITERAL );
private static final String FILE_PROTOCOL = "file";
private static final String CLASS_EXTENSION = ".class";
private static final String JAR_PROTOCOL = "jar";
private static final String UTF_8_ENCODING = "UTF-8";
/**
* copy source in the jar
*/
private final String root;
/**
* @param root source root folder of the recipes inside the jar.
*
* @throws NullPointerException if root is <tt>null</tt>
*/
public RecipeResourceCopier( @Nonnull final String root )
{
Preconditions.checkNotNull( root );
this.root = root;
}
/**
* copies recipes found in the root to destination.
*
* @param identifier only copy files which end with the identifier
* @param destination destination folder to which the recipes are copied to
*
* @throws URISyntaxException {@see #getResourceListing}
* @throws IOException {@see #getResourceListing} and if copying the detected resource to file is not possible
* @throws NullPointerException if either parameter is <tt>null</tt>
* @throws IllegalArgumentException if destination is not a directory
*/
public void copyTo( @Nonnull final String identifier, @Nonnull final File destination ) throws URISyntaxException, IOException
{
Preconditions.checkNotNull( destination );
Preconditions.checkArgument( destination.isDirectory() );
this.copyTo( identifier, destination, this.root );
}
/**
* @param destination destination folder to which the recipes are copied to
* @param directory the folder to copy.
*
* @throws URISyntaxException {@see #getResourceListing}
* @throws IOException {@see #getResourceListing} and if copying the detected resource to file is not possible
* @see {RecipeResourceCopier#copyTo(File)}
*/
private void copyTo( @Nonnull final String identifier, @Nonnull final File destination, @Nonnull final String directory ) throws URISyntaxException, IOException
{
assert identifier != null;
assert destination != null;
assert directory != null;
final Class<? extends RecipeResourceCopier> copierClass = this.getClass();
final String[] listing = this.getResourceListing( copierClass, directory );
for( final String list : listing )
{
if( list.endsWith( identifier ) )
{
// generate folder before the file is copied so no empty folders will be generated
FileUtils.forceMkdir( destination );
this.copyFile( destination, directory, list );
}
else if( !list.contains( "." ) )
{
final File subDirectory = new File( destination, list );
this.copyTo( identifier, subDirectory, directory + list + "/" );
}
}
}
/**
* Copies a single file inside a folder to the destination.
*
* @param destination folder to which the file is copied to
* @param directory the directory containing the file
* @param fileName the file to copy
*
* @throws IOException if copying the file is not possible
*/
private void copyFile( @Nonnull final File destination, @Nonnull final String directory, @Nonnull final String fileName ) throws IOException
{
assert destination != null;
assert directory != null;
assert fileName != null;
final Class<? extends RecipeResourceCopier> copierClass = this.getClass();
final InputStream inStream = copierClass.getResourceAsStream( '/' + directory + fileName );
final File outFile = new File( destination, fileName );
if( !outFile.exists() && inStream != null )
{
FileUtils.copyInputStreamToFile( inStream, outFile );
inStream.close();
}
}
/**
* List directory contents for a resource folder. Not recursive. This is basically a brute-force implementation.
* Works for regular files and also JARs.
*
* @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 Just the name of each member item, not the full paths.
*
* @throws URISyntaxException if it is a file path and the URL can not be converted to URI
* @throws IOException if jar path can not be decoded
* @throws UnsupportedOperationException if it is neither in jar nor in file path
*/
@Nonnull
private String[] getResourceListing( @Nonnull final Class<?> clazz, @Nonnull final String path ) throws URISyntaxException, IOException
{
assert clazz != null;
assert path != null;
final ClassLoader classLoader = clazz.getClassLoader();
if( classLoader == null )
{
throw new IllegalStateException( "ClassLoader was not found. It was probably loaded at a inappropriate time" );
}
URL dirURL = classLoader.getResource( path );
if( dirURL != null )
{
final String protocol = dirURL.getProtocol();
if( protocol.equals( FILE_PROTOCOL ) )
{
// A file path: easy enough
final URI uriOfURL = dirURL.toURI();
final File fileOfURI = new File( uriOfURL );
final String[] filesAndDirectoriesOfURI = fileOfURI.list();
if( filesAndDirectoriesOfURI == null )
{
throw new IllegalStateException( "Files and Directories were illegal. Either an abstract pathname does not denote a directory, or an I/O error occured." );
}
else
{
return filesAndDirectoriesOfURI;
}
}
}
if( dirURL == null )
{
/*
* In case of a jar file, we can't actually find a directory.
* Have to assume the same jar as clazz.
*/
final String className = clazz.getName();
final Matcher matcher = DOT_COMPILE_PATTERN.matcher( className );
final String me = matcher.replaceAll( "/" ) + CLASS_EXTENSION;
dirURL = classLoader.getResource( me );
}
if( dirURL != null )
{
final String protocol = dirURL.getProtocol();
if( protocol.equals( JAR_PROTOCOL ) )
{
/* A JAR path */
final String dirPath = dirURL.getPath();
final String jarPath = dirPath.substring( 5, dirPath.indexOf( '!' ) ); // strip out only
// the JAR file
final JarFile jar = new JarFile( URLDecoder.decode( jarPath, UTF_8_ENCODING ) );
try
{
final Enumeration<JarEntry> entries = jar.entries(); // gives ALL entries in jar
final Collection<String> result = new HashSet<String>( INITIAL_RESOURCE_CAPACITY ); // avoid duplicates
// in case it is a
// subdirectory
while( entries.hasMoreElements() )
{
final JarEntry entry = entries.nextElement();
final String entryFullName = entry.getName();
if( entryFullName.startsWith( path ) )
{ // filter according to the path
String entryName = entryFullName.substring( path.length() );
final int checkSubDir = entryName.indexOf( '/' );
if( checkSubDir >= 0 )
{
// if it is a subdirectory, we just return the directory name
entryName = entryName.substring( 0, checkSubDir );
}
result.add( entryName );
}
}
return result.toArray( new String[result.size()] );
}
finally
{
jar.close();
}
}
}
throw new UnsupportedOperationException( "Cannot list files for URL " + dirURL );
}
}