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