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

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