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    }