I've been inspired in part by MTG-JSON to try to come up with a sort of standard for how a Magic card might be represented as a Java Object. Here's how it currently looks.
The thought behind this is that you can read in cards from a file (I have this working with MTG-JSON) and then use it for whatever purpose you want. I've used it to perform queries that Gatherer could never do, such as "What are all the unique mana costs?" or "What card has the longest text box?" or "What creature types exist in all colors but one?" You know, cool stuff like that. The idea is, once the API is done, I'd make all the source code public.
My basic philosophy is, if a value on a card is optional, but its presence can be inferred by other attributes (power and toughness are optional, but they're only found on creatures), then you can access them directly, but that method might throw an exception if you're not careful. However, for attributes that have no relation to other properties of the card, they either exist or they don't, I use an Optional wrapper (part of Guava, but with the coming release of Java 8, it will be part of the language). I have comments on the areas I'm undecided about.
There are a few user-defined types I've created (most notably ManaCost) that I don't go into detail on. Let me know if you want to see how they're set up.
And finally, because I couldn't figure out how to upload a text file, here it is. I even color-coded it the way your IDE might. You're welcome. I didn't use CODE tags; though they would preserve my tabs, I figured color was more important (this is just an interface anyway, not really any indents.) If someone could tell me how to use Courier font, though, that would be nice.
Anyway, let me know what you think of how I've organized this. If it seems simple, I'll take that as a compliment, it was actually pretty difficult to work out how all the data would be represented.
/*
* One of NORMAL, SPLIT, FLIP, or DOUBLE_FACED
*/ Layout layout();
String name();
ManaCost manaCost();
Set<Supertype> supertypes();
Set<Type> types();
Set<String> subtypes();
/*
* A Printing has values Expansion and Rarity. This is a Multiset because
* some cards are printed multiple times in the same set (notably Basic
* Lands). An alternative to this way would be to have this return a
* Map<Expansion, Pair<Rarity, Integer>> or something similar; this would
* improve the performance of inExpansion. The Integer part would
* represent the number of printings in that entry.
*/ Multiset<Printing> printings();
String text();
/*
* Convenience method to determine if power() and toughness() will throw an
* exception.
*/ boolean isCreature();
/*
* Throws an exception if isCreature() returns false.
*
* Does this way make the most sense? Should I sacrifice size and speed
* for consistency here, and make these both Optional<Expression>? That
* seems kind of excessive.
*
* An expression encapsulates a String. If that String represents an
* integer, the value of that integer can be obtained.
*/ Expression power();
Expression toughness();
/*
* Unlike power and toughness, loyalty is optional even on Planeswalkers
* (see Garruk, the Veil-Cursed). Otherwise, this would simply be an int
* alongside a convenience method isPlaneswalker().
*/ Optional<Integer> loyalty();
/*
* If this card is:
* NORMAL: always true
* SPLIT: true if this card is the left half
* FLIP: true if this card is the top half
* DOUBLE_FACED: true if this card is the "Day" half
*/ boolean isMainHalf();
/*
* Throws an exception if hasOtherHalf is false. The reason this isn't an
* Optional<Card> is because its presence can be inferred from the layout.
* All the fields that use the Optional implementation cannot be inferred
* from other elements.
*/ Card otherHalf();
/*
* Convenience method mostly for debugging. Prints the card in a nice format.
*/ void print();
void print(PrintStream out); }
Oh yeah, a few notes:
Layout is an enum
ManaCost is basically an immutable container of Symbols (an enum)
Supertype and Type are enums
Printing is an immutable class
Expression (as I mentioned) is basically an immutable String wrapper with an optional int field
Color is an enum (duh)
Rarity is an enum
The thought behind this is that you can read in cards from a file (I have this working with MTG-JSON) and then use it for whatever purpose you want. I've used it to perform queries that Gatherer could never do, such as "What are all the unique mana costs?" or "What card has the longest text box?" or "What creature types exist in all colors but one?" You know, cool stuff like that. The idea is, once the API is done, I'd make all the source code public.
My basic philosophy is, if a value on a card is optional, but its presence can be inferred by other attributes (power and toughness are optional, but they're only found on creatures), then you can access them directly, but that method might throw an exception if you're not careful. However, for attributes that have no relation to other properties of the card, they either exist or they don't, I use an Optional wrapper (part of Guava, but with the coming release of Java 8, it will be part of the language). I have comments on the areas I'm undecided about.
There are a few user-defined types I've created (most notably ManaCost) that I don't go into detail on. Let me know if you want to see how they're set up.
And finally, because I couldn't figure out how to upload a text file, here it is. I even color-coded it the way your IDE might. You're welcome. I didn't use CODE tags; though they would preserve my tabs, I figured color was more important (this is just an interface anyway, not really any indents.) If someone could tell me how to use Courier font, though, that would be nice.
Anyway, let me know what you think of how I've organized this. If it seems simple, I'll take that as a compliment, it was actually pretty difficult to work out how all the data would be represented.
import java.io.PrintStream;
import java.util.Set;
import com.google.common.base.Optional;
import com.google.common.collect.Multiset;
public interface Card extends Comparable<Card> {
/*
* One of NORMAL, SPLIT, FLIP, or DOUBLE_FACED
*/
Layout layout();
String name();
ManaCost manaCost();
Set<Supertype> supertypes();
Set<Type> types();
Set<String> subtypes();
/*
* A Printing has values Expansion and Rarity. This is a Multiset because
* some cards are printed multiple times in the same set (notably Basic
* Lands). An alternative to this way would be to have this return a
* Map<Expansion, Pair<Rarity, Integer>> or something similar; this would
* improve the performance of inExpansion. The Integer part would
* represent the number of printings in that entry.
*/
Multiset<Printing> printings();
String text();
/*
* Convenience method to determine if power() and toughness() will throw an
* exception.
*/
boolean isCreature();
/*
* Throws an exception if isCreature() returns false.
*
* Does this way make the most sense? Should I sacrifice size and speed
* for consistency here, and make these both Optional<Expression>? That
* seems kind of excessive.
*
* An expression encapsulates a String. If that String represents an
* integer, the value of that integer can be obtained.
*/
Expression power();
Expression toughness();
/*
* Unlike power and toughness, loyalty is optional even on Planeswalkers
* (see Garruk, the Veil-Cursed). Otherwise, this would simply be an int
* alongside a convenience method isPlaneswalker().
*/
Optional<Integer> loyalty();
Set<Color> colors();
Set<Color> colorIdentity();
Optional<? extends Set<Color>> colorIndicator();
boolean isRarity(Rarity rarity);
boolean inExpansion(Expansion expansion);
/*
* Convenience method equivalent to layout() != Layout.NORMAL.
*/
boolean hasOtherHalf();
/*
* If this card is:
* NORMAL: always true
* SPLIT: true if this card is the left half
* FLIP: true if this card is the top half
* DOUBLE_FACED: true if this card is the "Day" half
*/
boolean isMainHalf();
/*
* Throws an exception if hasOtherHalf is false. The reason this isn't an
* Optional<Card> is because its presence can be inferred from the layout.
* All the fields that use the Optional implementation cannot be inferred
* from other elements.
*/
Card otherHalf();
/*
* Convenience method mostly for debugging. Prints the card in a nice format.
*/
void print();
void print(PrintStream out);
}
Oh yeah, a few notes:
Layout is an enum
ManaCost is basically an immutable container of Symbols (an enum)
Supertype and Type are enums
Printing is an immutable class
Expression (as I mentioned) is basically an immutable String wrapper with an optional int field
Color is an enum (duh)
Rarity is an enum