Closes #1452: Implements auto-generation of custom recipes

This commit is contained in:
thatsIch
2015-05-13 22:02:54 +02:00
parent 2722057c81
commit 0076f2ea51
10 changed files with 669 additions and 25 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
* 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
@@ -0,0 +1,99 @@
/*
* 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.core;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import javax.annotation.Nonnull;
import com.google.common.base.Preconditions;
import org.apache.commons.io.FileUtils;
import appeng.api.recipes.IRecipeHandler;
import appeng.recipes.loader.ConfigLoader;
import appeng.recipes.loader.JarLoader;
import appeng.recipes.loader.RecipeResourceCopier;
/**
* handles the decision if recipes should be loaded from jar, loaded from file system or force copied from jar
*
* @author thatsIch
* @version rv3 - 12.05.2015
* @since rv3 12.05.2015
*/
public class RecipeLoader implements Runnable
{
private final IRecipeHandler handler;
/**
* @param handler handler to load the recipes
*
* @throws NullPointerException if handler is <tt>null</tt>
*/
public RecipeLoader( @Nonnull IRecipeHandler handler )
{
Preconditions.checkNotNull( handler );
this.handler = handler;
}
@Override
public void run()
{
// setup copying
final RecipeResourceCopier copier = new RecipeResourceCopier( "assets/appliedenergistics2/recipes/" );
final File configDirectory = AppEng.instance().getConfigDirectory();
final File generatedRecipesDir = new File( configDirectory, "generated-recipes" );
final File userRecipesDir = new File( configDirectory, "user-recipes" );
final File readmeGenDest = new File( generatedRecipesDir, "README.html" );
final File readmeUserDest = new File( userRecipesDir, "README.html" );
// generates generated and user recipes dir
// will clean the generated every time to keep it up to date
// copies over the recipes in the jar over to the generated folder
// copies over the readmes
try
{
FileUtils.forceMkdir( generatedRecipesDir );
FileUtils.forceMkdir( userRecipesDir );
FileUtils.cleanDirectory( generatedRecipesDir );
copier.copyTo( generatedRecipesDir );
FileUtils.copyFile( readmeGenDest, readmeUserDest );
// parse recipes prioritising the user scripts by using the generated as template
this.handler.parseRecipes( new ConfigLoader( generatedRecipesDir, userRecipesDir ), "index.recipe" );
}
// on failure use jar parsing
catch( IOException e )
{
AELog.error( e );
this.handler.parseRecipes( new JarLoader( "/assets/appliedenergistics2/recipes/" ), "index.recipe" );
}
catch( URISyntaxException e )
{
AELog.error( e );
this.handler.parseRecipes( new JarLoader( "/assets/appliedenergistics2/recipes/" ), "index.recipe" );
}
}
}
+2 -10
View File
@@ -108,8 +108,6 @@ import appeng.recipes.handlers.Pulverizer;
import appeng.recipes.handlers.Shaped;
import appeng.recipes.handlers.Shapeless;
import appeng.recipes.handlers.Smelt;
import appeng.recipes.loader.ConfigLoader;
import appeng.recipes.loader.JarLoader;
import appeng.recipes.ores.OreDictionaryHandler;
import appeng.spatial.BiomeGenStorage;
import appeng.spatial.StorageWorldProvider;
@@ -516,14 +514,8 @@ public final class Registration
// Perform ore camouflage!
ItemMultiMaterial.instance.makeUnique();
if( AEConfig.instance.isFeatureEnabled( AEFeature.CustomRecipes ) )
{
this.recipeHandler.parseRecipes( new ConfigLoader( AppEng.instance().getConfigDirectory() ), "index.recipe" );
}
else
{
this.recipeHandler.parseRecipes( new JarLoader( "/assets/appliedenergistics2/recipes/" ), "index.recipe" );
}
final Runnable recipeLoader = new RecipeLoader( this.recipeHandler );
recipeLoader.run();
partHelper.registerNewLayer( "appeng.parts.layers.LayerISidedInventory", "net.minecraft.inventory.ISidedInventory" );
partHelper.registerNewLayer( "appeng.parts.layers.LayerIFluidHandler", "net.minecraftforge.fluids.IFluidHandler" );
@@ -1,6 +1,6 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
* 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
@@ -63,7 +63,7 @@ public enum AEFeature
UnsupportedDeveloperTools( "Misc", false ), Creative( "Misc" ),
GrinderLogging( "Misc", false ), Logging( "Misc" ), IntegrationLogging( "Misc", false ), CustomRecipes( "Crafting", false ), WebsiteRecipes( "Misc", false ),
GrinderLogging( "Misc", false ), Logging( "Misc" ), IntegrationLogging( "Misc", false ), WebsiteRecipes( "Misc", false ),
enableFacadeCrafting( "Crafting" ), inWorldSingularity( "Crafting" ), inWorldFluix( "Crafting" ), inWorldPurification( "Crafting" ), UpdateLogging( "Misc", false ),
@@ -1,6 +1,6 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
* 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
@@ -23,24 +23,35 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import javax.annotation.Nonnull;
import com.google.common.base.Preconditions;
import appeng.api.recipes.IRecipeLoader;
public final class ConfigLoader implements IRecipeLoader
{
private final File rootDirectory;
private final File generatedRecipesDir;
private final File userRecipesDir;
public ConfigLoader( File rootDirectory )
public ConfigLoader( File generatedRecipesDir, File userRecipesDir )
{
this.rootDirectory = rootDirectory;
this.generatedRecipesDir = generatedRecipesDir;
this.userRecipesDir = userRecipesDir;
}
@Override
public BufferedReader getFile( String s ) throws Exception
public BufferedReader getFile( @Nonnull String relativeFilePath ) throws Exception
{
final File f = new File( this.rootDirectory, s );
Preconditions.checkNotNull( relativeFilePath );
Preconditions.checkArgument( !relativeFilePath.isEmpty(), "Supplying an empty String will result creating a reader of a folder." );
return new BufferedReader( new InputStreamReader( new FileInputStream( f ), "UTF-8" ) );
final File generatedFile = new File( this.generatedRecipesDir, relativeFilePath );
final File userFile = new File( this.userRecipesDir, relativeFilePath );
final File toBeLoaded = ( userFile.exists() && userFile.isFile() ) ? userFile : generatedFile;
return new BufferedReader( new InputStreamReader( new FileInputStream( toBeLoaded ), "UTF-8" ) );
}
}
@@ -1,6 +1,6 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
* 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
@@ -21,6 +21,9 @@ package appeng.recipes.loader;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import javax.annotation.Nonnull;
import com.google.common.base.Preconditions;
import appeng.api.recipes.IRecipeLoader;
@@ -36,8 +39,11 @@ public class JarLoader implements IRecipeLoader
}
@Override
public BufferedReader getFile( String s ) throws Exception
public BufferedReader getFile( @Nonnull String s ) throws Exception
{
Preconditions.checkNotNull( s );
Preconditions.checkArgument( !s.isEmpty() );
return new BufferedReader( new InputStreamReader( this.getClass().getResourceAsStream( this.rootPath + s ), "UTF-8" ) );
}
}
@@ -0,0 +1,181 @@
/*
* 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.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.Pattern;
import javax.annotation.Nonnull;
import com.google.common.base.Preconditions;
import org.apache.commons.io.FileUtils;
/**
* 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 );
/**
* 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 String root )
{
Preconditions.checkNotNull( root );
this.root = root;
}
/**
* copies recipes found in the root to destination.
*
* @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 File destination ) throws URISyntaxException, IOException
{
Preconditions.checkNotNull( destination );
Preconditions.checkArgument( destination.isDirectory() );
final String[] listing = this.getResourceListing( this.getClass(), this.root );
for( String list : listing )
{
if( list.endsWith( ".recipe" ) || list.endsWith( ".html" ) )
{
final InputStream inStream = this.getClass().getResourceAsStream( '/' + this.root + list );
final File outFile = new File( destination, list );
if( !outFile.exists() )
{
if( 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( Class<?> clazz, String path ) throws URISyntaxException, IOException
{
assert clazz != null;
assert path != null;
URL dirURL = clazz.getClassLoader().getResource( path );
if( dirURL != null && dirURL.getProtocol().equals( "file" ) )
{
// A file path: easy enough
return new File( dirURL.toURI() ).list();
}
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 me = DOT_COMPILE_PATTERN.matcher( clazz.getName() ).replaceAll( "/" ) + ".class";
dirURL = clazz.getClassLoader().getResource( me );
}
if( dirURL != null && dirURL.getProtocol().equals( "jar" ) )
{
/* A JAR path */
final String jarPath = dirURL.getPath().substring( 5, dirURL.getPath().indexOf( '!' ) ); //strip out only the JAR file
final JarFile jar = new JarFile( URLDecoder.decode( jarPath, "UTF-8" ) );
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 String name = entries.nextElement().getName();
if( name.startsWith( path ) )
{ //filter according to the path
String entry = name.substring( path.length() );
final int checkSubDir = entry.indexOf( '/' );
if( checkSubDir >= 0 )
{
// if it is a subdirectory, we just return the directory name
entry = entry.substring( 0, checkSubDir );
}
result.add( entry );
}
}
return result.toArray( new String[result.size()] );
}
finally
{
jar.close();
}
}
throw new UnsupportedOperationException( "Cannot list files for URL " + dirURL );
}
}