001 /* 002 * The FML Forge Mod Loader suite. Copyright (C) 2012 cpw 003 * 004 * 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 005 * Software Foundation; either version 2.1 of the License, or any later version. 006 * 007 * 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 008 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 009 * 010 * 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 011 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 012 */ 013 package cpw.mods.fml.common; 014 015 import java.io.File; 016 import java.lang.annotation.Annotation; 017 import java.lang.reflect.Field; 018 import java.lang.reflect.Method; 019 import java.lang.reflect.Modifier; 020 import java.util.Arrays; 021 import java.util.List; 022 import java.util.Map; 023 import java.util.Set; 024 import java.util.logging.Level; 025 026 import com.google.common.base.Function; 027 import com.google.common.base.Strings; 028 import com.google.common.base.Throwables; 029 import com.google.common.collect.ArrayListMultimap; 030 import com.google.common.collect.BiMap; 031 import com.google.common.collect.ImmutableBiMap; 032 import com.google.common.collect.Lists; 033 import com.google.common.collect.Multimap; 034 import com.google.common.collect.SetMultimap; 035 import com.google.common.collect.Sets; 036 import com.google.common.eventbus.EventBus; 037 import com.google.common.eventbus.Subscribe; 038 039 import cpw.mods.fml.common.Mod.Instance; 040 import cpw.mods.fml.common.Mod.Metadata; 041 import cpw.mods.fml.common.discovery.ASMDataTable; 042 import cpw.mods.fml.common.discovery.ASMDataTable.ASMData; 043 import cpw.mods.fml.common.event.FMLConstructionEvent; 044 import cpw.mods.fml.common.event.FMLInitializationEvent; 045 import cpw.mods.fml.common.event.FMLPostInitializationEvent; 046 import cpw.mods.fml.common.event.FMLPreInitializationEvent; 047 import cpw.mods.fml.common.event.FMLServerStartedEvent; 048 import cpw.mods.fml.common.event.FMLServerStartingEvent; 049 import cpw.mods.fml.common.event.FMLServerStoppingEvent; 050 import cpw.mods.fml.common.event.FMLStateEvent; 051 import cpw.mods.fml.common.network.FMLNetworkHandler; 052 import cpw.mods.fml.common.versioning.ArtifactVersion; 053 import cpw.mods.fml.common.versioning.DefaultArtifactVersion; 054 055 public class FMLModContainer implements ModContainer 056 { 057 private Mod modDescriptor; 058 private Object modInstance; 059 private File source; 060 private ModMetadata modMetadata; 061 private String className; 062 private Map<String, Object> descriptor; 063 private boolean enabled = true; 064 private String internalVersion; 065 private boolean overridesMetadata; 066 private EventBus eventBus; 067 private LoadController controller; 068 private Multimap<Class<? extends Annotation>, Object> annotations; 069 private DefaultArtifactVersion processedVersion; 070 private boolean isNetworkMod; 071 072 private static final BiMap<Class<? extends FMLStateEvent>, Class<? extends Annotation>> modAnnotationTypes = ImmutableBiMap.<Class<? extends FMLStateEvent>, Class<? extends Annotation>>builder() 073 .put(FMLPreInitializationEvent.class, Mod.PreInit.class) 074 .put(FMLInitializationEvent.class, Mod.Init.class) 075 .put(FMLPostInitializationEvent.class, Mod.PostInit.class) 076 .put(FMLServerStartingEvent.class, Mod.ServerStarting.class) 077 .put(FMLServerStartedEvent.class, Mod.ServerStarted.class) 078 .put(FMLServerStoppingEvent.class, Mod.ServerStopping.class) 079 .build(); 080 private static final BiMap<Class<? extends Annotation>, Class<? extends FMLStateEvent>> modTypeAnnotations = modAnnotationTypes.inverse(); 081 private String annotationDependencies; 082 083 084 public FMLModContainer(String className, File modSource, Map<String,Object> modDescriptor) 085 { 086 this.className = className; 087 this.source = modSource; 088 this.descriptor = modDescriptor; 089 } 090 091 @Override 092 public String getModId() 093 { 094 return (String) descriptor.get("modid"); 095 } 096 097 @Override 098 public String getName() 099 { 100 return modMetadata.name; 101 } 102 103 @Override 104 public String getVersion() 105 { 106 return internalVersion; 107 } 108 109 @Override 110 public File getSource() 111 { 112 return source; 113 } 114 115 @Override 116 public ModMetadata getMetadata() 117 { 118 return modMetadata; 119 } 120 121 @Override 122 public void bindMetadata(MetadataCollection mc) 123 { 124 modMetadata = mc.getMetadataForId(getModId(), descriptor); 125 126 if (descriptor.containsKey("usesMetadata")) 127 { 128 overridesMetadata = !((Boolean)descriptor.get("usesMetadata")).booleanValue(); 129 } 130 131 if (overridesMetadata || !modMetadata.useDependencyInformation) 132 { 133 Set<ArtifactVersion> requirements = Sets.newHashSet(); 134 List<ArtifactVersion> dependencies = Lists.newArrayList(); 135 List<ArtifactVersion> dependants = Lists.newArrayList(); 136 annotationDependencies = (String) descriptor.get("dependencies"); 137 Loader.instance().computeDependencies(annotationDependencies, requirements, dependencies, dependants); 138 modMetadata.requiredMods = requirements; 139 modMetadata.dependencies = dependencies; 140 modMetadata.dependants = dependants; 141 FMLLog.finest("Parsed dependency info : %s %s %s", requirements, dependencies, dependants); 142 } 143 else 144 { 145 FMLLog.finest("Using mcmod dependency info : %s %s %s", modMetadata.requiredMods, modMetadata.dependencies, modMetadata.dependants); 146 } 147 if (Strings.isNullOrEmpty(modMetadata.name)) 148 { 149 FMLLog.info("Mod %s is missing the required element 'name'. Substituting %s", getModId(), getModId()); 150 modMetadata.name = getModId(); 151 } 152 internalVersion = (String) descriptor.get("version"); 153 if (Strings.isNullOrEmpty(internalVersion) && !Strings.isNullOrEmpty(modMetadata.version)) 154 { 155 FMLLog.warning("Mod %s is missing the required element 'version'. Falling back to metadata version %s", getModId(), modMetadata.version); 156 modMetadata.version = internalVersion; 157 } 158 if (Strings.isNullOrEmpty(internalVersion)) 159 { 160 FMLLog.warning("Mod %s is missing the required element 'version' and no fallback can be found. Substituting '1.0'.", getModId()); 161 modMetadata.version = internalVersion = "1.0"; 162 } 163 } 164 165 @Override 166 public void setEnabledState(boolean enabled) 167 { 168 this.enabled = enabled; 169 } 170 171 @Override 172 public Set<ArtifactVersion> getRequirements() 173 { 174 return modMetadata.requiredMods; 175 } 176 177 @Override 178 public List<ArtifactVersion> getDependencies() 179 { 180 return modMetadata.dependencies; 181 } 182 183 @Override 184 public List<ArtifactVersion> getDependants() 185 { 186 return modMetadata.dependants; 187 } 188 189 @Override 190 public String getSortingRules() 191 { 192 return ((overridesMetadata || !modMetadata.useDependencyInformation) ? Strings.nullToEmpty(annotationDependencies) : modMetadata.printableSortingRules()); 193 } 194 195 @Override 196 public boolean matches(Object mod) 197 { 198 return mod == modInstance; 199 } 200 201 @Override 202 public Object getMod() 203 { 204 return modInstance; 205 } 206 207 @Override 208 public boolean registerBus(EventBus bus, LoadController controller) 209 { 210 if (this.enabled) 211 { 212 FMLLog.fine("Enabling mod %s", getModId()); 213 this.eventBus = bus; 214 this.controller = controller; 215 eventBus.register(this); 216 return true; 217 } 218 else 219 { 220 return false; 221 } 222 } 223 224 private Multimap<Class<? extends Annotation>, Object> gatherAnnotations(Class<?> clazz) throws Exception 225 { 226 Multimap<Class<? extends Annotation>,Object> anns = ArrayListMultimap.create(); 227 228 for (Method m : clazz.getDeclaredMethods()) 229 { 230 for (Annotation a : m.getAnnotations()) 231 { 232 if (modTypeAnnotations.containsKey(a.annotationType())) 233 { 234 Class<?>[] paramTypes = new Class[] { modTypeAnnotations.get(a.annotationType()) }; 235 236 if (Arrays.equals(m.getParameterTypes(), paramTypes)) 237 { 238 m.setAccessible(true); 239 anns.put(a.annotationType(), m); 240 } 241 else 242 { 243 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)); 244 } 245 } 246 } 247 } 248 return anns; 249 } 250 251 private void processFieldAnnotations(ASMDataTable asmDataTable) throws Exception 252 { 253 SetMultimap<String, ASMData> annotations = asmDataTable.getAnnotationsFor(this); 254 255 parseSimpleFieldAnnotation(annotations, Instance.class.getName(), new Function<ModContainer, Object>() 256 { 257 public Object apply(ModContainer mc) 258 { 259 return mc.getMod(); 260 } 261 }); 262 parseSimpleFieldAnnotation(annotations, Metadata.class.getName(), new Function<ModContainer, Object>() 263 { 264 public Object apply(ModContainer mc) 265 { 266 return mc.getMetadata(); 267 } 268 }); 269 270 //TODO 271 // for (Object o : annotations.get(Block.class)) 272 // { 273 // Field f = (Field) o; 274 // f.set(modInstance, GameRegistry.buildBlock(this, f.getType(), f.getAnnotation(Block.class))); 275 // } 276 } 277 278 private void parseSimpleFieldAnnotation(SetMultimap<String, ASMData> annotations, String annotationClassName, Function<ModContainer, Object> retreiver) throws IllegalAccessException 279 { 280 String[] annName = annotationClassName.split("\\."); 281 String annotationName = annName[annName.length - 1]; 282 for (ASMData targets : annotations.get(annotationClassName)) 283 { 284 String targetMod = (String) targets.getAnnotationInfo().get("value"); 285 Field f = null; 286 Object injectedMod = null; 287 ModContainer mc = this; 288 boolean isStatic = false; 289 Class<?> clz = modInstance.getClass(); 290 if (!Strings.isNullOrEmpty(targetMod)) 291 { 292 if (Loader.isModLoaded(targetMod)) 293 { 294 mc = Loader.instance().getIndexedModList().get(targetMod); 295 } 296 else 297 { 298 mc = null; 299 } 300 } 301 if (mc != null) 302 { 303 try 304 { 305 clz = Class.forName(targets.getClassName(), true, Loader.instance().getModClassLoader()); 306 f = clz.getDeclaredField(targets.getObjectName()); 307 f.setAccessible(true); 308 isStatic = Modifier.isStatic(f.getModifiers()); 309 injectedMod = retreiver.apply(mc); 310 } 311 catch (Exception e) 312 { 313 Throwables.propagateIfPossible(e); 314 FMLLog.log(Level.WARNING, e, "Attempting to load @%s in class %s for %s and failing", annotationName, targets.getClassName(), mc.getModId()); 315 } 316 } 317 if (f != null) 318 { 319 Object target = null; 320 if (!isStatic) 321 { 322 target = modInstance; 323 if (!modInstance.getClass().equals(clz)) 324 { 325 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()); 326 continue; 327 } 328 } 329 f.set(target, injectedMod); 330 } 331 } 332 } 333 334 @Subscribe 335 public void constructMod(FMLConstructionEvent event) 336 { 337 try 338 { 339 ModClassLoader modClassLoader = event.getModClassLoader(); 340 modClassLoader.addFile(source); 341 Class<?> clazz = Class.forName(className, true, modClassLoader); 342 ASMDataTable asmHarvestedAnnotations = event.getASMHarvestedData(); 343 // TODO 344 asmHarvestedAnnotations.getAnnotationsFor(this); 345 annotations = gatherAnnotations(clazz); 346 isNetworkMod = FMLNetworkHandler.instance().registerNetworkMod(this, clazz, event.getASMHarvestedData()); 347 modInstance = clazz.newInstance(); 348 ProxyInjector.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide()); 349 processFieldAnnotations(event.getASMHarvestedData()); 350 } 351 catch (Throwable e) 352 { 353 controller.errorOccurred(this, e); 354 Throwables.propagateIfPossible(e); 355 } 356 } 357 358 @Subscribe 359 public void handleModStateEvent(FMLStateEvent event) 360 { 361 Class<? extends Annotation> annotation = modAnnotationTypes.get(event.getClass()); 362 if (annotation == null) 363 { 364 return; 365 } 366 try 367 { 368 for (Object o : annotations.get(annotation)) 369 { 370 Method m = (Method) o; 371 m.invoke(modInstance, event); 372 } 373 } 374 catch (Throwable t) 375 { 376 controller.errorOccurred(this, t); 377 Throwables.propagateIfPossible(t); 378 } 379 } 380 381 @Override 382 public ArtifactVersion getProcessedVersion() 383 { 384 if (processedVersion == null) 385 { 386 processedVersion = new DefaultArtifactVersion(getModId(), getVersion()); 387 } 388 return processedVersion; 389 } 390 @Override 391 public boolean isImmutable() 392 { 393 return false; 394 } 395 396 @Override 397 public boolean isNetworkMod() 398 { 399 return isNetworkMod; 400 } 401 402 @Override 403 public String getDisplayVersion() 404 { 405 return modMetadata.version; 406 } 407 }