Moved card plugin to client. Fixed Issue 143.

This commit is contained in:
magenoxx 2011-08-15 01:36:30 +04:00
parent 0892489229
commit 14bebea64d
55 changed files with 7 additions and 90 deletions

View file

@ -0,0 +1,510 @@
package org.mage.plugins.card;
import mage.cards.Card;
import mage.cards.MagePermanent;
import mage.cards.action.ActionCallback;
import mage.interfaces.plugin.CardPlugin;
import mage.utils.CardUtil;
import mage.view.CardView;
import mage.view.PermanentView;
import net.xeoh.plugins.base.annotations.PluginImplementation;
import net.xeoh.plugins.base.annotations.events.Init;
import net.xeoh.plugins.base.annotations.events.PluginLoaded;
import net.xeoh.plugins.base.annotations.meta.Author;
import org.apache.log4j.Logger;
import org.mage.card.arcane.Animation;
import org.mage.card.arcane.CardPanel;
import org.mage.card.arcane.ManaSymbols;
import org.mage.plugins.card.constants.Constants;
import org.mage.plugins.card.dl.DownloadGui;
import org.mage.plugins.card.dl.DownloadJob;
import org.mage.plugins.card.dl.Downloader;
import org.mage.plugins.card.dl.sources.GathererSets;
import org.mage.plugins.card.dl.sources.GathererSymbols;
import org.mage.plugins.card.images.DownloadPictures;
import org.mage.plugins.card.images.ImageCache;
import org.mage.plugins.card.info.CardInfoPaneImpl;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.util.*;
import java.util.List;
/**
* {@link CardPlugin} implementation.
*
* @author nantuko
* @version 0.1 01.11.2010 Mage permanents. Sorting card layout.
* @version 0.6 17.07.2011 #sortPermanents got option to display non-land permanents in one pile
* @version 0.7 29.07.2011 face down cards support
*/
@PluginImplementation
@Author(name = "nantuko")
public class CardPluginImpl implements CardPlugin {
private final static Logger log = Logger.getLogger(CardPluginImpl.class);
static private final int GUTTER_Y = 15;
static private final int GUTTER_X = 5;
static final float EXTRA_CARD_SPACING_X = 0.04f;
static private final float CARD_SPACING_Y = 0.03f;
static private final float STACK_SPACING_X = 0.07f;
static private final float STACK_SPACING_Y = 0.13f;
//static private final int MW_GUIDE_HEIGHT = 30;
private int landStackMax = 5;
private int cardWidthMin = 50, cardWidthMax = Constants.CARD_SIZE_FULL.width;
private boolean stackVertical = false;
private int playAreaWidth, playAreaHeight;
private int cardWidth, cardHeight;
private int extraCardSpacingX, cardSpacingX, cardSpacingY;
private int stackSpacingX, stackSpacingY;
private List<Row> rows = new ArrayList<Row>();
@Init
public void init() {
}
@PluginLoaded
public void newPlugin(CardPlugin plugin) {
ManaSymbols.loadImages();
log.info(plugin.toString() + " has been loaded.");
}
@Override
public String toString() {
return "[Card plugin, version 0.7]";
}
@Override
public MagePermanent getMagePermanent(PermanentView permanent, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage) {
boolean foil = canBeFoil && (new Random()).nextInt(5) == 0;
CardPanel cardPanel = new CardPanel(permanent, gameId, loadImage, callback, foil);
cardPanel.setCardBounds(0, 0, dimension.width, dimension.height);
boolean implemented = !permanent.getRarity().equals(mage.Constants.Rarity.NA);
cardPanel.setShowCastingCost(implemented);
return cardPanel;
}
@Override
public MagePermanent getMageCard(CardView permanent, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage) {
boolean foil = canBeFoil && (new Random()).nextInt(5) == 0;
CardPanel cardPanel = new CardPanel(permanent, gameId, loadImage, callback, foil);
cardPanel.setCardBounds(0, 0, dimension.width, dimension.height);
boolean implemented = !permanent.getRarity().equals(mage.Constants.Rarity.NA);
cardPanel.setShowCastingCost(implemented);
return cardPanel;
}
@Override
public void sortPermanents(Map<String, JComponent> ui, Collection<MagePermanent> permanents, Map<String, String> options) {
//TODO: add caching
//requires to find out is position have been changed that includes:
//adding/removing permanents, type change
if (ui == null)
throw new RuntimeException("Error: no components");
JComponent component = ui.get("jScrollPane");
JComponent component2 = ui.get("battlefieldPanel");
if (component == null)
throw new RuntimeException("Error: jScrollPane is missing");
if (component2 == null)
throw new RuntimeException("Error: battlefieldPanel is missing");
if (!(component instanceof JScrollPane))
throw new RuntimeException("Error: jScrollPane has wrong type.");
if (!(component instanceof JScrollPane))
throw new RuntimeException("Error: battlefieldPanel is missing");
JScrollPane jScrollPane = (JScrollPane) component;
JLayeredPane battlefieldPanel = (JLayeredPane) component2;
Row allLands = new Row();
outerLoop:
//
for (MagePermanent permanent : permanents) {
if (!CardUtil.isLand(permanent) || CardUtil.isCreature(permanent))
continue;
int insertIndex = -1;
// Find lands with the same name.
for (int i = 0, n = allLands.size(); i < n; i++) {
Stack stack = allLands.get(i);
MagePermanent firstPanel = stack.get(0);
if (firstPanel.getOriginal().getName().equals(permanent.getOriginal().getName())) {
if (!empty(firstPanel.getLinks())) {
// Put this land to the left of lands with the same name and attachments.
insertIndex = i;
break;
}
if (!empty(permanent.getLinks()) || stack.size() == landStackMax) {
// If this land has attachments or the stack is full, put it to the right.
insertIndex = i + 1;
continue;
}
// Add to stack.
stack.add(0, permanent);
continue outerLoop;
}
if (insertIndex != -1)
break;
}
Stack stack = new Stack();
stack.add(permanent);
allLands.add(insertIndex == -1 ? allLands.size() : insertIndex, stack);
}
Row allCreatures = new Row(permanents, RowType.creature);
Row allOthers = new Row(permanents, RowType.other);
boolean othersOnTheRight = true;
if (options != null && options.containsKey("nonLandPermanentsInOnePile")) {
if (options.get("nonLandPermanentsInOnePile").equals("true")) {
System.out.println("in one pile");
othersOnTheRight = false;
allCreatures.addAll(allOthers);
allOthers.clear();
}
}
cardWidth = cardWidthMax;
Rectangle rect = jScrollPane.getVisibleRect();
playAreaWidth = rect.width;
playAreaHeight = rect.height;
while (true) {
rows.clear();
cardHeight = Math.round(cardWidth * CardPanel.ASPECT_RATIO);
extraCardSpacingX = (int) Math.round(cardWidth * EXTRA_CARD_SPACING_X);
cardSpacingX = cardHeight - cardWidth + extraCardSpacingX;
cardSpacingY = (int) Math.round(cardHeight * CARD_SPACING_Y);
stackSpacingX = stackVertical ? 0 : (int) Math.round(cardWidth * STACK_SPACING_X);
stackSpacingY = (int) Math.round(cardHeight * STACK_SPACING_Y);
Row creatures = (Row) allCreatures.clone();
Row lands = (Row) allLands.clone();
Row others = (Row) allOthers.clone();
// Wrap all creatures and lands.
wrap(creatures, rows, -1);
int afterCreaturesIndex = rows.size();
wrap(lands, rows, afterCreaturesIndex);
// Store the current rows and others.
List<Row> storedRows = new ArrayList<Row>(rows.size());
for (Row row : rows)
storedRows.add((Row) row.clone());
Row storedOthers = (Row) others.clone();
// Fill in all rows with others.
for (Row row : rows)
fillRow(others, rows, row);
// Stop if everything fits, otherwise revert back to the stored values.
if (creatures.isEmpty() && lands.isEmpty() && others.isEmpty())
break;
rows = storedRows;
others = storedOthers;
// Try to put others on their own row(s) and fill in the rest.
wrap(others, rows, afterCreaturesIndex);
for (Row row : rows)
fillRow(others, rows, row);
// If that still doesn't fit, scale down.
if (creatures.isEmpty() && lands.isEmpty() && others.isEmpty())
break;
//cardWidth = (int)(cardWidth / 1.2);
//FIXME: -1 is too slow. why not binary search?
cardWidth--;
}
// Get size of all the rows.
int x, y = GUTTER_Y;
int maxRowWidth = 0;
for (Row row : rows) {
int rowBottom = 0;
x = GUTTER_X;
for (int stackIndex = 0, stackCount = row.size(); stackIndex < stackCount; stackIndex++) {
Stack stack = row.get(stackIndex);
rowBottom = Math.max(rowBottom, y + stack.getHeight());
x += stack.getWidth();
}
y = rowBottom;
maxRowWidth = Math.max(maxRowWidth, x);
}
//setPreferredSize(new Dimension(maxRowWidth - cardSpacingX, y - cardSpacingY));
//revalidate();
// Position all card panels.
x = 0;
y = GUTTER_Y;
for (Row row : rows) {
int rowBottom = 0;
x = GUTTER_X;
for (int stackIndex = 0, stackCount = row.size(); stackIndex < stackCount; stackIndex++) {
Stack stack = row.get(stackIndex);
// Align others to the right.
if (othersOnTheRight && RowType.other.isType(stack.get(0))) {
x = playAreaWidth - GUTTER_X + extraCardSpacingX;
for (int i = stackIndex, n = row.size(); i < n; i++)
x -= row.get(i).getWidth();
}
for (int panelIndex = 0, panelCount = stack.size(); panelIndex < panelCount; panelIndex++) {
MagePermanent panel = stack.get(panelIndex);
int stackPosition = panelCount - panelIndex - 1;
///setComponentZOrder((Component)panel, panelIndex);
int panelX = x + (stackPosition * stackSpacingX);
int panelY = y + (stackPosition * stackSpacingY);
//panel.setLocation(panelX, panelY);
try {
// may cause:
// java.lang.IllegalArgumentException: illegal component position 26 should be less then 26
battlefieldPanel.moveToBack(panel);
} catch (Exception e) {
e.printStackTrace();
}
panel.setCardBounds(panelX, panelY, cardWidth, cardHeight);
}
rowBottom = Math.max(rowBottom, y + stack.getHeight());
x += stack.getWidth();
}
y = rowBottom;
}
}
private boolean empty(List<?> list) {
return list == null || list.isEmpty();
}
private int wrap(Row sourceRow, List<Row> rows, int insertIndex) {
// The cards are sure to fit (with vertical scrolling) at the minimum card width.
boolean allowHeightOverflow = cardWidth == cardWidthMin;
Row currentRow = new Row();
for (int i = 0, n = sourceRow.size() - 1; i <= n; i++) {
Stack stack = sourceRow.get(i);
// If the row is not empty and this stack doesn't fit, add the row.
int rowWidth = currentRow.getWidth();
if (!currentRow.isEmpty() && rowWidth + stack.getWidth() > playAreaWidth) {
// Stop processing if the row is too wide or tall.
if (!allowHeightOverflow && rowWidth > playAreaWidth)
break;
if (!allowHeightOverflow && getRowsHeight(rows) + sourceRow.getHeight() > playAreaHeight)
break;
rows.add(insertIndex == -1 ? rows.size() : insertIndex, currentRow);
currentRow = new Row();
}
currentRow.add(stack);
}
// Add the last row if it is not empty and it fits.
if (!currentRow.isEmpty()) {
int rowWidth = currentRow.getWidth();
if (allowHeightOverflow || rowWidth <= playAreaWidth) {
if (allowHeightOverflow || getRowsHeight(rows) + sourceRow.getHeight() <= playAreaHeight) {
rows.add(insertIndex == -1 ? rows.size() : insertIndex, currentRow);
}
}
}
// Remove the wrapped stacks from the source row.
for (Row row : rows)
for (Stack stack : row)
sourceRow.remove(stack);
return insertIndex;
}
private void fillRow(Row sourceRow, List<Row> rows, Row row) {
int rowWidth = row.getWidth();
while (!sourceRow.isEmpty()) {
Stack stack = sourceRow.get(0);
rowWidth += stack.getWidth();
if (rowWidth > playAreaWidth)
break;
if (stack.getHeight() > row.getHeight()) {
if (getRowsHeight(rows) - row.getHeight() + stack.getHeight() > playAreaHeight)
break;
}
row.add(sourceRow.remove(0));
}
}
private int getRowsHeight(List<Row> rows) {
int height = 0;
for (Row row : rows)
height += row.getHeight();
return height - cardSpacingY + GUTTER_Y * 2;
}
static private enum RowType {
land, creature, other;
public boolean isType(MagePermanent card) {
switch (this) {
case land:
return CardUtil.isLand(card);
case creature:
return CardUtil.isCreature(card);
case other:
return !CardUtil.isLand(card) && !CardUtil.isCreature(card);
default:
throw new RuntimeException("Unhandled type: " + this);
}
}
}
private class Row extends ArrayList<Stack> {
public Row() {
super(16);
}
public Row(Collection<MagePermanent> permanents, RowType type) {
this();
addAll(permanents, type);
}
private void addAll(Collection<MagePermanent> permanents, RowType type) {
for (MagePermanent panel : permanents) {
if (!type.isType(panel)) {
continue;
}
Stack stack = new Stack();
stack.add(panel);
add(stack);
}
}
@Override
public boolean addAll(Collection<? extends Stack> c) {
boolean changed = super.addAll(c);
c.clear();
return changed;
}
private int getWidth() {
if (isEmpty())
return 0;
int width = 0;
for (Stack stack : this)
width += stack.getWidth();
return width + GUTTER_X * 2 - extraCardSpacingX;
}
private int getHeight() {
if (isEmpty())
return 0;
int height = 0;
for (Stack stack : this)
height = Math.max(height, stack.getHeight());
return height;
}
}
private class Stack extends ArrayList<MagePermanent> {
public Stack() {
super(8);
}
@Override
public boolean add(MagePermanent panel) {
boolean appended = super.add(panel);
//for (CardPanel attachedPanel : panel.attachedPanels)
//add(attachedPanel);
return appended;
}
private int getWidth() {
return cardWidth + (size() - 1) * stackSpacingX + cardSpacingX;
}
private int getHeight() {
return cardHeight + (size() - 1) * stackSpacingY + cardSpacingY;
}
}
/**
* Download images.
*
* @param allCards Set of cards to download images for.
* @param imagesPath Path to check in and store images to. Can be null, in such case default path should be used.
*/
@Override
public void downloadImages(Set<Card> allCards, String imagesPath) {
DownloadPictures.startDownload(null, allCards, imagesPath);
}
/**
* Download various symbols (mana, tap, set).
*
* @param imagesPath Path to check in and store symbols to. Can be null, in such case default path should be used.
*/
@Override
public void downloadSymbols(String imagesPath) {
final DownloadGui g = new DownloadGui(new Downloader());
Iterable<DownloadJob> it = new GathererSymbols(imagesPath);
for (DownloadJob job : it) {
g.getDownloader().add(job);
}
it = new GathererSets(imagesPath);
for(DownloadJob job:it) {
g.getDownloader().add(job);
}
JDialog d = new JDialog((Frame) null, "Download pictures", false);
d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
d.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
g.getDownloader().dispose();
}
});
d.setLayout(new BorderLayout());
d.add(g);
d.pack();
d.setVisible(true);
}
@Override
public Image getManaSymbolImage(String symbol) {
return ManaSymbols.getManaSymbolImage(symbol);
}
@Override
public void onAddCard(MagePermanent card, int count) {
if (card != null) {
Animation.showCard((CardPanel) card, count > 0 ? count : 1);
try {
while ((card).getAlpha() + 0.05f < 1) {
Thread.sleep(30);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onRemoveCard(MagePermanent card, int count) {
if (card != null) {
Animation.hideCard(card, count > 0 ? count : 1);
try {
while ((card).getAlpha() - 0.05f > 0) {
Thread.sleep(30);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public JComponent getCardInfoPane() {
return new CardInfoPaneImpl();
}
@Override
public BufferedImage getOriginalImage(CardView card) {
return ImageCache.getImageOriginal(card);
}
}

View file

@ -0,0 +1,22 @@
package org.mage.plugins.card.constants;
import java.awt.Rectangle;
import java.io.File;
public class Constants {
public static final String RESOURCE_PATH_MANA_LARGE = IO.imageBaseDir + File.separator + "symbols" + File.separator + "large";
public static final String RESOURCE_PATH_MANA_MEDIUM = IO.imageBaseDir + File.separator + "symbols" + File.separator + "medium";
public static final String RESOURCE_PATH_SET = IO.imageBaseDir + File.separator + "sets" + File.separator;
public static final String RESOURCE_PATH_SET_SMALL = RESOURCE_PATH_SET + File.separator + "small" + File.separator;
public static final Rectangle CARD_SIZE_FULL = new Rectangle(101, 149);
public static final Rectangle THUMBNAIL_SIZE_FULL = new Rectangle(102, 146);
public interface IO {
public static final String imageBaseDir = "plugins" + File.separator + "images";
public static final String IMAGE_PROPERTIES_FILE = "image.url.properties";
}
public static final String CARD_IMAGE_PATH_TEMPLATE = "." + File.separator + "plugins" + File.separator + "images/{set}/{name}.{collector}.full.jpg";
}

View file

@ -0,0 +1,195 @@
/**
* DownloadGui.java
*
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.IndexedPropertyChangeEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.BoundedRangeModel;
import javax.swing.BoxLayout;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import org.mage.plugins.card.dl.DownloadJob.State;
/**
* The class DownloadGui.
*
* @version V0.0 25.08.2010
* @author Clemens Koza
*/
public class DownloadGui extends JPanel {
private static final long serialVersionUID = -7346572382493844327L;
private final Downloader d;
private final DownloadListener l = new DownloadListener();
private final BoundedRangeModel model = new DefaultBoundedRangeModel(0, 0, 0, 0);
private final JProgressBar progress = new JProgressBar(model);
private final Map<DownloadJob, DownloadPanel> progresses = new HashMap<DownloadJob, DownloadPanel>();
private final JPanel panel = new JPanel();
public DownloadGui(Downloader d) {
super(new BorderLayout());
this.d = d;
d.addPropertyChangeListener(l);
JPanel p = new JPanel(new BorderLayout());
p.setBorder(BorderFactory.createTitledBorder("Progress:"));
p.add(progress);
JButton b = new JButton("X");
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getDownloader().dispose();
}
});
p.add(b, BorderLayout.EAST);
add(p, BorderLayout.NORTH);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
JScrollPane pane = new JScrollPane(panel);
pane.setPreferredSize(new Dimension(500, 300));
add(pane);
for(int i = 0; i < d.getJobs().size(); i++)
addJob(i, d.getJobs().get(i));
}
public Downloader getDownloader() {
return d;
}
private class DownloadListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if(evt.getSource() instanceof DownloadJob) {
DownloadPanel p = progresses.get(evt.getSource());
if("state".equals(name)) {
if(evt.getOldValue() == State.FINISHED || evt.getOldValue() == State.ABORTED) {
changeProgress(-1, 0);
}
if(evt.getNewValue() == State.FINISHED || evt.getOldValue() == State.ABORTED) {
changeProgress(+1, 0);
}
if(p != null) {
p.setVisible(p.getJob().getState() != State.FINISHED);
p.revalidate();
}
} else if("message".equals(name)) {
if(p != null) {
JProgressBar bar = p.getBar();
String message = p.getJob().getMessage();
bar.setStringPainted(message != null);
bar.setString(message);
}
}
} else if(evt.getSource() == d) {
if("jobs".equals(name)) {
IndexedPropertyChangeEvent ev = (IndexedPropertyChangeEvent) evt;
int index = ev.getIndex();
DownloadJob oldValue = (DownloadJob) ev.getOldValue();
if(oldValue != null) removeJob(index, oldValue);
DownloadJob newValue = (DownloadJob) ev.getNewValue();
if(newValue != null) addJob(index, newValue);
}
}
}
}
private synchronized void addJob(int index, DownloadJob job) {
job.addPropertyChangeListener(l);
changeProgress(0, +1);
DownloadPanel p = new DownloadPanel(job);
progresses.put(job, p);
panel.add(p, index);
panel.revalidate();
}
private synchronized void removeJob(int index, DownloadJob job) {
assert progresses.get(job) == panel.getComponent(index);
job.removePropertyChangeListener(l);
changeProgress(0, -1);
progresses.remove(job);
panel.remove(index);
panel.revalidate();
}
private synchronized void changeProgress(int progress, int total) {
progress += model.getValue();
total += model.getMaximum();
model.setMaximum(total);
model.setValue(progress);
this.progress.setStringPainted(true);
this.progress.setString(progress + "/" + total);
}
private class DownloadPanel extends JPanel {
private static final long serialVersionUID = 1187986738303477168L;
private DownloadJob job;
private JProgressBar bar;
public DownloadPanel(DownloadJob job) {
super(new BorderLayout());
this.job = job;
setBorder(BorderFactory.createTitledBorder(job.getName()));
add(bar = new JProgressBar(job.getProgress()));
JButton b = new JButton("X");
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
switch(getJob().getState()) {
case NEW:
case WORKING:
getJob().setState(State.ABORTED);
}
}
});
add(b, BorderLayout.EAST);
if(job.getState() == State.FINISHED | job.getState() == State.ABORTED) {
changeProgress(+1, 0);
}
setVisible(job.getState() != State.FINISHED);
String message = job.getMessage();
bar.setStringPainted(message != null);
bar.setString(message);
Dimension d = getPreferredSize();
d.width = Integer.MAX_VALUE;
setMaximumSize(d);
// d.width = 500;
// setMinimumSize(d);
}
public DownloadJob getJob() {
return job;
}
public JProgressBar getBar() {
return bar;
}
}
}

