// 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.
*
* 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
*
* 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))); outFile.println(""); } catch (FileNotFoundException e1) { throw new IllegalArgumentException("could not create a corrected file name " + resultFile + " next to " + file); } 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
* convenience method to find a source based on its toString
parameter value.
* The method only returns sources, which match the provided toString
* representation and which have a valid list of components (size greater 0).
*
toString
parameter the source to find
* must have
*
* @return the source matching the parameter and having a valid list of components or null if
* none is found
*/
private Source findValidSource(String toStringValue) {
Source existingSource = null;
List* corrects all wrong sources in the events of the session. For each wrong resource, the * {@link #correctEventSource(Event, Source)} method is called. *
* * @param session the session of which the events shall be corrected */ private void correctSources(Session session) { Source previousSource = null; for (Event event : session.getEvents()) { if ((event.getSource() == null) || (event.getSource().getComponents() == null) || (event.getSource().getComponents().size() == 0)) { correctEventSource(event, previousSource); } previousSource = event.getSource(); } } /** *
* corrects the source of an event. It first searches for a correct source with an equal
* toString
parameter. If there is any, this is reused. Otherwise it creates a
* new correct source. For this, it copies all parameters of the source of the provided previous
* event if they are not included in the source already. Furthermore, it adds all components
* of the source of the previous event. At last, it adds a further component based on the
* information found in the toString
parameter of the source.
*
* determines a component based on the toString
parameter of a source.
* For this, it parses the parameter value and tries to determine several infos such as the
* type and the name of it. The resulting components are not always distinguishable. This is,
* because the toString
parameter does not contain sufficient information for
* correct identification.
*
toString
parameter of a source
*
* @return the component parsed from the toString
parameter
*/
private Component getComponentFromToString(String toStringValue) {
ComponentFromToString component = new ComponentFromToString();
// search for the beginning of the parameters section. Up to this position we find the class
int start = toStringValue.indexOf('[');
String clazz = toStringValue.substring(0, start);
// the first parameters are x and y coordinate as well as the size. The size is one
// parameter, where with and height are separated with an 'x'
start = toStringValue.indexOf(',', start) + 1;
int end = toStringValue.indexOf(',', start);
component.setX(Integer.parseInt(toStringValue.substring(start, end)));
start = end + 1;
end = toStringValue.indexOf(',', start);
component.setY(Integer.parseInt(toStringValue.substring(start, end)));
start = end + 1;
end = toStringValue.indexOf('x', start);
component.setWidth(Integer.parseInt(toStringValue.substring(start, end)));
start = end + 1;
end = toStringValue.indexOf(',', start);
component.setHeight(Integer.parseInt(toStringValue.substring(start, end)));
// no start parsing the rest of the parameters and extract those having a key and a
// value and whose key is text, defaultIcon, or an alignment
int intermediate;
start = end + 1;
String title = null;
String icon = null;
String alignment = null;
do {
end = toStringValue.indexOf(',', start);
intermediate = toStringValue.indexOf('[', start);
if ((intermediate >= 0) && (intermediate < end)) {
// the value of the parameter itself contains brackets. So try to determine the
// real end of the parameter
end = toStringValue.indexOf(']', intermediate);
end = toStringValue.indexOf(',', end);
}
if (end < 0) {
//we reached the end of the stream. So the the end to the "end"
end = toStringValue.lastIndexOf(']');
}
intermediate = toStringValue.indexOf('=', start);
if ((intermediate >= 0) && (intermediate < end)) {
// this is a key value pair, so store the the parameter
String key = toStringValue.substring(start, intermediate);
String value = toStringValue.substring(intermediate + 1, end);
if ("text".equals(key)) {
title = value;
}
else if ("defaultIcon".equals(key)) {
icon = value;
}
else if ("alignmentX".equals(key) || "alignmentY".equals(key)) {
if (alignment == null) {
alignment = value;
}
else {
alignment += "/" + value;
}
}
}
/*else {
// this is a simple value, for now simply ignore it
String key = toStringValue.substring(start, end);
if (!"invalid".equals(key)) {
componentHash += key.hashCode();
component.params.add(new String[] { key, "true" });
}
}*/
start = end + 1;
}
while (start < toStringValue.lastIndexOf(']'));
// finish the component specification by setting the parameters
if ((title == null) || "".equals(title) || "null".equals(title)) {
if ((icon == null) || "".equals(icon) || "null".equals(icon)) {
title = clazz.substring(clazz.lastIndexOf('.') + 1) + "(";
// to be able to distinguish some elements, that usually have no name and icon, try
// to include some of their specific identifying information in their name.
if ("org.tigris.gef.presentation.FigTextEditor".equals(clazz) ||
"org.argouml.core.propertypanels.ui.UMLTextField".equals(clazz))
{
title += "height " + component.height + ", ";
}
else if ("org.argouml.core.propertypanels.ui.UMLLinkedList".equals(clazz) ||
"org.argouml.core.propertypanels.ui.LabelledComponent".equals(clazz))
{
title += "position " + component.getX() + "/" + component.getY() + ", ";
}
title += "alignment " + alignment + ")";
}
else {
// to be able to distinguish some elements, that usually have no name but an icon,
// try to include some of their specific identifying information in their name.
if ("org.tigris.toolbar.toolbutton.PopupToolBoxButton".equals(clazz))
{
icon = icon.substring(0, icon.lastIndexOf('@'));
title = clazz.substring(clazz.lastIndexOf('.') + 1) + "(position " +
component.getX() + ")";
}
else {
title = icon;
}
}
}
component.addParameter("title", title);
component.addParameter("class", clazz);
component.addParameter("icon", ((icon == null) ? "" : icon));
component.addParameter("index", "-1");
int hashCode = clazz.hashCode() + title.hashCode();
if (hashCode < 0) {
hashCode = -hashCode;
}
component.addParameter("hash", Integer.toString(hashCode, 16));
return component;
}
/**
* * used to dump a list of parameters to the provided print stream *
*/ private void dumpParams(PrintStream out, List* check if two parameter lists are equal. Thea are equal if the contain the same parameters * ignoring their order. *
* * @param params1 the first parameter list to be compared * @param params2 the second parameter list to be compared * * @return true if both lists contain the same parameters, false else. */ private boolean parametersEqual(List* used to carry all events of a session and to dump it to the output file *
*/ private class Session { /** */ private String type; /** */ private List* used to carry all information about an event and to dump it to the output file *
*/ private class Event { /** */ private String id; /** */ private List* used to carry all information about a source of an event and to dump it to the output file *
*/ private class Source { /** */ private List* used to carry all information about a component of a source and to dump it to the output file *
*/ private class Component { /** */ private List* represents a specific component, which was read from the toString parameter of a source *
*/ private class ComponentFromToString extends Component { /** */ private int x; /** */ private int y; /** */ private int width; /** */ private int height; /** * @param x the x to set */ private void setX(int x) { this.x = x; } /** * @return the x */ private int getX() { return x; } /** * @param y the y to set */ private void setY(int y) { this.y = y; } /** * @return the y */ private int getY() { return y; } /** * @param width the width to set */ private void setWidth(int width) { this.width = width; } /** * @param height the height to set */ private void setHeight(int height) { this.height = height; } /** * */ @Override protected void dump(PrintStream out) { if (out == null) { throw new IllegalArgumentException("out must not be null"); } out.println("* used to carry all information about a parameter being a key and a value *
*/ private class Parameter { /** */ private String name; /** */ private String value; /** * */ private Parameter(String name, String value) { if (name == null) { throw new IllegalArgumentException("name must not be null"); } if (value == null) { throw new IllegalArgumentException("value must not be null"); } this.name = name; this.value = value; } /** * @return the name */ private String getName() { return name; } /** * @return the value */ private String getValue() { return value; } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (obj instanceof Parameter) { return (name.equals(((Parameter) obj).name) && value.equals(((Parameter) obj).value)); } else { return false; } } /* (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return name.hashCode() + value.hashCode(); } } }