src/main/java/dmillerw/lore/common/lore/LoreLoader.java
public class LoreLoader {

    public static void initialize() {
        // Forces GLOBAL to be the first category
        categories.add(Lore.GLOBAL);

        for (File file : LoreExpansion.loreFolder.listFiles(ExtensionFilter.JSON)) {
            try {
                LoreLoader.loadLore(file);
            } catch (Exception ex) {
                LoreExpansion.logger.warn(String.format("Failed to parse %s", file.getName()));
                ex.printStackTrace();
            }
        }

        // Sort
        for (String category : getAllCategories()) {
            LinkedList<Lore> loreList = Lists.newLinkedList();
            loreList.addAll(getMap(category).values());
            Collections.sort(loreList, new Comparator<Lore>() {
                @Override
                public int compare(Lore o1, Lore o2) {
                    return Integer.compare(o1.sortingIndex, o2.sortingIndex);
                }
            });

            LinkedHashMap<String, Lore> newMap = Maps.newLinkedHashMap();

            for (Lore lore :loreList) {
                newMap.put(lore.ident, lore);
            }

            loreMap.put(category, newMap);
        }

        Collections.sort(categories);
    }

    public static void loadLore(File file) throws Exception {
        Lore lore = JsonUtil.gson().fromJson(new FileReader(file), Lore.class);

        if (!categories.contains(lore.category))
            categories.add(lore.category);

        lore.ident = FilenameUtils.getBaseName(file.getName());

        // Check to see if file exists
        if (!lore.sound.isEmpty()) {
            File audio = new File(LoreExpansion.audioFolder, lore.sound + ".ogg");
            if (!audio.exists() || !audio.isFile()) {
                LoreExpansion.logger.warn(String.format("Could not find %s audio file as defined in %s", lore.sound, file.getName()));
                lore.sound = "";
            }
        }

        LinkedHashMap<String, Lore> submap = getMap(lore.category);
        submap.put(lore.ident, lore);
        loreMap.put(lore.category, submap);
    }

    private static LinkedList<String> categories = Lists.newLinkedList();
    private static Map<String, LinkedHashMap<String, Lore>> loreMap = Maps.newHashMap();

    private static LinkedHashMap<String, Lore> getMap(String category) {
        LinkedHashMap<String, Lore> map = loreMap.get(category);
        if (map == null) {
            map = Maps.newLinkedHashMap();
            loreMap.put(category, map);
        }
        return map;
    }

    public static List<String> getAllCategories() {
        return categories;
    }

    public static ImmutableSet<Lore> getAllLore() {
        ImmutableSet.Builder<Lore> builder = new ImmutableSet.Builder<Lore>();
        for (LinkedHashMap<String, Lore> category : loreMap.values()) {
            builder.addAll(category.values());
        }
        return builder.build();
    }

    public static ImmutableSet<Lore> getAllLoreForCategory(String category) {
        ImmutableSet.Builder<Lore> builder = new ImmutableSet.Builder<Lore>();
        builder.addAll(getMap(category).values());
        return builder.build();
    }

    public static Lore getLore(LoreKey key) {
        return getMap(key.category).get(key.ident);
    }

    public static Lore getLore(String category, String ident) {
        return getMap(category).get(ident);
    }

    public static void clear() {
        loreMap.clear();
    }
}
src/main/java/dmillerw/lore/LoreExpansion.java
@Mod(modid = "LoreExp", name = "Lore Expansion", version = "%MOD_VERSION%", dependencies = "required-after:Forge@[%FORGE_VERSION%,)")
public class LoreExpansion {

    private static final String CONFIG_FOLDER = "LoreExpansion";
    private static final String LORE_FOLDER = "lore";
    private static final String AUDIO_FOLDER = "audio";

    public static final Logger logger = LogManager.getLogger("Lore Expansion");

    @Mod.Instance("LoreExp")
    public static LoreExpansion instance;

    @SidedProxy(serverSide = "dmillerw.lore.CommonProxy", clientSide = "dmillerw.lore.ClientProxy")
    public static CommonProxy proxy;

    public static File configFolder;
    public static File loreFolder;
    public static File audioFolder;

    public static Item lorePage;
    public static Item journal;

    @Mod.EventHandler
    public void preInit(FMLPreInitializationEvent event) {
        configFolder = Files.mkdir(event.getModConfigurationDirectory(), CONFIG_FOLDER);
        loreFolder = Files.mkdir(configFolder, LORE_FOLDER);
        audioFolder = Files.mkdir(loreFolder, AUDIO_FOLDER);

        proxy.preInit(event);
    }

    @Mod.EventHandler
    public void init(FMLInitializationEvent event) {
        NetworkRegistry.INSTANCE.registerGuiHandler(this, new GuiHandler());

        proxy.init(event);
    }