View file

@ -0,0 +1,213 @@
/**
* DownloadJob.java
*
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import javax.swing.BoundedRangeModel;
import javax.swing.DefaultBoundedRangeModel;
import org.mage.plugins.card.dl.beans.properties.Property;
import org.mage.plugins.card.dl.lm.AbstractLaternaBean;
/**
* The class DownloadJob.
*
* @version V0.0 25.08.2010
* @author Clemens Koza
*/
public class DownloadJob extends AbstractLaternaBean {
public static enum State {
NEW, WORKING, FINISHED, ABORTED;
}
private final String name;
private final Source source;
private final Destination destination;
private final Property<State> state = properties.property("state", State.NEW);
private final Property<String> message = properties.property("message");
private final Property<Exception> error = properties.property("error");
private final BoundedRangeModel progress = new DefaultBoundedRangeModel();
public DownloadJob(String name, Source source, Destination destination) {
this.name = name;
this.source = source;
this.destination = destination;
}
/**
* Sets the job's state. If the state is {@link State#ABORTED}, it instead sets the error to "ABORTED"
*/
public void setState(State state) {
if(state == State.ABORTED) setError("ABORTED");
else this.state.setValue(state);
}
/**
* Sets the job's state to {@link State#ABORTED} and the error message to the given message. Logs a warning
* with the given message.
*/
public void setError(String message) {
setError(message, null);
}
/**
* Sets the job's state to {@link State#ABORTED} and the error to the given exception. Logs a warning with the
* given exception.
*/
public void setError(Exception error) {
setError(null, error);
}
/**
* Sets the job's state to {@link State#ABORTED} and the error to the given exception. Logs a warning with the
* given message and exception.
*/
public void setError(String message, Exception error) {
if(message == null) message = error.toString();
log.warn(message, error);
this.state.setValue(State.ABORTED);
this.error.setValue(error);
this.message.setValue(message);
}
/**
* Sets the job's message.
*/
public void setMessage(String message) {
this.message.setValue(message);
}
public BoundedRangeModel getProgress() {
return progress;
}
public State getState() {
return state.getValue();
}
public Exception getError() {
return error.getValue();
}
public String getMessage() {
return message.getValue();
}
public String getName() {
return name;
}
public Source getSource() {
return source;
}
public Destination getDestination() {
return destination;
}
public static Source fromURL(final String url) {
return fromURL(null, url);
}
public static Source fromURL(final URL url) {
return fromURL(null, url);
}
public static Source fromURL(final Proxy proxy, final String url) {
return new Source() {
private URLConnection c;
public URLConnection getConnection() throws IOException {
if(c == null) c = proxy == null? new URL(url).openConnection():new URL(url).openConnection(proxy);
return c;
}
@Override
public InputStream open() throws IOException {
return getConnection().getInputStream();
}
@Override
public int length() throws IOException {
return getConnection().getContentLength();
}
};
}
public static Source fromURL(final Proxy proxy, final URL url) {
return new Source() {
private URLConnection c;
public URLConnection getConnection() throws IOException {
if(c == null) c = proxy == null? url.openConnection():url.openConnection(proxy);
return c;
}
@Override
public InputStream open() throws IOException {
return getConnection().getInputStream();
}
@Override
public int length() throws IOException {
return getConnection().getContentLength();
}
};
}
public static Destination toFile(final String file) {
return toFile(new File(file));
}
public static Destination toFile(final File file) {
return new Destination() {
@Override
public OutputStream open() throws IOException {
File parent = file.getAbsoluteFile().getParentFile();
//Trying to create the directory before checking it exists makes it threadsafe
if(!parent.mkdirs() && !parent.exists()) throw new IOException(parent
+ ": directory could not be created");
return new FileOutputStream(file);
}
@Override
public boolean exists() {
return file.isFile();
}
@Override
public void delete() throws IOException {
if(file.exists() && !file.delete()) throw new IOException(file + " couldn't be deleted");
}
};
}
public static interface Source {
public InputStream open() throws IOException;
public int length() throws IOException;
}
public static interface Destination {
public OutputStream open() throws IOException;
public boolean exists() throws IOException;
public void delete() throws IOException;
}
}

View file

