/**
* ExpReader.java
* Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.expeditee.io;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import org.expeditee.core.Point;
import org.expeditee.gui.Frame;
import org.expeditee.gui.FrameIO;
import org.expeditee.items.Constraint;
import org.expeditee.items.Dot;
import org.expeditee.items.Item;
import org.expeditee.items.Line;
import org.expeditee.items.Text;
import org.expeditee.stats.SessionStats;
/**
* Reads in Exp format files and constructs the Frame and Item objects they
* contain.
*
* @author mrww1
*
*/
public class ExpReader extends DefaultFrameReader {
public static final String EXTENTION = ".exp";
protected BufferedReader _reader = null;
protected String _frameName;
/**
* Does nothing, location must be set before use
*/
public ExpReader(String frameName) {
super();
_frameName = frameName;
}
/**
* Determines whether a string begins with tag.
*
* @param s
* a line of text
* @return true if s begins with a tag
*/
protected static boolean isValidLine(String s) {
// Previously lines in a .exp file had to start with a letter (A-Z, a-z). This allowed for an efficient check for valid lines.
// As we started to run out of spare letters to use for properties, we wished to use the full range of characters. But we did not
// wish to loose the efficiency of the Character.isLetter check. In order to maintain as much of this efficiency as possible, but
// allow for all characters, we take advantage of how || is evaluated:
// if the check for Character.isLetter passes, then the more complex map lookup operation does not take place.
if (s.length() < 2) {
return false;
}
char charZero = s.charAt(0);
if (charZero == '_') {
// If the character at the start is a underscore then we are dealing with the extended collection and must extract a string.
// This requires that the line contains a space character to split on and that the resulting split has at least two parts.
String[] parts = s.split(" ");
if (parts.length < 2) {
return false;
}
boolean isStringTag = _ItemTagsExt.keySet().contains(parts[0]);
if (isStringTag) {
return true;
}
} else {
// If we are looking at a letter then we can save ourselves from the less efficient general character check.
boolean isLetter = Character.isLetter(charZero);
if (isLetter) {
return true;
}
// If we are not looking at a letter but we are looking at a character that is not _ then it must be in the existing collection to be valid
boolean isCharacterTag = _ItemTags.keySet().contains(charZero);
if (isCharacterTag) {
return true;
}
}
// If we have somehow found a entry which does not pass any of the above tests then the line is invalid.
return false;
}
/**
* Reads a file with the given name from disk.
*
* @param frameName
* the name of the Frame to read in from a file.
* @return A new Frame object that contains any Items described in the file.
* @throws IOException
* Any exceptions occured by the BufferedReader.
*/
@Override
public Frame readFrame(BufferedReader reader) throws IOException {
_reader = reader;
String next = "";
Frame newFrame = new Frame();
try {
// Framename must be set before setting the frame number
newFrame.setName(_frameName);
List delayedActions = new LinkedList();
// First read all the header lines
next = readTheHeader(newFrame);
// Now read all the items
next = readTheItems(newFrame, delayedActions);
// Read the lines
next = readTheLines(newFrame);
// Read the constraints
next = readTheConstraints();
for(DelayedAction action: delayedActions) {
action.exec();
}
// Read the stats
next = readTheStats(newFrame);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Error reading frame file line: " + next + " "
+ e.getMessage());
}
//newFrame.refreshItemPermissions();
_reader.close();
FrameIO.setSavedProperties(newFrame);
newFrame.setChanged(false);
return newFrame;
}
protected String readTheStats(Frame newFrame) throws IOException {
String next = null;
while (_reader.ready() && ((next = _reader.readLine()) != null)) {
if (next.startsWith(SessionStats.ACTIVE_TIME_ATTRIBUTE)) {
try {
String value = next.substring(SessionStats.ACTIVE_TIME_ATTRIBUTE.length()).trim();
newFrame.setActiveTime(value);
} catch (Exception e) {
}
} else if (next.startsWith(SessionStats.DARK_TIME_ATTRIBUTE)) {
try {
String value = next.substring(SessionStats.DARK_TIME_ATTRIBUTE.length()).trim();
newFrame.setDarkTime(value);
} catch (Exception e) {
}
}
}
return next;
}
protected String readTheConstraints() throws IOException, Exception {
String next = null;
while (_reader.ready() && !(next = _reader.readLine()).equals("Z")) {
if (isValidLine(next)) {
Point idtype = separateValues(next.substring(2));
// The next line must be the endpoints
if (!_reader.ready()) {
throw new Exception("Unexpected end of file");
}
next = _reader.readLine();
Point startend = separateValues(next.substring(2));
Item a = _linePoints.get(startend.getX());
Item b = _linePoints.get(startend.getY());
new Constraint(a, b, idtype.getX(), idtype.getY());
}
}
return next;
}
protected String readTheLines(Frame newFrame) throws IOException, Exception {
String next = null;
while (_reader.ready() && !(next = _reader.readLine()).equals("Z")) {
if (isValidLine(next)) {
Point idtype = separateValues(next.substring(2));
// The next line must be the endpoints
if (!_reader.ready()) {
throw new Exception("Unexpected end of file");
}
next = _reader.readLine();
Point startend = separateValues(next.substring(2));
int start = startend.getX();
int end = startend.getY();
if (_linePoints.get(start) != null
&& _linePoints.get(end) != null) {
newFrame.addItem(new Line(_linePoints.get(start),
_linePoints.get(end), idtype.getX()));
} else {
System.out
.println("Error reading line with unknown end points");
}
}
}
return next;
}
protected String readTheItems(Frame newFrame, final List delayedActions) throws IOException {
String next = null;
Item currentItem = null;
while (_reader.ready() && !(next = _reader.readLine()).equals("Z")) {
// if this is the start of a new item add a new item
if (isValidLine(next)) {
if (getTag(next) == 'S') {
currentItem = newItem(next);
_linePoints.put(currentItem.getID(), currentItem);
newFrame.addItem(currentItem);
} else if (currentItem != null && actionShouldBeDelayed(getTag(next))) {
delayedActions.add(new DelayedAction(currentItem, next));
} else if (currentItem != null) {
processBodyLine(currentItem, next);
} else {
System.err.println("Error while reading in frame (ExpReader): Found body line but no current item to apply it to.");
}
}
}
return next;
}
protected Item newItem(String line) {
Item currentItem;
String value = getValue(line);
int id = Integer.parseInt(value.substring(2));
switch (value.charAt(0)) {
case 'P': // check if its a point
currentItem = new Dot(id);
break;
default:
currentItem = new Text(id);
break;
}
return currentItem;
}
protected String readTheHeader(Frame newFrame) throws IOException {
String next = null;
while (_reader.ready() && !(next = _reader.readLine()).equals("Z")) {
if (isValidLine(next)) {
processHeaderLine(newFrame, next);
}
}
return next;
}
public class DelayedAction {
private Item theItem;
private String theLine;
public DelayedAction(final Item theItem, final String theLine) {
this.theItem = theItem;
this.theLine = theLine;
}
public void exec() {
processBodyLine(theItem, theLine);
}
}
// Stores points used when constructing lines
protected HashMap _linePoints = new HashMap();
/**
* Processes the body section of the Exp file, which contains all Items
*
* @param frame
* The Frame to add any created Items to.
* @param line
* The line of text read in from the file to process.
*/
protected void processBodyLine(Item item, String line) {
Method toRun = null;
// separate the tag from the value
String tag = getTag(line).toString();
String value = null;
if (tag.charAt(0) != '_') {
value = getValue(line);
toRun = _ItemTags.get(tag.charAt(0));
} else {
tag = getTagExt(line);
value = getValueExt(line);
toRun = _ItemTagsExt.get(tag);
}
if (toRun == null) {
System.out.println("Error accessing tag method: " + tag);
}
Object[] vals = Conversion.Convert(toRun, value);
try {
if (vals != null) {
toRun.invoke(item, vals);
}
} catch (Exception e) {
System.out.println("Error running tag method: " + tag);
e.printStackTrace();
}
}
protected static Character getTag(String line) {
assert (line.length() > 0);
return line.charAt(0);
}
protected static String getTagExt(String line) {
assert(line.length() >= 2 && line.contains(" "));
String[] parts = line.split(" ");
assert(parts.length >= 2);
return parts[0];
}
protected static String getValue(String line) {
if (line.length() > 2) {
return line.substring(2);
} else {
return "";
}
}
protected static String getValueExt(String line) {
assert(line.length() >= 2 && line.contains(" "));
return line.substring(line.indexOf(" ") + 1);
}
protected static boolean actionShouldBeDelayed(Character c) {
return _DelayedItemTags.contains(c);
}
/**
* Reads the header section of the file, which contains information about
* the Frame.
*
* @param frame
* The Frame to assign the read values to.
* @param line
* The line from the file to process.
* @return False if the end of the header has been reached, True otherwise.
*/
private void processHeaderLine(Frame frame, String line) throws IOException {
// first separate the tag from the text
Character tag = getTag(line);
String value = getValue(line);
Method toRun = _FrameTags.get(tag);
if (toRun == null) {
if (tag != 'v') {
System.out.println("Tag '" + tag + "' in '" + line
+ "' is not supported.");
}
return;
}
Object[] vals = Conversion.Convert(toRun, value);
try {
toRun.invoke(frame, vals);
} catch (Exception e) {
System.out.println("Error running method: "
+ toRun.toGenericString());
e.printStackTrace();
}
}
// Returns a point from a String containing two ints separated by a space
protected Point separateValues(String line) {
int x = Integer.parseInt(line.substring(0, line.indexOf(" ")));
int y = Integer.parseInt(line.substring(line.indexOf(" ") + 1));
return new Point(x, y);
}
public static int getVersion(String fullpath) {
if (fullpath == null) return -1;
try {
BufferedReader reader = new BufferedReader(new FileReader(fullpath));
String next = "";
// First read the header lines until we get the version number
while (reader.ready() && !(next = reader.readLine()).equals("Z")) {
if (isValidLine(next)) {
Character tag = getTag(next);
String value = getValue(next);
if (tag.equals('V')) {
reader.close();
return Integer.parseInt(value);
}
}
}
reader.close();
} catch (Exception e) {
//e.printStackTrace();
// When this exception goes off, it is because it is trying to check if a frame that has yet to be saved
// to the filesystem has a version number greater than in memory one. This only happens for new frames,
// so is not a problem.
}
return -1;
}
public static String redirectTo(String fullPath) {
if (fullPath == null) return null;
try {
BufferedReader reader = new BufferedReader(new FileReader(fullPath));
String next = "";
while (reader.ready() && !(next = reader.readLine()).equals("Z")) {
if (next.startsWith("REDIRECT:")) {
String redirectTo = next.replace("REDIRECT:", "");
reader.close();
return redirectTo;
}
}
reader.close();
} catch (IOException e) {
//e.printStackTrace();
// When this exception goes off, it is because it is trying to check if a frame that has yet to be saved
// to the filesystem has a redirect. This comes out in teh wash later on when a redirect is next checked.
}
return null;
}
}