From 4ee655bef4cdf9e62a1b247e77754441de806f22 Mon Sep 17 00:00:00 2001
From: Eduardo Pedroni <e.pedroni91@gmail.com>
Date: Mon, 8 Jun 2015 16:57:44 +0200
Subject: Implemented sorting using reflection, not sure it was a good idea
 though

---
 src/eu/equalparts/cardbase/Cardbase.java           | 164 +++++++++++++++++++++
 .../cardbase/comparators/CardComparator.java       | 102 +++++++++++++
 src/eu/equalparts/cardbase/data/Card.java          |  22 +--
 src/eu/equalparts/cardbase/data/CardSet.java       |  34 -----
 .../cardbase/data/CardSetInformation.java          |  34 +++++
 src/eu/equalparts/cardbase/data/Cardbase.java      |  24 ---
 .../equalparts/cardbase/data/CardbaseManager.java  | 126 ----------------
 src/eu/equalparts/cardbase/data/FullCardSet.java   |  32 +++-
 .../cardbase/standalone/CardbaseCLI.java           |  89 +++++------
 src/eu/equalparts/cardbase/utils/IO.java           |  35 -----
 src/eu/equalparts/cardbase/utils/JSON.java         |  35 +++++
 src/eu/equalparts/cardbase/utils/MTGUniverse.java  |  17 ++-
 12 files changed, 432 insertions(+), 282 deletions(-)
 create mode 100644 src/eu/equalparts/cardbase/Cardbase.java
 create mode 100644 src/eu/equalparts/cardbase/comparators/CardComparator.java
 delete mode 100644 src/eu/equalparts/cardbase/data/CardSet.java
 create mode 100644 src/eu/equalparts/cardbase/data/CardSetInformation.java
 delete mode 100644 src/eu/equalparts/cardbase/data/Cardbase.java
 delete mode 100644 src/eu/equalparts/cardbase/data/CardbaseManager.java
 delete mode 100644 src/eu/equalparts/cardbase/utils/IO.java
 create mode 100644 src/eu/equalparts/cardbase/utils/JSON.java

(limited to 'src/eu')