@ -0,0 +1,157 @@
/**
* Downloader.java
*
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.BoundedRangeModel;
import org.apache.log4j.Logger;
import org.jetlang.channels.Channel;
import org.jetlang.channels.MemoryChannel;
import org.jetlang.core.Callback;
import org.jetlang.core.Disposable;
import org.jetlang.fibers.Fiber;
import org.jetlang.fibers.PoolFiberFactory;
import org.mage.plugins.card.dl.DownloadJob.Destination;
import org.mage.plugins.card.dl.DownloadJob.Source;
import org.mage.plugins.card.dl.DownloadJob.State;
import org.mage.plugins.card.dl.lm.AbstractLaternaBean;
/**
* The class Downloader.
*
* @version V0.0 25.08.2010
* @author Clemens Koza
*/
public class Downloader extends AbstractLaternaBean implements Disposable {
private static final Logger log = Logger.getLogger(Downloader.class);
private final List<DownloadJob> jobs = properties.list("jobs");
private final Channel<DownloadJob> channel = new MemoryChannel<DownloadJob>();
private final ExecutorService pool = Executors.newCachedThreadPool();
private final List<Fiber> fibers = new ArrayList<Fiber>();
public Downloader() {
PoolFiberFactory f = new PoolFiberFactory(pool);
//subscribe multiple fibers for parallel execution
for(int i = 0, numThreads = 10; i < numThreads; i++) {
Fiber fiber = f.create();
fiber.start();
fibers.add(fiber);
channel.subscribe(fiber, new DownloadCallback());
}
}
@Override
public void dispose() {
for(DownloadJob j:getJobs()) {
switch(j.getState()) {
case NEW:
case WORKING:
j.setState(State.ABORTED);
}
}
for(Fiber f:fibers)
f.dispose();
pool.shutdown();
}
@Override
protected void finalize() throws Throwable {
dispose();
super.finalize();
}
public void add(DownloadJob job) {
if(job.getState() == State.WORKING) throw new IllegalArgumentException("Job already running");
if(job.getState() == State.FINISHED) throw new IllegalArgumentException("Job already finished");
job.setState(State.NEW);
jobs.add(job);
channel.publish(job);
}
public List<DownloadJob> getJobs() {
return jobs;
}
/**
* Performs the download job: Transfers data from {@link Source} to {@link Destination} and updates the
* download job's state to reflect the progress.
*/
private class DownloadCallback implements Callback<DownloadJob> {
@Override
public void onMessage(DownloadJob job) {
//the job won't be processed by multiple threads
synchronized(job) {
if(job.getState() != State.NEW) return;
job.setState(State.WORKING);
}
try {
Source src = job.getSource();
Destination dst = job.getDestination();
BoundedRangeModel progress = job.getProgress();
if(dst.exists()) {
progress.setMaximum(1);
progress.setValue(1);
} else {
progress.setMaximum(src.length());
InputStream is = new BufferedInputStream(src.open());
try {
OutputStream os = new BufferedOutputStream(dst.open());
try {
byte[] buf = new byte[8 * 1024];
int total = 0;
for(int len; (len = is.read(buf)) != -1;) {
if(job.getState() == State.ABORTED) throw new IOException("Job was aborted");
progress.setValue(total += len);
os.write(buf, 0, len);
}
} catch(IOException ex) {
try {
dst.delete();
} catch(IOException ex1) {
log.warn("While deleting", ex1);
}
throw ex;
} finally {
try {
os.close();
} catch(IOException ex) {
log.warn("While closing", ex);
}
}
} finally {
try {
is.close();
} catch(IOException ex) {
log.warn("While closing", ex);
}
}
}
job.setState(State.FINISHED);
} catch(IOException ex) {
job.setError(ex);
}
}
}
}

View file

@ -0,0 +1,37 @@
/**
* AbstractBoundBean.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans;
import java.beans.PropertyChangeListener;
/**
* The class AbstractBoundBean.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public abstract class AbstractBoundBean implements BoundBean {
protected PropertyChangeSupport s = new PropertyChangeSupport(this);
public void addPropertyChangeListener(PropertyChangeListener listener) {
s.addPropertyChangeListener(listener);
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
s.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
s.removePropertyChangeListener(listener);
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
s.removePropertyChangeListener(propertyName, listener);
}
}

View file

@ -0,0 +1,27 @@
/**
* BoundBean.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans;
import java.beans.PropertyChangeListener;
/**
* The class BoundBean.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public interface BoundBean {
public void addPropertyChangeListener(PropertyChangeListener listener);
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener);
public void removePropertyChangeListener(PropertyChangeListener listener);
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener);
}

View file

@ -0,0 +1,121 @@
/**
* EventListenerList.java
*
* Created on 08.04.2010
*/
package org.mage.plugins.card.dl.beans;
import static java.util.Arrays.*;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import com.google.common.base.Function;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
* The class EventListenerList.
*
* @version V0.0 08.04.2010
* @author Clemens Koza
*/
public class EventListenerList extends javax.swing.event.EventListenerList {
private static final long serialVersionUID = -7545754245081842909L;
/**
* Returns an iterable over all listeners for the specified classes. the listener classes are in the specified
* order. for every class, listeners are in the reverse order of registering. A listener contained multiple
* times (for a single or multiple classes) is only returned the first time it occurs.
*/
public <T extends EventListener> Iterable<T> getIterable(final Class<? extends T>... listenerClass) {
//transform class -> iterable
List<Iterable<T>> l = Lists.transform(asList(listenerClass), new ClassToIterableFunction<T>());
//compose iterable (use an arraylist to memoize the function's results)
final Iterable<T> it = Iterables.concat(new ArrayList<Iterable<T>>(l));
//transform to singleton iterators
return new Iterable<T>() {
public Iterator<T> iterator() {
return new SingletonIterator<T>(it.iterator());
}
};
}
/**
* Returns an iterator over all listeners for the specified classes. the listener classes are in the specified
* order. for every class, listeners are in the reverse order of registering. A listener contained multiple
* times (for a single or multiple classes) is only returned the first time it occurs.
*/
public <T extends EventListener> Iterator<T> getIterator(Class<? extends T>... listenerClass) {
return getIterable(listenerClass).iterator();
}
/**
* Iterates backwards over the listeners registered for a class by using the original array. The Listener runs
* backwards, just as listener notification usually works.
*/
private class ListenerIterator<T> extends AbstractIterator<T> {
private final Class<? extends T> listenerClass;
private Object[] listeners = listenerList;
private int index = listeners.length;
private ListenerIterator(Class<? extends T> listenerClass) {
this.listenerClass = listenerClass;
}
@Override
@SuppressWarnings("unchecked")
protected T computeNext() {
for(index -= 2; index >= 0; index -= 2) {
if(listenerClass == listeners[index]) return (T) listeners[index + 1];
}
return endOfData();
}
}
/**
* Transforms a class to the associated listener iterator
*/
private class ClassToIterableFunction<T> implements Function<Class<? extends T>, Iterable<T>> {
public Iterable<T> apply(final Class<? extends T> from) {
return new Iterable<T>() {
public Iterator<T> iterator() {
return new ListenerIterator<T>(from);
}
};
}
}
/**
* Filters the delegate iterator so that every but the first occurrence of every element is ignored.
*/
private static class SingletonIterator<T> extends AbstractIterator<T> {
private Iterator<T> it;
private HashSet<T> previous = new HashSet<T>();
public SingletonIterator(Iterator<T> it) {
this.it = it;
}
@Override
protected T computeNext() {
while(it.hasNext()) {
T next = it.next();
if(previous.add(next)) return next;
}
return endOfData();
}
}
}

View file

@ -0,0 +1,31 @@
/**
* PropertyChangeSupport.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans;
/**
* The class PropertyChangeSupport.
*
* @version V0.0 16.07.2010
* @author Clemens Koza
*/
public class PropertyChangeSupport extends java.beans.PropertyChangeSupport {
private static final long serialVersionUID = -4241465377828790076L;
private Object sourceBean;
public PropertyChangeSupport(Object sourceBean) {
super(sourceBean);
this.sourceBean = sourceBean;
}
public Object getSourceBean() {
return sourceBean;
}
}

View file

@ -0,0 +1,360 @@
/**
* ListenableCollections.java
*
* Created on 25.04.2010
*/
package org.mage.plugins.card.dl.beans.collections;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.AbstractSequentialList;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.RandomAccess;
import java.util.Set;
/**
* The class ListenableCollections supports applications that need to listen for modifications on different
* collections. Unlike most listener models, a listenable collection does not have public methods for adding or
* removing listeners. Instead, the wrapping methods take exactly one listener, because listening to collections is
* low-level. That single listener will usually manage multiple high-level listeners.
*
* @version V0.0 25.04.2010
* @author Clemens Koza
*/
public final class ListenableCollections {
private ListenableCollections() {}
public static <E> List<E> listenableList(List<E> list, ListListener<E> listener) {
if(list instanceof RandomAccess) return new ListenableList<E>(list, listener);
else return new ListenableSequentialList<E>(list, listener);
}
public static <E> Set<E> listenableSet(Set<E> set, SetListener<E> listener) {
return new ListenableSet<E>(set, listener);
}
public static <K, V> Map<K, V> listenableMap(Map<K, V> map, MapListener<K, V> listener) {
return new ListenableMap<K, V>(map, listener);
}
public static interface ListListener<E> extends Serializable {
/**
* Notified after a value was added to the list.
*/
public void add(int index, E newValue);
/**
* Notified after a value in the list was changed.
*/
public void set(int index, E oldValue, E newValue);
/**
* Notified after a value was removed from the list.
*/
public void remove(int index, E oldValue);
}
private static class ListenableList<E> extends AbstractList<E> implements RandomAccess, Serializable {
private static final long serialVersionUID = 8622608480525537692L;
private List<E> delegate;
private ListListener<E> listener;
public ListenableList(List<E> delegate, ListListener<E> listener) {
this.delegate = delegate;
this.listener = listener;
}
@Override
public void add(int index, E e) {
delegate.add(index, e);
listener.add(index, e);
}
@Override
public E set(int index, E element) {
E e = delegate.set(index, element);
listener.set(index, e, element);
return e;
}
@Override
public E remove(int index) {
E e = delegate.remove(index);
listener.remove(index, e);
return e;
}
@Override
public E get(int index) {
return delegate.get(index);
}
@Override
public int size() {
return delegate.size();
}
}
private static class ListenableSequentialList<E> extends AbstractSequentialList<E> implements Serializable {
private static final long serialVersionUID = 3630474556578001885L;
private List<E> delegate;
private ListListener<E> listener;
public ListenableSequentialList(List<E> delegate, ListListener<E> listener) {
this.delegate = delegate;
this.listener = listener;
}
@Override
public ListIterator<E> listIterator(final int index) {
return new ListIterator<E>() {
private final ListIterator<E> it = delegate.listIterator(index);
private int lastIndex;
private E lastValue;
public boolean hasNext() {
return it.hasNext();
}
public boolean hasPrevious() {
return it.hasPrevious();
}
public E next() {
lastIndex = it.nextIndex();
lastValue = it.next();
return lastValue;
}
public int nextIndex() {
return it.nextIndex();
}
public E previous() {
lastIndex = it.previousIndex();
lastValue = it.previous();
return lastValue;
}
public int previousIndex() {
return it.previousIndex();
}
public void add(E o) {
it.add(o);
listener.add(previousIndex(), o);
}
public void set(E o) {
it.set(o);
listener.set(lastIndex, lastValue, o);
}
public void remove() {
it.remove();
listener.remove(lastIndex, lastValue);
}
};
}
@Override
public int size() {
return delegate.size();
}
}
public static interface SetListener<E> extends Serializable {
/**
* Notified after a value was added to the set.
*/
public void add(E newValue);
/**
* Notified after a value was removed from the set.
*/
public void remove(E oldValue);
}
private static class ListenableSet<E> extends AbstractSet<E> implements Serializable {
private static final long serialVersionUID = 7728087988927063221L;
private Set<E> delegate;
private SetListener<E> listener;
public ListenableSet(Set<E> set, SetListener<E> listener) {
delegate = set;
this.listener = listener;
}
@Override
public boolean contains(Object o) {
return delegate.contains(o);
}
@Override
public boolean add(E o) {
boolean b = delegate.add(o);
if(b) listener.add(o);
return b;
};
@SuppressWarnings("unchecked")
@Override
public boolean remove(Object o) {
boolean b = delegate.remove(o);
if(b) listener.remove((E) o);
return b;
}
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
private final Iterator<E> it = delegate.iterator();
private boolean hasLast;
private E last;
public boolean hasNext() {
return it.hasNext();
}
public E next() {
last = it.next();
hasLast = true;
return last;
}
public void remove() {
if(!hasLast) throw new IllegalStateException();
it.remove();
listener.remove(last);
}
};
}
@Override
public int size() {
return delegate.size();
}
}
public static interface MapListener<K, V> extends Serializable {
/**
* Notified after a value was put into the map.
*/
public void put(K key, V newValue);
/**
* Notified after a value in the map was changed.
*/
public void set(K key, V oldValue, V newValue);
/**
* Notified after a value was removed from the map.
*/
public void remove(K key, V oldValue);
}
private static class ListenableMap<K, V> extends AbstractMap<K, V> implements Serializable {
private static final long serialVersionUID = 4032087477448965103L;
private Map<K, V> delegate;
private MapListener<K, V> listener;
private Set<Entry<K, V>> entrySet;
public ListenableMap(Map<K, V> map, MapListener<K, V> listener) {
this.listener = listener;
delegate = map;
entrySet = new EntrySet();
}
@Override
public V put(K key, V value) {
if(delegate.containsKey(key)) {
V old = delegate.put(key, value);
listener.set(key, old, value);
return old;
} else {
delegate.put(key, value);
listener.put(key, value);
return null;
}
}
@SuppressWarnings("unchecked")
@Override
public V remove(Object key) {
if(delegate.containsKey(key)) {
V old = delegate.remove(key);
listener.remove((K) key, old);
return old;
} else return null;
}
@Override
public V get(Object key) {
return delegate.get(key);
}
@Override
public boolean containsKey(Object key) {
return delegate.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return delegate.containsValue(value);
}
@Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
return entrySet;
}
private final class EntrySet extends AbstractSet<Entry<K, V>> implements Serializable {
private static final long serialVersionUID = -780485106953107075L;
private final Set<Entry<K, V>> delegate = ListenableMap.this.delegate.entrySet();
@Override
public int size() {
return delegate.size();
}
@Override
public Iterator<Entry<K, V>> iterator() {
return new Iterator<Entry<K, V>>() {
private final Iterator<Entry<K, V>> it = delegate.iterator();
private boolean hasLast;
private Entry<K, V> last;
public boolean hasNext() {
return it.hasNext();
}
public Entry<K, V> next() {
last = it.next();
hasLast = true;
return last;
}
public void remove() {
if(!hasLast) throw new IllegalStateException();
hasLast = false;
it.remove();
listener.remove(last.getKey(), last.getValue());
}
};
}
}
}
}

