ForgePatch/fml/common/cpw/mods/fml/common/FMLModContainer.java

461 lines
16 KiB
Java
Raw Normal View History

2012-03-30 14:11:13 +00:00
/*
* The FML Forge Mod Loader suite. Copyright (C) 2012 cpw
*
2012-03-30 14:11:13 +00:00
* This library 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 2.1 of the License, or any later version.
*
2012-03-30 14:11:13 +00:00
* This library 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.
*
2012-03-30 14:11:13 +00:00
* You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package cpw.mods.fml.common;
2012-03-30 14:11:13 +00:00
import java.io.File;
import java.io.FileInputStream;
2012-07-23 19:03:17 +00:00
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
2012-04-03 03:06:30 +00:00
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
2012-04-03 03:06:30 +00:00
import com.google.common.base.Function;
import com.google.common.base.Strings;
2012-07-22 14:26:38 +00:00
import com.google.common.base.Throwables;
2012-07-23 19:03:17 +00:00
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
2012-07-22 14:26:38 +00:00
import com.google.common.collect.Lists;
2012-07-23 19:03:17 +00:00
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
2012-07-22 14:26:38 +00:00
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
2012-07-23 19:03:17 +00:00
import cpw.mods.fml.common.Mod.Instance;
import cpw.mods.fml.common.Mod.Metadata;
import cpw.mods.fml.common.discovery.ASMDataTable;
import cpw.mods.fml.common.discovery.ASMDataTable.ASMData;
2012-07-22 14:26:38 +00:00
import cpw.mods.fml.common.event.FMLConstructionEvent;
2012-07-23 19:03:17 +00:00
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.event.FMLServerStartedEvent;
import cpw.mods.fml.common.event.FMLServerStartingEvent;
import cpw.mods.fml.common.event.FMLServerStoppingEvent;
import cpw.mods.fml.common.event.FMLStateEvent;
import cpw.mods.fml.common.network.FMLNetworkHandler;
import cpw.mods.fml.common.versioning.ArtifactVersion;
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
2012-07-22 14:26:38 +00:00
public class FMLModContainer implements ModContainer
{
private Mod modDescriptor;
private Object modInstance;
private File source;
2012-05-15 20:19:46 +00:00
private ModMetadata modMetadata;
2012-07-22 14:26:38 +00:00
private String className;
private Map<String, Object> descriptor;
2012-07-23 19:03:17 +00:00
private boolean enabled = true;
private String internalVersion;
2012-07-22 14:26:38 +00:00
private boolean overridesMetadata;
private EventBus eventBus;
private LoadController controller;
2012-07-23 19:03:17 +00:00
private Multimap<Class<? extends Annotation>, Object> annotations;
private DefaultArtifactVersion processedVersion;
private boolean isNetworkMod;
private static final BiMap<Class<? extends FMLStateEvent>, Class<? extends Annotation>> modAnnotationTypes = ImmutableBiMap.<Class<? extends FMLStateEvent>, Class<? extends Annotation>>builder()
.put(FMLPreInitializationEvent.class, Mod.PreInit.class)
.put(FMLInitializationEvent.class, Mod.Init.class)
.put(FMLPostInitializationEvent.class, Mod.PostInit.class)
.put(FMLServerStartingEvent.class, Mod.ServerStarting.class)
.put(FMLServerStartedEvent.class, Mod.ServerStarted.class)
.put(FMLServerStoppingEvent.class, Mod.ServerStopping.class)
.build();
private static final BiMap<Class<? extends Annotation>, Class<? extends FMLStateEvent>> modTypeAnnotations = modAnnotationTypes.inverse();
private String annotationDependencies;
2012-07-22 14:26:38 +00:00
public FMLModContainer(String className, File modSource, Map<String,Object> modDescriptor)
{
2012-07-22 14:26:38 +00:00
this.className = className;
this.source = modSource;
this.descriptor = modDescriptor;
}
@Override
2012-07-22 14:26:38 +00:00
public String getModId()
{
2012-07-22 14:26:38 +00:00
return (String) descriptor.get("modid");
}
@Override
public String getName()
{
2012-07-22 14:26:38 +00:00
return modMetadata.name;
}
@Override
2012-07-22 14:26:38 +00:00
public String getVersion()
{
return internalVersion;
}
@Override
public File getSource()
{
return source;
}
@Override
2012-07-22 14:26:38 +00:00
public ModMetadata getMetadata()
{
2012-07-22 14:26:38 +00:00
return modMetadata;
}
@Override
2012-07-22 14:26:38 +00:00
public void bindMetadata(MetadataCollection mc)
{
2012-07-23 19:03:17 +00:00
modMetadata = mc.getMetadataForId(getModId(), descriptor);
2012-07-22 14:26:38 +00:00
if (descriptor.containsKey("usesMetadata"))
{
overridesMetadata = !((Boolean)descriptor.get("usesMetadata")).booleanValue();
2012-07-22 14:26:38 +00:00
}
2012-07-22 14:26:38 +00:00
if (overridesMetadata || !modMetadata.useDependencyInformation)
{
Set<ArtifactVersion> requirements = Sets.newHashSet();
List<ArtifactVersion> dependencies = Lists.newArrayList();
List<ArtifactVersion> dependants = Lists.newArrayList();
annotationDependencies = (String) descriptor.get("dependencies");
Loader.instance().computeDependencies(annotationDependencies, requirements, dependencies, dependants);
2012-07-22 14:26:38 +00:00
modMetadata.requiredMods = requirements;
modMetadata.dependencies = dependencies;
modMetadata.dependants = dependants;
FMLLog.finest("Parsed dependency info : %s %s %s", requirements, dependencies, dependants);
2012-07-22 14:26:38 +00:00
}
else
{
FMLLog.finest("Using mcmod dependency info : %s %s %s", modMetadata.requiredMods, modMetadata.dependencies, modMetadata.dependants);
}
if (Strings.isNullOrEmpty(modMetadata.name))
{
FMLLog.info("Mod %s is missing the required element 'name'. Substituting %s", getModId(), getModId());
modMetadata.name = getModId();
}
internalVersion = (String) descriptor.get("version");
if (Strings.isNullOrEmpty(internalVersion) && getSource().isFile())
{
Properties versionProps = searchForVersionProperties();
if (versionProps != null)
{
internalVersion = versionProps.getProperty(getModId()+".version");
FMLLog.fine("Found version %s for mod %s in version.properties", internalVersion, getModId());
}
}
if (Strings.isNullOrEmpty(internalVersion) && !Strings.isNullOrEmpty(modMetadata.version))
{
FMLLog.warning("Mod %s is missing the required element 'version' and a version.properties file could not be found. Falling back to metadata version %s", getModId(), modMetadata.version);
internalVersion = modMetadata.version;
}
if (Strings.isNullOrEmpty(internalVersion))
{
FMLLog.warning("Mod %s is missing the required element 'version' and no fallback can be found. Substituting '1.0'.", getModId());
modMetadata.version = internalVersion = "1.0";
}
}
public Properties searchForVersionProperties()
{
try
{
FMLLog.fine("Attempting to load the file version.properties from %s to locate a version number for %s", getSource().getName(), getModId());
Properties version = null;
if (getSource().isFile())
{
ZipFile source = new ZipFile(getSource());
ZipEntry versionFile = source.getEntry("version.properties");
if (versionFile!=null)
{
version = new Properties();
version.load(source.getInputStream(versionFile));
}
source.close();
}
else if (getSource().isDirectory())
{
File propsFile = new File(getSource(),"version.properties");
if (propsFile.exists() && propsFile.isFile())
{
version = new Properties();
FileInputStream fis = new FileInputStream(propsFile);
version.load(fis);
fis.close();
}
}
return version;
}
catch (Exception e)
{
Throwables.propagateIfPossible(e);
FMLLog.fine("Failed to find a usable version.properties file");
return null;
}
}
@Override
2012-07-22 14:26:38 +00:00
public void setEnabledState(boolean enabled)
{
2012-07-22 14:26:38 +00:00
this.enabled = enabled;
}
@Override
public Set<ArtifactVersion> getRequirements()
{
2012-07-22 14:26:38 +00:00
return modMetadata.requiredMods;
}
@Override
public List<ArtifactVersion> getDependencies()
{
2012-07-22 14:26:38 +00:00
return modMetadata.dependencies;
}
@Override
public List<ArtifactVersion> getDependants()
{
2012-07-22 14:26:38 +00:00
return modMetadata.dependants;
}
@Override
2012-07-22 14:26:38 +00:00
public String getSortingRules()
{
return ((overridesMetadata || !modMetadata.useDependencyInformation) ? Strings.nullToEmpty(annotationDependencies) : modMetadata.printableSortingRules());
}
@Override
2012-07-22 14:26:38 +00:00
public boolean matches(Object mod)
{
2012-07-22 14:26:38 +00:00
return mod == modInstance;
}
@Override
2012-07-22 14:26:38 +00:00
public Object getMod()
{
2012-07-22 14:26:38 +00:00
return modInstance;
}
@Override
2012-07-22 14:26:38 +00:00
public boolean registerBus(EventBus bus, LoadController controller)
{
2012-07-22 14:26:38 +00:00
if (this.enabled)
{
2012-07-23 19:03:17 +00:00
FMLLog.fine("Enabling mod %s", getModId());
2012-07-22 14:26:38 +00:00
this.eventBus = bus;
this.controller = controller;
eventBus.register(this);
return true;
}
else
{
return false;
}
}
2012-07-23 19:03:17 +00:00
private Multimap<Class<? extends Annotation>, Object> gatherAnnotations(Class<?> clazz) throws Exception
{
Multimap<Class<? extends Annotation>,Object> anns = ArrayListMultimap.create();
2012-07-23 19:03:17 +00:00
for (Method m : clazz.getDeclaredMethods())
{
for (Annotation a : m.getAnnotations())
{
if (modTypeAnnotations.containsKey(a.annotationType()))
2012-07-23 19:03:17 +00:00
{
Class<?>[] paramTypes = new Class[] { modTypeAnnotations.get(a.annotationType()) };
if (Arrays.equals(m.getParameterTypes(), paramTypes))
{
m.setAccessible(true);
anns.put(a.annotationType(), m);
}
else
{
FMLLog.severe("The mod %s appears to have an invalid method annotation %s. This annotation can only apply to methods with argument types %s -it will not be called", getModId(), a.annotationType().getSimpleName(), Arrays.toString(paramTypes));
}
2012-07-23 19:03:17 +00:00
}
}
}
return anns;
}
private void processFieldAnnotations(ASMDataTable asmDataTable) throws Exception
2012-07-23 19:03:17 +00:00
{
SetMultimap<String, ASMData> annotations = asmDataTable.getAnnotationsFor(this);
parseSimpleFieldAnnotation(annotations, Instance.class.getName(), new Function<ModContainer, Object>()
2012-07-23 19:03:17 +00:00
{
public Object apply(ModContainer mc)
{
return mc.getMod();
}
});
parseSimpleFieldAnnotation(annotations, Metadata.class.getName(), new Function<ModContainer, Object>()
{
public Object apply(ModContainer mc)
{
return mc.getMetadata();
}
});
//TODO
// for (Object o : annotations.get(Block.class))
// {
// Field f = (Field) o;
// f.set(modInstance, GameRegistry.buildBlock(this, f.getType(), f.getAnnotation(Block.class)));
// }
2012-07-23 19:03:17 +00:00
}
private void parseSimpleFieldAnnotation(SetMultimap<String, ASMData> annotations, String annotationClassName, Function<ModContainer, Object> retreiver) throws IllegalAccessException
{
String[] annName = annotationClassName.split("\\.");
String annotationName = annName[annName.length - 1];
for (ASMData targets : annotations.get(annotationClassName))
{
String targetMod = (String) targets.getAnnotationInfo().get("value");
Field f = null;
Object injectedMod = null;
ModContainer mc = this;
boolean isStatic = false;
Class<?> clz = modInstance.getClass();
if (!Strings.isNullOrEmpty(targetMod))
{
if (Loader.isModLoaded(targetMod))
{
mc = Loader.instance().getIndexedModList().get(targetMod);
}
else
{
mc = null;
}
}
if (mc != null)
{
try
{
clz = Class.forName(targets.getClassName(), true, Loader.instance().getModClassLoader());
f = clz.getDeclaredField(targets.getObjectName());
f.setAccessible(true);
isStatic = Modifier.isStatic(f.getModifiers());
injectedMod = retreiver.apply(mc);
}
catch (Exception e)
{
Throwables.propagateIfPossible(e);
FMLLog.log(Level.WARNING, e, "Attempting to load @%s in class %s for %s and failing", annotationName, targets.getClassName(), mc.getModId());
}
}
if (f != null)
{
Object target = null;
if (!isStatic)
{
target = modInstance;
if (!modInstance.getClass().equals(clz))
{
FMLLog.warning("Unable to inject @%s in non-static field %s.%s for %s as it is NOT the primary mod instance", annotationName, targets.getClassName(), targets.getObjectName(), mc.getModId());
continue;
}
}
f.set(target, injectedMod);
}
}
}
2012-07-22 14:26:38 +00:00
@Subscribe
2012-07-23 19:03:17 +00:00
public void constructMod(FMLConstructionEvent event)
{
try
2012-07-22 14:26:38 +00:00
{
ModClassLoader modClassLoader = event.getModClassLoader();
modClassLoader.addFile(source);
2012-07-23 19:03:17 +00:00
Class<?> clazz = Class.forName(className, true, modClassLoader);
ASMDataTable asmHarvestedAnnotations = event.getASMHarvestedData();
// TODO
asmHarvestedAnnotations.getAnnotationsFor(this);
2012-07-23 19:03:17 +00:00
annotations = gatherAnnotations(clazz);
isNetworkMod = FMLNetworkHandler.instance().registerNetworkMod(this, clazz, event.getASMHarvestedData());
2012-07-23 19:03:17 +00:00
modInstance = clazz.newInstance();
ProxyInjector.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide());
processFieldAnnotations(event.getASMHarvestedData());
2012-07-22 14:26:38 +00:00
}
2012-07-23 19:03:17 +00:00
catch (Throwable e)
2012-07-22 14:26:38 +00:00
{
controller.errorOccurred(this, e);
Throwables.propagateIfPossible(e);
}
}
2012-07-23 19:03:17 +00:00
@Subscribe
public void handleModStateEvent(FMLStateEvent event)
2012-07-23 19:03:17 +00:00
{
Class<? extends Annotation> annotation = modAnnotationTypes.get(event.getClass());
if (annotation == null)
2012-07-23 19:03:17 +00:00
{
return;
2012-07-23 19:03:17 +00:00
}
try
{
for (Object o : annotations.get(annotation))
2012-07-23 19:03:17 +00:00
{
Method m = (Method) o;
m.invoke(modInstance, event);
2012-07-23 19:03:17 +00:00
}
}
catch (Throwable t)
{
controller.errorOccurred(this, t);
Throwables.propagateIfPossible(t);
}
}
@Override
public ArtifactVersion getProcessedVersion()
{
if (processedVersion == null)
{
processedVersion = new DefaultArtifactVersion(getModId(), getVersion());
}
return processedVersion;
}
@Override
public boolean isImmutable()
{
return false;
}
@Override
public boolean isNetworkMod()
{
return isNetworkMod;
}
@Override
public String getDisplayVersion()
{
return modMetadata.version;
}
2012-03-30 14:11:13 +00:00
}