Graph.java

package edu.hawaii.ics.yucheng;

import java.net.URL;
import java.util.Scanner;

/**
 * A class that represents a mesh graph. This class is immutable.
 */
final class Graph {

    /**
     * This method returns true if the specified value is exceeds a range.
     * 
     * @param value The value.
     * 
     * @param low The low value (inclusive).
     * 
     * @param high The high value (inclusive).
     * 
     * @return True indicates the value exceeds the range.
     */
    private static boolean exceeds(final int value, final int low, final int high) {
        return value < low || value > high;
    }


    /**
     * Reads a vertex from a scanner.
     * 
     * @param scanner The scanner.
     * 
     * @return The vertex.
     */
    private static Vertex readVertex(final Scanner scanner) {
        assert null != scanner;

        // Read the next line, and check there are two fields separated by a comma.
        final String line = scanner.nextLine();
        final String[] fields = line.split(",");
        if (fields.length != 2)
            throw new GraphException("Invalid graph input: '" + line + "'.");

        // Parse each field into two integers, and return a vertex.
        final int a = Integer.parseInt(fields[0].trim());
        final int b = Integer.parseInt(fields[1].trim());
        return new Vertex(b, a);
    }

    /** The base location. */
    public final Vertex     base;

    /** The initial battery capacity. */
    public final int        capacity;

    /** The height of the graph. */
    public final int        height;

    /** The battery consumption values. */
    private final int[]     myConsumptions;

    /** The obstacles. */
    private final boolean[] myObstacles;

    /** The priority values. */
    private final int[]     myPriorities;

    /** The corresponding URL, if any. */
    public final URL        url;

    /** The width of the graph. */
    public final int        width;


    /**
     * Initializes a new instance of the class.
     * 
     * @param builder A graph builder.
     */
    Graph(final GraphBuilder builder) {
        assert null != builder;

        // Initialize the class data.
        base = builder.base();
        capacity = builder.capacity();
        height = builder.height;
        width = builder.width;
        url = null;

        // Allocate space for the arrays.
        myConsumptions = new int[width * height];
        myObstacles = new boolean[width * height];
        myPriorities = new int[width * height];

        // Copy the values from the builder.
        for (int j = 0; j < height; j++)
            for (int i = 0; i < width; i++) {
                final int index = indexOf(i, j);
                myConsumptions[index] = builder.consumption(i, j);
                myObstacles[index] = builder.obstacle(i, j);
                myPriorities[index] = builder.priority(i, j);
            }
    }


    /**
     * Initializes a new instance of the class.
     * 
     * @param scanner The scanner.
     * 
     * @param url The URL.
     */
    public Graph(final Scanner scanner, final URL url) {

        // Do not allow a null scanner object.
        if (scanner == null)
            throw new NullPointerException("scanner");

        // Store the class URL; null is okay.
        this.url = url;

        try {
            // Read the size of the mesh graph.
            final Vertex meshSize = readVertex(scanner);
            width = meshSize.x;
            height = meshSize.y;

            // Allocate space for the obstacles, and read them.
            myObstacles = new boolean[width * height];
            readObstacles(scanner);

            // Read the vertex for the base.
            final Vertex vertex = readVertex(scanner);
            base = new Vertex(vertex.x - 1, vertex.y - 1);
            verifyValidBase();

            // Read the grid of priorities and consumptions.
            myPriorities = readGrid(scanner);
            capacity = Integer.parseInt(scanner.nextLine().trim());
            myConsumptions = readGrid(scanner);

            // Ensure no data exists at the end of the stream.
            readToEnd(scanner);

        } catch (final GraphException e) {
            throw e;

        } catch (final Exception e) {
            throw new GraphException("Invalid graph.", e);
        }
    }


    /**
     * Builds a grid for the toString method.
     * 
     * @param builder The string builder.
     * 
     * @param grid The grid of values.
     */
    private void buildGrid(final StringBuilder builder, final int[] grid) {
        assert null != builder;
        assert null != grid;
        assert grid.length == width * height;

        int k = 0;
        for (int j = 0; j < height; j++) {
            for (int i = 0; i < width; i++)
                builder.append(grid[k++] + (i < width - 1 ? "\t" : ""));
            builder.append("\n");
        }
    }


    /**
     * Builds a grid of obstacles for the toString method.
     * 
     * @param builder The string builder.
     */
    private void buildObstacles(final StringBuilder builder) {
        assert null != builder;

        int k = 0;
        for (int j = 0; j < height; j++) {
            for (int i = 0; i < width; i++)
                builder.append(myObstacles[k++] ? "x" : " ");
            builder.append("\n");
        }
    }