View file

@ -0,0 +1,17 @@
/**
* This package contains bean-related utilities, most importantly
* <ul>
* <li>{@link net.slightlymagic.beans.BoundBean} and {@link net.slightlymagic.beans.AbstractBoundBean}, an interface and an
* abstract class for easier property change support for bean classes</li>
* <li>{@link net.slightlymagic.beans.properties.Properties} and its implementations. These make it easy for beans
* to use delegates, by providing a centralized configuration for bean properties. For example, a {@link
* net.slightlymagic.beans.properties.bound.BoundProperties} object creates properties that do automatic property
* change notifications. What exact configuration is used is thus hidden from the delegate.</li>
* <li>The {@link net.slightlymagic.beans.relational.Relations} class provides relational properties that model
* bidirectional 1:1, 1:n and m:n relationships.</li>
* </ul>
*/
package org.mage.plugins.card.dl.beans;

View file

@ -0,0 +1,47 @@
/**
* AbstractProperties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.mage.plugins.card.dl.beans.properties.basic.BasicProperty;
/**
* The class AbstractProperties.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public abstract class AbstractProperties implements Properties {
public <T> Property<T> property(String name, T value) {
return property(name, new BasicProperty<T>(value));
}
public <T> Property<T> property(String name) {
return property(name, new BasicProperty<T>());
}
public <E> List<E> list(String name) {
return list(name, new ArrayList<E>());
}
public <E> Set<E> set(String name) {
return set(name, new HashSet<E>());
}
public <K, V> Map<K, V> map(String name) {
return map(name, new HashMap<K, V>());
}
}

View file

@ -0,0 +1,38 @@
/**
* AbstractProperty.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;
import static java.lang.String.*;
/**
* The class AbstractProperty.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public abstract class AbstractProperty<T> implements Property<T> {
@Override
public int hashCode() {
T value = getValue();
return value == null? 0:value.hashCode();
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof Property<?>)) return false;
Object value = getValue();
Object other = ((Property<?>) obj).getValue();
return value == other || (value != null && value.equals(other));
}
@Override
public String toString() {
return valueOf(getValue());
}
}

View file

@ -0,0 +1,61 @@
/**
* CompoundProperties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;
import static java.util.Arrays.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The class CompoundProperties.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public class CompoundProperties extends AbstractProperties {
private List<Properties> delegates;
public CompoundProperties(Properties... delegates) {
this.delegates = asList(delegates);
Collections.reverse(this.delegates);
}
public CompoundProperties(List<Properties> delegates) {
this.delegates = new ArrayList<Properties>(delegates);
Collections.reverse(this.delegates);
}
public <T> Property<T> property(String name, Property<T> property) {
for(Properties p:delegates)
property = p.property(name, property);
return property;
}
public <E> List<E> list(String name, List<E> list) {
for(Properties p:delegates)
list = p.list(name, list);
return list;
}
public <E> Set<E> set(String name, Set<E> set) {
for(Properties p:delegates)
set = p.set(name, set);
return set;
}
public <K, V> Map<K, V> map(String name, Map<K, V> map) {
for(Properties p:delegates)
map = p.map(name, map);
return map;
}
}

View file

@ -0,0 +1,44 @@
/**
* Properties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.mage.plugins.card.dl.beans.properties.bound.BoundProperties;
/**
* The class Properties. A Properties object is a factory for bean properties. For example, the
* {@link BoundProperties} class produces properties that can be observed using {@link PropertyChangeListener}s.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public interface Properties {
public <T> Property<T> property(String name, Property<T> property);
public <E> List<E> list(String name, List<E> list);
public <E> Set<E> set(String name, Set<E> set);
public <K, V> Map<K, V> map(String name, Map<K, V> map);
public <T> Property<T> property(String name, T value);
public <T> Property<T> property(String name);
public <E> List<E> list(String name);
public <E> Set<E> set(String name);
public <K, V> Map<K, V> map(String name);
}

View file

@ -0,0 +1,32 @@
/**
* Property.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties;
/**
* The class Property.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public interface Property<T> {
public void setValue(T value);
public T getValue();
/**
* A property's hash code is its value's hashCode, or {@code null} if the value is null.
*/
@Override
public int hashCode();
/**
* Two properties are equal if their values are equal.
*/
@Override
public boolean equals(Object obj);
}

View file

@ -0,0 +1,40 @@
/**
* BoundProperties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties.basic;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.mage.plugins.card.dl.beans.properties.AbstractProperties;
import org.mage.plugins.card.dl.beans.properties.Property;
/**
* The class BoundProperties.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public class BasicProperties extends AbstractProperties {
public <T> Property<T> property(String name, Property<T> value) {
return value;
}
public <E> List<E> list(String name, List<E> list) {
return list;
}
public <E> Set<E> set(String name, Set<E> set) {
return set;
}
public <K, V> Map<K, V> map(String name, Map<K, V> map) {
return map;
}
}

View file

@ -0,0 +1,37 @@
/**
* BasicProperty.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.basic;
import org.mage.plugins.card.dl.beans.properties.AbstractProperty;
/**
* The class BasicProperty.
*
* @version V0.0 16.07.2010
* @author Clemens Koza
*/
public class BasicProperty<T> extends AbstractProperty<T> {
private T value;
public BasicProperty() {
this(null);
}
public BasicProperty(T initialValue) {
value = initialValue;
}
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}

View file

@ -0,0 +1,56 @@
/**
* BoundProperties.java
*
* Created on 24.08.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;
import static org.mage.plugins.card.dl.beans.collections.ListenableCollections.listenableList;
import static org.mage.plugins.card.dl.beans.collections.ListenableCollections.listenableMap;
import static org.mage.plugins.card.dl.beans.collections.ListenableCollections.listenableSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.mage.plugins.card.dl.beans.PropertyChangeSupport;
import org.mage.plugins.card.dl.beans.properties.AbstractProperties;
import org.mage.plugins.card.dl.beans.properties.Property;
/**
* The class BoundProperties.
*
* @version V0.0 24.08.2010
* @author Clemens Koza
*/
public class BoundProperties extends AbstractProperties {
public final PropertyChangeSupport s;
public BoundProperties(Object sourceBean) {
this(new PropertyChangeSupport(sourceBean));
}
public BoundProperties(PropertyChangeSupport s) {
if(s == null) throw new IllegalArgumentException("s == null");
this.s = s;
}
public <T> Property<T> property(String name, Property<T> property) {
return new BoundProperty<T>(s, name, property);
}
public <E> List<E> list(String name, List<E> list) {
return listenableList(list, new PropertyChangeListListener<E>(s, name));
}
public <E> Set<E> set(String name, Set<E> set) {
return listenableSet(set, new PropertyChangeSetListener<E>(s, set, name));
}
public <K, V> Map<K, V> map(String name, Map<K, V> map) {
return listenableMap(map, new PropertyChangeMapListener<K, V>(s, map, name));
}
}

View file

@ -0,0 +1,44 @@
/**
* BasicProperty.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;
import java.beans.PropertyChangeSupport;
import org.mage.plugins.card.dl.beans.properties.AbstractProperty;
import org.mage.plugins.card.dl.beans.properties.Property;
/**
* The class BasicProperty.
*
* @version V0.0 16.07.2010
* @author Clemens Koza
*/
public class BoundProperty<T> extends AbstractProperty<T> {
private PropertyChangeSupport s;
private String name;
private Property<T> property;
public BoundProperty(PropertyChangeSupport s, String name, Property<T> property) {
if(property == null) throw new IllegalArgumentException();
this.s = s;
this.name = name;
this.property = property;
}
public void setValue(T value) {
T old = getValue();
property.setValue(value);
s.firePropertyChange(name, old, getValue());
}
public T getValue() {
return property.getValue();
}
}

View file

@ -0,0 +1,43 @@
/**
* PropertyChangeListListener.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;
import org.mage.plugins.card.dl.beans.PropertyChangeSupport;
import org.mage.plugins.card.dl.beans.collections.ListenableCollections.ListListener;
/**
* The class PropertyChangeListListener.
*
* @version V0.0 16.07.2010
* @author Clemens Koza
*/
public class PropertyChangeListListener<E> implements ListListener<E> {
private static final long serialVersionUID = 625853864429729560L;
private PropertyChangeSupport s;
private String propertyName;
public PropertyChangeListListener(PropertyChangeSupport s, String propertyName) {
this.s = s;
this.propertyName = propertyName;
}
public void add(int index, E newValue) {
s.fireIndexedPropertyChange(propertyName, index, null, newValue);
}
public void set(int index, E oldValue, E newValue) {
s.fireIndexedPropertyChange(propertyName, index, oldValue, newValue);
}
public void remove(int index, E oldValue) {
s.fireIndexedPropertyChange(propertyName, index, oldValue, null);
}
}

View file

@ -0,0 +1,126 @@
/**
* PropertyChangeMapListener.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;
import java.beans.PropertyChangeEvent;
import java.util.Map;
import org.mage.plugins.card.dl.beans.PropertyChangeSupport;
import org.mage.plugins.card.dl.beans.collections.ListenableCollections.MapListener;
/**
* The class PropertyChangeMapListener. This listener alway fires events with {@link Map} -> {@link Map} as the
* value parameters, as nonindexed collection properties. The events will be {@link MapEvent} instances.
*
* @version V0.0 16.07.2010
* @author Clemens Koza
*/
public class PropertyChangeMapListener<K, V> implements MapListener<K, V> {
private static final long serialVersionUID = 625853864429729560L;
private PropertyChangeSupport s;
private Map<K, V> map;
private String propertyName;
public PropertyChangeMapListener(PropertyChangeSupport s, Map<K, V> map, String propertyName) {
this.s = s;
this.map = map;
this.propertyName = propertyName;
}
public void put(K key, V newValue) {
s.firePropertyChange(new MapPutEvent<K, V>(s.getSourceBean(), propertyName, map, key, newValue));
}
public void set(K key, V oldValue, V newValue) {
s.firePropertyChange(new MapSetEvent<K, V>(s.getSourceBean(), propertyName, map, key, oldValue, newValue));
}
public void remove(K key, V oldValue) {
s.firePropertyChange(new MapRemoveEvent<K, V>(s.getSourceBean(), propertyName, map, key, oldValue));
}
public static abstract class MapEvent<K, V> extends PropertyChangeEvent {
private static final long serialVersionUID = -651568020675693544L;
private Map<K, V> map;
private K key;
public MapEvent(Object source, String propertyName, Map<K, V> map, K key) {
super(source, propertyName, null, null);
this.map = map;
this.key = key;
}
@Override
public Map<K, V> getOldValue() {
//old and new value must not be equal
return null;
}
@Override
public Map<K, V> getNewValue() {
return map;
}
public K getKey() {
return key;
}
}
public static class MapPutEvent<K, V> extends MapEvent<K, V> {
private static final long serialVersionUID = 6006291474676939650L;
private V newElement;
public MapPutEvent(Object source, String propertyName, Map<K, V> map, K key, V newElement) {
super(source, propertyName, map, key);
this.newElement = newElement;
}
public V getNewElement() {
return newElement;
}
}
public static class MapSetEvent<K, V> extends MapEvent<K, V> {
private static final long serialVersionUID = -2419438379909500079L;
private V oldElement, newElement;
public MapSetEvent(Object source, String propertyName, Map<K, V> map, K key, V oldElement, V newElement) {
super(source, propertyName, map, key);
this.oldElement = oldElement;
this.newElement = newElement;
}
public V getOldElement() {
return oldElement;
}
public V getNewElement() {
return newElement;
}
}
public static class MapRemoveEvent<K, V> extends MapEvent<K, V> {
private static final long serialVersionUID = -2644879706878221895L;
private V oldElement;
public MapRemoveEvent(Object source, String propertyName, Map<K, V> map, K key, V oldElement) {
super(source, propertyName, map, key);
this.oldElement = oldElement;
}
public V getOldElement() {
return oldElement;
}
}
}

View file