    @Mod.EventHandler
    public void postInit(FMLPostInitializationEvent event) {
        proxy.postInit(event);
    }

    @Mod.EventHandler
    public void onServerStarting(FMLServerStartingEvent event) {
        event.registerServerCommand(new CommandLore());
    }
}
src/main/java/dmillerw/lore/common/lore/data/LoreKey.java
public class LoreKey {

    public static LoreKey fromNBT(NBTTagCompound nbt) {
        if (nbt == null)
            return null;

        return new LoreKey(nbt.getString("category"), nbt.getString("ident"));
    }

    public static LoreKey fromLore(Lore lore) {
        if (lore == null)
            return null;

        return new LoreKey(lore.category, lore.ident);
    }

    public final String category;
    public final String ident;

    public LoreKey(String category, String ident) {
        this.category = category;
        this.ident = ident;
    }

    public void writeToNBT(NBTTagCompound nbt) {
        nbt.setString("category", category);
        nbt.setString("ident", ident);
    }

    public boolean isLore(Lore lore) {
        return lore.ident.equals(ident) && lore.category.equals(category);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        LoreKey loreKey = (LoreKey) o;

        if (!category.equals(loreKey.category)) return false;
        return ident.equals(loreKey.ident);

    }

    @Override
    public int hashCode() {
        int result = category.hashCode();
        result = 31 * result + ident.hashCode();
        return result;
    }

    public LoreKey copy() {
        return new LoreKey(category, ident);
    }
}
src/main/java/dmillerw/lore/common/lore/PlayerHandler.java
public class PlayerHandler {

    public static final String LORE_PROPERTIES = "loreexp:collected_lore";

    public static void attach(EntityPlayer player) {
        player.registerExtendedProperties(LORE_PROPERTIES, new LoreProperties());
    }

    public static LoreProperties getCollectedLore(EntityPlayer player) {
        return (LoreProperties) player.getExtendedProperties(LORE_PROPERTIES);
    }
}
src/main/java/dmillerw/lore/common/network/packet/PacketReloadLore.java
public class PacketReloadLore implements IMessage {

    @Override
    public void toBytes(ByteBuf buf) {

    }

    @Override
    public void fromBytes(ByteBuf buf) {

    }

    public static class Handler implements IMessageHandler<PacketReloadLore, IMessage> {

        @Override
        public IMessage onMessage(PacketReloadLore message, MessageContext ctx) {
            LoreLoader.clear();
            LoreLoader.initialize();
            return null;
        }
    }
}
src/main/java/dmillerw/lore/common/lore/data/Lore.java
public class Lore {

    public static final String GLOBAL = "GLOBAL";

    public String category = GLOBAL;
    public String ident = "";
    public String title = "";
    public String body = "";
    public String sound = "";

    @SerializedName("sorting_index")
    public int sortingIndex = 0;

    public Commands commands = Commands.BLANK;

    public boolean autoplay = true;
    public boolean notify = true;
    public boolean hidden = false;
}
src/main/java/dmillerw/lore/common/item/ItemLorePage.java
public class ItemLorePage extends Item {

    public static void setLore(ItemStack stack, LoreKey key) {
        if (!stack.hasTagCompound()) {
            stack.setTagCompound(new NBTTagCompound());
        }

        NBTTagCompound nbt = stack.getTagCompound();
        key.writeToNBT(nbt);
        stack.setTagCompound(nbt);
    }

    public static LoreKey getLore(ItemStack stack) {
        if (!stack.hasTagCompound()) {
            return null;
        }

        return LoreKey.fromNBT(stack.getTagCompound());
    }

    private IIcon icon;

    public ItemLorePage() {
        super();

        setMaxDamage(0);
        setHasSubtypes(true);
        setMaxStackSize(1);
        setCreativeTab(TabLore.TAB);
    }

    @Override
    public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) {
        if (!world.isRemote) {
            if (player.capabilities.isCreativeMode) {
                LoreKey key = ItemLorePage.getLore(stack);
                if (key != null) {
                    Lore data = LoreLoader.getLore(key);

                    if (data != null) {
                        PlayerHandler.getCollectedLore(player).addLore(key);
                        PacketSyncLore.updateLore((EntityPlayerMP) player);

                        player.addChatComponentMessage(new ChatComponentText(String.format("Added lore page '%s'", key.ident)));
                    }
                }
            }
        }

        return stack;
    }

    @Override
    public void addInformation(ItemStack stack, EntityPlayer player, List list, boolean debug) {
        LoreKey key = ItemLorePage.getLore(stack);
        if (key != null) {
            Lore data = LoreLoader.getLore(key);

            if (data != null) {
                list.add(String.format("Title: %s", data.title));
                list.add(String.format("Category: %s", data.category));
            }
        }
    }

    @Override
    public void getSubItems(Item item, CreativeTabs tab, List list) {
        for (Lore data : LoreLoader.getAllLore()) {
            if (data != null) {
                ItemStack stack = new ItemStack(this);
                ItemLorePage.setLore(stack, LoreKey.fromLore(data));
                list.add(stack);
            }
        }
    }

    @Override
    public IIcon getIconFromDamage(int damage) {
        return icon;
    }

    @Override
    public void registerIcons(IIconRegister register) {
        icon = register.registerIcon("loreexp:scrap");
    }
}
src/main/java/dmillerw/lore/common/lore/data/entity/LoreProperties.java
public class LoreProperties implements IExtendedEntityProperties {

