From 1859fd1c4f4b53d4426f4161d3709b9fd2276747 Mon Sep 17 00:00:00 2001 From: drdev Date: Thu, 16 Oct 2014 18:22:48 +0000 Subject: [PATCH] Prevent randomly selecting version of a card that doesn't have art available when there's another version where art is available --- .gitattributes | 4 +- forge-core/src/main/java/forge/ImageKeys.java | 27 +- .../src/main/java/forge/StaticData.java | 8 +- .../src/main/java/forge/card/CardDb.java | 30 +- .../src/main/java/forge/item/PaperCard.java | 9 + .../src/main/java/forge/util/Base64Coder.java | 744 +++++++++--------- .../src/main/java/forge/util}/ImageUtil.java | 24 +- .../src/main/java/forge/ImageCache.java | 6 +- .../java/forge/gui/ImportSourceAnalyzer.java | 2 +- .../java/forge/itemmanager/CardManager.java | 17 +- .../java/forge/itemmanager/ItemManager.java | 8 +- .../src/forge/assets/FSkinImage.java | 1 + .../src/forge/assets/ImageCache.java | 1 + .../forge/deck/io/DeckHtmlSerializer.java | 2 +- .../forge/download/GuiDownloadPicturesLQ.java | 2 +- .../download/GuiDownloadSetPicturesLQ.java | 2 +- .../src/main/java/forge/model/FModel.java | 3 +- 17 files changed, 474 insertions(+), 416 deletions(-) rename {forge-gui => forge-core}/src/main/java/forge/util/Base64Coder.java (96%) rename {forge-gui/src/main/java/forge/assets => forge-core/src/main/java/forge/util}/ImageUtil.java (79%) diff --git a/.gitattributes b/.gitattributes index 07923f0ccaa..ca9bfe929cc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -222,6 +222,7 @@ forge-core/src/main/java/forge/item/SealedProduct.java -text forge-core/src/main/java/forge/item/TournamentPack.java -text forge-core/src/main/java/forge/item/package-info.java -text forge-core/src/main/java/forge/util/Aggregates.java -text +forge-core/src/main/java/forge/util/Base64Coder.java -text forge-core/src/main/java/forge/util/BinaryUtil.java -text forge-core/src/main/java/forge/util/BuildInfo.java -text forge-core/src/main/java/forge/util/CollectionSuppliers.java -text @@ -235,6 +236,7 @@ forge-core/src/main/java/forge/util/IHasName.java -text forge-core/src/main/java/forge/util/IItemReader.java -text forge-core/src/main/java/forge/util/IItemSerializer.java -text forge-core/src/main/java/forge/util/ITriggerEvent.java -text +forge-core/src/main/java/forge/util/ImageUtil.java -text forge-core/src/main/java/forge/util/ItemPool.java -text forge-core/src/main/java/forge/util/ItemPoolSorter.java -text forge-core/src/main/java/forge/util/Lang.java -text @@ -16958,7 +16960,6 @@ forge-gui/src/main/java/forge/achievement/TotalMatchWins.java -text forge-gui/src/main/java/forge/achievement/VariantWins.java -text forge-gui/src/main/java/forge/assets/FSkinProp.java -text forge-gui/src/main/java/forge/assets/ISkinImage.java -text -forge-gui/src/main/java/forge/assets/ImageUtil.java -text forge-gui/src/main/java/forge/card/CardDetailUtil.java -text forge-gui/src/main/java/forge/card/CardPreferences.java -text forge-gui/src/main/java/forge/card/CardReaderExperiments.java -text @@ -17142,7 +17143,6 @@ forge-gui/src/main/java/forge/sound/MusicPlaylist.java -text forge-gui/src/main/java/forge/sound/NoSoundClip.java -text forge-gui/src/main/java/forge/sound/SoundEffectType.java -text forge-gui/src/main/java/forge/sound/SoundSystem.java -text -forge-gui/src/main/java/forge/util/Base64Coder.java svneol=native#text/plain forge-gui/src/main/java/forge/util/Callback.java -text forge-gui/src/main/java/forge/util/Evaluator.java -text forge-gui/src/main/java/forge/util/GuiDisplayUtil.java -text diff --git a/forge-core/src/main/java/forge/ImageKeys.java b/forge-core/src/main/java/forge/ImageKeys.java index 86a7192c4b9..1b710df84e3 100644 --- a/forge-core/src/main/java/forge/ImageKeys.java +++ b/forge-core/src/main/java/forge/ImageKeys.java @@ -1,11 +1,13 @@ package forge; import java.io.File; +import java.util.Map; import org.apache.commons.lang3.StringUtils; import forge.card.CardDb; import forge.item.*; +import forge.util.ImageUtil; public class ImageKeys { public static final String CARD_PREFIX = "c:"; @@ -24,10 +26,12 @@ public class ImageKeys { private static String CACHE_CARD_PICS_DIR, CACHE_TOKEN_PICS_DIR, CACHE_ICON_PICS_DIR, CACHE_BOOSTER_PICS_DIR, CACHE_FATPACK_PICS_DIR, CACHE_BOOSTERBOX_PICS_DIR, CACHE_PRECON_PICS_DIR, CACHE_TOURNAMENTPACK_PICS_DIR; + private static Map CACHE_CARD_PICS_SUBDIR; - public static void initializeDirs(String cards, String tokens, String icons, String boosters, + public static void initializeDirs(String cards, Map cardsSub, String tokens, String icons, String boosters, String fatPacks, String boosterBoxes, String precons, String tournamentPacks) { CACHE_CARD_PICS_DIR = cards; + CACHE_CARD_PICS_SUBDIR = cardsSub; CACHE_TOKEN_PICS_DIR = tokens; CACHE_ICON_PICS_DIR = icons; CACHE_BOOSTER_PICS_DIR = boosters; @@ -115,12 +119,12 @@ public class ImageKeys { File file = findFile(dir, filename); // some S00 cards are really part of 6ED - /*if (file == null) { //TODO: Uncomment this - String s2kAlias = ImageUtil.getSetFolder("S00"); + if (file == null) { + String s2kAlias = getSetFolder("S00"); if (filename.startsWith(s2kAlias)) { - file = findFile(dir, filename.replace(s2kAlias, ImageUtil.getSetFolder("6ED"))); + file = findFile(dir, filename.replace(s2kAlias, getSetFolder("6ED"))); } - }*/ + } // try without set prefix String setlessFilename = null; @@ -141,6 +145,12 @@ public class ImageKeys { return file; } + public static String getSetFolder(String edition) { + return !CACHE_CARD_PICS_SUBDIR.containsKey(edition) + ? StaticData.instance().getEditions().getCode2ByCode(edition) // by default 2-letter codes from MWS are used + : CACHE_CARD_PICS_SUBDIR.get(edition); // may use custom paths though + } + private static File findFile(String dir, String filename) { for (String ext : FILE_EXTENSIONS) { File file = new File(dir, filename + ext); @@ -151,8 +161,9 @@ public class ImageKeys { return null; } - //shortcut for determine if a card image exists - public static boolean doesCardImageExist(String filename) { - return findFile(CACHE_CARD_PICS_DIR, filename) != null; + //shortcut for determining if a card image exists for a given card + //should only be called from PaperCard.hasImage() + public static boolean hasImage(PaperCard pc) { + return findFile(CACHE_CARD_PICS_DIR, ImageUtil.getImageKey(pc, false, true)) != null; } } diff --git a/forge-core/src/main/java/forge/StaticData.java b/forge-core/src/main/java/forge/StaticData.java index 395c0949dde..6509ede118c 100644 --- a/forge-core/src/main/java/forge/StaticData.java +++ b/forge-core/src/main/java/forge/StaticData.java @@ -52,8 +52,12 @@ public class StaticData { } } - commonCards = new CardDb(regularCards, editions, false, false); - variantCards = new CardDb(variantsCards, editions, false, false); + commonCards = new CardDb(regularCards, editions); + variantCards = new CardDb(variantsCards, editions); + + //muse initialize after establish field values for the sake of card image logic + commonCards.initialize(false, false); + variantCards.initialize(false, false); this.boosters = new StorageBase("Boosters", editions.getBoosterGenerator()); this.specialBoosters = new StorageBase("Special boosters", new SealedProduct.Template.Reader(new File(blockDataFolder, "boosters-special.txt"))); diff --git a/forge-core/src/main/java/forge/card/CardDb.java b/forge-core/src/main/java/forge/card/CardDb.java index 3a24af05a7e..e9f0f2aea1f 100644 --- a/forge-core/src/main/java/forge/card/CardDb.java +++ b/forge-core/src/main/java/forge/card/CardDb.java @@ -110,10 +110,13 @@ public final class CardDb implements ICardDatabase { return new CardRequest(cardName, setName, artIndex, isFoil); } } - - public CardDb(Map rules, CardEdition.Collection editions0, boolean logMissingPerEdition, boolean logMissingSummary) { + + public CardDb(Map rules, CardEdition.Collection editions0) { this.rulesByName = rules; this.editions = editions0; + } + + public void initialize(boolean logMissingPerEdition, boolean logMissingSummary) { Set allMissingCards = new LinkedHashSet(); List missingCards = new ArrayList(); for (CardEdition e : editions.getOrderedEditions()) { @@ -176,11 +179,24 @@ public final class CardDb implements ICardDatabase { uniqueCardsByName.clear(); allCards.clear(); for (Entry> kv : allCardsByName.asMap().entrySet()) { - uniqueCardsByName.put(kv.getKey(), Iterables.getFirst(kv.getValue(), null)); + uniqueCardsByName.put(kv.getKey(), getFirstWithImage(kv.getValue())); allCards.addAll(kv.getValue()); } } + private PaperCard getFirstWithImage(Collection cards) { + //NOTE: this is written this way to avoid checking final card in list + Iterator iterator = cards.iterator(); + PaperCard pc = iterator.next(); + while (iterator.hasNext()) { + if (pc.hasImage()) { + return pc; + } + pc = iterator.next(); + } + return pc; + } + public boolean setPreferredArt(String cardName, String preferredArt) { CardRequest request = CardRequest.fromString(cardName + NameSetSeparator + preferredArt); PaperCard pc = tryGetCard(request); @@ -236,7 +252,7 @@ public final class CardDb implements ICardDatabase { if (request.artIndex <= 0) { // this stands for 'random art' Collection candidates; if (reqEdition == null) { - candidates = cards; + candidates = new ArrayList(cards); } else { candidates = new ArrayList(); @@ -250,6 +266,12 @@ public final class CardDb implements ICardDatabase { return null; } result = Aggregates.random(candidates); + + //if card image doesn't exist for chosen candidate, try another one if possible + while (candidates.size() > 1 && !result.hasImage()) { + candidates.remove(result); + result = Aggregates.random(candidates); + } } else { for (PaperCard pc : cards) { diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java index f954e5aaab5..22f353f59a3 100644 --- a/forge-core/src/main/java/forge/item/PaperCard.java +++ b/forge-core/src/main/java/forge/item/PaperCard.java @@ -19,6 +19,7 @@ package forge.item; import com.google.common.base.Function; +import forge.ImageKeys; import forge.card.CardRarity; import forge.card.CardRules; @@ -39,6 +40,7 @@ public final class PaperCard implements Comparable, InventoryItemFro private final String edition; private final int artIndex; private final boolean foil; + private Boolean hasImage; // Calculated fields are below: private final transient CardRarity rarity; // rarity is given in ctor when set is assigned @@ -88,6 +90,13 @@ public final class PaperCard implements Comparable, InventoryItemFro return "Card"; } + public boolean hasImage() { + if (hasImage == null) { //cache value since it's not free to calculate + hasImage = ImageKeys.hasImage(this); + } + return hasImage; + } + /** * Lambda to get rules for selects from list of printed cards. */ diff --git a/forge-gui/src/main/java/forge/util/Base64Coder.java b/forge-core/src/main/java/forge/util/Base64Coder.java similarity index 96% rename from forge-gui/src/main/java/forge/util/Base64Coder.java rename to forge-core/src/main/java/forge/util/Base64Coder.java index 00e208413ae..e641ffef940 100644 --- a/forge-gui/src/main/java/forge/util/Base64Coder.java +++ b/forge-core/src/main/java/forge/util/Base64Coder.java @@ -1,372 +1,372 @@ -/* - * 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 . - */ -// www.source-code.biz, www.inventec.ch/chdh -// -// This module is multi-licensed and may be used under the terms -// of any of the following licenses: -// -// EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal -// LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html -// GPL, GNU General Public License, V2 or later, http://www.gnu.org/licenses/gpl.html -// AL, Apache License, V2.0 or later, http://www.apache.org/licenses -// BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php -// -// Please contact the author if you need another license. -// This module is provided "as is", without warranties of any kind. - -package forge.util; - -/** - * A Base64 encoder/decoder. - *

