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