c =
+ DTN2Manager.getHosts(bundle.destination_EID);
+ if (c==null || c.isEmpty()) {
+ Debug.p( "Couldn't find destination matching '" +
+ bundle.destination_EID+"'");
+ r.reply = false;
+ r.bytes_sent = 0;
+ return r;
+ }
+
+ // Create a message for each matched recipient
+ // XXX: Ideally we'd only have one message,
+ // but ONE requires each message to have exactly one recipient
+ for (DTN2Manager.EIDHost e : c) {
+ // Create a new message in the queue
+ this.events.enqueMsg(this.host_id, e.host_id, bundle);
+ }
+
+ // Pretend we've transmitted the whole bundle
+ r.reply = true;
+ r.bytes_sent = bundle.file.length();
+
+ return r;
+ }
+
+ public void connected() {
+ /* The ECLA has been connected, we can now set it up through
+ the console */
+ this.console = new DTNConsoleConnection(this.c_host, this.c_port);
+ Thread t = new Thread(this.console);
+ t.start();
+ this.console.queue("link add one dtn:one ALWAYSON extcl " +
+ "protocol=ONE\n");
+ this.console.queue("route add \"dtn://*\" one\n");
+ }
+
+ public boolean error(String reason, Exception exception,
+ boolean fatal) {
+ return false;
+ }
+
+ public boolean parseError(String reason) {
+ return false;
+ }
+ //********************************************************************//
+ }
+ //************************************************************************//
+
+
+
+ //************************************************************************//
+ // EventQueue Implementation //
+ //************************************************************************//
+ public ExternalEvent nextEvent() {
+ if (!this.events.isEmpty()) {
+ return this.events.remove();
+ } else
+ return new ExternalEvent(Double.MAX_VALUE);
+ }
+
+ public double nextEventsTime() {
+ if (!this.events.isEmpty())
+ return SimClock.getTime();
+ else
+ return Double.MAX_VALUE;
+ }
+ //************************************************************************//
+
+
+ //************************************************************************//
+ // Public Methods //
+ //************************************************************************//
+
+ /**
+ * Creates a parser handler for the given host.
+ * @param hostID ID of the host that this parser corresponds to
+ * @param consoleHost Hostname of the dtnd
+ * @param consolePort Console port of the dtnd
+ */
+ public DTN2Events.ParserHandler getParserHandler(int hostID,
+ String consoleHost, int consolePort) {
+ return new ParserHandler(hostID, this, consoleHost, consolePort);
+ }
+ //************************************************************************//
+
+
+ //************************************************************************//
+ // Private Methods //
+ //************************************************************************//
+ private void enqueMsg(int from, int to, Bundle bundle) {
+ String id;
+ id = "bundle."+from+"-"+to+"-"+bundle.creation_timestamp_time+
+ "-"+bundle.creation_timestamp_seq_no;
+ MessageCreateEvent e = new MessageCreateEvent(from, to, id,
+ (int)(bundle.file.length()), 0, SimClock.getTime());
+ synchronized (this.events) {
+ this.events.add(e);
+ }
+ DTN2Manager.addBundle(id,bundle);
+ }
+
+ // Keep track of the bundles we've received
+ private void regMsg(Bundle bundle) {
+ String key = bundle.source_EID+":"+bundle.destination_EID+":"+
+ bundle.creation_timestamp_time+":"+bundle.creation_timestamp_seq_no;
+ if (!this.bundle_list.containsKey(key))
+ this.bundle_list.put(key,null);
+ }
+
+ // Check if the bundle has been received before
+ private boolean isReg(Bundle bundle) {
+ String key = bundle.source_EID+":"+bundle.destination_EID+":"+
+ bundle.creation_timestamp_time+":"+bundle.creation_timestamp_seq_no;
+ return this.bundle_list.containsKey(key);
+ }
+ //************************************************************************//
+
+}
diff --git a/input/EventQueue.java b/input/EventQueue.java
new file mode 100644
index 000000000..df0c67bd9
--- /dev/null
+++ b/input/EventQueue.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+/**
+ * Interface for event queues. Any class that is not a movement model or a
+ * routing module but wishes to provide events for the simulation (like creating
+ * messages) must implement this interface and register itself to the
+ * simulator. See the {@link EventQueueHandler} class for configuration
+ * instructions.
+ */
+public interface EventQueue {
+
+ /**
+ * Returns the next event in the queue or ExternalEvent with time of
+ * double.MAX_VALUE if there are no events left.
+ * @return The next event
+ */
+ public ExternalEvent nextEvent();
+
+ /**
+ * Returns next event's time or Double.MAX_VALUE if there are no
+ * events left in the queue.
+ * @return Next event's time
+ */
+ public double nextEventsTime();
+
+}
diff --git a/input/EventQueueHandler.java b/input/EventQueueHandler.java
new file mode 100644
index 000000000..90fa5f4d7
--- /dev/null
+++ b/input/EventQueueHandler.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import core.Settings;
+
+/**
+ *
+ * Handler for managing event queues. Supports two different type of event
+ * queues: external event queues and event generator classes.
+ * For external event queues, the events are defined in external data
+ * file(s) (see e.g. input.StandarEventsReader). Event generator classes
+ * define events dynamically. Both type of event queues must implement
+ * the input.EventQueue interface.
+ *
+ * The total number of event queues to load is defined with variable
+ * NROF_SETTING
, e.g.
+ * Events.nrof = 3
+ * Separate event queues are configured with syntax
+ * EventsN.variable = value
e.g.:
+ * Events1.filePath = ee/messages.txt
+ * or
+ * Events2.class = RandomMessageGenerator
+ *
+ * External event files are used when the variable PATH_SETTING
+ * is used to define the path to the event file and event generator class
+ * is loaded when the name of the class is defined with
+ * CLASS_SETTING
.
+ */
+public class EventQueueHandler {
+ /** Event queue settings main namespace ({@value})*/
+ public static final String SETTINGS_NAMESPACE = "Events";
+ /** number of event queues -setting id ({@value})*/
+ public static final String NROF_SETTING = "nrof";
+
+ /** name of the events class (for class based events) -setting id
+ * ({@value}) */
+ public static final String CLASS_SETTING = "class";
+ /** name of the package where event generator classes are looked from */
+ public static final String CLASS_PACKAGE = "input";
+
+ /** number of events to preload from file -setting id ({@value})*/
+ public static final String PRELOAD_SETTING = "nrofPreload";
+ /** path of external events file -setting id ({@value})*/
+ public static final String PATH_SETTING = "filePath";
+
+ private List queues;
+
+ /**
+ * Creates a new EventQueueHandler which can be queried for
+ * event queues.
+ */
+ public EventQueueHandler() {
+ Settings settings = new Settings(SETTINGS_NAMESPACE);
+ int nrof = settings.getInt(NROF_SETTING);
+ this.queues = new ArrayList();
+
+ for (int i=1; i <= nrof; i++) {
+ Settings s = new Settings(SETTINGS_NAMESPACE + i);
+
+ if (s.contains(PATH_SETTING)) { // external events file
+ int preload = 0;
+ String path = "";
+ if (s.contains(PRELOAD_SETTING)) {
+ preload = s.getInt(PRELOAD_SETTING);
+ }
+ path = s.getSetting(PATH_SETTING);
+
+ queues.add(new ExternalEventsQueue(path, preload));
+ }
+ else if (s.contains(CLASS_SETTING)) { // event generator class
+ String className = CLASS_PACKAGE + "." +
+ s.getSetting(CLASS_SETTING);
+ EventQueue eq = (EventQueue)s.createIntializedObject(className);
+
+ queues.add(eq);
+ }
+ }
+ }
+
+ /**
+ * Returns all the loaded event queues
+ * @return all the loaded event queues
+ */
+ public List getEventQueues() {
+ return this.queues;
+ }
+
+}
diff --git a/input/ExternalEvent.java b/input/ExternalEvent.java
new file mode 100644
index 000000000..9a81fc86c
--- /dev/null
+++ b/input/ExternalEvent.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import java.io.Serializable;
+
+import core.World;
+
+/**
+ * Super class for all external events. All new classes of external events
+ * must extend this class. This can also be used as a dummy event if only
+ * an update request (and no further actions) to all hosts is needed.
+ */
+public class ExternalEvent implements Comparable, Serializable {
+ /** Time of the event (simulated seconds) */
+ protected double time;
+
+ public ExternalEvent(double time) {
+ this.time = time;
+ }
+
+ /**
+ * Processes the external event.
+ * @param world World where the actors of the event are
+ */
+ public void processEvent(World world) {
+ // this is just a dummy event
+ }
+
+ /**
+ * Returns the time when this event should happen.
+ * @return Event's time
+ */
+ public double getTime() {
+ return this.time;
+ }
+
+ /**
+ * Compares two external events by their time.
+ * @return -1, zero, 1 if this event happens before, at the same time,
+ * or after the other event
+ * @param other The other external event
+ */
+ public int compareTo(ExternalEvent other) {
+ if (this.time == other.time) {
+ return 0;
+ }
+ else if (this.time < other.time) {
+ return -1;
+ }
+ else {
+ return 1;
+ }
+ }
+
+ /**
+ * Returns a String representation of the event
+ * @return a String representation of the event
+ */
+ public String toString() {
+ return "ExtEvent @ " + this.time;
+ }
+
+}
diff --git a/input/ExternalEventsQueue.java b/input/ExternalEventsQueue.java
new file mode 100644
index 000000000..108540a20
--- /dev/null
+++ b/input/ExternalEventsQueue.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import core.Settings;
+
+/**
+ * Queue of external events. This class also takes care of buffering
+ * the events and preloading only a proper amount of them.
+ */
+public class ExternalEventsQueue implements EventQueue {
+ /** ExternalEvents namespace ({@value})*/
+ public static final String SETTINGS_NAMESPACE = "ExternalEvents";
+ /** number of event to preload -setting id ({@value})*/
+ public static final String PRELOAD_SETTING = "nrofPreload";
+ /** path of external events file -setting id ({@value})*/
+ public static final String PATH_SETTING = "filePath";
+
+ /** default number of preloaded events */
+ public static final int DEFAULT_NROF_PRELOAD = 500;
+
+ private File eventsFile;
+ private ExternalEventsReader reader;
+ private int nextEventIndex;
+ private int nrofPreload;
+ private List queue;
+ private boolean allEventsRead = false;
+
+ /**
+ * Creates a new Queue from a file
+ * @param filePath Path to the file where the events are read from. If
+ * file ends with extension defined in {@link BinaryEventsReader#BINARY_EXT}
+ * the file is assumed to be a binary file.
+ * @param nrofPreload How many events to preload
+ * @see BinaryEventsReader#BINARY_EXT
+ * @see BinaryEventsReader#storeToBinaryFile(String, List)
+ */
+ public ExternalEventsQueue(String filePath, int nrofPreload) {
+ setNrofPreload(nrofPreload);
+ init(filePath);
+ }
+
+ /**
+ * Create a new Queue based on the given settings: {@link #PRELOAD_SETTING}
+ * and {@link #PATH_SETTING}. The path setting supports value filling.
+ * @param s The settings
+ */
+ public ExternalEventsQueue(Settings s) {
+ if (s.contains(PRELOAD_SETTING)) {
+ setNrofPreload(s.getInt(PRELOAD_SETTING));
+ }
+ else {
+ setNrofPreload(DEFAULT_NROF_PRELOAD);
+ }
+ String eeFilePath = s.valueFillString(s.getSetting(PATH_SETTING));
+ init(eeFilePath);
+ }
+
+ /**
+ * Sets maximum number of events that are read when the next preload occurs
+ * @param nrof Maximum number of events to read. If less than 1, default
+ * value ( {@value DEFAULT_NROF_PRELOAD} ) is used.
+ */
+ public void setNrofPreload(int nrof) {
+ if (nrof < 1) {
+ nrof = DEFAULT_NROF_PRELOAD;
+ }
+ this.nrofPreload = nrof;
+ }
+
+ private void init(String eeFilePath) {
+ this.eventsFile = new File(eeFilePath);
+
+ if (BinaryEventsReader.isBinaryEeFile(eventsFile)) {
+ this.reader = new BinaryEventsReader(eventsFile);
+ }
+ else {
+ this.reader = new StandardEventsReader(eventsFile);
+ }
+
+ this.queue = readEvents(nrofPreload);
+ this.nextEventIndex = 0;
+ }
+
+ /**
+ * Returns next event's time or Double.MAX_VALUE if there are no
+ * events left
+ * @return Next event's time
+ */
+ public double nextEventsTime() {
+ if (eventsLeftInBuffer() <= 0 ) {
+ // in case user request time of an event that doesn't exist
+ return Double.MAX_VALUE;
+ }
+ else {
+ return queue.get(nextEventIndex).getTime();
+ }
+ }
+
+ /**
+ * Returns the next event in the queue or ExternalEvent with time of
+ * double.MAX_VALUE if there are no events left
+ * @return The next event
+ */
+ public ExternalEvent nextEvent() {
+ if (queue.size() == 0) { // no more events
+ return new ExternalEvent(Double.MAX_VALUE);
+ }
+
+ ExternalEvent ee = queue.get(nextEventIndex);
+ nextEventIndex++;
+
+ if (nextEventIndex >= queue.size()) { // ran out of events
+ queue = readEvents(nrofPreload);
+ nextEventIndex = 0;
+ }
+
+ return ee;
+ }
+
+ /**
+ * Returns the amount of events left in the buffer at the moment
+ * (the amount can increase later if more events are read).
+ * @return The amount of events left or 0 there aren't any events
+ */
+ public int eventsLeftInBuffer() {
+ if (queue == null || queue.size() == 0) {
+ return 0;
+ }
+ else {
+ return this.queue.size() - this.nextEventIndex;
+ }
+ }
+
+
+ /**
+ * Read some events from the external events reader
+ * @param nrof Maximum number of events to read
+ * @return A List of events that were read or an empty list if no events
+ * could be read
+ */
+ private List readEvents(int nrof) {
+ if (allEventsRead) {
+ return new ArrayList(0);
+ }
+
+ List events = reader.readEvents(nrof);
+
+ if (nrof > 0 && events.size() == 0) {
+ reader.close();
+ allEventsRead = true;
+ }
+
+ return events;
+ }
+
+}
diff --git a/input/ExternalEventsReader.java b/input/ExternalEventsReader.java
new file mode 100644
index 000000000..01fc065df
--- /dev/null
+++ b/input/ExternalEventsReader.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import java.util.List;
+
+/**
+ * Interface for external event readers.
+ */
+public interface ExternalEventsReader {
+
+ /**
+ * Read events from the reader
+ * @param nrof Maximum number of events to read
+ * @return Events in a List
+ */
+ public List readEvents(int nrof);
+
+ /**
+ * Closes the input file streams of the reader.
+ */
+ public void close();
+
+
+}
diff --git a/input/ExternalMovementReader.java b/input/ExternalMovementReader.java
new file mode 100644
index 000000000..1ef7a480f
--- /dev/null
+++ b/input/ExternalMovementReader.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+import util.Tuple;
+
+import core.Coord;
+import core.SettingsError;
+
+
+/**
+ * Reader for ExternalMovement movement model's time-location tuples.
+ *
+ * First line of the file should be the offset header. Syntax of the header
+ * should be:
+ * minTime maxTime minX maxX minY maxY minZ maxZ
+ *
+ * Last two values (Z-axis) are ignored at the moment but can be present
+ * in the file.
+ *
+ * Following lines' syntax should be:
+ * time id xPos yPos
+ * where time
is the time when a node with id
should
+ * be at location (xPos, yPos)
.
+ *
+ *
+ * All lines must be sorted by time. Sampling interval (time difference between
+ * two time instances) must be same for the whole file.
+ *
+ */
+public class ExternalMovementReader {
+ /* Prefix for comment lines (lines starting with this are ignored) */
+ public static final String COMMENT_PREFIX = "#";
+ private Scanner scanner;
+ private double lastTimeStamp = -1;
+ private String lastLine;
+ private double minTime;
+ private double maxTime;
+ private double minX;
+ private double maxX;
+ private double minY;
+ private double maxY;
+ private boolean normalize;
+
+
+ /**
+ * Constructor. Creates a new reader that reads the data from a file.
+ * @param inFilePath Path to the file where the data is read
+ * @throws SettingsError if the file wasn't found
+ */
+ public ExternalMovementReader(String inFilePath) {
+ this.normalize = true;
+ File inFile = new File(inFilePath);
+ try {
+ scanner = new Scanner(inFile);
+ } catch (FileNotFoundException e) {
+ throw new SettingsError("Couldn't find external movement input " +
+ "file " + inFile);
+ }
+
+ String offsets = scanner.nextLine();
+
+ try {
+ Scanner lineScan = new Scanner(offsets);
+ minTime = lineScan.nextDouble();
+ maxTime = lineScan.nextDouble();
+ minX = lineScan.nextDouble();
+ maxX = lineScan.nextDouble();
+ minY = lineScan.nextDouble();
+ maxY = lineScan.nextDouble();
+ } catch (Exception e) {
+ throw new SettingsError("Invalid offset line '" + offsets + "'");
+ }
+
+ lastLine = scanner.nextLine();
+ }
+
+ /**
+ * Sets normalizing of read values on/off. If on, values returned by
+ * {@link #readNextMovements()} are decremented by minimum values of the
+ * offsets. Default is on (normalize).
+ * @param normalize If true, normalizing is on (false -> off).
+ */
+ public void setNormalize(boolean normalize) {
+ this.normalize = normalize;
+ }
+
+ /**
+ * Reads all new id-coordinate tuples that belong to the same time instance
+ * @return A list of tuples or empty list if there were no more moves
+ * @throws SettingError if an invalid line was read
+ */
+ public List> readNextMovements() {
+ ArrayList> moves =
+ new ArrayList>();
+
+ if (!scanner.hasNextLine()) {
+ return moves;
+ }
+
+ Scanner lineScan = new Scanner(lastLine);
+ double time = lineScan.nextDouble();
+ String id = lineScan.next();
+ double x = lineScan.nextDouble();
+ double y = lineScan.nextDouble();
+
+ if (normalize) {
+ time -= minTime;
+ x -= minX;
+ y -= minY;
+ }
+
+ lastTimeStamp = time;
+
+ while (scanner.hasNextLine() && lastTimeStamp == time) {
+ lastLine = scanner.nextLine();
+
+ if (lastLine.trim().length() == 0 ||
+ lastLine.startsWith(COMMENT_PREFIX)) {
+ continue; /* skip empty and comment lines */
+ }
+
+ // add previous line's tuple
+ moves.add(new Tuple(id, new Coord(x,y)));
+
+ lineScan = new Scanner(lastLine);
+
+ try {
+ time = lineScan.nextDouble();
+ id = lineScan.next();
+ x = lineScan.nextDouble();
+ y = lineScan.nextDouble();
+ } catch (Exception e) {
+ throw new SettingsError("Invalid line '" + lastLine + "'");
+ }
+
+ if (normalize) {
+ time -= minTime;
+ x -= minX;
+ y -= minY;
+ }
+ }
+
+ if (!scanner.hasNextLine()) { // add the last tuple of the file
+ moves.add(new Tuple(id, new Coord(x,y)));
+ }
+
+ return moves;
+ }
+
+ /**
+ * Returns the time stamp where the last moves read with
+ * {@link #readNextMovements()} belong to.
+ * @return The time stamp
+ */
+ public double getLastTimeStamp() {
+ return lastTimeStamp;
+ }
+
+ /**
+ * Returns offset maxTime
+ * @return the maxTime
+ */
+ public double getMaxTime() {
+ return maxTime;
+ }
+
+ /**
+ * Returns offset maxX
+ * @return the maxX
+ */
+ public double getMaxX() {
+ return maxX;
+ }
+
+ /**
+ * Returns offset maxY
+ * @return the maxY
+ */
+ public double getMaxY() {
+ return maxY;
+ }
+
+ /**
+ * Returns offset minTime
+ * @return the minTime
+ */
+ public double getMinTime() {
+ return minTime;
+ }
+
+ /**
+ * Returns offset minX
+ * @return the minX
+ */
+ public double getMinX() {
+ return minX;
+ }
+
+ /**
+ * Returns offset minY
+ * @return the minY
+ */
+ public double getMinY() {
+ return minY;
+ }
+
+}
diff --git a/input/ExternalPathMovementReader.java b/input/ExternalPathMovementReader.java
new file mode 100644
index 000000000..b49d95628
--- /dev/null
+++ b/input/ExternalPathMovementReader.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+
+package input;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import core.SettingsError;
+
+
+/**
+ * External movement reader for traces that are in path format. Uses two
+ * trace files, one for the paths and one for specifying activity times.
+ * Nodes will follow the paths in the trace file, and pause between paths.
+ * Activity times refer to the periods of time when there is valid trace data
+ * about the node.
+ *
+ * Reads external traces that are of the form:
+ * id time_1,x_1,y_1 time_2,x_2,y_2 ... \n
+ *
+ * The first line should be:>
+ * maxID minTime maxTime minX maxX minY maxY
+ *
+ *
Activity trace file format is:
+ * id activeStart activeEnd\n
+ *
+ *
+ * The ID in the trace files must match IDs of nodes in the simulation, the
+ * coordinates must match the ONE coordinate system (units in meters) and the
+ * times must match the ONE simulation time.
+ *
+ *
+ * Trace and activity files ending in .zip are assumed to be
+ * compressed and will be automatically uncompressed during reading. The whole
+ * trace is loaded into memory at once.
+ *
+ * @author teemuk
+ *
+ */
+public class ExternalPathMovementReader {
+ // Singletons are evil, but I'm lazy
+ private static Map singletons =
+ new HashMap();
+
+ /**
+ * Represents a point on the path.
+ */
+ public class Entry {
+ public double time;
+ public double x;
+ public double y;
+ }
+
+ /**
+ * Describes a node's activity time
+ */
+ public class ActiveTime {
+ public double start;
+ public double end;
+ }
+
+ // Path cache
+ private List>> paths = null;
+ // Activity cache
+ private List> activeTimes = null;
+
+ // Settings
+ private boolean normalize = true;
+ private double minTime;
+ private double maxTime;
+ private double minX;
+ private double maxX;
+ private double minY;
+ private double maxY;
+ private int maxID;
+
+ /**
+ * Creates a new reader by parsing the given files and building the internal
+ * caches.
+ *
+ * @param traceFilePath path to the trace file
+ * @param activityFilePath path to the activity file
+ */
+ private ExternalPathMovementReader(String traceFilePath,
+ String activityFilePath) throws IOException {
+ // Open the trace file for reading
+ File inFile = new File(traceFilePath);
+ long traceSize = inFile.length();
+ long totalRead = 0;
+ long readSize = 0;
+ long printSize = 5*1024*1024;
+
+ BufferedReader reader = null;
+ try {
+ if (traceFilePath.endsWith(".zip")) {
+ // Grab the first entry from the zip file
+ // TODO: try to find the correct entry based on file name
+ ZipFile zf = new ZipFile(traceFilePath);
+ ZipEntry ze = zf.entries().nextElement();
+ reader = new BufferedReader(
+ new InputStreamReader(zf.getInputStream(ze)));
+ traceSize = ze.getSize();
+ } else {
+ reader = new BufferedReader(
+ new FileReader(traceFilePath));
+ }
+ } catch (FileNotFoundException e1) {
+ throw new SettingsError("Couldn't find external movement input " +
+ "file " + inFile);
+ }
+
+ /*Scanner scanner = null;
+ try {
+ scanner = new Scanner(inFile);
+ } catch (FileNotFoundException e) {
+ throw new SettingsError("Couldn't find external movement input " +
+ "file " + inFile);
+ }*/
+
+ // Parse header
+ String offsets = reader.readLine();
+ if (offsets == null) {
+ throw new SettingsError("No offset line found.");
+ }
+ readSize += offsets.length() + 1;
+ try {
+ Scanner lineScan = new Scanner(offsets);
+ this.maxID = lineScan.nextInt();
+ this.minTime = lineScan.nextDouble();
+ this.maxTime = lineScan.nextDouble();
+ this.minX = lineScan.nextDouble();
+ this.maxX = lineScan.nextDouble();
+ this.minY = lineScan.nextDouble();
+ this.maxY = lineScan.nextDouble();
+ } catch (Exception e) {
+ throw new SettingsError("Invalid offset line '" + offsets + "'");
+ }
+
+ // Initialize path cache
+ this.paths = new ArrayList>>(this.maxID + 1);
+ for (int i=0; i<=this.maxID; i++) {
+ this.paths.add(i, new LinkedList>());
+ }
+
+ // Parse traces
+ String line = reader.readLine();
+ while (line != null) {
+
+ readSize += line.length() + 1;
+ if (readSize >= printSize) {
+ totalRead += readSize;
+ readSize = 0;
+ System.out.println("Processed " + (totalRead/1024) + "KB out" +
+ " of " + (traceSize/1024) + "KB (" +
+ Math.round(100.0*totalRead/traceSize) + "%)");
+ }
+
+ if (line.equals("")) {
+ line = reader.readLine();
+ continue; // Skip empty lines
+ }
+ Scanner traceScan = new Scanner(line);
+ int id = traceScan.nextInt();
+ List> paths = this.paths.get(id);
+ List path = new LinkedList();
+ while (traceScan.hasNext()) {
+ String dataPoint = traceScan.next();
+ int d1 = dataPoint.indexOf(',');
+ int d2 = dataPoint.indexOf(',', d1+1);
+
+ Entry e = new Entry();
+ e.time = Double.parseDouble(dataPoint.substring(0, d1));
+ e.x = Double.parseDouble(dataPoint.substring(d1+1, d2));
+ e.y = Double.parseDouble(dataPoint.substring(d2+1));
+
+ if (this.normalize) {
+ e.time -= this.minTime;
+ e.x -= this.minX;
+ e.y -= this.minY;
+ }
+
+ path.add(e);
+ }
+ paths.add(path);
+
+ line = reader.readLine();
+ }
+
+ // Parse activity times
+ inFile = new File(activityFilePath);
+ reader = null;
+ try {
+ if (activityFilePath.endsWith(".zip")) {
+ // Grab the first entry from the zip file
+ // TODO: try to find the correct entry based on file name
+ ZipFile zf = new ZipFile(activityFilePath);
+ ZipEntry ze = zf.entries().nextElement();
+ reader = new BufferedReader(
+ new InputStreamReader(zf.getInputStream(ze)));
+ } else {
+ reader = new BufferedReader(
+ new FileReader(activityFilePath));
+ }
+ } catch (FileNotFoundException e) {
+ throw new SettingsError("Couldn't find external activity input " +
+ "file " + inFile);
+ }
+
+ // Init activity cache
+ this.activeTimes = new ArrayList>(this.maxID + 1);
+ for (int i=0; i<=this.maxID; i++) {
+ this.activeTimes.add(new LinkedList());
+ }
+
+ // Parse the file
+ line = reader.readLine();
+ while (line != null) {
+ Scanner traceScan = new Scanner(line);
+ int id = traceScan.nextInt();
+ double start = traceScan.nextDouble();
+ double end = traceScan.nextDouble();
+ List times = this.activeTimes.get(id);
+ ActiveTime a = new ActiveTime();
+ a.start = start;
+ a.end = end;
+ if (this.normalize) {
+ a.start -= this.minTime;
+ a.end -= this.minTime;
+ }
+ times.add(a);
+
+ line = reader.readLine();
+ }
+ }
+
+ /**
+ * Returns the path for the node with the given ID.
+ *
+ * @param ID ID of the node
+ * @return full path for the node.
+ */
+ public List> getPaths(int ID) {
+ return this.paths.get(ID);
+ }
+
+ /**
+ * Returns the active time for the given ID.
+ *
+ * @param ID ID of the node
+ * @return active times for the node.
+ */
+ public List getActive(int ID) {
+ return this.activeTimes.get(ID);
+ }
+
+ /**
+ * Sets normalizing of read values on/off. If on, values returned by
+ * {@link #readNextMovements()} are decremented by minimum values of the
+ * offsets. Default is on (normalize).
+ * @param normalize If true, normalizing is on (false -> off).
+ */
+ public void setNormalize(boolean normalize) {
+ this.normalize = normalize;
+ }
+
+
+ /**
+ * Returns offset maxTime
+ * @return the maxTime
+ */
+ public double getMaxTime() {
+ return maxTime;
+ }
+
+ /**
+ * Returns offset maxX
+ * @return the maxX
+ */
+ public double getMaxX() {
+ return maxX;
+ }
+
+ /**
+ * Returns offset maxY
+ * @return the maxY
+ */
+ public double getMaxY() {
+ return maxY;
+ }
+
+ /**
+ * Returns offset minTime
+ * @return the minTime
+ */
+ public double getMinTime() {
+ return minTime;
+ }
+
+ /**
+ * Returns offset minX
+ * @return the minX
+ */
+ public double getMinX() {
+ return minX;
+ }
+
+ /**
+ * Returns offset minY
+ * @return the minY
+ */
+ public double getMinY() {
+ return minY;
+ }
+
+
+ /**
+ * Get an instance of the reader for the given file path. If the file has
+ * already been read previously it will not be read again and instead the
+ * previous instance of the reader will be returned.
+ *
+ * @param filePath path where the file is read from
+ * @return instance of the reader that has loaded all the paths from the
+ * given trace file.
+ */
+ public static ExternalPathMovementReader getInstance(String traceFilePath,
+ String activeFilePath) {
+ if (!ExternalPathMovementReader.singletons.containsKey(traceFilePath)) {
+ try {
+ ExternalPathMovementReader.singletons.put(traceFilePath,
+ new ExternalPathMovementReader(traceFilePath,
+ activeFilePath));
+ } catch (IOException e) {
+ System.exit(1);
+ }
+ }
+ return ExternalPathMovementReader.singletons.get(traceFilePath);
+ }
+}
diff --git a/input/MessageBurstGenerator.java b/input/MessageBurstGenerator.java
new file mode 100644
index 000000000..1dcb0b11e
--- /dev/null
+++ b/input/MessageBurstGenerator.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import core.Settings;
+
+/**
+ * Message creation -external events generator. Creates bursts of messages where
+ * every source node (defined with {@link MessageEventGenerator#HOST_RANGE_S})
+ * creates a new message to every destination node (defined with
+ * {@link MessageEventGenerator#TO_HOST_RANGE_S})on every interval.
+ * The message size, burst times, and inter-burst intervals can be configured
+ * like with {@link MessageEventGenerator}.
+ * @see MessageEventGenerator
+ */
+public class MessageBurstGenerator extends MessageEventGenerator {
+ /** next index to use from the "from" range */
+ private int nextFromOffset;
+ private int nextToOffset;
+
+ public MessageBurstGenerator(Settings s) {
+ super(s);
+ this.nextFromOffset = 0;
+ this.nextToOffset = 0;
+
+ if (this.toHostRange == null) {
+ this.toHostRange = this.hostRange;
+ }
+ }
+
+ /**
+ * Returns the next message creation event
+ * @see input.EventQueue#nextEvent()
+ */
+ public ExternalEvent nextEvent() {
+ int responseSize = 0; /* no responses requested */
+ int msgSize;
+ int interval;
+ int from;
+ int to;
+ boolean nextBurst = false;
+
+ from = this.hostRange[0] + nextFromOffset;
+ to = this.toHostRange[0] + nextToOffset;
+
+ if (to == from) { /* skip self */
+ to = this.toHostRange[0] + (++nextToOffset);
+ }
+
+ msgSize = drawMessageSize();
+ MessageCreateEvent mce = new MessageCreateEvent(from, to, getID(),
+ msgSize, responseSize, this.nextEventsTime);
+
+ if (to < this.toHostRange[1] - 1) {
+ this.nextToOffset++;
+ } else {
+ if (from < this.hostRange[1] - 1) {
+ this.nextFromOffset++;
+ this.nextToOffset = 0;
+ } else {
+ nextBurst = true;
+ }
+ }
+
+ if (this.hostRange[0] + nextFromOffset ==
+ this.toHostRange[0] + nextToOffset) {
+ /* to and from would be same for next event */
+ nextToOffset++;
+ if (nextToOffset >= toHostRange[1]) {
+ /* TODO: doesn't work correctly with non-aligned ranges */
+ nextBurst = true;
+ }
+ }
+
+ if (nextBurst) {
+ interval = drawNextEventTimeDiff();
+ this.nextEventsTime += interval;
+ this.nextFromOffset = 0;
+ this.nextToOffset = 0;
+ }
+
+ if (this.msgTime != null && this.nextEventsTime > this.msgTime[1]) {
+ /* next event would be later than the end time */
+ this.nextEventsTime = Double.MAX_VALUE;
+ }
+
+ return mce;
+ }
+
+}
\ No newline at end of file
diff --git a/input/MessageCreateEvent.java b/input/MessageCreateEvent.java
new file mode 100644
index 000000000..27d8f0236
--- /dev/null
+++ b/input/MessageCreateEvent.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import core.DTNHost;
+import core.Message;
+import core.World;
+
+/**
+ * External event for creating a message.
+ */
+public class MessageCreateEvent extends MessageEvent {
+ private int size;
+ private int responseSize;
+
+ /**
+ * Creates a message creation event with a optional response request
+ * @param from The creator of the message
+ * @param to Where the message is destined to
+ * @param id ID of the message
+ * @param size Size of the message
+ * @param responseSize Size of the requested response message or 0 if
+ * no response is requested
+ * @param time Time, when the message is created
+ */
+ public MessageCreateEvent(int from, int to, String id, int size,
+ int responseSize, double time) {
+ super(from,to, id, time);
+ this.size = size;
+ this.responseSize = responseSize;
+ }
+
+
+ /**
+ * Creates the message this event represents.
+ */
+ @Override
+ public void processEvent(World world) {
+ DTNHost to = world.getNodeByAddress(this.toAddr);
+ DTNHost from = world.getNodeByAddress(this.fromAddr);
+
+ Message m = new Message(from, to, this.id, this.size);
+ m.setResponseSize(this.responseSize);
+ from.createNewMessage(m);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " [" + fromAddr + "->" + toAddr + "] " +
+ "size:" + size + " CREATE";
+ }
+}
diff --git a/input/MessageDeleteEvent.java b/input/MessageDeleteEvent.java
new file mode 100644
index 000000000..b29671779
--- /dev/null
+++ b/input/MessageDeleteEvent.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import core.DTNHost;
+import core.Message;
+import core.World;
+
+/**
+ * External event for deleting a message.
+ */
+
+public class MessageDeleteEvent extends MessageEvent {
+ /** is the delete caused by a drop (not "normal" removing) */
+ private boolean drop;
+
+ /**
+ * Creates a message delete event
+ * @param host Where to delete the message
+ * @param id ID of the message
+ * @param time Time when the message is deleted
+ */
+ public MessageDeleteEvent(int host, String id, double time,
+ boolean drop) {
+ super(host, host, id, time);
+ this.drop = drop;
+ }
+
+ /**
+ * Deletes the message
+ */
+ @Override
+ public void processEvent(World world) {
+ DTNHost host = world.getNodeByAddress(this.fromAddr);
+
+ if (id.equals(StandardEventsReader.ALL_MESSAGES_ID)) {
+ List ids = new ArrayList();
+ for (Message m : host.getMessageCollection()) {
+ ids.add(m.getId());
+ }
+ for (String nextId : ids) {
+ host.deleteMessage(nextId, drop);
+ }
+ } else {
+ host.deleteMessage(id, drop);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " [" + fromAddr + "] DELETE";
+ }
+
+}
diff --git a/input/MessageEvent.java b/input/MessageEvent.java
new file mode 100644
index 000000000..fb267b143
--- /dev/null
+++ b/input/MessageEvent.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+/**
+ * A message related external event
+ */
+public abstract class MessageEvent extends ExternalEvent {
+ /** address of the node the message is from */
+ protected int fromAddr;
+ /** address of the node the message is to */
+ protected int toAddr;
+ /** identifier of the message */
+ protected String id;
+
+ /**
+ * Creates a message event
+ * @param from Where the message comes from
+ * @param to Who the message goes to
+ * @param id ID of the message
+ * @param time Time when the message event occurs
+ */
+ public MessageEvent(int from, int to, String id, double time) {
+ super(time);
+ this.fromAddr = from;
+ this.toAddr= to;
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return "MSG @" + this.time + " " + id;
+ }
+}
diff --git a/input/MessageEventGenerator.java b/input/MessageEventGenerator.java
new file mode 100644
index 000000000..1c9aa45bd
--- /dev/null
+++ b/input/MessageEventGenerator.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import java.util.Random;
+
+import core.Settings;
+import core.SettingsError;
+
+/**
+ * Message creation -external events generator. Creates uniformly distributed
+ * message creation patterns whose message size and inter-message intervals can
+ * be configured.
+ */
+public class MessageEventGenerator implements EventQueue {
+ /** Message size range -setting id ({@value}). Can be either a single
+ * value or a range (min, max) of uniformly distributed random values.
+ * Defines the message size (bytes). */
+ public static final String MESSAGE_SIZE_S = "size";
+ /** Message creation interval range -setting id ({@value}). Can be either a
+ * single value or a range (min, max) of uniformly distributed
+ * random values. Defines the inter-message creation interval (seconds). */
+ public static final String MESSAGE_INTERVAL_S = "interval";
+ /** Sender/receiver address range -setting id ({@value}).
+ * The lower bound is inclusive and upper bound exclusive. */
+ public static final String HOST_RANGE_S = "hosts";
+ /** (Optional) receiver address range -setting id ({@value}).
+ * If a value for this setting is defined, the destination hosts are
+ * selected from this range and the source hosts from the
+ * {@link #HOST_RANGE_S} setting's range.
+ * The lower bound is inclusive and upper bound exclusive. */
+ public static final String TO_HOST_RANGE_S = "tohosts";
+
+ /** Message ID prefix -setting id ({@value}). The value must be unique
+ * for all message sources, so if you have more than one message generator,
+ * use different prefix for all of them. The random number generator's
+ * seed is derived from the prefix, so by changing the prefix, you'll get
+ * also a new message sequence. */
+ public static final String MESSAGE_ID_PREFIX_S = "prefix";
+ /** Message creation time range -setting id ({@value}). Defines the time
+ * range when messages are created. No messages are created before the first
+ * and after the second value. By default, messages are created for the
+ * whole simulation time. */
+ public static final String MESSAGE_TIME_S = "time";
+
+ /** Time of the next event (simulated seconds) */
+ protected double nextEventsTime = 0;
+ /** Range of host addresses that can be senders or receivers */
+ protected int[] hostRange = {0, 0};
+ /** Range of host addresses that can be receivers */
+ protected int[] toHostRange = null;
+ /** Next identifier for a message */
+ private int id = 0;
+ /** Prefix for the messages */
+ protected String idPrefix;
+ /** Size range of the messages (min, max) */
+ private int[] sizeRange;
+ /** Interval between messages (min, max) */
+ private int[] msgInterval;
+ /** Time range for message creation (min, max) */
+ protected double[] msgTime;
+
+ /** Random number generator for this Class */
+ protected Random rng;
+
+ /**
+ * Constructor, initializes the interval between events,
+ * and the size of messages generated, as well as number
+ * of hosts in the network.
+ * @param s Settings for this generator.
+ */
+ public MessageEventGenerator(Settings s){
+ this.sizeRange = s.getCsvInts(MESSAGE_SIZE_S);
+ this.msgInterval = s.getCsvInts(MESSAGE_INTERVAL_S);
+ this.hostRange = s.getCsvInts(HOST_RANGE_S, 2);
+ this.idPrefix = s.getSetting(MESSAGE_ID_PREFIX_S);
+
+ if (s.contains(MESSAGE_TIME_S)) {
+ this.msgTime = s.getCsvDoubles(MESSAGE_TIME_S, 2);
+ }
+ else {
+ this.msgTime = null;
+ }
+ if (s.contains(TO_HOST_RANGE_S)) {
+ this.toHostRange = s.getCsvInts(TO_HOST_RANGE_S, 2);
+ }
+ else {
+ this.toHostRange = null;
+ }
+
+ /* if prefix is unique, so will be the rng's sequence */
+ this.rng = new Random(idPrefix.hashCode());
+
+ if (this.sizeRange.length == 1) {
+ /* convert single value to range with 0 length */
+ this.sizeRange = new int[] {this.sizeRange[0], this.sizeRange[0]};
+ }
+ else {
+ s.assertValidRange(this.sizeRange, MESSAGE_SIZE_S);
+ }
+ if (this.msgInterval.length == 1) {
+ this.msgInterval = new int[] {this.msgInterval[0],
+ this.msgInterval[0]};
+ }
+ else {
+ s.assertValidRange(this.msgInterval, MESSAGE_INTERVAL_S);
+ }
+ s.assertValidRange(this.hostRange, HOST_RANGE_S);
+
+ if (this.hostRange[1] - this.hostRange[0] < 2) {
+ if (this.toHostRange == null) {
+ throw new SettingsError("Host range must contain at least two "
+ + "nodes unless toHostRange is defined");
+ }
+ else if (toHostRange[0] == this.hostRange[0] &&
+ toHostRange[1] == this.hostRange[1]) {
+ // XXX: teemuk: Since (X,X) == (X,X+1) in drawHostAddress()
+ // there's still a boundary condition that can cause an
+ // infinite loop.
+ throw new SettingsError("If to and from host ranges contain" +
+ " only one host, they can't be the equal");
+ }
+ }
+
+ /* calculate the first event's time */
+ this.nextEventsTime = (this.msgTime != null ? this.msgTime[0] : 0)
+ + msgInterval[0] +
+ (msgInterval[0] == msgInterval[1] ? 0 :
+ rng.nextInt(msgInterval[1] - msgInterval[0]));
+ }
+
+
+ /**
+ * Draws a random host address from the configured address range
+ * @param hostRange The range of hosts
+ * @return A random host address
+ */
+ protected int drawHostAddress(int hostRange[]) {
+ if (hostRange[1] == hostRange[0]) {
+ return hostRange[0];
+ }
+ return hostRange[0] + rng.nextInt(hostRange[1] - hostRange[0]);
+ }
+
+ /**
+ * Generates a (random) message size
+ * @return message size
+ */
+ protected int drawMessageSize() {
+ int sizeDiff = sizeRange[0] == sizeRange[1] ? 0 :
+ rng.nextInt(sizeRange[1] - sizeRange[0]);
+ return sizeRange[0] + sizeDiff;
+ }
+
+ /**
+ * Generates a (random) time difference between two events
+ * @return the time difference
+ */
+ protected int drawNextEventTimeDiff() {
+ int timeDiff = msgInterval[0] == msgInterval[1] ? 0 :
+ rng.nextInt(msgInterval[1] - msgInterval[0]);
+ return msgInterval[0] + timeDiff;
+ }
+
+ /**
+ * Draws a destination host address that is different from the "from"
+ * address
+ * @param hostRange The range of hosts
+ * @param from the "from" address
+ * @return a destination address from the range, but different from "from"
+ */
+ protected int drawToAddress(int hostRange[], int from) {
+ int to;
+ do {
+ to = this.toHostRange != null ? drawHostAddress(this.toHostRange):
+ drawHostAddress(this.hostRange);
+ } while (from==to);
+
+ return to;
+ }
+
+ /**
+ * Returns the next message creation event
+ * @see input.EventQueue#nextEvent()
+ */
+ public ExternalEvent nextEvent() {
+ int responseSize = 0; /* zero stands for one way messages */
+ int msgSize;
+ int interval;
+ int from;
+ int to;
+
+ /* Get two *different* nodes randomly from the host ranges */
+ from = drawHostAddress(this.hostRange);
+ to = drawToAddress(hostRange, from);
+
+ msgSize = drawMessageSize();
+ interval = drawNextEventTimeDiff();
+
+ /* Create event and advance to next event */
+ MessageCreateEvent mce = new MessageCreateEvent(from, to, this.getID(),
+ msgSize, responseSize, this.nextEventsTime);
+ this.nextEventsTime += interval;
+
+ if (this.msgTime != null && this.nextEventsTime > this.msgTime[1]) {
+ /* next event would be later than the end time */
+ this.nextEventsTime = Double.MAX_VALUE;
+ }
+
+ return mce;
+ }
+
+ /**
+ * Returns next message creation event's time
+ * @see input.EventQueue#nextEventsTime()
+ */
+ public double nextEventsTime() {
+ return this.nextEventsTime;
+ }
+
+ /**
+ * Returns a next free message ID
+ * @return next globally unique message ID
+ */
+ protected String getID(){
+ this.id++;
+ return idPrefix + this.id;
+ }
+}
diff --git a/input/MessageRelayEvent.java b/input/MessageRelayEvent.java
new file mode 100644
index 000000000..4d9d43199
--- /dev/null
+++ b/input/MessageRelayEvent.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import core.DTNHost;
+import core.World;
+
+/**
+ * External event for all the stages of relaying a message between two
+ * hosts (start and possible abort or delivery).
+ */
+public class MessageRelayEvent extends MessageEvent {
+ private int stage;
+
+ /** Message relay stage constant for start of sending */
+ public static final int SENDING = 1;
+ /** Message relay stage constant for ready delivery */
+ public static final int TRANSFERRED = 2;
+ /** Message relay stage constant for aborted delivery */
+ public static final int ABORTED = 3;
+ /** Stage constant -> String representation mapping */
+ public static final String[] STAGE_STRINGS = {"SENDING",
+ "TRANSFERRED", "ABORTED"};
+
+ /**
+ * Creates a message relaying event
+ * @param from Where the message comes from (at this hop)
+ * @param to Who the message goes to (at this hop)
+ * @param id ID of the message
+ * @param time Time when this event happens
+ * @param stage The stage of the event (SENDING, TRANSFERRED, or ABORTED)
+ */
+ public MessageRelayEvent(int from, int to, String id, double time,
+ int stage) {
+ super(from, to, id, time);
+ this.stage = stage;
+ }
+
+ /**
+ * Relays the message
+ */
+ public void processEvent(World world) {
+ // get DTNHosts and pass messages between them
+ DTNHost from = world.getNodeByAddress(this.fromAddr);
+ DTNHost to = world.getNodeByAddress(this.toAddr);
+
+ switch(stage) {
+ case SENDING:
+ from.sendMessage(id, to);
+ break;
+ case TRANSFERRED:
+ to.messageTransferred(id, from);
+ break;
+ case ABORTED:
+ to.messageAborted(id, from, -1);
+ break;
+ default:
+ assert false : "Invalid stage (" + stage + ") for " + this;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " [" + fromAddr + "->" + toAddr + "] " +
+ STAGE_STRINGS[stage-1];
+ }
+
+}
diff --git a/input/OneFromEachMessageGenerator.java b/input/OneFromEachMessageGenerator.java
new file mode 100644
index 000000000..b30399819
--- /dev/null
+++ b/input/OneFromEachMessageGenerator.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import core.Settings;
+import core.SettingsError;
+
+/**
+ * Message creation -external events generator. Creates one message from
+ * every source node (defined with {@link MessageEventGenerator#HOST_RANGE_S})
+ * to one of the destination nodes (defined with
+ * {@link MessageEventGenerator#TO_HOST_RANGE_S}).
+ * The message size, first messages time and the intervals between creating
+ * messages can be configured like with {@link MessageEventGenerator}. End
+ * time is not respected, but messages are created until every from-node has
+ * created a message.
+ * @see MessageEventGenerator
+ */
+public class OneFromEachMessageGenerator extends MessageEventGenerator {
+ private List fromIds;
+
+ public OneFromEachMessageGenerator(Settings s) {
+ super(s);
+ this.fromIds = new ArrayList();
+
+ if (toHostRange == null) {
+ throw new SettingsError("Destination host (" + TO_HOST_RANGE_S +
+ ") must be defined");
+ }
+ for (int i = hostRange[0]; i < hostRange[1]; i++) {
+ fromIds.add(i);
+ }
+ Collections.shuffle(fromIds, rng);
+ }
+
+ /**
+ * Returns the next message creation event
+ * @see input.EventQueue#nextEvent()
+ */
+ public ExternalEvent nextEvent() {
+ int responseSize = 0; /* no responses requested */
+ int from;
+ int to;
+
+ from = this.fromIds.remove(0);
+ to = drawToAddress(toHostRange, -1);
+
+ if (to == from) { /* skip self */
+ if (this.fromIds.size() == 0) { /* oops, no more from addresses */
+ this.nextEventsTime = Double.MAX_VALUE;
+ return new ExternalEvent(Double.MAX_VALUE);
+ } else {
+ from = this.fromIds.remove(0);
+ }
+ }
+
+ if (this.fromIds.size() == 0) {
+ this.nextEventsTime = Double.MAX_VALUE; /* no messages left */
+ } else {
+ this.nextEventsTime += drawNextEventTimeDiff();
+ }
+
+ MessageCreateEvent mce = new MessageCreateEvent(from, to, getID(),
+ drawMessageSize(), responseSize, this.nextEventsTime);
+
+ return mce;
+ }
+
+}
\ No newline at end of file
diff --git a/input/OneToEachMessageGenerator.java b/input/OneToEachMessageGenerator.java
new file mode 100644
index 000000000..157fa27e5
--- /dev/null
+++ b/input/OneToEachMessageGenerator.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import core.Settings;
+import core.SettingsError;
+
+/**
+ * Message creation -external events generator. Creates one message from
+ * source node/nodes (defined with {@link MessageEventGenerator#HOST_RANGE_S})
+ * to all destination nodes (defined with
+ * {@link MessageEventGenerator#TO_HOST_RANGE_S}).
+ * The message size, first messages time and the intervals between creating
+ * messages can be configured like with {@link MessageEventGenerator}. End
+ * time is not respected, but messages are created until there's a message for
+ * every destination node.
+ * @see MessageEventGenerator
+ */
+public class OneToEachMessageGenerator extends MessageEventGenerator {
+ private List toIds;
+
+ public OneToEachMessageGenerator(Settings s) {
+ super(s);
+ this.toIds = new ArrayList();
+
+ if (toHostRange == null) {
+ throw new SettingsError("Destination host (" + TO_HOST_RANGE_S +
+ ") must be defined");
+ }
+ for (int i = toHostRange[0]; i < toHostRange[1]; i++) {
+ toIds.add(i);
+ }
+ Collections.shuffle(toIds, rng);
+ }
+
+ /**
+ * Returns the next message creation event
+ * @see input.EventQueue#nextEvent()
+ */
+ public ExternalEvent nextEvent() {
+ int responseSize = 0; /* no responses requested */
+ int from;
+ int to;
+
+ from = drawHostAddress(hostRange);
+ to = this.toIds.remove(0);
+
+ if (to == from) { /* skip self */
+ if (this.toIds.size() == 0) { /* oops, no more from addresses */
+ this.nextEventsTime = Double.MAX_VALUE;
+ return new ExternalEvent(Double.MAX_VALUE);
+ } else {
+ to = this.toIds.remove(0);
+ }
+ }
+
+ if (this.toIds.size() == 0) {
+ this.nextEventsTime = Double.MAX_VALUE; /* no messages left */
+ } else {
+ this.nextEventsTime += drawNextEventTimeDiff();
+ }
+
+ MessageCreateEvent mce = new MessageCreateEvent(from, to, getID(),
+ drawMessageSize(), responseSize, this.nextEventsTime);
+
+ return mce;
+ }
+
+}
\ No newline at end of file
diff --git a/input/ScheduledUpdatesQueue.java b/input/ScheduledUpdatesQueue.java
new file mode 100644
index 000000000..b4b98f7a4
--- /dev/null
+++ b/input/ScheduledUpdatesQueue.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Event queue where simulation objects can request an update to happen
+ * at the specified simulation time. Multiple updates at the same time
+ * are merged to a single update.
+ */
+public class ScheduledUpdatesQueue implements EventQueue {
+ /** Time of the event (simulated seconds) */
+ private ExternalEvent nextEvent;
+ private List updates;
+
+ /**
+ * Constructor. Creates an empty update queue.
+ */
+ public ScheduledUpdatesQueue(){
+ this.nextEvent = new ExternalEvent(Double.MAX_VALUE);
+ this.updates = new ArrayList();
+ }
+
+ /**
+ * Returns the next scheduled event or event with time Double.MAX_VALUE
+ * if there aren't any.
+ * @return the next scheduled event
+ */
+ public ExternalEvent nextEvent() {
+ ExternalEvent event = this.nextEvent;
+
+ if (this.updates.size() == 0) {
+ this.nextEvent = new ExternalEvent(Double.MAX_VALUE);
+ }
+ else {
+ this.nextEvent = this.updates.remove(0);
+ }
+
+ return event;
+ }
+
+ /**
+ * Returns the next scheduled event's time or Double.MAX_VALUE if there
+ * aren't any events left
+ * @return the next scheduled event's time
+ */
+ public double nextEventsTime() {
+ return this.nextEvent.getTime();
+ }
+
+ /**
+ * Add a new update request for the given time
+ * @param simTime The time when the update should happen
+ */
+ public void addUpdate(double simTime) {
+ ExternalEvent ee = new ExternalEvent(simTime);
+
+ if (ee.compareTo(nextEvent) == 0) { // this event is already next
+ return;
+ }
+ else if (this.nextEvent.getTime() > simTime) { // new nextEvent
+ putToQueue(this.nextEvent); // put the old nextEvent back to q
+ this.nextEvent = ee;
+ }
+ else { // given event happens later..
+ putToQueue(ee);
+ }
+ }
+
+ /**
+ * Puts a event to the queue in the right place
+ * @param ee The event to put to the queue
+ */
+ private void putToQueue(ExternalEvent ee) {
+ double eeTime = ee.getTime();
+
+ for (int i=0, n=this.updates.size(); i no need for new
+ }
+ else if (eeTime < time) {
+ this.updates.add(i, ee);
+ return;
+ }
+ }
+
+ /* all existing updates are earlier -> add to the end of the list */
+ this.updates.add(ee);
+ }
+
+ public String toString() {
+ String times = "updates @ " + this.nextEvent.getTime();
+
+ for (ExternalEvent ee : this.updates) {
+ times += ", " + ee.getTime();
+ }
+
+ return times;
+ }
+}
diff --git a/input/StandardEventsReader.java b/input/StandardEventsReader.java
new file mode 100644
index 000000000..7e09f661c
--- /dev/null
+++ b/input/StandardEventsReader.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+import java.util.regex.Pattern;
+
+import core.SimError;
+
+/**
+ *
+ * External events reader for standard-format events
+ * (created e.g by the dtnsim2parser).
+ *
+ *
+ * Syntax:
+ *
+ * <time> <actionId> <msgId> <hostId>
+ * [<host2Id> [<size>] [<respSize>]]
+ *
+ *
+ * All actions (except CONNECTION) must have first four fields. SEND, DELIVERED
+ * and ABORT actions need host2Id field too (the host who the message is/was
+ * being transferred to). CREATE action needs the additional size
+ * (of the message) field and can have also size-of-the-response field if
+ * a response to this message is requested.
+ * CONNNECTION action is followed by the two hosts which connect (or
+ * disconnect) to each other and then either "up" or "down" depending on whether
+ * the connection was created or destroyed.
+ *
+ * Message DROP and REMOVE events can use {@value #ALL_MESSAGES_ID} as the
+ * message ID for referring to all messages the node has in message buffer
+ * (i.e., to delete all messages).
+ *
+ */
+public class StandardEventsReader implements ExternalEventsReader {
+ /** Identifier of message creation event ({@value}) */
+ public static final String CREATE = "C";
+ /** Identifier of message transfer start event ({@value}) */
+ public static final String SEND = "S";
+ /** Identifier of message delivered event ({@value}) */
+ public static final String DELIVERED = "DE";
+ /** Identifier of message transfer aborted event ({@value}) */
+ public static final String ABORT = "A";
+ /** Identifier of message dropped event ({@value}) */
+ public static final String DROP = "DR";
+ /** Identifier of message removed event ({@value}) */
+ public static final String REMOVE = "R";
+ /** Identifier of connection event ({@value}) */
+ public static final String CONNECTION = "CONN";
+ /** Value identifier of connection down event ({@value}) */
+ public static final String CONNECTION_DOWN = "down";
+ /** Value identifier of connection up event ({@value}) */
+ public static final String CONNECTION_UP = "up";
+ /** Message identifier to use to refer to all messages ({@value}) */
+ public static final String ALL_MESSAGES_ID = "*";
+
+ //private Scanner scanner;
+ private BufferedReader reader;
+
+ public StandardEventsReader(File eventsFile){
+ try {
+ //this.scanner = new Scanner(eventsFile);
+ this.reader = new BufferedReader(new FileReader(eventsFile));
+ } catch (FileNotFoundException e) {
+ throw new SimError(e.getMessage(),e);
+ }
+ }
+
+
+ public List readEvents(int nrof) {
+ ArrayList events = new ArrayList(nrof);
+ int eventsRead = 0;
+ // skip empty and comment lines
+ Pattern skipPattern = Pattern.compile("(#.*)|(^\\s*$)");
+
+ String line;
+ try {
+ line = this.reader.readLine();
+ } catch (IOException e1) {
+ throw new SimError("Reading from external event file failed.");
+ }
+ while (eventsRead < nrof && line != null) {
+ Scanner lineScan = new Scanner(line);
+ if (skipPattern.matcher(line).matches()) {
+ // skip empty and comment lines
+ try {
+ line = this.reader.readLine();
+ } catch (IOException e) {
+ throw new SimError("Reading from external event file " +
+ "failed.");
+ }
+ continue;
+ }
+
+ double time;
+ String action;
+ String msgId;
+ int hostAddr;
+ int host2Addr;
+
+ try {
+ time = lineScan.nextDouble();
+ action = lineScan.next();
+
+ if (action.equals(DROP)) {
+ msgId = lineScan.next();
+ hostAddr = getHostAddress(lineScan.next());
+ events.add(new MessageDeleteEvent(hostAddr, msgId,
+ time, true));
+ }
+ else if (action.equals(REMOVE)) {
+ msgId = lineScan.next();
+ hostAddr = getHostAddress(lineScan.next());
+ events.add(new MessageDeleteEvent(hostAddr, msgId,
+ time, false));
+ }
+ else if (action.equals(CONNECTION)) {
+ String connEventType;
+ boolean isUp;
+ hostAddr = getHostAddress(lineScan.next());
+ host2Addr = getHostAddress(lineScan.next());
+ connEventType = lineScan.next();
+
+ String interfaceId = null;
+ if (lineScan.hasNext()) {
+ interfaceId = lineScan.next();
+ }
+
+ if (connEventType.equalsIgnoreCase(CONNECTION_UP)) {
+ isUp = true;
+ }
+ else if (connEventType.equalsIgnoreCase(CONNECTION_DOWN)) {
+ isUp = false;
+ }
+ else {
+ throw new SimError("Unknown up/down value '" +
+ connEventType + "'");
+ }
+
+ ConnectionEvent ce = new ConnectionEvent(hostAddr,
+ host2Addr, interfaceId, isUp, time);
+
+ events.add(ce);
+ }
+ else {
+ msgId = lineScan.next();
+ hostAddr = getHostAddress(lineScan.next());
+
+ host2Addr = getHostAddress(lineScan.next());
+
+ if (action.equals(CREATE)){
+ int size = lineScan.nextInt();
+ int respSize = 0;
+ if (lineScan.hasNextInt()) {
+ respSize = lineScan.nextInt();
+ }
+ events.add(new MessageCreateEvent(hostAddr, host2Addr,
+ msgId, size, respSize, time));
+ }
+ else {
+ int stage = -1;
+ if (action.equals(SEND)) {
+ stage = MessageRelayEvent.SENDING;
+ }
+ else if (action.equals(DELIVERED)) {
+ stage = MessageRelayEvent.TRANSFERRED;
+ }
+ else if (action.equals(ABORT)) {
+ stage = MessageRelayEvent.ABORTED;
+ }
+ else {
+ throw new SimError("Unknown action '" + action +
+ "' in external events");
+ }
+ events.add(new MessageRelayEvent(hostAddr, host2Addr,
+ msgId, time, stage));
+ }
+ }
+ // discard the newline in the end
+ if (lineScan.hasNextLine()) {
+ lineScan.nextLine(); // TODO: test
+ }
+ eventsRead++;
+ if (eventsRead < nrof) {
+ line = this.reader.readLine();
+ }
+ } catch (Exception e) {
+ throw new SimError("Can't parse external event " +
+ (eventsRead+1) + " from '" + line + "'", e);
+ }
+ }
+
+ return events;
+ }
+
+ /**
+ * Parses a host address from a hostId string (the numeric part after
+ * optional non-numeric part).
+ * @param hostId The id to parse the address from
+ * @return The address
+ * @throws SimError if no address could be parsed from the id
+ */
+ private int getHostAddress(String hostId) {
+ String addressPart = "";
+ if (hostId.matches("^\\d+$")) {
+ addressPart = hostId; // host id is only the address
+ }
+ else if (hostId.matches("^\\D+\\d+$")) {
+ String [] parts = hostId.split("\\D");
+ addressPart = parts[parts.length-1]; // last occurence is the addr
+ }
+ else {
+ throw new SimError("Invalid host ID '" + hostId + "'");
+ }
+
+ return Integer.parseInt(addressPart);
+ }
+
+ public void close() {
+ try {
+ this.reader.close();
+ } catch (IOException e) {}
+ }
+
+}
diff --git a/input/WKTMapReader.java b/input/WKTMapReader.java
new file mode 100644
index 000000000..24ac27ec6
--- /dev/null
+++ b/input/WKTMapReader.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import movement.map.MapNode;
+import movement.map.SimMap;
+import core.Coord;
+
+/**
+ * "Well-known text syntax" map data reader.
+ * Note: Understands only LINESTRING
s and
+ * MULTILINESTRING
s. Skips all POINT
data.
+ * Other data causes IOException.
+ */
+public class WKTMapReader extends WKTReader {
+ private Hashtable nodes;
+ /** are all paths bidirectional */
+ private boolean bidirectionalPaths = true;
+ private int nodeType = -1;
+
+ /**
+ * Constructor. Creates a new WKT reader ready for addPaths() calls.
+ * @param bidi If true, all read paths are set bidirectional (i.e. if node A
+ * is a neighbor of node B, node B is also a neighbor of node A).
+ */
+ public WKTMapReader(boolean bidi) {
+ this.bidirectionalPaths = bidi;
+ this.nodes = new Hashtable();
+ }
+
+ /**
+ * Sets bidirectional paths on/off.
+ * @param bidi If true, all paths are set bidirectional (false -> not)
+ */
+ public void setBidirectional(boolean bidi) {
+ this.bidirectionalPaths = bidi;
+ }
+
+ /**
+ * Returns the map nodes that were read in a collection
+ * @return the map nodes that were read in a collection
+ */
+ public Collection getNodes() {
+ return this.nodes.values();
+ }
+
+ /**
+ * Returns the original Map object that was used to read the map
+ * @return the original Map object that was used to read the map
+ */
+ public Map getNodesHash() {
+ return this.nodes;
+ }
+
+ /**
+ * Returns new a SimMap that is based on the read map
+ * @return new a SimMap that is based on the read map
+ */
+ public SimMap getMap() {
+ return new SimMap(this.nodes);
+ }
+
+ /**
+ * Adds paths to the map and adds given type to all nodes' type.
+ * @param file The file where the WKT data is read from
+ * @param type The type to use (integer value, see class {@link MapNode}))
+ * @throws IOException If something went wrong while reading the file
+ */
+ public void addPaths(File file, int type) throws IOException {
+ addPaths(new FileReader(file), type);
+ }
+
+
+ /**
+ * Add paths to current path set. Adding paths multiple times
+ * has the same result as concatenating the data before adding it.
+ * @param input Reader where the WKT data is read from
+ * @param nodeType The type to use (integer value, see class
+ * {@link MapNode}))
+ * @throws IOException if something went wrong with reading from the input
+ */
+ public void addPaths(Reader input, int nodeType) throws IOException {
+ this.nodeType = nodeType;
+ String type;
+ String contents;
+
+ init(input);
+
+ while((type = nextType()) != null) {
+ if (type.equals(LINESTRING)) {
+ contents = readNestedContents();
+ updateMap(parseLineString(contents));
+ }
+ else if (type.equals(MULTILINESTRING)) {
+ for (List list : parseMultilinestring()) {
+ updateMap(list);
+ }
+ }
+ else {
+ // known type but not interesting -> skip
+ readNestedContents();
+ }
+ }
+ }
+
+ /**
+ * Updates simulation map with coordinates in the list
+ * @param coords The list of coordinates
+ */
+ private void updateMap(List coords) {
+ MapNode previousNode = null;
+ for (Coord c : coords) {
+ previousNode = createOrUpdateNode(c, previousNode);
+ }
+ }
+
+ /**
+ * Creates or updates a node that is in location c and next to
+ * node previous
+ * @param c The location coordinates of the node
+ * @param previous Previous node whose neighbor node at c is
+ * @return The created/updated node
+ */
+ private MapNode createOrUpdateNode(Coord c, MapNode previous) {
+ MapNode n = null;
+
+ n = nodes.get(c); // try to get the node at that location
+
+ if (n == null) { // no node in that location -> create new
+ n = new MapNode(c);
+ nodes.put(c, n);
+ }
+
+ if (previous != null) {
+ n.addNeighbor(previous);
+ if (bidirectionalPaths) {
+ previous.addNeighbor(n);
+ }
+ }
+
+ if (nodeType != -1) {
+ n.addType(nodeType);
+ }
+
+ return n;
+ }
+
+}
diff --git a/input/WKTReader.java b/input/WKTReader.java
new file mode 100644
index 000000000..2c8c83caf
--- /dev/null
+++ b/input/WKTReader.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package input;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+import core.Coord;
+
+/**
+ * Class for reading "Well-known text syntax" files. See e.g.
+ * Wikipedia for
+ * WKT syntax details. For example, Open JUMP
+ * GIS program can save compatible data from many other formats.
+ */
+public class WKTReader {
+ /** known WKT type LINESTRING */
+ public static final String LINESTRING = "LINESTRING";
+ /** known WKT type MULTILINESTRING */
+ public static final String MULTILINESTRING = "MULTILINESTRING";
+ /** known WKT type POINT */
+ public static final String POINT = "POINT";
+
+ /** are all lines of the file read */
+ private boolean done;
+ /** reader for the data */
+ private BufferedReader reader;
+
+ /**
+ * Read point data from a file
+ * @param file The file to read points from
+ * @return A list of coordinates read from the file
+ * @throws IOException if something went wrong while reading
+ */
+ public List readPoints(File file) throws IOException {
+ return readPoints(new FileReader(file));
+ }
+
+ /**
+ * Read point data from a Reader
+ * @param r The Reader to read points from
+ * @return A list of coordinates that were read
+ * @throws IOException if something went wrong while reading
+ */
+ public List readPoints(Reader r) throws IOException {
+ List points = new ArrayList();
+
+ String type;
+ init(r);
+
+ while((type = nextType()) != null) {
+ if (type.equals(POINT)) {
+ points.add(parsePoint());
+ }
+ else {
+ // known type but not interesting -> skip
+ readNestedContents();
+ }
+ }
+
+ return points;
+ }
+
+ /**
+ * Read line (LINESTRING) data from a file
+ * @param file The file to read data from
+ * @return A list of coordinate lists read from the file
+ * @throws IOException if something went wrong while reading
+ */
+ public List> readLines(File file) throws IOException {
+ List> lines = new ArrayList>();
+
+ String type;
+ init(new FileReader(file));
+
+ while((type = nextType()) != null) {
+ if (type.equals(LINESTRING)) {
+ lines.add(parseLineString(readNestedContents()));
+ }
+ else {
+ // known type but not interesting -> skip
+ readNestedContents();
+ }
+ }
+
+ return lines;
+ }
+
+
+ /**
+ * Initialize the reader to use a certain input reader
+ * @param input The input to use
+ */
+ protected void init(Reader input) {
+ setDone(false);
+ reader = new BufferedReader(input);
+ }
+
+ /**
+ * Returns the next type read from the reader given at init or null
+ * if no more types can be read
+ * @return the next type read from the reader given at init
+ * @throws IOException
+ */
+ protected String nextType() throws IOException {
+ String type = null;
+
+ while (!done && type == null) {
+ type = readWord(reader);
+
+ if (type.length() < 1) { // discard empty lines
+ type = null;
+ continue;
+ }
+ }
+
+ return type;
+ }
+
+ /**
+ * Returns true if type is one of the known WKT types
+ * @param type The type to check
+ * @return true if type is one of the known WKT types
+ */
+ protected boolean isKnownType(String type) {
+ if (type.equals(LINESTRING)) {
+ return true;
+ }
+ else if (type.equals(MULTILINESTRING)) {
+ return true;
+ }
+ else if (type.equals(POINT)) {
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * Reads a "word", ie whitespace delimited string of characters, from
+ * the reader
+ * @param r Reader to read the characters from
+ * @return The word that was read (or empty string if nothing was read)
+ * @throws IOException
+ */
+ protected String readWord(Reader r) throws IOException {
+ StringBuffer buf = new StringBuffer();
+ char c = skipAllWhitespace(r);
+
+ // read non-whitespace part
+ while(c != (char)-1 && !Character.isWhitespace(c)) {
+ buf.append(c);
+ c = (char)r.read();
+ }
+
+ if (c == (char)-1) {
+ setDone(true);
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Parses a MULTILINESTRING statement that has nested linestrings from
+ * the current reader
+ * @return List of parsed Coord lists
+ * @throws IOException
+ */
+ protected List> parseMultilinestring()
+ throws IOException {
+ List> list = new ArrayList>();
+ String multiContents = readNestedContents(reader);
+ StringReader r2 = new StringReader(multiContents);
+ String lineString = readNestedContents(r2);
+
+ while (lineString.length() > 0) {
+ list.add(parseLineString(lineString));
+ lineString = readNestedContents(r2);
+ }
+
+ return list;
+ }
+
+ /**
+ * Parses a WKT point data from the intialized reader
+ * @return Point data as a Coordinate
+ * @throws IOException if couldn't parse coordinate values
+ */
+ protected Coord parsePoint() throws IOException {
+ String coords = readNestedContents(reader);
+ Scanner s = new Scanner(coords);
+ double x,y;
+
+ try {
+ x = s.nextDouble();
+ y = s.nextDouble();
+ } catch (RuntimeException e) {
+ throw new IOException("Bad coordinate values: '" + coords + "'");
+ }
+
+ return new Coord(x,y);
+ }
+
+ /**
+ * Reads and skips all characters until character "until" is read or
+ * end of stream is reached. Also the expected character is discarded.
+ * @param r Reader to read characters from
+ * @param until What character to expect
+ * @throws IOException
+ */
+ protected void skipUntil(Reader r, char until) throws IOException {
+ char c;
+ do {
+ c = (char)r.read();
+ } while (c != until && c != (char)-1);
+ }
+
+ /**
+ * Skips all consecutive whitespace characters from reader
+ * @param r Reader where the whitespace is skipped
+ * @return First non-whitespace character read from the reader
+ * @throws IOException
+ */
+ protected char skipAllWhitespace(Reader r) throws IOException {
+ char c;
+ do {
+ c = (char)r.read();
+ } while (Character.isWhitespace(c) && c != (char)-1);
+
+ return c;
+ }
+
+ /**
+ * Reads everything from the first opening parenthesis until line that
+ * ends to a closing parenthesis and returns the contents in one string
+ * @param r Reader to read the input from
+ * @return The text between the parentheses
+ */
+ public String readNestedContents(Reader r) throws IOException {
+ StringBuffer contents = new StringBuffer();
+ int parOpen; // nrof open parentheses
+ char c = '\0';
+
+ skipUntil(r,'(');
+ parOpen = 1;
+
+ while (c != (char)-1 && parOpen > 0) {
+ c = (char)r.read();
+ if (c == '(') {
+ parOpen++;
+ }
+ if (c == ')') {
+ parOpen--;
+ }
+ if (Character.isWhitespace(c)) {
+ c = ' '; // convert all whitespace to basic space
+ }
+ contents.append(c);
+ }
+
+ contents.deleteCharAt(contents.length()-1); // remove last ')'
+ return contents.toString();
+ }
+
+ /**
+ * Returns nested contents from the reader given at init
+ * @return nested contents from the reader given at init
+ * @throws IOException
+ * @see #readNestedContents(Reader)
+ */
+ public String readNestedContents() throws IOException {
+ return readNestedContents(reader);
+ }
+
+ /**
+ * Parses coordinate tuples from "LINESTRING" lines
+ * @param line String that contains the whole "LINESTRING"'s content
+ * @return List of coordinates parsed from the linestring
+ */
+ protected List parseLineString(String line) {
+ List coords = new ArrayList();
+ Scanner lineScan;
+ Scanner tupleScan;
+ double x,y;
+ Coord c;
+
+ lineScan = new Scanner(line);
+ lineScan.useDelimiter(",");
+
+ while (lineScan.hasNext()) {
+ tupleScan = new Scanner(lineScan.next());
+ x = Double.parseDouble(tupleScan.next());
+ y = Double.parseDouble(tupleScan.next());
+ c = new Coord(x,y);
+
+ coords.add(c);
+ }
+
+ return coords;
+ }
+
+ /**
+ * Returns true if the whole file has been read
+ * @return true if the whole file has been read
+ */
+ protected boolean isDone() {
+ return this.done;
+ }
+
+ /**
+ * Sets the "is file read" state
+ * @param done If true, reading is done
+ */
+ protected void setDone(boolean done) {
+ this.done = done;
+ }
+
+}
diff --git a/input/package.html b/input/package.html
new file mode 100644
index 000000000..ab7fd2259
--- /dev/null
+++ b/input/package.html
@@ -0,0 +1,8 @@
+
+
+
+
+Provides interfaces and classes for reading input data from external sources.
+
+
+
\ No newline at end of file
diff --git a/interfaces/ConnectivityGrid.java b/interfaces/ConnectivityGrid.java
new file mode 100644
index 000000000..4ee1082ae
--- /dev/null
+++ b/interfaces/ConnectivityGrid.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package interfaces;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+
+import movement.MovementModel;
+
+import core.Coord;
+import core.DTNSim;
+import core.NetworkInterface;
+import core.Settings;
+import core.SettingsError;
+import core.World;
+
+/**
+ *
+ * Overlay grid of the world where each interface is put on a cell depending
+ * of its location. This is used in cell-based optimization of connecting
+ * the interfaces.
+ *
+ * The idea in short:
+ * Instead of checking for every interface if some of the other interfaces are close
+ * enough (this approach obviously doesn't scale) we check only interfaces that
+ * are "close enough" to be possibly connected. Being close enough is
+ * determined by keeping track of the approximate location of the interfaces
+ * by storing them in overlay grid's cells and updating the cell information
+ * every time the interfaces move. If two interfaces are in the same cell or in
+ * neighboring cells, they have a chance of being close enough for
+ * connection. Then only that subset of interfaces is checked for possible
+ * connectivity.
+ *
+ *
+ * Note: this class does NOT support negative
+ * coordinates. Also, it makes sense to normalize the coordinates to start
+ * from zero to conserve memory.
+ */
+public class ConnectivityGrid extends ConnectivityOptimizer {
+
+ /**
+ * Cell based optimization cell size multiplier -setting id ({@value}).
+ * Used in {@link World#OPTIMIZATION_SETTINGS_NS} name space.
+ * Single ConnectivityCell's size is the biggest radio range times this.
+ * Larger values save memory and decrease startup time but may result in
+ * slower simulation.
+ * Default value is {@link #DEF_CON_CELL_SIZE_MULT}.
+ * Smallest accepted value is 1.
+ */
+ public static final String CELL_SIZE_MULT_S = "cellSizeMult";
+ /** default value for cell size multiplier ({@value}) */
+ public static final int DEF_CON_CELL_SIZE_MULT = 5;
+
+ private GridCell[][] cells;
+ private HashMap ginterfaces;
+ private int cellSize;
+ private int rows;
+ private int cols;
+ private static int worldSizeX;
+ private static int worldSizeY;
+ private static int cellSizeMultiplier;
+
+ static HashMap gridobjects;
+
+ static {
+ DTNSim.registerForReset(ConnectivityGrid.class.getCanonicalName());
+ reset();
+ }
+
+ public static void reset() {
+ gridobjects = new HashMap();
+
+ Settings s = new Settings(MovementModel.MOVEMENT_MODEL_NS);
+ int [] worldSize = s.getCsvInts(MovementModel.WORLD_SIZE,2);
+ worldSizeX = worldSize[0];
+ worldSizeY = worldSize[1];
+
+ s.setNameSpace(World.OPTIMIZATION_SETTINGS_NS);
+ if (s.contains(CELL_SIZE_MULT_S)) {
+ cellSizeMultiplier = s.getInt(CELL_SIZE_MULT_S);
+ }
+ else {
+ cellSizeMultiplier = DEF_CON_CELL_SIZE_MULT;
+ }
+ if (cellSizeMultiplier < 1) {
+ throw new SettingsError("Too small value (" + cellSizeMultiplier +
+ ") for " + World.OPTIMIZATION_SETTINGS_NS +
+ "." + CELL_SIZE_MULT_S);
+ }
+ }
+
+ /**
+ * Creates a new overlay connectivity grid
+ * @param cellSize Cell's edge's length (must be larger than the largest
+ * radio coverage's diameter)
+ */
+ private ConnectivityGrid(int cellSize) {
+ this.rows = worldSizeY/cellSize + 1;
+ this.cols = worldSizeX/cellSize + 1;
+ // leave empty cells on both sides to make neighbor search easier
+ this.cells = new GridCell[rows+2][cols+2];
+ this.cellSize = cellSize;
+
+ for (int i=0; i();
+ }
+
+ /**
+ * Returns a connectivity grid object based on a hash value
+ * @param key A hash value that separates different interfaces from each other
+ * @param maxRange Maximum range used by the radio technology using this
+ * connectivity grid.
+ * @return The connectivity grid object for a specific interface
+ */
+ public static ConnectivityGrid ConnectivityGridFactory(int key,
+ double maxRange) {
+ if (gridobjects.containsKey((Integer)key)) {
+ return (ConnectivityGrid)gridobjects.get((Integer)key);
+ } else {
+ ConnectivityGrid newgrid =
+ new ConnectivityGrid((int)Math.ceil(maxRange *
+ cellSizeMultiplier));
+ gridobjects.put((Integer)key,newgrid);
+ return newgrid;
+ }
+ }
+
+ /**
+ * Adds a network interface to the overlay grid
+ * @param ni The new network interface
+ */
+ public void addInterface(NetworkInterface ni) {
+ GridCell c = cellFromCoord(ni.getLocation());
+ c.addInterface(ni);
+ ginterfaces.put(ni,c);
+ }
+
+ /**
+ * Removes a network interface from the overlay grid
+ * @param ni The interface to be removed
+ */
+ public void removeInterface(NetworkInterface ni) {
+ GridCell c = ginterfaces.get(ni);
+ if (c != null) {
+ c.removeInterface(ni);
+ }
+ ginterfaces.remove(ni);
+ }
+
+ /**
+ * Adds interfaces to overlay grid
+ * @param interfaces Collection of interfaces to add
+ */
+ public void addInterfaces(Collection interfaces) {
+ for (NetworkInterface n : interfaces) {
+ addInterface(n);
+ }
+ }
+
+ /**
+ * Checks and updates (if necessary) interface's position in the grid
+ * @param ni The interface to update
+ */
+ public void updateLocation(NetworkInterface ni) {
+ GridCell oldCell = (GridCell)ginterfaces.get(ni);
+ GridCell newCell = cellFromCoord(ni.getLocation());
+
+ if (newCell != oldCell) {
+ oldCell.moveInterface(ni, newCell);
+ ginterfaces.put(ni,newCell);
+ }
+ }
+
+ /**
+ * Finds all neighboring cells and the cell itself based on the coordinates
+ * @param c The coordinates
+ * @return Array of neighboring cells
+ */
+ private GridCell[] getNeighborCellsByCoord(Coord c) {
+ // +1 due empty cells on both sides of the matrix
+ int row = (int)(c.getY()/cellSize) + 1;
+ int col = (int)(c.getX()/cellSize) + 1;
+ return getNeighborCells(row,col);
+ }
+
+ /**
+ * Returns an array of Cells that contains the neighbors of a certain
+ * cell and the cell itself.
+ * @param row Row index of the cell
+ * @param col Column index of the cell
+ * @return Array of neighboring Cells
+ */
+ private GridCell[] getNeighborCells(int row, int col) {
+ return new GridCell[] {
+ cells[row-1][col-1],cells[row-1][col],cells[row-1][col+1],//1st row
+ cells[row][col-1],cells[row][col],cells[row][col+1],//2nd row
+ cells[row+1][col-1],cells[row+1][col],cells[row+1][col+1]//3rd row
+ };
+ }
+
+ /**
+ * Get the cell having the specific coordinates
+ * @param c Coordinates
+ * @return The cell
+ */
+ private GridCell cellFromCoord(Coord c) {
+ // +1 due empty cells on both sides of the matrix
+ int row = (int)(c.getY()/cellSize) + 1;
+ int col = (int)(c.getX()/cellSize) + 1;
+
+ assert row > 0 && row <= rows && col > 0 && col <= cols : "Location " +
+ c + " is out of world's bounds";
+
+ return this.cells[row][col];
+ }
+
+ /**
+ * Returns all interfaces that use the same technology and channel
+ */
+ public Collection getAllInterfaces() {
+ return (Collection)ginterfaces.keySet();
+ }
+
+ /**
+ * Returns all interfaces that are "near" (i.e., in neighboring grid cells)
+ * and use the same technology and channel as the given interface
+ * @param ni The interface whose neighboring interfaces are returned
+ * @return List of near interfaces
+ */
+ public Collection getNearInterfaces(
+ NetworkInterface ni) {
+ ArrayList niList = new ArrayList();
+ GridCell loc = (GridCell)ginterfaces.get(ni);
+
+ if (loc != null) {
+ GridCell[] neighbors =
+ getNeighborCellsByCoord(ni.getLocation());
+ for (int i=0; i < neighbors.length; i++) {
+ niList.addAll(neighbors[i].getInterfaces());
+ }
+ }
+
+ return niList;
+ }
+
+
+ /**
+ * Returns a string representation of the ConnectivityCells object
+ * @return a string representation of the ConnectivityCells object
+ */
+ public String toString() {
+ return getClass().getSimpleName() + " of size " +
+ this.cols + "x" + this.rows + ", cell size=" + this.cellSize;
+ }
+
+ /**
+ * A single cell in the cell grid. Contains the interfaces that are
+ * currently in that part of the grid.
+ */
+ public class GridCell {
+ // how large array is initially chosen
+ private static final int EXPECTED_INTERFACE_COUNT = 5;
+ private ArrayList interfaces;
+
+ private GridCell() {
+ this.interfaces = new ArrayList(
+ EXPECTED_INTERFACE_COUNT);
+ }
+
+ /**
+ * Returns a list of of interfaces in this cell
+ * @return a list of of interfaces in this cell
+ */
+ public ArrayList getInterfaces() {
+ return this.interfaces;
+ }
+
+ /**
+ * Adds an interface to this cell
+ * @param ni The interface to add
+ */
+ public void addInterface(NetworkInterface ni) {
+ this.interfaces.add(ni);
+ }
+
+ /**
+ * Removes an interface from this cell
+ * @param ni The interface to remove
+ */
+ public void removeInterface(NetworkInterface ni) {
+ this.interfaces.remove(ni);
+ }
+
+ /**
+ * Moves a interface in a Cell to another Cell
+ * @param ni The interface to move
+ * @param to The cell where the interface should be moved to
+ */
+ public void moveInterface(NetworkInterface ni, GridCell to) {
+ to.addInterface(ni);
+ boolean removeOk = this.interfaces.remove(ni);
+ assert removeOk : "interface " + ni +
+ " not found from cell with " + interfaces.toString();
+ }
+
+ /**
+ * Returns a string representation of the cell
+ * @return a string representation of the cell
+ */
+ public String toString() {
+ return getClass().getSimpleName() + " with " +
+ this.interfaces.size() + " interfaces :" + this.interfaces;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/interfaces/ConnectivityOptimizer.java b/interfaces/ConnectivityOptimizer.java
new file mode 100644
index 000000000..59e8b5290
--- /dev/null
+++ b/interfaces/ConnectivityOptimizer.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package interfaces;
+
+import java.util.Collection;
+
+import core.NetworkInterface;
+
+/**
+ * A superclass for schemes for optimizing the location of possible contacts
+ * with network interfaces of a specific range
+ */
+abstract public class ConnectivityOptimizer {
+
+ /**
+ * Adds a network interface to the optimizer (unless it is already present)
+ */
+ abstract public void addInterface(NetworkInterface ni);
+
+ /**
+ * Adds a collection of network interfaces to the optimizer (except of those
+ * already added
+ */
+ abstract public void addInterfaces(Collection interfaces);
+
+ /**
+ * Updates a network interface's location
+ */
+ abstract public void updateLocation(NetworkInterface ni);
+
+ /**
+ * Finds all network interfaces that might be located so that they can be
+ * connected with the network interface
+ *
+ * @param ni network interface that needs to be connected
+ * @return A collection of network interfaces within proximity
+ */
+ abstract public Collection getNearInterfaces(
+ NetworkInterface ni);
+
+ /**
+ * Finds all other interfaces that are registered to the
+ * ConnectivityOptimizer
+ */
+ abstract public Collection getAllInterfaces();
+}
diff --git a/interfaces/DistanceCapacityInterface.java b/interfaces/DistanceCapacityInterface.java
new file mode 100644
index 000000000..83059869f
--- /dev/null
+++ b/interfaces/DistanceCapacityInterface.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2014 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package interfaces;
+
+import java.util.Collection;
+import core.Connection;
+import core.NetworkInterface;
+import core.Settings;
+import core.VBRConnection;
+
+/**
+ * A Network Interface that that takes in to account he distance from the
+ * other (connected) interface when determining the capacity of the links.
+ * The distance-dependent transmission speeds are given as comma-separated
+ * values using setting {@link this#TRANSMIT_SPEEDS_S}.
+ */
+public class DistanceCapacityInterface extends NetworkInterface {
+
+ /**
+ * Comma-separated list of speed values -setting id ({@value} ). The first
+ * value is the speed at distance 0 and the following are speeds at equal
+ * steps until the last one is the speed at the end of the transmit range (
+ * {@link NetworkInterface#TRANSMIT_RANGE_S}). The speed between the steps
+ * is linearly interpolated.
+ */
+ public static final String TRANSMIT_SPEEDS_S = "transmitSpeeds";
+
+ protected final int[] transmitSpeeds;
+
+ /**
+ * Reads the interface settings from the Settings file
+ */
+ public DistanceCapacityInterface(Settings s) {
+ super(s);
+ transmitSpeeds = s.getCsvInts(TRANSMIT_SPEEDS_S);
+ }
+
+ /**
+ * Copy constructor
+ * @param ni the copied network interface object
+ */
+ public DistanceCapacityInterface(DistanceCapacityInterface ni) {
+ super(ni);
+ transmitSpeeds = ni.transmitSpeeds;
+ }
+
+ public NetworkInterface replicate() {
+ return new DistanceCapacityInterface(this);
+ }
+
+ /**
+ * Tries to connect this host to another host. The other host must be
+ * active and within range of this host for the connection to succeed.
+ * @param anotherInterface The interface to connect to
+ */
+ public void connect(NetworkInterface anotherInterface) {
+ if (isScanning()
+ && anotherInterface.getHost().isRadioActive()
+ && isWithinRange(anotherInterface)
+ && !isConnected(anotherInterface)
+ && (this != anotherInterface)) {
+
+ Connection con = new VBRConnection(this.host, this,
+ anotherInterface.getHost(), anotherInterface);
+ connect(con,anotherInterface);
+ }
+ }
+
+ /**
+ * Updates the state of current connections (i.e. tears down connections
+ * that are out of range and creates new ones).
+ */
+ public void update() {
+ if (optimizer == null) {
+ return; /* nothing to do */
+ }
+
+ // First break the old ones
+ optimizer.updateLocation(this);
+ for (int i=0; i interfaces =
+ optimizer.getNearInterfaces(this);
+ for (NetworkInterface i : interfaces) {
+ connect(i);
+ }
+
+ /* update all connections */
+ for (Connection con : getConnections()) {
+ con.update();
+ }
+ }
+
+ /**
+ * Creates a connection to another host. This method does not do any checks
+ * on whether the other node is in range or active
+ * @param anotherInterface The interface to create the connection to
+ */
+ public void createConnection(NetworkInterface anotherInterface) {
+ if (!isConnected(anotherInterface) && (this != anotherInterface)) {
+ Connection con = new VBRConnection(this.host, this,
+ anotherInterface.getHost(), anotherInterface);
+ connect(con,anotherInterface);
+ }
+ }
+
+ /**
+ * Returns the transmit speed to another interface based on the
+ * distance to this interface
+ * @param ni The other network interface
+ */
+ @Override
+ public int getTransmitSpeed(NetworkInterface ni) {
+ double distance;
+ double fractionIndex;
+ double decimal;
+ double speed;
+ int index;
+
+ /* distance to the other interface */
+ distance = ni.getLocation().distance(this.getLocation());
+
+ if (distance >= this.transmitRange) {
+ return 0;
+ }
+
+ /* interpolate between the two speeds */
+ fractionIndex = (distance / this.transmitRange) *
+ (this.transmitSpeeds.length - 1);
+ index = (int)(fractionIndex);
+ decimal = fractionIndex - index;
+
+ speed = this.transmitSpeeds[index] * (1-decimal) +
+ this.transmitSpeeds[index + 1] * decimal;
+
+ return (int)speed;
+ }
+
+ /**
+ * Returns a string representation of the object.
+ * @return a string representation of the object.
+ */
+ public String toString() {
+ return "DistanceCapacityInterface " + super.toString();
+ }
+
+}
diff --git a/interfaces/InterferenceLimitedInterface.java b/interfaces/InterferenceLimitedInterface.java
new file mode 100644
index 000000000..90b9426c0
--- /dev/null
+++ b/interfaces/InterferenceLimitedInterface.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package interfaces;
+
+import java.util.Collection;
+
+import core.Connection;
+import core.NetworkInterface;
+import core.Settings;
+import core.VBRConnection;
+
+/**
+ * A simple Network Interface that provides a variable bit-rate service, where
+ * the bit-rate depends on the number of other transmitting stations within
+ * range The current transmit speed is updated only if there are ongoing
+ * transmissions. The configured transmit speed is the maximum obtainable speed.
+ */
+public class InterferenceLimitedInterface extends NetworkInterface {
+ protected int currentTransmitSpeed;
+ protected int numberOfTransmissions;
+
+ public InterferenceLimitedInterface(Settings s) {
+ super(s);
+ this.currentTransmitSpeed = 0;
+ this.numberOfTransmissions = 0;
+ }
+
+ /**
+ * Copy constructor
+ * @param ni the copied network interface object
+ */
+ public InterferenceLimitedInterface(InterferenceLimitedInterface ni) {
+ super(ni);
+ this.transmitRange = ni.transmitRange;
+ this.transmitSpeed = ni.transmitSpeed;
+ this.currentTransmitSpeed = 0;
+ this.numberOfTransmissions = 0;
+ }
+
+
+ public NetworkInterface replicate() {
+ return new InterferenceLimitedInterface(this);
+ }
+
+ /**
+ * Returns the transmit speed of this network layer
+ * @return the transmit speed
+ */
+ @Override
+ public int getTransmitSpeed(NetworkInterface ni) {
+ return this.currentTransmitSpeed;
+ }
+
+ /**
+ * Tries to connect this host to another host. The other host must be
+ * active and within range of this host for the connection to succeed.
+ * @param anotherInterface The host to connect to
+ */
+ public void connect(NetworkInterface anotherInterface) {
+ if (isScanning()
+ && anotherInterface.getHost().isRadioActive()
+ && isWithinRange(anotherInterface)
+ && !isConnected(anotherInterface)
+ && (this != anotherInterface)) {
+ // new contact within range
+
+ Connection con = new VBRConnection(this.host, this,
+ anotherInterface.getHost(), anotherInterface);
+ connect(con, anotherInterface);
+ }
+ }
+
+ /**
+ * Updates the state of current connections (i.e., tears down connections
+ * that are out of range).
+ */
+ public void update() {
+ if (optimizer == null) {
+ return; /* nothing to do */
+ }
+
+ // First break the old ones
+ optimizer.updateLocation(this);
+ for (int i=0; i interfaces =
+ optimizer.getNearInterfaces(this);
+ for (NetworkInterface i : interfaces)
+ connect(i);
+
+ // Find the current number of transmissions
+ // (to calculate the current transmission speed
+ numberOfTransmissions = 0;
+ int numberOfActive = 1;
+ for (Connection con : this.connections) {
+ if (con.getMessage() != null) {
+ numberOfTransmissions++;
+ }
+ if (((InterferenceLimitedInterface)con.getOtherInterface(this)).
+ isTransferring() == true) {
+ numberOfActive++;
+ }
+ }
+
+ int ntrans = numberOfTransmissions;
+ if ( numberOfTransmissions < 1) ntrans = 1;
+ if ( numberOfActive <2 ) numberOfActive = 2;
+
+ // Based on the equation of Gupta and Kumar - and the transmission speed
+ // is divided equally to all the ongoing transmissions
+ currentTransmitSpeed = (int)Math.floor((double)transmitSpeed /
+ (Math.sqrt((1.0*numberOfActive) *
+ Math.log(1.0*numberOfActive))) /
+ ntrans );
+
+ for (Connection con : getConnections()) {
+ con.update();
+ }
+ }
+
+ /**
+ * Creates a connection to another host. This method does not do any checks
+ * on whether the other node is in range or active
+ * @param anotherInterface The interface to create the connection to
+ */
+ public void createConnection(NetworkInterface anotherInterface) {
+ if (!isConnected(anotherInterface) && (this != anotherInterface)) {
+ // new contact within range
+
+ Connection con = new VBRConnection(this.host, this,
+ anotherInterface.getHost(), anotherInterface);
+ connect(con,anotherInterface);
+ }
+ }
+
+ /**
+ * Returns true if this interface is actually transmitting data
+ */
+ public boolean isTransferring() {
+ return (numberOfTransmissions > 0);
+ }
+
+ /**
+ * Returns a string representation of the object.
+ * @return a string representation of the object.
+ */
+ public String toString() {
+ return "InterfaceLimitedInterface " + super.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/interfaces/SimpleBroadcastInterface.java b/interfaces/SimpleBroadcastInterface.java
new file mode 100644
index 000000000..a1ad967df
--- /dev/null
+++ b/interfaces/SimpleBroadcastInterface.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package interfaces;
+
+import java.util.Collection;
+
+import core.CBRConnection;
+import core.Connection;
+import core.NetworkInterface;
+import core.Settings;
+
+/**
+ * A simple Network Interface that provides a constant bit-rate service, where
+ * one transmission can be on at a time.
+ */
+public class SimpleBroadcastInterface extends NetworkInterface {
+
+ /**
+ * Reads the interface settings from the Settings file
+ */
+ public SimpleBroadcastInterface(Settings s) {
+ super(s);
+ }
+
+ /**
+ * Copy constructor
+ * @param ni the copied network interface object
+ */
+ public SimpleBroadcastInterface(SimpleBroadcastInterface ni) {
+ super(ni);
+ }
+
+ public NetworkInterface replicate() {
+ return new SimpleBroadcastInterface(this);
+ }
+
+ /**
+ * Tries to connect this host to another host. The other host must be
+ * active and within range of this host for the connection to succeed.
+ * @param anotherInterface The interface to connect to
+ */
+ public void connect(NetworkInterface anotherInterface) {
+ if (isScanning()
+ && anotherInterface.getHost().isRadioActive()
+ && isWithinRange(anotherInterface)
+ && !isConnected(anotherInterface)
+ && (this != anotherInterface)) {
+ // new contact within range
+ // connection speed is the lower one of the two speeds
+ int conSpeed = anotherInterface.getTransmitSpeed(this);
+ if (conSpeed > this.transmitSpeed) {
+ conSpeed = this.transmitSpeed;
+ }
+
+ Connection con = new CBRConnection(this.host, this,
+ anotherInterface.getHost(), anotherInterface, conSpeed);
+ connect(con,anotherInterface);
+ }
+ }
+
+ /**
+ * Updates the state of current connections (i.e. tears down connections
+ * that are out of range and creates new ones).
+ */
+ public void update() {
+ if (optimizer == null) {
+ return; /* nothing to do */
+ }
+
+ // First break the old ones
+ optimizer.updateLocation(this);
+ for (int i=0; i interfaces =
+ optimizer.getNearInterfaces(this);
+ for (NetworkInterface i : interfaces) {
+ connect(i);
+ }
+ }
+
+ /**
+ * Creates a connection to another host. This method does not do any checks
+ * on whether the other node is in range or active
+ * @param anotherInterface The interface to create the connection to
+ */
+ public void createConnection(NetworkInterface anotherInterface) {
+ if (!isConnected(anotherInterface) && (this != anotherInterface)) {
+ // connection speed is the lower one of the two speeds
+ int conSpeed = anotherInterface.getTransmitSpeed(this);
+ if (conSpeed > this.transmitSpeed) {
+ conSpeed = this.transmitSpeed;
+ }
+
+ Connection con = new CBRConnection(this.host, this,
+ anotherInterface.getHost(), anotherInterface, conSpeed);
+ connect(con,anotherInterface);
+ }
+ }
+
+ /**
+ * Returns a string representation of the object.
+ * @return a string representation of the object.
+ */
+ public String toString() {
+ return "SimpleBroadcastInterface " + super.toString();
+ }
+
+}
diff --git a/lib/DTNConsoleConnection.jar b/lib/DTNConsoleConnection.jar
new file mode 100644
index 000000000..04040c4a0
Binary files /dev/null and b/lib/DTNConsoleConnection.jar differ
diff --git a/lib/ECLA.jar b/lib/ECLA.jar
new file mode 100644
index 000000000..78f17e499
Binary files /dev/null and b/lib/ECLA.jar differ
diff --git a/movement/BusControlSystem.java b/movement/BusControlSystem.java
new file mode 100644
index 000000000..9e94031ff
--- /dev/null
+++ b/movement/BusControlSystem.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package movement;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import movement.map.SimMap;
+import core.Coord;
+import core.DTNSim;
+
+/**
+ * This class controls busses and passengers that can use the bus.
+ * There can be many bus BusControlSystems, but a bus or passenger can only
+ * belong to one system.
+ *
+ * @author Frans Ekman
+ */
+public class BusControlSystem {
+ public static final String BUS_CONTROL_SYSTEM_NR = "busControlSystemNr";
+
+ private static HashMap systems;
+
+ private HashMap busses;
+ private HashMap travellers;
+ private List busStops;
+
+ private SimMap simMap;
+
+ static {
+ DTNSim.registerForReset(BusControlSystem.class.getCanonicalName());
+ reset();
+ }
+
+ /**
+ * Creates a new instance of BusControlSystem without any travelers or
+ * busses
+ * @param systemID The unique ID of this system.
+ */
+ private BusControlSystem(int systemID) {
+ busses = new HashMap();
+ travellers = new HashMap();
+ }
+
+ public static void reset() {
+ systems = new HashMap();
+ }
+
+ /**
+ * Called by busses belonging to this system every time the bus has stopped.
+ * It calls every passengers enterBus() method so that the passengers can
+ * enter the bus if they want to.
+ * @param busID Unique identifier of the bus
+ * @param busStop Coordinates of the bus stop
+ * @param nextPath The path to the next stop
+ */
+ public void busHasStopped(int busID, Coord busStop, Path nextPath) {
+ Iterator iterator = travellers.values().
+ iterator();
+ while (iterator.hasNext()) {
+ BusTravellerMovement traveller = (BusTravellerMovement)iterator.
+ next();
+ if (traveller.getLocation() != null) {
+ if ((traveller.getLocation()).equals(busStop)) {
+ if (traveller.getState() == BusTravellerMovement.
+ STATE_WAITING_FOR_BUS) {
+ Path path = new Path(nextPath);
+ traveller.enterBus(path);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a reference to a BusControlSystem with ID provided as parameter.
+ * If a system does not already exist with the requested ID, a new one is
+ * created.
+ * @param systemID unique ID of the system
+ * @return The bus control system with the provided ID
+ */
+ public static BusControlSystem getBusControlSystem(int systemID) {
+ Integer id = new Integer(systemID);
+
+ if (systems.containsKey(id)) {
+ return systems.get(id);
+ } else {
+ BusControlSystem bcs = new BusControlSystem(systemID);
+ systems.put(id, bcs);
+ return bcs;
+ }
+ }
+
+ /**
+ * Registers a bus to be part of a bus control system
+ * @param bus The bus to register
+ */
+ public void registerBus(BusMovement bus) {
+ busses.put(bus.getID(), bus);
+ }
+
+ /**
+ * Registers a traveller/passenger to be part of a bus control system
+ * @param traveller The traveller to register
+ */
+ public void registerTraveller(BusTravellerMovement traveller) {
+ travellers.put(traveller.getID(), traveller);
+ }
+
+ /**
+ * Provide the system with the map
+ * @param map
+ */
+ public void setMap(SimMap map) {
+ this.simMap = map;
+ }
+
+ /**
+ * Get the underlying map of the system
+ * @return The map
+ */
+ public SimMap getMap() {
+ return this.simMap;
+ }
+
+ /**
+ * @return A list of all bus stops belonging to this system
+ */
+ public List getBusStops() {
+ return busStops;
+ }
+
+ /**
+ * Set the bus stops that belong to this system
+ * @param busStops
+ */
+ public void setBusStops(List busStops) {
+ this.busStops = busStops;
+ }
+
+}
diff --git a/movement/BusMovement.java b/movement/BusMovement.java
new file mode 100644
index 000000000..d8a63a481
--- /dev/null
+++ b/movement/BusMovement.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package movement;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import movement.map.MapNode;
+import core.Coord;
+import core.Settings;
+
+/**
+ * This class controls the movement of busses. It informs the bus control system
+ * the bus is registered with every time the bus stops.
+ *
+ * @author Frans Ekman
+ */
+public class BusMovement extends MapRouteMovement {
+
+ private BusControlSystem controlSystem;
+ private int id;
+ private static int nextID = 0;
+ private boolean startMode;
+ private List stops;
+
+ /**
+ * Creates a new instance of BusMovement
+ * @param settings
+ */
+ public BusMovement(Settings settings) {
+ super(settings);
+ int bcs = settings.getInt(BusControlSystem.BUS_CONTROL_SYSTEM_NR);
+ controlSystem = BusControlSystem.getBusControlSystem(bcs);
+ controlSystem.setMap(super.getMap());
+ this.id = nextID++;
+ controlSystem.registerBus(this);
+ startMode = true;
+ stops = new LinkedList();
+ List stopNodes = super.getStops();
+ for (MapNode node : stopNodes) {
+ stops.add(node.getLocation().clone());
+ }
+ controlSystem.setBusStops(stops);
+ }
+
+ /**
+ * Create a new instance from a prototype
+ * @param proto
+ */
+ public BusMovement(BusMovement proto) {
+ super(proto);
+ this.controlSystem = proto.controlSystem;
+ this.id = nextID++;
+ controlSystem.registerBus(this);
+ startMode = true;
+ }
+
+ @Override
+ public Coord getInitialLocation() {
+ return (super.getInitialLocation()).clone();
+ }
+
+ @Override
+ public Path getPath() {
+ Coord lastLocation = (super.getLastLocation()).clone();
+ Path path = super.getPath();
+ if (!startMode) {
+ controlSystem.busHasStopped(id, lastLocation, path);
+ }
+ startMode = false;
+ return path;
+ }
+
+ @Override
+ public BusMovement replicate() {
+ return new BusMovement(this);
+ }
+
+ /**
+ * Returns unique ID of the bus
+ * @return unique ID of the bus
+ */
+ public int getID() {
+ return id;
+ }
+
+}
diff --git a/movement/BusTravellerMovement.java b/movement/BusTravellerMovement.java
new file mode 100644
index 000000000..51b8b8bdc
--- /dev/null
+++ b/movement/BusTravellerMovement.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package movement;
+
+import java.util.List;
+import java.util.Random;
+
+import movement.map.DijkstraPathFinder;
+import movement.map.MapNode;
+import movement.map.SimMap;
+import core.Coord;
+import core.Settings;
+
+/**
+ *
+ * This class controls the movement of bus travellers. A bus traveller belongs
+ * to a bus control system. A bus traveller has a destination and a start
+ * location. If the direct path to the destination is longer than the path the
+ * node would have to walk if it would take the bus, the node uses the bus. If
+ * the destination is not provided, the node will pass a random number of stops
+ * determined by Markov chains (defined in settings).
+ *
+ * @author Frans Ekman
+ *
+ */
+public class BusTravellerMovement extends MapBasedMovement implements
+ SwitchableMovement, TransportMovement {
+
+ public static final String PROBABILITIES_STRING = "probs";
+ public static final String PROBABILITY_TAKE_OTHER_BUS = "probTakeOtherBus";
+
+ public static final int STATE_WAITING_FOR_BUS = 0;
+ public static final int STATE_DECIDED_TO_ENTER_A_BUS = 1;
+ public static final int STATE_TRAVELLING_ON_BUS = 2;
+ public static final int STATE_WALKING_ELSEWHERE = 3;
+
+ private int state;
+ private Path nextPath;
+ private Coord location;
+ private Coord latestBusStop;
+ private BusControlSystem controlSystem;
+ private int id;
+ private ContinueBusTripDecider cbtd;
+ private double[] probabilities;
+ private double probTakeOtherBus;
+ private DijkstraPathFinder pathFinder;
+
+ private Coord startBusStop;
+ private Coord endBusStop;
+
+ private boolean takeBus;
+
+ private static int nextID = 0;
+
+ /**
+ * Creates a BusTravellerModel
+ * @param settings
+ */
+ public BusTravellerMovement(Settings settings) {
+ super(settings);
+ int bcs = settings.getInt(BusControlSystem.BUS_CONTROL_SYSTEM_NR);
+ controlSystem = BusControlSystem.getBusControlSystem(bcs);
+ id = nextID++;
+ controlSystem.registerTraveller(this);
+ nextPath = new Path();
+ state = STATE_WALKING_ELSEWHERE;
+ if (settings.contains(PROBABILITIES_STRING)) {
+ probabilities = settings.getCsvDoubles(PROBABILITIES_STRING);
+ }
+ if (settings.contains(PROBABILITY_TAKE_OTHER_BUS)) {
+ probTakeOtherBus = settings.getDouble(PROBABILITY_TAKE_OTHER_BUS);
+ }
+ cbtd = new ContinueBusTripDecider(rng, probabilities);
+ pathFinder = new DijkstraPathFinder(null);
+ takeBus = true;
+ }
+
+ /**
+ * Creates a BusTravellerModel from a prototype
+ * @param proto
+ */
+ public BusTravellerMovement(BusTravellerMovement proto) {
+ super(proto);
+ state = proto.state;
+ controlSystem = proto.controlSystem;
+ if (proto.location != null) {
+ location = proto.location.clone();
+ }
+ nextPath = proto.nextPath;
+ id = nextID++;
+ controlSystem.registerTraveller(this);
+ probabilities = proto.probabilities;
+ cbtd = new ContinueBusTripDecider(rng, probabilities);
+ pathFinder = proto.pathFinder;
+ this.probTakeOtherBus = proto.probTakeOtherBus;
+ takeBus = true;
+ }
+
+ @Override
+ public Coord getInitialLocation() {
+
+ MapNode[] mapNodes = (MapNode[])getMap().getNodes().
+ toArray(new MapNode[0]);
+ int index = rng.nextInt(mapNodes.length - 1);
+ location = mapNodes[index].getLocation().clone();
+
+ List allStops = controlSystem.getBusStops();
+ Coord closestToNode = getClosestCoordinate(allStops, location.clone());
+ latestBusStop = closestToNode.clone();
+
+ return location.clone();
+ }
+
+ @Override
+ public Path getPath() {
+ if (!takeBus) {
+ return null;
+ }
+ if (state == STATE_WAITING_FOR_BUS) {
+ return null;
+ } else if (state == STATE_DECIDED_TO_ENTER_A_BUS) {
+ state = STATE_TRAVELLING_ON_BUS;
+ List coords = nextPath.getCoords();
+ location = (coords.get(coords.size() - 1)).clone();
+ return nextPath;
+ } else if (state == STATE_WALKING_ELSEWHERE) {
+ // Try to find back to the bus stop
+ SimMap map = controlSystem.getMap();
+ if (map == null) {
+ return null;
+ }
+ MapNode thisNode = map.getNodeByCoord(location);
+ MapNode destinationNode = map.getNodeByCoord(latestBusStop);
+ List nodes = pathFinder.getShortestPath(thisNode,
+ destinationNode);
+ Path path = new Path(generateSpeed());
+ for (MapNode node : nodes) {
+ path.addWaypoint(node.getLocation());
+ }
+ location = latestBusStop.clone();
+ return path;
+ }
+
+ return null;
+ }
+
+ /**
+ * Switches state between getPath() calls
+ * @return Always 0
+ */
+ protected double generateWaitTime() {
+ if (state == STATE_WALKING_ELSEWHERE) {
+ if (location.equals(latestBusStop)) {
+ state = STATE_WAITING_FOR_BUS;
+ }
+ }
+ if (state == STATE_TRAVELLING_ON_BUS) {
+ state = STATE_WAITING_FOR_BUS;
+ }
+ return 0;
+ }
+
+ @Override
+ public MapBasedMovement replicate() {
+ return new BusTravellerMovement(this);
+ }
+
+ public int getState() {
+ return state;
+ }
+
+ /**
+ * Get the location where the bus is located when it has moved its path
+ * @return The end point of the last path returned
+ */
+ public Coord getLocation() {
+ if (location == null) {
+ return null;
+ }
+ return location.clone();
+ }
+
+ /**
+ * Notifies the node at the bus stop that a bus is there. Nodes inside
+ * busses are also notified.
+ * @param nextPath The next path the bus is going to take
+ */
+ public void enterBus(Path nextPath) {
+
+ if (startBusStop != null && endBusStop != null) {
+ if (location.equals(endBusStop)) {
+ state = STATE_WALKING_ELSEWHERE;
+ latestBusStop = location.clone();
+ } else {
+ state = STATE_DECIDED_TO_ENTER_A_BUS;
+ this.nextPath = nextPath;
+ }
+ return;
+ }
+
+ if (!cbtd.continueTrip()) {
+ state = STATE_WAITING_FOR_BUS;
+ this.nextPath = null;
+ /* It might decide not to start walking somewhere and wait
+ for the next bus */
+ if (rng.nextDouble() > probTakeOtherBus) {
+ state = STATE_WALKING_ELSEWHERE;
+ latestBusStop = location.clone();
+ }
+ } else {
+ state = STATE_DECIDED_TO_ENTER_A_BUS;
+ this.nextPath = nextPath;
+ }
+ }
+
+ public int getID() {
+ return id;
+ }
+
+
+ /**
+ * Small class to help nodes decide if they should continue the bus trip.
+ * Keeps the state of nodes, i.e. how many stops they have passed so far.
+ * Markov chain probabilities for the decisions.
+ *
+ * NOT USED BY THE WORKING DAY MOVEMENT MODEL
+ *
+ * @author Frans Ekman
+ */
+ class ContinueBusTripDecider {
+
+ private double[] probabilities; // Probability to travel with bus
+ private int state;
+ private Random rng;
+
+ public ContinueBusTripDecider(Random rng, double[] probabilities) {
+ this.rng = rng;
+ this.probabilities = probabilities;
+ state = 0;
+ }
+
+ /**
+ *
+ * @return true if node should continue
+ */
+ public boolean continueTrip() {
+ double rand = rng.nextDouble();
+ if (rand < probabilities[state]) {
+ incState();
+ return true;
+ } else {
+ resetState();
+ return false;
+ }
+ }
+
+ /**
+ * Call when a stop has been passed
+ */
+ private void incState() {
+ if (state < probabilities.length - 1) {
+ state++;
+ }
+ }
+
+ /**
+ * Call when node has finished it's trip
+ */
+ private void resetState() {
+ state = 0;
+ }
+ }
+
+ /**
+ * Help method to find the closest coordinate from a list of coordinates,
+ * to a specific location
+ * @param allCoords list of coordinates to compare
+ * @param coord destination node
+ * @return closest to the destination
+ */
+ private static Coord getClosestCoordinate(List allCoords,
+ Coord coord) {
+ Coord closestCoord = null;
+ double minDistance = Double.POSITIVE_INFINITY;
+ for (Coord temp : allCoords) {
+ double distance = temp.distance(coord);
+ if (distance < minDistance) {
+ minDistance = distance;
+ closestCoord = temp;
+ }
+ }
+ return closestCoord.clone();
+ }
+
+ /**
+ * Sets the next route for the traveller, so that it can decide wether it
+ * should take the bus or not.
+ * @param nodeLocation
+ * @param nodeDestination
+ */
+ public void setNextRoute(Coord nodeLocation, Coord nodeDestination) {
+
+ // Find closest stops to current location and destination
+ List allStops = controlSystem.getBusStops();
+
+ Coord closestToNode = getClosestCoordinate(allStops, nodeLocation);
+ Coord closestToDestination = getClosestCoordinate(allStops,
+ nodeDestination);
+
+ // Check if it is shorter to walk than take the bus
+ double directDistance = nodeLocation.distance(nodeDestination);
+ double busDistance = nodeLocation.distance(closestToNode) +
+ nodeDestination.distance(closestToDestination);
+
+ if (directDistance < busDistance) {
+ takeBus = false;
+ } else {
+ takeBus = true;
+ }
+
+ this.startBusStop = closestToNode;
+ this.endBusStop = closestToDestination;
+ this.latestBusStop = startBusStop.clone();
+ }
+
+ /**
+ * @see SwitchableMovement
+ */
+ public Coord getLastLocation() {
+ return location.clone();
+ }
+
+ /**
+ * @see SwitchableMovement
+ */
+ public void setLocation(Coord lastWaypoint) {
+ location = lastWaypoint.clone();
+ }
+
+ /**
+ * @see SwitchableMovement
+ */
+ public boolean isReady() {
+ if (state == STATE_WALKING_ELSEWHERE) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public static void reset() {
+ nextID = 0;
+ }
+
+}
diff --git a/movement/CarMovement.java b/movement/CarMovement.java
new file mode 100644
index 000000000..19518ef33
--- /dev/null
+++ b/movement/CarMovement.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package movement;
+
+import java.util.List;
+
+import movement.map.DijkstraPathFinder;
+import movement.map.MapNode;
+import core.Coord;
+import core.Settings;
+
+/**
+ * The CarMovement class representing the car movement submodel
+ *
+ * @author Frans Ekman
+ */
+public class CarMovement extends MapBasedMovement implements
+ SwitchableMovement, TransportMovement {
+
+ private Coord from;
+ private Coord to;
+
+ private DijkstraPathFinder pathFinder;
+
+ /**
+ * Car movement constructor
+ * @param settings
+ */
+ public CarMovement(Settings settings) {
+ super(settings);
+ pathFinder = new DijkstraPathFinder(getOkMapNodeTypes());
+ }
+
+ /**
+ * Construct a new CarMovement instance from a prototype
+ * @param proto
+ */
+ public CarMovement(CarMovement proto) {
+ super(proto);
+ this.pathFinder = proto.pathFinder;
+ }
+
+ /**
+ * Sets the next route to be taken
+ * @param nodeLocation
+ * @param nodeDestination
+ */
+ public void setNextRoute(Coord nodeLocation, Coord nodeDestination) {
+ from = nodeLocation.clone();
+ to = nodeDestination.clone();
+ }
+
+ @Override
+ public Path getPath() {
+ Path path = new Path(generateSpeed());
+
+ MapNode fromNode = getMap().getNodeByCoord(from);
+ MapNode toNode = getMap().getNodeByCoord(to);
+
+ List nodePath = pathFinder.getShortestPath(fromNode, toNode);
+
+ for (MapNode node : nodePath) { // create a Path from the shortest path
+ path.addWaypoint(node.getLocation());
+ }
+
+ lastMapNode = toNode;
+
+ return path;
+ }
+
+ /**
+ * @see SwitchableMovement
+ * @return true
+ */
+ public boolean isReady() {
+ return true;
+ }
+}
diff --git a/movement/ClusterMovement.java b/movement/ClusterMovement.java
new file mode 100644
index 000000000..c4d666303
--- /dev/null
+++ b/movement/ClusterMovement.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+
+/**
+ * Random waypoint movement where the coordinates are restricted to circular
+ * area defined by a central point and range.
+ * @author teemuk
+ */
+package movement;
+
+import core.Coord;
+import core.Settings;
+
+public class ClusterMovement extends RandomWaypoint {
+ /** Range of the cluster */
+ public static final String CLUSTER_RANGE = "clusterRange";
+ /** Center point of the cluster */
+ public static final String CLUSTER_CENTER = "clusterCenter";
+
+ private int p_x_center = 100, p_y_center = 100;
+ private double p_range = 100.0;
+
+ public ClusterMovement(Settings s) {
+ super(s);
+
+ if (s.contains(CLUSTER_RANGE)){
+ this.p_range = s.getDouble(CLUSTER_RANGE);
+ }
+ if (s.contains(CLUSTER_CENTER)){
+ int[] center = s.getCsvInts(CLUSTER_CENTER,2);
+ this.p_x_center = center[0];
+ this.p_y_center = center[1];
+ }
+ }
+
+ private ClusterMovement(ClusterMovement cmv) {
+ super(cmv);
+ this.p_range = cmv.p_range;
+ this.p_x_center = cmv.p_x_center;
+ this.p_y_center = cmv.p_y_center;
+ }
+
+ @Override
+ protected Coord randomCoord() {
+ double x = (rng.nextDouble()*2 - 1)*this.p_range;
+ double y = (rng.nextDouble()*2 - 1)*this.p_range;
+ while (x*x + y*y>this.p_range*this.p_range) {
+ x = (rng.nextDouble()*2 - 1)*this.p_range;
+ y = (rng.nextDouble()*2 - 1)*this.p_range;
+ }
+ x += this.p_x_center;
+ y += this.p_y_center;
+ return new Coord(x,y);
+ }
+
+ @Override
+ public int getMaxX() {
+ return (int)Math.ceil(this.p_x_center + this.p_range);
+ }
+
+ @Override
+ public int getMaxY() {
+ return (int)Math.ceil(this.p_y_center + this.p_range);
+ }
+
+ @Override
+ public ClusterMovement replicate() {
+ return new ClusterMovement(this);
+ }
+}
diff --git a/movement/EveningActivityControlSystem.java b/movement/EveningActivityControlSystem.java
new file mode 100644
index 000000000..f8fc1042e
--- /dev/null
+++ b/movement/EveningActivityControlSystem.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package movement;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Random;
+
+import core.Coord;
+import core.DTNSim;
+
+/**
+ * This class controls the group mobility of the people meeting their friends in
+ * the evening
+ *
+ * @author Frans Ekman
+ */
+public class EveningActivityControlSystem {
+
+ private HashMap eveningActivityNodes;
+ private List meetingSpots;
+ private EveningTrip[] nextTrips;
+
+ private Random rng;
+
+ private static HashMap
+ controlSystems;
+
+ static {
+ DTNSim.registerForReset(EveningActivityControlSystem.class.
+ getCanonicalName());
+ reset();
+ }
+
+ /**
+ * Creates a new instance of EveningActivityControlSystem without any nodes
+ * or meeting spots, with the ID given as parameter
+ * @param id
+ */
+ private EveningActivityControlSystem(int id) {
+ eveningActivityNodes = new HashMap();
+ }
+
+ public static void reset() {
+ controlSystems = new HashMap();
+ }
+
+ /**
+ * Register a evening activity node with the system
+ * @param eveningMovement activity movement
+ */
+ public void addEveningActivityNode(EveningActivityMovement eveningMovement) {
+ eveningActivityNodes.put(new Integer(eveningMovement.getID()),
+ eveningMovement);
+ }
+
+ /**
+ * Sets the meeting locations the nodes can choose among
+ * @param meetingSpots
+ */
+ public void setMeetingSpots(List meetingSpots) {
+ this.meetingSpots = meetingSpots;
+ this.nextTrips = new EveningTrip[meetingSpots.size()];
+ }
+
+ /**
+ * This method gets the instruction for a node, i.e. When/where and with
+ * whom to go.
+ * @param eveningActivityNodeID unique ID of the node
+ * @return Instructions object
+ */
+ public EveningTrip getEveningInstructions(int eveningActivityNodeID) {
+ EveningActivityMovement eveningMovement = eveningActivityNodes.get(
+ new Integer(eveningActivityNodeID));
+ if (eveningMovement != null) {
+ int index = eveningActivityNodeID % meetingSpots.size();
+ if (nextTrips[index] == null) {
+ int nrOfEveningMovementNodes = (int)(eveningMovement.
+ getMinGroupSize() +
+ (double)(eveningMovement.getMaxGroupSize() -
+ eveningMovement.getMinGroupSize()) *
+ rng.nextDouble());
+ Coord loc = meetingSpots.get(index).clone();
+ nextTrips[index] = new EveningTrip(nrOfEveningMovementNodes,
+ loc);
+ }
+ nextTrips[index].addNode(eveningMovement);
+ if (nextTrips[index].isFull()) {
+ EveningTrip temp = nextTrips[index];
+ nextTrips[index] = null;
+ return temp;
+ } else {
+ return nextTrips[index];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the meeting spot for the node
+ * @param id
+ * @return Coordinates of the spot
+ */
+ public Coord getMeetingSpotForID(int id) {
+ int index = id % meetingSpots.size();
+ Coord loc = meetingSpots.get(index).clone();
+ return loc;
+ }
+
+
+ /**
+ * Sets the random number generator to be used
+ * @param rand
+ */
+ public void setRandomNumberGenerator(Random rand) {
+ this.rng = rand;
+ }
+
+ /**
+ * Returns a reference to a EveningActivityControlSystem with ID provided as
+ * parameter. If a system does not already exist with the requested ID, a
+ * new one is created.
+ * @param id unique ID of the EveningActivityControlSystem
+ * @return The EveningActivityControlSystem with the provided ID
+ */
+ public static EveningActivityControlSystem getEveningActivityControlSystem(
+ int id) {
+ if (controlSystems.containsKey(new Integer(id))) {
+ return controlSystems.get(new Integer(id));
+ } else {
+ EveningActivityControlSystem scs =
+ new EveningActivityControlSystem(id);
+ controlSystems.put(new Integer(id), scs);
+ return scs;
+ }
+ }
+
+}
diff --git a/movement/EveningActivityMovement.java b/movement/EveningActivityMovement.java
new file mode 100644
index 000000000..60fbf6074
--- /dev/null
+++ b/movement/EveningActivityMovement.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package movement;
+
+import input.WKTReader;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+
+import movement.map.DijkstraPathFinder;
+import movement.map.MapNode;
+import movement.map.SimMap;
+import core.Coord;
+import core.Settings;
+
+/**
+ * A Class to model movement when people are out shopping or doing other
+ * activities with friends. If the node happens to be at some other location
+ * than the place where the shopping starts (where it meets its friends), it
+ * first travels to the destination along the shortest path.
+ *
+ * @author Frans Ekman
+ */
+public class EveningActivityMovement extends MapBasedMovement
+ implements SwitchableMovement {
+
+ private static final int WALKING_TO_MEETING_SPOT_MODE = 0;
+ private static final int EVENING_ACTIVITY_MODE = 1;
+
+ public static final String NR_OF_MEETING_SPOTS_SETTING = "nrOfMeetingSpots";
+ public static final String EVENING_ACTIVITY_CONTROL_SYSTEM_NR_SETTING =
+ "shoppingControlSystemNr";
+
+ public static final String MEETING_SPOTS_FILE_SETTING = "meetingSpotsFile";
+
+ public static final String MIN_GROUP_SIZE_SETTING = "minGroupSize";
+ public static final String MAX_GROUP_SIZE_SETTING = "maxGroupSize";
+
+ public static final String MIN_WAIT_TIME_SETTING =
+ "minAfterShoppingStopTime";
+ public static final String MAX_WAIT_TIME_SETTING =
+ "maxAfterShoppingStopTime";
+
+ private static int nrOfMeetingSpots = 10;
+
+ private int mode;
+ private boolean ready;
+ private DijkstraPathFinder pathFinder;
+
+ private Coord lastWaypoint;
+ private Coord startAtLocation;
+
+ private EveningActivityControlSystem scs;
+ private EveningTrip trip;
+
+ private boolean readyToShop;
+
+ private int id;
+
+ private static int nextID = 0;
+
+ private int minGroupSize;
+ private int maxGroupSize;
+
+ /**
+ * Creates a new instance of EveningActivityMovement
+ * @param settings
+ */
+ public EveningActivityMovement(Settings settings) {
+ super(settings);
+ super.backAllowed = false;
+ pathFinder = new DijkstraPathFinder(null);
+ mode = WALKING_TO_MEETING_SPOT_MODE;
+
+ nrOfMeetingSpots = settings.getInt(NR_OF_MEETING_SPOTS_SETTING);
+
+ minGroupSize = settings.getInt(MIN_GROUP_SIZE_SETTING);
+ maxGroupSize = settings.getInt(MAX_GROUP_SIZE_SETTING);
+
+ MapNode[] mapNodes = (MapNode[])getMap().getNodes().
+ toArray(new MapNode[0]);
+
+ String shoppingSpotsFile = null;
+ try {
+ shoppingSpotsFile = settings.getSetting(MEETING_SPOTS_FILE_SETTING);
+ } catch (Throwable t) {
+ // Do nothing;
+ }
+
+ List meetingSpotLocations = null;
+
+ if (shoppingSpotsFile == null) {
+ meetingSpotLocations = new LinkedList();
+ for (int i=0; i();
+ List locationsRead = (new WKTReader()).readPoints(
+ new File(shoppingSpotsFile));
+ for (Coord coord : locationsRead) {
+ SimMap map = getMap();
+ Coord offset = map.getOffset();
+ // mirror points if map data is mirrored
+ if (map.isMirrored()) {
+ coord.setLocation(coord.getX(), -coord.getY());
+ }
+ coord.translate(offset.getX(), offset.getY());
+ meetingSpotLocations.add(coord);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ this.id = nextID++;
+
+ int scsID = settings.getInt(EVENING_ACTIVITY_CONTROL_SYSTEM_NR_SETTING);
+
+ scs = EveningActivityControlSystem.getEveningActivityControlSystem(scsID);
+ scs.setRandomNumberGenerator(rng);
+ scs.addEveningActivityNode(this);
+ scs.setMeetingSpots(meetingSpotLocations);
+
+ maxPathLength = 100;
+ minPathLength = 10;
+
+ maxWaitTime = settings.getInt(MAX_WAIT_TIME_SETTING);
+ minWaitTime = settings.getInt(MIN_WAIT_TIME_SETTING);
+ }
+
+ /**
+ * Creates a new instance of EveningActivityMovement from a prototype
+ * @param proto
+ */
+ public EveningActivityMovement(EveningActivityMovement proto) {
+ super(proto);
+ this.pathFinder = proto.pathFinder;
+ this.mode = proto.mode;
+ this.id = nextID++;
+ scs = proto.scs;
+ scs.addEveningActivityNode(this);
+ this.setMinGroupSize(proto.getMinGroupSize());
+ this.setMaxGroupSize(proto.getMaxGroupSize());
+ }
+
+ /**
+ * @return Unique ID of the shopper
+ */
+ public int getID() {
+ return this.id;
+ }
+
+ @Override
+ public Coord getInitialLocation() {
+
+ MapNode[] mapNodes = (MapNode[])getMap().getNodes().
+ toArray(new MapNode[0]);
+ int index = rng.nextInt(mapNodes.length - 1);
+ lastWaypoint = mapNodes[index].getLocation().clone();
+ return lastWaypoint.clone();
+ }
+
+ @Override
+ public Path getPath() {
+ if (mode == WALKING_TO_MEETING_SPOT_MODE) {
+ // Try to find to the shopping center
+ SimMap map = super.getMap();
+ if (map == null) {
+ return null;
+ }
+ MapNode thisNode = map.getNodeByCoord(lastWaypoint);
+ MapNode destinationNode = map.getNodeByCoord(startAtLocation);
+
+ List nodes = pathFinder.getShortestPath(thisNode,
+ destinationNode);
+ Path path = new Path(generateSpeed());
+ for (MapNode node : nodes) {
+ path.addWaypoint(node.getLocation());
+ }
+ lastWaypoint = startAtLocation.clone();
+ mode = EVENING_ACTIVITY_MODE;
+ return path;
+ } else if (mode == EVENING_ACTIVITY_MODE) {
+ readyToShop = true;
+ if (trip.allMembersPresent()) {
+ Path path = trip.getPath();
+ if (path == null) {
+ super.lastMapNode = super.getMap().
+ getNodeByCoord(lastWaypoint);
+ path = super.getPath(); // TODO Create levy walk path
+ lastWaypoint = super.lastMapNode.getLocation();
+ trip.setPath(path);
+ double waitTimeAtEnd = (maxWaitTime - minWaitTime) *
+ rng.nextDouble() + minWaitTime;
+ trip.setWaitTimeAtEnd(waitTimeAtEnd);
+ trip.setDestination(lastWaypoint);
+ }
+ lastWaypoint = trip.getDestination();
+ ready = true;
+ return path;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ protected double generateWaitTime() {
+ if (ready) {
+ double wait = trip.getWaitTimeAtEnd();
+ return wait;
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public MapBasedMovement replicate() {
+ return new EveningActivityMovement(this);
+ }
+
+ /**
+ * @see SwitchableMovement
+ */
+ public Coord getLastLocation() {
+ return lastWaypoint.clone();
+ }
+
+ /**
+ * @see SwitchableMovement
+ */
+ public boolean isReady() {
+ return ready;
+ }
+
+ /**
+ * @see SwitchableMovement
+ */
+ public void setLocation(Coord lastWaypoint) {
+ this.lastWaypoint = lastWaypoint.clone();
+ ready = false;
+ mode = WALKING_TO_MEETING_SPOT_MODE;
+ }
+
+ /**
+ * Sets the node ready to start a shopping trip.
+ * @return The coordinate of the place where the shopping trip starts
+ */
+ public Coord getShoppingLocationAndGetReady() {
+ readyToShop = false; // New shopping round starts
+ trip = scs.getEveningInstructions(id);
+ startAtLocation = trip.getLocation().clone();
+ return startAtLocation.clone();
+ }
+
+
+ public Coord getShoppingLocation() {
+ return scs.getMeetingSpotForID(id).clone();
+ }
+
+
+ /**
+ * Checks if a node is at the correct place where the shopping begins
+ * @return true if node is ready and waiting for the rest of the group to
+ * arrive
+ */
+ public boolean isReadyToShop() {
+ return readyToShop;
+ }
+
+ public static void reset() {
+ nextID = 0;
+ }
+
+ public int getMinGroupSize() {
+ return minGroupSize;
+ }
+
+ public void setMinGroupSize(int minGroupSize) {
+ this.minGroupSize = minGroupSize;
+ }
+
+ public int getMaxGroupSize() {
+ return maxGroupSize;
+ }
+
+ public void setMaxGroupSize(int maxGroupSize) {
+ this.maxGroupSize = maxGroupSize;
+ }
+
+}
diff --git a/movement/EveningTrip.java b/movement/EveningTrip.java
new file mode 100644
index 000000000..8e34e3825
--- /dev/null
+++ b/movement/EveningTrip.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2010 Aalto University, ComNet
+ * Released under GPLv3. See LICENSE.txt for details.
+ */
+package movement;
+
+import core.Coord;
+
+/**
+ * A class to encapsulate information about a shopping trip
+ * 1. Where the trip begins
+ * 2. Where it ends
+ * 3. The path
+ * 4. All nodes in the group
+ *
+ * @author Frans Ekman
+ */
+public class EveningTrip {
+ private EveningActivityMovement[] eveningActivityNodes;
+ private int eveningActivityNodesInBuffer;
+ private Path path;
+ private Coord location;
+ private Coord destination;
+ private double waitTimeAtEnd;
+
+ /**
+ * Create a new instance of a EveningTrip
+ * @param nrOfeveningActivityNodes The number of shoppers in the group
+ * @param location Where the trip starts
+ */
+ public EveningTrip(int nrOfeveningActivityNodes, Coord location) {
+ eveningActivityNodes =
+ new EveningActivityMovement[nrOfeveningActivityNodes];
+ this.location = location;
+ eveningActivityNodesInBuffer = 0;
+ }
+
+ /**
+ * Add an evening activity node to the group
+ * @param eveningActivityNode
+ * @return true if there was room in the group
+ */
+ public boolean addNode(EveningActivityMovement eveningActivityNode) {
+ if (isFull()) {
+ return false;
+ } else {
+ eveningActivityNodes[eveningActivityNodesInBuffer] =
+ eveningActivityNode;
+ eveningActivityNodesInBuffer++;
+ return true;
+ }
+ }
+
+ /**
+ * Sets the shopping path for the group
+ * @param path
+ */
+ public void setPath(Path path) {
+ this.path = new Path(path);
+ }
+
+ /**
+ * @return The shopping trip path
+ */
+ public Path getPath() {
+ if (path != null) {
+ return new Path(this.path);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @return The location where the shopping trip starts
+ */
+ public Coord getLocation() {
+ return location;
+ }
+
+ /**
+ * @return true if the group is full
+ */
+ public boolean isFull() {
+ return eveningActivityNodesInBuffer >= eveningActivityNodes.length;
+ }
+
+ /**
+ * Checks if all members of the group have found their way to the meeting
+ * point
+ * @return true if all nodes are there
+ */
+ public boolean allMembersPresent() {
+ if (!isFull()) {
+ return false;
+ }
+ for (int i=0; i idMapping;
+ /** initial locations for nodes */
+ private static List> initLocations;
+ /** time of the very first location data */
+ private static double initTime;
+ /** sampling interval (seconds) of the location data */
+ private static double samplingInterval;
+ /** last read time stamp after preloading */
+ private static double lastPreloadTime;
+ /** how many time intervals to load on every preload run */
+ private static double nrofPreload = 10;
+ /** minimum number intervals that should be preloaded ahead of sim time */
+ private static final double MIN_AHEAD_INTERVALS = 2;
+
+ /** the very first location of the node */
+ private Coord intialLocation;
+ /** queue of path-start-time, path tuples */
+ private Queue