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