@ -0,0 +1,96 @@
/**
* PropertyChangeSetListener.java
*
* Created on 16.07.2010
*/
package org.mage.plugins.card.dl.beans.properties.bound;
import java.beans.PropertyChangeEvent;
import java.util.Set;
import org.mage.plugins.card.dl.beans.PropertyChangeSupport;
import org.mage.plugins.card.dl.beans.collections.ListenableCollections.SetListener;
/**
* The class PropertyChangeSetListener. This listener always fires events with {@link Set} -> {@link Set} as the
* value parameters, as nonindexed collection properties. The events will be {@link SetEvent} instances.
*
* @version V0.0 16.07.2010
* @author Clemens Koza
*/
public class PropertyChangeSetListener<E> implements SetListener<E> {
private static final long serialVersionUID = 625853864429729560L;
private PropertyChangeSupport s;
private Set<E> set;
private String propertyName;
public PropertyChangeSetListener(PropertyChangeSupport s, Set<E> set, String propertyName) {
this.s = s;
this.set = set;
this.propertyName = propertyName;
}
public void add(E newValue) {
s.firePropertyChange(new SetAddEvent<E>(s.getSourceBean(), propertyName, set, newValue));
}
public void remove(E oldValue) {
s.firePropertyChange(new SetRemoveEvent<E>(s.getSourceBean(), propertyName, set, oldValue));
}
public static abstract class SetEvent<E> extends PropertyChangeEvent {
private static final long serialVersionUID = -651568020675693544L;
private Set<E> set;
public SetEvent(Object source, String propertyName, Set<E> set) {
super(source, propertyName, null, null);
this.set = set;
}
@Override
public Set<E> getOldValue() {
//old and new value must not be equal
return null;
}
@Override
public Set<E> getNewValue() {
return set;
}
}
public static class SetAddEvent<E> extends SetEvent<E> {
private static final long serialVersionUID = 9041766866796759871L;
private E newElement;
public SetAddEvent(Object source, String propertyName, Set<E> set, E newElement) {
super(source, propertyName, set);
this.newElement = newElement;
}
public E getNewElement() {
return newElement;
}
}
public static class SetRemoveEvent<E> extends SetEvent<E> {
private static final long serialVersionUID = -1315342339926392385L;
private E oldElement;
public SetRemoveEvent(Object source, String propertyName, Set<E> set, E oldElement) {
super(source, propertyName, set);
this.oldElement = oldElement;
}
public E getOldElement() {
return oldElement;
}
}
}

View file

@ -0,0 +1,27 @@
/**
* AbstractLaternaBean.java
*
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl.lm;
import org.apache.log4j.Logger;
import org.mage.plugins.card.dl.beans.AbstractBoundBean;
import org.mage.plugins.card.dl.beans.EventListenerList;
import org.mage.plugins.card.dl.beans.properties.Properties;
import org.mage.plugins.card.dl.beans.properties.bound.BoundProperties;
/**
* The class AbstractLaternaBean.
*
* @version V0.0 25.08.2010
* @author Clemens Koza
*/
public class AbstractLaternaBean extends AbstractBoundBean {
protected static final Logger log = Logger.getLogger(AbstractLaternaBean.class);
protected Properties properties = new BoundProperties(s);
protected EventListenerList listeners = new EventListenerList();
}

View file

@ -0,0 +1,12 @@
package org.mage.plugins.card.dl.sources;
/**
*
* @author North
*/
public interface CardImageSource {
String generateURL(Integer collectorId, String cardSet) throws Exception;
String generateTokenUrl(String name, String set);
Float getAverageSize();
}

View file

@ -0,0 +1,78 @@
package org.mage.plugins.card.dl.sources;
import org.mage.plugins.card.dl.DownloadJob;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import static org.mage.plugins.card.dl.DownloadJob.fromURL;
import static org.mage.plugins.card.dl.DownloadJob.toFile;
public class GathererSets implements Iterable<DownloadJob> {
private final static String SETS_PATH = File.separator + "sets";
private final static File DEFAULT_OUT_DIR = new File("plugins" + File.separator + "images" + SETS_PATH);
private static File outDir = DEFAULT_OUT_DIR;
private static final String[] symbols = {"DIS", "DST", "GPT", "RAV", "MRD", "10E", "HOP", "SHM", "EVE", "APC", "TMP", "CHK"};
private static final String[] withMythics = {"ALA", "CFX", "ARB", "ZEN", "WWK", "ROE", "SOM", "M10", "M11", "M12", "DDF", "MBS", "NPH"};
private static final HashMap<String, String> symbolsReplacements = new HashMap<String, String>();
static {
symbolsReplacements.put("CFX", "CON");
symbolsReplacements.put("APC", "AP");
symbolsReplacements.put("TMP", "TE");
}
public GathererSets(String path) {
if (path == null) {
useDefaultDir();
} else {
changeOutDir(path);
}
}
@Override
public Iterator<DownloadJob> iterator() {
ArrayList<DownloadJob> jobs = new ArrayList<DownloadJob>();
for (String symbol : symbols) {
jobs.add(generateDownloadJob(symbol, "C"));
jobs.add(generateDownloadJob(symbol, "U"));
jobs.add(generateDownloadJob(symbol, "R"));
}
for (String symbol : withMythics) {
jobs.add(generateDownloadJob(symbol, "C"));
jobs.add(generateDownloadJob(symbol, "U"));
jobs.add(generateDownloadJob(symbol, "R"));
jobs.add(generateDownloadJob(symbol, "M"));
}
return jobs.iterator();
}
private DownloadJob generateDownloadJob(String set, String rarity) {
File dst = new File(outDir, set + "-" + rarity + ".jpg");
if (symbolsReplacements.containsKey(set)) {
set = symbolsReplacements.get(set);
}
String url = "http://gatherer.wizards.com/Handlers/Image.ashx?type=symbol&set=" + set + "&size=small&rarity=" + rarity;
return new DownloadJob(set + "-" + rarity, fromURL(url), toFile(dst));
}
private void changeOutDir(String path) {
File file = new File(path + SETS_PATH);
if (file.exists()) {
outDir = file;
} else {
file.mkdirs();
if (file.exists()) {
outDir = file;
}
}
}
private void useDefaultDir() {
outDir = DEFAULT_OUT_DIR;
}
}

View file

@ -0,0 +1,109 @@
/**
* GathererSymbols.java
*
* Created on 25.08.2010
*/
package org.mage.plugins.card.dl.sources;
import com.google.common.collect.AbstractIterator;
import org.mage.plugins.card.dl.DownloadJob;
import java.io.File;
import java.util.Iterator;
import static java.lang.String.format;
import static org.mage.plugins.card.dl.DownloadJob.fromURL;
import static org.mage.plugins.card.dl.DownloadJob.toFile;
/**
* The class GathererSymbols.
*
* @version V0.0 25.08.2010
* @author Clemens Koza
*/
public class GathererSymbols implements Iterable<DownloadJob> {
//TODO chaos and planeswalker symbol
//chaos: http://gatherer.wizards.com/Images/Symbols/chaos.gif
private final static String SYMBOLS_PATH = File.separator + "symbols";
private final static File DEFAULT_OUT_DIR = new File("plugins" + File.separator + "images" + SYMBOLS_PATH);
private static File outDir = DEFAULT_OUT_DIR;
private static final String urlFmt = "http://gatherer.wizards.com/handlers/image.ashx?size=%1$s&name=%2$s&type=symbol";
private static final String[] sizes = {"small", "medium", "large"};
private static final String[] symbols = {"W", "U", "B", "R", "G",
"W/U", "U/B", "B/R", "R/G", "G/W", "W/B", "U/R", "B/G", "R/W", "G/U",
"2/W", "2/U", "2/B", "2/R", "2/G",
"WP", "UP", "BP", "RP", "GP",
"X", "S", "T", "Q"};
private static final int minNumeric = 0, maxNumeric = 16;
public GathererSymbols(String path) {
if (path == null) {
useDefaultDir();
} else {
changeOutDir(path);
}
}
@Override
public Iterator<DownloadJob> iterator() {
return new AbstractIterator<DownloadJob>() {
private int sizeIndex, symIndex, numeric = minNumeric;
private File dir = new File(outDir, sizes[sizeIndex]);
@Override
protected DownloadJob computeNext() {
String sym;
if(symIndex < symbols.length) {
sym = symbols[symIndex++];
} else if(numeric <= maxNumeric) {
sym = "" + (numeric++);
} else {
sizeIndex++;
if(sizeIndex == sizes.length) return endOfData();
symIndex = 0;
numeric = 0;
dir = new File(outDir, sizes[sizeIndex]);
return computeNext();
}
String symbol = sym.replaceAll("/", "");
File dst = new File(dir, symbol + ".jpg");
if(symbol.equals("T")) symbol = "tap";
else if(symbol.equals("Q")) symbol = "untap";
else if(symbol.equals("S")) symbol = "snow";
String url = format(urlFmt, sizes[sizeIndex], symbol);
return new DownloadJob(sym, fromURL(url), toFile(dst));
}
};
}
private void changeOutDir(String path) {
File file = new File(path + SYMBOLS_PATH);
if (file.exists()) {
outDir = file;
} else {
file.mkdirs();
if (file.exists()) {
outDir = file;
}
}
}
private void useDefaultDir() {
outDir = DEFAULT_OUT_DIR;
}
}

View file

@ -0,0 +1,82 @@
package org.mage.plugins.card.dl.sources;
import java.util.HashMap;
import java.util.Map;
import org.mage.plugins.card.utils.CardImageUtils;
/**
*
* @author North
*/
public class MagicCardsImageSource implements CardImageSource {
private static CardImageSource instance = new MagicCardsImageSource();
private static final Map<String, String> setNameReplacement = new HashMap<String, String>() {
{
put("NPH", "new-phyrexia");
put("MBS", "mirrodin-besieged");
put("SOM", "scars-of-mirrodin");
put("M11", "magic-2011");
put("ROE", "rise-of-the-eldrazi");
put("PVC", "duel-decks-phyrexia-vs-the-coalition");
put("WWK", "worldwake");
put("ZEN", "zendikar");
put("HOP", "planechase");
put("M10", "magic-2010");
put("GVL", "duel-decks-garruk-vs-liliana");
put("ARB", "alara-reborn");
put("DVD", "duel-decks-divine-vs-demonic");
put("CON", "conflux");
put("JVC", "duel-decks-jace-vs-chandra");
put("ALA", "shards-of-alara");
put("EVE", "eventide");
put("SHM", "shadowmoor");
put("EVG", "duel-decks-elves-vs-goblins");
put("MOR", "morningtide");
put("LRW", "lorwyn");
put("10E", "tenth-edition");
put("CSP", "coldsnap");
put("CHK", "player-rewards-2004");
}
private static final long serialVersionUID = 1L;
};
public static CardImageSource getInstance() {
if (instance == null) {
instance = new MagicCardsImageSource();
}
return instance;
}
@Override
public String generateURL(Integer collectorId, String cardSet) throws Exception {
if (collectorId == null || cardSet == null) {
throw new Exception("Wrong parameters for image: collector id: " + collectorId + ",card set: " + cardSet);
}
String set = CardImageUtils.updateSet(cardSet, true);
String url = "http://magiccards.info/scans/en/";
url += set.toLowerCase() + "/" + collectorId + ".jpg";
return url;
}
@Override
public String generateTokenUrl(String name, String set) {
String _name = name.replaceAll(" ", "-").toLowerCase();
String _set = "not-supported-set";
if (setNameReplacement.containsKey(set)) {
_set = setNameReplacement.get(set);
} else {
_set += "-" + set;
}
String url = "http://magiccards.info/extras/token/" + _set + "/" + _name + ".jpg";
return url;
}
@Override
public Float getAverageSize() {
return 70.0f;
}
}

View file

@ -0,0 +1,82 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.mage.plugins.card.dl.sources;
import java.util.HashMap;
import java.util.Map;
import org.mage.plugins.card.utils.CardImageUtils;
/**
*
* @author North
*/
public class MtgatheringRuImageSource implements CardImageSource {
private static CardImageSource hqInstance;
private static CardImageSource mqInstance;
private static CardImageSource lqInstance;
private static final Map setsAliases;
static {
setsAliases = new HashMap();
setsAliases.put("M11", "magic2011");
}
private String quality;
public static CardImageSource getHqInstance() {
if (hqInstance == null) {
hqInstance = new MtgatheringRuImageSource("hq");
}
return hqInstance;
}
public static CardImageSource getMqInstance() {
if (mqInstance == null) {
mqInstance = new MtgatheringRuImageSource("md");
}
return mqInstance;
}
public static CardImageSource getLqInstance() {
if (lqInstance == null) {
lqInstance = new MtgatheringRuImageSource("lq");
}
return lqInstance;
}
public MtgatheringRuImageSource(String quality) {
this.quality = quality;
}
@Override
public String generateURL(Integer collectorId, String cardSet) throws Exception {
if (collectorId == null || cardSet == null) {
throw new Exception("Wrong parameters for image: collector id: " + collectorId + ",card set: " + cardSet);
}
if (setsAliases.get(cardSet) != null) {
String set = CardImageUtils.updateSet(cardSet, true);
String url = "http://mtgathering.ru/scans/en/";
url += set.toLowerCase() + "/" + quality + "/" + collectorId + ".jpg";
return url;
}
return null;
}
@Override
public String generateTokenUrl(String name, String set) {
return null;
}
@Override
public Float getAverageSize() {
if(quality.equalsIgnoreCase("hq"))
return 80.0f;
if(quality.equalsIgnoreCase("md"))
return 30.0f;
if(quality.equalsIgnoreCase("lq"))
return 9.0f;
return 0f;
}
}

View file

