diff options
author | Eduardo Pedroni <e.pedroni91@gmail.com> | 2015-06-24 18:58:15 +0200 |
---|---|---|
committer | Eduardo Pedroni <e.pedroni91@gmail.com> | 2015-06-24 18:58:15 +0200 |
commit | bf29c3d96160eeaa64f60a512a0a9f4d7c85167a (patch) | |
tree | d748a0fa4016c188b3d4be5bdd719261b40655b0 /src/eu/equalparts/cardbase/comparator | |
parent | 59174889aba82c64ae63e0a5622d6cbcab63dc39 (diff) |
Mulling over the generic comparator issue
Diffstat (limited to 'src/eu/equalparts/cardbase/comparator')
3 files changed, 173 insertions, 0 deletions
diff --git a/src/eu/equalparts/cardbase/comparator/CardComparator.java b/src/eu/equalparts/cardbase/comparator/CardComparator.java new file mode 100644 index 0000000..78200b8 --- /dev/null +++ b/src/eu/equalparts/cardbase/comparator/CardComparator.java @@ -0,0 +1,126 @@ +package eu.equalparts.cardbase.comparator; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Comparator; +import java.util.function.BiFunction; + +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> { + + public enum Order { + NATURAL, REVERSE; + } + + /** + * The field being compared. + */ + private Field fieldToCompare; + + private BiFunction<Comparable, Comparable, Integer> comparisonDelegate = this::defaultComparison; + + /** + * 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; + + // if annotated with a special comparator, set the comparison delegate here + this.comparisonDelegate = ComparatorDelegates::compareManaCost; + + } 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) { + /* + * we've already checked that the field is self comparable, + * so we are now free to cast to whatever type it is and compare. + */ + try { + Comparable field1 = (Comparable) fieldToCompare.get(o1); + Comparable field2 = (Comparable) fieldToCompare.get(o2); + + return comparisonDelegate.apply(field1, 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(); + } + + // fallback, this shouldn't happen + return 0; + } + + /** + * The standard comparison operation, which uses the field's own {@code compareTo()} + * method. + * + * @param field1 the first object to be compared. + * @param field2 the second object to be compared. + * @return a negative integer, zero, or a positive integer as the + * first argument is less than, equal to, or greater than the + * second. + */ + private int defaultComparison(Comparable field1, Comparable field2) { + return field1.compareTo(field2); + } + + /** + * 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/comparator/ComparatorDelegates.java b/src/eu/equalparts/cardbase/comparator/ComparatorDelegates.java new file mode 100644 index 0000000..5736381 --- /dev/null +++ b/src/eu/equalparts/cardbase/comparator/ComparatorDelegates.java @@ -0,0 +1,39 @@ +package eu.equalparts.cardbase.comparator; + + +abstract class ComparatorDelegates { + + private ComparatorDelegates() {} + + public static Integer compareManaCost(Comparable<String> field1, Comparable<String> field2) { + // avoid casting syntax nightmare + String mc1 = (String) field1, mc2 = (String) field2; + + // first by number of colours + int mc1count = 0, mc2count = 0; + if (mc1.contains("W")) mc1count++; + if (mc1.contains("U")) mc1count++; + if (mc1.contains("B")) mc1count++; + if (mc1.contains("R")) mc1count++; + if (mc1.contains("G")) mc1count++; + + if (mc2.contains("W")) mc2count++; + if (mc2.contains("U")) mc2count++; + if (mc2.contains("B")) mc2count++; + if (mc2.contains("R")) mc2count++; + if (mc2.contains("G")) mc2count++; + + if (mc1count != mc2count) + return (mc1count < mc2count) ? -1 : ((mc1count == mc2count) ? 0 : 1); + + // next by colour wheel + + return 0; + } + +} +/* + * first by number of colours + * next by colour wheel: white > blue > black > red > green + * next by cmc + */
\ No newline at end of file diff --git a/src/eu/equalparts/cardbase/comparator/SpecialFields.java b/src/eu/equalparts/cardbase/comparator/SpecialFields.java new file mode 100644 index 0000000..8ecc82c --- /dev/null +++ b/src/eu/equalparts/cardbase/comparator/SpecialFields.java @@ -0,0 +1,8 @@ +package eu.equalparts.cardbase.comparator; + + +public class SpecialFields { + + public @interface ManaCost {} + +} |