    private List<LoreKey> collectedLore = new ArrayList<LoreKey>();
    private List<LoreKey> autoplayed = new ArrayList<LoreKey>();

    @Override
    public void init(Entity entity, World world) {

    }

    @Override
    public void loadNBTData(NBTTagCompound compound) {
        NBTTagList list = compound.getTagList("list", Constants.NBT.TAG_COMPOUND);
        for (int i = 0; i < list.tagCount(); i++) {
            NBTTagCompound tag = list.getCompoundTagAt(i);
            LoreKey key = LoreKey.fromNBT(tag);
            collectedLore.add(key);
            if (tag.hasKey("played")) {
                autoplayed.add(key);
            }
        }
    }

    @Override
    public void saveNBTData(NBTTagCompound compound) {
        NBTTagList list = new NBTTagList();
        for (LoreKey key : collectedLore) {
            NBTTagCompound tag = new NBTTagCompound();
            key.writeToNBT(tag);
            if (autoplayed.contains(key)) {
                tag.setBoolean("played", true);
            }
            list.appendTag(tag);
        }
        compound.setTag("list", list);
    }

    public List<LoreKey> getLore() {
        return collectedLore;
    }

    public void clear() {
        collectedLore.clear();
        autoplayed.clear();
    }

    public boolean hasLore(LoreKey key) {
        return collectedLore.contains(key);
    }

    public void removeLore(LoreKey loreKey) {
        collectedLore.remove(loreKey);
    }

    public void addLore(LoreKey key) {
        collectedLore.add(key);
    }

    public boolean canAutoplay(LoreKey key) {
        return !autoplayed.contains(key);
    }

    public void setAutoplayed(LoreKey key, boolean state) {
        if (state && !autoplayed.contains(key)) {
            autoplayed.add(key);
        } else if (!state && autoplayed.contains(key)) {
            autoplayed.remove(key);
        }
    }
}
src/main/java/dmillerw/lore/common/network/packet/PacketSyncLore.java
public class PacketSyncLore implements IMessage {

    public static void updateLore(EntityPlayerMP player) {
        PacketHandler.INSTANCE.sendTo(new PacketSyncLore(player), player);
    }

    private List<LoreKey> lore;

    public PacketSyncLore() {

    }

    public PacketSyncLore(EntityPlayer player) {
        this.lore = PlayerHandler.getCollectedLore(player).getLore();
    }

    @Override
    public void toBytes(ByteBuf buf) {
        buf.writeInt(lore.size());
        if (!lore.isEmpty()) {
            for (int i = 0; i < lore.size(); i++) {
                LoreKey key = lore.get(i);

                ByteBufUtils.writeUTF8String(buf, key.category);
                ByteBufUtils.writeUTF8String(buf, key.ident);
            }
        }
    }

    @Override
    public void fromBytes(ByteBuf buf) {
        lore = new ArrayList<LoreKey>();
        int size = buf.readInt();
        for (int i = 0; i < size; i++) {
            lore.add(new LoreKey(ByteBufUtils.readUTF8String(buf), ByteBufUtils.readUTF8String(buf)));
        }
    }

    public static class Handler implements IMessageHandler<PacketSyncLore, IMessage> {

        @Override
        public IMessage onMessage(PacketSyncLore message, MessageContext ctx) {
            World world = LoreExpansion.proxy.getClientWorld();
            if (world == null) {
                return null;
            }

            GuiJournal.playerLore.clear();
            GuiJournal.playerLore = message.lore;

            if (!GuiJournal.playerLore.contains(GuiJournal.selectedLore)) {
                GuiJournal.selectedLore = null;
                SoundHandler.INSTANCE.stop(); // Just in case
            }

            return null;
        }
    }
}
src/main/java/dmillerw/lore/common/network/packet/PacketHandler.java
public class PacketHandler {

    public static final SimpleNetworkWrapper INSTANCE = NetworkRegistry.INSTANCE.newSimpleChannel("loreexp");

    public static void init() {
        INSTANCE.registerMessage(PacketNotification.Handler.class, PacketNotification.class, -1, Side.SERVER);
        INSTANCE.registerMessage(PacketNotification.Handler.class, PacketNotification.class, 1, Side.CLIENT);
        INSTANCE.registerMessage(PacketSyncLore.Handler.class, PacketSyncLore.class, 2, Side.CLIENT);
        INSTANCE.registerMessage(PacketReloadLore.Handler.class, PacketReloadLore.class, 3, Side.CLIENT);
    }
}


