001    package cpw.mods.fml.common;
002    
003    import java.lang.reflect.InvocationTargetException;
004    import java.util.List;
005    import java.util.Map;
006    import java.util.Map.Entry;
007    import java.util.logging.Level;
008    
009    import com.google.common.base.Joiner;
010    import com.google.common.collect.ArrayListMultimap;
011    import com.google.common.collect.BiMap;
012    import com.google.common.collect.ImmutableBiMap;
013    import com.google.common.collect.ImmutableMap;
014    import com.google.common.collect.ImmutableMap.Builder;
015    import com.google.common.collect.Iterables;
016    import com.google.common.collect.Lists;
017    import com.google.common.collect.Multimap;
018    import com.google.common.eventbus.EventBus;
019    import com.google.common.eventbus.Subscribe;
020    
021    import cpw.mods.fml.common.LoaderState.ModState;
022    import cpw.mods.fml.common.event.FMLLoadEvent;
023    import cpw.mods.fml.common.event.FMLPreInitializationEvent;
024    import cpw.mods.fml.common.event.FMLStateEvent;
025    
026    public class LoadController
027    {
028        private Loader loader;
029        private EventBus masterChannel;
030        private ImmutableMap<String,EventBus> eventChannels;
031        private LoaderState state;
032        private Multimap<String, ModState> modStates = ArrayListMultimap.create();
033        private Multimap<String, Throwable> errors = ArrayListMultimap.create();
034        private Map<String, ModContainer> modList;
035        private List<ModContainer> activeModList = Lists.newArrayList();
036        private ModContainer activeContainer;
037        private BiMap<ModContainer, Object> modObjectList;
038    
039        public LoadController(Loader loader)
040        {
041            this.loader = loader;
042            this.masterChannel = new EventBus("FMLMainChannel");
043            this.masterChannel.register(this);
044    
045            state = LoaderState.NOINIT;
046    
047    
048        }
049    
050        @Subscribe
051        public void buildModList(FMLLoadEvent event)
052        {
053            this.modList = loader.getIndexedModList();
054            Builder<String, EventBus> eventBus = ImmutableMap.builder();
055    
056            for (ModContainer mod : loader.getModList())
057            {
058                EventBus bus = new EventBus(mod.getModId());
059                boolean isActive = mod.registerBus(bus, this);
060                if (isActive)
061                {
062                    FMLLog.fine("Activating mod %s", mod.getModId());
063                    activeModList.add(mod);
064                    modStates.put(mod.getModId(), ModState.UNLOADED);
065                    eventBus.put(mod.getModId(), bus);
066                }
067                else
068                {
069                    FMLLog.warning("Mod %s has been disabled through configuration", mod.getModId());
070                    modStates.put(mod.getModId(), ModState.UNLOADED);
071                    modStates.put(mod.getModId(), ModState.DISABLED);
072                }
073            }
074    
075            eventChannels = eventBus.build();
076        }
077    
078        public void distributeStateMessage(LoaderState state, Object... eventData)
079        {
080            if (state.hasEvent())
081            {
082                masterChannel.post(state.getEvent(eventData));
083            }
084        }
085    
086        public void transition(LoaderState desiredState)
087        {
088            LoaderState oldState = state;
089            state = state.transition(!errors.isEmpty());
090            if (state != desiredState)
091            {
092                Throwable toThrow = null;
093                FMLLog.severe("Fatal errors were detected during the transition from %s to %s. Loading cannot continue", oldState, desiredState);
094                StringBuilder sb = new StringBuilder();
095                printModStates(sb);
096                FMLLog.severe(sb.toString());
097                FMLLog.severe("The following problems were captured during this phase");
098                for (Entry<String, Throwable> error : errors.entries())
099                {
100                    FMLLog.log(Level.SEVERE, error.getValue(), "Caught exception from %s", error.getKey());
101                    if (error.getValue() instanceof IFMLHandledException)
102                    {
103                        toThrow = error.getValue();
104                    }
105                    else if (toThrow == null)
106                    {
107                        toThrow = error.getValue();
108                    }
109                }
110                if (toThrow != null && toThrow instanceof RuntimeException)
111                {
112                    throw (RuntimeException)toThrow;
113                }
114                else
115                {
116                    throw new LoaderException(toThrow);
117                }
118            }
119        }
120    
121        public ModContainer activeContainer()
122        {
123            return activeContainer;
124        }
125    
126        @Subscribe
127        public void propogateStateMessage(FMLStateEvent stateEvent)
128        {
129            if (stateEvent instanceof FMLPreInitializationEvent)
130            {
131                modObjectList = buildModObjectList();
132            }
133            for (ModContainer mc : activeModList)
134            {
135                activeContainer = mc;
136                String modId = mc.getModId();
137                stateEvent.applyModContainer(activeContainer());
138                FMLLog.finer("Posting state event %s to mod %s", stateEvent.getEventType(), modId);
139                eventChannels.get(modId).post(stateEvent);
140                FMLLog.finer("State event %s delivered to mod %s", stateEvent.getEventType(), modId);
141                activeContainer = null;
142                if (!errors.containsKey(modId))
143                {
144                    modStates.put(modId, stateEvent.getModState());
145                }
146                else
147                {
148                    modStates.put(modId, ModState.ERRORED);
149                }
150            }
151        }
152    
153        public ImmutableBiMap<ModContainer, Object> buildModObjectList()
154        {
155            ImmutableBiMap.Builder<ModContainer, Object> builder = ImmutableBiMap.<ModContainer, Object>builder();
156            for (ModContainer mc : activeModList)
157            {
158                if (!mc.isImmutable() && mc.getMod()!=null)
159                {
160                    builder.put(mc, mc.getMod());
161                }
162                if (mc.getMod()==null && !mc.isImmutable() && state!=LoaderState.CONSTRUCTING)
163                {
164                    FMLLog.severe("There is a severe problem with %s - it appears not to have constructed correctly", mc.getModId());
165                    if (state != LoaderState.CONSTRUCTING)
166                    {
167                        this.errorOccurred(mc, new RuntimeException());
168                    }
169                }
170            }
171            return builder.build();
172        }
173    
174        public void errorOccurred(ModContainer modContainer, Throwable exception)
175        {
176            if (exception instanceof InvocationTargetException)
177            {
178                errors.put(modContainer.getModId(), ((InvocationTargetException)exception).getCause());
179            }
180            else
181            {
182                errors.put(modContainer.getModId(), exception);
183            }
184        }
185    
186        public void printModStates(StringBuilder ret)
187        {
188            for (ModContainer mc : loader.getModList())
189            {
190                ret.append("\n\t").append(mc.getModId()).append(" [").append(mc.getName()).append("] (").append(mc.getSource().getName()).append(") ");
191                Joiner.on("->"). appendTo(ret, modStates.get(mc.getModId()));
192            }
193        }
194    
195        public List<ModContainer> getActiveModList()
196        {
197            return activeModList;
198        }
199    
200        public ModState getModState(ModContainer selectedMod)
201        {
202            return Iterables.getLast(modStates.get(selectedMod.getModId()), ModState.AVAILABLE);
203        }
204    
205        public void distributeStateMessage(Class<?> customEvent)
206        {
207            try
208            {
209                masterChannel.post(customEvent.newInstance());
210            }
211            catch (Exception e)
212            {
213                FMLLog.log(Level.SEVERE, e, "An unexpected exception");
214                throw new LoaderException(e);
215            }
216        }
217    
218        public BiMap<ModContainer, Object> getModObjectList()
219        {
220            if (modObjectList == null)
221            {
222                FMLLog.severe("Detected an attempt by a mod %s to perform game activity during mod construction. This is a serious programming error.", activeContainer);
223                return buildModObjectList();
224            }
225            return ImmutableBiMap.copyOf(modObjectList);
226        }
227    
228        public boolean isInState(LoaderState state)
229        {
230            return this.state == state;
231        }
232    }