diff --git a/src/eu/equalparts/cardbase/Cardbase.java b/src/eu/equalparts/cardbase/Cardbase.java
new file mode 100644
index 0000000..14afc10
--- /dev/null
+++ b/src/eu/equalparts/cardbase/Cardbase.java
@@ -0,0 +1,164 @@
+package eu.equalparts.cardbase;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonMappingException;
+
+import eu.equalparts.cardbase.comparators.CardComparator;
+import eu.equalparts.cardbase.data.Card;
+import eu.equalparts.cardbase.utils.JSON;
+
+/**
+ * Provides a variety of utility methods to interact with the loaded cardbase.
+ * 
+ * @author Eduardo Pedroni
+ */
+public class Cardbase {
+	
+	/**
+	 * The cards in the cardbase.
+	 */
+	private List<Card> cards;
+	/**
+	 * Debug flag is raised when the DEBUG environment variable is set. This causes additional
+	 * information to be printed to the console.
+	 */
+	public static final boolean DEBUG = System.getenv("CB_DEBUG") != null; 
+	
+	/**
+	 * Creates an empty cardbase.
+	 */
+	public Cardbase() {
+		cards = new ArrayList<Card>();
+	}
+	
+	/**
+	 * Initialises the cardbase with the contents of a file.
+	 *
+	 * @param cardbaseFile the cardbase JSON to load.
+	 * 
+	 * @throws JsonParseException if the specified file does not contain valid JSON.
+	 * @throws JsonMappingException if the specified file structure does not match that of {@code Cardbase}.
+	 * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
+	 */
+	public Cardbase(File cardbaseFile) throws JsonParseException, JsonMappingException, IOException {
+		cards = JSON.mapper.readValue(cardbaseFile, new TypeReference<ArrayList<Card>>() {});
+	}
+
+	/**
+	 * Writes the provided {@code Cardbase} to the provided file in JSON format.
+	 * 
+	 * @param file the file to which to write the {@code Cardbase}.
+	 * @param cardbase the {@code Cardbase} to write out.
+	 * 
+	 * @throws JsonGenerationException if the data structure given does not generate valid JSON.
+	 * @throws JsonMappingException if the data structure given does not generate valid JSON as well?
+	 * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
+	 */
+	public void writeCardbase(File outputFile) throws JsonGenerationException, JsonMappingException, IOException {
+		JSON.mapper.writeValue(outputFile, cards);
+	}
+
+	/**
+	 * Adds a specific amount of a card to the cardbase.
+	 * If the card is not already in the cardbase, it is added.
+	 * If it is already present, the count is simply updated.
+	 * 
+	 * @param cardToAdd the card to be added.
+	 * @param count the amount of the card to be added.
+	 */
+	public void addCard(Card cardToAdd, Integer count) {
+		Card card = getCard(cardToAdd.setCode, cardToAdd.number);
+		if (card != null) {
+			card.count += count;
+		} else {
+			cardToAdd.count = count;
+			cards.add(cardToAdd);
+		}
+	}
+
+	/**
+	 * Removes a specific amount of a card from the cardbase.
+	 * If the card is not present in the cardbase, nothing happens.
+	 * If the card is present in the cardbase, the specified amount is removed.
+	 * If that amount is equal to or exceeds the count already in the cardbase,
+	 * the card entry is removed altogether.
+	 * <br><br>
+	 * In any case, the value returned is the actual number of cards removed.
+	 * For example, if 5 Shivan Dragons are in the cardbase and the method is
+	 * called to remove 10 Shivan Dragons, the {@code Card} representing the
+	 * Shivan Dragon is removed from the cardbase, and the value returned is 5.
+	 * 
+	 * @param cardToRemove the card to be removed.
+	 * @param count the amount of the card to be removed.
+	 * @return the number of cards actually removed.
+	 */
+	public Integer removeCard(Card cardToRemove, Integer count) {
+		Card card = getCard(cardToRemove.setCode, cardToRemove.number);
+		Integer removed = 0;
+		if (card != null) {
+			if (card.count <= count) {
+				cards.remove(card);
+				removed = card.count;
+			} else {
+				card.count -= count;
+				removed = count;
+			}
+		} 
+		return removed;
+	}
+
+	/**
+	 * This method is intended to allow iteration directly on the list of cards,
+	 * while at the same time retaining control over the insert and remove procedures.
+	 * The returned {@code List} is a read-only; trying to modify its structure will
+	 * result in a {@code UnsupportedOperationException}.
+	 * 
+	 * @return an unmodifiable list of all the cards in the cardbase.
+	 */
+	public List<Card> getCards() {
+		return Collections.unmodifiableList(cards);
+	}
+	
+	/**
+	 * Sorts the cardbase by the specified field. The field must be specified exactly
+	 * as it is defined in {@code Card}, case-sensitive. It must also be comparable to
+	 * itself, as {@code String} and {@code Integer} are.
+	 * 
+	 * @param fieldName the declared name of the field to be used for sorting.
+	 * @return true if the sort was successful, false if no such field was found.
+	 */
+	public boolean sortBy(String fieldName) {
+		for (Field field : Card.class.getDeclaredFields()) {
+			if (field.getName().equals(fieldName)) {
+				cards.sort(new CardComparator(field));
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Returns a card from the cardbase by set code and number.
+	 * If no such card is in the cardbase, returns null.
+	 * 
+	 * @param setCode the set to which the requested card belongs.
+	 * @param number the requested card's set number.
+	 * @return the requested {@code Card} or null if no card is found.
+	 */
+	public Card getCard(String setCode, String number) {
+		for (Card card : cards) {
+			if (card.setCode.equals(setCode) && card.number.equals(number))
+				return card;
+		}
+		
+		return null;
+	}
+}
diff --git a/src/eu/equalparts/cardbase/comparators/CardComparator.java b/src/eu/equalparts/cardbase/comparators/CardComparator.java
new file mode 100644
index 0000000..3640c8b
--- /dev/null
+++ b/src/eu/equalparts/cardbase/comparators/CardComparator.java
@@ -0,0 +1,102 @@
+package eu.equalparts.cardbase.comparators;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Comparator;
+
+import eu.equalparts.cardbase.Cardbase;
+import eu.equalparts.cardbase.data.Card;
+
+/**
+ * I'm new to this reflection business, so bear with me.
+ * <br><br>
+ * The idea here is to avoid having to write one class
+ * for each comparable field in {@code Card}. The program
+ * can dynamically instantiate them as cards are compared 
+ * by different fields.
+ * <br><br>
+ * This class uses reflection to determine if the specified
+ * field is comparable with itself upon construction, and throws
+ * an {@code IllegalArgumentException} if that is not the case.
+ * 
+ * @author Eduardo Pedroni
+ *
+ */
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class CardComparator implements Comparator<Card> {
+
+	/**
+	 * The field being compared.
+	 */
+	private Field fieldToCompare;
+
+	/**
+	 * Creates a new comparator for the specified field only. This class
+	 * will only be constructed successfully if the field comes from
+	 * {@code Card} and can be compared to itself (i.e. implements
+	 * {@code Comparable<T>} where T is its own type.
+	 * <br>
+	 * For reference, {@code String} and {@code Integer} are both self comparable.
+	 * 
+	 * @param fieldToCompare the field this comparator will use to compare cards, as declared in {@code Card}.
+	 */
+	public CardComparator(Field fieldToCompare) {
+		if (fieldToCompare.getDeclaringClass().equals(Card.class) &&
+				isSelfComparable(fieldToCompare.getType())) {
+
+			this.fieldToCompare = fieldToCompare;
+		} else {
+			System.out.println(fieldToCompare.isAccessible());
+			System.out.println(fieldToCompare.getDeclaringClass().equals(Card.class));
+			System.out.println(isSelfComparable(fieldToCompare.getType()));
+			throw new IllegalArgumentException("The field provided is not valid.");
+		}
+	}
+
+	@Override
+	public int compare(Card o1, Card o2) {
+		try {
+			/*
+			 * we've already checked that the field is self comparable,
+			 * so we are now free to cast to whatever type it is and compare.
+			 */
+			Comparable field1 = (Comparable) fieldToCompare.get(o1);
+			Comparable field2 = (Comparable) fieldToCompare.get(o2);
+			
+			return field1.compareTo(field2);
+
+		} catch (IllegalArgumentException e) {
+			System.out.println("Error: class Card does not define field" + fieldToCompare.getName() + ".");
+			if (Cardbase.DEBUG) e.printStackTrace();
+		} catch (IllegalAccessException e) {
+			System.out.println("Error: field " + fieldToCompare.getName() + " in Card is not visible.");
+			if (Cardbase.DEBUG) e.printStackTrace();
+		}
+		
+		// not comparable, this should never happen
+		return 0;
+	}
+
+	/**
+	 * Use reflection to determine if the specified class can be compared with itself.
+	 * 
+	 * @param type the type to analyse.
+	 * @return true if the type can be compared to itself using {@code compareTo()}, false otherwise.
+	 */
+	private boolean isSelfComparable(Class<?> type) {
+
+		// go through all interfaces implemented by this class
+		for (Type implementedInterface : type.getGenericInterfaces()) {
+			// check if any parameterised interface found is "Comparable"
+			if (implementedInterface instanceof ParameterizedType) {
+				ParameterizedType genericInterface = (ParameterizedType) implementedInterface;
+				if (genericInterface.getRawType().equals(Comparable.class)) {
+					// check that the type argument of comparable is the same as the field type itself
+					return genericInterface.getActualTypeArguments()[0].equals(type);
+				}
+			}
+		}
+		return false;
+	}
+}
diff --git a/src/eu/equalparts/cardbase/data/Card.java b/src/eu/equalparts/cardbase/data/Card.java
index 1bf6a75..03e7dea 100644
--- a/src/eu/equalparts/cardbase/data/Card.java
+++ b/src/eu/equalparts/cardbase/data/Card.java
@@ -1,19 +1,19 @@
 package eu.equalparts.cardbase.data;
 
-import java.util.ArrayList;
+import java.util.List;
 
 public class Card {
 	
-	public String layout;
-	public String name;
-	public ArrayList<String> names;
-	public String manaCost;
-	public Integer cmc;
-	public ArrayList<String> colors;
+	public String layout = "";
+	public String name = "";
+	public List<String> names;
+	public String manaCost = "";
+	public Integer cmc = 0;
+	public List<String> colors;
 	public String type;
-	public ArrayList<String> supertypes;
-	public ArrayList<String> types;
-	public ArrayList<String> subtypes;
+	public List<String> supertypes;
+	public List<String> types;
+	public List<String> subtypes;
 	public String rarity;
 	public String text;
 	public String flavor;
@@ -23,7 +23,7 @@ public class Card {
 	public String toughness;
 	public Integer loyalty;
 	public Integer multiverseid;
-	public ArrayList<String> variations;
+	public List<String> variations;
 	public String imageName;
 	public String border;
 	public String watermark;
diff --git a/src/eu/equalparts/cardbase/data/CardSet.java b/src/eu/equalparts/cardbase/data/CardSet.java
deleted file mode 100644
index b06be7c..0000000
--- a/src/eu/equalparts/cardbase/data/CardSet.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package eu.equalparts.cardbase.data;
-
-public class CardSet {
-
-	private String name;
-	private String code;
-	private String releaseDate;
-	
-	/**
-	 * @return the set's name.
-	 */
-	public String getName() {
-		return name;
-	}
-
-	/**
-	 * @return the set code.
-	 */
-	public String getCode() {
-		return code;
-	}
-
-	/**
-	 * @return the set's release date.
-	 */
-	public String getReleaseDate() {
-		return releaseDate;
-	}
-
-	@Override
-	public String toString() {
-		return String.format("%1$-12s : %2$s", code, name, releaseDate);
-	}
-}
diff --git a/src/eu/equalparts/cardbase/data/CardSetInformation.java b/src/eu/equalparts/cardbase/data/CardSetInformation.java
new file mode 100644
index 0000000..d3394b7
--- /dev/null
+++ b/src/eu/equalparts/cardbase/data/CardSetInformation.java
@@ -0,0 +1,34 @@
+package eu.equalparts.cardbase.data;
+
+public class CardSetInformation {
+
+	private String name;
+	private String code;
+	private String releaseDate;
+	
+	/**
+	 * @return the set's name.
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @return the set code.
+	 */
+	public String getCode() {
+		return code;
+	}
+
+	/**
+	 * @return the set's release date.
+	 */
+	public String getReleaseDate() {
+		return releaseDate;
+	}
+
+	@Override
+	public String toString() {
+		return String.format("%1$-12s : %2$s", code, name, releaseDate);
+	}
+}
diff --git a/src/eu/equalparts/cardbase/data/Cardbase.java b/src/eu/equalparts/cardbase/data/Cardbase.java
deleted file mode 100644
index 021fac2..0000000
--- a/src/eu/equalparts/cardbase/data/Cardbase.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package eu.equalparts.cardbase.data;
-
-import java.util.ArrayList;
-
-public class Cardbase {
-
-	public ArrayList<Card> cards = new ArrayList<>();
-	public ArrayList<Deck> decks = new ArrayList<>();
-	
-	/**
-	 * @param setCode the set to which the requested card belongs.
-	 * @param number the requested card's set number.
-	 * @return the requested {@code Card} or null if no card is found.
-	 */
-	public Card getCardByNumber(String setCode, String number) {
-		for (Card card : cards) {
-			if (card.setCode.equals(setCode) && card.number.equals(number))
-				return card;
-		}
-		
-		return null;
-	}
-	
-}
diff --git a/src/eu/equalparts/cardbase/data/CardbaseManager.java b/src/eu/equalparts/cardbase/data/CardbaseManager.java
deleted file mode 100644
index d6ba6ee..0000000
--- a/src/eu/equalparts/cardbase/data/CardbaseManager.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package eu.equalparts.cardbase.data;
-import java.io.File;
-import java.io.IOException;
-import java.util.Iterator;
-
-import com.fasterxml.jackson.core.JsonGenerationException;
-import com.fasterxml.jackson.core.JsonParseException;
-import com.fasterxml.jackson.databind.JsonMappingException;
-
-import eu.equalparts.cardbase.utils.IO;
-
-/**
- * Provides a variety of utility methods to interact with the loaded cardbase.
- * 
- * @author Eduardo Pedroni
- */
-public class CardbaseManager {
-
-	/**
-	 * The cardbase being managed.
-	 */
-	private Cardbase cardbase;
-	
-	/**
-	 * Creates an empty cardbase.
-	 * 
-	 */
-	public CardbaseManager() {
-		cardbase = new Cardbase();
-	}
-	
-	/**
-	 * Initialises the cardbase with the contents of a file.
-	 *
-	 * @param cardbaseFile the cardbase JSON to load.
-	 * 
-	 * @throws JsonParseException if the specified file does not contain valid JSON.
-	 * @throws JsonMappingException if the specified file structure does not match that of {@code Cardbase}.
-	 * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
-	 */
-	public CardbaseManager(File cardbaseFile) throws JsonParseException, JsonMappingException, IOException {
-		cardbase = IO.jsonMapper.readValue(cardbaseFile, Cardbase.class);
-	}
-
-	/**
-	 * Writes the provided {@code Cardbase} to the provided file in JSON format.
-	 * 
-	 * @param file the file to which to write the {@code Cardbase}.
-	 * @param cardbase the {@code Cardbase} to write out.
-	 * 
-	 * @throws JsonGenerationException if the data structure given does not generate valid JSON.
-	 * @throws JsonMappingException if the data structure given does not generate valid JSON as well?
-	 * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
-	 */
-	public void writeCardbase(File outputFile) throws JsonGenerationException, JsonMappingException, IOException {
-		IO.jsonMapper.writeValue(outputFile, cardbase);
-	}
-
-	/**
-	 * Adds a specific amount of a card to the cardbase.
-	 * If the card is not already in the cardbase, it is added.
-	 * If it is already present, the count is simply updated.
-	 * 
-	 * @param cardToAdd the card to be added.
-	 * @param count the amount of the card to be added.
-	 */
-	public void addCard(Card cardToAdd, Integer count) {
-		Card card = cardbase.getCardByNumber(cardToAdd.setCode, cardToAdd.number);
-		if (card != null) {
-			card.count += count;
-		} else {
-			cardToAdd.count = count;
-			cardbase.cards.add(cardToAdd);
-		}
-	}
-
-	/**
-	 * Removes a specific amount of a card from the cardbase.
-	 * If the card is not present in the cardbase, nothing happens.
-	 * If the card is present in the cardbase, the specified amount is removed.
-	 * If that amount is equal to or exceeds the count already in the cardbase,
-	 * the card entry is removed altogether.
-	 * <br><br>
-	 * In any case, the value returned is the actual number of cards removed.
-	 * For example, if 5 Shivan Dragons are in the cardbase and the method is
-	 * called to remove 10 Shivan Dragons, the {@code Card} representing the
-	 * Shivan Dragon is removed from the cardbase, and the value returned is 5.
-	 * 
-	 * @param cardToRemove the card to be removed.
-	 * @param count the amount of the card to be removed.
-	 * @return the number of cards actually removed.
-	 */
-	public Integer removeCard(Card cardToRemove, Integer count) {
-		Card card = cardbase.getCardByNumber(cardToRemove.setCode, cardToRemove.number);
-		Integer removed = 0;
-		if (card != null) {
-			if (card.count <= count) {
-				cardbase.cards.remove(card);
-				removed = card.count;
-			} else {
-				card.count -= count;
-				removed = count;
-			}
-		} 
-		return removed;
-	}
-
-	/**
-	 * @return an iterator to the {@code Card}s in the cardbase.
-	 */
-	public Iterator<Card> cardIterator() {
-		return cardbase.cards.iterator();
-	}
-
-	/**
-	 * Returns a card from the cardbase by set code and number.
-	 * If no such card is in the cardbase, returns null.
-	 * 
-	 * @param setCode the set to which the requested card belongs.
-	 * @param number the requested card's set number.
-	 * @return the requested {@code Card} or null if no card is found.
-	 */
-	public Card getCard(String setCode, String number) {
-		return cardbase.getCardByNumber(setCode, number);
-	}
-}
diff --git a/src/eu/equalparts/cardbase/data/FullCardSet.java b/src/eu/equalparts/cardbase/data/FullCardSet.java
index 0f58633..d469829 100644
--- a/src/eu/equalparts/cardbase/data/FullCardSet.java
+++ b/src/eu/equalparts/cardbase/data/FullCardSet.java
@@ -1,15 +1,39 @@
 package eu.equalparts.cardbase.data;
 
-import java.util.ArrayList;
+import java.util.List;
 
-public class FullCardSet extends CardSet {
+public class FullCardSet {
 	
+	private String name;
+	private String code;
+	private String releaseDate;
 	private String border;
 	private String type;
 	private String block;
 	private String gathererCode;
-	private ArrayList<Card> cards;
+	private List<Card> cards;
 
+	/**
+	 * @return the set's name.
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @return the set code.
+	 */
+	public String getCode() {
+		return code;
+	}
+
+	/**
+	 * @return the set's release date.
+	 */
+	public String getReleaseDate() {
+		return releaseDate;
+	}
+	
 	/**
 	 * @return the set's border type.
 	 */
@@ -41,7 +65,7 @@ public class FullCardSet extends CardSet {
 	/**
 	 * @return a full list of the set's cards.
 	 */
-	public ArrayList<Card> getCards() {
+	public List<Card> getCards() {
 		return cards;
 	}
 
diff --git a/src/eu/equalparts/cardbase/standalone/CardbaseCLI.java b/src/eu/equalparts/cardbase/standalone/CardbaseCLI.java
index a8b11ba..108b753 100644
--- a/src/eu/equalparts/cardbase/standalone/CardbaseCLI.java
+++ b/src/eu/equalparts/cardbase/standalone/CardbaseCLI.java
@@ -6,17 +6,16 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.util.Arrays;
-import java.util.Iterator;
 import java.util.Scanner;
 
 import com.fasterxml.jackson.core.JsonGenerationException;
 import com.fasterxml.jackson.core.JsonParseException;
 import com.fasterxml.jackson.databind.JsonMappingException;
 
+import eu.equalparts.cardbase.Cardbase;
 import eu.equalparts.cardbase.data.Card;
-import eu.equalparts.cardbase.data.CardbaseManager;
 import eu.equalparts.cardbase.data.FullCardSet;
-import eu.equalparts.cardbase.data.CardSet;
+import eu.equalparts.cardbase.data.CardSetInformation;
 import eu.equalparts.cardbase.utils.MTGUniverse;
 
 /**
@@ -60,9 +59,9 @@ public class CardbaseCLI {
 	 */
 	private FullCardSet selectedSet = null;
 	/**
-	 * The manager object which allows interaction with cardbase data structure.
+	 * The actual cardbase being interfaced with.
 	 */
-	private CardbaseManager cardbaseManager;
+	private Cardbase cardbase;
 	/**
 	 * Printed to the console when the user enters the help command.
 	 */
@@ -71,11 +70,6 @@ public class CardbaseCLI {
 	 * The cardbase file off which we are currently working, if any.
 	 */
 	private File cardbaseFile = null;
-	/**
-	 * Debug flag is raised when the DEBUG environment variable is set. This causes additional
-	 * information to be printed to the console.
-	 */
-	private boolean debug = false;
 	/**
 	 * Save flag is raised when cards are added or removed and causes a prompt to be shown
 	 * if the user tries to exit with unsaved changed.
@@ -106,10 +100,7 @@ public class CardbaseCLI {
 		System.out.println("Welcome to Cardbase CLI!");
 
 		// set debug flag if we are debugging
-		if (System.getenv("CB_DEBUG") != null) {
-			System.out.println("Debug mode is on.");
-			debug = true;
-		}
+		if (Cardbase.DEBUG) System.out.println("Debug mode is on.");
 
 		// make the CardbaseManager
 		if (args.length > 0) {
@@ -117,19 +108,19 @@ public class CardbaseCLI {
 			if (cardbaseFile.exists() && cardbaseFile.isFile() && cardbaseFile.canRead()) {
 				System.out.println("Loading cardbase from \"" + args[0] + "\".");
 				try {
-					cardbaseManager = new CardbaseManager(cardbaseFile);
+					cardbase = new Cardbase(cardbaseFile);
 				} catch (JsonParseException e) {
 					System.out.println("Error: poorly formatted cardbase, check the syntax and try again.");
 					// although the problem could also be with the upstream CardSetList json.
-					if (debug) e.printStackTrace();
+					if (Cardbase.DEBUG) e.printStackTrace();
 					System.exit(1);
 				} catch (JsonMappingException e) {
 					System.out.println("Error: unexpected fields found in cardbase, it may be from an old version?");
-					if (debug) e.printStackTrace();
+					if (Cardbase.DEBUG) e.printStackTrace();
 					System.exit(1);
 				} catch (IOException e) {
 					System.out.println("Error: something went wrong reading cardbase file, abort...");
-					if (debug) e.printStackTrace();
+					if (Cardbase.DEBUG) e.printStackTrace();
 					System.exit(1);
 				}
 			} else {
@@ -138,7 +129,7 @@ public class CardbaseCLI {
 			}
 		} else {
 			System.out.println("No cardbase file was provided, creating a clean cardbase.");
-			cardbaseManager = new CardbaseManager();
+			cardbase = new Cardbase();
 		}
 
 		// load help information
@@ -162,7 +153,7 @@ public class CardbaseCLI {
 				// print prompt
 				System.out.print(selectedSet == null ? "> " : selectedSet.getCode() + " > ");
 				// condition input and interpret
-				String[] raw = consoleReader.readLine().trim().toLowerCase().split("[ \t]+");
+				String[] raw = consoleReader.readLine().trim().split("[ \t]+");
 				String command = raw[0];
 				String[] args = Arrays.copyOfRange(raw, 1, raw.length);
 
@@ -186,13 +177,15 @@ public class CardbaseCLI {
 				} else if (command.equalsIgnoreCase("remove") 
 						|| command.equalsIgnoreCase("rm")) {
 					remove(args);
+				} else if (command.equalsIgnoreCase("sort")) {
+					sort(args);
 				} else {
 					add(command, args);
 				}
 			}
 		} catch (IOException e) {
 			System.out.println("Error: something went wrong with stdin, exiting...");
-			if (debug) e.printStackTrace();
+			if (Cardbase.DEBUG) e.printStackTrace();
 		}
 	}
 
@@ -223,7 +216,7 @@ public class CardbaseCLI {
 			} else {
 				// handle these exceptions locally - they don't necessarily mean the program should exit
 				try {
-					cardbaseManager.writeCardbase(outputFile);
+					cardbase.writeCardbase(outputFile);
 					// we are now working off outputFile, which may or may not be the same as cardbaseFile at this point
 					cardbaseFile = outputFile;
 					System.out.println("Cardbase was saved to \"" + outputFile.getAbsolutePath() + "\". "
@@ -231,10 +224,10 @@ public class CardbaseCLI {
 					savePrompt = false;
 				} catch (JsonGenerationException | JsonMappingException e) {
 					System.out.println("Error: something terrible happened to the internal cardbase data structure. Oops.");
-					if (debug) e.printStackTrace();
+					if (Cardbase.DEBUG) e.printStackTrace();
 				} catch (IOException e) {
 					System.out.println("Error: lost contact with the output file, try again?");
-					if (debug) e.printStackTrace();
+					if (Cardbase.DEBUG) e.printStackTrace();
 				}
 			}
 		} else {
@@ -258,7 +251,7 @@ public class CardbaseCLI {
 	 * Print a list of valid set codes.
 	 */
 	public void sets() {
-		for (CardSet set : MTGUniverse.getCardSetList()) {
+		for (CardSetInformation set : MTGUniverse.getCardSetList()) {
 			// CardSet has an overridden toString()
 			System.out.println(set);
 		}
@@ -283,13 +276,13 @@ public class CardbaseCLI {
 				}
 			} catch (JsonParseException e) {
 				System.out.println("Error: JSON fetched from upstream was not formatted properly.");
-				if (debug) e.printStackTrace();
+				if (Cardbase.DEBUG) e.printStackTrace();
 			} catch (JsonMappingException e) {
 				System.out.println("Error: JSON fetched from upstream does not match the data structure used internally.");
-				if (debug) e.printStackTrace();
+				if (Cardbase.DEBUG) e.printStackTrace();
 			} catch (IOException e) {
 				System.out.println("Error: JSON could not be fetched from upstream.");
-				if (debug) e.printStackTrace();
+				if (Cardbase.DEBUG) e.printStackTrace();
 			}
 		} else {
 			System.out.println("Please enter a set code (use \"sets\" to see all valid set codes).");
@@ -300,12 +293,10 @@ public class CardbaseCLI {
 	 * Print a brief list of the whole cardbase.
 	 */
 	public void glance() {
-		Card current;
 		int total = 0;
-		for (Iterator<Card> i = cardbaseManager.cardIterator(); i.hasNext();) {
-			current = i.next();
-			printGlance(current);
-			total += current.count;
+		for (Card card : cardbase.getCards()) {
+			printGlance(card);
+			total += card.count;
 		}
 		System.out.println("Total: " + total);
 	}
@@ -319,7 +310,7 @@ public class CardbaseCLI {
 		// if a card is specified, peruse only that
 		if (args.length > 0) {
 			if (selectedSet != null) {
-				Card card = cardbaseManager.getCard(selectedSet.getCode(), args[0]);
+				Card card = cardbase.getCard(selectedSet.getCode(), args[0]);
 				if (card != null) {
 					printPerusal(card);
 				} else {
@@ -330,12 +321,10 @@ public class CardbaseCLI {
 			}
 		} else {
 			// peruse all cards in cardbase
-			Card current;
 			int total = 0;
-			for (Iterator<Card> i = cardbaseManager.cardIterator(); i.hasNext();) {
-				current = i.next();
-				printPerusal(current);
-				total += current.count;
+			for (Card card : cardbase.getCards()) {
+				printPerusal(card);
+				total += card.count;
 			}
 			System.out.println("Total: " + total);
 		}
@@ -420,6 +409,24 @@ public class CardbaseCLI {
 			System.out.println("Select a set before adding cards.");
 		}
 	}
+	
+	/**
+	 * Sort the cardbase by a specified parameter.
+	 * 
+	 * @param args the ordering to sort by.
+	 */
+	public void sort(String[] args) {
+		if (args.length > 0) {
+			String fieldName = args[0];
+			if (cardbase.sortBy(fieldName)) {
+				System.out.println("Cardbase sorted by " + fieldName);
+			} else {
+				System.out.println("Unknown field: " + fieldName);
+			}
+		} else {
+			System.out.println("Please enter the field to use for sorting.");
+		}
+	}
 
 	/**
 	 * Add the specified count of the specified card
@@ -429,7 +436,7 @@ public class CardbaseCLI {
 	 * @param count the number of times to add it.
 	 */
 	private void addCard(Card card, Integer count) {
-		cardbaseManager.addCard(card, count);
+		cardbase.addCard(card, count);
 		System.out.println("Added " + count + "x " + card.name + ".");
 		savePrompt = true;
 		lastAction = Action.ADD;
@@ -444,7 +451,7 @@ public class CardbaseCLI {
 	 * @param count the number of times to remove it.
 	 */
 	private void removeCard(Card card, Integer count) {
-		Integer removed = cardbaseManager.removeCard(card, count); 
+		Integer removed = cardbase.removeCard(card, count); 
 		if (removed > 0) {
 			System.out.println("Removed " + removed + "x " + card.name + ".");
 			savePrompt = true;
diff --git a/src/eu/equalparts/cardbase/utils/IO.java b/src/eu/equalparts/cardbase/utils/IO.java
deleted file mode 100644
index 5d4bef5..0000000
--- a/src/eu/equalparts/cardbase/utils/IO.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package eu.equalparts.cardbase.utils;
-
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-/**
- * This class simply holds an {@code ObjectMapper} to be used whenever JSON must be parsed.
- * In the future it may be removed in favour of individual mappers for each function.
- * 
- * @author Eduardo Pedroni
- */
-public final class IO {
-	
-	/**
-	 * The Jackson {@code ObjectMapper} which parses fetched JSON files.
-	 */
-	public static final ObjectMapper jsonMapper = createMapper();
-
-	/**
-	 * Private constructor, this class is not to be instantiated.
-	 */
-	private IO() {}
-	
-	/**
-	 * Instantiate and configure Jackson mapper statically. 
-	 * 
-	 * @return the {@code ObjectMapper}, ready to use.
-	 */
-	private static ObjectMapper createMapper() {
-		ObjectMapper objectMapper = new ObjectMapper();
-		// TODO decide what to do about this
-		objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
-		return objectMapper;
-	}
-}
diff --git a/src/eu/equalparts/cardbase/utils/JSON.java b/src/eu/equalparts/cardbase/utils/JSON.java
new file mode 100644
index 0000000..a5992c7
--- /dev/null
+++ b/src/eu/equalparts/cardbase/utils/JSON.java
@@ -0,0 +1,35 @@
+package eu.equalparts.cardbase.utils;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * This class simply holds an {@code ObjectMapper} to be used whenever JSON must be parsed.
+ * In the future it may be removed in favour of individual mappers for each function.
+ * 
+ * @author Eduardo Pedroni
+ */
+public final class JSON {
+	
+	/**
+	 * The Jackson {@code ObjectMapper} which parses fetched JSON files.
+	 */
+	public static final ObjectMapper mapper = createMapper();
+
+	/**
+	 * Private constructor, this class is not to be instantiated.
+	 */
+	private JSON() {}
+	
+	/**
+	 * Instantiate and configure Jackson mapper statically. 
+	 * 
+	 * @return the {@code ObjectMapper}, ready to use.
+	 */
+	private static ObjectMapper createMapper() {
+		ObjectMapper objectMapper = new ObjectMapper();
+		// TODO decide what to do about this
+		objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+		return objectMapper;
+	}
+}
diff --git a/src/eu/equalparts/cardbase/utils/MTGUniverse.java b/src/eu/equalparts/cardbase/utils/MTGUniverse.java
index 7211f47..0bcda5c 100644
--- a/src/eu/equalparts/cardbase/utils/MTGUniverse.java
+++ b/src/eu/equalparts/cardbase/utils/MTGUniverse.java
@@ -10,7 +10,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.JsonMappingException;
 
 import eu.equalparts.cardbase.data.Card;
-import eu.equalparts.cardbase.data.CardSet;
+import eu.equalparts.cardbase.data.CardSetInformation;
 import eu.equalparts.cardbase.data.FullCardSet;
 
 /**
@@ -37,7 +37,7 @@ public final class MTGUniverse {
 	/**
 	 * A cache of CardSets to avoid querying the server many times for the same information.
 	 */
-	private static ArrayList<CardSet> cardSets;
+	private static ArrayList<CardSetInformation> cardSets;
 
 	/**
 	 * A cache of {@code FullCardSets} to avoid querying the server many times for the same information.
@@ -76,6 +76,9 @@ public final class MTGUniverse {
 	/**
 	 * Returns the specified set in the form of a {@code FullCardSet} object. If the specified
 	 * set code does not correspond to a set, this returns null.
+	 * <br>
+	 * This method takes care of case differences in set code names.
+	 * 
 	 * 
 	 * @param setCode the code of the set to be returned.
 	 * @return the requested {@code FullCardSet} or null if no set matches the given code.
@@ -94,7 +97,7 @@ public final class MTGUniverse {
 			} 
 			// not cached; fetch, cache and return it
 			else {
-				requestedSet = IO.jsonMapper.readValue(new URL(BASE_URL + validCode + ".json"), FullCardSet.class);
+				requestedSet = JSON.mapper.readValue(new URL(BASE_URL + validCode + ".json"), FullCardSet.class);
 				// MTG JSON does not include set code in the card information, but it is useful for sorting
 				for (Card card : requestedSet.getCards()) {
 					card.setCode = validCode;
@@ -108,17 +111,17 @@ public final class MTGUniverse {
 	/**
 	 * @return a list of all card sets in the form of {@code CardSet} objects.
 	 */
-	public static ArrayList<CardSet> getCardSetList() {
+	public static ArrayList<CardSetInformation> getCardSetList() {
 		// if the list isn't cached, fetch and cache it
 		if (cardSets == null) {
 			try {
-				cardSets = IO.jsonMapper.readValue(new URL(BASE_URL + "SetList.json"), new TypeReference<ArrayList<CardSet>>() {});
+				cardSets = JSON.mapper.readValue(new URL(BASE_URL + "SetList.json"), new TypeReference<ArrayList<CardSetInformation>>() {});
 			} catch (Exception e) {
 				System.out.println("Error: could not fetch/parse set code list from upstream, loading fallback json...");
 				e.printStackTrace();
 				
 				try {
-					cardSets = IO.jsonMapper.readValue(MTGUniverse.class.getResourceAsStream(FALLBACK_LIST_PATH), new TypeReference<ArrayList<CardSet>>() {});
+					cardSets = JSON.mapper.readValue(MTGUniverse.class.getResourceAsStream(FALLBACK_LIST_PATH), new TypeReference<ArrayList<CardSetInformation>>() {});
 				} catch (Exception f) {
 					System.out.println("Error: could not parse fallback set code list, aborting...");
 					f.printStackTrace();
@@ -142,7 +145,7 @@ public final class MTGUniverse {
 	 * @return the valid form of the set code if any, null otherwise.
 	 */
 	public static String validateSetCode(String setCode) {
-		for (CardSet cardSet : getCardSetList()) {
+		for (CardSetInformation cardSet : getCardSetList()) {
 			if (cardSet.getCode().equalsIgnoreCase(setCode)) {
 				return cardSet.getCode();
 			}
-- 
cgit v1.2.3