// Copyright 2012 Georg-August-Universität Göttingen, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package de.ugoe.cs.autoquest.plugin.jfc;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import de.ugoe.cs.util.StringTools;
import de.ugoe.cs.util.console.Console;
/**
*
* corrects older JFC log files which sometimes do not contain correct source specifications for
* events. It parses the file and adds component specifications to the sources, that do not have
* them. For each invalid source it checks, if there is another source with the same
* toString parameter but a complete list of components. If one is found, it is reused
* as source for the event with the wrong source. If none is found, a new component list is
* generated. This contains as parent components the components of the source of the previous event.
* The leaf component is parsed from the toString parameter that is provided with the
* source specifications. The resulting leaf nodes are not fully correct. They may pretend to
* be equal although they are not. Furthermore they may resist at a position in the GUI tree where
* they are not in reality. But more correctness is not achievable based on the
* toString parameter.
*
*
* @version $Revision: $ $Date: 05.09.2012$
* @author 2012, last modified by $Author: pharms$
*/
public class JFCTraceCorrector extends DefaultHandler {
/**
*
* the file to write the result into
*
*/
private PrintStream outFile;
/**
*
* the currently parsed event
*
*/
private Event currentEvent;
/**
*
* the currently parsed source of the currently parsed event
*
*/
private Source currentSource;
/**
*
* the list of all sources parsed in a file identified through their toString
* representation
*
*/
private Map> allSources = new HashMap>();
/**
*
* the currently parsed component of the currently parsed source of the currently parsed event
*
*/
private Component currentComponent;
/**
*
* the currently parsed session
*
*/
private Session currentSession;
/**
*
* corrects the given file and returns the name of the file into which the result was written
*
*
* @param filename the name of the file to be corrected
*
* @return the name of the file with the corrected logfile
*
* @throws IllegalArgumentException if the filename is null
*/
public String correctFile(String filename) throws IllegalArgumentException {
if (filename == null) {
throw new IllegalArgumentException("filename must not be null");
}
return correctFile(new File(filename)).getAbsolutePath();
}
/**
*
* corrects the given file, stores the result in the second provided file and returns the
* name of the file into which the result was written
*
*
* @param filename the name of the file to be corrected
* @param resultFile the name of the file into which the corrected log shall be written
*
* @return the name of the file with the corrected logfile
*
* @throws IllegalArgumentException if the filename or resultFile is null
*/
public String correctFile(String filename, String resultFile) throws IllegalArgumentException {
if ((filename == null) | (resultFile == null)) {
throw new IllegalArgumentException("filename and resultFile must not be null");
}
return correctFile(new File(filename), new File(resultFile)).getAbsolutePath();
}
/**
*
* corrects the given file and returns the file into which the result was written. The name
* of the resulting file is contains the suffix "_corrected" before the dot.
*
*
* @param file the file to be corrected
*
* @return the file containing the corrected logfile
*
* @throws IllegalArgumentException if the file is null
*/
public File correctFile(File file) throws IllegalArgumentException {
if (file == null) {
throw new IllegalArgumentException("file must not be null");
}
int index = file.getName().lastIndexOf('.');
String fileName =
file.getName().substring(0, index) + "_corrected" + file.getName().substring(index);
File resultFile = new File(file.getParentFile(), fileName);
return correctFile(file, resultFile);
}
/**
*
* corrects the given file, stores the result in the second provided file and returns the
* file into which the result was written
*
*
* @param file the file to be corrected
* @param resultFile the file into which the corrected log shall be written
*
* @return the file with the corrected logfile
*
* @throws IllegalArgumentException if the file or resultFile is null or if they are equal
*/
public File correctFile(File file, File resultFile) throws IllegalArgumentException {
if ((file == null) || (resultFile == null)) {
throw new IllegalArgumentException("file and result file must not be null");
}
if (file.getAbsolutePath().equals(resultFile.getAbsolutePath())) {
throw new IllegalArgumentException("file and result file must not be equal");
}
try {
outFile = new PrintStream
(new BufferedOutputStream(new FileOutputStream(resultFile)), false, "UTF-8");
outFile.println("");
}
catch (FileNotFoundException e1) {
throw new IllegalArgumentException("could not create a corrected file name " +
resultFile + " next to " + file);
}
catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("this host does not support UTF-8 encoding");
}
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setValidating(true);
SAXParser saxParser = null;
InputSource inputSource = null;
try {
saxParser = spf.newSAXParser();
inputSource =
new InputSource(new InputStreamReader(new FileInputStream(file), "UTF-8"));
}
catch (UnsupportedEncodingException e) {
Console.printerr("Error parsing file + " + file.getName());
Console.logException(e);
return null;
}
catch (ParserConfigurationException e) {
Console.printerr("Error parsing file + " + file.getName());
Console.logException(e);
return null;
}
catch (SAXException e) {
Console.printerr("Error parsing file + " + file.getName());
Console.logException(e);
return null;
}
catch (FileNotFoundException e) {
Console.printerr("Error parsing file + " + file.getName());
Console.logException(e);
return null;
}
if (inputSource != null) {
inputSource.setSystemId("file://" + file.getAbsolutePath());
try {
if (saxParser == null) {
throw new RuntimeException("SAXParser creation failed");
}
saxParser.parse(inputSource, this);
}
catch (SAXParseException e) {
Console.printerrln("Failure parsing file in line " + e.getLineNumber() +
", column " + e.getColumnNumber() + ".");
Console.logException(e);
return null;
}
catch (SAXException e) {
Console.printerr("Error parsing file + " + file.getName());
Console.logException(e);
return null;
}
catch (IOException e) {
Console.printerr("Error parsing file + " + file.getName());
Console.logException(e);
return null;
}
}
if (outFile != null) {
outFile.close();
}
return resultFile;
}
/*
* (non-Javadoc)
*
* @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
* java.lang.String, org.xml.sax.Attributes)
*/
public void startElement(String uri, String localName, String qName, Attributes atts)
throws SAXException
{
if (qName.equals("sessions")) {
if (currentSession != null) {
throw new SAXException("nested sessions are not allowed");
}
currentSession = new Session("sessions");
}
else if (qName.equals("newsession")) {
if (currentSession != null) {
currentSession.dump(outFile);
}
currentSession = new Session("newsession");
}
else if (qName.equals("event")) {
if (currentEvent != null) {
throw new SAXException("nested events are not allowed");
}
currentEvent = new Event(atts.getValue("id"));
}
else if (qName.equals("source")) {
if (currentSource != null) {
throw new SAXException("nested sources are not allowed");
}
currentSource = new Source();
}
else if (qName.equals("component")) {
if (currentComponent != null) {
throw new SAXException("nested components are not allowed");
}
currentComponent = new Component();
}
else if (qName.equals("param")) {
if (currentComponent != null) {
currentComponent.addParameter(atts.getValue("name"), atts.getValue("value"));
}
else if (currentSource != null) {
currentSource.addParameter(atts.getValue("name"), atts.getValue("value"));
}
else if (currentEvent != null) {
currentEvent.addParameter(atts.getValue("name"), atts.getValue("value"));
}
else {
throw new SAXException("parameter occurred at an unexpected place");
}
}
else {
throw new SAXException("unexpected tag " + qName);
}
}
/*
* (non-Javadoc)
*
* @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
* java.lang.String)
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// we do not have to check, if the current objects are null, as the Parser itself will
// throw an exception, if there is a closing tag for a non existent opening tag. But
// with a correct opening tag, the member variables will not be null (see startElement())
if (qName.equals("sessions")) {
correctSources(currentSession);
currentSession.dump(outFile);
currentSession = null;
allSources.clear();
}
else if (qName.equals("newsession")) {
correctSources(currentSession);
currentSession.dump(outFile);
currentSession = null;
allSources.clear();
}
else if (qName.equals("event")) {
currentSession.addEvent(currentEvent);
currentEvent = null;
}
else if (qName.equals("source")) {
currentEvent.setSource(getUniqueSource(currentSource));
currentSource = null;
}
else if (qName.equals("component")) {
currentSource.addComponent(currentComponent);
currentComponent = null;
}
else if (!qName.equals("param")) {
throw new SAXException("unexpected closing tag " + qName);
}
}
/**
*
* returns a source object, that is equal to the provided one but that is unique throughout
* the parsing process. The method may return the provided source, if this is the first
* occurrence of this source. This method is needed to reduce the amount of source
* representations that are instantiated during parsing log files.
*
*
* @param source the source to search for a unique representation
*
* @return the unique representation of the source
*/
private Source getUniqueSource(Source source) {
Source existingSource = null;
List