diff --git a/Mage.Client/pom.xml b/Mage.Client/pom.xml index ae03ce324d..3a2612a36a 100644 --- a/Mage.Client/pom.xml +++ b/Mage.Client/pom.xml @@ -54,9 +54,9 @@ 0.8.6 - com.google.collections - google-collections - 1.0 + com.google.guava + guava + 20.0 org.swinglabs diff --git a/Mage.Client/src/main/java/mage/client/components/MageRoundPane.java b/Mage.Client/src/main/java/mage/client/components/MageRoundPane.java index 7f7a774562..b5957413e7 100644 --- a/Mage.Client/src/main/java/mage/client/components/MageRoundPane.java +++ b/Mage.Client/src/main/java/mage/client/components/MageRoundPane.java @@ -1,20 +1,21 @@ package mage.client.components; -import com.google.common.base.Function; -import com.google.common.collect.MapMaker; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.RenderingHints; +import java.awt.*; import java.awt.image.BufferedImage; -import java.util.Map; import java.util.Objects; -import javax.swing.JPanel; -import mage.client.util.ImageCaches; + +import javax.swing.*; + import org.jdesktop.swingx.graphics.GraphicsUtilities; import org.jdesktop.swingx.graphics.ShadowRenderer; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import mage.client.util.ImageCaches; +import mage.client.util.SoftValuesLoadingCache; + /** * Mage round pane with transparency. Used for tooltips. * @@ -26,14 +27,12 @@ public class MageRoundPane extends JPanel { private int Y_OFFSET = 30; private final Color defaultBackgroundColor = new Color(141, 130, 112, 200); // color of the frame of the popup window private Color backgroundColor = defaultBackgroundColor; - private static final int ALPHA = 0; - private static final Map SHADOW_IMAGE_CACHE; - private static final Map IMAGE_CACHE; + private static final SoftValuesLoadingCache SHADOW_IMAGE_CACHE; + private static final SoftValuesLoadingCache IMAGE_CACHE; static { - SHADOW_IMAGE_CACHE = ImageCaches.register(new MapMaker().softValues().makeComputingMap((Function) key -> createShadowImage(key))); - - IMAGE_CACHE = ImageCaches.register(new MapMaker().softValues().makeComputingMap((Function) key -> createImage(key))); + SHADOW_IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(MageRoundPane::createShadowImage)); + IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(MageRoundPane::createImage)); } private static final class ShadowKey { @@ -136,7 +135,7 @@ public class MageRoundPane extends JPanel { @Override protected void paintComponent(Graphics g) { - g.drawImage(IMAGE_CACHE.get(new Key(getWidth(), getHeight(), X_OFFSET, Y_OFFSET, backgroundColor)), 0, 0, null); + g.drawImage(IMAGE_CACHE.getOrThrow(new Key(getWidth(), getHeight(), X_OFFSET, Y_OFFSET, backgroundColor)), 0, 0, null); } private static BufferedImage createImage(Key key) { @@ -150,7 +149,7 @@ public class MageRoundPane extends JPanel { Graphics2D g2 = image.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - BufferedImage shadow = SHADOW_IMAGE_CACHE.get(new ShadowKey(w, h)); + BufferedImage shadow = SHADOW_IMAGE_CACHE.getOrThrow(new ShadowKey(w, h)); { int xOffset = (shadow.getWidth() - w) / 2; diff --git a/Mage.Client/src/main/java/mage/client/util/ImageCaches.java b/Mage.Client/src/main/java/mage/client/util/ImageCaches.java index 2f5fe74345..479370288a 100644 --- a/Mage.Client/src/main/java/mage/client/util/ImageCaches.java +++ b/Mage.Client/src/main/java/mage/client/util/ImageCaches.java @@ -2,7 +2,8 @@ package mage.client.util; import java.util.ArrayList; -import java.util.Map; + +import com.google.common.cache.Cache; /** * @@ -10,20 +11,20 @@ import java.util.Map; */ public final class ImageCaches { - private static final ArrayList IMAGE_CACHES; + private final static ArrayList> IMAGE_CACHES; static { IMAGE_CACHES = new ArrayList<>(); } - public static Map register(Map map) { + public static , K, V> C register(C map) { IMAGE_CACHES.add(map); return map; } public static void flush() { - for (Map map : IMAGE_CACHES) { - map.clear(); + for (Cache map : IMAGE_CACHES) { + map.invalidateAll(); } } } diff --git a/Mage.Client/src/main/java/mage/client/util/SoftValuesLoadingCache.java b/Mage.Client/src/main/java/mage/client/util/SoftValuesLoadingCache.java new file mode 100644 index 0000000000..c07fafe03e --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/util/SoftValuesLoadingCache.java @@ -0,0 +1,58 @@ +package mage.client.util; + +import static com.google.common.cache.CacheBuilder.newBuilder; + +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +import com.google.common.base.Function; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.ForwardingLoadingCache; +import com.google.common.cache.LoadingCache; + +public class SoftValuesLoadingCache extends ForwardingLoadingCache> { + + private final LoadingCache> cache; + + public SoftValuesLoadingCache(CacheLoader> loader) { + cache = newBuilder().softValues().build(loader); + } + + @Override + protected LoadingCache> delegate() { + return cache; + } + + public V getOrThrow(K key) { + V v = getOrNull(key); + if (v == null) { + throw new NullPointerException(); + } + return v; + } + + public V getOrNull(K key) { + try { + return get(key).orElse(null); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + public V peekIfPresent(K key) { + Optional value = getIfPresent(key); + if (value != null) { + return value.orElse(null); + } + return null; + } + + public static SoftValuesLoadingCache from(CacheLoader> loader) { + return new SoftValuesLoadingCache<>(loader); + } + + public static SoftValuesLoadingCache from(Function loader) { + return from(CacheLoader.from(k -> Optional.ofNullable(loader.apply(k)))); + } + +} diff --git a/Mage.Client/src/main/java/mage/client/util/TransformedImageCache.java b/Mage.Client/src/main/java/mage/client/util/TransformedImageCache.java index 77ad5b8917..53706a6d0c 100644 --- a/Mage.Client/src/main/java/mage/client/util/TransformedImageCache.java +++ b/Mage.Client/src/main/java/mage/client/util/TransformedImageCache.java @@ -5,16 +5,16 @@ */ package mage.client.util; -import com.google.common.base.Function; -import com.google.common.collect.MapMaker; -import com.mortennobel.imagescaling.ResampleOp; -import java.awt.Graphics2D; -import java.awt.GraphicsConfiguration; -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; -import java.awt.Transparency; +import java.awt.*; import java.awt.image.BufferedImage; -import java.util.Map; + +import javax.annotation.Nullable; + +import com.google.common.base.Function; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.mortennobel.imagescaling.ResampleOp; /** * @@ -68,19 +68,20 @@ public final class TransformedImageCache { } } - private static final Map> IMAGE_CACHE; + private static final SoftValuesLoadingCache> IMAGE_CACHE; static { // TODO: can we use a single map? - IMAGE_CACHE = ImageCaches.register(new MapMaker().softValues().makeComputingMap((Function>) key -> new MapMaker().weakKeys().softValues().makeComputingMap(image -> { - if (key.width != image.getWidth() || key.height != image.getHeight()) { - image = resizeImage(image, key.width, key.height); - } - if (key.angle != 0.0) { - image = rotateImage(image, key.angle); - } - return image; - }))); + IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from( + key -> SoftValuesLoadingCache.from(image -> { + if (key.width != image.getWidth() || key.height != image.getHeight()) { + image = resizeImage(image, key.width, key.height); + } + if (key.angle != 0.0) { + image = rotateImage(image, key.angle); + } + return image; + }))); } private static BufferedImage rotateImage(BufferedImage image, double angle) { @@ -145,6 +146,6 @@ public final class TransformedImageCache { if (resHeight < 3) { resHeight = 3; } - return IMAGE_CACHE.get(new Key(resWidth, resHeight, angle)).get(image); + return IMAGE_CACHE.getOrThrow(new Key(resWidth, resHeight, angle)).getOrThrow(image); } } diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java index 0ffdaddc92..1a683e2aa3 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java @@ -1,11 +1,27 @@ package org.mage.card.arcane; -import com.google.common.base.Function; -import com.google.common.collect.MapMaker; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.StringTokenizer; +import java.util.UUID; + +import javax.swing.*; + +import org.apache.log4j.Logger; +import org.jdesktop.swingx.graphics.GraphicsUtilities; +import org.mage.plugins.card.images.ImageCache; +import org.mage.plugins.card.utils.impl.ImageManagerImpl; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + import mage.cards.action.ActionCallback; +import mage.client.constants.Constants; import mage.client.dialog.PreferencesDialog; import mage.client.util.ImageCaches; import mage.client.util.ImageHelper; +import mage.client.util.SoftValuesLoadingCache; import mage.components.ImagePanel; import mage.components.ImagePanelStyle; import mage.constants.AbilityType; @@ -13,18 +29,6 @@ import mage.view.CardView; import mage.view.CounterView; import mage.view.PermanentView; import mage.view.StackAbilityView; -import org.apache.log4j.Logger; -import org.jdesktop.swingx.graphics.GraphicsUtilities; -import org.mage.plugins.card.images.ImageCache; -import org.mage.plugins.card.utils.impl.ImageManagerImpl; -import mage.client.constants.Constants; - -import javax.swing.*; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.util.Map; -import java.util.StringTokenizer; -import java.util.UUID; /** * Class for drawing the mage card object by using a form based JComponent @@ -78,7 +82,7 @@ public class CardPanelComponentImpl extends CardPanel { private boolean displayTitleAnyway; private boolean displayFullImagePath; - private static final Map IMAGE_CACHE; + private final static SoftValuesLoadingCache IMAGE_CACHE; static class Key { @@ -175,7 +179,7 @@ public class CardPanelComponentImpl extends CardPanel { } static { - IMAGE_CACHE = ImageCaches.register(new MapMaker().softValues().makeComputingMap((Function) key -> createImage(key))); + IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(CardPanelComponentImpl::createImage)); } static private boolean canShowCardIcons(int cardFullWidth, boolean cardHasImage){ @@ -380,7 +384,7 @@ public class CardPanelComponentImpl extends CardPanel { } g2d.drawImage( - IMAGE_CACHE.get( + IMAGE_CACHE.getOrThrow( new Key(getWidth(), getHeight(), getCardWidth(), getCardHeight(), getCardXOffset(), getCardYOffset(), hasImage, isSelected(), isChoosable(), gameCard.isPlayable(), gameCard.isCanAttack())), 0, 0, null); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java index b3e3a49514..aa5fde9a39 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java @@ -1,7 +1,19 @@ package org.mage.card.arcane; -import com.google.common.collect.MapMaker; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import org.apache.log4j.Logger; +import org.jdesktop.swingx.graphics.GraphicsUtilities; +import org.mage.plugins.card.images.ImageCache; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + import mage.cards.action.ActionCallback; +import mage.client.constants.Constants; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; @@ -9,15 +21,6 @@ import mage.view.CardView; import mage.view.CounterView; import mage.view.PermanentView; import mage.view.StackAbilityView; -import org.apache.log4j.Logger; -import org.jdesktop.swingx.graphics.GraphicsUtilities; -import org.mage.plugins.card.images.ImageCache; -import mage.client.constants.Constants; - -import java.awt.*; -import java.awt.image.BufferedImage; -import java.util.Map; -import java.util.UUID; public class CardPanelRenderImpl extends CardPanel { @@ -215,7 +218,7 @@ public class CardPanelRenderImpl extends CardPanel { } // Map of generated images - private static final Map IMAGE_CACHE = new MapMaker().softValues().makeMap(); + private final static Cache IMAGE_CACHE = CacheBuilder.newBuilder().softValues().build(); // The art image for the card, loaded in from the disk private BufferedImage artImage; @@ -265,7 +268,11 @@ public class CardPanelRenderImpl extends CardPanel { = new ImageKey(gameCard, artImage, getCardWidth(), getCardHeight(), isChoosable(), isSelected()); - cardImage = IMAGE_CACHE.computeIfAbsent(key, k -> renderCard()); + try { + cardImage = IMAGE_CACHE.get(key, this::renderCard); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } // No cached copy exists? Render one and cache it } diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/GlowText.java b/Mage.Client/src/main/java/org/mage/card/arcane/GlowText.java index 9062ffe6d8..8d95a11ac0 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/GlowText.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/GlowText.java @@ -1,7 +1,5 @@ package org.mage.card.arcane; -import com.google.common.base.Function; -import com.google.common.collect.MapMaker; import java.awt.*; import java.awt.font.FontRenderContext; import java.awt.font.LineBreakMeasurer; @@ -15,10 +13,18 @@ import java.text.BreakIterator; import java.util.Locale; import java.util.Map; import java.util.Objects; + import javax.swing.*; -import mage.client.util.ImageCaches; + import org.jdesktop.swingx.graphics.GraphicsUtilities; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import mage.client.util.ImageCaches; +import mage.client.util.SoftValuesLoadingCache; + public class GlowText extends JLabel { private static final long serialVersionUID = 1827677946939348001L; @@ -28,7 +34,7 @@ public class GlowText extends JLabel { private Color glowColor; private boolean wrap; private int lineCount = 0; - private static final Map IMAGE_CACHE; + private static final SoftValuesLoadingCache IMAGE_CACHE; private static final class Key { @@ -125,7 +131,7 @@ public class GlowText extends JLabel { } static { - IMAGE_CACHE = ImageCaches.register(new MapMaker().softValues().makeComputingMap((Function) GlowText::createImage)); + IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(GlowText::createGlowImage)); } public void setGlow(Color glowColor, int size, float intensity) { @@ -152,10 +158,10 @@ public class GlowText extends JLabel { return; } - g.drawImage(IMAGE_CACHE.get(new Key(getWidth(), getHeight(), getText(), getFont(), getForeground(), glowSize, glowIntensity, glowColor, wrap)), 0, 0, null); + g.drawImage(IMAGE_CACHE.getOrThrow(new Key(getWidth(), getHeight(), getText(), getFont(), getForeground(), glowSize, glowIntensity, glowColor, wrap)), 0, 0, null); } - private static BufferedImage createImage(Key key) { + private static BufferedImage createGlowImage(Key key) { Dimension size = new Dimension(key.width, key.height); BufferedImage image = GraphicsUtilities.createCompatibleTranslucentImage(size.width, size.height); Graphics2D g2d = image.createGraphics(); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java index a3aac320d4..92927b0509 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java @@ -1,29 +1,35 @@ package org.mage.plugins.card.images; -import com.google.common.base.Function; -import com.google.common.collect.ComputationException; -import com.google.common.collect.MapMaker; -import mage.client.constants.Constants; -import mage.client.dialog.PreferencesDialog; -import mage.client.util.TransformedImageCache; -import mage.view.CardView; -import net.java.truevfs.access.TFile; -import net.java.truevfs.access.TFileInputStream; -import net.java.truevfs.access.TFileOutputStream; -import org.apache.log4j.Logger; -import org.mage.plugins.card.dl.sources.DirectLinksForDownload; -import org.mage.plugins.card.utils.CardImageUtils; - -import javax.imageio.ImageIO; import java.awt.*; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; -import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.Nullable; +import javax.imageio.ImageIO; + +import org.apache.log4j.Logger; +import org.mage.plugins.card.dl.sources.DirectLinksForDownload; +import org.mage.plugins.card.utils.CardImageUtils; + +import com.google.common.base.Function; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ComputationException; + +import mage.client.constants.Constants; +import mage.client.dialog.PreferencesDialog; +import mage.client.util.SoftValuesLoadingCache; +import mage.client.util.TransformedImageCache; +import mage.view.CardView; +import net.java.truevfs.access.TFile; +import net.java.truevfs.access.TFileInputStream; +import net.java.truevfs.access.TFileOutputStream; + /** * This class stores ALL card images in a cache with soft values. this means * that the images may be garbage collected when they are not needed any more, @@ -45,8 +51,8 @@ public final class ImageCache { private static final Logger LOGGER = Logger.getLogger(ImageCache.class); - private static final Map IMAGE_CACHE; - private static final Map FACE_IMAGE_CACHE; + private static final SoftValuesLoadingCache IMAGE_CACHE; + private static final SoftValuesLoadingCache FACE_IMAGE_CACHE; /** * Common pattern for keys. Format: "##" @@ -55,11 +61,10 @@ public final class ImageCache { static { // softValues() = Specifies that each value (not key) stored in the map should be wrapped in a SoftReference (by default, strong references are used). Softly-referenced objects will be garbage-collected in a globally least-recently-used manner, in response to memory demand. - IMAGE_CACHE = new MapMaker().softValues().makeComputingMap(new Function() { + IMAGE_CACHE = SoftValuesLoadingCache.from(new Function() { @Override public BufferedImage apply(String key) { try { - boolean usesVariousArt = false; if (key.matches(".*#usesVariousArt.*")) { usesVariousArt = true; @@ -179,58 +184,44 @@ public final class ImageCache { } }); - FACE_IMAGE_CACHE = new MapMaker().softValues().makeComputingMap(new Function() { - @Override - public BufferedImage apply(String key) { - try { + FACE_IMAGE_CACHE = SoftValuesLoadingCache.from(key -> { + try { + Matcher m = KEY_PATTERN.matcher(key); - Matcher m = KEY_PATTERN.matcher(key); + if (m.matches()) { + String name = m.group(1); + String set = m.group(2); + //Integer artid = Integer.parseInt(m.group(2)); - if (m.matches()) { - String name = m.group(1); - String set = m.group(2); - //Integer artid = Integer.parseInt(m.group(2)); + String path; + path = CardImageUtils.generateFaceImagePath(name, set); - String path; - path = CardImageUtils.generateFaceImagePath(name, set); - - if (path == null) { - return null; - } - TFile file = getTFile(path); - if (file == null) { - return null; - } - - BufferedImage image = loadImage(file); - return image; - } else { - throw new RuntimeException( - "Requested face image doesn't fit the requirement for key (##: " + key); + if (path == null) { + return null; } - } catch (Exception ex) { - if (ex instanceof ComputationException) { - throw (ComputationException) ex; - } else { - throw new ComputationException(ex); + TFile file = getTFile(path); + if (file == null) { + return null; } - } - } - public BufferedImage makeThumbnailByFile(String key, TFile file, String thumbnailPath) { - BufferedImage image = loadImage(file); - image = getWizardsCard(image); - if (image == null) { - return null; + BufferedImage image = loadImage(file); + return image; + } else { + throw new RuntimeException( + "Requested face image doesn't fit the requirement for key (##: " + key); + } + } catch (Exception ex) { + if (ex instanceof ComputationException) { + throw (ComputationException) ex; + } else { + throw new ComputationException(ex); } - LOGGER.debug("creating thumbnail for " + key); - return makeThumbnail(image, thumbnailPath); } }); } public static void clearCache() { - IMAGE_CACHE.clear(); + IMAGE_CACHE.invalidateAll(); } public static String getFilePath(CardView card, int width) { @@ -406,13 +397,7 @@ public final class ImageCache { */ private static BufferedImage getImage(String key) { try { - return IMAGE_CACHE.get(key); - } catch (NullPointerException ex) { - // unfortunately NullOutputException, thrown when apply() returns - // null, is not public - // NullOutputException is a subclass of NullPointerException - // legitimate, happens when a card has no image - return null; + return IMAGE_CACHE.getOrNull(key); } catch (ComputationException ex) { // too low memory if (ex.getCause() instanceof NullPointerException) { @@ -428,13 +413,7 @@ public final class ImageCache { */ private static BufferedImage getFaceImage(String key) { try { - return FACE_IMAGE_CACHE.get(key); - } catch (NullPointerException ex) { - // unfortunately NullOutputException, thrown when apply() returns - // null, is not public - // NullOutputException is a subclass of NullPointerException - // legitimate, happens when a card has no image - return null; + return FACE_IMAGE_CACHE.getOrNull(key); } catch (ComputationException ex) { if (ex.getCause() instanceof NullPointerException) { return null; @@ -449,7 +428,7 @@ public final class ImageCache { * the cache. */ private static BufferedImage tryGetImage(String key) { - return IMAGE_CACHE.containsKey(key) ? IMAGE_CACHE.get(key) : null; + return IMAGE_CACHE.peekIfPresent(key); } /**