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;
008
009import com.google.common.base.Joiner;
010import com.google.common.collect.ArrayListMultimap;
011import com.google.common.collect.BiMap;
012import com.google.common.collect.ImmutableBiMap;
013import com.google.common.collect.ImmutableMap;
014import com.google.common.collect.ImmutableMap.Builder;
015import com.google.common.collect.Iterables;
016import com.google.common.collect.Lists;
017import com.google.common.collect.Multimap;
018import com.google.common.eventbus.EventBus;
019import com.google.common.eventbus.Subscribe;
020
021import cpw.mods.fml.common.LoaderState.ModState;
022import cpw.mods.fml.common.event.FMLEvent;
023import cpw.mods.fml.common.event.FMLLoadEvent;
024import cpw.mods.fml.common.event.FMLPreInitializationEvent;
025import cpw.mods.fml.common.event.FMLStateEvent;
026
027public 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}