diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java index ad61e89d849..29ef276b9a7 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java @@ -1123,7 +1123,8 @@ class ImportFilter extends FileFilter { || ext.toLowerCase(Locale.ENGLISH).equals("mwdeck") || ext.toLowerCase(Locale.ENGLISH).equals("txt") || ext.toLowerCase(Locale.ENGLISH).equals("dek") - || ext.toLowerCase(Locale.ENGLISH).equals("cod")) { + || ext.toLowerCase(Locale.ENGLISH).equals("cod") + || ext.toLowerCase(Locale.ENGLISH).equals("o8d")) { return true; } } @@ -1132,7 +1133,7 @@ class ImportFilter extends FileFilter { @Override public String getDescription() { - return "*.dec | *.mwDeck | *.txt | *.dek | *.cod"; + return "*.dec | *.mwDeck | *.txt | *.dek | *.cod | *.o8d"; } } diff --git a/Mage/src/main/java/mage/cards/decks/importer/CardLookup.java b/Mage/src/main/java/mage/cards/decks/importer/CardLookup.java index a151caf01a8..2a7aa1ae3e1 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/CardLookup.java +++ b/Mage/src/main/java/mage/cards/decks/importer/CardLookup.java @@ -1,7 +1,9 @@ package mage.cards.decks.importer; +import java.util.List; import java.util.Optional; +import mage.cards.repository.CardCriteria; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; @@ -13,4 +15,8 @@ public class CardLookup { return Optional.ofNullable(CardRepository.instance.findPreferedCoreExpansionCard(name, true)); } + public List lookupCardInfo(CardCriteria criteria) { + return CardRepository.instance.findCards(criteria); + } + } diff --git a/Mage/src/main/java/mage/cards/decks/importer/CodDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/CodDeckImporter.java index dedffadcf58..a79a6beeb5b 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/CodDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/CodDeckImporter.java @@ -1,10 +1,5 @@ package mage.cards.decks.importer; -import static javax.xml.xpath.XPathConstants.NODESET; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -12,26 +7,14 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; - import org.w3c.dom.Document; import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; import mage.cards.decks.DeckCardInfo; import mage.cards.decks.DeckCardLists; import mage.cards.repository.CardInfo; -import mage.cards.repository.CardRepository; -public class CodDeckImporter extends DeckImporter { - - private XPathFactory xpathFactory = XPathFactory.newInstance(); - private DocumentBuilder builder = getDocumentBuilder(); +public class CodDeckImporter extends XmlDeckImporter { @Override public DeckCardLists importDeck(String filename, StringBuilder errorMessages) { @@ -88,30 +71,4 @@ public class CodDeckImporter extends DeckImporter { }; } - private List getNodes(Document doc, String xpathExpression) throws XPathExpressionException { - NodeList nodes = (NodeList) xpathFactory.newXPath().evaluate(xpathExpression, doc, NODESET); - ArrayList list = new ArrayList<>(); - for (int i = 0; i < nodes.getLength(); i++) { - list.add(nodes.item(i)); - } - return list; - } - - private DocumentBuilder getDocumentBuilder() { - try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - return factory.newDocumentBuilder(); - } catch (ParserConfigurationException e) { - throw new RuntimeException(); - } - } - - protected Document getXmlDocument(String filename) throws IOException { - try { - return builder.parse(new File(filename)); - } catch (SAXException e) { - throw new IOException(e); - } - } - } diff --git a/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java index 81868e427ff..2e20ded6412 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java @@ -32,6 +32,8 @@ public abstract class DeckImporter { return new DekDeckImporter(); } else if (file.toLowerCase(Locale.ENGLISH).endsWith("cod")) { return new CodDeckImporter(); + } else if (file.toLowerCase(Locale.ENGLISH).endsWith("o8d")) { + return new O8dDeckImporter(); } else { return null; } diff --git a/Mage/src/main/java/mage/cards/decks/importer/MWSDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/MWSDeckImporter.java index fd541aa1a51..7c23d28c60b 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/MWSDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/MWSDeckImporter.java @@ -43,13 +43,13 @@ public class MWSDeckImporter extends PlainTextDeckImporter { CardCriteria criteria = new CardCriteria(); criteria.name(lineName); criteria.setCodes(setCode); - List cards = CardRepository.instance.findCards(criteria); + List cards = getCardLookup().lookupCardInfo(criteria); if (!cards.isEmpty()) { cardInfo = cards.get(RandomUtil.nextInt(cards.size())); } } if (cardInfo == null) { - cardInfo = CardRepository.instance.findPreferedCoreExpansionCard(lineName, true); + cardInfo = getCardLookup().lookupCardInfo(lineName).orElse(null); } if (cardInfo == null) { diff --git a/Mage/src/main/java/mage/cards/decks/importer/O8dDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/O8dDeckImporter.java new file mode 100644 index 00000000000..cefc64e9616 --- /dev/null +++ b/Mage/src/main/java/mage/cards/decks/importer/O8dDeckImporter.java @@ -0,0 +1,71 @@ +package mage.cards.decks.importer; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import mage.cards.decks.DeckCardInfo; +import mage.cards.decks.DeckCardLists; +import mage.cards.repository.CardInfo; + +public class O8dDeckImporter extends XmlDeckImporter { + + @Override + public DeckCardLists importDeck(String filename, StringBuilder errorMessages) { + try { + Document doc = getXmlDocument(filename); + DeckCardLists decklist = new DeckCardLists(); + + List mainCards = getNodes(doc, "/deck/section[@name='Main']/card"); + decklist.setCards(mainCards.stream() + .flatMap(toDeckCardInfo(getCardLookup(), errorMessages)) + .collect(Collectors.toList())); + + List sideboardCards = getNodes(doc, "/deck/section[@name='Sideboard']/card"); + decklist.setSideboard(sideboardCards.stream() + .flatMap(toDeckCardInfo(getCardLookup(), errorMessages)) + .collect(Collectors.toList())); + + return decklist; + } catch (Exception e) { + logger.error("Error loading deck", e); + errorMessages.append("There was an error loading the deck."); + return new DeckCardLists(); + } + } + + private static int getQuantityFromNode(Node node) { + Node numberNode = node.getAttributes().getNamedItem("qty"); + if (numberNode == null) { + return 1; + } + try { + return Math.min(100, Math.max(1, Integer.parseInt(numberNode.getNodeValue()))); + } catch (NumberFormatException e) { + return 1; + } + } + + private static Function> toDeckCardInfo(CardLookup lookup, StringBuilder errors) { + return node -> { + String name = node.getTextContent(); + Optional cardInfo = lookup.lookupCardInfo(name); + if (cardInfo.isPresent()) { + CardInfo info = cardInfo.get(); + return Collections.nCopies( + getQuantityFromNode(node), + new DeckCardInfo(info.getName(), info.getCardNumber(), info.getSetCode())).stream(); + } else { + errors.append("Could not find card: '").append(name).append("'\n"); + return Stream.empty(); + } + }; + } + +} diff --git a/Mage/src/main/java/mage/cards/decks/importer/XmlDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/XmlDeckImporter.java new file mode 100644 index 00000000000..8a88799656f --- /dev/null +++ b/Mage/src/main/java/mage/cards/decks/importer/XmlDeckImporter.java @@ -0,0 +1,52 @@ +package mage.cards.decks.importer; + +import static javax.xml.xpath.XPathConstants.NODESET; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +public abstract class XmlDeckImporter extends DeckImporter { + + private XPathFactory xpathFactory = XPathFactory.newInstance(); + private DocumentBuilder builder = getDocumentBuilder(); + + protected List getNodes(Document doc, String xpathExpression) throws XPathExpressionException { + NodeList nodes = (NodeList) xpathFactory.newXPath().evaluate(xpathExpression, doc, NODESET); + ArrayList list = new ArrayList<>(); + for (int i = 0; i < nodes.getLength(); i++) { + list.add(nodes.item(i)); + } + return list; + } + + private DocumentBuilder getDocumentBuilder() { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + return factory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new RuntimeException(); + } + } + + protected Document getXmlDocument(String filename) throws IOException { + try { + return builder.parse(new File(filename)); + } catch (SAXException e) { + throw new IOException(e); + } + } + +} diff --git a/Mage/src/main/java/mage/cards/repository/CardCriteria.java b/Mage/src/main/java/mage/cards/repository/CardCriteria.java index 6e72fcde256..85882512b24 100644 --- a/Mage/src/main/java/mage/cards/repository/CardCriteria.java +++ b/Mage/src/main/java/mage/cards/repository/CardCriteria.java @@ -309,4 +309,97 @@ public class CardCriteria { qb.orderBy(sortBy, true); } } + + public String getName() { + return name; + } + + public String getNameExact() { + return nameExact; + } + + public String getRules() { + return rules; + } + + public List getSetCodes() { + return setCodes; + } + + public List getTypes() { + return types; + } + + public List getNotTypes() { + return notTypes; + } + + public List getSupertypes() { + return supertypes; + } + + public List getNotSupertypes() { + return notSupertypes; + } + + public List getSubtypes() { + return subtypes; + } + + public List getRarities() { + return rarities; + } + + public Boolean getDoubleFaced() { + return doubleFaced; + } + + public boolean isBlack() { + return black; + } + + public boolean isBlue() { + return blue; + } + + public boolean isGreen() { + return green; + } + + public boolean isRed() { + return red; + } + + public boolean isWhite() { + return white; + } + + public boolean isColorless() { + return colorless; + } + + public Integer getConvertedManaCost() { + return convertedManaCost; + } + + public String getSortBy() { + return sortBy; + } + + public Long getStart() { + return start; + } + + public Long getCount() { + return count; + } + + public int getMinCardNumber() { + return minCardNumber; + } + + public int getMaxCardNumber() { + return maxCardNumber; + } + } diff --git a/Mage/src/test/java/mage/cards/decks/importer/CodDeckImportTest.java b/Mage/src/test/java/mage/cards/decks/importer/CodDeckImportTest.java index 77256b69cf8..9dd94dfdb88 100644 --- a/Mage/src/test/java/mage/cards/decks/importer/CodDeckImportTest.java +++ b/Mage/src/test/java/mage/cards/decks/importer/CodDeckImportTest.java @@ -2,31 +2,20 @@ package mage.cards.decks.importer; import static org.junit.Assert.assertEquals; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - import org.junit.Test; -import mage.cards.Card; -import mage.cards.CardImpl; -import mage.cards.decks.DeckCardInfo; import mage.cards.decks.DeckCardLists; -import mage.cards.repository.CardInfo; public class CodDeckImportTest { - private static final FakeCardLookup LOOKUP = new FakeCardLookup() - .addCard("Forest") - .addCard("Razorverge Thicket") - .addCard("Avacyn's Pilgrim") - .addCard("War Priest of Thune"); + private static final FakeCardLookup LOOKUP = new FakeCardLookup(false) + .addCard("Forest") + .addCard("Razorverge Thicket") + .addCard("Avacyn's Pilgrim") + .addCard("War Priest of Thune"); @Test - public void testImportCod() { + public void testImport() { CodDeckImporter importer = new CodDeckImporter() { @Override public CardLookup getCardLookup() { @@ -35,7 +24,7 @@ public class CodDeckImportTest { }; StringBuilder errors = new StringBuilder(); DeckCardLists deck = importer.importDeck( - "src/test/java/mage/cards/decks/importer/testdeck.cod", errors); + "src/test/java/mage/cards/decks/importer/samples/testdeck.cod", errors); assertEquals("Deck Name", deck.getName()); TestDeckChecker.checker() @@ -48,9 +37,4 @@ public class CodDeckImportTest { assertEquals("Could not find card: '@#$NOT A REAL CARD NAME@#$'\n", errors.toString()); } - private static void assertCardSame(String name, DeckCardInfo card) { - assertEquals(name, card.getCardName()); - assertEquals(1, card.getQuantity()); - } - } diff --git a/Mage/src/test/java/mage/cards/decks/importer/DecDeckImportTest.java b/Mage/src/test/java/mage/cards/decks/importer/DecDeckImportTest.java index 613a8f6c14d..65803c884b4 100644 --- a/Mage/src/test/java/mage/cards/decks/importer/DecDeckImportTest.java +++ b/Mage/src/test/java/mage/cards/decks/importer/DecDeckImportTest.java @@ -2,41 +2,13 @@ package mage.cards.decks.importer; import static org.junit.Assert.assertEquals; -import java.util.Collections; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - import org.junit.Test; -import mage.cards.decks.DeckCardInfo; import mage.cards.decks.DeckCardLists; -import mage.cards.repository.CardInfo; public class DecDeckImportTest { - private static final FakeCardLookup LOOKUP = new FakeCardLookup() - .addCard("Masticore") - .addCard("Metalworker") - .addCard("Phyrexian Colossus") - .addCard("Crumbling Sanctuary") - .addCard("Grim Monolith") - .addCard("Mishra's Helix") - .addCard("Phyrexian Processor") - .addCard("Tangle Wire") - .addCard("Thran Dynamo") - .addCard("Voltaic Key") - .addCard("Tinker") - .addCard("Brainstorm") - .addCard("Crystal Vein") - .addCard("Island") - .addCard("Rishadan Port") - .addCard("Saprazzan Skerry") - .addCard("Annul") - .addCard("Chill") - .addCard("Miscalculation") - .addCard("Mishra's Helix") - .addCard("Rising Waters"); + private static final FakeCardLookup LOOKUP = new FakeCardLookup(); @Test public void testImport() { @@ -48,7 +20,7 @@ public class DecDeckImportTest { } }; DeckCardLists deck = importer.importDeck( - "src/test/java/mage/cards/decks/importer/testdeck.dec", errors); + "src/test/java/mage/cards/decks/importer/samples/testdeck.dec", errors); TestDeckChecker.checker() .addMain("Masticore", 4) @@ -77,15 +49,4 @@ public class DecDeckImportTest { assertEquals("", errors.toString()); } - private static FakeCardLookup getFakeCardLookup() { - FakeCardLookup lookup = new FakeCardLookup() { - @Override - public Optional lookupCardInfo(String name) { - System.out.println(name); - return super.lookupCardInfo(name); - } - }; - return lookup; - } - } diff --git a/Mage/src/test/java/mage/cards/decks/importer/FakeCardLookup.java b/Mage/src/test/java/mage/cards/decks/importer/FakeCardLookup.java index 4db37db6d75..105f0707853 100644 --- a/Mage/src/test/java/mage/cards/decks/importer/FakeCardLookup.java +++ b/Mage/src/test/java/mage/cards/decks/importer/FakeCardLookup.java @@ -1,14 +1,26 @@ package mage.cards.decks.importer; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; +import mage.cards.repository.CardCriteria; import mage.cards.repository.CardInfo; public class FakeCardLookup extends CardLookup { private final Map lookup = new HashMap<>(); + private final boolean alwaysMatches; + + public FakeCardLookup() { + this(true); + } + + public FakeCardLookup(boolean alwaysMatches) { + this.alwaysMatches = alwaysMatches; + } public FakeCardLookup addCard(String cardName) { lookup.put(cardName, new CardInfo() {{ @@ -17,13 +29,26 @@ public class FakeCardLookup extends CardLookup { return this; } - @Override - public Optional lookupCardInfo(String name) { - CardInfo card = lookup.get(name); - if (card == null) { - System.out.println("Couldn't find: " + name); + public Optional lookupCardInfo(String cardName) { + CardInfo card = lookup.get(cardName); + if (card != null) { + return Optional.of(card); } - return Optional.ofNullable(card); + + if (alwaysMatches) { + return Optional.of(new CardInfo() {{ + name = cardName; + }}); + } + + return Optional.empty(); + } + + @Override + public List lookupCardInfo(CardCriteria criteria) { + return lookupCardInfo(criteria.getName()) + .map(Collections::singletonList) + .orElse(Collections.emptyList()); } } diff --git a/Mage/src/test/java/mage/cards/decks/importer/MwsDeckImportTest.java b/Mage/src/test/java/mage/cards/decks/importer/MwsDeckImportTest.java new file mode 100644 index 00000000000..cf364d81080 --- /dev/null +++ b/Mage/src/test/java/mage/cards/decks/importer/MwsDeckImportTest.java @@ -0,0 +1,57 @@ +package mage.cards.decks.importer; + +import static org.junit.Assert.assertEquals; + +import java.util.Collections; +import java.util.List; + +import org.junit.Test; + +import mage.cards.decks.DeckCardLists; +import mage.cards.repository.CardCriteria; +import mage.cards.repository.CardInfo; + +public class MwsDeckImportTest { + + private static final FakeCardLookup LOOKUP = new FakeCardLookup(); + + @Test + public void testImport() { + MWSDeckImporter importer = new MWSDeckImporter() { + @Override + public CardLookup getCardLookup() { + return LOOKUP; + } + }; + StringBuilder errors = new StringBuilder(); + DeckCardLists deck = importer.importDeck( + "src/test/java/mage/cards/decks/importer/samples/testdeck.mwDeck", errors); + + TestDeckChecker.checker() + .addMain("Mutavault", 4) + .addMain("Plains", 18) + .addMain("Daring Skyjek", 2) + .addMain("Azorius Arrester", 4) + .addMain("Banisher Priest", 4) + .addMain("Boros Elite", 4) + .addMain("Dryad Militant", 4) + .addMain("Imposing Sovereign", 4) + .addMain("Precinct Captain", 4) + .addMain("Soldier of the Pantheon", 4) + .addMain("Spear of Heliod", 3) + .addMain("Rootborn Defenses", 1) + .addMain("Brave the Elements", 4) + + .addSide("Wear/Tear", 1) + .addSide("Glare of Heresy", 2) + .addSide("Fiendslayer Paladin", 3) + .addSide("Riot Control", 3) + .addSide("Ajani, Caller of the Pride", 3) + .addSide("Rootborn Defenses", 3) + + .verify(deck, 60, 15); + + assertEquals("", errors.toString()); + } + +} diff --git a/Mage/src/test/java/mage/cards/decks/importer/O8dDeckImportTest.java b/Mage/src/test/java/mage/cards/decks/importer/O8dDeckImportTest.java new file mode 100644 index 00000000000..4eade3abd73 --- /dev/null +++ b/Mage/src/test/java/mage/cards/decks/importer/O8dDeckImportTest.java @@ -0,0 +1,33 @@ +package mage.cards.decks.importer; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import mage.cards.decks.DeckCardLists; + +public class O8dDeckImportTest { + + private static final FakeCardLookup LOOKUP = new FakeCardLookup(); + + @Test + public void testImport() { + O8dDeckImporter importer = new O8dDeckImporter() { + @Override + public CardLookup getCardLookup() { + return LOOKUP; + } + }; + StringBuilder errors = new StringBuilder(); + DeckCardLists deck = importer.importDeck( + "src/test/java/mage/cards/decks/importer/samples/testdeck.o8d", errors); + + TestDeckChecker.checker() + .addMain("Forest", 1) + .addSide("Island", 2) + .verify(deck, 1, 2); + + assertEquals("", errors.toString()); + } + +} diff --git a/Mage/src/test/java/mage/cards/decks/importer/testdeck.cod b/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.cod similarity index 100% rename from Mage/src/test/java/mage/cards/decks/importer/testdeck.cod rename to Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.cod diff --git a/Mage/src/test/java/mage/cards/decks/importer/testdeck.dec b/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.dec similarity index 100% rename from Mage/src/test/java/mage/cards/decks/importer/testdeck.dec rename to Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.dec diff --git a/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.mwDeck b/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.mwDeck new file mode 100644 index 00000000000..527f58f60d4 --- /dev/null +++ b/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.mwDeck @@ -0,0 +1,23 @@ +// Deck file for Magic Workstation (http://www.magicworkstation.com) +// NAME : WW Human +// CREATOR : meltiin (magic-ville.com) +// FORMAT : Standard + 4 [M14] Mutavault + 18 [UNH] Plains + 2 [GTC] Daring Skyjek + 4 [RTR] Azorius Arrester + 4 [M14] Banisher Priest + 4 [GTC] Boros Elite + 4 [RTR] Dryad Militant + 4 [M14] Imposing Sovereign + 4 [RTR] Precinct Captain + 4 [THS] Soldier of the Pantheon + 3 [THS] Spear of Heliod + 1 [RTR] Rootborn Defenses + 4 [M14] Brave the Elements +SB: 1 [DGM] Wear/Tear +SB: 2 [THS] Glare of Heresy +SB: 3 [M14] Fiendslayer Paladin +SB: 3 [DGM] Riot Control +SB: 3 [M14] Ajani, Caller of the Pride +SB: 3 [RTR] Rootborn Defenses \ No newline at end of file diff --git a/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.o8d b/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.o8d new file mode 100644 index 00000000000..556671d0f4a --- /dev/null +++ b/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.o8d @@ -0,0 +1,8 @@ + +
+ Forest +
+
+ Island +
+