package com.openMap1.mapper.util;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.StringTokenizer;
import java.util.Vector;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.ecore.EPackage;
import org.osgi.framework.Bundle;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.openMap1.mapper.converters.AbstractMapperWrapper;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.core.XMLException;
import com.openMap1.mapper.MappedStructure;
/**
* static file utilities that require the classes IFile and IPath - which I cannot find
* in jar files, so cannot deploy outside Eclipse
* @author robert
*
*/
public class EclipseFileUtil {
public static String TEMPORARY_XML_FILE = "/eclipseTempFile.xml";
public static String TEMPORARY_TEXT_FILE = "/eclipseTempFile.TXT";
public static String getAbsoluteLocation(IFolder folder)
{
return folder.getFullPath().toString();
}
/**
* @param URIString a URI in the workspace,
* which may or may not begin with 'platform:/resource/'
* @return the IFile or IFile handle at that URI
*/
public static IFile getFile(String URIString)
{
String pathString = URIString;
if (pathString.startsWith(FileUtil.platformPreface()))
pathString = pathString.substring(FileUtil.platformPreface().length()-1);
IFile file = FileUtil.getRoot().getFile(new Path(pathString));
return file;
}
/**
*
* @param file
* @return resource location of an IFile
*/
public static String getResourceLocation(IFile file)
{
String start = FileUtil.platformPreface(); // string 'platform:/resource/'
start = start.substring(0,start.length()-1); // remove the final '/'
return start + file.getFullPath().toString();
}
public static EPackage getClassModel(IFile file) throws MapperException
{
IFolder folder = (IFolder) file.getParent();
// this does not introduce '%20' if filenames have spaces
String path = folder.getLocationURI().toString() + "/" + file.getName();
String filePath = FileUtil.removeFilePrefix(path); // remove 'file:'
return FileUtil.getClassModel(filePath);
}
public static Element getRoot(IFile file) throws MapperException
{
IFolder folder = (IFolder) file.getParent();
// this does not introduce '%20' if filenames have spaces
String path = folder.getLocationURI().toString() + "/" + file.getName();
String filePath = FileUtil.removeFilePrefix(path); // remove 'file:'
Element root = XMLUtil.getRootElement(filePath);
return root;
}
public static Vector<String> textLines(IFile file) throws MapperException
{
IFolder folder = (IFolder) file.getParent();
// this does not introduce '%20' if filenames have spaces
String path = folder.getLocationURI().toString() + "/" + file.getName();
String filePath = FileUtil.removeFilePrefix(path); // remove 'file:'
return FileUtil.textLines(filePath);
}
/**
*
* @param file
* @return
* @throws MapperException
*/
public static Vector<String[]> csvRows(IFile file) throws MapperException
{
Vector<String> lines = textLines(file);
// count the number of columns from the header row
StringTokenizer st = new StringTokenizer(lines.get(0),",");
int columns = st.countTokens();
Vector<String[]> rows = new Vector<String[]>();
for (int i = 0; i < lines.size(); i++)
rows.add(FileUtil.parseCSVLine(columns, lines.get(i)));
return rows;
}
/**
* @param mapperLocation the location of a mapper file
* @return the last changed date of the corresponding WProc file,
* in milliseconds since Jan 1 1970, if it exists;
* or 0 if it does not exist
*/
public static long wProcFileDate(MappedStructure mappedStructure)
{
long wpDate = 0;
try{
IFile wpFile = proceduresFile(mappedStructure);
if (wpFile != null)
{
boolean exists = wpFile.exists(); // separate line for debugging
if (exists) wpDate = wpFile.getLocalTimeStamp();
}
}
catch(Exception ex) {}
return wpDate;
}
/**
* get the procedures file for the current Mapping file
* from the standard location in this project.
* This is the same sub-folder of the Translators folder as the sub-folder of
* the MappingSets folder holding the mapping set; make the folder if necessary
* @return
*/
static public IFile proceduresFile(MappedStructure mappedStructure) throws MapperException
{
String resourceLocation = mappedStructure.eResource().getURI().toString();
// deal with an initial '//' which occurs on macs
if (resourceLocation.startsWith("file://")) resourceLocation = "file:/" + resourceLocation.substring(7);
// System.out.println("Mapped structure location: " + resourceLocation);
// this line has been removed, as it is always overridden by the next line
// if (resourceLocation.startsWith("file:/")) resourceLocation = FileUtil.removeFilePrefix(resourceLocation);
if (!resourceLocation.startsWith("platform:/resource"))
resourceLocation = FileUtil.resourceLocation(resourceLocation);
// find or create the chain of subfolders required
IFolder wProcFolder = EclipseFileUtil.makeWProcFolders(resourceLocation);
// return the handle to a file (which may not yet exist) in the inner folder
IFile proceduresFile = wProcFolder.getFile(EclipseFileUtil.wProcFileName(resourceLocation));
return proceduresFile;
}
/**
* append a line of text, which must not be more than about 1000 characters
* (to avoid a thread-jamming issue in this Noddy implementation)
* @param text the text to be appended
* @param file the file to append it to
*/
static public void appendLine(String text, IFile file) throws MapperException
{
try
{
file.appendContents(textStream(text), true, true, null);
}
catch (Exception ex) {throw new MapperException(ex.getMessage());}
}
/**
*
* @param text a text string less than 1000 characters long
* @return an InputStream of the text - to create an IFile or append to one
* @throws MapperException
*/
static public InputStream textStream(String text) throws MapperException
{
PipedInputStream in = new PipedInputStream();
String line = text;
if (text.length() > 1000)
{
System.out.println("Line truncated to 1000 chars: " + text);
line = text.substring(0,1000);
}
try
{
PipedOutputStream out = new PipedOutputStream(in);
FileUtil.nl(line, out);
out.close();
}
catch (Exception ex) {throw new MapperException(ex.getMessage());}
return in;
}
static void trace(String s) {System.out.println(s);}
/**
* derive the name of a Wproc file from a full path to a mapping set file.
* The name is the same as the mapping set file name, except for the extension
* 'wproc'
* @param mapperLocation
* @return
*/
public static String wProcFileName(String mapperLocation)
{
String fileName = "";
StringTokenizer st = new StringTokenizer(FileUtil.wProcLocation(mapperLocation),"/");
while (st.hasMoreTokens()) fileName = st.nextToken();
return fileName;
}
/**
* Works only inside Eclipse, where there is a project.
* finds and if necessary creates a set of nested folders in the project workspace
* as needed to write a wproc file inside the innermost folder
* @param mapperLocation the string to the corresponding mapper file
* @return the innermost folder
*/
public static IFolder makeWProcFolders(String mapperLocation) throws MapperException
{
IProject theProject = FileUtil.getProject(mapperLocation);
if ((theProject == null)|((theProject != null) && (!theProject.exists())))
throw new MapperException
("Cannot find project for WProc file at " + mapperLocation);
IFolder transFolder = theProject.getFolder("Translators");
if ((transFolder == null)|((transFolder != null) && (!transFolder.exists())))
throw new MapperException
("There is no 'Translators' folder in project " + theProject.getName());
IFolder currentFolder = transFolder;
StringTokenizer st = new StringTokenizer(mapperLocation,"/");
// start when you have gone beyond the MappingSet folder
boolean foundMapperFolder = false;
while (st.hasMoreTokens()) try
{
String folderName = st.nextToken();
// the last token is not a folder; it is the file name
if ((foundMapperFolder) && (st.hasMoreTokens()))
{
// try to find the next folder inside the current folder
IFolder nextFolder = currentFolder.getFolder(folderName);
// if it does not exist, create it
if (!nextFolder.exists()) {nextFolder.create(true,false,null);}
// get ready for the next nested folder
currentFolder = nextFolder;
}
if (folderName.equals("MappingSets")) foundMapperFolder = true;
}
catch (Exception ex)
{throw new MapperException("Exception creating folder for wproc file: " + ex.getMessage());}
return currentFolder;
}
/**
* write the result of an XMLWriter or wrapper transform to an IFile,
* so it will be known to Eclipse
* @param output output object - XML Element or String[] array
* @param theFile the IFile to be written to
* @param fileType constant defined in class AbstractMapperWrapper
* @throws MapperException
*/
public static void writeOutputObject(Object output,IFile theFile, int fileType)
throws MapperException
{
if (fileType == AbstractMapperWrapper.XML_TYPE)
{
if (!(output instanceof Document))
throw new MapperException("Output is not a Document but is a " + output.getClass().getName());
Document outDoc = (Document)output;
writeOutputResource(outDoc, theFile, true);
}
else if (fileType == AbstractMapperWrapper.TEXT_TYPE)
{
if (!(output instanceof String[]))
throw new MapperException("Output is not a String array is a " + output.getClass().getName());
String[] text = (String[])output;
writeOutputText(text, theFile);
}
}
/**
* wrtie out a csv file, expressed as a Vector of String arrays
* @param csvRows
* @param theFile
* @throws MapperException
*/
public static void writeCSVFile(Vector<String[]> csvRows, IFile theFile)
throws MapperException
{
try
{
// write to a temporary file
String root = ResourcesPlugin.getWorkspace().getRoot().getLocation().toString();
String tempFileLocation = root + TEMPORARY_TEXT_FILE;
FileUtil.writeCSVFile(tempFileLocation, csvRows);
// read the temporary file into the IFile
FileInputStream fileStream = new FileInputStream(tempFileLocation);
// delete any existing IFile before writing this one
if (theFile.exists()) theFile.delete(true, null);
theFile.create(fileStream, false, null);
}
catch (Exception ex) {throw new MapperException(ex.getMessage());}
}
/**
*
* @param text
* @param theFile
* @throws MapperException
*/
public static void writeOutputText(String[] text, IFile theFile)
throws MapperException
{
try
{
// write the text array to a temporary file
String root = ResourcesPlugin.getWorkspace().getRoot().getLocation().toString();
String tempFileLocation = root + TEMPORARY_TEXT_FILE;
FileOutputStream fo = new FileOutputStream(tempFileLocation);
for (int i = 0; i < text.length; i++) FileUtil.nl(text[i],fo);
fo.close();
// read the temporary file into the IFile
FileInputStream fileStream = new FileInputStream(tempFileLocation);
if (theFile.exists()) {theFile.appendContents(fileStream, false, false, null);}
else {theFile.create(fileStream, false, null);}
}
catch (Exception ex) {throw new MapperException(ex.getMessage());}
}
/**
* write XML to an IFile. This implementation is a kludge which make a temporary
* XML file somewhere outside the workspace, and leaves it there.
*
* @param outDoc the XML document to be written
* @param theFile the IFile, which may exist or may just be a handle.
* If it already exists, append the XML to its existing contents
* @param isFormatted if true, put in line breaks and indenting
* @throws XMLException if anything goes wrong
*/
public static void writeOutputResource(Document outDoc, IFile theFile, boolean isFormatted)
throws MapperException
{
if (outDoc == null) throw new MapperException("Null Document being written to file " + theFile.getName());
String tempFileLocation = workspaceRoot() + TEMPORARY_XML_FILE;
// System.out.println("Temporary file location: " + tempFileLocation);
XMLUtil.writeOutput(outDoc,tempFileLocation,isFormatted);
try{
FileInputStream fileStream = new FileInputStream(tempFileLocation);
if (theFile.exists()) {theFile.appendContents(fileStream, false, false, null);}
else {theFile.create(fileStream, false, null);}
}
catch (Exception ex) {throw new XMLException(ex.getMessage());}
}
/**
*
* @param outDoc
* @param resourceLocation
* @param isFormatted
* @throws XMLException
*/
public static void writeOutputResource(Document outDoc, String resourceLocation, boolean isFormatted)
throws MapperException
{
IProject theProject = FileUtil.getProject(resourceLocation);
// find the path to the folder within the project
String start = "platform:/resource/" + theProject.getName();
String folderPath = FileUtil.getFolder(resourceLocation).substring(start.length() + 1);
folderPath = folderPath.substring(0, folderPath.length()-1); // remove final '/'
// find the file name
String fileName = FileUtil.getFileName(resourceLocation);
// make the IFile handle and make sure the file does not already exist
IFolder theFolder = theProject.getFolder(folderPath);
IFile newFile = theFolder.getFile(fileName);
try {if (newFile.exists()) newFile.delete(true, null);}
catch (CoreException ex) {throw new MapperException("Failed to detete file: " + ex.getMessage());}
// write the XML to the IFile
writeOutputResource(outDoc, newFile,isFormatted);
}
public static String workspaceRoot()
{
return ResourcesPlugin.getWorkspace().getRoot().getLocation().toString();
}
/**
* write XML to an IFile. This implementation is a kludge which make a temporary
* XML file somewhere in the workspace, and leaves it there.
*
* @param outDoc the XML document to be written
* @param outputFile the IFile, which may exist or may just be a handle.
* If it already exists, append the XM to its existing contents
* @param isFormatted if true, put in line breaks and indenting
* @throws XMLException if anything goes wrong
*/
public static void writeTransformedOutputResource(Document outDoc, IFile outputFile, IFile xslFile)
throws XMLException
{
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
String tempFileLocation = root.getLocation().toString() + "/mapperTemporaryFile.xml";
try{
InputStream xslStream = xslFile.getContents();
XMLUtil.writeTransformedOutput(outDoc,tempFileLocation,xslStream);
FileInputStream fileStream = new FileInputStream(tempFileLocation);
if (outputFile.exists()) outputFile.appendContents(fileStream, false, false, null);
else outputFile.create(fileStream, false, null);
}
catch (Exception ex) {throw new XMLException(ex.getMessage());}
}
//-----------------------------------------------------------------------------------------------
// Saving and restoring information about the user or the session
// (1) the last selected class in the class model view
//-----------------------------------------------------------------------------------------------
// number of different ECore models whose last selected class can be stored
private static int MAX_STACK_SIZE = 5;
/**
* @return the path to the latest selected class, or null
* if none is stored for this model
*/
public static String getSelectedClassPath(String model)
{
String path = null;
String[][] classStack = readStack();
for (int i = 0; i < MAX_STACK_SIZE; i++)
if (classStack[i][0].equals(model)) path = classStack[i][1];
return path;
}
/**
* save the class path for this model, updating the LRU stack of models and class paths
* @param model
* @param path
*/
public static void saveClassPath(String model, String path) throws MapperException
{
String[][] oldClassStack = readStack(); // previous version of stack
String[][] classStack = new String[MAX_STACK_SIZE][2]; // stack to be stored in file
for (int i = 0; i < MAX_STACK_SIZE; i++) classStack[i][0] = "";
// put the model and path at the top of the stack to be stored
int newStackPos = 0;
classStack[newStackPos][0] = model;
classStack[newStackPos][1] = path;
// update the rest of the stack to be stored
for (int i = 0; i < MAX_STACK_SIZE; i++)
{
// re-store class paths for all models except the one just added or replaced
if (!oldClassStack[i][0].equals(model))
{
newStackPos++;
if (newStackPos < MAX_STACK_SIZE) // never try to exceed stack size
{
classStack[newStackPos][0] = oldClassStack[i][0];
classStack[newStackPos][1] = oldClassStack[i][1];
}
}
}
writeStack(classStack);
}
private static String[][] readStack()
{
String[][] classStack = new String[MAX_STACK_SIZE][2];
for (int i = 0; i <MAX_STACK_SIZE; i++) classStack[i][0] = "";
try
{
Element classRoot = XMLUtil.getRootElement(getSelectedClassFileLocation());
Vector<Element> classEls = XMLUtil.namedChildElements(classRoot, "class");
for (int i = 0; i < classEls.size(); i++) if (i < MAX_STACK_SIZE)
{
Element classEl = classEls.get(i);
classStack[i][0] = classEl.getAttribute("model");
classStack[i][1] = classEl.getAttribute("path");
}
}
catch (Exception ex) {}
return classStack;
}
/**
* make or replace the local file holding the path to the last selected class
* in the class model view
*/
private static void writeStack(String[][] classStack) throws MapperException
{
try{
Document classDoc = XMLUtil.makeOutDoc();
Element rootEl = XMLUtil.newElement(classDoc, "SelectedClasses");
classDoc.appendChild(rootEl);
for (int i = 0; i < MAX_STACK_SIZE; i++) if (!classStack[i][0].equals(""))
{
Element classEl = XMLUtil.newElement(classDoc,"class");
classEl.setAttribute("model", classStack[i][0]);
classEl.setAttribute("path", classStack[i][1]);
rootEl.appendChild(classEl);
}
XMLUtil.writeOutput(classDoc, getSelectedClassFileLocation(), true);
}
catch (Exception ex) {throw new MapperException(ex.getMessage());}
}
/**
* @return the location and name of the file containing the path to the last selected classes
*/
private static String getSelectedClassFileLocation()
{
Bundle thisPlugin = Platform.getBundle("com.openMap1.mapper");
IPath path = Platform.getStateLocation(thisPlugin);
String location = path.toString() + "/selectedClasses.xml";
return location;
}
}