Question: import com.google.common.collect.Maps;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.relauncher.Side;
import dmillerw.lore.LoreExpansion;
import dmillerw.lore.common.item.ItemLorePage;
import dmillerw.lore.common.lore.LoreLoader;
import dmillerw.lore.common.lore.PlayerHandler;
import dmillerw.lore.common.lore.data.Lore;
import dmillerw.lore.common.lore.data.LoreKey;
import dmillerw.lore.common.lore.data.entity.LoreProperties;
import dmillerw.lore.common.network.packet.PacketHandler;
import dmillerw.lore.common.network.packet.PacketReloadLore;
import dmillerw.lore.common.network.packet.PacketSyncLore;
import net.minecraft.command.CommandBase;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.EnumChatFormatting;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

                    player.addChatComponentMessage(new ChatComponentText(String.format("Removed Lore [%s, %s]", category, ident)));
                }
            // Specific, other player
            } else if (args.length == 4) {
                EntityPlayer player = getPlayer(sender, args[1]);
                if (player == null) {
                    sender.addChatMessage(new ChatComponentText("Could not find player" + args[1]));
                    return;
                }

                String category = args[2];
                String ident = args[3];

                PlayerHandler.getCollectedLore(player).removeLore(new LoreKey(category, ident));

                player.addChatComponentMessage(new ChatComponentText(String.format("Removed Lore [%s, %s] from %s", category, ident, player.getDisplayName())));
            }

            return;
        }

        // Give
        if (args[0].equals("give")) {
            // Self, all
            if (args.length == 2) {
                if (args[1].equals("all")) {
                    Set<Lore> lores = LoreLoader.getAllLore();
                    for (Lore lore : lores) {
                        final LoreKey key = LoreKey.fromLore(lore);

                        LoreProperties properties = PlayerHandler.getCollectedLore((EntityPlayer) sender);
                        if (!properties.hasLore(key))
                            properties.addLore(key);
                    }

                    PacketSyncLore.updateLore((EntityPlayerMP) sender);

                    sender.addChatMessage(new ChatComponentText("Gave " + ((EntityPlayer)sender).getDisplayName() + " all lore"));

                    return;
                }
            }

            if (args.length == 3) {
                // Other, all
                if (args[2].equals("all")) {
                    EntityPlayer player = getPlayer(sender, args[1]);
                    if (player == null) {
                        sender.addChatMessage(new ChatComponentText("Could not find player" + args[1]));
                        return;
                    }

                    Set<Lore> lores = LoreLoader.getAllLore();
                    for (Lore lore : lores) {
                        final LoreKey key = LoreKey.fromLore(lore);

                        LoreProperties properties = PlayerHandler.getCollectedLore(player);
                        if (!properties.hasLore(key))
                            properties.addLore(key);
                    }

                    PacketSyncLore.updateLore((EntityPlayerMP) sender);

                    sender.addChatMessage(new ChatComponentText("Gave Player "  + player.getDisplayName() + " all lore"));

                    return;
                // Self, specific
                } else {
                    if (!validCategory(args[1])) {
                        sender.addChatMessage(new ChatComponentText(args[1] + " is not a valid Lore category!"));
                        return;
                    }

                    if (!validLore(args[1], args[2])) {
                        sender.addChatMessage(new ChatComponentText("Could not find lore '" + args[2] + "' in category '" + args[1] + "'"));
                        return;
                    }

                    giveLore((EntityPlayer) sender, args[1], args[2]);

                    return;
                }
            }

            // Other, specific
            if (args.length == 4) {
                if (!validCategory(args[2])) {
                    sender.addChatMessage(new ChatComponentText(args[2] + " is not a valid Lore category!"));
                    return;
                }

                if (!validLore(args[2], args[3])) {
                    sender.addChatMessage(new ChatComponentText("Could not find lore '" + args[3] + "' in category '" + args[2] + "'"));
                    return;
                }

                EntityPlayer player = getPlayer(sender, args[1]);
                if (player == null) {
                    sender.addChatMessage(new ChatComponentText("Could not find player" + args[1]));
                    return;
                }

                giveLore(player, args[2], args[3]);

                return;
            }
        }
    }

    private boolean validCategory(String category) {
        return LoreLoader.getAllCategories().contains(category);
    }

    private boolean validLore(String category, String ident) {
        return LoreLoader.getLore(category, ident) != null;
    }

    private void giveLore(EntityPlayer entityPlayer, String category, String ident) {
        LoreKey key = new LoreKey(category, ident);
Answer:         ItemStack itemStack = new ItemStack(LoreExpansion.lorePage);