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