fi.jyu.ties454.cleaningAgents.infra.Floor.java Source code

Java tutorial

Introduction

Here is the source code for fi.jyu.ties454.cleaningAgents.infra.Floor.java

Source

/*******************************************************************************
 * Copyright 2016 Michael Cochez
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/
package fi.jyu.ties454.cleaningAgents.infra;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.stream.Collectors;

import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Table.Cell;
import com.google.common.collect.TreeBasedTable;

public class Floor {

    interface FloorUpdateListener {
        void update();
    }

    private final List<FloorUpdateListener> listeners = new LinkedList<>();

    public void addListener(FloorUpdateListener l) {
        this.listeners.add(l);
        this.fireUpdate();
    }

    private void fireUpdate() {
        this.listeners.forEach(l -> l.update());
    }

    /**
     * Internally the structure is stored as a table. The axis of this table are
     * somewhat unexpected. The rows are X and the columns are Y!, This seems
     * opposite from what is used otherwise.
     */
    private final TreeBasedTable<Integer, Integer, FloorState> map;

    private Floor() {
        this(TreeBasedTable.create());
    }

    private Floor(TreeBasedTable<Integer, Integer, FloorState> map) {
        this.map = map;
    }

    /**
     * Copies the current map (but not any of the listeners attached to it)
     * 
     * @return A new map
     */
    public synchronized Floor copyMap() {
        TreeBasedTable<Integer, Integer, FloorState> mapCopy = TreeBasedTable.create(this.map);
        return new Floor(mapCopy);
    }

    /**
     * Get a random location.
     *
     * @param r
     * @return
     */
    Location getRandomLocation(Random r) {
        Cell<Integer, Integer, FloorState> cell = Iterables.get(this.map.cellSet(),
                r.nextInt((this.map.cellSet().size())));
        Location l = new Location(cell.getColumnKey(), cell.getRowKey());
        if (!this.isValidLocation(l)) {
            throw new Error("Randomly chose location must be valid");
        }
        return l;
    }

    public boolean isValidLocation(Location potentialNewLocation) {
        return this.map.contains(potentialNewLocation.Y, potentialNewLocation.X);
    }

    synchronized public void clean(Location l) {
        Preconditions.checkArgument(this.isValidLocation(l));
        if (this.state(l) != FloorState.CLEAN) {
            this.map.put(l.Y, l.X, FloorState.CLEAN);
            this.fireUpdate();
        }
    }

    synchronized public void soil(Location l) {
        Preconditions.checkArgument(this.isValidLocation(l));
        if (this.state(l) != FloorState.DIRTY) {
            this.map.put(l.Y, l.X, FloorState.DIRTY);
            this.fireUpdate();
        }
    }

    synchronized public FloorState state(Location l) {
        Preconditions.checkArgument(this.isValidLocation(l));
        return this.map.get(l.Y, l.X);
    }

    synchronized public boolean isDirty(Location l) {
        Preconditions.checkArgument(this.isValidLocation(l));
        return this.state(l) == FloorState.DIRTY;
    }

    /**
     * What fraction of the floor surface is dirty?
     */
    public synchronized double dirtyFraction() {
        double dirty = (double) this.map.values().stream().filter(c -> c == FloorState.DIRTY)
                .collect(Collectors.counting());
        double size = this.map.size();
        return dirty / size;
    }

    public static Floor createSimple() {
        Floor e = new Floor();
        for (int y = 0; y < 30; y++) {
            for (int x = 0; x < 20; x++) {
                e.map.put(y, x, FloorState.CLEAN);
            }
        }
        return e;
    }

    private static final char CLEANCHAR = 'C';
    private static final char DIRTYCHAR = '#';
    private static final char VOIDCHAR = ' ';

    synchronized void writeToWriter(Writer r) throws IOException {
        int y = 0;
        // traversal is in order (treebased)
        for (Entry<Integer, Map<Integer, FloorState>> row : this.map.rowMap().entrySet()) {
            while (y != row.getKey()) {
                r.write('\n');
                y++;
            }
            int x = 0;
            for (Entry<Integer, FloorState> column : row.getValue().entrySet()) {
                while (column.getKey() != x) {
                    r.write(Floor.VOIDCHAR);
                    x++;
                }
                if (column.getValue() == FloorState.CLEAN) {
                    r.write(Floor.CLEANCHAR);
                } else if (column.getValue() == FloorState.DIRTY) {
                    r.write(Floor.DIRTYCHAR);
                } else {
                    throw new Error();
                }
                x++;
            }
        }
        r.write('\n');
    }

    public void writeToFile(File descriptor) throws FileNotFoundException, IOException {
        try (BufferedWriter r = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(descriptor), StandardCharsets.UTF_8))) {
            this.writeToWriter(r);
        }
    }

    public static Floor readFromReader(Reader r0) throws IOException {
        Floor e = new Floor();
        String line;
        int y = 0;
        try (BufferedReader r = new BufferedReader(r0)) {
            while ((line = r.readLine()) != null) {
                int x = 0;
                // ASCII encoding, so bytes can be used
                for (byte b : line.getBytes()) {
                    switch (b) {
                    case VOIDCHAR:
                        // void
                        break;
                    case CLEANCHAR:
                        // clean
                        e.map.put(y, x, FloorState.CLEAN);
                        break;
                    case DIRTYCHAR:
                        // dirty
                        e.map.put(y, x, FloorState.DIRTY);
                        break;
                    default:
                        throw new Error("Unknown character in map " + (char) b);
                    }
                    x++;
                }
                y++;
            }
        }
        return e;
    }

    public static Floor readFromFile(File descriptor) throws FileNotFoundException, IOException {
        try (BufferedReader r = new BufferedReader(
                new InputStreamReader(new FileInputStream(descriptor), StandardCharsets.UTF_8))) {
            return Floor.readFromReader(r);
        }
    }

    @Override
    public synchronized String toString() {
        StringWriter w = new StringWriter();
        try {
            this.writeToWriter(w);
        } catch (IOException e) {
            throw new Error("Writing to stringwriter should never throw IOExceptions", e);
        }
        return w.toString();
    }

    public synchronized List<String> writeToStringList() {
        List<String> l = new ArrayList<>();
        int y = 0;
        // traversal is in order (treebased)
        for (Entry<Integer, Map<Integer, FloorState>> row : this.map.rowMap().entrySet()) {
            while (y != row.getKey()) {
                l.add("");
                y++;
            }
            StringBuilder line = new StringBuilder();
            int x = 0;
            for (Entry<Integer, FloorState> column : row.getValue().entrySet()) {
                while (column.getKey() != x) {
                    line.append(Floor.VOIDCHAR);
                    x++;
                }
                if (column.getValue() == FloorState.CLEAN) {
                    line.append(Floor.CLEANCHAR);
                } else if (column.getValue() == FloorState.DIRTY) {
                    line.append(Floor.DIRTYCHAR);
                } else {
                    throw new Error();
                }
                x++;
            }
            l.add(line.toString());
            y++;
        }
        return l;
    }

}