/** * 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; } }