    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.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    }