source: trunk/quest-plugin-jfc/src/main/java/de/ugoe/cs/quest/plugin/jfc/JFCTraceCorrector.java @ 829

Last change on this file since 829 was 829, checked in by pharms, 12 years ago
  • improved correction of sources by also considering other correct sources with the same value of the toString parameter
File size: 27.3 KB
Line 
1package de.ugoe.cs.quest.plugin.jfc;
2
3import java.io.File;
4import java.io.FileInputStream;
5import java.io.FileNotFoundException;
6import java.io.FileOutputStream;
7import java.io.IOException;
8import java.io.InputStreamReader;
9import java.io.PrintStream;
10import java.io.UnsupportedEncodingException;
11import java.util.ArrayList;
12import java.util.List;
13
14import javax.xml.parsers.ParserConfigurationException;
15import javax.xml.parsers.SAXParser;
16import javax.xml.parsers.SAXParserFactory;
17
18import org.xml.sax.Attributes;
19import org.xml.sax.InputSource;
20import org.xml.sax.SAXException;
21import org.xml.sax.SAXParseException;
22import org.xml.sax.helpers.DefaultHandler;
23
24import de.ugoe.cs.util.StringTools;
25import de.ugoe.cs.util.console.Console;
26
27/**
28 * <p>
29 * corrects older JFC log files which sometimes do not contain correct source specifications for
30 * events. It parses the file and adds component specifications to the sources, that do not have
31 * them. For each invalid source it checks, if there is another source with the same
32 * <code>toString</code> parameter but a complete list of components. If one is found, it is reused
33 * as source for the event with the wrong source. If none is found, a new component list is
34 * generated. This contains as parent components the components of the source of the previous event.
35 * The leaf component is parsed from the <code>toString</code> parameter that is provided with the
36 * source specifications. The resulting leaf nodes are not fully correct. They may pretend to
37 * be equal although they are not. Furthermore they may resist at a position in the GUI tree where
38 * they are not in reality. But more correctness is not achievable based on the
39 * <code>toString</code> parameter.
40 * </p>
41 *
42 * @version $Revision: $ $Date: 05.09.2012$
43 * @author 2012, last modified by $Author: pharms$
44 */
45public class JFCTraceCorrector  extends DefaultHandler {
46
47    /**
48     * <p>
49     * the file to write the result into
50     * </p>
51     */
52    private PrintStream outFile;
53   
54    /**
55     * <p>
56     * the currently parsed event
57     * </p>
58     */
59    private Event currentEvent;
60
61    /**
62     * <p>
63     * the currently parsed source of the currently parsed event
64     * </p>
65     */
66    private Source currentSource;
67
68    /**
69     * <p>
70     * the list of all sources parsed in a file
71     * </p>
72     */
73    private List<Source> allSources = new ArrayList<Source>();
74
75    /**
76     * <p>
77     * the currently parsed component of the currently parsed source of the currently parsed event
78     * </p>
79     */
80    private Component currentComponent;
81
82    /**
83     * <p>
84     * the currently parsed session
85     * </p>
86     */
87    private Session currentSession;
88
89    /**
90     * <p>
91     * corrects the given file and returns the name of the file into which the result was written
92     * </p>
93     *
94     * @param filename the name of the file to be corrected
95     *
96     * @return the name of the file with the corrected logfile
97     *
98     * @throws IllegalArgumentException if the filename is null
99     */
100    public String correctFile(String filename) throws IllegalArgumentException {
101        if (filename == null) {
102            throw new IllegalArgumentException("filename must not be null");
103        }
104
105        return correctFile(new File(filename)).getAbsolutePath();
106    }
107
108    /**
109     * <p>
110     * corrects the given file, stores the result in the second provided file and returns the
111     * name of the file into which the result was written
112     * </p>
113     *
114     * @param filename   the name of the file to be corrected
115     * @param resultFile the name of the file into which the corrected log shall be written
116     *
117     * @return the name of the file with the corrected logfile
118     *
119     * @throws IllegalArgumentException if the filename or resultFile is null
120     */
121    public String correctFile(String filename, String resultFile) throws IllegalArgumentException {
122        if ((filename == null) | (resultFile == null)) {
123            throw new IllegalArgumentException("filename and resultFile must not be null");
124        }
125
126        return correctFile(new File(filename), new File(resultFile)).getAbsolutePath();
127    }
128
129    /**
130     * <p>
131     * corrects the given file and returns the file into which the result was written. The name
132     * of the resulting file is contains the suffix "_corrected" before the dot.
133     * </p>
134     *
135     * @param file the file to be corrected
136     *
137     * @return the file containing the corrected logfile
138     *
139     * @throws IllegalArgumentException if the file is null
140     */
141    public File correctFile(File file) throws IllegalArgumentException {
142        if (file == null) {
143            throw new IllegalArgumentException("file must not be null");
144        }
145
146        int index = file.getName().lastIndexOf('.');
147        String fileName =
148            file.getName().substring(0, index) + "_corrected" + file.getName().substring(index);
149
150        File resultFile = new File(file.getParentFile(), fileName);
151
152        return correctFile(file, resultFile);
153    }
154
155    /**
156     * <p>
157     * corrects the given file, stores the result in the second provided file and returns the
158     * file into which the result was written
159     * </p>
160     *
161     * @param file       the file to be corrected
162     * @param resultFile the file into which the corrected log shall be written
163     *
164     * @return the file with the corrected logfile
165     *
166     * @throws IllegalArgumentException if the file or resultFile is null or if they are equal
167     */
168    public File correctFile(File file, File resultFile) throws IllegalArgumentException {
169        if ((file == null) || (resultFile == null)) {
170            throw new IllegalArgumentException("file and result file must not be null");
171        }
172       
173        if (file.getAbsolutePath().equals(resultFile.getAbsolutePath())) {
174            throw new IllegalArgumentException("file and result file must not be equal");
175        }
176       
177        try {
178            outFile = new PrintStream(new FileOutputStream(resultFile));
179            outFile.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
180        }
181        catch (FileNotFoundException e1) {
182            throw new IllegalArgumentException("could not create a corrected file name " +
183                                               resultFile + " next to " + file);
184        }
185       
186
187        SAXParserFactory spf = SAXParserFactory.newInstance();
188        spf.setValidating(true);
189
190        SAXParser saxParser = null;
191        InputSource inputSource = null;
192        try {
193            saxParser = spf.newSAXParser();
194            inputSource =
195                new InputSource(new InputStreamReader(new FileInputStream(file), "UTF-8"));
196        }
197        catch (UnsupportedEncodingException e) {
198            Console.printerr("Error parsing file + " + file.getName());
199            Console.logException(e);
200            return null;
201        }
202        catch (ParserConfigurationException e) {
203            Console.printerr("Error parsing file + " + file.getName());
204            Console.logException(e);
205            return null;
206        }
207        catch (SAXException e) {
208            Console.printerr("Error parsing file + " + file.getName());
209            Console.logException(e);
210            return null;
211        }
212        catch (FileNotFoundException e) {
213            Console.printerr("Error parsing file + " + file.getName());
214            Console.logException(e);
215            return null;
216        }
217        if (inputSource != null) {
218            inputSource.setSystemId("file://" + file.getAbsolutePath());
219            try {
220                if (saxParser == null) {
221                    throw new RuntimeException("SAXParser creation failed");
222                }
223                saxParser.parse(inputSource, this);
224            }
225            catch (SAXParseException e) {
226                Console.printerrln("Failure parsing file in line " + e.getLineNumber() +
227                    ", column " + e.getColumnNumber() + ".");
228                Console.logException(e);
229                return null;
230            }
231            catch (SAXException e) {
232                Console.printerr("Error parsing file + " + file.getName());
233                Console.logException(e);
234                return null;
235            }
236            catch (IOException e) {
237                Console.printerr("Error parsing file + " + file.getName());
238                Console.logException(e);
239                return null;
240            }
241        }
242       
243        if (outFile != null) {
244            outFile.close();
245        }
246       
247        return resultFile;
248    }
249
250    /*
251     * (non-Javadoc)
252     *
253     * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
254     * java.lang.String, org.xml.sax.Attributes)
255     */
256    public void startElement(String uri, String localName, String qName, Attributes atts)
257        throws SAXException
258    {
259        if (qName.equals("sessions")) {
260            currentSession = new Session();
261            currentSession.type = "sessions";
262        }
263        else if (qName.equals("newsession")) {
264            if (currentSession != null) {
265                currentSession.dump(outFile);
266            }
267           
268            currentSession = new Session();
269            currentSession.type = "newsession";
270        }
271        else if (qName.equals("event")) {
272            currentEvent = new Event();
273            currentEvent.id = atts.getValue("id");
274        }
275        else if (qName.equals("source")) {
276            currentSource = new Source();
277        }
278        else if (qName.equals("component")) {
279            currentComponent = new Component();
280        }
281        else if (qName.equals("param")) {
282            if (currentComponent != null) {
283                currentComponent.params.add
284                    (new String[] {atts.getValue("name"), atts.getValue("value") });
285            }
286            else if (currentSource != null) {
287                currentSource.params.add
288                    (new String[] {atts.getValue("name"), atts.getValue("value") });
289            }
290            else if (currentEvent != null) {
291                currentEvent.params.add
292                    (new String[] {atts.getValue("name"), atts.getValue("value") });
293            }
294            else {
295                throw new SAXException("parameter occurred at an unexpected place");
296            }
297        }
298        else {
299            throw new SAXException("unexpected tag " + qName);
300        }
301    }
302
303    /*
304     * (non-Javadoc)
305     *
306     * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
307     * java.lang.String)
308     */
309    @Override
310    public void endElement(String uri, String localName, String qName) throws SAXException {
311        if (qName.equals("sessions")) {
312            correctSources(currentSession);
313
314            currentSession.dump(outFile);
315            currentSession = null;
316        }
317        else if (qName.equals("newsession")) {
318            correctSources(currentSession);
319           
320            currentSession.dump(outFile);
321            currentSession = null;
322        }
323        else if (qName.equals("event")) {
324            currentSession.events.add(currentEvent);
325            currentEvent = null;
326        }
327        else if (qName.equals("source")) {
328            rememberSource(currentSource);
329            currentEvent.source = currentSource;
330            currentSource = null;
331        }
332        else if (qName.equals("component")) {
333            currentSource.components.add(currentComponent);
334            currentComponent = null;
335        }
336        else if (!qName.equals("param")) {
337            throw new SAXException("unexpected closing tag " + qName);
338        }
339
340    }
341
342    /**
343     * <p>
344     * stores a parsed source for later correction or reuse
345     * </p>
346     *
347     * @param source the source to store
348     */
349    private void rememberSource(Source source) {
350        allSources.add(source);
351    }
352
353    /**
354     * <p>
355     * corrects all wrong sources in the events of the session. For each wrong resource, the
356     * {@link #correctEventSource(Event, Source)} method is called.
357     * </p>
358     *
359     * @param session the session of which the events shall be corrected
360     */
361    private void correctSources(Session session) {
362        Source previousSource = null;
363        for (Event event : session.events) {
364            if ((event.source == null) || (event.source.components == null) ||
365                (event.source.components.size() == 0))
366            {
367                correctEventSource(event, previousSource);
368            }
369           
370            previousSource = event.source;
371        }
372    }
373
374    /**
375     * <p>
376     * corrects the source of an event. It first searches for a correct source with an equal
377     * <code>toString</code> parameter. If there is any, this is reused. Otherwise it creates a
378     * new correct source. For this, it copies all parameters of the source of the provided previous
379     * event if they are not included in the source already. Furthermore, it adds all components
380     * of the source of the previous event. At last, it adds a further component based on the
381     * information found in the <code>toString</code> parameter of the source.
382     * </p>
383     *
384     * @param event          the event of which the source must be corrected
385     * @param previousSource the source of the previous event to be potentially reused partially
386     */
387    private void correctEventSource(Event event, Source previousSource) {
388        String toStringValue = null;
389       
390        if ((event.source != null) && (event.source.params != null)) {
391            toStringValue = getToStringParam(event.source);
392        }
393       
394        Source existingSource = null;
395       
396        if (toStringValue != null) {
397            for (Source candidate : allSources) {
398                if (toStringValue.equals(getToStringParam(candidate)) &&
399                    (candidate.components != null) && (candidate.components.size() > 0))
400                {
401                    existingSource = candidate;
402                }
403            }
404        }
405       
406        if (existingSource != null) {
407            event.source = existingSource;
408        }
409        else {
410            if (previousSource != null) {
411                for (String[] parameterOfPreviousSource : previousSource.params) {
412                    boolean foundParameter = false;
413                    for (String[] parameter : event.source.params) {
414                        if (parameter[0].equals(parameterOfPreviousSource[0])) {
415                            foundParameter = true;
416                            break;
417                        }
418                    }
419
420                    if (!foundParameter) {
421                        event.source.params.add(parameterOfPreviousSource);
422                    }
423                }
424   
425                for (Component component : previousSource.components) {
426                    if (!(component instanceof ComponentFromToString)) {
427                        event.source.components.add(component);
428                    }
429                }
430            }
431
432            event.source.components.add(getComponentFromToString(toStringValue));
433        }
434    }
435
436    /**
437     * <p>
438     * retrieves the value of the <code>toString</code> parameter of the provided source.
439     * </p>
440     *
441     * @param source the source to read the parameter of
442     * @return the value of the parameter
443     */
444    private String getToStringParam(Source source) {
445        String value = null;
446       
447        for (String[] param : source.params) {
448            if (("toString".equals(param[0])) && (param[1] != null) && (!"".equals(param[1]))) {
449                value = param[1];
450                break;
451            }
452        }
453       
454        return value;
455    }
456
457    /**
458     * <p>
459     * determines a component based on the <code>toString</code> parameter of a source.
460     * For this, it parses the parameter value and tries to determine several infos such as the
461     * type and the name of it. The resulting components are not always distinguishable. This is,
462     * because the <code>toString</code> parameter does not contain sufficient information for
463     * correct identification.
464     * </p>
465     *
466     * @param toStringValue the <code>toString</code> parameter of a source
467     *
468     * @return the component parsed from the <code>toString</code> parameter
469     */
470    private Component getComponentFromToString(String toStringValue) {
471        ComponentFromToString component = new ComponentFromToString();
472       
473        // search for the beginning of the parameters section. Up to this position we find the class
474        int start = toStringValue.indexOf('[');
475        String clazz = toStringValue.substring(0, start);
476       
477        // the first parameters are x and y coordinate as well as the size. The size is one
478        // parameter, where with and height are separated with an 'x'
479        start = toStringValue.indexOf(',', start) + 1;
480        int end = toStringValue.indexOf(',', start);
481       
482        component.x = Integer.parseInt(toStringValue.substring(start, end));
483       
484        start = end + 1;
485        end = toStringValue.indexOf(',', start);
486
487        component.y = Integer.parseInt(toStringValue.substring(start, end));
488
489        start = end + 1;
490        end = toStringValue.indexOf('x', start);
491
492        component.width = Integer.parseInt(toStringValue.substring(start, end));
493
494        start = end + 1;
495        end = toStringValue.indexOf(',', start);
496
497        component.height = Integer.parseInt(toStringValue.substring(start, end));
498
499        // no start parsing the rest of the parameters and extract those having a key and a
500        // value and whose key is text, defaultIcon, or an alignment
501        int intermediate;
502        start = end + 1;
503
504        String title = null;
505        String icon = null;
506        String alignment = null;
507       
508        do {
509            end = toStringValue.indexOf(',', start);
510            intermediate = toStringValue.indexOf('[', start);
511           
512            if ((intermediate >= 0) && (intermediate < end)) {
513                // the value of the parameter itself contains brackets. So try to determine the
514                // real end of the parameter
515                end = toStringValue.indexOf(']', intermediate);
516                end = toStringValue.indexOf(',', end);
517            }
518           
519            if (end < 0) {
520                //we reached the end of the stream. So the the end to the "end"
521                end = toStringValue.lastIndexOf(']');
522            }
523           
524            intermediate = toStringValue.indexOf('=', start);
525           
526            if ((intermediate >= 0) && (intermediate < end)) {
527                // this is a key value pair, so store the the parameter
528                String key = toStringValue.substring(start, intermediate);
529                String value = toStringValue.substring(intermediate + 1, end);
530               
531                if ("text".equals(key)) {
532                    title = value;
533                }
534                else if ("defaultIcon".equals(key)) {
535                    icon = value;
536                }
537                else if ("alignmentX".equals(key) || "alignmentY".equals(key)) {
538                    if (alignment == null) {
539                        alignment = value;
540                    }
541                    else {
542                        alignment += "/" + value;
543                    }
544                }
545            }
546            /*else {
547                // this is a simple value, for now simply ignore it
548                String key = toStringValue.substring(start, end);
549                if (!"invalid".equals(key)) {
550                    componentHash += key.hashCode();
551                    component.params.add(new String[] { key, "true" });
552                }
553            }*/
554           
555            start = end + 1;
556        }
557        while (start < toStringValue.lastIndexOf(']'));
558       
559        // finish the component specification by setting the parameters
560        if ((title == null) || "".equals(title) || "null".equals(title)) {
561            if ((icon == null) || "".equals(icon) || "null".equals(icon)) {
562                title = clazz.substring(clazz.lastIndexOf('.') + 1) + "(";
563               
564                // to be able to distinguish some elements, that usually have no name and icon, try
565                // to include some of their specific identifying information in their name.
566                if ("org.tigris.gef.presentation.FigTextEditor".equals(clazz) ||
567                    "org.argouml.core.propertypanels.ui.UMLTextField".equals(clazz))
568                {
569                    title += "height " + component.height + ", ";
570                }
571                else if ("org.argouml.core.propertypanels.ui.UMLLinkedList".equals(clazz) ||
572                         "org.argouml.core.propertypanels.ui.LabelledComponent".equals(clazz))
573                {
574                    title += "position " + component.x + "/" + component.y + ", ";
575                }
576               
577                title += "alignment " + alignment + ")";
578            }
579            else {
580                title = icon;
581            }
582        }
583       
584        component.params.add(new String[] { "title", title } );
585        component.params.add(new String[] { "class", clazz } );
586        component.params.add(new String[] { "icon", icon } );
587        component.params.add(new String[] { "index", "-1" });
588       
589        int hashCode = clazz.hashCode() + title.hashCode();
590       
591        if (hashCode < 0) {
592            hashCode = -hashCode;
593        }
594       
595        component.params.add(new String[] { "hash", Integer.toString(hashCode, 16) });
596
597        return component;
598    }   
599
600    /**
601     * <p>
602     * used to dump a list of parameters to the provided print stream
603     * </p>
604     */
605    private void dumpParams(PrintStream out, List<String[]> params, String indent) {
606        for (String[] param : params) {
607            out.print(indent);
608            out.print("<param name=\"");
609            out.print(StringTools.xmlEntityReplacement(param[0]));
610            out.print("\" value=\"");
611            out.print(StringTools.xmlEntityReplacement(param[1]));
612            out.println("\" />");
613        }
614       
615    }
616   
617    /**
618     * <p>
619     * used to carry all events of a session and to dump it to the output file
620     * </p>
621     */
622    private class Session {
623        private String type;
624        private List<Event> events = new ArrayList<Event>();
625       
626        public void dump(PrintStream out) {
627            out.print("<");
628            out.print(type);
629            out.println(">");
630
631            for (Event event : events) {
632                event.dump(out);
633            }
634           
635            out.print("</");
636            out.print(type);
637            out.println(">");
638        }
639    }
640
641    /**
642     * <p>
643     * used to carry all information about an event and to dump it to the output file
644     * </p>
645     */
646    private class Event {
647        private String id;
648        private List<String[]> params = new ArrayList<String[]>();
649        private Source source;
650       
651        private void dump(PrintStream out) {
652            out.print("<event id=\"");
653            out.print(StringTools.xmlEntityReplacement(id));
654            out.println("\">");
655           
656            dumpParams(out, params, " ");
657            source.dump(out);
658           
659            out.println("</event>");
660        }
661    }
662
663    /**
664     * <p>
665     * used to carry all information about a source of an event and to dump it to the output file
666     * </p>
667     */
668    private class Source {
669        private List<String[]> params = new ArrayList<String[]>();
670        private List<Component> components = new ArrayList<Component>();
671
672        private void dump(PrintStream out) {
673            out.println(" <source>");
674           
675            dumpParams(out, params, "  ");
676           
677            for (Component component : components) {
678                component.dump(out);
679            }
680           
681            out.println(" </source>");
682        }
683    }
684   
685    /**
686     * <p>
687     * used to carry all information about a component of a source and to dump it to the output file
688     * </p>
689     */
690    private class Component {
691        protected List<String[]> params = new ArrayList<String[]>();
692       
693        protected void dump(PrintStream out) {
694            out.println("  <component>");
695            dumpParams(out, params, "   ");
696            out.println("  </component>");
697        }
698
699        public boolean equals(Object other) {
700            if (this == other) {
701                return true;
702            }
703           
704            if (!(other instanceof Component)) {
705                return false;
706            }
707           
708            Component otherComp = (Component) other;
709           
710            boolean allParamsEqual = (params.size() == otherComp.params.size());
711           
712            if (allParamsEqual) {
713                for (int i = 0; i < params.size(); i++) {
714                    if (!params.get(i)[0].equals(otherComp.params.get(i)[0]) &&
715                        !params.get(i)[1].equals(otherComp.params.get(i)[1]))
716                    {
717                        allParamsEqual = false;
718                        break;
719                    }
720                }
721            }
722           
723            return allParamsEqual;
724        }
725    }
726
727    /**
728     * <p>
729     * represents a specific component, which was read from the toString parameter of a source
730     * </p>
731     */
732    private class ComponentFromToString extends Component {
733        private int x;
734        private int y;
735        private int width;
736        private int height;
737       
738        @Override
739        protected void dump(PrintStream out) {
740            out.println("  <component>");
741           
742            out.print("   ");
743            out.print("<param name=\"x\" value=\"");
744            out.print(x);
745            out.println("\" />");
746
747            out.print("   ");
748            out.print("<param name=\"y\" value=\"");
749            out.print(y);
750            out.println("\" />");
751
752            out.print("   ");
753            out.print("<param name=\"width\" value=\"");
754            out.print(width);
755            out.println("\" />");
756
757            out.print("   ");
758            out.print("<param name=\"height\" value=\"");
759            out.print(height);
760            out.println("\" />");
761
762            dumpParams(out, params, "   ");
763            out.println("  </component>");
764        }
765       
766        /*public boolean equals(Object other) {
767            if (this == other) {
768                return true;
769            }
770           
771            if (!(other instanceof ComponentFromToString)) {
772                return false;
773            }
774           
775            ComponentFromToString otherComp = (ComponentFromToString) other;
776           
777            // ignore the coordinates and the width as it may change over time
778            boolean allParamsEqual =
779                (height == otherComp.height) && (params.size() == otherComp.params.size());
780           
781            if (allParamsEqual) {
782                for (int i = 0; i < params.size(); i++) {
783                    if (!"x".equals(params.get(i)[0]) && !"y".equals(params.get(i)[0]) &&
784                        !"width".equals(params.get(i)[0]) && !"height".equals(params.get(i)[0]) &&
785                        !"index".equals(params.get(i)[0]) && !"hash".equals(params.get(i)[0]) &&
786                        !params.get(i)[0].equals(otherComp.params.get(i)[0]) &&
787                        !params.get(i)[1].equals(otherComp.params.get(i)[1]))
788                    {
789                        allParamsEqual = false;
790                        break;
791                    }
792                }
793            }
794           
795            return allParamsEqual;
796        }*/
797    }
798
799}
Note: See TracBrowser for help on using the repository browser.