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