    /**
     * Returns the consumption value at the specified location in the graph.
     * 
     * @param x The x coordinate (zero-bound).
     * 
     * @param y The y coordinate (zero-bound).
     * 
     * @return The consumption value.
     */
    public int consumption(final int x, final int y) {
        return myConsumptions[indexOf(x, y)];
    }


    /**
     * Returns the array index for a specified location.
     * 
     * @param x The x coordinate.
     * 
     * @param y The y coordinate.
     * 
     * @return The array index.
     */
    private int indexOf(final int x, final int y) {

        if (exceeds(x, 0, width - 1))
            throw new IndexOutOfBoundsException("x");
        if (exceeds(y, 0, height - 1))
            throw new IndexOutOfBoundsException("y");

        return x + y * width;
    }


    /**
     * Returns true if the specified location is an obstacle.
     * 
     * @param x The x coordinate (zero-bound).
     * 
     * @param y The y coordinate (zero-bound).
     * 
     * @return True indicates the specified location is an obstacle.
     */
    public boolean isObstacle(final int x, final int y) {
        return myObstacles[indexOf(x, y)];
    }


    /**
     * Returns the priority at the specified location.
     * 
     * @param x The x coordinate (zero-bound).
     * 
     * @param y The y coordinate (zero-bound).
     * 
     * @return The priority at the specified location.
     */
    public int priority(final int x, final int y) {
        return myPriorities[indexOf(x, y)];
    }


    /**
     * Reads a grid of numbers from a scanner.
     * 
     * @param scanner The scanner.
     * 
     * @return An array of values.
     */
    private int[] readGrid(final Scanner scanner) {
        assert null != scanner;

        // Allocate space for the array.
        final int[] grid = new int[width * height];

        // Loop over each row and column. Each column is separated by a tab, and
        // each row is separated by a new line. Each row, check the appropriate
        // number of columns.
        int k = 0;
        for (int j = 0; j < height; j++) {

            // Read the fields.
            final String line = scanner.nextLine();
            final String[] fields = line.split("\t");

            // Check the number of columns.
            if (fields.length != width)
                throw new GraphException("Invalid graph input: '" + line + "'.");

            // Parse each field as an integer.
            for (int i = 0; i < width; i++)
                grid[k++] = Integer.parseInt(fields[i].trim());
        }

        // Return the array of values read above.
        return grid;
    }


    /**
     * Reads the obstacles from a scanner.
     * 
     * @param scanner The scanner.
     */
    private void readObstacles(final Scanner scanner) {
        assert null != scanner;
        assert null != myObstacles;
        assert myObstacles.length == width * height;

        // Loop over each row and column. Each row is a line the length of the
        // number of columns. Each field character is either an 'x' or a space. Each
        // row, check the appropriate number of columns.
        int k = 0;
        for (int j = 0; j < height; j++) {

            // Read the entire row and check the number of fields.
            final String line = scanner.nextLine();
            if (line.length() != width)
                throw new GraphException("Invalid graph input: '" + line + "'.");

            // Read each character and check for bad input.
            for (int i = 0; i < width; i++) {
                final char ch = line.charAt(i);
                if (" xX".indexOf(ch) < 0)
                    throw new GraphException("Invalid graph input: '" + line + "'.");
                myObstacles[k++] = "xX".indexOf(ch) >= 0;
            }
        }
    }


    /**
     * Reads content from the scanner after the entire graph has been read.
     * 
     * @param scanner The scanner.
     */
    private void readToEnd(final Scanner scanner) {
        assert null != scanner;

        // Loop until all lines have been read. Allow whitespace but nothing else.
        while (scanner.hasNextLine()) {
            final String line = scanner.nextLine();
            if (line.trim().length() > 0)
                throw new GraphException("Invalid graph input: '" + line + "'.");
        }
    }


    /**
     * Returns the string representation of the class data.
     * 
     * @return The string representation of the class data.
     */
    @Override
    public String toString() {
        final StringBuilder builder = new StringBuilder();
        builder.append(height + "," + width + "\n");
        buildObstacles(builder);
        builder.append(base + "\n");
        buildGrid(builder, myPriorities);
        builder.append(capacity + "\n");
        buildGrid(builder, myConsumptions);
        return builder.toString();
    }


    /**
     * Verifies the base is valid for the graph size.
     */
    private void verifyValidBase() {
        assert null != base;

        if (exceeds(base.x, 0, width - 1) || exceeds(base.y, 0, height - 1))
            throw new GraphException("Invalid graph base: '" + base + "'.");
    }
}
Valid HTML 4.01 Valid CSS