@ -0,0 +1,95 @@
package org.mage.plugins.card.dl.sources;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
/**
*
* @author North
*/
public class WizardCardsImageSource implements CardImageSource {
private static CardImageSource instance;
private static Map setsAliases;
private Map sets;
public static CardImageSource getInstance() {
if (instance == null) {
instance = new WizardCardsImageSource();
}
return instance;
}
public WizardCardsImageSource() {
sets = new HashMap();
setsAliases = new HashMap();
setsAliases.put("M12", "magic2012/cig");
setsAliases.put("NPH", "newphyrexia/spoiler");
setsAliases.put("MBS", "mirrodinbesieged/spoiler");
setsAliases.put("SOM", "scarsofmirrodin/spoiler");
setsAliases.put("M11", "magic2011/spoiler");
setsAliases.put("ROE", "riseoftheeldrazi/spoiler");
setsAliases.put("WWK", "worldwake/spoiler");
setsAliases.put("ZEN", "zendikar/spoiler");
setsAliases.put("M10", "magic2010/spoiler");
setsAliases.put("ARB", "alarareborn/spoiler");
setsAliases.put("CON", "conflux/spoiler");
setsAliases.put("ALA", "shardsofalara/spoiler");
}
private List<String> getSetLinks(String cardSet) {
List<String> setLinks = new ArrayList<String>();
try {
Document doc = Jsoup.connect("http://www.wizards.com/magic/tcg/article.aspx?x=mtg/tcg/" + (String) setsAliases.get(cardSet)).get();
Elements cardsImages = doc.select("img[height$=370]");
for (int i = 0; i < cardsImages.size(); i++) {
setLinks.add(cardsImages.get(i).attr("src"));
}
} catch (IOException ex) {
System.out.println("Exception when parsing the wizards page: " + ex.getMessage());
}
return setLinks;
}
@Override
public String generateURL(Integer collectorId, String cardSet) throws Exception {
if (collectorId == null || cardSet == null) {
throw new Exception("Wrong parameters for image: collector id: " + collectorId + ",card set: " + cardSet);
}
if (setsAliases.get(cardSet) != null) {
List<String> setLinks = (List<String>) sets.get(cardSet);
if (setLinks == null) {
setLinks = getSetLinks(cardSet);
sets.put(cardSet, setLinks);
}
String link;
if (setLinks.size() >= collectorId) {
link = setLinks.get(collectorId - 1);
} else {
link = setLinks.get(collectorId - 21);
link = link.replace(Integer.toString(collectorId - 20), (Integer.toString(collectorId - 20) + "a"));
}
if (!link.startsWith("http://")) {
link = "http://www.wizards.com" + link;
}
return link;
}
return null;
}
@Override
public String generateTokenUrl(String name, String set) {
return null;
}
@Override
public Float getAverageSize() {
return 60.0f;
}
}

View file

@ -0,0 +1,100 @@
package org.mage.plugins.card.images;
/**
*
* @author North
*/
public class CardInfo {
private String name;
private String set;
private Integer collectorId;
private boolean token;
public CardInfo(String name, String set, Integer collectorId) {
this.name = name;
this.set = set;
this.collectorId = collectorId;
token = false;
}
public CardInfo(String name, String set, Integer collectorId, boolean token) {
this.name = name;
this.set = set;
this.collectorId = collectorId;
this.token = token;
}
public CardInfo(final CardInfo card) {
this.name = card.name;
this.set = card.set;
this.collectorId = card.collectorId;
this.token = card.token;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final CardInfo other = (CardInfo) obj;
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
return false;
}
if ((this.set == null) ? (other.set != null) : !this.set.equals(other.set)) {
return false;
}
if (this.collectorId != other.collectorId && (this.collectorId == null || !this.collectorId.equals(other.collectorId))) {
return false;
}
if (this.token != other.token) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 5;
hash = 47 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 47 * hash + (this.set != null ? this.set.hashCode() : 0);
hash = 47 * hash + (this.collectorId != null ? this.collectorId.hashCode() : 0);
hash = 47 * hash + (this.token ? 1 : 0);
return hash;
}
public Integer getCollectorId() {
return collectorId;
}
public void setCollectorId(Integer collectorId) {
this.collectorId = collectorId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSet() {
return set;
}
public void setSet(String set) {
this.set = set;
}
public boolean isToken() {
return token;
}
public void setToken(boolean token) {
this.token = token;
}
}

View file

