KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > htmlparser > lexerapplications > thumbelina > Thumbelina


1 // HTMLParser Library $Name: v1_5_20050313 $ - A java-based parser for HTML
2
// http://sourceforge.org/projects/htmlparser
3
// Copyright (C) 2003 Derrick Oswald
4
//
5
// Revision Control Information
6
//
7
// $Source: /cvsroot/htmlparser/htmlparser/src/org/htmlparser/lexerapplications/thumbelina/Thumbelina.java,v $
8
// $Author: derrickoswald $
9
// $Date: 2005/02/13 20:36:00 $
10
// $Revision: 1.7 $
11
//
12
// This library is free software; you can redistribute it and/or
13
// modify it under the terms of the GNU Lesser General Public
14
// License as published by the Free Software Foundation; either
15
// version 2.1 of the License, or (at your option) any later version.
16
//
17
// This library is distributed in the hope that it will be useful,
18
// but WITHOUT ANY WARRANTY; without even the implied warranty of
19
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20
// Lesser General Public License for more details.
21
//
22
// You should have received a copy of the GNU Lesser General Public
23
// License along with this library; if not, write to the Free Software
24
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25
//
26

27 package org.htmlparser.lexerapplications.thumbelina;
28
29 import java.awt.BorderLayout JavaDoc;
30 import java.awt.Component JavaDoc;
31 import java.awt.Image JavaDoc;
32 import java.awt.event.ItemEvent JavaDoc;
33 import java.awt.event.ItemListener JavaDoc;
34 import java.awt.event.WindowAdapter JavaDoc;
35 import java.awt.event.WindowEvent JavaDoc;
36 import java.awt.image.ImageObserver JavaDoc;
37 import java.beans.PropertyChangeListener JavaDoc;
38 import java.beans.PropertyChangeSupport JavaDoc;
39 import java.io.IOException JavaDoc;
40 import java.net.MalformedURLException JavaDoc;
41 import java.net.URL JavaDoc;
42 import java.util.ArrayList JavaDoc;
43 import java.util.HashMap JavaDoc;
44
45 import javax.swing.BoxLayout JavaDoc;
46 import javax.swing.DefaultListModel JavaDoc;
47 import javax.swing.JCheckBox JavaDoc;
48 import javax.swing.JFrame JavaDoc;
49 import javax.swing.JLabel JavaDoc;
50 import javax.swing.JList JavaDoc;
51 import javax.swing.JOptionPane JavaDoc;
52 import javax.swing.JPanel JavaDoc;
53 import javax.swing.JProgressBar JavaDoc;
54 import javax.swing.JScrollPane JavaDoc;
55 import javax.swing.JSlider JavaDoc;
56 import javax.swing.JSplitPane JavaDoc;
57 import javax.swing.JTextField JavaDoc;
58 import javax.swing.ListSelectionModel JavaDoc;
59 import javax.swing.ScrollPaneConstants JavaDoc;
60 import javax.swing.border.BevelBorder JavaDoc;
61 import javax.swing.event.ChangeEvent JavaDoc;
62 import javax.swing.event.ChangeListener JavaDoc;
63 import javax.swing.event.ListSelectionEvent JavaDoc;
64 import javax.swing.event.ListSelectionListener JavaDoc;
65
66 import org.htmlparser.Node;
67 import org.htmlparser.Tag;
68 import org.htmlparser.lexer.Lexer;
69 import org.htmlparser.util.ParserException;
70
71 /**
72  * View images behind thumbnails.
73  */

