    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}