@ -0,0 +1,538 @@
package org.mage.plugins.card.images;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.management.ImmutableDescriptor;
import javax.swing.AbstractButton;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import mage.cards.Card;
import org.apache.log4j.Logger;
import org.mage.plugins.card.constants.Constants;
import org.mage.plugins.card.dl.sources.CardImageSource;
import org.mage.plugins.card.dl.sources.MagicCardsImageSource;
import org.mage.plugins.card.dl.sources.MtgatheringRuImageSource;
import org.mage.plugins.card.dl.sources.WizardCardsImageSource;
import org.mage.plugins.card.properties.SettingsManager;
import org.mage.plugins.card.utils.CardImageUtils;
public class DownloadPictures extends DefaultBoundedRangeModel implements Runnable {
private int type;
private JTextField addr, port;
private JProgressBar bar;
private JOptionPane dlg;
private boolean cancel;
private JButton closeButton;
private JButton startDownloadButton;
private int cardIndex;
private ArrayList<CardInfo> cards;
private JComboBox jComboBox1;
private JLabel jLabel1;
private static boolean offlineMode = false;
private JCheckBox checkBox;
private final Object sync = new Object();
private String imagesPath;
private static CardImageSource cardImageSource;
private Proxy p;
private ExecutorService executor = Executors.newFixedThreadPool(10);
public static final Proxy.Type[] types = Proxy.Type.values();
public static void main(String[] args) {
startDownload(null, null, null);
}
public static void startDownload(JFrame frame, Set<Card> allCards, String imagesPath) {
ArrayList<CardInfo> cards = getNeededCards(allCards, imagesPath);
/*
* if (cards == null || cards.size() == 0) {
* JOptionPane.showMessageDialog(null,
* "All card pictures have been downloaded."); return; }
*/
DownloadPictures download = new DownloadPictures(cards, imagesPath);
JDialog dlg = download.getDlg(frame);
dlg.setVisible(true);
dlg.dispose();
download.setCancel(true);
}
public JDialog getDlg(JFrame frame) {
String title = "Downloading";
final JDialog dialog = this.dlg.createDialog(frame, title);
closeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dialog.setVisible(false);
}
});
return dialog;
}
public void setCancel(boolean cancel) {
this.cancel = cancel;
}
public DownloadPictures(ArrayList<CardInfo> cards, String imagesPath) {
this.cards = cards;
this.imagesPath = imagesPath;
addr = new JTextField("Proxy Address");
port = new JTextField("Proxy Port");
bar = new JProgressBar(this);
JPanel p0 = new JPanel();
p0.setLayout(new BoxLayout(p0, BoxLayout.Y_AXIS));
// Proxy Choice
ButtonGroup bg = new ButtonGroup();
String[] labels = { "No Proxy", "HTTP Proxy", "SOCKS Proxy" };
for (int i = 0; i < types.length; i++) {
JRadioButton rb = new JRadioButton(labels[i]);
rb.addChangeListener(new ProxyHandler(i));
bg.add(rb);
p0.add(rb);
if (i == 0)
rb.setSelected(true);
}
// Proxy config
p0.add(addr);
p0.add(port);
p0.add(Box.createVerticalStrut(5));
jLabel1 = new JLabel();
jLabel1.setText("Please select server:");
jLabel1.setAlignmentX(Component.LEFT_ALIGNMENT);
p0.add(jLabel1);
p0.add(Box.createVerticalStrut(5));
ComboBoxModel jComboBox1Model = new DefaultComboBoxModel(new String[] { "magiccards.info", "wizards.com", "mtgathering.ru HQ", "mtgathering.ru MQ", "mtgathering.ru LQ"});
jComboBox1 = new JComboBox();
cardImageSource = MagicCardsImageSource.getInstance();
jComboBox1.setModel(jComboBox1Model);
jComboBox1.setAlignmentX(Component.LEFT_ALIGNMENT);
jComboBox1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JComboBox cb = (JComboBox) e.getSource();
switch (cb.getSelectedIndex()) {
case 0:
cardImageSource = MagicCardsImageSource.getInstance();
break;
case 1:
cardImageSource = WizardCardsImageSource.getInstance();
break;
case 2:
cardImageSource = MtgatheringRuImageSource.getHqInstance();
break;
case 3:
cardImageSource = MtgatheringRuImageSource.getMqInstance();
break;
case 4:
cardImageSource = MtgatheringRuImageSource.getLqInstance();
break;
}
int count = DownloadPictures.this.cards.size();
float mb = (count * cardImageSource.getAverageSize()) / 1024;
bar.setString(String.format(cardIndex == count ? "%d of %d cards finished! Please close!"
: "%d of %d cards finished! Please wait! [%.1f Mb]", 0, count, mb));
}
});
p0.add(jComboBox1);
p0.add(Box.createVerticalStrut(5));
// Start
startDownloadButton = new JButton("Start download");
startDownloadButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new Thread(DownloadPictures.this).start();
startDownloadButton.setEnabled(false);
checkBox.setEnabled(false);
}
});
p0.add(Box.createVerticalStrut(5));
// Progress
p0.add(bar);
bar.setStringPainted(true);
int count = cards.size();
float mb = (count * cardImageSource.getAverageSize()) / 1024;
bar.setString(String.format(cardIndex == cards.size() ? "%d of %d cards finished! Please close!"
: "%d of %d cards finished! Please wait! [%.1f Mb]", 0, cards.size(), mb));
Dimension d = bar.getPreferredSize();
d.width = 300;
bar.setPreferredSize(d);
p0.add(Box.createVerticalStrut(5));
checkBox = new JCheckBox("Download for current game only.");
p0.add(checkBox);
p0.add(Box.createVerticalStrut(5));
checkBox.setEnabled(!offlineMode);
checkBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int count = DownloadPictures.this.cards.size();
float mb = (count * cardImageSource.getAverageSize()) / 1024;
bar.setString(String.format(cardIndex == count ? "%d of %d cards finished! Please close!"
: "%d of %d cards finished! Please wait! [%.1f Mb]", 0, count, mb));
}
});
// JOptionPane
Object[] options = { startDownloadButton, closeButton = new JButton("Cancel") };
dlg = new JOptionPane(p0, JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[1]);
}
private static ArrayList<CardInfo> getNeededCards(Set<Card> allCards, String imagesPath) {
ArrayList<CardInfo> cardsToDownload = new ArrayList<CardInfo>();
/**
* read all card names and urls
*/
ArrayList<CardInfo> allCardsUrls = new ArrayList<CardInfo>();
try {
offlineMode = true;
for (Card card : allCards) {
if (card.getCardNumber() > 0 && !card.getExpansionSetCode().isEmpty()) {
CardInfo url = new CardInfo(card.getName(), card.getExpansionSetCode(), card.getCardNumber(), false);
allCardsUrls.add(url);
} else {
if (card.getCardNumber() < 1) {
System.err.println("There was a critical error!");
log.error("Card has no collector ID and won't be sent to client: " + card);
} else if (card.getExpansionSetCode().isEmpty()) {
System.err.println("There was a critical error!");
log.error("Card has no set name and won't be sent to client:" + card);
}
}
}
allCardsUrls.addAll(getTokenCardUrls());
} catch (Exception e) {
log.error(e);
}
File file;
/**
* check to see which cards we already have
*/
for (CardInfo card : allCardsUrls) {
boolean withCollectorId = false;
if (card.getName().equals("Forest") || card.getName().equals("Mountain") || card.getName().equals("Swamp") || card.getName().equals("Island")
|| card.getName().equals("Plains")) {
withCollectorId = true;
}
file = new File(CardImageUtils.getImagePath(card, withCollectorId, imagesPath));
if (!file.exists()) {
cardsToDownload.add(card);
}
}
for (CardInfo card : cardsToDownload) {
if (card.isToken()) {
log.info("Card to download: " + card.getName() + " (Token) ");
} else {
try {
log.info("Card to download: " + card.getName() + " (" + card.getSet() + ")");
} catch (Exception e) {
log.error(e);
}
}
}
return cardsToDownload;
}
private static ArrayList<CardInfo> getTokenCardUrls() throws RuntimeException {
ArrayList<CardInfo> list = new ArrayList<CardInfo>();
HashSet<String> filter = new HashSet<String>();
InputStream in = DownloadPictures.class.getClassLoader().getResourceAsStream("card-pictures-tok.txt");
readImageURLsFromFile(in, list, filter);
return list;
}
private static void readImageURLsFromFile(InputStream in, ArrayList<CardInfo> list, Set<String> filter) throws RuntimeException {
if (in == null) {
log.error("resources input stream is null");
return;
}
BufferedReader reader = null;
InputStreamReader input = null;
try {
input = new InputStreamReader(in);
reader = new BufferedReader(input);
String line;
line = reader.readLine();
while (line != null) {
line = line.trim();
if (line.startsWith("|")) { // new format
String[] params = line.split("\\|");
if (params.length >= 4) {
if (params[1].toLowerCase().equals("generate") && params[2].startsWith("TOK:")) {
String set = params[2].substring(4);
CardInfo card = new CardInfo(params[3], set, 0, true);
list.add(card);
}
} else {
log.error("wrong format for image urls: " + line);
}
}
line = reader.readLine();
}
} catch (Exception ex) {
log.error(ex);
throw new RuntimeException("DownloadPictures : readFile() error");
} finally {
if (input != null) {
try {
input.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private class ProxyHandler implements ChangeListener {
private int type;
public ProxyHandler(int type) {
this.type = type;
}
@Override
public void stateChanged(ChangeEvent e) {
if (((AbstractButton) e.getSource()).isSelected()) {
DownloadPictures.this.type = type;
addr.setEnabled(type != 0);
port.setEnabled(type != 0);
}
}
}
@Override
public void run() {
File base = new File(this.imagesPath != null ? imagesPath : Constants.IO.imageBaseDir);
if (!base.exists()) {
base.mkdir();
}
if (type == 0)
p = Proxy.NO_PROXY;
else
try {
p = new Proxy(types[type], new InetSocketAddress(addr.getText(), Integer.parseInt(port.getText())));
} catch (Exception ex) {
throw new RuntimeException("Gui_DownloadPictures : error 1 - " + ex);
}
if (p != null) {
HashSet<String> ignoreUrls = SettingsManager.getIntance().getIgnoreUrls();
update(0);
for (int i = 0; i < cards.size() && !cancel; i++) {
try {
CardInfo card = cards.get(i);
log.info("Downloading card: " + card.getName() + " (" + card.getSet() + ")");
String url;
if (ignoreUrls.contains(card.getSet()) || card.isToken()) {
if (card.getCollectorId() != 0) {
continue;
}
url = cardImageSource.generateTokenUrl(card.getName(), card.getSet());
} else {
url = cardImageSource.generateURL(card.getCollectorId(), card.getSet());
}
if (url != null) {
Runnable task = new DownloadTask(card, new URL(url), imagesPath);
executor.execute(task);
} else {
synchronized (sync) {
update(cardIndex + 1);
}
}
} catch (Exception ex) {
log.error(ex, ex);
}
}
executor.shutdown();
while (!executor.isTerminated()) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {}
}
}
closeButton.setText("Close");
}
private final class DownloadTask implements Runnable {
private CardInfo card;
private URL url;
private String imagesPath;
public DownloadTask(CardInfo card, URL url, String imagesPath) {
this.card = card;
this.url = url;
this.imagesPath = imagesPath;
}
@Override
public void run() {
try {
BufferedInputStream in = new BufferedInputStream(url.openConnection(p).getInputStream());
createDirForCard(card, imagesPath);
boolean withCollectorId = false;
if (card.getName().equals("Forest") || card.getName().equals("Mountain") || card.getName().equals("Swamp")
|| card.getName().equals("Island") || card.getName().equals("Plains")) {
withCollectorId = true;
}
File fileOut = new File(CardImageUtils.getImagePath(card, withCollectorId));
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(fileOut));
byte[] buf = new byte[1024];
int len = 0;
while ((len = in.read(buf)) != -1) {
// user cancelled
if (cancel) {
in.close();
out.flush();
out.close();
// delete what was written so far
fileOut.delete();
}
out.write(buf, 0, len);
}
in.close();
out.flush();
out.close();
synchronized (sync) {
update(cardIndex + 1);
}
} catch (Exception e) {
log.error(e, e);
}
}
}
private static File createDirForCard(CardInfo card, String imagesPath) throws Exception {
File setDir = new File(CardImageUtils.getImageDir(card, imagesPath));
if (!setDir.exists()) {
setDir.mkdirs();
}
return setDir;
}
private void update(int card) {
this.cardIndex = card;
int count = DownloadPictures.this.cards.size();
if (cardIndex < count) {
float mb = ((count - card) * cardImageSource.getAverageSize()) / 1024;
bar.setString(String.format("%d of %d cards finished! Please wait! [%.1f Mb]",
card, count, mb));
} else {
Iterator<CardInfo> cardsIterator = DownloadPictures.this.cards.iterator();
while (cardsIterator.hasNext()) {
CardInfo cardInfo = cardsIterator.next();
boolean withCollectorId = false;
if (cardInfo.getName().equals("Forest") || cardInfo.getName().equals("Mountain") || cardInfo.getName().equals("Swamp") || cardInfo.getName().equals("Island")
|| cardInfo.getName().equals("Plains")) {
withCollectorId = true;
}
File file = new File(CardImageUtils.getImagePath(cardInfo, withCollectorId));
if (file.exists()) {
cardsIterator.remove();
}
}
count = DownloadPictures.this.cards.size();
if (count == 0) {
bar.setString(String.format("0 cards remaining! Please close!", count));
} else {
bar.setString(String.format("%d cards remaining! Please choose another source!", count));
executor = Executors.newFixedThreadPool(10);
startDownloadButton.setEnabled(true);
}
}
}
private static final Logger log = Logger.getLogger(DownloadPictures.class);
private static final long serialVersionUID = 1L;
}

View file

@ -0,0 +1,253 @@
package org.mage.plugins.card.images;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import mage.view.CardView;
import org.apache.log4j.Logger;
import org.mage.plugins.card.constants.Constants;
import org.mage.plugins.card.utils.CardImageUtils;
import com.google.common.base.Function;
import com.google.common.collect.ComputationException;
import com.google.common.collect.MapMaker;
import com.mortennobel.imagescaling.ResampleOp;
import java.awt.Graphics2D;
/**
* This class stores ALL card images in a cache with soft values. this means
* that the images may be collected when they are not needed any more, but will
* be kept as long as possible.
*
* Key format: "<cardname>#<setname>#<collectorID>#<param>"
*
* where param is:
*
* <ul>
* <li>#Normal: request for unrotated image</li>
* <li>#Tapped: request for rotated image</li>
* <li>#Cropped: request for cropped image that is used for Shandalar like card
* look</li>
* </ul>
*/
public class ImageCache {
private static final Logger log = Logger.getLogger(ImageCache.class);
private static final Map<String, BufferedImage> imageCache;
/**
* Common pattern for keys.
* Format: "<cardname>#<setname>#<collectorID>"
*/
private static final Pattern KEY_PATTERN = Pattern.compile("(.*)#(.*)#(.*)");
static {
imageCache = new MapMaker().softValues().makeComputingMap(new Function<String, BufferedImage>() {
@Override
public BufferedImage apply(String key) {
try {
boolean thumbnail = false;
if (key.endsWith("#thumb")) {
thumbnail = true;
key = key.replace("#thumb", "");
}
Matcher m = KEY_PATTERN.matcher(key);
if (m.matches()) {
String name = m.group(1);
String set = m.group(2);
Integer collectorId = Integer.parseInt(m.group(3));
CardInfo info = new CardInfo(name, set, collectorId);
if (collectorId == 0) info.setToken(true);
String path = CardImageUtils.getImagePath(info);
if (path == null) return null;
File file = new File(path);
if (thumbnail && path.endsWith(".jpg")) {
String thumbnailPath = path.replace(".jpg", ".thumb.jpg");
File thumbnailFile = new File(thumbnailPath);
if (thumbnailFile.exists()) {
//log.debug("loading thumbnail for " + key + ", path="+thumbnailPath);
return loadImage(thumbnailFile);
} else {
BufferedImage image = loadImage(file);
image = getWizardsCard(image);
if (image == null) return null;
//log.debug("creating thumbnail for " + key);
return makeThumbnail(image, thumbnailPath);
}
} else {
return getWizardsCard(loadImage(file));
}
} else {
throw new RuntimeException(
"Requested image doesn't fit the requirement for key (<cardname>#<setname>#<collectorID>): " + key);
}
} catch (Exception ex) {
if (ex instanceof ComputationException)
throw (ComputationException) ex;
else
throw new ComputationException(ex);
}
}
});
}
public static BufferedImage getWizardsCard(BufferedImage image) {
if (image.getWidth() == 265 && image.getHeight() == 370) {
BufferedImage crop = new BufferedImage(256, 360, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = crop.createGraphics();
graphics2D.drawImage(image, 0, 0, 255, 360, 5, 5, 261, 365, null);
graphics2D.dispose();
return crop;
} else {
return image;
}
}
public static BufferedImage getThumbnail(CardView card) {
String key = getKey(card) + "#thumb";
//log.debug("#key: " + key);
return getImage(key);
}
public static BufferedImage getImageOriginal(CardView card) {
String key = getKey(card);
//log.debug("#key: " + key);
return getImage(key);
}
/**
* Returns the Image corresponding to the key
*/
private static BufferedImage getImage(String key) {
try {
BufferedImage image = imageCache.get(key);
return image;
} 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;
} catch (ComputationException ex) {
if (ex.getCause() instanceof NullPointerException)
return null;
log.error(ex,ex);
return null;
}
}
/**
* Returns the map key for a card, without any suffixes for the image size.
*/
private static String getKey(CardView card) {
String set = card.getExpansionSetCode();
String key = card.getName() + "#" + set + "#" + String.valueOf(card.getCardNumber());
return key;
}
/**
* Load image from file
*
* @param file
* file to load image from
* @return {@link BufferedImage}
*/
public static BufferedImage loadImage(File file) {
BufferedImage image = null;
if (!file.exists()) {
return null;
}
try {
image = ImageIO.read(file);
} catch (Exception e) {
log.error(e, e);
}
return image;
}
public static BufferedImage makeThumbnail(BufferedImage original, String path) {
BufferedImage image = getResizedImage(original, Constants.THUMBNAIL_SIZE_FULL);
File imagePath = new File(path);
try {
//log.debug("thumbnail path:"+path);
ImageIO.write(image, "jpg", imagePath);
} catch (Exception e) {
log.error(e,e);
}
return image;
}
/**
* Returns an image scaled to the size given
*/
public static BufferedImage getNormalSizeImage(BufferedImage original) {
if (original == null) {
return null;
}
int srcWidth = original.getWidth();
int srcHeight = original.getHeight();
int tgtWidth = Constants.CARD_SIZE_FULL.width;
int tgtHeight = Constants.CARD_SIZE_FULL.height;
if (srcWidth == tgtWidth && srcHeight == tgtHeight)
return original;
ResampleOp resampleOp = new ResampleOp(tgtWidth, tgtHeight);
BufferedImage image = resampleOp.filter(original, null);
return image;
}
/**
* Returns an image scaled to the size appropriate for the card picture
* panel For future use.
*/
private static BufferedImage getFullSizeImage(BufferedImage original, double scale) {
if (scale == 1)
return original;
ResampleOp resampleOp = new ResampleOp((int) (original.getWidth() * scale), (int) (original.getHeight() * scale));
BufferedImage image = resampleOp.filter(original, null);
return image;
}
/**
* Returns an image scaled to the size appropriate for the card picture
* panel
*/
public static BufferedImage getResizedImage(BufferedImage original, Rectangle sizeNeed) {
ResampleOp resampleOp = new ResampleOp(sizeNeed.width, sizeNeed.height);
BufferedImage image = resampleOp.filter(original, null);
return image;
}
/**
* Returns the image appropriate to display the card in the picture panel
*/
public static BufferedImage getImage(CardView card, int width, int height) {
String key = getKey(card);
BufferedImage original = getImage(key);
if (original == null)
return null;
double scale = Math.min((double) width / original.getWidth(), (double) height / original.getHeight());
if (scale > 1)
scale = 1;
return getFullSizeImage(original, scale);
}
}

View file

@ -0,0 +1,207 @@
package org.mage.plugins.card.info;
import mage.Constants;
import mage.components.CardInfoPane;
import mage.utils.CardUtil;
import mage.utils.ThreadUtils;
import mage.view.CardView;
import mage.view.CounterView;
import mage.view.PermanentView;
import org.mage.card.arcane.ManaSymbols;
import org.mage.card.arcane.UI;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* Card info pane for displaying card rules.
* Supports drawing mana symbols.
*
* @author nantuko
*/
public class CardInfoPaneImpl extends JEditorPane implements CardInfoPane {
private CardView currentCard;
public CardInfoPaneImpl() {
UI.setHTMLEditorKit(this);
setEditable(false);
setBackground(Color.white);
}
public void setCard(final CardView card) {
if (card == null) return;
if (isCurrentCard(card)) return;
currentCard = card;
ThreadUtils.threadPool.submit(new Runnable() {
public void run() {
try {
if (!card.equals(currentCard)) return;
String manaCost = "";
for (String m : card.getManaCost()) {
manaCost += m;
}
String castingCost = UI.getDisplayManaCost(manaCost);
castingCost = ManaSymbols.replaceSymbolsWithHTML(castingCost, false);
int symbolCount = 0;
int offset = 0;
while ((offset = castingCost.indexOf("<img", offset) + 1) != 0)
symbolCount++;
List<String> rules = card.getRules();
List<String> rulings = new ArrayList<String>(rules);
if (card instanceof PermanentView) {
List<CounterView> counters = ((PermanentView) card).getCounters();
int count = counters != null ? counters.size() : 0;
if (count > 0) {
StringBuilder sb = new StringBuilder();
int index = 0;
for (CounterView counter : ((PermanentView) card).getCounters()) {
if (counter.getCount() > 0) {
if (index == 0) {
sb.append("<b>Counters:</b> ");
} else {
sb.append(", ");
}
sb.append(counter.getCount() + "x<i>" + counter.getName() + "</i>");
index++;
}
}
rulings.add(sb.toString());
}
int damage = ((PermanentView)card).getDamage();
if (damage > 0) {
rulings.add("<span color='red'><b>Damage dealt:</b> " + damage + "</span>");
}
}
boolean smallImages = true;
int fontSize = 11;
String fontFamily = "tahoma";
/*if (prefs.fontFamily == CardFontFamily.arial)
fontFamily = "arial";
else if (prefs.fontFamily == CardFontFamily.verdana) {
fontFamily = "verdana";
}*/
final StringBuffer buffer = new StringBuffer(512);
buffer.append("<html><body style='font-family:");
buffer.append(fontFamily);
buffer.append(";font-size:");
buffer.append(fontSize);
buffer.append("pt;margin:0px 1px 0px 1px'>");
buffer.append("<table cellspacing=0 cellpadding=0 border=0 width='100%'>");
buffer.append("<tr><td valign='top'><b>");
buffer.append(card.getName());
buffer.append("</b></td><td align='right' valign='top' style='width:");
buffer.append(symbolCount * 11 + 1);
buffer.append("px'>");
buffer.append(castingCost);
buffer.append("</td></tr></table>");
buffer.append("<table cellspacing=0 cellpadding=0 border=0 width='100%'><tr><td style='margin-left: 1px'>");
buffer.append(getTypes(card));
buffer.append("</td><td align='right'>");
switch (card.getRarity()) {
case RARE:
buffer.append("<b color='#FFBF00'>");
break;
case UNCOMMON:
buffer.append("<b color='silver'>");
break;
case COMMON:
buffer.append("<b color='black'>");
break;
case MYTHIC:
buffer.append("<b color='#D5330B'>");
break;
}
String rarity = card.getRarity().getCode();
if (card.getExpansionSetCode() != null) {
buffer.append(ManaSymbols.replaceSetCodeWithHTML(card.getExpansionSetCode().toUpperCase(), rarity));
}
buffer.append("</td></tr></table>");
String pt = "";
if (CardUtil.isCreature(card)) {
pt = card.getPower() + "/" + card.getToughness();
} else if (CardUtil.isPlaneswalker(card)) {
pt = card.getLoyalty().toString();
}
if (pt.length() > 0) {
buffer.append("<table cellspacing=0 cellpadding=0 border=0 width='100%' valign='bottom'><tr><td>");
buffer.append("<b>");
buffer.append(pt);
buffer.append("</b>");
buffer.append("</td></tr></table>");
}
String legal = "";
if (rulings.size() > 0) {
legal = legal.replaceAll("#([^#]+)#", "<i>$1</i>");
legal = legal.replaceAll("\\s*//\\s*", "<hr width='50%'>");
legal = legal.replace("\r\n", "<div style='font-size:5pt'></div>");
legal += "<br>";
for (String ruling : rulings) {
if (!ruling.replace(".", "").trim().isEmpty()) {
legal += "<p style='margin: 2px'>";
legal += ruling;
legal += "</p>";
}
}
}
if (legal.length() > 0) {
//buffer.append("<br>");
legal = legal.replaceAll("\\{this\\}", card.getName());
legal = legal.replaceAll("\\{source\\}", card.getName());
buffer.append(ManaSymbols.replaceSymbolsWithHTML(legal, smallImages));
}
buffer.append("<br></body></html>");
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (!card.equals(currentCard)) return;
setText(buffer.toString());
//System.out.println(buffer.toString());
setCaretPosition(0);
//ThreadUtils.sleep(300);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
private String getTypes(CardView card) {
String types = "";
for (String superType : card.getSuperTypes()) {
types += superType + " ";
}
for (Constants.CardType cardType : card.getCardTypes()) {
types += cardType.toString() + " ";
}
if (card.getSubTypes().size() > 0) {
types += "- ";
}
for (String subType : card.getSubTypes()) {
types += subType + " ";
}
return types.trim();
}
public boolean isCurrentCard(CardView card) {
return currentCard != null && card.equals(currentCard);
}
}

View file

@ -0,0 +1,78 @@
package org.mage.plugins.card.properties;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Properties;
import org.mage.plugins.card.constants.Constants;
public class SettingsManager {
private static SettingsManager settingsManager = null;
public static SettingsManager getIntance() {
if (settingsManager == null) {
synchronized (SettingsManager.class) {
if (settingsManager == null) settingsManager = new SettingsManager();
}
}
return settingsManager;
}
private SettingsManager() {
loadImageProperties();
}
public void reloadImageProperties() {
loadImageProperties();
}
private void loadImageProperties() {
imageUrlProperties = new Properties();
try {
InputStream is = SettingsManager.class.getClassLoader().getResourceAsStream(Constants.IO.IMAGE_PROPERTIES_FILE);
if (is == null)
throw new RuntimeException("Couldn't load " + Constants.IO.IMAGE_PROPERTIES_FILE);
imageUrlProperties.load(is);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
public String getSetNameReplacement(String setName) {
String result = setName;
if (imageUrlProperties != null) {
result = imageUrlProperties.getProperty(setName, setName);
}
return result;
}
public HashSet<String> getIgnoreUrls() {
HashSet<String> ignoreUrls = new HashSet<String>();
if (imageUrlProperties != null) {
String result = imageUrlProperties.getProperty("ignore.urls");
if (result != null) {
String[] ignore = result.split(",");
ignoreUrls.addAll(Arrays.asList(ignore));
}
}
return ignoreUrls;
}
public ArrayList<String> getTokenLookupOrder() {
ArrayList<String> order = new ArrayList<String>();
if (imageUrlProperties != null) {
String result = imageUrlProperties.getProperty("token.lookup.order");
if (result != null) {
String[] sets = result.split(",");
order.addAll(Arrays.asList(sets));
}
}
return order;
}
private Properties imageUrlProperties;
}

View file

@ -0,0 +1,71 @@
package org.mage.plugins.card.utils;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
/**
* Utility class for creating BufferedImage object from Image instance.
*
* @author nantuko
*/
public class BufferedImageBuilder {
private static final int DEFAULT_IMAGE_TYPE = BufferedImage.TYPE_INT_RGB;
/**
* Hide constructor
*/
private BufferedImageBuilder() {
}
public static BufferedImage bufferImage(Image image) {
return bufferImage(image, DEFAULT_IMAGE_TYPE);
}
public static BufferedImage bufferImage(Image image, int type) {
BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
Graphics2D g = bufferedImage.createGraphics();
g.drawImage(image, null, null);
//waitForImage(bufferedImage);
return bufferedImage;
}
private void waitForImage(BufferedImage bufferedImage) {
final ImageLoadStatus imageLoadStatus = new ImageLoadStatus();
bufferedImage.getHeight(new ImageObserver() {
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
if (infoflags == ALLBITS) {
imageLoadStatus.heightDone = true;
return true;
}
return false;
}
});
bufferedImage.getWidth(new ImageObserver() {
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
if (infoflags == ALLBITS) {
imageLoadStatus.widthDone = true;
return true;
}
return false;
}
});
while (!imageLoadStatus.widthDone && !imageLoadStatus.heightDone) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
}
}
}
class ImageLoadStatus {
public boolean widthDone = false;
public boolean heightDone = false;
}
}

View file

@ -0,0 +1,154 @@
package org.mage.plugins.card.utils;
import java.io.File;
import java.util.HashMap;
import org.mage.plugins.card.constants.Constants;
import org.mage.plugins.card.images.CardInfo;
import org.mage.plugins.card.properties.SettingsManager;
public class CardImageUtils {
private static HashMap<CardInfo, String> pathCache = new HashMap<CardInfo, String>();
/**
* Get path to image for specific card.
*
* @param card
* card to get path for
* @return String if image exists, else null
*/
public static String getImagePath(CardInfo card) {
String filePath;
String suffix = ".jpg";
File file = null;
if (card.isToken()) {
if (pathCache.containsKey(card)) {
return pathCache.get(card);
}
filePath = getTokenImagePath(card);
file = new File(filePath);
if (!file.exists()) {
filePath = searchForCardImage(card);
file = new File(filePath);
}
if (file.exists()) {
pathCache.put(card, filePath);
}
} else {
filePath = getImagePath(card, false);
file = new File(filePath);
if (!file.exists()) {
filePath = getImagePath(card, true);
file = new File(filePath);
}
}
/**
* try current directory
*/
if (file == null || !file.exists()) {
filePath = cleanString(card.getName()) + suffix;
file = new File(filePath);
}
if (file.exists()) {
return filePath;
} else {
return null;
}
}
private static String getTokenImagePath(CardInfo card) {
String filename = getImagePath(card, false);
File file = new File(filename);
if (!file.exists()) {
CardInfo updated = new CardInfo(card);
updated.setName(card.getName() + " 1");
filename = getImagePath(updated, false);
file = new File(filename);
if (!file.exists()) {
updated = new CardInfo(card);
updated.setName(card.getName() + " 2");
filename = getImagePath(updated, false);
file = new File(filename);
}
}
return filename;
}
private static String searchForCardImage(CardInfo card) {
File file = null;
String path = "";
CardInfo c = new CardInfo(card);
for (String set : SettingsManager.getIntance().getTokenLookupOrder()) {
c.setSet(set);
path = getTokenImagePath(c);
file = new File(path);
if (file.exists()) {
pathCache.put(card, path);
return path;
}
}
return "";
}
public static String cleanString(String in) {
in = in.trim();
StringBuilder out = new StringBuilder();
char c;
for (int i = 0; i < in.length(); i++) {
c = in.charAt(i);
if (c == ' ' || c == '-')
out.append('_');
else if (Character.isLetterOrDigit(c)) {
out.append(c);
}
}
return out.toString().toLowerCase();
}
public static String updateSet(String cardSet, boolean forUrl) {
String set = cardSet.toLowerCase();
if (set.equals("con")) {
set = "cfx";
}
if (forUrl) {
set = SettingsManager.getIntance().getSetNameReplacement(set);
}
return set;
}
public static String getImageDir(CardInfo card, String imagesPath) {
if (card.getSet() == null) {
return "";
}
String set = updateSet(card.getSet(), false).toUpperCase();
String imagesDir = (imagesPath != null ? imagesPath : Constants.IO.imageBaseDir);
if (card.isToken()) {
return imagesDir + File.separator + "TOK" + File.separator + set;
} else {
return imagesDir + File.separator + set;
}
}
public static String getImagePath(CardInfo card, boolean withCollector) {
return getImagePath(card, withCollector, null);
}
public static String getImagePath(CardInfo card, boolean withCollector, String imagesPath) {
if (withCollector) {
return getImageDir(card, imagesPath) + File.separator + card.getName() + "." + card.getCollectorId() + ".full.jpg";
} else {
return getImageDir(card, imagesPath) + File.separator + card.getName() + ".full.jpg";
}
}
}

View file

@ -0,0 +1,7 @@
package org.mage.plugins.card.utils;
import java.awt.*;
public interface ImageManager {
public Image getSicknessImage();
}

View file

@ -0,0 +1,52 @@
package org.mage.plugins.card.utils;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.ImageProducer;
import java.awt.image.RGBImageFilter;
public class Transparency {
public static Image makeColorTransparent(Image im, final Color color) {
ImageFilter filter = new RGBImageFilter() {
// the color we are looking for... Alpha bits are set to opaque
public int markerRGB = color.getRGB() | 0xFF000000;
@Override
public final int filterRGB(int x, int y, int rgb) {
if ((rgb | 0xFF000000) == markerRGB) {
// Mark the alpha bits as zero - transparent
return 0x00FFFFFF & rgb;
} else {
// nothing to do
return rgb;
}
}
};
ImageProducer ip = new FilteredImageSource(im.getSource(), filter);
return Toolkit.getDefaultToolkit().createImage(ip);
}
public static BufferedImage makeImageTranslucent(BufferedImage source,
double alpha) {
BufferedImage target = new BufferedImage(source.getWidth(), source
.getHeight(), java.awt.Transparency.TRANSLUCENT);
// Get the images graphics
Graphics2D g = target.createGraphics();
// Set the Graphics composite to Alpha
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
(float) alpha));
// Draw the image into the prepared reciver image
g.drawImage(source, null, 0, 0);
// let go of all system resources in this Graphics
g.dispose();
// Return the image
return target;
}
}

View file

@ -0,0 +1,57 @@
package org.mage.plugins.card.utils.impl;
import java.awt.Color;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.CropImageFilter;
import java.awt.image.FilteredImageSource;
import java.net.URL;
import javax.imageio.ImageIO;
import org.mage.plugins.card.utils.BufferedImageBuilder;
import org.mage.plugins.card.utils.ImageManager;
import org.mage.plugins.card.utils.Transparency;
public class ImageManagerImpl implements ImageManager {
private static ImageManagerImpl fInstance = new ImageManagerImpl();
public static ImageManagerImpl getInstance() {
return fInstance;
}
@Override
public BufferedImage getSicknessImage() {
if (imageSickness == null) {
Image image = getImageFromResourceTransparent("/sickness.png", Color.WHITE, new Rectangle(296, 265));
Toolkit tk = Toolkit.getDefaultToolkit();
image = tk.createImage(new FilteredImageSource(image.getSource(), new CropImageFilter(0, 0, 200, 285)));
imageSickness = BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB);
}
return imageSickness;
}
protected static Image getImageFromResourceTransparent(String path, Color mask, Rectangle rec) {
BufferedImage image = null;
Image imageCardTransparent = null;
Image resized = null;
URL imageURL = ImageManager.class.getResource(path);
try {
image = ImageIO.read(imageURL);
imageCardTransparent = Transparency.makeColorTransparent(image, mask);
resized = imageCardTransparent.getScaledInstance(rec.width, rec.height, java.awt.Image.SCALE_SMOOTH);
} catch (Exception e) {
e.printStackTrace();
}
return resized;
}
private static BufferedImage imageSickness = null;
}