- *

- * This class is used to encode and decode data in Base64 format as described in - * RFC 1521. - *

- *

- * Project home page: www. - * source-code.biz/base64coder/java
- * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
- * Multi-licensed: EPL / LGPL / GPL / AL / BSD. - * - * @author Forge - * @version $Id$ - */ -public final class Base64Coder { - - // The line separator string of the operating system. - /** - * Constant. - * systemLineSeparator="System.getProperty(line.separator)" - */ - private static final String SYSTEM_LINE_SEPARATOR = System.getProperty("line.separator"); - - // Mapping table from 6-bit nibbles to Base64 characters. - /** Constant map1=new char[64]. */ - private static char[] map1 = new char[64]; - - static { - int i = 0; - for (char c = 'A'; c <= 'Z'; c++) { - Base64Coder.map1[i++] = c; - } - for (char c = 'a'; c <= 'z'; c++) { - Base64Coder.map1[i++] = c; - } - for (char c = '0'; c <= '9'; c++) { - Base64Coder.map1[i++] = c; - } - Base64Coder.map1[i++] = '+'; - Base64Coder.map1[i++] = '/'; - } - - // Mapping table from Base64 characters to 6-bit nibbles. - /** Constant map2=new byte[128]. */ - private static byte[] map2 = new byte[128]; - - static { - for (int i = 0; i < Base64Coder.map2.length; i++) { - Base64Coder.map2[i] = -1; - } - for (int i = 0; i < 64; i++) { - Base64Coder.map2[Base64Coder.map1[i]] = (byte) i; - } - } - - /** - * Encodes a string into Base64 format. No blanks or line breaks are - * inserted. - * - * @param s - * A String to be encoded. - * @return A String containing the Base64 encoded data. - */ - public static String encodeString(final String s) { - return new String(Base64Coder.encode(s.getBytes())); - } - - /** - *

- * encodeString. - *

- * - * @param s - * a {@link java.lang.String} object. - * @param noPad - * a boolean. - * @return a {@link java.lang.String} object. - */ - public static String encodeString(final String s, final boolean noPad) { - String t = new String(Base64Coder.encode(s.getBytes())); - - if (noPad) { - t = t.replace("=", ""); - } - - return t; - } - - /** - * Encodes a byte array into Base 64 format and breaks the output into lines - * of 76 characters. This method is compatible with - * sun.misc.BASE64Encoder.encodeBuffer(byte[]). - * - * @param in - * An array containing the data bytes to be encoded. - * @return A String containing the Base64 encoded data, broken into lines. - */ - public static String encodeLines(final byte[] in) { - return Base64Coder.encodeLines(in, 0, in.length, 76, Base64Coder.SYSTEM_LINE_SEPARATOR); - } - - /** - * Encodes a byte array into Base 64 format and breaks the output into - * lines. - * - * @param in - * An array containing the data bytes to be encoded. - * @param iOff - * Offset of the first byte in in to be processed. - * @param iLen - * Number of bytes to be processed in in, starting - * at iOff. - * @param lineLen - * Line length for the output data. Should be a multiple of 4. - * @param lineSeparator - * The line separator to be used to separate the output lines. - * @return A String containing the Base64 encoded data, broken into lines. - */ - public static String encodeLines(final byte[] in, final int iOff, final int iLen, final int lineLen, - final String lineSeparator) { - final int blockLen = (lineLen * 3) / 4; - if (blockLen <= 0) { - throw new IllegalArgumentException(); - } - final int lines = ((iLen + blockLen) - 1) / blockLen; - final int bufLen = (((iLen + 2) / 3) * 4) + (lines * lineSeparator.length()); - final StringBuilder buf = new StringBuilder(bufLen); - int ip = 0; - while (ip < iLen) { - final int l = Math.min(iLen - ip, blockLen); - buf.append(Base64Coder.encode(in, iOff + ip, l)); - buf.append(lineSeparator); - ip += l; - } - return buf.toString(); - } - - /** - * Encodes a byte array into Base64 format. No blanks or line breaks are - * inserted in the output. - * - * @param in - * An array containing the data bytes to be encoded. - * @return A character array containing the Base64 encoded data. - */ - public static char[] encode(final byte[] in) { - return Base64Coder.encode(in, 0, in.length); - } - - /** - * Encodes a byte array into Base64 format. No blanks or line breaks are - * inserted in the output. - * - * @param in - * An array containing the data bytes to be encoded. - * @param iLen - * Number of bytes to process in in. - * @return A character array containing the Base64 encoded data. - */ - public static char[] encode(final byte[] in, final int iLen) { - return Base64Coder.encode(in, 0, iLen); - } - - /** - * Encodes a byte array into Base64 format. No blanks or line breaks are - * inserted in the output. - * - * @param in - * An array containing the data bytes to be encoded. - * @param iOff - * Offset of the first byte in in to be processed. - * @param iLen - * Number of bytes to process in in, starting at - * iOff. - * @return A character array containing the Base64 encoded data. - */ - public static char[] encode(final byte[] in, final int iOff, final int iLen) { - final int oDataLen = ((iLen * 4) + 2) / 3; // output length without - // padding - final int oLen = ((iLen + 2) / 3) * 4; // output length including - // padding - final char[] out = new char[oLen]; - int ip = iOff; - final int iEnd = iOff + iLen; - int op = 0; - while (ip < iEnd) { - final int i0 = in[ip++] & 0xff; - final int i1 = ip < iEnd ? in[ip++] & 0xff : 0; - final int i2 = ip < iEnd ? in[ip++] & 0xff : 0; - final int o0 = i0 >>> 2; - final int o1 = ((i0 & 3) << 4) | (i1 >>> 4); - final int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6); - final int o3 = i2 & 0x3F; - out[op++] = Base64Coder.map1[o0]; - out[op++] = Base64Coder.map1[o1]; - out[op] = op < oDataLen ? Base64Coder.map1[o2] : '='; - op++; - out[op] = op < oDataLen ? Base64Coder.map1[o3] : '='; - op++; - } - return out; - } - - /** - * Decodes a string from Base64 format. No blanks or line breaks are allowed - * within the Base64 encoded input data. - * - * @param s - * A Base64 String to be decoded. - * @return A String containing the decoded data. - * - * If the input is not valid Base64 encoded data. - */ - public static String decodeString(final String s) { - return new String(Base64Coder.decode(s)); - } - - /** - * Decodes a byte array from Base64 format and ignores line separators, tabs - * and blanks. CR, LF, Tab and Space characters are ignored in the input - * data. This method is compatible with - * sun.misc.BASE64Decoder.decodeBuffer(String). - * - * @param s - * A Base64 String to be decoded. - * @return An array containing the decoded data bytes. - * - * If the input is not valid Base64 encoded data. - */ - public static byte[] decodeLines(final String s) { - final char[] buf = new char[s.length()]; - int p = 0; - for (int ip = 0; ip < s.length(); ip++) { - final char c = s.charAt(ip); - if ((c != ' ') && (c != '\r') && (c != '\n') && (c != '\t')) { - buf[p++] = c; - } - } - return Base64Coder.decode(buf, 0, p); - } - - /** - * Decodes a byte array from Base64 format. No blanks or line breaks are - * allowed within the Base64 encoded input data. - * - * @param s - * A Base64 String to be decoded. - * @return An array containing the decoded data bytes. - * - * If the input is not valid Base64 encoded data. - */ - public static byte[] decode(final String s) { - return Base64Coder.decode(s.toCharArray()); - } - - /** - * Decodes a byte array from Base64 format. No blanks or line breaks are - * allowed within the Base64 encoded input data. - * - * @param in - * A character array containing the Base64 encoded data. - * @return An array containing the decoded data bytes. - * - * If the input is not valid Base64 encoded data. - */ - public static byte[] decode(final char[] in) { - return Base64Coder.decode(in, 0, in.length); - } - - /** - * Decodes a byte array from Base64 format. No blanks or line breaks are - * allowed within the Base64 encoded input data. - * - * @param in - * A character array containing the Base64 encoded data. - * @param iOff - * Offset of the first character in in to be - * processed. - * @param iLen - * Number of characters to process in in, starting - * at iOff. - * @return An array containing the decoded data bytes. - * - * If the input is not valid Base64 encoded data. - */ - public static byte[] decode(final char[] in, final int iOff, int iLen) { - if ((iLen % 4) != 0) { - throw new IllegalArgumentException("Length of Base64 encoded input string is not a multiple of 4."); - } - while ((iLen > 0) && (in[(iOff + iLen) - 1] == '=')) { - iLen--; - } - final int oLen = (iLen * 3) / 4; - final byte[] out = new byte[oLen]; - int ip = iOff; - final int iEnd = iOff + iLen; - int op = 0; - while (ip < iEnd) { - final int i0 = in[ip++]; - final int i1 = in[ip++]; - final int i2 = ip < iEnd ? in[ip++] : 'A'; - final int i3 = ip < iEnd ? in[ip++] : 'A'; - if ((i0 > 127) || (i1 > 127) || (i2 > 127) || (i3 > 127)) { - throw new IllegalArgumentException("Illegal character in Base64 encoded data."); - } - final int b0 = Base64Coder.map2[i0]; - final int b1 = Base64Coder.map2[i1]; - final int b2 = Base64Coder.map2[i2]; - final int b3 = Base64Coder.map2[i3]; - if ((b0 < 0) || (b1 < 0) || (b2 < 0) || (b3 < 0)) { - throw new IllegalArgumentException("Illegal character in Base64 encoded data."); - } - final int o0 = (b0 << 2) | (b1 >>> 4); - final int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2); - final int o2 = ((b2 & 3) << 6) | b3; - out[op++] = (byte) o0; - if (op < oLen) { - out[op++] = (byte) o1; - } - if (op < oLen) { - out[op++] = (byte) o2; - } - } - return out; - } - - // Dummy constructor. - /** - *

- * Constructor for Base64Coder. - *

- */ - private Base64Coder() { - } - -} // end class Base64Coder +/* + * 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 . + */ +// www.source-code.biz, www.inventec.ch/chdh +// +// This module is multi-licensed and may be used under the terms +// of any of the following licenses: +// +// EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal +// LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html +// GPL, GNU General Public License, V2 or later, http://www.gnu.org/licenses/gpl.html +// AL, Apache License, V2.0 or later, http://www.apache.org/licenses +// BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php +// +// Please contact the author if you need another license. +// This module is provided "as is", without warranties of any kind. + +package forge.util; + +/** + * A Base64 encoder/decoder. + *

+ *

+ * This class is used to encode and decode data in Base64 format as described in + * RFC 1521. + *

+ *

+ * Project home page: www. + * source-code.biz/base64coder/java
+ * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
+ * Multi-licensed: EPL / LGPL / GPL / AL / BSD. + * + * @author Forge + * @version $Id: Base64Coder.java 13541 2012-01-26 21:20:51Z Max mtg $ + */ +public final class Base64Coder { + + // The line separator string of the operating system. + /** + * Constant. + * systemLineSeparator="System.getProperty(line.separator)" + */ + private static final String SYSTEM_LINE_SEPARATOR = System.getProperty("line.separator"); + + // Mapping table from 6-bit nibbles to Base64 characters. + /** Constant map1=new char[64]. */ + private static char[] map1 = new char[64]; + + static { + int i = 0; + for (char c = 'A'; c <= 'Z'; c++) { + Base64Coder.map1[i++] = c; + } + for (char c = 'a'; c <= 'z'; c++) { + Base64Coder.map1[i++] = c; + } + for (char c = '0'; c <= '9'; c++) { + Base64Coder.map1[i++] = c; + } + Base64Coder.map1[i++] = '+'; + Base64Coder.map1[i++] = '/'; + } + + // Mapping table from Base64 characters to 6-bit nibbles. + /** Constant map2=new byte[128]. */ + private static byte[] map2 = new byte[128]; + + static { + for (int i = 0; i < Base64Coder.map2.length; i++) { + Base64Coder.map2[i] = -1; + } + for (int i = 0; i < 64; i++) { + Base64Coder.map2[Base64Coder.map1[i]] = (byte) i; + } + } + + /** + * Encodes a string into Base64 format. No blanks or line breaks are + * inserted. + * + * @param s + * A String to be encoded. + * @return A String containing the Base64 encoded data. + */ + public static String encodeString(final String s) { + return new String(Base64Coder.encode(s.getBytes())); + } + + /** + *

+ * encodeString. + *

+ * + * @param s + * a {@link java.lang.String} object. + * @param noPad + * a boolean. + * @return a {@link java.lang.String} object. + */ + public static String encodeString(final String s, final boolean noPad) { + String t = new String(Base64Coder.encode(s.getBytes())); + + if (noPad) { + t = t.replace("=", ""); + } + + return t; + } + + /** + * Encodes a byte array into Base 64 format and breaks the output into lines + * of 76 characters. This method is compatible with + * sun.misc.BASE64Encoder.encodeBuffer(byte[]). + * + * @param in + * An array containing the data bytes to be encoded. + * @return A String containing the Base64 encoded data, broken into lines. + */ + public static String encodeLines(final byte[] in) { + return Base64Coder.encodeLines(in, 0, in.length, 76, Base64Coder.SYSTEM_LINE_SEPARATOR); + } + + /** + * Encodes a byte array into Base 64 format and breaks the output into + * lines. + * + * @param in + * An array containing the data bytes to be encoded. + * @param iOff + * Offset of the first byte in in to be processed. + * @param iLen + * Number of bytes to be processed in in, starting + * at iOff. + * @param lineLen + * Line length for the output data. Should be a multiple of 4. + * @param lineSeparator + * The line separator to be used to separate the output lines. + * @return A String containing the Base64 encoded data, broken into lines. + */ + public static String encodeLines(final byte[] in, final int iOff, final int iLen, final int lineLen, + final String lineSeparator) { + final int blockLen = (lineLen * 3) / 4; + if (blockLen <= 0) { + throw new IllegalArgumentException(); + } + final int lines = ((iLen + blockLen) - 1) / blockLen; + final int bufLen = (((iLen + 2) / 3) * 4) + (lines * lineSeparator.length()); + final StringBuilder buf = new StringBuilder(bufLen); + int ip = 0; + while (ip < iLen) { + final int l = Math.min(iLen - ip, blockLen); + buf.append(Base64Coder.encode(in, iOff + ip, l)); + buf.append(lineSeparator); + ip += l; + } + return buf.toString(); + } + + /** + * Encodes a byte array into Base64 format. No blanks or line breaks are + * inserted in the output. + * + * @param in + * An array containing the data bytes to be encoded. + * @return A character array containing the Base64 encoded data. + */ + public static char[] encode(final byte[] in) { + return Base64Coder.encode(in, 0, in.length); + } + + /** + * Encodes a byte array into Base64 format. No blanks or line breaks are + * inserted in the output. + * + * @param in + * An array containing the data bytes to be encoded. + * @param iLen + * Number of bytes to process in in. + * @return A character array containing the Base64 encoded data. + */ + public static char[] encode(final byte[] in, final int iLen) { + return Base64Coder.encode(in, 0, iLen); + } + + /** + * Encodes a byte array into Base64 format. No blanks or line breaks are + * inserted in the output. + * + * @param in + * An array containing the data bytes to be encoded. + * @param iOff + * Offset of the first byte in in to be processed. + * @param iLen + * Number of bytes to process in in, starting at + * iOff. + * @return A character array containing the Base64 encoded data. + */ + public static char[] encode(final byte[] in, final int iOff, final int iLen) { + final int oDataLen = ((iLen * 4) + 2) / 3; // output length without + // padding + final int oLen = ((iLen + 2) / 3) * 4; // output length including + // padding + final char[] out = new char[oLen]; + int ip = iOff; + final int iEnd = iOff + iLen; + int op = 0; + while (ip < iEnd) { + final int i0 = in[ip++] & 0xff; + final int i1 = ip < iEnd ? in[ip++] & 0xff : 0; + final int i2 = ip < iEnd ? in[ip++] & 0xff : 0; + final int o0 = i0 >>> 2; + final int o1 = ((i0 & 3) << 4) | (i1 >>> 4); + final int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6); + final int o3 = i2 & 0x3F; + out[op++] = Base64Coder.map1[o0]; + out[op++] = Base64Coder.map1[o1]; + out[op] = op < oDataLen ? Base64Coder.map1[o2] : '='; + op++; + out[op] = op < oDataLen ? Base64Coder.map1[o3] : '='; + op++; + } + return out; + } + + /** + * Decodes a string from Base64 format. No blanks or line breaks are allowed + * within the Base64 encoded input data. + * + * @param s + * A Base64 String to be decoded. + * @return A String containing the decoded data. + * + * If the input is not valid Base64 encoded data. + */ + public static String decodeString(final String s) { + return new String(Base64Coder.decode(s)); + } + + /** + * Decodes a byte array from Base64 format and ignores line separators, tabs + * and blanks. CR, LF, Tab and Space characters are ignored in the input + * data. This method is compatible with + * sun.misc.BASE64Decoder.decodeBuffer(String). + * + * @param s + * A Base64 String to be decoded. + * @return An array containing the decoded data bytes. + * + * If the input is not valid Base64 encoded data. + */ + public static byte[] decodeLines(final String s) { + final char[] buf = new char[s.length()]; + int p = 0; + for (int ip = 0; ip < s.length(); ip++) { + final char c = s.charAt(ip); + if ((c != ' ') && (c != '\r') && (c != '\n') && (c != '\t')) { + buf[p++] = c; + } + } + return Base64Coder.decode(buf, 0, p); + } + + /** + * Decodes a byte array from Base64 format. No blanks or line breaks are + * allowed within the Base64 encoded input data. + * + * @param s + * A Base64 String to be decoded. + * @return An array containing the decoded data bytes. + * + * If the input is not valid Base64 encoded data. + */ + public static byte[] decode(final String s) { + return Base64Coder.decode(s.toCharArray()); + } + + /** + * Decodes a byte array from Base64 format. No blanks or line breaks are + * allowed within the Base64 encoded input data. + * + * @param in + * A character array containing the Base64 encoded data. + * @return An array containing the decoded data bytes. + * + * If the input is not valid Base64 encoded data. + */ + public static byte[] decode(final char[] in) { + return Base64Coder.decode(in, 0, in.length); + } + + /** + * Decodes a byte array from Base64 format. No blanks or line breaks are + * allowed within the Base64 encoded input data. + * + * @param in + * A character array containing the Base64 encoded data. + * @param iOff + * Offset of the first character in in to be + * processed. + * @param iLen + * Number of characters to process in in, starting + * at iOff. + * @return An array containing the decoded data bytes. + * + * If the input is not valid Base64 encoded data. + */ + public static byte[] decode(final char[] in, final int iOff, int iLen) { + if ((iLen % 4) != 0) { + throw new IllegalArgumentException("Length of Base64 encoded input string is not a multiple of 4."); + } + while ((iLen > 0) && (in[(iOff + iLen) - 1] == '=')) { + iLen--; + } + final int oLen = (iLen * 3) / 4; + final byte[] out = new byte[oLen]; + int ip = iOff; + final int iEnd = iOff + iLen; + int op = 0; + while (ip < iEnd) { + final int i0 = in[ip++]; + final int i1 = in[ip++]; + final int i2 = ip < iEnd ? in[ip++] : 'A'; + final int i3 = ip < iEnd ? in[ip++] : 'A'; + if ((i0 > 127) || (i1 > 127) || (i2 > 127) || (i3 > 127)) { + throw new IllegalArgumentException("Illegal character in Base64 encoded data."); + } + final int b0 = Base64Coder.map2[i0]; + final int b1 = Base64Coder.map2[i1]; + final int b2 = Base64Coder.map2[i2]; + final int b3 = Base64Coder.map2[i3]; + if ((b0 < 0) || (b1 < 0) || (b2 < 0) || (b3 < 0)) { + throw new IllegalArgumentException("Illegal character in Base64 encoded data."); + } + final int o0 = (b0 << 2) | (b1 >>> 4); + final int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2); + final int o2 = ((b2 & 3) << 6) | b3; + out[op++] = (byte) o0; + if (op < oLen) { + out[op++] = (byte) o1; + } + if (op < oLen) { + out[op++] = (byte) o2; + } + } + return out; + } + + // Dummy constructor. + /** + *

+ * Constructor for Base64Coder. + *

+ */ + private Base64Coder() { + } + +} // end class Base64Coder diff --git a/forge-gui/src/main/java/forge/assets/ImageUtil.java b/forge-core/src/main/java/forge/util/ImageUtil.java similarity index 79% rename from forge-gui/src/main/java/forge/assets/ImageUtil.java rename to forge-core/src/main/java/forge/util/ImageUtil.java index 164b182579e..f26846bc64a 100644 --- a/forge-gui/src/main/java/forge/assets/ImageUtil.java +++ b/forge-core/src/main/java/forge/util/ImageUtil.java @@ -1,15 +1,13 @@ -package forge.assets; +package forge.util; import org.apache.commons.lang3.StringUtils; +import forge.ImageKeys; import forge.StaticData; import forge.card.CardDb; import forge.card.CardRules; import forge.card.CardSplitType; import forge.item.PaperCard; -import forge.model.FModel; -import forge.properties.ForgeConstants; -import forge.properties.ForgePreferences.FPref; import forge.util.Base64Coder; public class ImageUtil { @@ -32,9 +30,9 @@ public class ImageUtil { public static String getImageRelativePath(PaperCard cp, boolean backFace, boolean includeSet, boolean isDownloadUrl) { final String nameToUse = cp == null ? null : getNameToUse(cp, backFace); - if ( null == nameToUse ) + if (nameToUse == null) { return null; - + } StringBuilder s = new StringBuilder(); CardRules card = cp.getRules(); @@ -43,7 +41,7 @@ public class ImageUtil { final int cntPictures; final boolean hasManyPictures; - final CardDb db = !card.isVariant() ? FModel.getMagicDb().getCommonCards() : FModel.getMagicDb().getVariantCards(); + final CardDb db = !card.isVariant() ? StaticData.instance().getCommonCards() : StaticData.instance().getVariantCards(); if (includeSet) { cntPictures = db.getPrintCount(card.getName(), edition); hasManyPictures = cntPictures > 1; @@ -78,27 +76,17 @@ public class ImageUtil { } if (includeSet) { - String editionAliased = isDownloadUrl ? FModel.getMagicDb().getEditions().getCode2ByCode(edition) : getSetFolder(edition); + String editionAliased = isDownloadUrl ? StaticData.instance().getEditions().getCode2ByCode(edition) : ImageKeys.getSetFolder(edition); return String.format("%s/%s", editionAliased, fname); } else { return fname; } } - public static boolean mayEnlarge() { - return FModel.getPreferences().getPrefBoolean(FPref.UI_SCALE_LARGER); - } - public static boolean hasBackFacePicture(PaperCard cp) { CardSplitType cst = cp.getRules().getSplitType(); return cst == CardSplitType.Transform || cst == CardSplitType.Flip; } - - public static String getSetFolder(String edition) { - return !ForgeConstants.CACHE_CARD_PICS_SUBDIR.containsKey(edition) - ? FModel.getMagicDb().getEditions().getCode2ByCode(edition) // by default 2-letter codes from MWS are used - : ForgeConstants.CACHE_CARD_PICS_SUBDIR.get(edition); // may use custom paths though - } public static String getNameToUse(PaperCard cp, boolean backFace) { final CardRules card = cp.getRules(); diff --git a/forge-gui-desktop/src/main/java/forge/ImageCache.java b/forge-gui-desktop/src/main/java/forge/ImageCache.java index 2b9fc95e86f..0502f2d846a 100644 --- a/forge-gui-desktop/src/main/java/forge/ImageCache.java +++ b/forge-gui-desktop/src/main/java/forge/ImageCache.java @@ -33,13 +33,15 @@ import com.google.common.cache.LoadingCache; import com.mortennobel.imagescaling.ResampleOp; import forge.assets.FSkinProp; -import forge.assets.ImageUtil; import forge.game.card.CardView; import forge.item.InventoryItem; import forge.match.MatchUtil; +import forge.model.FModel; import forge.properties.ForgeConstants; +import forge.properties.ForgePreferences.FPref; import forge.toolbox.FSkin; import forge.toolbox.FSkin.SkinIcon; +import forge.util.ImageUtil; /** * This class stores ALL card images in a cache with soft values. this means @@ -170,7 +172,7 @@ public class ImageCache { double scaleX = (-1 == width ? 1 : (double)width / original.getWidth()); double scaleY = (-1 == height? 1 : (double)height / original.getHeight()); double bestFitScale = Math.min(scaleX, scaleY); - if ((bestFitScale > 1) && !ImageUtil.mayEnlarge()) { + if ((bestFitScale > 1) && !FModel.getPreferences().getPrefBoolean(FPref.UI_SCALE_LARGER)) { bestFitScale = 1; } diff --git a/forge-gui-desktop/src/main/java/forge/gui/ImportSourceAnalyzer.java b/forge-gui-desktop/src/main/java/forge/gui/ImportSourceAnalyzer.java index 6137424df1b..3e491aac72f 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/ImportSourceAnalyzer.java +++ b/forge-gui-desktop/src/main/java/forge/gui/ImportSourceAnalyzer.java @@ -20,7 +20,6 @@ package forge.gui; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; -import forge.assets.ImageUtil; import forge.card.CardEdition; import forge.card.CardRules; import forge.item.IPaperCard; @@ -28,6 +27,7 @@ import forge.item.PaperCard; import forge.model.FModel; import forge.properties.ForgeConstants; import forge.util.FileUtil; +import forge.util.ImageUtil; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; diff --git a/forge-gui-desktop/src/main/java/forge/itemmanager/CardManager.java b/forge-gui-desktop/src/main/java/forge/itemmanager/CardManager.java index 18309382918..a51aff36f92 100644 --- a/forge-gui-desktop/src/main/java/forge/itemmanager/CardManager.java +++ b/forge-gui-desktop/src/main/java/forge/itemmanager/CardManager.java @@ -7,10 +7,11 @@ import forge.itemmanager.filters.*; import forge.model.FModel; import forge.quest.QuestWorld; import forge.screens.home.quest.DialogChooseSets; - import javax.swing.*; +import java.util.HashMap; import java.util.List; +import java.util.Map.Entry; /** * ItemManager for cards @@ -37,6 +38,20 @@ public class CardManager extends ItemManager { buildAddFilterMenu(menu, this); } + @Override + protected Iterable> getUnique(Iterable> items) { + //use special technique for getting unique cards so that cards without art aren't shown + HashMap> map = new HashMap>(); + for (Entry item : items) { + final String key = item.getKey().getName(); + final Entry oldValue = map.get(key); + if (oldValue == null || !oldValue.getKey().hasImage()) { //only replace in map if old value doesn't have image + map.put(key, item); + } + } + return map.values(); + } + /* Static overrides shared with SpellShopManager*/ public static void addDefaultFilters(final ItemManager itemManager) { diff --git a/forge-gui-desktop/src/main/java/forge/itemmanager/ItemManager.java b/forge-gui-desktop/src/main/java/forge/itemmanager/ItemManager.java index 25274c62090..9ce85ef10cb 100644 --- a/forge-gui-desktop/src/main/java/forge/itemmanager/ItemManager.java +++ b/forge-gui-desktop/src/main/java/forge/itemmanager/ItemManager.java @@ -996,6 +996,10 @@ public abstract class ItemManager extends JPanel implem this.updateView(true, this.getSelectedItems()); } + protected Iterable> getUnique(Iterable> items) { + return Aggregates.uniqueByLast(items, this.pool.FN_GET_NAME); + } + /** * * updateView. @@ -1011,7 +1015,7 @@ public abstract class ItemManager extends JPanel implem if (useFilter && this.wantUnique) { Predicate> filterForPool = Predicates.compose(this.filterPredicate, this.pool.FN_GET_KEY); - Iterable> items = Aggregates.uniqueByLast(Iterables.filter(this.pool, filterForPool), this.pool.FN_GET_NAME); + Iterable> items = getUnique(Iterables.filter(this.pool, filterForPool)); this.model.addItems(items); } else if (useFilter) { @@ -1019,7 +1023,7 @@ public abstract class ItemManager extends JPanel implem this.model.addItems(Iterables.filter(this.pool, pred)); } else if (this.wantUnique) { - Iterable> items = Aggregates.uniqueByLast(this.pool, this.pool.FN_GET_NAME); + Iterable> items = getUnique(this.pool); this.model.addItems(items); } else if (!useFilter && forceFilter) { diff --git a/forge-gui-mobile/src/forge/assets/FSkinImage.java b/forge-gui-mobile/src/forge/assets/FSkinImage.java index 2d76a32ad54..9d33095479e 100644 --- a/forge-gui-mobile/src/forge/assets/FSkinImage.java +++ b/forge-gui-mobile/src/forge/assets/FSkinImage.java @@ -10,6 +10,7 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion; import forge.Graphics; import forge.properties.ForgeConstants; +import forge.util.ImageUtil; /** Properties of various components that make up the skin. * This interface allows all enums to be under the same roof. diff --git a/forge-gui-mobile/src/forge/assets/ImageCache.java b/forge-gui-mobile/src/forge/assets/ImageCache.java index ad3b516053d..6aa7eb3fbe3 100644 --- a/forge-gui-mobile/src/forge/assets/ImageCache.java +++ b/forge-gui-mobile/src/forge/assets/ImageCache.java @@ -30,6 +30,7 @@ import forge.item.InventoryItem; import forge.item.PaperCard; import forge.match.MatchUtil; import forge.properties.ForgeConstants; +import forge.util.ImageUtil; import org.apache.commons.lang3.StringUtils; diff --git a/forge-gui/src/main/java/forge/deck/io/DeckHtmlSerializer.java b/forge-gui/src/main/java/forge/deck/io/DeckHtmlSerializer.java index 2b8b287db85..e3631c84570 100644 --- a/forge-gui/src/main/java/forge/deck/io/DeckHtmlSerializer.java +++ b/forge-gui/src/main/java/forge/deck/io/DeckHtmlSerializer.java @@ -1,9 +1,9 @@ package forge.deck.io; -import forge.assets.ImageUtil; import forge.deck.Deck; import forge.item.PaperCard; import forge.properties.ForgeConstants; +import forge.util.ImageUtil; import freemarker.template.Configuration; import freemarker.template.DefaultObjectWrapper; import freemarker.template.Template; diff --git a/forge-gui/src/main/java/forge/download/GuiDownloadPicturesLQ.java b/forge-gui/src/main/java/forge/download/GuiDownloadPicturesLQ.java index 787bbb65f32..0a9d89c75ad 100644 --- a/forge-gui/src/main/java/forge/download/GuiDownloadPicturesLQ.java +++ b/forge-gui/src/main/java/forge/download/GuiDownloadPicturesLQ.java @@ -17,11 +17,11 @@ */ package forge.download; -import forge.assets.ImageUtil; import forge.card.CardRules; import forge.item.PaperCard; import forge.model.FModel; import forge.properties.ForgeConstants; +import forge.util.ImageUtil; import org.apache.commons.lang3.StringUtils; diff --git a/forge-gui/src/main/java/forge/download/GuiDownloadSetPicturesLQ.java b/forge-gui/src/main/java/forge/download/GuiDownloadSetPicturesLQ.java index a98f34bf73f..90b291a7337 100644 --- a/forge-gui/src/main/java/forge/download/GuiDownloadSetPicturesLQ.java +++ b/forge-gui/src/main/java/forge/download/GuiDownloadSetPicturesLQ.java @@ -19,11 +19,11 @@ package forge.download; import com.google.common.collect.Iterables; -import forge.assets.ImageUtil; import forge.card.CardEdition; import forge.item.PaperCard; import forge.model.FModel; import forge.properties.ForgeConstants; +import forge.util.ImageUtil; import org.apache.commons.lang3.StringUtils; diff --git a/forge-gui/src/main/java/forge/model/FModel.java b/forge-gui/src/main/java/forge/model/FModel.java index 2a1221ad918..13dae55eb71 100644 --- a/forge-gui/src/main/java/forge/model/FModel.java +++ b/forge-gui/src/main/java/forge/model/FModel.java @@ -87,7 +87,8 @@ public class FModel { private static GameFormat.Collection formats; public static void initialize(final IProgressBar progressBar) { - ImageKeys.initializeDirs(ForgeConstants.CACHE_CARD_PICS_DIR, + ImageKeys.initializeDirs( + ForgeConstants.CACHE_CARD_PICS_DIR, ForgeConstants.CACHE_CARD_PICS_SUBDIR, ForgeConstants.CACHE_TOKEN_PICS_DIR, ForgeConstants.CACHE_ICON_PICS_DIR, ForgeConstants.CACHE_BOOSTER_PICS_DIR, ForgeConstants.CACHE_FATPACK_PICS_DIR, ForgeConstants.CACHE_BOOSTERBOX_PICS_DIR, ForgeConstants.CACHE_PRECON_PICS_DIR,