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 + "'."); } }