CMSI 281
Homework #1
Partial Answers
  1. A UML class diagram for a portion of an HR application:

    To be done in class.

  2. An immutable 2-D Point class:
    package edu.lmu.cs.geometry;
    
    /**
     * An immutable 2-D point class.
     */
    public class Point {
    
        private double x;
        private double y;
    
        /**
         * Constructs a point.
         */
        public Point(double x, double y) {
            this.x = x;
            this.y = y;
        }
    
        /**
         * Returns the x coordinate.
         */
        public double getX() {
            return x;
        }
    
        /**
         * Returns the y coordinate.
         */
        public double getY() {
            return y;
        }
    
        /**
         * Returns the distance from this point to the given point.
         */
        public double distanceTo(Point p) {
            return Math.sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y));
        }
    
        @Override
        public int hashCode() {
            final int PRIME = 31;
            int result = 1;
            long temp = Double.doubleToLongBits(x);
            result = PRIME * result + (int) (temp ^ (temp >>> 32));
            temp = Double.doubleToLongBits(y);
            result = PRIME * result + (int) (temp ^ (temp >>> 32));
            return result;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null) return false;
            if (getClass() != obj.getClass()) return false;
            final Point other = (Point) obj;
            if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x))
                return false;
            if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y))
                return false;
            return true;
        }
    
        /**
         * Returns a conventional string representation of this point,
         * namely "(x,y)".
         */
        @Override
        public String toString() {
            return "(" + x + "," + y + ")";
        }
    }
    
  3. Unit test for points:
    package edu.lmu.cs.geometry;
    
    import static org.junit.Assert.assertEquals;
    import static org.junit.Assert.assertFalse;
    
    import org.junit.Test;
    
    /**
     * A unit test for points.
     */
    public class PointTest {
        private static double TOLERANCE = 0.0000000001;	
    
        @Test
        public void testGetters() {
            Point p = new Point(3, 5);
            assertEquals(p.getX(), 3.0, TOLERANCE);
            assertEquals(p.getY(), 5.0, TOLERANCE);
        }
    
        @Test
        public void testToString() {
            Point p = new Point(3, 5);
            assertEquals(p.toString(), "(3.0,5.0)");
        }
    
        @Test
        public void testEquals() {
            Point p = new Point(3, 5);
            Point q = new Point(3.0000000000001, 5);
            assertEquals(p, p);
            assertEquals(p, new Point(3, 5));
            assertFalse(p.equals(q));
            assertFalse(p.equals(new Point(3, 5.2)));
            assertFalse(p.equals("I am not a point"));
            assertFalse(p.equals("(3.0,5.0)"));
        }
    
        /**
         * Some distance checks.  We also check that the distance computation
         * is symmetric in all cases.
         */
        @Test
        public void testDistance() {
            Point p = new Point(3, 5);
            Point q = new Point(9, 5);
            
            // Check points with same y value
            assertEquals(p.distanceTo(q), 6.0, TOLERANCE);
            assertEquals(q.distanceTo(p), 6.0, TOLERANCE);
            
            // Check points with same x value
            q = new Point(3, 12);
            assertEquals(p.distanceTo(q), 7.0, TOLERANCE);
            assertEquals(q.distanceTo(p), 7.0, TOLERANCE);
            
            // A 5-12-13 right triangle
            q = new Point(-2, 17);
            assertEquals(p.distanceTo(q), 13.0, TOLERANCE);
            assertEquals(q.distanceTo(p), 13.0, TOLERANCE);
        }
    }
    
  4. A mutable 2-D line class:
    package edu.lmu.cs.geometry;
    
    /**
     * A 2-D line class.
     */
    public class Line {
    
        // A line is represented as a point and a direction, specified as
        // the number of radians rotated counterclockwise from the x-axis.
        // Invariant: the angle is always stored as a value between 0.0
        // inclusive and 2*Math.PI (exclusive).
    
        private Point base;
        private double direction;
    
        /**
         * Constructs a line from base point coordinates and a direction.
         * The line constructor takes in point coordinates rather than
         * points so we never have to worry about pesky nulls.
         */
        public Line(double x, double y, double direction) {
            this.base = new Point(x, y);
            this.direction = direction;
            normalizeDirection();
        }
    
        /**
         * Returns a point on this line.
         */
        public Point getBase() {
            return base;
        }
    
        /**
         * Returns the direction of this line, in radians counterclockwise
         * from the x axis.
         */
        public double getDirection() {
            return direction;
        }
    
        /**
         * Shift the line so that it contains a point with the given
         * coorindates.
         */
        public void moveTo(double x, double y) {
            base = new Point(x, y);
        }
    
        /**
         * Rotate the line by theta radians around its base.
         */
        public void rotate(double theta) {
            direction += theta;
            normalizeDirection();
        }
    
        /**
         * Returns the distance from this line to the given point.  Adapted
         * from http://softsurfer.com/Archive/algorithm_0102/algorithm_0102.htm,
         * which gave the distance formula assuming that two points on a line
         * were given.  The two points chosen were p and p+<cos(direction),
         * sin(direction)> then the equation simplified quite a bit.
         */
        public double distanceTo(Point p) {
            return Math.abs(Math.sin(direction) * (base.getX() - p.getX())
                    + Math.cos(direction) * (p.getY() - base.getY()));
        }
    
        /**
         * Returns whether the direction of this line is within a given
         * number of radians to another line.
         */
        public boolean isParallelTo(Line other, double tolerance) {
            return Math.abs(direction - other.getDirection())
                    < Math.abs(tolerance);
        }
    
        /**
         * Returns a conventional string representation of this line,
         * something like <tt>(2,4)+&lt;8,2&gt;u</tt>.
         */
        @Override
        public String toString() {
            return base + " + <" + Math.cos(direction) + ","
                    + Math.sin(direction) + ">u";
        }
    
        /**
         * Force the direction to be between 0 and 2&pi;.
         */
        private void normalizeDirection() {
            direction %= 2.0 * Math.PI;
            if (direction < 0.0) {
                direction += 2.0 * Math.PI;
            }
        }
    }
    
  5. Unit tests for lines:
    package edu.lmu.cs.geometry;
    
    import static org.junit.Assert.assertEquals;
    import static org.junit.Assert.assertNotNull;
    import static org.junit.Assert.assertTrue;
    import static org.junit.Assert.assertFalse;
    
    import org.junit.Test;
    
    /**
     * A unit test for lines.
     */
    public class LineTest {
        private static double TOLERANCE = 0.0000000001;
    
        @Test
        public void testGetters() {
            Line line = new Line(3, 5, 0);
            assertNotNull(line);
            assertNotNull(line.getBase());
            assertEquals(line.getBase(), new Point(3, 5));
            assertEquals(line.getDirection(), 0.0, TOLERANCE);
        }
    
        @Test
        public void testToString() {
            Line line = new Line(3, 5, 0);
            assertEquals(line.toString(), "(3.0,5.0) + <1.0,0.0>u");
        }
    
        @Test
        public void testMutators() {
            Line line = new Line(2, 9, Math.PI/4);
            assertEquals(line.getBase(), new Point(2, 9));
            line.moveTo(12, 6);
            assertEquals(line.getBase(), new Point(12, 6));
            assertEquals(line.getDirection(), Math.PI/4, TOLERANCE);
            line.rotate(3.0*Math.PI/4);
            assertLineDirection(line, Math.PI);
            line.rotate(0.3);
            line.rotate(101*Math.PI);
            assertLineDirection(line, 0.3);
            line = new Line(4, 4, 0);
            line.rotate(-101*Math.PI);
            assertLineDirection(line, Math.PI);
        }
    
        @Test
        public void testParallel() {
            Line l1 = new Line(3, 5, 4);
            Line l2 = new Line(33, -529, 4 + 8 * Math.PI);
            Line l3 = new Line(0, 0, 4 - 200 * Math.PI);
            Line l4 = new Line(6, 8888, 1000);
            assertTrue(l1.isParallelTo(l2, TOLERANCE));
            assertTrue(l2.isParallelTo(l1, TOLERANCE));
            assertTrue(l1.isParallelTo(l3, TOLERANCE));
            assertFalse(l1.isParallelTo(l4, TOLERANCE));
        }
        
        /**
         * For various lines -- horizontal, vertical, positive slope,
         * negative slope -- test distance to points in all four quadrants
         * around the base point as well as points on the line.
         */
        @Test
        public void testDistances() {
    
            // Vertical line in up direction
            Line line = new Line(3, 4, Math.PI / 2.0);
            assertDistance(line, 0, -5, 3.0);
            assertDistance(line, 5, -3, 2.0);
            assertDistance(line, 2, 9, 1.0);
            assertDistance(line, 6, 7, 3.0);
            assertDistance(line, 3, 3, 0.0);
            assertDistance(line, 3, 5, 0.0);
    
            // Vertical line in down direction
            line = new Line(3, 4, 3 * Math.PI / 2.0);
            assertDistance(line, 0, -5, 3.0);
            assertDistance(line, 5, -3, 2.0);
            assertDistance(line, 2, 9, 1.0);
            assertDistance(line, 6, 7, 3.0);
            assertDistance(line, 3, 3, 0.0);
            assertDistance(line, 3, 5, 0.0);
    
            // Horizontal line going from left to right
            line = new Line(3, 4, 0);
            assertDistance(line, -3, -5, 9.0);
            assertDistance(line, 5, -3, 7.0);
            assertDistance(line, 2, 9, 5.0);
            assertDistance(line, 6, 7, 3.0);
            assertDistance(line, 2, 4, 0.0);
            assertDistance(line, 4, 4, 0.0);
    
            // Horizontal line going from right to left
            line = new Line(3, 4, Math.PI);
            assertDistance(line, -3, -5, 9.0);
            assertDistance(line, 5, -3, 7.0);
            assertDistance(line, 2, 9, 5.0);
            assertDistance(line, 6, 7, 3.0);
            assertDistance(line, 2, 4, 0.0);
            assertDistance(line, 4, 4, 0.0);
    
            // Positive slope from quadrant 3 to quadrant 1
            line = new Line(4, 3, Math.atan2(3, 4));
            assertDistance(line, -4, 7, 8.0);
            assertDistance(line, 0, -5, 4.0);
            assertDistance(line, 8, 1, 4.0);
            assertDistance(line, 12, 59, 40.0);
            assertDistance(line, -4, -3, 0.0);
            assertDistance(line, 16, 12, 0.0);
    
            // Positive slope from quadrant 1 to quadrant 3
            line = new Line(4, 3, Math.atan2(-3, -4));
            assertDistance(line, -4, 7, 8.0);
            assertDistance(line, 0, -5, 4.0);
            assertDistance(line, 8, 1, 4.0);
            assertDistance(line, 12, 59, 40.0);
            assertDistance(line, -4, -3, 0.0);
            assertDistance(line, 16, 12, 0.0);
    
            // Negative slope from quadrant 4 to quadrant 2
            line = new Line(-4, 3, Math.atan2(3, -4));
            assertDistance(line, -4, -7, 8.0);
            assertDistance(line, 0, 5, 4.0);
            assertDistance(line, -12, 8.5, 0.4);
            assertDistance(line, -12, 24, 12.0);
            assertDistance(line, -12, 9, 0.0);
            assertDistance(line, 8, -6, 0.0);
    
            // Negative slope from quadrant 2 to quadrant 4
            line = new Line(-4, 3, Math.atan2(-3, 4));
            assertDistance(line, -4, -7, 8.0);
            assertDistance(line, 0, 5, 4.0);
            assertDistance(line, -12, 8.5, 0.4);
            assertDistance(line, -12, 24, 12.0);
            assertDistance(line, -12, 9, 0.0);
            assertDistance(line, 8, -6, 0.0);
        }
    
        // Helper for direction tests.
        private void assertLineDirection(Line line, double expected) {
            assertEquals(line.getDirection(), expected, TOLERANCE);
        }
    
        // Helper for distance tests.
        private void assertDistance(Line line, double x, double y, double expected) {
            assertEquals(line.distanceTo(new Point(x, y)), expected, TOLERANCE);
        }
    }
    
  6. Playing cards:
    package edu.lmu.cs.games;
    
    /**
     * An immutable playing card.
     */
    public class Card {
    
        private Rank rank;
        private Suit suit;
    
        public static enum Rank {
            ACE("A"), TWO("2"), THREE("3"), FOUR("4"), FIVE("5"),
            SIX("6"), SEVEN("7"), EIGHT("8"), NINE("9"), TEN("10"),
            JACK("J"), QUEEN("Q"), KING("K");
    
            private String displayValue;
    
            private Rank(String displayValue) {
                this.displayValue = displayValue;
            }
    
            /**
             * Returns the rank whose display value is the given string.
             */
            public static Rank fromString(String text) {
                for (Rank r: values()) {
                    if (r.displayValue.equals(text)) return r;
                }
                throw new IllegalArgumentException("No rank for " + text);
            }
    
            @Override
            public String toString() {
                return displayValue;
            }
        }
    
        public static enum Suit {
            CLUBS, DIAMONDS, HEARTS, SPADES;
    
            @Override
            public String toString() {
                return name().substring(0, 1);
            }
    
            /**
             * Returns the suit whose display value is the given string.
             */
            public static Suit fromString(String text) {
                for (Suit s: values()) {
                    if (s.toString().equals(text)) return s;
                }
                throw new IllegalArgumentException("No suit for " + text);
            }
    
        }
    
        // Pre-allocate a cache of all possible cards
        private static Card[][] cache =
            new Card[Rank.values().length][Suit.values().length];
    
        // Fill in the cache with all possible cards
        static {
            for (Rank rank: Rank.values()) {
                for (Suit suit: Suit.values()) {
                    cache[rank.ordinal()][suit.ordinal()] = new Card(rank, suit);
                }
            }
        }
    
        /**
         * Returns the unique card whose display value is the given
         * string, e.g. "10S", "KH", "6D".
         */
        public static Card fromString(String descriptor) {
            if (descriptor == null || descriptor.length() < 2) {
                throw new IllegalArgumentException("Bad card description: "
                        + descriptor);
            }
            String rank = descriptor.substring(0, descriptor.length() - 1);
            String suit = descriptor.substring(descriptor.length() - 1);
            return fromRankAndSuit(Rank.fromString(rank),
                    Suit.fromString(suit));
        }
    
        /**
         * Return the unique card with the given rank and suit.
         */
        public static Card fromRankAndSuit(Rank rank, Suit suit) {
            return cache[rank.ordinal()][suit.ordinal()];
        }
    
        /**
         * Constructs a card with the given rank and suit.  This constructor
         * is private because the only way to get a card object from outside
         * of this class is through the factory method, which always pulls
         * the proper card from the cache.
         */
        private Card(Rank rank, Suit suit) {
            this.rank = rank;
            this.suit = suit;
        }
    
        /**
         * Returns the rank of this card.
         */
        public Rank getRank() {
            return rank;
        }
    
        /**
         * Returns the suit of this card.
         */
        public Suit getSuit() {
            return suit;
        }
    
        @Override
        public String toString() {
            return rank.toString() + suit.toString();
        }
    }
    

    and unit tests

    package edu.lmu.cs.games;
    
    import static org.junit.Assert.assertEquals;
    import static org.junit.Assert.assertFalse;
    import static org.junit.Assert.assertTrue;
    
    import org.junit.Test;
    
    import edu.lmu.cs.games.Card.Rank;
    import edu.lmu.cs.games.Card.Suit;
    
    /**
     * Unit tester for the card class.
     */
    public class CardTest {
    
        @Test
        public void testRankAndSuitFactoryMethod() {
            Card c = Card.fromRankAndSuit(Rank.SIX, Suit.CLUBS);
            assertEquals(c.getRank(), Rank.SIX);
            assertEquals(c.getSuit(), Suit.CLUBS);
            assertFalse(c.getRank() == Rank.KING);
            assertFalse(c.getSuit() == Suit.SPADES);
            assertEquals(c.toString(), "6C");
        }
    
        @Test
        public void testDescriptorFactoryMethod() {
            assertCardEquals(Card.fromString("AS"), Rank.ACE, Suit.SPADES);
            assertCardEquals(Card.fromString("2S"), Rank.TWO, Suit.SPADES);
            assertCardEquals(Card.fromString("3S"), Rank.THREE, Suit.SPADES);
            assertCardEquals(Card.fromString("4S"), Rank.FOUR, Suit.SPADES);
            assertCardEquals(Card.fromString("5S"), Rank.FIVE, Suit.SPADES);
            assertCardEquals(Card.fromString("6C"), Rank.SIX, Suit.CLUBS);
            assertCardEquals(Card.fromString("7S"), Rank.SEVEN, Suit.SPADES);
            assertCardEquals(Card.fromString("8S"), Rank.EIGHT, Suit.SPADES);
            assertCardEquals(Card.fromString("9S"), Rank.NINE, Suit.SPADES);
            assertCardEquals(Card.fromString("10H"), Rank.TEN, Suit.HEARTS);
            assertCardEquals(Card.fromString("JS"), Rank.JACK, Suit.SPADES);
            assertCardEquals(Card.fromString("QD"), Rank.QUEEN, Suit.DIAMONDS);
            assertCardEquals(Card.fromString("KS"), Rank.KING, Suit.SPADES);
        }
    
        @Test
        public void testUnique() {
            Card c1 = Card.fromRankAndSuit(Rank.NINE, Suit.CLUBS);
            Card c2 = Card.fromRankAndSuit(Rank.NINE, Suit.CLUBS);
            assertTrue(c1 == c2);
            c1 = Card.fromString("10S");
            c2 = Card.fromString("10S");
            assertTrue(c1 == c2);
        }
    
        @Test
        public void testToString() {
            assertCardStringEquals(Rank.ACE, Suit.SPADES, "AS");
            assertCardStringEquals(Rank.TWO, Suit.DIAMONDS, "2D");
            assertCardStringEquals(Rank.THREE, Suit.SPADES, "3S");
            assertCardStringEquals(Rank.FOUR, Suit.SPADES, "4S");
            assertCardStringEquals(Rank.FIVE, Suit.SPADES, "5S");
            assertCardStringEquals(Rank.SIX, Suit.HEARTS, "6H");
            assertCardStringEquals(Rank.SEVEN, Suit.SPADES, "7S");
            assertCardStringEquals(Rank.EIGHT, Suit.SPADES, "8S");
            assertCardStringEquals(Rank.NINE, Suit.SPADES, "9S");
            assertCardStringEquals(Rank.TEN, Suit.SPADES, "10S");
            assertCardStringEquals(Rank.JACK, Suit.SPADES, "JS");
            assertCardStringEquals(Rank.QUEEN, Suit.SPADES, "QS");
            assertCardStringEquals(Rank.KING, Suit.SPADES, "KS");
        }
    
        // Helper for factory method tests
        private void assertCardEquals(Card card, Rank rank, Suit suit) {
            assertEquals(card.getRank(), rank);
            assertEquals(card.getSuit(), suit);
        }
    
        // Helper for toString tests
        private void assertCardStringEquals(Rank rank, Suit suit, String expected) {
            assertEquals(Card.fromRankAndSuit(rank, suit).toString(), expected);
        }
    }