74 public class Thumbelina
75     extends
76         JPanel JavaDoc // was: java.awt.Canvas
77
implements
78         Runnable JavaDoc,
79         ItemListener JavaDoc,
80         ChangeListener JavaDoc,
81         ListSelectionListener JavaDoc
82 {
83     /**
84      * Property name for current URL binding.
85      */

86     public static final String JavaDoc PROP_CURRENT_URL_PROPERTY = "currentURL";
87     /**
88      * Property name for queue size binding.
89      */

90     public static final String JavaDoc PROP_URL_QUEUE_PROPERTY = "queueSize";
91     /**
92      * Property name for visited URL size binding.
93      */

94     public static final String JavaDoc PROP_URL_VISITED_PROPERTY = "visitedSize";
95
96     /**
97      * URL's to visit.
98      */

99     private ArrayList JavaDoc mUrls;
100
101     /**
102      * URL's visited.
103      */

104     protected HashMap JavaDoc mVisited;
105
106     /**
107      * Images requested.
108      */

109     protected HashMap JavaDoc mRequested;
110
111     /**
112      * Images being tracked currently.
113      */

114     protected HashMap JavaDoc mTracked;
115
116     /**
117      * Background thread.
118      */

119     protected Thread JavaDoc mThread;
120
121     /**
122      * Activity state.
123      * <code>true</code> means processing URLS, <code>false</code> not.
124      */

125     protected boolean mActive;
126
127     /**
128      * The picture sequencer.
129      */

130     protected Sequencer mSequencer;
131
132     /**
133      * The central area for pictures.
134      */

135     protected PicturePanel mPicturePanel;
136
137     /**
138      * Value returned when no links are discovered.
139      */

140     protected static final URL JavaDoc[][] NONE = { { }, { } };
141
142     /**
143      * Bound property support.
144      */

145     protected PropertyChangeSupport JavaDoc mPropertySupport;
146
147     /**
148      * The URL being currently being examined.
149      */

150     protected String JavaDoc mCurrentURL;
151
152     /**
153      * If <code>true</code>, does not follow links containing cgi calls.
154      */

155     protected boolean mDiscardCGI;
156
157     /**
158      * If <code>true</code>, does not follow links containing queries (?).
159      */

160     protected boolean mDiscardQueries;
161
162     /**
163      * Background thread checkbox in status bar.
164      */

165     protected JCheckBox JavaDoc mBackgroundToggle;
166
167     /**
168      * History list.
169      */

170     protected JList JavaDoc mHistory;
171
172     /**
173      * Scroller for the picture panel.
174      */

175     protected JScrollPane JavaDoc mPicturePanelScroller;
176
177     /**
178      * Scroller for the history list.
179      */

180     protected JScrollPane JavaDoc mHistoryScroller;
181
182     /**
183      * Main panel in central area.
184      */

185     protected JSplitPane JavaDoc mMainArea;
186
187     /**
188      * Status bar.
189      */

190     protected JPanel JavaDoc mPowerBar;
191
192     /**
193      * Image request queue monitor in status bar.
194      */

195     protected JProgressBar JavaDoc mQueueProgress;
196
197     /**
198      * Image ready queue monitor in status bar.
199      */

200     protected JProgressBar JavaDoc mReadyProgress;
201
202     /**
203      * Sequencer thread toggle in status bar.
204      */

205     protected JCheckBox JavaDoc mRunToggle;
206
207     /**
208      * Sequencer speed slider in status bar.
209      */

210     protected JSlider JavaDoc mSpeedSlider;
211
212     /**
213      * URL report in status bar.
214      */

215     protected JTextField JavaDoc mUrlText;
216
217     /**
218      * URL queue size display in status bar.
219      */

220     protected JLabel JavaDoc mQueueSize;
221
222     /**
223      * URL visited count display in status bar.
224      */

225     protected JLabel JavaDoc mVisitedSize;
226
227     /**
228      * Creates a new instance of Thumbelina.
229      */

230     public Thumbelina ()
231     {
232         this ((URL JavaDoc)null);
233     }
234
235     /**
236      * Creates a new instance of Thumbelina.
237      * @param url Single URL to enter into the 'to follow' list.
238      * @exception MalformedURLException If the url is malformed.
239      */

240     public Thumbelina (final String JavaDoc url)
241         throws
242             MalformedURLException JavaDoc
243     {
244         this (null == url ? null : new URL JavaDoc (url));
245     }
246
247     /**
248      * Creates a new instance of Thumbelina.
249      * @param url URL to enter into the 'to follow' list.
250      */

251     public Thumbelina (final URL JavaDoc url)
252     {
253         mUrls = new ArrayList JavaDoc ();
254         mVisited = new HashMap JavaDoc ();
255         mRequested = new HashMap JavaDoc ();
256         mTracked = new HashMap JavaDoc ();
257         mThread = null;
258         mActive = true;
259         mPicturePanel = new PicturePanel (this);
260         mSequencer = new Sequencer (this);
261         mPropertySupport = new PropertyChangeSupport JavaDoc (this);
262         mCurrentURL = null;
263         mDiscardCGI = true;
264         mDiscardQueries = true;
265
266         // JComponent specific
267
setDoubleBuffered (false);
268         setLayout (new java.awt.BorderLayout JavaDoc ());
269         mPicturePanel.setDoubleBuffered (false);
270
271         mThread = new Thread JavaDoc (this);
272         mThread.setName ("BackgroundThread");
273         mThread.start ();
274         initComponents ();
275
276         mRunToggle.addItemListener (this);
277         mBackgroundToggle.addItemListener (this);
278         mSpeedSlider.addChangeListener (this);
279         mHistory.addListSelectionListener (this);
280
281         memCheck ();
282
283         if (null != url)
284             append (url);
285     }
286
287     /**
288      * Check for low memory situation.
289      * Report to the user a bad situation.
290      */

291     protected void memCheck ()
292     {
293         Runtime JavaDoc runtime;
294         long maximum;
295
296         if (System.getProperty ("java.version").startsWith ("1.4"))
297         {
298             runtime = Runtime.getRuntime ();
299             runtime.gc ();
300             maximum = runtime.maxMemory ();
301             if (maximum < 67108864L) // 64MB
302
JOptionPane.showMessageDialog (
303                     null,
304                     "Maximum available memory is low (" + maximum + " bytes).\n"
305                     + "\n"
306                     + "It is strongly suggested to increase the maximum memory\n"
307                     + "available by using the JVM command line switch -Xmx with\n"
308                     + "a suitable value, such as -Xmx256M for example.",
309                     "Thumbelina - Low memory",
310                     JOptionPane.WARNING_MESSAGE,
311                     null /*Icon*/);
312         }
313     }
314
315     /**
316      * Reset this Thumbelina.
317      * Clears the sequencer of pending images, resets the picture panel,
318      * emptiies the 'to be examined' list of URLs.
319      */

320     public void reset ()
321     {
322         int oldsize;
323
324         synchronized (mUrls)
325         {
326             mSequencer.reset ();
327             mPicturePanel.reset ();
328             oldsize = mUrls.size ();
329             mUrls.clear ();
330         }
331         updateQueueSize (oldsize, mUrls.size ());
332     }
333
334     /**
335      * Append the given URL to the queue.
336      * Adds the url only if it isn't already in the queue,
337      * and notifys listeners about the addition.
338      * @param url The url to add.
339      */

340     public void append (final URL JavaDoc url)
341     {
342         String JavaDoc href;
343         boolean found;
344         URL JavaDoc u;
345         int oldsize;
346
347         href = url.toExternalForm ();
348         found = false;
349         oldsize = -1;
350         synchronized (mUrls)
351         {
352             for (int i = 0; !found && (i < mUrls.size ()); i++)
353             {
354                 u = (URL JavaDoc)mUrls.get (i);
355                 if (href.equals (u.toExternalForm ()))
356                     found = true;
357             }
358             if (!found)
359             {
360                 oldsize = mUrls.size ();
361                 mUrls.add (url);
362                 mUrls.notify ();
363             }
364         }
365         if (-1 != oldsize)
366             updateQueueSize (oldsize, mUrls.size ());
367     }
368
369     /**
370      * Append the given URLs to the queue.
371      * @param list The list of URL objects to add.
372      */

373     public void append (final ArrayList JavaDoc list)
374     {
375         for (int i = 0; i < list.size (); i++)
376             append ((URL JavaDoc)list.get (i));
377     }
378
379     /**
380      * Filter URLs and add to queue.
381      * Removes already visited links and appends the rest (if any) to the
382      * visit pending list.
383      * @param urls The list of URL's to add to the 'to visit' list.
384      * @return Returns the filered list.
385      */

386     protected ArrayList JavaDoc filter (final URL JavaDoc[] urls)
387     {
388         ArrayList JavaDoc list;
389         URL JavaDoc url;
390         String JavaDoc ref;
391
392         list = new ArrayList JavaDoc ();
393         for (int i = 0; i < urls.length; i++)
394         {
395             url = urls[i];
396             ref = url.toExternalForm ();
397             // ignore cgi
398
if (!mDiscardCGI || (-1 == ref.indexOf ("/cgi-bin/")))
399                 // ignore queries
400
if (!mDiscardQueries || (-1 == ref.indexOf ("?")))
401                     // ignore duplicates
402
if (!mVisited.containsKey (ref))
403                     {
404                         try
405                         {
406                             url.openConnection ();
407                             list.add (url);
408                         }
409                         catch (IOException JavaDoc ioe)
410                         {
411                             // unknown host or some other problem... discard
412
}
413                     }
414         }
415
416         return (list);
417     }
418
419     /**
420      * Initialize the GUI.
421      */

422     private void initComponents ()
423     {
424         mPowerBar = new JPanel JavaDoc ();
425         mUrlText = new JTextField JavaDoc ();
426         mRunToggle = new JCheckBox JavaDoc ();
427         mSpeedSlider = new JSlider JavaDoc ();
428         mReadyProgress = new JProgressBar JavaDoc ();
429         mQueueProgress = new JProgressBar JavaDoc ();
430         mBackgroundToggle = new JCheckBox JavaDoc ();
431         mMainArea = new JSplitPane JavaDoc ();
432         mPicturePanelScroller = new JScrollPane JavaDoc ();
433         mHistoryScroller = new JScrollPane JavaDoc ();
434         mHistory = new JList JavaDoc ();
435         mQueueSize = new JLabel JavaDoc ();
436         mVisitedSize = new JLabel JavaDoc ();
437
438         mPowerBar.setLayout (new BoxLayout JavaDoc (mPowerBar, BoxLayout.X_AXIS));
439
440         mPowerBar.setBorder (new BevelBorder JavaDoc (BevelBorder.LOWERED));
441         mPowerBar.add (mUrlText);
442
443         mRunToggle.setSelected (true);
444         mRunToggle.setText ("On/Off");
445         mRunToggle.setToolTipText ("Starts/stops the image presentation.");
446         mPowerBar.add (mRunToggle);
447
448         mSpeedSlider.setMajorTickSpacing (1000);
449         mSpeedSlider.setMaximum (5000);
450         mSpeedSlider.setPaintTicks (true);
451         mSpeedSlider.setToolTipText ("Set inter-image delay.");
452         mSpeedSlider.setValue (500);
453         mSpeedSlider.setInverted (true);
454         mPowerBar.add (mSpeedSlider);
455
456         mReadyProgress.setToolTipText ("Pending images..");
457         mReadyProgress.setStringPainted (true);
458         mPowerBar.add (mReadyProgress);
459
460         mQueueProgress.setToolTipText ("Outstanding image fetches..");
461         mQueueProgress.setStringPainted (true);
462         mPowerBar.add (mQueueProgress);
463
464         mBackgroundToggle.setSelected (true);
465         mBackgroundToggle.setText ("On/Off");
466         mBackgroundToggle.setToolTipText ("Starts/stops background fetching.");
467         mPowerBar.add (mBackgroundToggle);
468
469         mVisitedSize.setBorder (new BevelBorder JavaDoc (BevelBorder.LOWERED));
470         mVisitedSize.setText ("00000");
471         mVisitedSize.setToolTipText ("Number of URLs examined.");
472         mPowerBar.add (mVisitedSize);
473         mQueueSize.setBorder (new BevelBorder JavaDoc (BevelBorder.LOWERED));
474         mQueueSize.setText ("00000");
475         mQueueSize.setToolTipText ("Number of URLs in queue.");
476         mPowerBar.add (mQueueSize);
477
478         mHistory.setModel (new DefaultListModel JavaDoc ());
479         mHistory.setToolTipText ("History");
480         mHistory.setDoubleBuffered (false);
481         mHistory.setSelectionMode (
482             ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
483         mHistoryScroller.setViewportView (mHistory);
484         mHistoryScroller.setDoubleBuffered (false);
485         mPicturePanelScroller.setViewportView (mPicturePanel);
486         mPicturePanelScroller.setDoubleBuffered (false);
487         mPicturePanelScroller.setHorizontalScrollBarPolicy (
488             ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
489         mPicturePanelScroller.setVerticalScrollBarPolicy (
490             ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
491
492         add (mMainArea, java.awt.BorderLayout.CENTER);
493         mMainArea.setLeftComponent (mHistoryScroller);
494         mMainArea.setRightComponent (mPicturePanelScroller);
495         add (mPowerBar, java.awt.BorderLayout.SOUTH);
496     }
497
498     /**
499      * Gets the state of status bar visibility.
500      * @return <code>true</code> if the status bar is visible.
501      */

502     public boolean getStatusBarVisible ()
503     {
504         boolean ret;
505
506         ret = false;
507
508         for (int i = 0; !ret && (i < getComponentCount ()); i++)
509             if (mPowerBar == getComponent (i))
510                 ret = true;
511
512         return (ret);
513     }
514
515     /**
516      * Sets the status bar visibility.
517      * @param visible The new visibility state.
518      * If <code>true</code>, the status bar will be unhidden.
519      */

520     public void setStatusBarVisible (final boolean visible)
521     {
522         int index;
523
524         index = -1;
525         for (int i = 0; (-1 == index) && (i < getComponentCount ()); i++)
526             if (mPowerBar == getComponent (i))
527                 index = i;
528         if (visible)
529         {
530             if (-1 == index)
531             {
532                 add (mPowerBar, java.awt.BorderLayout.SOUTH);
533                 invalidate ();
534                 validate ();
535             }
536         }
537         else
538             if (-1 != index)
539             {
540                 remove (index);
541                 invalidate ();
542                 validate ();
543             }
544     }
545
546     /**
547      * Gets the state of history list visibility.
548      * @return <code>true</code> if the history list is visible.
549      */

550     public boolean getHistoryListVisible ()
551     {
552         boolean ret;
553
554         ret = false;
555
556         for (int i = 0; !ret && (i < getComponentCount ()); i++)
557             // check indirectly because the history list is in a splitter
558
if (mMainArea == getComponent (i))
559                 ret = true;
560
561         return (ret);
562     }
563
564     /**
565      * Sets the history list visibility.
566      * @param visible The new visibility state.
567      * If <code>true</code>, the history list will be unhidden.
568      */

569     public void setHistoryListVisible (final boolean visible)
570     {
571         int pictpanel;
572         int splitter;
573         Component JavaDoc component;
574
575         pictpanel = -1;
576         splitter = -1;
577         for (int i = 0; i < getComponentCount (); i++)
578         {
579             component = getComponent (i);
580             if (mPicturePanelScroller == component)
581                 pictpanel = i;
582             else if (mMainArea == component)
583                 splitter = i;
584         }
585         if (visible)
586         {
587             if (-1 != pictpanel)
588             {
589                 remove (pictpanel);
590                 add (mMainArea, java.awt.BorderLayout.CENTER);
591                 mMainArea.setLeftComponent (mHistoryScroller);
592                 //mPicturePanelScroller.setViewportView (mPicturePanel);
593
mMainArea.setRightComponent (mPicturePanelScroller);
594                 invalidate ();
595                 validate ();
596             }
597         }
598         else
599             if (-1 != splitter)
600             {
601                 remove (splitter);
602                 add (mPicturePanelScroller, java.awt.BorderLayout.CENTER);
603                 invalidate ();
604                 validate ();
605             }
606     }
607
608     /**
609      * Gets the state of the sequencer thread.
610      * @return <code>true</code> if the thread is pumping images.
611      */

612     public boolean getSequencerActive ()
613     {
614         return (mSequencer.mActive);
615     }
616
617     /**
618      * Sets the sequencer activity state.
619      * The sequencer is the thread that moves images from the pending list
620      * to the picture panel on a timed basis.
621      * @param active The new activity state.
622      * If <code>true</code>, the sequencer will be turned on.
623      * This may alter the speed setting if it is set to zero.
624      */

625     public void setSequencerActive (final boolean active)
626     {
627         // check the delay is not zero
628
if (0 == getSpeed ())
629             setSpeed (Sequencer.DEFAULT_DELAY);
630         mSequencer.mActive = active;
631         if (active)
632             synchronized (mSequencer.mPending)
633             {
634                 mSequencer.mPending.notify ();
635             }
636         if (active != mRunToggle.isSelected ())
637             mRunToggle.setSelected (active);
638     }
639
640     /**
641      * Gets the state of the background thread.
642      * @return <code>true</code> if the thread is examining web pages.
643      */

644     public boolean getBackgroundThreadActive ()
645     {
646         return (mActive);
647     }
648
649     /**
650      * Sets the state of the background thread activity.
651      * The background thread is responsible for examining URLs that are on
652      * the queue for thumbnails, and starting the image fetch operation.
653      * @param active If <code>true</code>,
654      * the background thread will be turned on.
655      */

656     public void setBackgroundThreadActive (final boolean active)
657     {
658         mActive = active;
659         if (active)
660             synchronized (mUrls)
661             {
662                 mUrls.notify ();
663             }
664         if (active != mBackgroundToggle.isSelected ())
665             mBackgroundToggle.setSelected (active);
666     }
667
668     /**
669      * Get the sequencer delay time.
670      * @return The number of milliseconds between image additions to the panel.
671      */

672     public int getSpeed ()
673     {
674         return (mSequencer.getDelay ());
675     }
676
677     /**
678      * Set the sequencer delay time.
679      * The sequencer is the thread that moves images from the pending list
680      * to the picture panel on a timed basis. This value sets the number of
681      * milliseconds it waits between pictures.
682      * Setting it to zero toggles the running state off.
683      * @param speed The sequencer delay in milliseconds.
684      */

685     public void setSpeed (final int speed)
686     {
687         if (0 == speed)
688             mRunToggle.setSelected (false);
689         else
690         {
691             mRunToggle.setSelected (true);
692             mSequencer.setDelay (speed);
693         }
694         if (speed != mSpeedSlider.getValue ())
695             mSpeedSlider.setValue (speed);
696     }
697
698     /**
699      * Getter for property discardCGI.
700      * @return Value of property discardCGI.
701      *
702      */

703     public boolean isDiscardCGI ()
704     {
705         return (mDiscardCGI);
706     }
707
708     /**
709      * Setter for property discardCGI.
710      * @param discard New value of property discardCGI.
711      *
712      */

713     public void setDiscardCGI (final boolean discard)
714     {
715         mDiscardCGI = discard;
716     }
717
718     /**
719      * Getter for property discardQueries.
720      * @return Value of property discardQueries.
721      *
722      */

723     public boolean isDiscardQueries ()
724     {
725         return (mDiscardQueries);
726     }
727
728     /**
729      * Setter for property discardQueries.
730      * @param discard New value of property discardQueries.
731      *
732      */

733     public void setDiscardQueries (final boolean discard)
734     {
735         mDiscardQueries = discard;
736     }
737
738     /**
739      * Check if the url looks like an image.
740      * @param url The usrl to check for image characteristics.
741      * @return <code>true</code> if the url ends in a recognized image
742      * extension.
743      */

744     protected boolean isImage (final String JavaDoc url)
745     {
746         String JavaDoc lower = url.toLowerCase ();
747         return (lower.endsWith (".jpg") || lower.endsWith (".gif")
748             || lower.endsWith (".png"));
749     }
750
751     /**
752      * Get the links of an element of a document.
753      * Only gets the links on IMG elements that reference another image.
754      * The latter is based on suffix (.jpg, .gif and .png).
755      * @param lexer The fully conditioned lexer, ready to rock.
756      * @param docbase The url to read.
757      * @return The URLs, targets of the IMG links;
758      * @exception IOException If the underlying infrastructure throws it.
759      * @exception ParserException If there is a problem parsing the url.
760      */

761     protected URL JavaDoc[][] extractImageLinks (final Lexer lexer, final URL JavaDoc docbase)
762         throws
763             IOException JavaDoc,
764             ParserException
765     {
766         HashMap JavaDoc images;
767         HashMap JavaDoc links;
768         boolean ina; // true when within a <A></A> pair
769
Node node;
770         Tag tag;
771         String JavaDoc name;
772         Tag startatag;
773         Tag imgtag;
774         String JavaDoc href;
775         String JavaDoc src;
776         URL JavaDoc url;
777         URL JavaDoc[][] ret;
778
779         images = new HashMap JavaDoc ();
780         links = new HashMap JavaDoc ();
781         ina = false;
782         startatag = null;
783         imgtag = null;
784         while (null != (node = lexer.nextNode ()))
785         {
786             if (node instanceof Tag)
787             {
788                 tag = (Tag)node;
789                 name = tag.getTagName ();
790                 if ("A".equals (name))
791                 {
792                     if (tag.isEndTag ())
793                     {
794                         ina = false;
795                         if (null != imgtag)
796                         {
797                             // evidence of a thumb
798
href = startatag.getAttribute ("HREF");
799                             if (null != href)
800                             {
801                                 if (isImage (href))
802                                 {
803                                     src = imgtag.getAttribute ("SRC");
804                                     if (null != src)
805                                         try
806                                         {
807                                             url = new URL JavaDoc (docbase, href);
808                                             // eliminate duplicates
809
href = url.toExternalForm ();
810                                             if (!images.containsKey (href))
811                                                 images.put (href, url);
812                                         }
813                                         catch (MalformedURLException JavaDoc murle)
814                                         {
815                                             // oops, forget it
816
}
817                                 }
818                             }
819                         }
820                     }
821                     else
822                     {
823                         startatag = tag;
824                         imgtag = null;
825                         ina = true;
826                         href = startatag.getAttribute ("HREF");
827                         if (null != href)
828                         {
829                             if (!isImage (href))
830                                 try
831                                 {
832                                     url = new URL JavaDoc (docbase, href);
833                                     // eliminate duplicates
834
href = url.toExternalForm ();
835                                     if (!links.containsKey (href))
836                                         links.put (href, url);
837                 &nb