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

Last change on this file since 840 was 840, checked in by pharms, 12 years ago
  • corrected erroneous reuse of a component at a different place in the hierarchy just because it has the same toString parameter
  • followed the advice of Mr. Dr. Big to implement everything more defensive
File size: 39.5 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                title = icon;
627            }
628        }
629       
630        component.addParameter("title", title);
631        component.addParameter("class", clazz);
632        component.addParameter("icon", ((icon == null) ? "" : icon));
633        component.addParameter("index", "-1");
634       
635        int hashCode = clazz.hashCode() + title.hashCode();
636       
637        if (hashCode < 0) {
638            hashCode = -hashCode;
639        }
640       
641        component.addParameter("hash", Integer.toString(hashCode, 16));
642
643        return component;
644    }   
645
646    /**
647     * <p>
648     * used to dump a list of parameters to the provided print stream
649     * </p>
650     */
651    private void dumpParams(PrintStream out, List<Parameter> params, String indent) {
652        for (Parameter param : params) {
653            out.print(indent);
654            out.print("<param name=\"");
655            out.print(StringTools.xmlEntityReplacement(param.getName()));
656            out.print("\" value=\"");
657            out.print(StringTools.xmlEntityReplacement(param.getValue()));
658            out.println("\" />");
659        }
660       
661    }
662   
663    /**
664     * <p>
665     * check if two parameter lists are equal. Thea are equal if the contain the same parameters
666     * ignoring their order.
667     * </p>
668     *
669     * @param params1 the first parameter list to be compared
670     * @param params2 the second parameter list to be compared
671     *
672     * @return true if both lists contain the same parameters, false else.
673     */
674    private boolean parametersEqual(List<Parameter> params1, List<Parameter> params2) {
675        if (params1 == null) {
676            return params2 == null;
677        }
678       
679        if (params2 == null) {
680            return false;
681        }
682       
683        if (params1.size() != params2.size()) {
684            return false;
685        }
686       
687        boolean found;
688        for (Parameter param1 : params1) {
689            found = false;
690           
691            for (Parameter param2 : params2) {
692                if (param1.equals(param2)) {
693                    found = true;
694                    break;
695                }
696            }
697           
698            if (!found) {
699                return false;
700            }
701        }
702       
703        return true;
704    }
705   
706   
707    /**
708     * <p>
709     * used to carry all events of a session and to dump it to the output file
710     * </p>
711     */
712    private class Session {
713       
714        /** */
715        private String type;
716       
717        /** */
718        private List<Event> events = new ArrayList<Event>();
719       
720        /**
721         *
722         */
723        private Session(String type) {
724            if (type == null) {
725                throw new IllegalArgumentException("type must not be null");
726            }
727           
728            this.type = type;
729        }
730       
731        /**
732         *
733         */
734        private void addEvent(Event event) {
735            if (event == null) {
736                throw new IllegalArgumentException("event must not be null");
737            }
738           
739            events.add(event);
740        }
741       
742        /**
743         * @return the events
744         */
745        private List<Event> getEvents() {
746            return Collections.unmodifiableList(events);
747        }
748
749        /**
750         *
751         */
752        private void dump(PrintStream out) {
753            if (out == null) {
754                throw new IllegalArgumentException("out must not be null");
755            }
756           
757            out.print("<");
758            out.print(type);
759            out.println(">");
760
761            for (Event event : events) {
762                event.dump(out);
763            }
764           
765            out.print("</");
766            out.print(type);
767            out.println(">");
768        }
769    }
770
771    /**
772     * <p>
773     * used to carry all information about an event and to dump it to the output file
774     * </p>
775     */
776    private class Event {
777
778        /** */
779        private String id;
780
781        /** */
782        private List<Parameter> params = new ArrayList<Parameter>();
783
784        /** */
785        private Source source;
786       
787        /**
788         *
789         */
790        private Event(String id) {
791            if (id == null) {
792                throw new IllegalArgumentException("id must not be null");
793            }
794           
795            this.id = id;
796        }
797       
798        /**
799         * @param source the source to set
800         */
801        private void setSource(Source source) {
802            this.source = source;
803        }
804
805        /**
806         * @return the source
807         */
808        private Source getSource() {
809            return source;
810        }
811
812        /**
813         *
814         */
815        private void addParameter(String name, String value) {
816            if (name == null) {
817                throw new IllegalArgumentException("name must not be null");
818            }
819           
820            if (value == null) {
821                throw new IllegalArgumentException("value must not be null");
822            }
823           
824            params.add(new Parameter(name, value));
825        }
826       
827        /**
828         *
829         */
830        private void dump(PrintStream out) {
831            if (out == null) {
832                throw new IllegalArgumentException("out must not be null");
833            }
834           
835            out.print("<event id=\"");
836            out.print(StringTools.xmlEntityReplacement(id));
837            out.println("\">");
838           
839            dumpParams(out, params, " ");
840            source.dump(out);
841           
842            out.println("</event>");
843        }
844    }
845
846    /**
847     * <p>
848     * used to carry all information about a source of an event and to dump it to the output file
849     * </p>
850     */
851    private class Source {
852       
853        /** */
854        private List<Parameter> params = new ArrayList<Parameter>();
855       
856        /** */
857        private List<Component> components = new ArrayList<Component>();
858       
859        /** */
860        private String toStringValue = null;
861
862        /**
863         *
864         */
865        private String getToStringValue() {
866            if (toStringValue == null) {
867                for (Parameter param : params) {
868                    if (("toString".equals(param.getName())) &&
869                        (param.getValue() != null) && (!"".equals(param.getValue())))
870                    {
871                        toStringValue = param.getValue();
872                        break;
873                    }
874                }
875            }
876           
877            return toStringValue;
878        }
879
880        /**
881         *
882         */
883        private void addParameter(String name, String value) {
884            if (name == null) {
885                throw new IllegalArgumentException("name must not be null");
886            }
887           
888            if (value == null) {
889                throw new IllegalArgumentException("value must not be null");
890            }
891           
892            params.add(new Parameter(name, value));
893        }
894       
895        /**
896         *
897         */
898        private void addParameter(Parameter parameter) {
899            if (parameter == null) {
900                throw new IllegalArgumentException("parameter must not be null");
901            }
902           
903            params.add(parameter);
904        }
905       
906        /**
907         * @return the params
908         */
909        private List<Parameter> getParameters() {
910            return Collections.unmodifiableList(params);
911        }
912
913        /**
914         *
915         */
916        private void addComponent(Component component) {
917            if (component == null) {
918                throw new IllegalArgumentException("component must not be null");
919            }
920           
921            components.add(component);
922        }
923       
924        /**
925         * @return the components
926         */
927        private List<Component> getComponents() {
928            return Collections.unmodifiableList(components);
929        }
930
931        /**
932         *
933         */
934        private void dump(PrintStream out) {
935            if (out == null) {
936                throw new IllegalArgumentException("out must not be null");
937            }
938           
939            out.println(" <source>");
940           
941            dumpParams(out, params, "  ");
942           
943            for (Component component : components) {
944                component.dump(out);
945            }
946           
947            out.println(" </source>");
948        }
949       
950        /* (non-Javadoc)
951         * @see java.lang.Object#equals(java.lang.Object)
952         */
953        @Override
954        public boolean equals(Object obj) {
955            if (this == obj) {
956                return true;
957            }
958           
959            if (obj instanceof Source) {
960                Source other = (Source) obj;
961               
962                if ((getToStringValue() != other.getToStringValue()) ||
963                    ((getToStringValue() != null) &&
964                     (!getToStringValue().equals(other.getToStringValue()))))
965                {
966                    return false;
967                }
968               
969                if (!parametersEqual(params, other.params)) {
970                    return false;
971                }
972               
973                if (components == null) {
974                    return other.components == null;
975                }
976               
977                if (other.components == null) {
978                    return false;
979                }
980               
981                if (components.size() != other.components.size()) {
982                    return false;
983                }
984
985                for (int i = 0; i < components.size(); i++) {
986                    if (!components.get(i).equals(other.components.get(i))) {
987                        return false;
988                    }
989                }
990               
991                return true;
992            }
993           
994            return false;
995        }
996
997        /* (non-Javadoc)
998         * @see java.lang.Object#hashCode()
999         */
1000        @Override
1001        public int hashCode() {
1002            String str = getToStringValue();
1003           
1004            if (str != null) {
1005                return str.hashCode();
1006            }
1007            else {
1008                // ensure that all incomplete sources provide the same hashcode
1009                return 0;
1010            }
1011        }
1012
1013    }
1014   
1015    /**
1016     * <p>
1017     * used to carry all information about a component of a source and to dump it to the output file
1018     * </p>
1019     */
1020    private class Component {
1021       
1022        /** */
1023        private List<Parameter> params = new ArrayList<Parameter>();
1024       
1025        /**
1026         *
1027         */
1028        protected void addParameter(String name, String value) {
1029            if (name == null) {
1030                throw new IllegalArgumentException("name must not be null");
1031            }
1032           
1033            if (value == null) {
1034                throw new IllegalArgumentException("value must not be null");
1035            }
1036           
1037            params.add(new Parameter(name, value));
1038        }
1039       
1040        /**
1041         * @return the params
1042         */
1043        private List<Parameter> getParameters() {
1044            return Collections.unmodifiableList(params);
1045        }
1046
1047        /**
1048         *
1049         */
1050        protected void dump(PrintStream out) {
1051            if (out == null) {
1052                throw new IllegalArgumentException("out must not be null");
1053            }
1054           
1055            out.println("  <component>");
1056            dumpParams(out, params, "   ");
1057            out.println("  </component>");
1058        }
1059
1060        /**
1061         *
1062         */
1063        public boolean equals(Object obj) {
1064            if (this == obj) {
1065                return true;
1066            }
1067           
1068            if (obj instanceof Component) {
1069                return parametersEqual(params, ((Component) obj).params);
1070            }
1071            else {   
1072               return false;
1073            }
1074        }
1075
1076        /* (non-Javadoc)
1077         * @see java.lang.Object#hashCode()
1078         */
1079        @Override
1080        public int hashCode() {
1081            // all components with an equally sized parameter list can be equal. This does not
1082            // work, if not all component parameters are set yet. But we do not use components
1083            // in a hash map so we provide an easy implementation
1084            return params.size();
1085        }
1086    }
1087
1088    /**
1089     * <p>
1090     * represents a specific component, which was read from the toString parameter of a source
1091     * </p>
1092     */
1093    private class ComponentFromToString extends Component {
1094       
1095        /** */
1096        private int x;
1097       
1098        /** */
1099        private int y;
1100       
1101        /** */
1102        private int width;
1103       
1104        /** */
1105        private int height;
1106       
1107        /**
1108         * @param x the x to set
1109         */
1110        private void setX(int x) {
1111            this.x = x;
1112        }
1113
1114        /**
1115         * @return the x
1116         */
1117        private int getX() {
1118            return x;
1119        }
1120
1121       /**
1122         * @param y the y to set
1123         */
1124        private void setY(int y) {
1125            this.y = y;
1126        }
1127
1128        /**
1129         * @return the y
1130         */
1131        private int getY() {
1132            return y;
1133        }
1134
1135        /**
1136         * @param width the width to set
1137         */
1138        private void setWidth(int width) {
1139            this.width = width;
1140        }
1141
1142        /**
1143         * @param height the height to set
1144         */
1145        private void setHeight(int height) {
1146            this.height = height;
1147        }
1148
1149        /**
1150         *
1151         */
1152        @Override
1153        protected void dump(PrintStream out) {
1154            if (out == null) {
1155                throw new IllegalArgumentException("out must not be null");
1156            }
1157           
1158            out.println("  <component>");
1159           
1160            out.print("   ");
1161            out.print("<param name=\"x\" value=\"");
1162            out.print(x);
1163            out.println("\" />");
1164
1165            out.print("   ");
1166            out.print("<param name=\"y\" value=\"");
1167            out.print(y);
1168            out.println("\" />");
1169
1170            out.print("   ");
1171            out.print("<param name=\"width\" value=\"");
1172            out.print(width);
1173            out.println("\" />");
1174
1175            out.print("   ");
1176            out.print("<param name=\"height\" value=\"");
1177            out.print(height);
1178            out.println("\" />");
1179
1180            dumpParams(out, super.getParameters(), "   ");
1181            out.println("  </component>");
1182        }
1183       
1184        /**
1185         *
1186         */
1187        public boolean equals(Object obj) {
1188            if (!super.equals(obj)) {
1189                return false;
1190            }
1191           
1192            if (obj instanceof ComponentFromToString) {
1193                ComponentFromToString other = (ComponentFromToString) obj;
1194                return (x == other.x) && (y == other.y) &&
1195                    (width == other.width) && (height == other.height);
1196            }
1197            else {   
1198               return false;
1199            }
1200        }
1201
1202        /* (non-Javadoc)
1203         * @see java.lang.Object#hashCode()
1204         */
1205        @Override
1206        public int hashCode() {
1207            return super.hashCode() + x + y + width + height;
1208        }
1209    }
1210
1211    /**
1212     * <p>
1213     * used to carry all information about a parameter being a key and a value
1214     * </p>
1215     */
1216    private class Parameter {
1217       
1218        /** */
1219        private String name;
1220       
1221        /** */
1222        private String value;
1223       
1224        /**
1225         *
1226         */
1227        private Parameter(String name, String value) {
1228            if (name == null) {
1229                throw new IllegalArgumentException("name must not be null");
1230            }
1231           
1232            if (value == null) {
1233                throw new IllegalArgumentException("value must not be null");
1234            }
1235           
1236            this.name = name;
1237            this.value = value;
1238        }
1239
1240        /**
1241         * @return the name
1242         */
1243        private String getName() {
1244            return name;
1245        }
1246
1247        /**
1248         * @return the value
1249         */
1250        private String getValue() {
1251            return value;
1252        }
1253
1254        /* (non-Javadoc)
1255         * @see java.lang.Object#equals(java.lang.Object)
1256         */
1257        @Override
1258        public boolean equals(Object obj) {
1259            if (obj instanceof Parameter) {
1260                return
1261                    (name.equals(((Parameter) obj).name) && value.equals(((Parameter) obj).value));
1262            }
1263            else {
1264                return false;
1265            }
1266        }
1267
1268        /* (non-Javadoc)
1269         * @see java.lang.Object#hashCode()
1270         */
1271        @Override
1272        public int hashCode() {
1273            return name.hashCode() + value.hashCode();
1274        }
1275       
1276       
1277    }
1278       
1279}
Note: See TracBrowser for help on using the repository browser.