// (C) Uri Wilensky. https://github.com/NetLogo/NetLogo
package org.nlogo.agent;
import org.nlogo.api.AgentException;
import org.nlogo.api.AgentVariables;
import org.nlogo.api.ImporterUser;
import org.nlogo.api.Perspective;
import org.nlogo.api.PlotInterface;
import org.nlogo.api.PlotPenInterface;
import org.nlogo.api.WorldDimensions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public strictfp class Importer
implements org.nlogo.api.ImportErrorHandler {
final ImporterUser importerUser;
final ErrorHandler errorHandler;
final World world;
final StringReader stringReader;
Set<Object> shapesNotToImport;
Set<String> breedsNotToImport;
List<String> someBreedOwns;
List<String> someLinkBreedOwns;
static final String SCREEN_EDGE_X_HEADER = "SCREEN-EDGE-X";
static final String SCREEN_EDGE_Y_HEADER = "SCREEN-EDGE-Y";
static final String MIN_PXCOR_HEADER = "MIN-PXCOR";
static final String MAX_PXCOR_HEADER = "MAX-PXCOR";
static final String MIN_PYCOR_HEADER = "MIN-PYCOR";
static final String MAX_PYCOR_HEADER = "MAX-PYCOR";
static final String PERSPECTIVE_HEADER = "PERSPECTIVE";
static final String SUBJECT_HEADER = "SUBJECT";
static final String NEXT_INDEX_HEADER = "NEXTINDEX";
static final String DIRECTED_LINKS_HEADER = "DIRECTED-LINKS";
static final String TICKS_HEADER = "TICKS";
boolean needToResize = false;
private boolean olderThan40beta2 = false;
//these variables, varsToImport, builtInVars, and tooManyValuesForSection,
//are initialized each time importAgents() is called. the variables are
//reused for each of the three types of agents. they should be global since
//they are passed around a lot and it makes the code more general by not
//forcing us to have different variables for each agent type when the
//reusing them will suffice.
//--mag 12/13/01, 5/2/03
java.util.BitSet varsToImport;
String[] builtInVars;
boolean tooManyValuesForSection = false;
boolean convertPenDown = false;
boolean convertTopology = false;
boolean importLinks = true;
int TURTLE_BREED;
void setupVarsToImport(int size) {
varsToImport = new java.util.BitSet(size);
for (int j = 0; j < size; j++) {
varsToImport.set(j);
}
}
public Importer(Importer.ErrorHandler errorHandler, World world, ImporterUser importerUser,
Importer.StringReader stringReader) {
this.errorHandler = errorHandler;
this.world = world;
this.importerUser = importerUser;
this.stringReader = stringReader;
TURTLE_BREED = Turtle.VAR_BREED;
shapesNotToImport = new HashSet<Object>();
breedsNotToImport = new HashSet<String>();
someBreedOwns = getAllBreedVars();
someLinkBreedOwns = getAllLinkBreedVars();
specialVariables = fillSpecialVariables();
//essentialVarHeadersToImport = fillEssentialVarsToImport() ;
}
void checkVersion(String versionNumber)
throws AbortingImportException {
if (versionNumber.startsWith("1.") ||
versionNumber.startsWith("2.0") ||
versionNumber.startsWith("2.1") ||
versionNumber.startsWith("2.2pre1") ||
versionNumber.startsWith("2.2pre2")) {
convertPenDown = true;
convertTopology = true;
importLinks = false;
} else if (versionNumber.startsWith("3.0")) {
convertTopology = true;
importLinks = false;
}
if (versionNumber.startsWith("1.") ||
versionNumber.startsWith("2.") ||
versionNumber.startsWith("3.")) {
importLinks = false;
}
if (versionNumber.startsWith("1.") ||
versionNumber.startsWith("2.") ||
versionNumber.startsWith("3.") ||
versionNumber.startsWith("4.0pre") ||
versionNumber.startsWith("4.0alpha") ||
versionNumber.startsWith("4.0beta1")) {
olderThan40beta2 = true;
}
}
public void importWorld(java.io.BufferedReader fileBuff)
throws java.io.IOException {
lines = fileBuff;
try {
while (hasMoreLines(false)) {
String[] line = nextLine();
String versionHeader = "export-world data (NetLogo ";
if (line[0].startsWith(versionHeader)) {
String versionNumber = line[0].substring(versionHeader.length());
checkVersion(versionNumber);
}
if (line[0].trim().equals("RANDOM STATE")) {
hasMoreLines(false);
line = nextLine();
world.mainRNG.load(line[0]);
}
}
essentialVarHeadersToImport = fillEssentialVarsToImport();
world.clearAll();
importAgents(Observer.class);
importAgents(Turtle.class);
importAgents(Patch.class);
checkForBlankTurtles();
if (importLinks) {
importAgents(Link.class);
}
if (nextLine != null && nextLine.indexOf("DRAWING") != -1) {
importDrawing();
}
if (nextLine != null && nextLine.indexOf("OUTPUT") != -1) {
importOutputArea();
}
if (needToResize) {
importerUser.resizeView();
}
importPlots();
importExtensionData();
} catch (AbortingImportException aix) {
world.clearAll();
if (aix.errorType != ImportError.ERROR_GIVEN) {
showError(aix);
}
} catch (InvalidDataException e) {
errorHandler.showError("Error Importing Drawing",
"Invalid data length, the drawing will not be imported", false);
}
}
void importPlots()
throws java.io.IOException {
if (hasMoreLines(false)) {
String[] line = nextLine();
String currentPlot = line[0];
if (currentPlot.length() > 0) {
importerUser.currentPlot(currentPlot);
}
while (hasMoreLines(false)) {
line = nextLine();
try {
String plotName = (String) getTokenValue(line[0], false, false);
PlotInterface plot = importerUser.getPlot(plotName);
if (plot == null) {
errorHandler.showError("Error Importing Plots",
"The plot \"" + plotName + "\" does not exist.",
false);
// gobble up remaining lines of this section
while (hasMoreLines(false)) { } // NOPMD empty while loop OK
return;
} else {
int numPens = importIntro(plot);
importPens(plot, numPens);
importPoints(plot);
}
plot.makeDirty();
} catch (ClassCastException e) {
throw new AbortingImportException
(ImportError.ILLEGAL_CLASS_CAST_ERROR, "");
}
}
}
}
int importIntro(PlotInterface plot)
throws java.io.IOException {
// this is the header line and we don't really care about it since
// we have to set everything by hand anyway.
if (hasMoreLines(false) &&
hasMoreLines(false)) {
String[] line = nextLine();
plot.xMin_$eq(readNumber(line[0]));
plot.xMax_$eq(readNumber(line[1]));
plot.yMin_$eq(readNumber(line[2]));
plot.yMax_$eq(readNumber(line[3]));
plot.autoPlotOn_$eq(readBoolean(line[4]));
plot.currentPen_$eq(readString(line[5]));
plot.legendIsOpen_$eq(readBoolean(line[6]));
return (int) readNumber(line[7]);
}
return 0;
}
void importPens(PlotInterface plot, int numPens)
throws java.io.IOException {
if (hasMoreLines(false)) {
for (int i = 0; i < numPens; i++) {
if (hasMoreLines(false)) {
String[] line = nextLine();
Object value = getTokenValue(line[0], false, false);
if (value instanceof Junk) {
return;
}
scala.Option<PlotPenInterface> penMaybe = plot.getPen((String) value);
if (penMaybe.isDefined()) {
PlotPenInterface pen = penMaybe.get();
pen.isDown_$eq(readBoolean(line[1]));
pen.mode_$eq((int) readNumber(line[2]));
pen.interval_$eq(readNumber(line[3]));
pen.color_$eq(org.nlogo.api.Color.getARGBbyPremodulatedColorNumber
(readNumber(line[4])));
pen.x_$eq(readNumber(line[5]));
} else {
errorHandler.showError("Error Importing Plots",
"The pen \"" + value + "\" does not exist.", false);
while (hasMoreLines(false)) {
nextLine();
}
}
}
}
}
}
void importPoints(PlotInterface plot)
throws java.io.IOException {
if (hasMoreLines(false)) {
String[] line = nextLine();
String[] pens = new String[((line.length - 1) / 4) + 1];
for (int i = 0; i < pens.length; i++) {
pens[i] = readString(line[i * 4]);
}
if (hasMoreLines(false)) {
while (hasMoreLines(true)) {
String[] data = nextLine();
for (int i = 0; i < pens.length; i++) {
scala.Option<PlotPenInterface> penMaybe = plot.getPen(pens[i]);
if (penMaybe.isDefined()) {
PlotPenInterface pen = penMaybe.get();
// there may be blank fields in the list of points
// since some pens may have more points than others.
if (data[i * 4].length() > 0) {
try {
pen.plot(readNumber(data[i * 4]),
readNumber(data[i * 4 + 1]),
org.nlogo.api.Color.getARGBbyPremodulatedColorNumber
((int) readNumber(data[i * 4 + 2])),
readBoolean(data[i * 4 + 3]));
} catch (ClassCastException e) {
errorHandler.showError("Import Error",
"Error while importing " + plot.name() +
", this point will be skipped.", false);
}
}
} else {
errorHandler.showError("Error Importing Plots",
"The pen \"" + pens[i] + "\" does not exist.", false);
}
}
}
}
}
}
private double readNumber(String line) {
Object value = getTokenValue(line, false, false);
if (!(value instanceof Junk)) {
return ((Double) value).doubleValue();
}
return 0;
}
private boolean readBoolean(String line) {
Object value = getTokenValue(line, false, false);
if (!(value instanceof Junk)) {
return ((Boolean) value).booleanValue();
}
return false;
}
private String readString(String line) {
Object value = getTokenValue(line, false, false);
if (!(value instanceof Junk)) {
return (String) value;
}
return null;
}
void importOutputArea()
throws java.io.IOException {
StringBuilder outputString = new StringBuilder();
while (hasMoreLines(false)) {
String[] fields = nextLine();
for (int i = 0; i < fields.length; i++) {
outputString.append(fields[i]);
}
}
if (outputString.length() > 0) {
importerUser.setOutputAreaContents((String) getTokenValue(outputString.toString(), false, false));
} else {
// blank the output area
importerUser.setOutputAreaContents("");
}
}
void importDrawing()
throws java.io.IOException {
if (hasMoreLines(false)) {
Double patchSize = Double.valueOf(nextLine()[0]);
importerUser.patchSize(patchSize.doubleValue());
importerUser.resizeView();
needToResize = false;
int width = (int) (patchSize.doubleValue() * world.worldWidth());
int height = (int) (patchSize.doubleValue() * world.worldHeight());
StringBuilder colorString = new StringBuilder(width * height * 32);
try {
while (hasMoreLines(false)) {
String[] line = nextLine();
for (int i = 0; i < line.length; i++) {
colorString.append(stringReader.readFromString(line[i].replaceAll(",", "")));
}
}
int[] colors = fromHexString(colorString.toString());
if (colors.length != (width * height * 4)) {
throw new InvalidDataException
("The data was not the correct length for the size of the world");
}
world.trailDrawer.setColors(colors);
} catch (StringReaderException e) {
throw new InvalidDataException
("invalid drawing data: drawing will not be imported");
}
}
}
public void importExtensionData() {
try {
if (hasMoreLines(false)) {
String[] line = nextLine();
String extensionName = line[0];
while (hasMoreLines(false)) {
List<String[]> lines = new ArrayList<String[]>();
do {
line = nextLine();
if (importerUser.isExtensionName(line[0])) {
break;
}
lines.add(line);
} while (hasMoreLines(false));
importerUser.importExtensionData(extensionName, lines, this);
extensionName = line[0];
}
}
} catch (org.nlogo.api.ExtensionException e) {
errorHandler.showError("Error Importing Extension Data", e.getMessage(), false);
} catch (java.io.IOException e) {
errorHandler.showError("Error Importing Extension Data", e.getMessage(), false);
}
}
void importAgents(Class<? extends Agent> agentClass)
throws java.io.IOException {
tooManyValuesForSection = false;
builtInVars = getImplicitVariables(agentClass);
String[] headers = getHeaders(agentClass);
setupVarsToImport(headers.length);
while (hasMoreLines(false)) {
String[] line = nextLine();
importOneAgent(agentClass, line, headers);
}
}
void importOneAgent(Class<? extends Agent> agentClass, String[] line, String[] headers) {
Map<String, Object> varVals = getVarVals(headers, line, agentClass);
if (agentClass == Observer.class) {
setScreenDimensions(varVals);
}
// if there were any agentsets in the values that getVarVals() fetched,
// then those values may have become invalid as a result of resizing
// the world, so we'd better call getVarVals over again - ST 12/21/04
varVals = getVarVals(headers, line, agentClass);
Agent agent = nextAgent(agentClass, varVals);
for (int i = 0; i < headers.length; i++) {
String header = headers[i];
if (isSpecialVariable(agentClass, header)) // breed, sex/sey, pxcor/pycor, label/plabel, who, others?
{
handleSpecialVariable(agent, header, varVals, i);
} else {
int varIndex = getVarIndex(agent, header, i);
if (varIndex != -1) {
Object value = varVals.get(header);
if (value != null) {
setVarVal(agent, varIndex, header, value);
}
}
// otherwise leave default value alone
}
}
}
Agent nextAgent(Class<? extends Agent> agentClass, Map<String, Object> varVals) {
if (agentClass == Observer.class) {
return world.observer();
}
if (agentClass == Turtle.class) {
// don't use agent.setVariable() for the turtles' id and breed since
// these are needed in order to create the right number and types of
// variables for the turtle. --mag 3/25/03
AgentSet breed =
getTurtleBreed(varVals,
builtInVars[TURTLE_BREED]);
long id = getTurtleId(varVals, builtInVars[Turtle.VAR_WHO]);
Turtle turtle = world.getOrCreateTurtle(id);
turtle.setBreed(breed);
return turtle;
}
if (agentClass == Patch.class) {
// don't use agent.setVariable() for the patches' pxcor and pycor since
// these are needed in order to create the right patch. --mag 3/25/03
return getPatch(varVals);
}
if (agentClass == Link.class) {
AgentSet breed =
getLinkBreed(varVals,
builtInVars[Link.VAR_BREED]);
Turtle end1 = getLinkEnd(varVals, builtInVars[Link.VAR_END1]);
Turtle end2 = getLinkEnd(varVals, builtInVars[Link.VAR_END2]);
return world.getOrCreateLink(end1, end2, breed);
}
// there are no other agents
return null;
}
void handleSpecialVariable(Agent agent, String header, Map<String, Object> varVals, int headerIndex) {
try {
if (!(agent instanceof Observer)) {
int varIndex = getVarIndex(agent, header, headerIndex);
if (varIndex != -1) {
if (agent instanceof Turtle) {
handleSpecialTurtleVariable((Turtle) agent, varVals.get(header), varIndex);
} else if (agent instanceof Patch) {
handleSpecialPatchVariable((Patch) agent, varVals.get(header), varIndex);
} else if (agent instanceof Link) {
handleSpecialLinkVariable((Link) agent, varVals.get(header), varIndex, header);
}
}
} else {
handleSpecialObserverVariable((Observer) agent, varVals.get(header), header);
}
} catch (ImportException ix) {
showError(ix);
}
}
void handleSpecialObserverVariable(Observer observer, Object val, String header) {
if (header.equals(PERSPECTIVE_HEADER)) {
observer.perspective(Perspective.load(((Double) val).intValue()));
} else if (header.equals(SUBJECT_HEADER) && val instanceof Agent) {
observer.targetAgent((Agent) val);
} else if (header.equals(NEXT_INDEX_HEADER)) {
world.nextTurtleIndex(((Double) val).longValue());
} else if (header.equals(DIRECTED_LINKS_HEADER)) {
String str = (String) val;
if (!str.equals("NEITHER")) {
world.links().setDirected(str.equals("DIRECTED"));
}
} else if (header.equals(TICKS_HEADER)) {
world.tickCounter.ticks_$eq(((Double) val).doubleValue());
}
}
void handleSpecialTurtleVariable(Turtle turtle, Object val, int varIndex) {
switch (varIndex) {
case Turtle.VAR_SHAPE:
setTurtleShape(turtle, val, builtInVars[Turtle.VAR_SHAPE], varIndex);
break;
case Turtle.VAR_LABEL:
setVarVal(turtle, varIndex, builtInVars[Turtle.VAR_LABEL], getLabel(val));
break;
case Turtle.VAR_WHO:
case Turtle.VAR_BREED:
//do nothing since we already took care of them
break;
default:
throw new IllegalStateException();
}
}
void handleSpecialLinkVariable(Link link, Object val, int varIndex, String header) {
switch (varIndex) {
case Link.VAR_LABEL:
setVarVal(link, varIndex, builtInVars[Link.VAR_LABEL], getLabel(val));
break;
case Link.VAR_END1:
case Link.VAR_END2:
case Link.VAR_BREED:
break;
default:
throw new IllegalStateException();
}
}
void handleSpecialPatchVariable(Patch patch, Object val, int varIndex) {
switch (varIndex) {
case Patch.VAR_PLABEL:
setVarVal(patch, varIndex, builtInVars[Patch.VAR_PLABEL], getLabel(val));
break;
case Patch.VAR_PXCOR:
case Patch.VAR_PYCOR:
//do nothing since we already took care of them
break;
default:
throw new IllegalStateException();
}
}
//
// utility functions:
//
//getting and setting variables
//given the set of lines of the file, the delimiter for the values in a line, and the type of agent
//(or Globals for the observer) for these headers,
//this will return an array of uppercase strings containing the headers to be imported for this agent
String[] getHeaders(Class<? extends Agent> agentClass)
throws java.io.IOException {
if (!hasMoreLines(false)) {
String abortingError = "No " + printName(agentClass) + " headers have been imported. " +
"Globals, Turtles, and Patches must be in the same import file.";
throw new AbortingImportException
(ImportError.UNEXPECTED_EOF_ERROR, abortingError);
}
String[] mixedCaseHeaders = nextLine();
List<String> headers = new ArrayList<String>();
for (int i = 0; i < mixedCaseHeaders.length; i++) {
// ignore blank fields in headers
if (!mixedCaseHeaders[i].trim().equals("")) {
if (convertPenDown && mixedCaseHeaders[i].equalsIgnoreCase("PEN-DOWN?")) {
headers.add("PEN-MODE");
} else {
headers.add(mixedCaseHeaders[i].toUpperCase());
}
}
}
String[] headersArr = headers.toArray(new String[headers.size()]);
varHeadersImported(agentClass, headersArr, true);
varHeadersImported(agentClass, headersArr, false);
return headersArr;
}
// don't ask the user about these headers when we're
// importing from an old export file
// we know they are not there and should be set to a default
List<String> getOptionalHeaders(Class<? extends Agent> agentClass) {
if (convertPenDown && agentClass == Turtle.class) {
return Arrays.asList(new String[]
{"PEN-SIZE", "PEN-COLOR"});
}
if (olderThan40beta2 && agentClass == Link.class) {
return Arrays.asList(new String[]
{"SHAPE", "TIE-MODE"});
} else if (agentClass == Link.class) {
return Arrays.asList(new String[]
{"TIE-MODE"});
}
return null;
}
//given an array of headers, a comma-delimited string of the agent's values
//for each header, and the agent's class, this will return a map of
//the parsed values keyed by the variable header. if an essential variable
//for this agent type does not have a valid value, this will throw an
//abortingexception
Map<String, Object> getVarVals(String[] headersArr, String[] values, Class<? extends Agent> agentClass) {
Map<String, Object> varVals = new HashMap<String, Object>();
if (!tooManyValuesForSection && values.length > headersArr.length) {
// Only warn the user if one of the extra values is non empty
for (int i = headersArr.length; i < values.length; i++) {
if (!values[i].equals("")) {
tooManyValuesForSection = true;
showError(new ImportException(
ImportError.TOO_MANY_VALUES_ERROR,
"Too Many Values For Agent",
"There are a total of " + headersArr.length + " "
+ printName(agentClass) + " variables declared in this "
+ "model (including built-in " +
((agentClass == Turtle.class || agentClass == Link.class)
? "and breed " : "")
+ "variables). The import-world file has at least one agent "
+ "in the " + printSectionName()
+ " section with more than this number of values.",
"All the extra values will be ignored for this section."));
}
}
}
for (int i = 0; i < headersArr.length; i++) {
// handle turtle breeds specially so that we can give a different
// error message if something there is an error
boolean turtleBreedVar =
(agentClass == Turtle.class) && (headersArr[i].equals(builtInVars[TURTLE_BREED]));
boolean linkBreedVar =
(agentClass == Link.class) && (headersArr[i].equals
(builtInVars[Link.VAR_BREED]));
if (convertPenDown && headersArr[i].equals("PEN-MODE")) {
if (values[i].equalsIgnoreCase("FALSE")) {
values[i] = "\"up\"";
} else if (values[i].equals("TRUE")) {
values[i] = "\"down\"";
}
}
Object value = values[i].equals("") ? (Object) new Junk() :
getTokenValue(values[i], turtleBreedVar, linkBreedVar);
// check to see if this variable is an essential variable and if it is,
// that we were actually able to get a valid value for it.
if (essentialVarHeadersToImport.get(agentClass).contains(headersArr[i])
&& (value instanceof Junk)) {
String abortingError = "A " + printName(agentClass) +
" with the essential variable " + headersArr[i] +
" cannot be imported since the agent's value in the import" +
" file for " + headersArr[i] + " could not be imported.";
throw new AbortingImportException
(ImportError.UNIMPORTED_ESSENTIAL_VAR_ERROR, abortingError);
}
varVals.put(headersArr[i], value);
}
return varVals;
}
boolean validBreed(String breed) {
return (world.getBreed(breed.toUpperCase()) != null)
|| breed.equalsIgnoreCase("TURTLES")
|| breed.equalsIgnoreCase("PATCHES")
|| breed.equalsIgnoreCase("LINKS");
}
// if we have troubles parsing the value and the user doesn't care, return
// an instance of Junk. we will replace it later in the import with an
// appropriate value.
// default access for unit testing
Object getTokenValue(String valueString, boolean turtleBreedVar, boolean linkBreedVar) {
try {
return stringReader.readFromString(valueString);
} catch (StringReaderException ex) {
// this is where we should look for ExtensionTypes that
// can handle this value -- CLB
if (turtleBreedVar) {
if (!breedsNotToImport.contains(valueString)) {
breedsNotToImport.add(valueString);
showError(
new ImportException(
ImportError.ILLEGAL_BREED_ERROR,
"Illegal Breed",
ex.getMessage(),
"all turtles with this breed will be made as regular turtles"));
}
return world.turtles();
} else if (linkBreedVar) {
if (!breedsNotToImport.contains(valueString)) {
breedsNotToImport.add(valueString);
showError(
new ImportException
(ImportError.ILLEGAL_BREED_ERROR,
"Illegal Link Breed",
ex.getMessage(),
"all links with this breed will be made as regular links"));
}
return world.links();
} else {
showError(
new ImportException(
ImportError.PARSING_ERROR,
"Parsing Error",
"error parsing the values:\n" + valueString,
"the import will continue if it can, but values for this " +
"agent's variables will be set to an appropriate default",
ex.getMessage()));
}
}
return new Junk();
}
//given an agent, a header of the variable being imported, and the index of that header in the headers array
//this will return the index to the variables array of the variable at headerIndex in the import file
//or it will return -1 if the variable at headerIndex is not in this agent's set of variables
int getVarIndex(Agent agent, String header, int headerIndex) {
Class<? extends Agent> agentClass = agent.getAgentClass();
int varIndex = Arrays.asList(builtInVars).indexOf(header);
String agentType = printName(agentClass);
//check to see if the variable is a built-in variable agent variable
if (varIndex == -1) {
if (agentClass == Observer.class) {
varIndex = world.observerOwnsIndexOf(header);
} else if (agentClass == Patch.class) {
varIndex = world.patchesOwnIndexOf(header);
} else if (agentClass == Turtle.class) {
varIndex = world.turtlesOwnIndexOf(header);
//check to see if the variable is a turtlesOwn variable
if (varIndex == -1) {
varIndex = getBreedVarIndex((Turtle) agent, header);
if (varIndex == -1 && someBreedOwns.contains(header)) {
// return -1 since some breed in the model owns this var
return -1;
}
}
} else if (agentClass == Link.class) {
varIndex = world.linksOwnIndexOf(header);
if (varIndex == -1) {
varIndex = getLinkBreedVarIndex((Link) agent, header);
if (varIndex == -1 && someLinkBreedOwns.contains(header)) {
// return -1 since some breed in the model owns this var
return -1;
}
}
}
}
//if none do, check to see if we are still importing this variable.
//if we are, throw an error.
if ((varsToImport.get(headerIndex)) && (varIndex == -1)) {
varsToImport.clear(headerIndex);
showError(
new ImportException(
ImportError.ILLEGAL_AGENT_VAR_ERROR,
"Illegal " + agentType + " Variable",
"the " + agentType + " variable " + header + " does not " +
"exist in this model.",
"the import will continue but this variable will be ignored."));
}
return varIndex;
}
//given a turtle, the header for the variable being imported, and the index to the header in the headers array
//this will return the index to the location in the variables array for this breed variable
//or it will return -1 if it is not this turtle's breeds variable
int getBreedVarIndex(Turtle turtle, String header) {
//check to see if this turtle is a breed or not and check to see if this turtle's breed
//has this variable in it
if (turtle.getBreed() != world.turtles() && world.breedOwns(turtle.getBreed(), header)) {
//if it does, get the index for it and return it
return world.breedsOwnIndexOf(turtle.getBreed(), header);
}
//since this variable is not in this turtle's set of variables, return -1
return -1;
}
int getLinkBreedVarIndex(Link link, String header) {
if (link.getBreed() != world.links() && world.linkBreedOwns(link.getBreed(), header)) {
return world.linkBreedsOwnIndexOf(link.getBreed(), header);
}
return -1;
}
void setVarVal(Agent agent, int index, String header, Object value) {
try {
if (value instanceof Junk) {
value = World.ZERO;
}
agent.setVariable(index, value);
} catch (AgentException ae) {
showError(
new ImportException(
ImportError.SETTING_VAR_ERROR,
"Error Setting Value",
"could not set " + agent + "'s variable " + header +
" to " + value,
"the import will continue, but the variable will be set " +
"to an appropriate default."));
} catch (org.nlogo.api.LogoException ae) {
showError(
new ImportException(
ImportError.SETTING_VAR_ERROR,
"Error Setting Value",
"could not set " + agent + "'s variable " + header +
" to " + value,
"the import will continue, but the variable will be set " +
"to an appropriate default."));
}
}
//functions for special handling of variables
Patch getPatch(Map<String, Object> varVals) {
try {
int pxcor = ((Double) varVals.get(builtInVars[Patch.VAR_PXCOR])).intValue();
int pycor = ((Double) varVals.get(builtInVars[Patch.VAR_PYCOR])).intValue();
if (!world.validPatchCoordinates(pxcor, pycor)) {
String abortingError = "Illegal Patch Coordinate- pxcor and pycor must be in range.";
throw new AbortingImportException
(ImportError.ILLEGAL_PCOR_ERROR, abortingError);
}
return world.fastGetPatchAt(pxcor, pycor);
} catch (ClassCastException cce) {
String abortingError = "Illegal Patch Coordinate- pxcor and pycor must be integers.";
throw new AbortingImportException
(ImportError.ILLEGAL_CLASS_CAST_ERROR, abortingError);
}
}
long getTurtleId(Map<String, Object> varVals, String whoHeaderName) {
try {
return ((Double) varVals.get(whoHeaderName)).longValue();
} catch (ClassCastException cce) {
String abortingError = "Illegal Who- a turtle's who must be an integer.";
throw new AbortingImportException
(ImportError.ILLEGAL_CLASS_CAST_ERROR, abortingError);
}
}
long getLinkId(Map<String, Object> varVals, String whoHeaderName) {
try {
return ((Double) varVals.get(whoHeaderName)).longValue();
} catch (ClassCastException cce) {
String abortingError = "Illegal lwho- a link's who must be an integer.";
throw new AbortingImportException
(ImportError.ILLEGAL_CLASS_CAST_ERROR, abortingError);
}
}
Turtle getLinkEnd(Map<String, Object> varVals, String headerName) {
try {
return (Turtle) varVals.get(headerName);
} catch (ClassCastException cce) {
String abortingError = "Illegal End a link's end points must be a turtle.";
throw new AbortingImportException
(ImportError.ILLEGAL_CLASS_CAST_ERROR, abortingError);
}
}
void setTurtleShape(Turtle turtle, Object shape, String header, int varIndex) {
try {
turtle.setVariable(varIndex, shape);
} catch (AgentException ae) {
if (!shapesNotToImport.contains(shape)) {
shapesNotToImport.add(shape);
throw new ImportException(
ImportError.ILLEGAL_SHAPE_ERROR,
"Illegal Shape",
ae.getMessage(),
"setting " + turtle + "'s shape to its breed's default shape");
}
setVarVal(turtle, varIndex, header, world.turtleBreedShapes.breedShape
(turtle.getBreed()));
}
}
AgentSet getTurtleBreed(Map<String, Object> varVals, String breedHeaderName) {
if (varVals.containsKey(breedHeaderName)) {
return (AgentSet) varVals.get(breedHeaderName);
}
return world.turtles();
}
AgentSet getLinkBreed(Map<String, Object> varVals, String breedHeaderName) {
if (varVals.containsKey(breedHeaderName)) {
return (AgentSet) varVals.get(breedHeaderName);
}
return world.links();
}
Object getLabel(Object val) {
if (val instanceof Junk) {
return "";
}
return val;
}
void setScreenDimensions(Map<String, Object> varVals) {
try {
int minx, maxx, miny, maxy;
if (!convertTopology) {
minx = ((Double) varVals.get(MIN_PXCOR_HEADER)).intValue();
maxx = ((Double) varVals.get(MAX_PXCOR_HEADER)).intValue();
miny = ((Double) varVals.get(MIN_PYCOR_HEADER)).intValue();
maxy = ((Double) varVals.get(MAX_PYCOR_HEADER)).intValue();
} else {
int sex = ((Double) varVals.get(SCREEN_EDGE_X_HEADER)).intValue();
int sey = ((Double) varVals.get(SCREEN_EDGE_Y_HEADER)).intValue();
minx = -sex;
maxx = sex;
miny = -sey;
maxy = sey;
}
if (minx != world.minPxcor() || maxx != world.maxPxcor() ||
miny != world.minPycor() || maxy != world.maxPycor()) {
importerUser.setDimensions(new WorldDimensions(minx, maxx, miny, maxy));
needToResize = true;
}
} catch (ClassCastException cce) {
String abortingError = "Illegal Screen dimension- max-px/ycor, min-px/ycor must be numbers.";
throw new AbortingImportException
(ImportError.ILLEGAL_CLASS_CAST_ERROR, abortingError);
}
}
//functions for getting and filling helper lists and maps
//maybe move this to World.java???
//returns a List containing all the breed variables for this model
List<String> getAllBreedVars() {
List<String> allBreedOwns = new ArrayList<String>();
Map<String, Object> breeds = world.getBreeds();
if (breeds != null) {
for (Iterator<Object> breedElt = breeds.values().iterator();
breedElt.hasNext();) {
AgentSet breed = (AgentSet) breedElt.next();
List<String> breedOwns =
world.program().breedsOwn().get(breed.printName());
if (breedOwns != null) {
for (int i = 0; i < breedOwns.size(); i++) {
allBreedOwns.add(breedOwns.get(i));
}
}
}
}
return allBreedOwns;
}
List<String> getAllLinkBreedVars() {
List<String> allBreedOwns = new ArrayList<String>();
Map<String, Object> breeds = world.getLinkBreeds();
if (breeds != null) {
for (Iterator<Object> breedElt = breeds.values().iterator();
breedElt.hasNext();) {
AgentSet breed = (AgentSet) breedElt.next();
List<String> breedOwns =
world.program().linkBreedsOwn().get(breed.printName());
if (breedOwns != null) {
for (int i = 0; i < breedOwns.size(); i++) {
allBreedOwns.add(breedOwns.get(i));
}
}
}
}
return allBreedOwns;
}
Map<Class<? extends Agent>, List<String>> specialVariables;
//fill the specialVariables map with the strings of the headers that need special handling to have a
//successful import
Map<Class<? extends Agent>, List<String>> fillSpecialVariables() {
Map<Class<? extends Agent>, List<String>> result =
new HashMap<Class<? extends Agent>, List<String>>();
List<String> specialObserverVars = stringArrayToList(getSpecialObserverVariables());
List<String> specialTurtleVars = stringArrayToList(getSpecialTurtleVariables());
List<String> specialPatchVars = stringArrayToList(getSpecialPatchVariables());
List<String> specialLinkVars = stringArrayToList(getSpecialLinkVariables());
result.put(Observer.class, specialObserverVars);
result.put(Turtle.class, specialTurtleVars);
result.put(Patch.class, specialPatchVars);
result.put(Link.class, specialLinkVars);
return result;
}
private List<String> stringArrayToList(String[] vars) {
List<String> list = new ArrayList<String>(vars.length);
for (int i = 0; i < vars.length; i++) {
list.add(vars[i]);
}
return list;
}
String[] getSpecialObserverVariables() {
return new String[]{MIN_PXCOR_HEADER,
MAX_PXCOR_HEADER,
MIN_PYCOR_HEADER,
MAX_PYCOR_HEADER,
SCREEN_EDGE_X_HEADER,
SCREEN_EDGE_Y_HEADER,
PERSPECTIVE_HEADER,
SUBJECT_HEADER,
NEXT_INDEX_HEADER,
DIRECTED_LINKS_HEADER,
TICKS_HEADER};
}
String[] getSpecialTurtleVariables() {
String[] vars = AgentVariables.getImplicitTurtleVariables(false);
return new String[]
{vars[Turtle.VAR_WHO], vars[Turtle.VAR_BREED],
vars[Turtle.VAR_LABEL], vars[Turtle.VAR_SHAPE]};
}
String[] getSpecialPatchVariables() {
String[] vars = AgentVariables.getImplicitPatchVariables(false);
return new String[]
{vars[Patch.VAR_PXCOR], vars[Patch.VAR_PYCOR],
vars[Patch.VAR_PLABEL]};
}
String[] getSpecialLinkVariables() {
String[] vars = AgentVariables.getImplicitLinkVariables();
return new String[]
{vars[Link.VAR_BREED], vars[Link.VAR_LABEL],
vars[Link.VAR_END1], vars[Link.VAR_END2]};
}
boolean isSpecialVariable(Class<? extends Agent> agentClass, String header) {
return specialVariables.get(agentClass).contains(header);
}
Map<Class<? extends Agent>, List<String>> essentialVarHeadersToImport;
//fill the essentialVarHeadersToImport map with the strings of the headers that are essential to a
//successful import
Map<Class<? extends Agent>, List<String>> fillEssentialVarsToImport() {
Map<Class<? extends Agent>, List<String>> result =
new HashMap<Class<? extends Agent>, List<String>>();
List<String> essentialObserverVarHeaders;
if (!convertTopology) {
essentialObserverVarHeaders = stringArrayToList(getEssentialObserverVars());
} else {
essentialObserverVarHeaders = stringArrayToList(getEssentialObserverVarsOld());
}
List<String> essentialTurtleVarHeaders = stringArrayToList(getEssentialTurtleVariables());
List<String> essentialPatchVarHeaders = stringArrayToList(getEssentialPatchVariables());
List<String> essentialLinkVarHeaders = stringArrayToList(getEssentialLinkVariables());
result.put(Observer.class, essentialObserverVarHeaders);
result.put(Turtle.class, essentialTurtleVarHeaders);
result.put(Patch.class, essentialPatchVarHeaders);
result.put(Link.class, essentialLinkVarHeaders);
return result;
}
String[] getEssentialObserverVars() {
return new String[]
{MIN_PXCOR_HEADER,
MAX_PXCOR_HEADER,
MIN_PYCOR_HEADER,
MAX_PYCOR_HEADER};
}
String[] getEssentialObserverVarsOld() {
return new String[]
{SCREEN_EDGE_X_HEADER,
SCREEN_EDGE_Y_HEADER};
}
String[] getEssentialTurtleVariables() {
return new String[]
{AgentVariables.getImplicitTurtleVariables(false)[Turtle.VAR_WHO]};
}
String[] getEssentialPatchVariables() {
String[] vars = AgentVariables.getImplicitPatchVariables(false);
return new String[]
{vars[Patch.VAR_PXCOR], vars[Patch.VAR_PYCOR]};
}
String[] getEssentialLinkVariables() {
String[] vars = AgentVariables.getImplicitLinkVariables();
return new String[]
{vars[Link.VAR_END1], vars[Link.VAR_END2]};
}
//if essentialHeaders is true,
//if this returns successfully, then all the essential headers for the variables for agentClass have been
//imported
//if essentialHeaders is false,
//if this returns successfully, either all headers for the variables for agentClass are there or the user
//doesn't care if the variables for the missing headers are set to an appropriate default
void varHeadersImported(Class<? extends Agent> agentClass, String[] headers, boolean essentialHeaders) {
List<String> headersToCheckFor =
essentialHeaders
? essentialVarHeadersToImport.get(agentClass)
: Arrays.asList(builtInVars);
List<String> optionalHeaders = getOptionalHeaders(agentClass);
for (int i = 0; i < headersToCheckFor.size(); i++) {
String header = headersToCheckFor.get(i);
boolean foundHeader = false;
for (int j = 0; j < headers.length; j++) {
if (header.equals(headers[j])) {
foundHeader = true;
break;
}
}
if (!foundHeader) {
if (essentialHeaders) {
String abortingError = header + " is not in the list of variables to be imported " +
"from the import file in the " + printSectionName() + " section. " +
"This variable is essential to a model.";
throw new AbortingImportException
(ImportError.UNDECLARED_ESSENTIAL_VAR_ERROR, abortingError);
} else if (optionalHeaders == null || !optionalHeaders.contains(header)) {
showError(
new ImportException(
ImportError.UNDECLARED_AGENT_VAR_ERROR,
"Implicit Variable Not Declared",
"the " + printName(agentClass) + " variable " +
header + " was not declared.",
"the import will continue but all agents with this " +
"variable will have it set to an appropriate default."));
}
}
}
}
String[] getImplicitVariables(Class<? extends Agent> agentClass) {
if (agentClass == Observer.class) {
return AgentVariables.getImplicitObserverVariables();
}
if (agentClass == Turtle.class) {
return AgentVariables.getImplicitTurtleVariables(world.program().is3D());
}
if (agentClass == Patch.class) {
return AgentVariables.getImplicitPatchVariables(world.program().is3D());
}
if (agentClass == Link.class) {
return AgentVariables.getImplicitLinkVariables();
}
throw new IllegalStateException();
}
//code to handle peek for the StringTokenizer
int lineNum = 0;
String nextLine; //the next line of input from the import file
private String[] nextLineFields;
private static final int REQUIRED_SECTIONS = 3;
private final String[] sentinels = {"GLOBALS", "TURTLES", "PATCHES", "LINKS", "DRAWING", "OUTPUT", "PLOTS", "EXTENSIONS", "DONE",};
private final int numSentinels = sentinels.length - 1;
private int currentSentinel = 0;
java.io.BufferedReader lines;
/**
* @return whether there more lines in the current section, that
* is, if we have not yet reached the next sentinel (or reached end
* of file)
*/
public boolean hasMoreLines(boolean returnBlankLines)
throws java.io.IOException {
nextLine = lines.readLine();
lineNum++;
if (nextLine == null) // needed to cope with cross-platform line terminator weirdness
{
if (currentSentinel != numSentinels) {
// older export files are not going to have a drawing section
// it's ok. just move on. ev 6/20/05
// Also, not all exports are going to have an output area, CLB /7/15/05
// old exports will not have LINKS sections either -- CLB 12/28/2005
// ooh, plots too. ev 7/7/06
if (sentinels[currentSentinel].equals("DRAWING") ||
sentinels[currentSentinel].equals("LINKS") ||
sentinels[currentSentinel].equals("OUTPUT") ||
sentinels[currentSentinel].equals("PLOTS") ||
sentinels[currentSentinel].equals("EXTENSIONS")) {
currentSentinel++;
return false;
}
String abortingError = "No " + sentinels[currentSentinel] +
" have been imported. Globals, Turtles, and Patches " +
"must be in the same import file.";
throw new AbortingImportException
(ImportError.UNEXPECTED_EOF_ERROR, abortingError);
}
return false;
}
if (nextLine.equals("")) // eat up blank lines
{
// changing the rules a little it's possible
// to use blank lines as subsection delimiters
// if you choose
if (returnBlankLines) {
return false;
}
return hasMoreLines(false);
}
try {
nextLineFields = ImportLexer.lex(nextLine);
} catch (ImportLexer.LexerException le) {
throw new AbortingImportException
(ImportError.CSV_LEXING_ERROR,
"At line " + lineNum + ": " + le.getMessage());
}
if (nextLineFields.length <= 0) {
if (returnBlankLines) {
return true;
} else {
return hasMoreLines(returnBlankLines);
}
}
if (nextLineFields[0].toUpperCase().startsWith(sentinels[currentSentinel])) {
currentSentinel++;
return false;
} else {
if (anotherSentinelEquals(nextLineFields[0].toUpperCase())) {
// only throw an error if we are still in the required sections
// phase of the import file
if (currentSentinel < REQUIRED_SECTIONS) {
String abortingError = "The agents are in the wrong order in the import file. " +
"The global variables should be first, followed by the turtles, " +
"followed by the patches. Found " + nextLineFields[0] +
" but needed " + sentinels[currentSentinel];
throw new AbortingImportException
(ImportError.FILE_STRUCTURE_ERROR, abortingError);
}
return false;
}
return true;
}
}
boolean anotherSentinelEquals(String line) {
for (int i = 0; i < currentSentinel; i++) {
if (line.equals(sentinels[i])) {
return true;
}
}
for (int i = currentSentinel + 1; i < numSentinels; i++) {
if (line.equals(sentinels[i])) {
return true;
}
}
return false;
}
String[] nextLine() {
return nextLineFields;
}
public String next() {
return nextLine;
}
//misc functions
String printSectionName() {
return ((currentSentinel > 0) ? sentinels[currentSentinel - 1] : "UNKNOWN");
}
String printName(Class<? extends Agent> agentClass) {
if (agentClass == Observer.class) {
return "Global";
}
if (agentClass == Turtle.class) {
return "Turtle";
}
if (agentClass == Patch.class) {
return "Patch";
}
if (agentClass == Link.class) {
return "Link";
}
// there are no other agents
return "";
}
void checkForBlankTurtles() {
for (AgentSet.Iterator iter = world.turtles().iterator(); iter.hasNext();) {
Turtle turtle = (Turtle) iter.next();
if (turtle.getBreed() == null) {
String abortingError = turtle.toString() + " was referenced in an agentset or agent " +
"but was not defined in the TURTLES section.";
throw new AbortingImportException
(ImportError.BLANK_TURTLE_ERROR, abortingError);
}
}
}
enum ImportError {
// nonfatal
ILLEGAL_AGENT_VAR_ERROR,
ILLEGAL_SHAPE_ERROR,
ILLEGAL_BREED_ERROR,
PARSING_ERROR,
SETTING_VAR_ERROR,
UNDECLARED_AGENT_VAR_ERROR,
TOO_MANY_VALUES_ERROR,
// pseudo-error, used for separating into fatal and nonfatal
LAST_NONFATAL_ERROR,
// fatal
ILLEGAL_CLASS_CAST_ERROR,
UNEXPECTED_EOF_ERROR,
ERROR_GIVEN,
FILE_STRUCTURE_ERROR,
UNDECLARED_ESSENTIAL_VAR_ERROR,
UNIMPORTED_ESSENTIAL_VAR_ERROR,
BLANK_TURTLE_ERROR,
CSV_LEXING_ERROR,
ILLEGAL_PCOR_ERROR,
IMPORT_3D_ERROR,
UNKNOWN_ERROR
}
strictfp class ImportException extends RuntimeException {
public ImportError type;
public String message;
public String action;
public String title;
public ImportException(ImportError errorType, String errorTitle,
String errorMessage, String defaultAction) {
super(errorTitle + "- Error Type: " + errorType);
type = errorType;
title = errorTitle;
message = errorMessage;
action = defaultAction;
}
public ImportException(ImportError errorType, String errorTitle, String
errorMessage, String defaultAction, String additionalInfo) {
this(errorType, errorTitle, errorMessage, defaultAction);
message += "\n\nAdditional Information: " + additionalInfo;
}
}
static final String NO_DETAILS = "";
class AbortingImportException extends RuntimeException {
ImportError errorType;
public String title;
public String details;
public AbortingImportException(ImportError errorType, String details) {
super("Fatal Error Type:" + errorType);
this.errorType = errorType;
title = "Fatal Error- " + getErrorMessage();
this.details = details + "\n\nThe import will now abort.";
}
String getErrorMessage() {
String message;
switch (errorType) {
case ERROR_GIVEN:
message = "Error Already Given";
break;
case ILLEGAL_CLASS_CAST_ERROR:
message = "Illegal Type Cast";
break;
case UNEXPECTED_EOF_ERROR:
message = "Unexpected End of File";
break;
case FILE_STRUCTURE_ERROR:
message = "Incorrect Structure For Import File";
break;
case UNDECLARED_ESSENTIAL_VAR_ERROR:
message = "Essential Variable Not Declared";
break;
case UNIMPORTED_ESSENTIAL_VAR_ERROR:
message = "Essential Variable Not Imported";
break;
case BLANK_TURTLE_ERROR:
message = "Referenced Turtle Not Defined";
break;
case CSV_LEXING_ERROR:
message = "Invalid CSV File";
break;
case IMPORT_3D_ERROR:
message = "You cannot import a 2D world into 3D NetLogo";
break;
default:
message = "Unknown Fatal Error";
}
return message;
}
}
//displaying errors of some kind
void showError(ImportException ix) {
if (ix.type.compareTo(ImportError.LAST_NONFATAL_ERROR) > 0) {
throw new AbortingImportException
(ImportError.UNKNOWN_ERROR, "An unknown error has occurred. The import will now abort.");
}
if (!errorHandler.showError("Warning: " + ix.title,
"Error Importing at Line " + lineNum + ": " + ix.message +
"\n\nAction to be Taken: " + ix.action, false)) {
throw new AbortingImportException
(ImportError.ERROR_GIVEN, NO_DETAILS);
}
}
public void showError(String title, String message, String defaultAction) {
showError
(new ImportException(ImportError.PARSING_ERROR, title, message, defaultAction));
}
void showError(AbortingImportException aix) {
errorHandler.showError(aix.title, aix.details, true);
}
///
Object readFromString(String s)
throws StringReaderException {
return stringReader.readFromString(s);
}
public interface StringReader {
Object readFromString(String s)
throws StringReaderException;
}
public static strictfp class StringReaderException
extends Exception {
public StringReaderException(String message) {
super(message);
}
}
public interface ErrorHandler {
/**
* display an error to the user in an appropriate manner.
*
* @param title indicates the type of error.
* @param errorDetails indicates the details of the error and what
* course of action will be pursued.
* @param fatalError <code>true</code> if this error is considered a
* fata error. <code>false</code> otherwise.
* @return <code>true</code> if the user wishes to continue with the
* action suggested in the <code>errorDetails</code>. <code>false</code>
* if the user doesn't want to continue. if <code>fatalError</code> is
* <code>true</code>, the return value will be ignored.
*/
boolean showError(String title, String errorDetails,
boolean fatalError);
}
// default access for unit testing
strictfp class Junk {
}
///
static class InvalidDataException
extends java.io.IOException {
public InvalidDataException(String message) {
super(message);
}
}
///
private static int[] fromHexString(String s)
throws InvalidDataException {
int stringLength = s.length();
int[] ints = new int[stringLength / 8];
// if it isn't a multiple of four we can't convert it.
if ((stringLength % 8) == 0) {
for (int i = 0, j = 0; j < (stringLength / 8); j++) {
ints[j] = charToNibble(s.charAt(i++)) << 28;
ints[j] |= charToNibble(s.charAt(i++)) << 24;
ints[j] |= charToNibble(s.charAt(i++)) << 20;
ints[j] |= charToNibble(s.charAt(i++)) << 16;
ints[j] |= charToNibble(s.charAt(i++)) << 12;
ints[j] |= charToNibble(s.charAt(i++)) << 8;
ints[j] |= charToNibble(s.charAt(i++)) << 4;
ints[j] |= charToNibble(s.charAt(i++));
}
} else {
throw new InvalidDataException
("The data must be a multiple of 4 to covert from Hex string to ints");
}
return ints;
}
/**
* convert a single char to corresponding nibble.
*
* @param c char to convert. must be 0-9 a-f A-F, no
* spaces, plus or minus signs.
* @return corresponding integer
*/
private static int charToNibble(char c) {
if ('0' <= c && c <= '9') {
return c - '0';
} else if ('a' <= c && c <= 'f') {
return c - 'a' + 0xa;
} else if ('A' <= c && c <= 'F') {
return c - 'A' + 0xa;
} else {
throw new IllegalArgumentException("Invalid hex character: " + c);
}
}
}