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

Last change on this file since 843 was 843, checked in by pharms, 12 years ago
  • added special handling for correcting org.tigris.toolbar.toolbutton.PopupToolBoxButton? occurrences as they contain an object reference to another object in their toString representation each time they occur. Creating a name for them without this reference is much more correct and reduces the number of distinct elements in the condensed GUI model extremely.
File size: 40.1 KB
Line 
1package de.ugoe.cs.quest.plugin.jfc;
2
3import java.io.BufferedOutputStream;
4import java.io.File;
5import java.io.FileInputStream;
6import java.io.FileNotFoundException;
7import java.io.FileOutputStream;
8import java.io.IOException;
9import java.io.InputStreamReader;
10import java.io.PrintStream;
11import java.io.UnsupportedEncodingException;
12import java.util.ArrayList;
13import java.util.Collections;
14import java.util.HashMap;
15import java.util.List;
16import java.util.Map;
17
18import javax.xml.parsers.ParserConfigurationException;
19import javax.xml.parsers.SAXParser;
20import javax.xml.parsers.SAXParserFactory;
21
22import org.xml.sax.Attributes;
23import org.xml.sax.InputSource;
24import org.xml.sax.SAXException;
25import org.xml.sax.SAXParseException;
26import org.xml.sax.helpers.DefaultHandler;
27
28import de.ugoe.cs.util.StringTools;
29import de.ugoe.cs.util.console.Console;
30
31/**
32 * <p>
33 * corrects older JFC log files which sometimes do not contain correct source specifications for
34 * events. It parses the file and adds component specifications to the sources, that do not have
35 * them. For each invalid source it checks, if there is another source with the same
36 * <code>toString</code> parameter but a complete list of components. If one is found, it is reused
37 * as source for the event with the wrong source. If none is found, a new component list is
38 * generated. This contains as parent components the components of the source of the previous event.
39 * The leaf component is parsed from the <code>toString</code> parameter that is provided with the
40 * source specifications. The resulting leaf nodes are not fully correct. They may pretend to
41 * be equal although they are not. Furthermore they may resist at a position in the GUI tree where
42 * they are not in reality. But more correctness is not achievable based on the
43 * <code>toString</code> parameter.
44 * </p>
45 *
46 * @version $Revision: $ $Date: 05.09.2012$
47 * @author 2012, last modified by $Author: pharms$
48 */
49public class JFCTraceCorrector  extends DefaultHandler {
50
51    /**
52     * <p>
53     * the file to write the result into
54     * </p>
55     */
56    private PrintStream outFile;
57   
58    /**
59     * <p>
60     * the currently parsed event
61     * </p>
62     */
63    private Event currentEvent;
64
65    /**
66     * <p>
67     * the currently parsed source of the currently parsed event
68     * </p>
69     */
70    private Source currentSource;
71
72    /**
73     * <p>
74     * the list of all sources parsed in a file identified through their <code>toString</code>
75     * representation
76     * </p>
77     */
78    private Map<String, List<Source>> allSources = new HashMap<String, List<Source>>();
79
80    /**
81     * <p>
82     * the currently parsed component of the currently parsed source of the currently parsed event
83     * </p>
84     */
85    private Component currentComponent;
86
87    /**
88     * <p>
89     * the currently parsed session
90     * </p>
91     */
92    private Session currentSession;
93
94    /**
95     * <p>
96     * corrects the given file and returns the name of the file into which the result was written
97     * </p>
98     *
99     * @param filename the name of the file to be corrected
100     *
101     * @return the name of the file with the corrected logfile
102     *
103     * @throws IllegalArgumentException if the filename is null
104     */
105    public String correctFile(String filename) throws IllegalArgumentException {
106        if (filename == null) {
107            throw new IllegalArgumentException("filename must not be null");
108        }
109
110        return correctFile(new File(filename)).getAbsolutePath();
111    }
112
113    /**
114     * <p>
115     * corrects the given file, stores the result in the second provided file and returns the
116     * name of the file into which the result was written
117     * </p>
118     *
119     * @param filename   the name of the file to be corrected
120     * @param resultFile the name of the file into which the corrected log shall be written
121     *
122     * @return the name of the file with the corrected logfile
123     *
124     * @throws IllegalArgumentException if the filename or resultFile is null
125     */
126    public String correctFile(String filename, String resultFile) throws IllegalArgumentException {
127        if ((filename == null) | (resultFile == null)) {
128            throw new IllegalArgumentException("filename and resultFile must not be null");
129        }
130
131        return correctFile(new File(filename), new File(resultFile)).getAbsolutePath();
132    }
133
134    /**
135     * <p>
136     * corrects the given file and returns the file into which the result was written. The name
137     * of the resulting file is contains the suffix "_corrected" before the dot.
138     * </p>
139     *
140     * @param file the file to be corrected
141     *
142     * @return the file containing the corrected logfile
143     *
144     * @throws IllegalArgumentException if the file is null
145     */
146    public File correctFile(File file) throws IllegalArgumentException {
147        if (file == null) {
148            throw new IllegalArgumentException("file must not be null");
149        }
150
151        int index = file.getName().lastIndexOf('.');
152        String fileName =
153            file.getName().substring(0, index) + "_corrected" + file.getName().substring(index);
154
155        File resultFile = new File(file.getParentFile(), fileName);
156
157        return correctFile(file, resultFile);
158    }
159
160    /**
161     * <p>
162     * corrects the given file, stores the result in the second provided file and returns the
163     * file into which the result was written
164     * </p>
165     *
166     * @param file       the file to be corrected
167     * @param resultFile the file into which the corrected log shall be written
168     *
169     * @return the file with the corrected logfile
170     *
171     * @throws IllegalArgumentException if the file or resultFile is null or if they are equal
172     */
173    public File correctFile(File file, File resultFile) throws IllegalArgumentException {
174        if ((file == null) || (resultFile == null)) {
175            throw new IllegalArgumentException("file and result file must not be null");
176        }
177       
178        if (file.getAbsolutePath().equals(resultFile.getAbsolutePath())) {
179            throw new IllegalArgumentException("file and result file must not be equal");
180        }
181       
182        try {
183            outFile = new PrintStream(new BufferedOutputStream(new FileOutputStream(resultFile)));
184            outFile.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
185        }
186        catch (FileNotFoundException e1) {
187            throw new IllegalArgumentException("could not create a corrected file name " +
188                                               resultFile + " next to " + file);
189        }
190       
191
192        SAXParserFactory spf = SAXParserFactory.newInstance();
193        spf.setValidating(true);
194
195        SAXParser saxParser = null;
196        InputSource inputSource = null;
197        try {
198            saxParser = spf.newSAXParser();
199            inputSource =
200                new InputSource(new InputStreamReader(new FileInputStream(file), "UTF-8"));
201        }
202        catch (UnsupportedEncodingException e) {
203            Console.printerr("Error parsing file + " + file.getName());
204            Console.logException(e);
205            return null;
206        }
207        catch (ParserConfigurationException e) {
208            Console.printerr("Error parsing file + " + file.getName());
209            Console.logException(e);
210            return null;
211        }
212        catch (SAXException e) {
213            Console.printerr("Error parsing file + " + file.getName());
214            Console.logException(e);
215            return null;
216        }
217        catch (FileNotFoundException e) {
218            Console.printerr("Error parsing file + " + file.getName());
219            Console.logException(e);
220            return null;
221        }
222        if (inputSource != null) {
223            inputSource.setSystemId("file://" + file.getAbsolutePath());
224            try {
225                if (saxParser == null) {
226                    throw new RuntimeException("SAXParser creation failed");
227                }
228                saxParser.parse(inputSource, this);
229            }
230            catch (SAXParseException e) {
231                Console.printerrln("Failure parsing file in line " + e.getLineNumber() +
232                    ", column " + e.getColumnNumber() + ".");
233                Console.logException(e);
234                return null;
235            }
236            catch (SAXException e) {
237                Console.printerr("Error parsing file + " + file.getName());
238                Console.logException(e);
239                return null;
240            }
241            catch (IOException e) {
242                Console.printerr("Error parsing file + " + file.getName());
243                Console.logException(e);
244                return null;
245            }
246        }
247       
248        if (outFile != null) {
249            outFile.close();
250        }
251       
252        return resultFile;
253    }
254
255    /*
256     * (non-Javadoc)
257     *
258     * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
259     * java.lang.String, org.xml.sax.Attributes)
260     */
261    public void startElement(String uri, String localName, String qName, Attributes atts)
262        throws SAXException
263    {
264        if (qName.equals("sessions")) {
265            if (currentSession != null) {
266                throw new SAXException("nested sessions are not allowed");
267            }
268           
269            currentSession = new Session("sessions");
270        }
271        else if (qName.equals("newsession")) {
272            if (currentSession != null) {
273                currentSession.dump(outFile);
274            }
275           
276            currentSession = new Session("newsession");
277        }
278        else if (qName.equals("event")) {
279            if (currentEvent != null) {
280                throw new SAXException("nested events are not allowed");
281            }
282           
283            currentEvent = new Event(atts.getValue("id"));
284        }
285        else if (qName.equals("source")) {
286            if (currentSource != null) {
287                throw new SAXException("nested sources are not allowed");
288            }
289           
290            currentSource = new Source();
291        }
292        else if (qName.equals("component")) {
293            if (currentComponent != null) {
294                throw new SAXException("nested components are not allowed");
295            }
296           
297            currentComponent = new Component();
298        }
299        else if (qName.equals("param")) {
300            if (currentComponent != null) {
301                currentComponent.addParameter(atts.getValue("name"), atts.getValue("value"));
302            }
303            else if (currentSource != null) {
304                currentSource.addParameter(atts.getValue("name"), atts.getValue("value"));
305            }
306            else if (currentEvent != null) {
307                currentEvent.addParameter(atts.getValue("name"), atts.getValue("value"));
308            }
309            else {
310                throw new SAXException("parameter occurred at an unexpected place");
311            }
312        }
313        else {
314            throw new SAXException("unexpected tag " + qName);
315        }
316    }
317
318    /*
319     * (non-Javadoc)
320     *
321     * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
322     * java.lang.String)
323     */
324    @Override
325    public void endElement(String uri, String localName, String qName) throws SAXException {
326        // we do not have to check, if the current objects are null, as the Parser itself will
327        // throw an exception, if there is a closing tag for a non existent opening tag. But
328        // with a correct opening tag, the member variables will not be null (see startElement())
329        if (qName.equals("sessions")) {
330            correctSources(currentSession);
331
332            currentSession.dump(outFile);
333            currentSession = null;
334            allSources.clear();
335        }
336        else if (qName.equals("newsession")) {
337            correctSources(currentSession);
338           
339            currentSession.dump(outFile);
340            currentSession = null;
341            allSources.clear();
342        }
343        else if (qName.equals("event")) {
344            currentSession.addEvent(currentEvent);
345            currentEvent = null;
346        }
347        else if (qName.equals("source")) {
348            currentEvent.setSource(getUniqueSource(currentSource));
349            currentSource = null;
350        }
351        else if (qName.equals("component")) {
352            currentSource.addComponent(currentComponent);
353            currentComponent = null;
354        }
355        else if (!qName.equals("param")) {
356            throw new SAXException("unexpected closing tag " + qName);
357        }
358
359    }
360
361    /**
362     * <p>
363     * returns a source object, that is equal to the provided one but that is unique throughout
364     * the parsing process. The method may return the provided source, if this is the first
365     * occurrence of this source. This method is needed to reduce the amount of source
366     * representations that are instantiated during parsing log files.
367     * </p>
368     *
369     * @param source the source to search for a unique representation
370     *
371     * @return the unique representation of the source
372     */
373    private Source getUniqueSource(Source source) {
374        Source existingSource = null;
375       
376        List<Source> candidates = allSources.get(source.getToStringValue());
377       
378        if (candidates != null) {
379            for (Source candidate : candidates) {
380                if (candidate.equals(source)) {
381                    existingSource = candidate;
382                    break;
383                }
384            }
385        }
386       
387        if (existingSource == null) {
388            if (candidates == null) {
389                candidates = new ArrayList<Source>();
390                allSources.put(source.getToStringValue(), candidates);
391            }
392           
393            candidates.add(source);
394            existingSource = source;
395        }
396       
397        return existingSource;
398    }
399
400    /**
401     * <p>
402     * convenience method to find a source based on its <code>toString</code> parameter value.
403     * The method only returns sources, which match the provided <code>toString</code>
404     * representation and which have a valid list of components (size greater 0).
405     * </p>
406     *
407     * @param toStringValue the value of the <code>toString</code> parameter the source to find
408     *                      must have
409     *
410     * @return the source matching the parameter and having a valid list of components or null if
411     *         none is found
412     */
413    private Source findValidSource(String toStringValue) {
414        Source existingSource = null;
415       
416        List<Source> candidates = allSources.get(toStringValue);
417       
418        if (candidates != null) {
419            for (Source candidate : candidates) {
420                if ((candidate.getComponents() != null) && (candidate.getComponents().size() > 0))
421                {
422                    existingSource = candidate;
423                    break;
424                }
425            }
426        }
427       
428        return existingSource;
429    }
430
431    /**
432     * <p>
433     * corrects all wrong sources in the events of the session. For each wrong resource, the
434     * {@link #correctEventSource(Event, Source)} method is called.
435     * </p>
436     *
437     * @param session the session of which the events shall be corrected
438     */
439    private void correctSources(Session session) {
440        Source previousSource = null;
441        for (Event event : session.getEvents()) {
442            if ((event.getSource() == null) || (event.getSource().getComponents() == null) ||
443                (event.getSource().getComponents().size() == 0))
444            {
445                correctEventSource(event, previousSource);
446            }
447           
448            previousSource = event.getSource();
449        }
450    }
451
452    /**
453     * <p>
454     * corrects the source of an event. It first searches for a correct source with an equal
455     * <code>toString</code> parameter. If there is any, this is reused. Otherwise it creates a
456     * new correct source. For this, it copies all parameters of the source of the provided previous
457     * event if they are not included in the source already. Furthermore, it adds all components
458     * of the source of the previous event. At last, it adds a further component based on the
459     * information found in the <code>toString</code> parameter of the source.
460     * </p>
461     *
462     * @param event          the event of which the source must be corrected
463     * @param previousSource the source of the previous event to be potentially reused partially
464     */
465    private void correctEventSource(Event event, Source previousSource) {
466        Source existingSource = null;
467       
468        if ((event.getSource() != null) && (event.getSource().getToStringValue() != null)) {
469            existingSource = findValidSource(event.getSource().getToStringValue());
470        }
471       
472        if (existingSource != null) {
473            event.setSource(existingSource);
474        }
475        else {
476            if (previousSource != null) {
477                for (Parameter parameterOfPreviousSource : previousSource.getParameters()) {
478                    boolean foundParameter = false;
479                    for (Parameter parameter : event.getSource().getParameters()) {
480                        if (parameter.getName().equals(parameterOfPreviousSource.getName())) {
481                            foundParameter = true;
482                            break;
483                        }
484                    }
485
486                    if (!foundParameter) {
487                        event.getSource().addParameter(parameterOfPreviousSource);
488                    }
489                }
490   
491                for (Component component : previousSource.getComponents()) {
492                    if (!(component instanceof ComponentFromToString)) {
493                        event.getSource().addComponent(component);
494                    }
495                }
496            }
497
498            event.getSource().addComponent
499                (getComponentFromToString(event.getSource().getToStringValue()));
500        }
501    }
502
503    /**
504     * <p>
505     * determines a component based on the <code>toString</code> parameter of a source.
506     * For this, it parses the parameter value and tries to determine several infos such as the
507     * type and the name of it. The resulting components are not always distinguishable. This is,
508     * because the <code>toString</code> parameter does not contain sufficient information for
509     * correct identification.
510     * </p>
511     *
512     * @param toStringValue the <code>toString</code> parameter of a source
513     *
514     * @return the component parsed from the <code>toString</code> parameter
515     */
516    private Component getComponentFromToString(String toStringValue) {
517        ComponentFromToString component = new ComponentFromToString();
518       
519        // search for the beginning of the parameters section. Up to this position we find the class
520        int start = toStringValue.indexOf('[');
521        String clazz = toStringValue.substring(0, start);
522       
523        // the first parameters are x and y coordinate as well as the size. The size is one
524        // parameter, where with and height are separated with an 'x'
525        start = toStringValue.indexOf(',', start) + 1;
526        int end = toStringValue.indexOf(',', start);
527       
528        component.setX(Integer.parseInt(toStringValue.substring(start, end)));
529       
530        start = end + 1;
531        end = toStringValue.indexOf(',', start);
532
533        component.setY(Integer.parseInt(toStringValue.substring(start, end)));
534
535        start = end + 1;
536        end = toStringValue.indexOf('x', start);
537
538        component.setWidth(Integer.parseInt(toStringValue.substring(start, end)));
539
540        start = end + 1;
541        end = toStringValue.indexOf(',', start);
542
543        component.setHeight(Integer.parseInt(toStringValue.substring(start, end)));
544
545        // no start parsing the rest of the parameters and extract those having a key and a
546        // value and whose key is text, defaultIcon, or an alignment
547        int intermediate;
548        start = end + 1;
549
550        String title = null;
551        String icon = null;
552        String alignment = null;
553       
554        do {
555            end = toStringValue.indexOf(',', start);
556            intermediate = toStringValue.indexOf('[', start);
557           
558            if ((intermediate >= 0) && (intermediate < end)) {
559                // the value of the parameter itself contains brackets. So try to determine the
560                // real end of the parameter
561                end = toStringValue.indexOf(']', intermediate);
562                end = toStringValue.indexOf(',', end);
563            }
564           
565            if (end < 0) {
566                //we reached the end of the stream. So the the end to the "end"
567                end = toStringValue.lastIndexOf(']');
568            }
569           
570            intermediate = toStringValue.indexOf('=', start);
571           
572            if ((intermediate >= 0) && (intermediate < end)) {
573                // this is a key value pair, so store the the parameter
574                String key = toStringValue.substring(start, intermediate);
575                String value = toStringValue.substring(intermediate + 1, end);
576               
577                if ("text".equals(key)) {
578                    title = value;
579                }
580                else if ("defaultIcon".equals(key)) {
581                    icon = value;
582                }
583                else if ("alignmentX".equals(key) || "alignmentY".equals(key)) {
584                    if (alignment == null) {
585                        alignment = value;
586                    }
587                    else {
588                        alignment += "/" + value;
589                    }
590                }
591            }
592            /*else {
593                // this is a simple value, for now simply ignore it
594                String key = toStringValue.substring(start, end);
595                if (!"invalid".equals(key)) {
596                    componentHash += key.hashCode();
597                    component.params.add(new String[] { key, "true" });
598                }
599            }*/
600           
601            start = end + 1;
602        }
603        while (start < toStringValue.lastIndexOf(']'));
604       
605        // finish the component specification by setting the parameters
606        if ((title == null) || "".equals(title) || "null".equals(title)) {
607            if ((icon == null) || "".equals(icon) || "null".equals(icon)) {
608                title = clazz.substring(clazz.lastIndexOf('.') + 1) + "(";
609               
610                // to be able to distinguish some elements, that usually have no name and icon, try
611                // to include some of their specific identifying information in their name.
612                if ("org.tigris.gef.presentation.FigTextEditor".equals(clazz) ||
613                    "org.argouml.core.propertypanels.ui.UMLTextField".equals(clazz))
614                {
615                    title += "height " + component.height + ", ";
616                }
617                else if ("org.argouml.core.propertypanels.ui.UMLLinkedList".equals(clazz) ||
618                         "org.argouml.core.propertypanels.ui.LabelledComponent".equals(clazz))
619                {
620                    title += "position " + component.getX() + "/" + component.getY() + ", ";
621                }
622               
623                title += "alignment " + alignment + ")";
624            }
625            else {
626                // to be able to distinguish some elements, that usually have no name but an icon,
627                // try to include some of their specific identifying information in their name.
628                if ("org.tigris.toolbar.toolbutton.PopupToolBoxButton".equals(clazz))
629                {
630                    icon = icon.substring(0, icon.lastIndexOf('@'));
631                    title = clazz.substring(clazz.lastIndexOf('.') + 1) + "(position " +
632                        component.getX() + ")";
633                }
634                else {
635                    title = icon;
636                }
637            }
638        }
639       
640        component.addParameter("title", title);
641        component.addParameter("class", clazz);
642        component.addParameter("icon", ((icon == null) ? "" : icon));
643        component.addParameter("index", "-1");
644       
645        int hashCode = clazz.hashCode() + title.hashCode();
646       
647        if (hashCode < 0) {
648            hashCode = -hashCode;
649        }
650       
651        component.addParameter("hash", Integer.toString(hashCode, 16));
652
653        return component;
654    }   
655
656    /**
657     * <p>
658     * used to dump a list of parameters to the provided print stream
659     * </p>
660     */
661    private void dumpParams(PrintStream out, List<Parameter> params, String indent) {
662        for (Parameter param : params) {
663            out.print(indent);
664            out.print("<param name=\"");
665            out.print(StringTools.xmlEntityReplacement(param.getName()));
666            out.print("\" value=\"");
667            out.print(StringTools.xmlEntityReplacement(param.getValue()));
668            out.println("\" />");
669        }
670       
671    }
672   
673    /**
674     * <p>
675     * check if two parameter lists are equal. Thea are equal if the contain the same parameters
676     * ignoring their order.
677     * </p>
678     *
679     * @param params1 the first parameter list to be compared
680     * @param params2 the second parameter list to be compared
681     *
682     * @return true if both lists contain the same parameters, false else.
683     */
684    private boolean parametersEqual(List<Parameter> params1, List<Parameter> params2) {
685        if (params1 == null) {
686            return params2 == null;
687        }
688       
689        if (params2 == null) {
690            return false;
691        }
692       
693        if (params1.size() != params2.size()) {
694            return false;
695        }
696       
697        boolean found;
698        for (Parameter param1 : params1) {
699            found = false;
700           
701            for (Parameter param2 : params2) {
702                if (param1.equals(param2)) {
703                    found = true;
704                    break;
705                }
706            }
707           
708            if (!found) {
709                return false;
710            }
711        }
712       
713        return true;
714    }
715   
716   
717    /**
718     * <p>
719     * used to carry all events of a session and to dump it to the output file
720     * </p>
721     */
722    private class Session {
723       
724        /** */
725        private String type;
726       
727        /** */
728        private List<Event> events = new ArrayList<Event>();
729       
730        /**
731         *
732         */
733        private Session(String type) {
734            if (type == null) {
735                throw new IllegalArgumentException("type must not be null");
736            }
737           
738            this.type = type;
739        }
740       
741        /**
742         *
743         */
744        private void addEvent(Event event) {
745            if (event == null) {
746                throw new IllegalArgumentException("event must not be null");
747            }
748           
749            events.add(event);
750        }
751       
752        /**
753         * @return the events
754         */
755        private List<Event> getEvents() {
756            return Collections.unmodifiableList(events);
757        }
758
759        /**
760         *
761         */
762        private void dump(PrintStream out) {
763            if (out == null) {
764                throw new IllegalArgumentException("out must not be null");
765            }
766           
767            out.print("<");
768            out.print(type);
769            out.println(">");
770
771            for (Event event : events) {
772                event.dump(out);
773            }
774           
775            out.print("</");
776            out.print(type);
777            out.println(">");
778        }
779    }
780
781    /**
782     * <p>
783     * used to carry all information about an event and to dump it to the output file
784     * </p>
785     */
786    private class Event {
787
788        /** */
789        private String id;
790
791        /** */
792        private List<Parameter> params = new ArrayList<Parameter>();
793
794        /** */
795        private Source source;
796       
797        /**
798         *
799         */
800        private Event(String id) {
801            if (id == null) {
802                throw new IllegalArgumentException("id must not be null");
803            }
804           
805            this.id = id;
806        }
807       
808        /**
809         * @param source the source to set
810         */
811        private void setSource(Source source) {
812            this.source = source;
813        }
814
815        /**
816         * @return the source
817         */
818        private Source getSource() {
819            return source;
820        }
821
822        /**
823         *
824         */
825        private void addParameter(String name, String value) {
826            if (name == null) {
827                throw new IllegalArgumentException("name must not be null");
828            }
829           
830            if (value == null) {
831                throw new IllegalArgumentException("value must not be null");
832            }
833           
834            params.add(new Parameter(name, value));
835        }
836       
837        /**
838         *
839         */
840        private void dump(PrintStream out) {
841            if (out == null) {
842                throw new IllegalArgumentException("out must not be null");
843            }
844           
845            out.print("<event id=\"");
846            out.print(StringTools.xmlEntityReplacement(id));
847            out.println("\">");
848           
849            dumpParams(out, params, " ");
850            source.dump(out);
851           
852            out.println("</event>");
853        }
854    }
855
856    /**
857     * <p>
858     * used to carry all information about a source of an event and to dump it to the output file
859     * </p>
860     */
861    private class Source {
862       
863        /** */
864        private List<Parameter> params = new ArrayList<Parameter>();
865       
866        /** */
867        private List<Component> components = new ArrayList<Component>();
868       
869        /** */
870        private String toStringValue = null;
871
872        /**
873         *
874         */
875        private String getToStringValue() {
876            if (toStringValue == null) {
877                for (Parameter param : params) {
878                    if (("toString".equals(param.getName())) &&
879                        (param.getValue() != null) && (!"".equals(param.getValue())))
880                    {
881                        toStringValue = param.getValue();
882                        break;
883                    }
884                }
885            }
886           
887            return toStringValue;
888        }
889
890        /**
891         *
892         */
893        private void addParameter(String name, String value) {
894            if (name == null) {
895                throw new IllegalArgumentException("name must not be null");
896            }
897           
898            if (value == null) {
899                throw new IllegalArgumentException("value must not be null");
900            }
901           
902            params.add(new Parameter(name, value));
903        }
904       
905        /**
906         *
907         */
908        private void addParameter(Parameter parameter) {
909            if (parameter == null) {
910                throw new IllegalArgumentException("parameter must not be null");
911            }
912           
913            params.add(parameter);
914        }
915       
916        /**
917         * @return the params
918         */
919        private List<Parameter> getParameters() {
920            return Collections.unmodifiableList(params);
921        }
922
923        /**
924         *
925         */
926        private void addComponent(Component component) {
927            if (component == null) {
928                throw new IllegalArgumentException("component must not be null");
929            }
930           
931            components.add(component);
932        }
933       
934        /**
935         * @return the components
936         */
937        private List<Component> getComponents() {
938            return Collections.unmodifiableList(components);
939        }
940
941        /**
942         *
943         */
944        private void dump(PrintStream out) {
945            if (out == null) {
946                throw new IllegalArgumentException("out must not be null");
947            }
948           
949            out.println(" <source>");
950           
951            dumpParams(out, params, "  ");
952           
953            for (Component component : components) {
954                component.dump(out);
955            }
956           
957            out.println(" </source>");
958        }
959       
960        /* (non-Javadoc)
961         * @see java.lang.Object#equals(java.lang.Object)
962         */
963        @Override
964        public boolean equals(Object obj) {
965            if (this == obj) {
966                return true;
967            }
968           
969            if (obj instanceof Source) {
970                Source other = (Source) obj;
971               
972                if ((getToStringValue() != other.getToStringValue()) ||
973                    ((getToStringValue() != null) &&
974                     (!getToStringValue().equals(other.getToStringValue()))))
975                {
976                    return false;
977                }
978               
979                if (!parametersEqual(params, other.params)) {
980                    return false;
981                }
982               
983                if (components == null) {
984                    return other.components == null;
985                }
986               
987                if (other.components == null) {
988                    return false;
989                }
990               
991                if (components.size() != other.components.size()) {
992                    return false;
993                }
994
995                for (int i = 0; i < components.size(); i++) {
996                    if (!components.get(i).equals(other.components.get(i))) {
997                        return false;
998                    }
999                }
1000               
1001                return true;
1002            }
1003           
1004            return false;
1005        }
1006
1007        /* (non-Javadoc)
1008         * @see java.lang.Object#hashCode()
1009         */
1010        @Override
1011        public int hashCode() {
1012            String str = getToStringValue();
1013           
1014            if (str != null) {
1015                return str.hashCode();
1016            }
1017            else {
1018                // ensure that all incomplete sources provide the same hashcode
1019                return 0;
1020            }
1021        }
1022
1023    }
1024   
1025    /**
1026     * <p>
1027     * used to carry all information about a component of a source and to dump it to the output file
1028     * </p>
1029     */
1030    private class Component {
1031       
1032        /** */
1033        private List<Parameter> params = new ArrayList<Parameter>();
1034       
1035        /**
1036         *
1037         */
1038        protected void addParameter(String name, String value) {
1039            if (name == null) {
1040                throw new IllegalArgumentException("name must not be null");
1041            }
1042           
1043            if (value == null) {
1044                throw new IllegalArgumentException("value must not be null");
1045            }
1046           
1047            params.add(new Parameter(name, value));
1048        }
1049       
1050        /**
1051         * @return the params
1052         */
1053        private List<Parameter> getParameters() {
1054            return Collections.unmodifiableList(params);
1055        }
1056
1057        /**
1058         *
1059         */
1060        protected void dump(PrintStream out) {
1061            if (out == null) {
1062                throw new IllegalArgumentException("out must not be null");
1063            }
1064           
1065            out.println("  <component>");
1066            dumpParams(out, params, "   ");
1067            out.println("  </component>");
1068        }
1069
1070        /**
1071         *
1072         */
1073        public boolean equals(Object obj) {
1074            if (this == obj) {
1075                return true;
1076            }
1077           
1078            if (obj instanceof Component) {
1079                return parametersEqual(params, ((Component) obj).params);
1080            }
1081            else {   
1082               return false;
1083            }
1084        }
1085
1086        /* (non-Javadoc)
1087         * @see java.lang.Object#hashCode()
1088         */
1089        @Override
1090        public int hashCode() {
1091            // all components with an equally sized parameter list can be equal. This does not
1092            // work, if not all component parameters are set yet. But we do not use components
1093            // in a hash map so we provide an easy implementation
1094            return params.size();
1095        }
1096    }
1097
1098    /**
1099     * <p>
1100     * represents a specific component, which was read from the toString parameter of a source
1101     * </p>
1102     */
1103    private class ComponentFromToString extends Component {
1104       
1105        /** */
1106        private int x;
1107       
1108        /** */
1109        private int y;
1110       
1111        /** */
1112        private int width;
1113       
1114        /** */
1115        private int height;
1116       
1117        /**
1118         * @param x the x to set
1119         */
1120        private void setX(int x) {
1121            this.x = x;
1122        }
1123
1124        /**
1125         * @return the x
1126         */
1127        private int getX() {
1128            return x;
1129        }
1130
1131       /**
1132         * @param y the y to set
1133         */
1134        private void setY(int y) {
1135            this.y = y;
1136        }
1137
1138        /**
1139         * @return the y
1140         */
1141        private int getY() {
1142            return y;
1143        }
1144
1145        /**
1146         * @param width the width to set
1147         */
1148        private void setWidth(int width) {
1149            this.width = width;
1150        }
1151
1152        /**
1153         * @param height the height to set
1154         */
1155        private void setHeight(int height) {
1156            this.height = height;
1157        }
1158
1159        /**
1160         *
1161         */
1162        @Override
1163        protected void dump(PrintStream out) {
1164            if (out == null) {
1165                throw new IllegalArgumentException("out must not be null");
1166            }
1167           
1168            out.println("  <component>");
1169           
1170            out.print("   ");
1171            out.print("<param name=\"x\" value=\"");
1172            out.print(x);
1173            out.println("\" />");
1174
1175            out.print("   ");
1176            out.print("<param name=\"y\" value=\"");
1177            out.print(y);
1178            out.println("\" />");
1179
1180            out.print("   ");
1181            out.print("<param name=\"width\" value=\"");
1182            out.print(width);
1183            out.println("\" />");
1184
1185            out.print("   ");
1186            out.print("<param name=\"height\" value=\"");
1187            out.print(height);
1188            out.println("\" />");
1189
1190            dumpParams(out, super.getParameters(), "   ");
1191            out.println("  </component>");
1192        }
1193       
1194        /**
1195         *
1196         */
1197        public boolean equals(Object obj) {
1198            if (!super.equals(obj)) {
1199                return false;
1200            }
1201           
1202            if (obj instanceof ComponentFromToString) {
1203                ComponentFromToString other = (ComponentFromToString) obj;
1204                return (x == other.x) && (y == other.y) &&
1205                    (width == other.width) && (height == other.height);
1206            }
1207            else {   
1208               return false;
1209            }
1210        }
1211
1212        /* (non-Javadoc)
1213         * @see java.lang.Object#hashCode()
1214         */
1215        @Override
1216        public int hashCode() {
1217            return super.hashCode() + x + y + width + height;
1218        }
1219    }
1220
1221    /**
1222     * <p>
1223     * used to carry all information about a parameter being a key and a value
1224     * </p>
1225     */
1226    private class Parameter {
1227       
1228        /** */
1229        private String name;
1230       
1231        /** */
1232        private String value;
1233       
1234        /**
1235         *
1236         */
1237        private Parameter(String name, String value) {
1238            if (name == null) {
1239                throw new IllegalArgumentException("name must not be null");
1240            }
1241           
1242            if (value == null) {
1243                throw new IllegalArgumentException("value must not be null");
1244            }
1245           
1246            this.name = name;
1247            this.value = value;
1248        }
1249
1250        /**
1251         * @return the name
1252         */
1253        private String getName() {
1254            return name;
1255        }
1256
1257        /**
1258         * @return the value
1259         */
1260        private String getValue() {
1261            return value;
1262        }
1263
1264        /* (non-Javadoc)
1265         * @see java.lang.Object#equals(java.lang.Object)
1266         */
1267        @Override
1268        public boolean equals(Object obj) {
1269            if (obj instanceof Parameter) {
1270                return
1271                    (name.equals(((Parameter) obj).name) && value.equals(((Parameter) obj).value));
1272            }
1273            else {
1274                return false;
1275            }
1276        }
1277
1278        /* (non-Javadoc)
1279         * @see java.lang.Object#hashCode()
1280         */
1281        @Override
1282        public int hashCode() {
1283            return name.hashCode() + value.hashCode();
1284        }
1285       
1286       
1287    }
1288       
1289}
Note: See TracBrowser for help on using the repository browser.