Java tutorial
/* * Forge: Play Magic: the Gathering. * Copyright (C) 2011 Forge Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package forge.quest.io; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import forge.card.CardEdition; import forge.deck.CardPool; import forge.deck.Deck; import forge.deck.DeckGroup; import forge.deck.DeckSection; import forge.item.*; import forge.model.FModel; import forge.properties.ForgeConstants; import forge.quest.QuestController; import forge.quest.QuestEventDraft; import forge.quest.QuestMode; import forge.quest.bazaar.QuestItemType; import forge.quest.data.*; import forge.util.FileUtil; import forge.util.IgnoringXStream; import forge.util.ItemPool; import forge.util.XmlUtil; import org.apache.commons.lang3.StringUtils; import org.w3c.dom.*; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.*; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map.Entry; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * <p> * QuestDataIO class. * </p> * * @author Forge * @version $Id: QuestDataIO.java 29632 2015-06-13 02:23:02Z KrazyTheFox $ */ public class QuestDataIO { static { //ensure save directory exists if this class is used FileUtil.ensureDirectoryExists(ForgeConstants.QUEST_SAVE_DIR); } /** * Gets the serializer. * * @param isIgnoring the is ignoring * @return the serializer */ protected static XStream getSerializer(final boolean isIgnoring) { final XStream xStream = isIgnoring ? new IgnoringXStream() : new XStream(); xStream.registerConverter(new ItemPoolToXml()); xStream.registerConverter(new DeckToXml()); xStream.registerConverter(new DraftTournamentToXml()); xStream.registerConverter(new GameFormatQuestToXml()); xStream.registerConverter(new QuestModeToXml()); xStream.autodetectAnnotations(true); xStream.alias("CardPool", ItemPool.class); xStream.alias("DeckSection", CardPool.class); return xStream; } /** * <p> * loadData. * </p> * * @param xmlSaveFile *   {@link java.io.File} * @return {@link forge.quest.data.QuestData} */ public static QuestData loadData(final File xmlSaveFile) { try { QuestData data; final GZIPInputStream zin = new GZIPInputStream(new FileInputStream(xmlSaveFile)); final StringBuilder xml = new StringBuilder(); final char[] buf = new char[1024]; final InputStreamReader reader = new InputStreamReader(zin); while (reader.ready()) { final int len = reader.read(buf); if (len == -1) { break; } // when end of stream was reached xml.append(buf, 0, len); } zin.close(); String bigXML = xml.toString(); data = (QuestData) QuestDataIO.getSerializer(true).fromXML(bigXML); if (data.getVersionNumber() != QuestData.CURRENT_VERSION_NUMBER) { try { QuestDataIO.updateSaveFile(data, bigXML, xmlSaveFile.getName().replace(".dat", "")); } catch (final Exception e) { //BugReporter.reportException(e); throw new RuntimeException(e); } } return data; } catch (final Exception ex) { //BugReporter.reportException(ex, "Error loading Quest Data"); throw new RuntimeException(ex); } } private static <T> void setFinalField(final Class<T> clasz, final String fieldName, final T instance, final Object newValue) throws IllegalAccessException, NoSuchFieldException { final Field field = clasz.getDeclaredField(fieldName); field.setAccessible(true); field.set(instance, newValue); // no difference here (used only to set // initial lives) } /** * <p> * updateSaveFile. * </p> * * @param newData * a {@link forge.quest.data.QuestData} object. * @param input * a {@link java.lang.String} object. * @throws ParserConfigurationException * @throws IOException * @throws SAXException * @throws NoSuchFieldException * @throws IllegalAccessException */ private static void updateSaveFile(final QuestData newData, final String input, String filename) throws ParserConfigurationException, SAXException, IOException, IllegalAccessException, NoSuchFieldException { final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); final InputSource is = new InputSource(); is.setCharacterStream(new StringReader(input)); final Document document = builder.parse(is); final int saveVersion = newData.getVersionNumber(); if (saveVersion < 3) { QuestDataIO.setFinalField(QuestData.class, "assets", newData, new QuestAssets(null)); final int diffIdx = Integer .parseInt(document.getElementsByTagName("diffIndex").item(0).getTextContent()); QuestDataIO.setFinalField(QuestData.class, "achievements", newData, new QuestAchievements(diffIdx)); } if (saveVersion < 4) { QuestDataIO.setFinalField(QuestAssets.class, "inventoryItems", newData.getAssets(), new EnumMap<QuestItemType, Integer>(QuestItemType.class)); } if (saveVersion < 5) { QuestDataIO.setFinalField(QuestAssets.class, "combatPets", newData.getAssets(), new HashMap<String, QuestItemCondition>()); } if (saveVersion < 6) { QuestDataIO.setFinalField(QuestData.class, "petSlots", newData, new HashMap<>()); } if (saveVersion < 8) { QuestDataIO.setFinalField(QuestData.class, "isCharmActive", newData, false); } final QuestAssets qS = newData.getAssets(); final QuestAchievements qA = newData.getAchievements(); switch (saveVersion) { // There should be a fall-through between the cases so that each // version's changes get applied progressively case 0: // First beta release with new file format, // inventory needs to be migrated QuestDataIO.setFinalField(QuestAssets.class, "inventoryItems", newData.getAssets(), new EnumMap<QuestItemType, Integer>(QuestItemType.class)); qS.setItemLevel(QuestItemType.ESTATES, Integer.parseInt(document.getElementsByTagName("estatesLevel").item(0).getTextContent())); qS.setItemLevel(QuestItemType.LUCKY_COIN, Integer.parseInt(document.getElementsByTagName("luckyCoinLevel").item(0).getTextContent())); qS.setItemLevel(QuestItemType.SLEIGHT, Integer.parseInt(document.getElementsByTagName("sleightOfHandLevel").item(0).getTextContent())); final int gearLevel = Integer .parseInt(document.getElementsByTagName("gearLevel").item(0).getTextContent()); if (gearLevel >= 1) { newData.getAssets().setItemLevel(QuestItemType.MAP, 1); } if (gearLevel == 2) { newData.getAssets().setItemLevel(QuestItemType.ZEPPELIN, 1); } //$FALL-THROUGH$ case 1: // nothing to do here, everything is managed by CardPoolToXml // deserializer case 2: // questdata was divided into assets and achievements if (StringUtils.isBlank(newData.getName())) { QuestDataIO.setFinalField(QuestData.class, "name", newData, filename); } QuestDataIO.setFinalField(QuestAchievements.class, "win", qA, Integer.parseInt(document.getElementsByTagName("win").item(0).getTextContent())); QuestDataIO.setFinalField(QuestAchievements.class, "lost", qA, Integer.parseInt(document.getElementsByTagName("lost").item(0).getTextContent())); Node nw; if ((nw = document.getElementsByTagName("winstreakBest").item(0)) != null) { QuestDataIO.setFinalField(QuestAchievements.class, "winstreakBest", qA, Integer.parseInt(nw.getTextContent())); } if ((nw = document.getElementsByTagName("winstreakCurrent").item(0)) != null) { QuestDataIO.setFinalField(QuestAchievements.class, "winstreakCurrent", qA, Integer.parseInt(nw.getTextContent())); } QuestDataIO.setFinalField(QuestAchievements.class, "challengesPlayed", qA, Integer.parseInt(document.getElementsByTagName("challengesPlayed").item(0).getTextContent())); final List<Integer> completedChallenges = new ArrayList<>(); QuestDataIO.setFinalField(QuestAchievements.class, "completedChallenges", qA, completedChallenges); if ((nw = document.getElementsByTagName("completedChallenges").item(0)) != null) { final NodeList ccs = nw.getChildNodes(); for (int iN = 0; iN < ccs.getLength(); iN++) { final Node n0 = ccs.item(iN); if (n0.getNodeType() != Node.ELEMENT_NODE) { continue; } completedChallenges.add(Integer.parseInt(n0.getTextContent())); } } final XStream xs = QuestDataIO.getSerializer(true); QuestDataIO.setFinalField(QuestAssets.class, "credits", qS, Integer.parseInt(document.getElementsByTagName("credits").item(0).getTextContent())); QuestDataIO.setFinalField(QuestAssets.class, "cardPool", qS, QuestDataIO.readAsset(xs, document, "cardPool", ItemPool.class)); QuestDataIO.setFinalField(QuestAssets.class, "myDecks", qS, QuestDataIO.readAsset(xs, document, "myDecks", HashMap.class)); QuestDataIO.setFinalField(QuestAssets.class, "shopList", qS, QuestDataIO.readAsset(xs, document, "shopList", ItemPool.class)); QuestDataIO.setFinalField(QuestAssets.class, "newCardList", qS, QuestDataIO.readAsset(xs, document, "newCardList", ItemPool.class)); //$FALL-THROUGH$ case 3: // QuestInventory class no longer exists - KV pairs of // QuestItemPair => level moved to assets final Node oldInventory = saveVersion > 0 ? document.getElementsByTagName("inventory").item(1) : null; if (null != oldInventory) { for (int iN = 0; iN < oldInventory.getChildNodes().getLength(); iN++) { final Node _n = oldInventory.getChildNodes().item(iN); if (_n.getNodeType() != Node.ELEMENT_NODE) { continue; } final Element n = (Element) _n; final String name = n.getElementsByTagName("string").item(0).getTextContent(); final QuestItemType qType = QuestItemType.valueFromSaveKey(name); int level = 0; for (int iX = 0; iX < n.getChildNodes().getLength(); iX++) { final Node _x = n.getChildNodes().item(iX); if (_x.getNodeType() != Node.ELEMENT_NODE) { continue; } final Element x = (Element) _x; if (!x.getTagName().startsWith("forge.quest.data.")) { continue; } final String sLevel = x.getElementsByTagName("level").item(0).getTextContent(); if (StringUtils.isNotBlank(sLevel)) { level = Integer.parseInt(sLevel); } } qS.setItemLevel(qType, level); } } //$FALL-THROUGH$ case 4: if (saveVersion > 0) { NodeList pets = document.getElementsByTagName("pets").item(0).getChildNodes(); for (int i = 0; i < pets.getLength(); i++) { if (pets.item(i).getNodeType() != Node.ELEMENT_NODE) { continue; } final Element entry = (Element) pets.item(i); String name = entry.getElementsByTagName("string").item(0).getTextContent(); String sLevel = entry.getElementsByTagName("level").item(0).getTextContent(); qS.setPetLevel(name, Integer.parseInt(sLevel)); } } // pet manager will be re-engineered here //$FALL-THROUGH$ case 5: case 6: // have to convert completed challenges list members to strings. for (int i = qA.getLockedChallenges().size() - 1; i >= 0; i--) { Object lc = qA.getLockedChallenges().get(i); if (lc != null) { qA.getLockedChallenges().set(i, lc.toString()); } } for (int i = qA.getCurrentChallenges().size() - 1; i >= 0; i--) { Object lc = qA.getCurrentChallenges().get(i); if (lc != null) { qA.getCurrentChallenges().set(i, lc.toString()); } } //$FALL-THROUGH$ case 7: case 8: QuestDataIO.setFinalField(QuestAssets.class, "draftDecks", qS, new HashMap<String, DeckGroup>()); break; } // mark the QD as the latest version newData.setVersionNumber(QuestData.CURRENT_VERSION_NUMBER); } @SuppressWarnings("unchecked") private static <T> T readAsset(final XStream xs, final Document doc, final String tagName, final Class<T> clasz) { final NodeList nn = doc.getElementsByTagName(tagName); final Node n = nn.item(0); final Attr att = doc.createAttribute("resolves-to"); att.setValue(clasz.getCanonicalName()); n.getAttributes().setNamedItem(att); final String xmlData = XmlUtil.nodeToString(n); return (T) xs.fromXML(xmlData); } /** * <p> * saveData. * </p> * * @param qd * a {@link forge.quest.data.QuestData} object. */ public static synchronized void saveData(final QuestData qd) { try { final XStream xStream = QuestDataIO.getSerializer(false); final File f = new File(ForgeConstants.QUEST_SAVE_DIR, qd.getName()); //Copy the save file in case the save fails FileUtil.copyFile(f + ".dat", f + ".dat.bak"); QuestDataIO.savePacked(f + ".dat", xStream, qd); // QuestDataIO.saveUnpacked(f + ".xml", xStream, qd); } catch (final Exception ex) { //BugReporter.reportException(ex, "Error saving Quest Data."); throw new RuntimeException(ex); } } private static void savePacked(final String f, final XStream xStream, final QuestData qd) throws IOException { final BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(f)); final GZIPOutputStream zout = new GZIPOutputStream(bout); xStream.toXML(qd, zout); zout.flush(); zout.close(); } @SuppressWarnings("unused") // used only for debug purposes private static void saveUnpacked(final String f, final XStream xStream, final QuestData qd) throws IOException { final BufferedOutputStream boutUnp = new BufferedOutputStream(new FileOutputStream(f)); xStream.toXML(qd, boutUnp); boutUnp.flush(); boutUnp.close(); } private static class GameFormatQuestToXml implements Converter { @SuppressWarnings("rawtypes") @Override public boolean canConvert(final Class clasz) { return clasz.equals(GameFormatQuest.class); } @Override public void marshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) { writer.startNode("format"); GameFormatQuest format = (GameFormatQuest) source; writer.addAttribute("name", format.getName()); writer.addAttribute("unlocksUsed", Integer.toString(format.getUnlocksUsed())); writer.addAttribute("canUnlock", format.canUnlockSets() ? "1" : "0"); writer.endNode(); for (String set : format.getAllowedSetCodes()) { writer.startNode("set"); writer.addAttribute("s", set); writer.endNode(); } for (String ban : format.getBannedCardNames()) { writer.startNode("ban"); writer.addAttribute("s", ban); writer.endNode(); } } @Override public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) { reader.moveDown(); String name = reader.getAttribute("name"); String unlocksUsed = reader.getAttribute("unlocksUsed"); boolean canUnlock = !("0".equals(reader.getAttribute("canUnlock"))); List<String> allowedSets = new ArrayList<>(); List<String> bannedCards = new ArrayList<>(); reader.moveUp(); while (reader.hasMoreChildren()) { reader.moveDown(); final String nodename = reader.getNodeName(); if (nodename.equals("ban")) { bannedCards.add(reader.getAttribute("s")); // System.out.println("Added + " + toBan + " to banned cards"); } else if (nodename.equals("set")) { allowedSets.add(reader.getAttribute("s")); // System.out.println("Added + " + toSets + " to legal sets"); } reader.moveUp(); } GameFormatQuest res = new GameFormatQuest(name, allowedSets, bannedCards); try { if (StringUtils.isNotEmpty(unlocksUsed)) { setFinalField(GameFormatQuest.class, "unlocksUsed", res, Integer.parseInt(unlocksUsed)); } setFinalField(GameFormatQuest.class, "allowUnlocks", res, canUnlock); } catch (NumberFormatException | IllegalAccessException | NoSuchFieldException e) { e.printStackTrace(); } return res; } } private static class QuestModeToXml implements Converter { @SuppressWarnings("rawtypes") @Override public boolean canConvert(final Class clasz) { return clasz.equals(QuestMode.class); } @Override public void marshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) { QuestMode mode = (QuestMode) source; String sMode = mode.toString(); writer.setValue(sMode); } @Override public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) { final String value = reader.getValue(); return QuestMode.smartValueOf(value, QuestMode.Classic); } } private static class DraftTournamentToXml implements Converter { @SuppressWarnings("rawtypes") @Override public boolean canConvert(Class type) { return type.equals(QuestEventDraftContainer.class); } @Override public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { QuestEventDraftContainer drafts = (QuestEventDraftContainer) source; for (QuestEventDraft draft : drafts) { writer.startNode("draft"); writer.startNode("title"); writer.setValue(draft.getTitle()); writer.endNode(); writer.startNode("packs"); String output = ""; for (int i = 0; i < draft.getBoosterConfiguration().length; i++) { output += draft.getBoosterConfiguration()[i]; if (i != draft.getBoosterConfiguration().length - 1) { output += "/"; } } writer.setValue(output); writer.endNode(); writer.startNode("entryFee"); writer.setValue(String.valueOf(draft.getEntryFee())); writer.endNode(); writer.startNode("block"); writer.setValue(draft.getBlock()); writer.endNode(); writer.startNode("standings"); int i = 0; for (String standing : draft.getStandings()) { writer.startNode("s" + i++); writer.setValue(standing); writer.endNode(); } writer.endNode(); writer.startNode("aiNames"); i = 0; for (String name : draft.getAINames()) { writer.startNode("ain" + i++); writer.setValue(name); writer.endNode(); } writer.endNode(); writer.startNode("aiIcons"); i = 0; for (int icon : draft.getAIIcons()) { writer.startNode("aii" + i++); writer.setValue(icon + ""); writer.endNode(); } writer.endNode(); writer.startNode("started"); writer.setValue("" + draft.isStarted()); writer.endNode(); writer.startNode("age"); writer.setValue("" + draft.getAge()); writer.endNode(); writer.endNode(); } } @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { QuestEventDraftContainer output = new QuestEventDraftContainer(); while (reader.hasMoreChildren()) { reader.moveDown(); String draftName = null; String boosterConfiguration = null; int entryFee = 1500; int age = 15; String block = null; String[] standings = new String[15]; String[] aiNames = new String[7]; int[] aiIcons = new int[7]; boolean started = false; while (reader.hasMoreChildren()) { reader.moveDown(); switch (reader.getNodeName()) { case "title": draftName = reader.getValue(); break; case "packs": boosterConfiguration = reader.getValue(); break; case "entryFee": entryFee = Integer.parseInt(reader.getValue()); break; case "block": block = reader.getValue(); break; case "standings": int i = 0; while (reader.hasMoreChildren()) { reader.moveDown(); standings[i++] = reader.getValue(); reader.moveUp(); } break; case "aiNames": int ii = 0; while (reader.hasMoreChildren()) { reader.moveDown(); aiNames[ii++] = reader.getValue(); reader.moveUp(); } break; case "aiIcons": int iii = 0; while (reader.hasMoreChildren()) { reader.moveDown(); aiIcons[iii++] = Integer.parseInt(reader.getValue()); reader.moveUp(); } break; case "started": started = Boolean.parseBoolean(reader.getValue()); break; case "age": age = Integer.parseInt(reader.getValue()); break; } reader.moveUp(); } QuestEventDraft draft = new QuestEventDraft(draftName); draft.setBoosterConfiguration(boosterConfiguration); draft.setEntryFee(entryFee); draft.setBlock(block); draft.setStandings(standings); draft.setAINames(aiNames); draft.setAIIcons(aiIcons); draft.setStarted(started); draft.setAge(age); output.add(draft); reader.moveUp(); } return output; } } public static class DeckToXml extends ItemPoolToXml { /* (non-Javadoc) * @see com.thoughtworks.xstream.converters.ConverterMatcher#canConvert(java.lang.Class) */ @SuppressWarnings("rawtypes") @Override public boolean canConvert(Class type) { return type.equals(Deck.class); } /* (non-Javadoc) * @see com.thoughtworks.xstream.converters.Converter#marshal(java.lang.Object, com.thoughtworks.xstream.io.HierarchicalStreamWriter, com.thoughtworks.xstream.converters.MarshallingContext) */ @Override public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { Deck d = (Deck) source; writer.startNode("name"); writer.setValue(d.getName()); writer.endNode(); for (Entry<DeckSection, CardPool> ds : d) { writer.startNode(ds.getKey().toString()); for (final Entry<PaperCard, Integer> e : ds.getValue()) { this.write(e.getKey(), e.getValue(), writer); } writer.endNode(); } } /* (non-Javadoc) * @see com.thoughtworks.xstream.converters.Converter#unmarshal(com.thoughtworks.xstream.io.HierarchicalStreamReader, com.thoughtworks.xstream.converters.UnmarshallingContext) */ @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { reader.moveDown(); // <name> tag MUST be at first position at all times String deckName = reader.getValue(); reader.moveUp(); final Deck result = new Deck(deckName); while (reader.hasMoreChildren()) { reader.moveDown(); DeckSection section = DeckSection.smartValueOf(reader.getNodeName()); if (null == section) throw new RuntimeException("Quest deck has unknown section: " + reader.getNodeName()); CardPool pool = result.getOrCreate(section); while (reader.hasMoreChildren()) { reader.moveDown(); final String sCnt = reader.getAttribute("n"); final int cnt = StringUtils.isNumeric(sCnt) ? Integer.parseInt(sCnt) : 1; final String nodename = reader.getNodeName(); if ("string".equals(nodename)) { pool.add(FModel.getMagicDb().getCommonCards().getCard(reader.getValue())); } else if ("card".equals(nodename)) { // new format pool.add(this.readCardPrinted(reader), cnt); } reader.moveUp(); } reader.moveUp(); } return result; } } public static class ItemPoolToXml implements Converter { @SuppressWarnings("rawtypes") @Override public boolean canConvert(final Class clasz) { return clasz.equals(ItemPool.class); } protected void write(final PaperCard cref, final Integer count, final HierarchicalStreamWriter writer) { writer.startNode("card"); writer.addAttribute("c", cref.getName()); writer.addAttribute("s", cref.getEdition()); if (cref.isFoil()) { writer.addAttribute("foil", "1"); } writer.addAttribute("i", Integer.toString(cref.getArtIndex())); writer.addAttribute("n", count.toString()); writer.endNode(); } protected void write(final BoosterPack booster, final Integer count, final HierarchicalStreamWriter writer) { writer.startNode("booster"); if (booster.getEdition().equals("?")) { writer.addAttribute("s", booster.getName().substring(0, booster.getName().indexOf(booster.getItemType()) - 1)); } else { writer.addAttribute("s", booster.getEdition()); } writer.addAttribute("n", count.toString()); writer.endNode(); } protected void write(final FatPack fatpack, final Integer count, final HierarchicalStreamWriter writer) { writer.startNode("fpack"); writer.addAttribute("s", fatpack.getEdition()); writer.addAttribute("n", count.toString()); writer.endNode(); } protected void write(final BoosterBox boosterbox, final Integer count, final HierarchicalStreamWriter writer) { writer.startNode("bbox"); writer.addAttribute("s", boosterbox.getEdition()); writer.addAttribute("n", count.toString()); writer.endNode(); } protected void write(final TournamentPack booster, final Integer count, final HierarchicalStreamWriter writer) { writer.startNode("tpack"); writer.addAttribute("s", booster.getEdition()); writer.addAttribute("n", count.toString()); writer.endNode(); } protected void write(final PreconDeck deck, final Integer count, final HierarchicalStreamWriter writer) { writer.startNode("precon"); writer.addAttribute("name", deck.getName()); writer.addAttribute("n", count.toString()); writer.endNode(); } @Override public void marshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) { @SuppressWarnings("unchecked") final ItemPool<InventoryItem> pool = (ItemPool<InventoryItem>) source; for (final Entry<InventoryItem, Integer> e : pool) { final InventoryItem item = e.getKey(); final Integer count = e.getValue(); if (item instanceof PaperCard) { this.write((PaperCard) item, count, writer); } else if (item instanceof BoosterPack) { this.write((BoosterPack) item, count, writer); } else if (item instanceof TournamentPack) { this.write((TournamentPack) item, count, writer); } else if (item instanceof FatPack) { this.write((FatPack) item, count, writer); } else if (item instanceof BoosterBox) { this.write((BoosterBox) item, count, writer); } else if (item instanceof PreconDeck) { this.write((PreconDeck) item, count, writer); } } } @Override public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) { final ItemPool<InventoryItem> result = new ItemPool<>(InventoryItem.class); while (reader.hasMoreChildren()) { reader.moveDown(); final String sCnt = reader.getAttribute("n"); final int cnt = StringUtils.isNumeric(sCnt) ? Integer.parseInt(sCnt) : 1; final String nodename = reader.getNodeName(); if ("string".equals(nodename)) { result.add(FModel.getMagicDb().getCommonCards().getCard(reader.getValue())); } else if ("card".equals(nodename)) { // new format result.add(this.readCardPrinted(reader), cnt); } else if ("booster".equals(nodename)) { result.add(this.readBooster(reader), cnt); } else if ("tpack".equals(nodename)) { result.add(this.readTournamentPack(reader), cnt); } else if ("fpack".equals(nodename)) { result.add(this.readFatPack(reader), cnt); } else if ("bbox".equals(nodename)) { result.add(this.readBoosterBox(reader), cnt); } else if ("precon".equals(nodename)) { final PreconDeck toAdd = this.readPreconDeck(reader); if (null != toAdd) { result.add(toAdd, cnt); } } reader.moveUp(); } return result; } protected PreconDeck readPreconDeck(final HierarchicalStreamReader reader) { String name = reader.getAttribute("name"); if (name == null) { name = reader.getAttribute("s"); } return QuestController.getPrecons().get(name); } protected BoosterPack readBooster(final HierarchicalStreamReader reader) { String s = reader.getAttribute("s"); if (SealedProduct.specialSets.contains(s) || s.equals("?")) { return BoosterPack.FN_FROM_COLOR.apply(s); } else { final CardEdition ed = FModel.getMagicDb().getEditions().get(s); return BoosterPack.FN_FROM_SET.apply(ed); } } protected TournamentPack readTournamentPack(final HierarchicalStreamReader reader) { final CardEdition ed = FModel.getMagicDb().getEditions().get(reader.getAttribute("s")); return TournamentPack.FN_FROM_SET.apply(ed); } protected FatPack readFatPack(final HierarchicalStreamReader reader) { final CardEdition ed = FModel.getMagicDb().getEditions().get(reader.getAttribute("s")); return FatPack.FN_FROM_SET.apply(ed); } protected BoosterBox readBoosterBox(final HierarchicalStreamReader reader) { final CardEdition ed = FModel.getMagicDb().getEditions().get(reader.getAttribute("s")); return BoosterBox.FN_FROM_SET.apply(ed); } protected PaperCard readCardPrinted(final HierarchicalStreamReader reader) { final String name = reader.getAttribute("c"); final String set = reader.getAttribute("s"); final String sIndex = reader.getAttribute("i"); final short index = StringUtils.isNumeric(sIndex) ? Short.parseShort(sIndex) : 0; final boolean foil = "1".equals(reader.getAttribute("foil")); PaperCard c = FModel.getMagicDb().getCommonCards().getCard(name, set, index); if (null == c) c = FModel.getMagicDb().getCommonCards().getCard(name); return foil ? FModel.getMagicDb().getCommonCards().getFoiled(c) : c; } } }