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                                 }
838                                 catch (MalformedURLException JavaDoc murle)
839                                 {
840                                     // well, obviously we don't want this one
841
}
842                         }
843                     }
844                 }
845                 else if (ina && "IMG".equals (name))
846                     imgtag = tag;
847             }
848         }
849
850         ret = new URL JavaDoc[2][];
851         ret[0] = new URL JavaDoc[images.size ()];
852         images.values ().toArray (ret[0]);
853         ret[1] = new URL JavaDoc[links.size ()];
854         links.values ().toArray (ret[1]);
855
856         return (ret);
857     }
858
859     /**
860      * Get the image links from the current URL.
861      * @param url The URL to get the links from
862      * @return An array of two URL arrays, index 0 is a list of images,
863      * index 1 is a list of links to possibly follow.
864      */

865     protected URL JavaDoc[][] getImageLinks (final URL JavaDoc url)
866     {
867         Lexer lexer;
868         URL JavaDoc[][] ret;
869
870         if (null != url)
871         {
872             try
873             {
874                 lexer = new Lexer (url.openConnection ());
875                 ret = extractImageLinks (lexer, url);
876             }
877             catch (Throwable JavaDoc t)
878             {
879                 System.out.println (t.getMessage ());
880                 ret = NONE;
881             }
882         }
883         else
884             ret = NONE;
885
886         return (ret);
887     }
888
889     /**
890      * Get the picture panel object encapsulated by this Thumbelina.
891      * @return The picture panel.
892      */

893     public PicturePanel getPicturePanel ()
894     {
895         return (mPicturePanel);
896     }
897
898     /**
899      * Add a PropertyChangeListener to the listener list.
900      * The listener is registered for all properties.
901      * @param listener The PropertyChangeListener to be added.
902      */

903     public void addPropertyChangeListener (
904         final PropertyChangeListener JavaDoc listener)
905     {
906         mPropertySupport.addPropertyChangeListener (listener);
907     }
908
909     /**
910      * Remove a PropertyChangeListener from the listener list.
911      * This removes a PropertyChangeListener that was registered for all
912      * properties.
913      * @param listener The PropertyChangeListener to be removed.
914      */

915     public void removePropertyChangeListener (
916         final PropertyChangeListener JavaDoc listener)
917     {
918         mPropertySupport.removePropertyChangeListener (listener);
919     }
920
921     /**
922      * Return the URL currently being examined.
923      * This is a bound property. Notifications are available via the
924      * PROP_CURRENT_URL_PROPERTY property.
925      * @return The size of the 'to be examined' list.
926      */

927     public String JavaDoc getCurrentURL ()
928     {
929         return (mCurrentURL);
930     }
931
932     /**
933      * Set the current URL being examined.
934      * @param url The url that is being examined.
935      */

936     protected void setCurrentURL (final String JavaDoc url)
937     {
938         String JavaDoc oldValue;
939
940         if (((null != url) && !url.equals (mCurrentURL))
941             || ((null == url) && (null != mCurrentURL)))
942         {
943             oldValue = mCurrentURL;
944             mCurrentURL = url;
945             mPropertySupport.firePropertyChange (
946                 PROP_CURRENT_URL_PROPERTY, oldValue, url);
947         }
948     }
949
950     /**
951      * Apply a change in 'to be examined' URL list size.
952      * Sends notification via the <code>PROP_URL_QUEUE_PROPERTY</code> property
953      * and updates the status bar.
954      * @param original The original size of the list.
955      * @param current The new size of the list.
956      */

957     protected void updateQueueSize (final int original, final int current)
958     {
959         StringBuffer JavaDoc buffer;
960
961         buffer = new StringBuffer JavaDoc ();
962         buffer.append (current);
963         while (buffer.length () < 5)
964             buffer.insert (0, '0');
965         mQueueSize.setText (buffer.toString ());
966         mPropertySupport.firePropertyChange (
967             PROP_URL_QUEUE_PROPERTY, original, current);
968     }
969
970     /**
971      * Apply a change in 'visited' URL list size.
972      * Sends notification via the <code>PROP_URL_VISITED_PROPERTY</code>
973      * property and updates the status bar.
974      * @param original The original size of the list.
975      * @param current The new size of the list.
976      */

977     protected void updateVisitedSize (final int original, final int current)
978     {
979         StringBuffer JavaDoc buffer;
980
981         buffer = new StringBuffer JavaDoc ();
982         buffer.append (current);
983         while (buffer.length () < 5)
984             buffer.insert (0, '0');
985         mVisitedSize.setText (buffer.toString ());
986         mPropertySupport.firePropertyChange (
987             PROP_URL_VISITED_PROPERTY, original, current);
988     }
989
990     /**
991      * Fetch images.
992      * Ask the toolkit to make the image from a URL, and add a tracker
993      * to handle it when it's received.
994      * Add details to the rquested and tracked lists and update
995      * the status bar.
996      * @param images The list of images to fetch.
997      */

998     protected void fetch (final URL JavaDoc[] images)
999     {
1000        Image JavaDoc image;
1001        Tracker tracker;
1002        int size;
1003
1004        for (int j = 0; j < images.length; j++)
1005        {
1006            if (!mRequested.containsKey (
1007                images[j].toExternalForm ()))
1008            {
1009                image = getToolkit ().createImage (images[j]);
1010                tracker = new Tracker (images[j]);
1011                synchronized (mTracked)
1012                {
1013                    size = mTracked.size () + 1;
1014                    if (mQueueProgress.getMaximum () < size)
1015                    {
1016                        try
1017                        {
1018                            mTracked.wait ();
1019                        }
1020                        catch (InterruptedException JavaDoc ie)
1021                        {
1022                            // this won't happen, just continue on
1023
}
1024                        }
1025                    mRequested.put (images[j].toExternalForm (), images[j]);
1026                    mTracked.put (images[j].toExternalForm (), images[j]);
1027                    mQueueProgress.setValue (size);
1028                    image.getWidth (tracker); // trigger the observer
1029
}
1030            }
1031        }
1032    }
1033
1034    //
1035
// Runnable interface
1036
//
1037

1038    /**
1039     * The main processing loop.
1040     * Pull suspect URLs off the queue one at a time, fetch and parse it,
1041     * request images and enqueue further links.
1042     */

1043    public void run ()
1044    {
1045        URL JavaDoc link;
1046        int original;
1047        String JavaDoc href;
1048        URL JavaDoc[][] urls;
1049
1050        while (true)
1051        {
1052            try
1053            {
1054                link = null;
1055                original = -1;
1056                synchronized (mUrls)
1057                {
1058                    if (0 != mUrls.size ())
1059                    {
1060                        original = mUrls.size ();
1061                        link = (URL JavaDoc)mUrls.remove (0);
1062                    }
1063                    else
1064                        // don't spin crazily on an empty list
1065
Thread.sleep (100);
1066                }
1067                if (null != link)
1068                {
1069                    updateQueueSize (original, mUrls.size ());
1070                    href = link.toExternalForm ();
1071                    setCurrentURL (href);
1072                    mVisited.put (href, link);
1073                    updateVisitedSize (
1074                        mVisited.size () - 1, mVisited.size ());
1075                    urls = getImageLinks (link);
1076                    fetch (urls[0]);
1077                    //append (filter (urls[1]));
1078
synchronized (mEnqueuers)
1079                    {
1080                        Enqueuer enqueuer = new Enqueuer (urls[1]);
1081                        enqueuer.setPriority (Thread.MIN_PRIORITY);
1082                        mEnqueuers.add (enqueuer);
1083                        enqueuer.start ();
1084                    }
1085                    setCurrentURL (null);
1086                }
1087                if (!mActive)
1088                    synchronized (mUrls)
1089                    {
1090                        mUrls.wait ();
1091                    }
1092            }
1093            catch (Throwable JavaDoc t)
1094            {
1095                t.printStackTrace ();
1096            }
1097        }
1098    }
1099
1100    static ArrayList JavaDoc mEnqueuers = new ArrayList JavaDoc ();
1101    
1102    class Enqueuer extends Thread JavaDoc
1103    {
1104        URL JavaDoc[] mList;
1105
1106        public Enqueuer (URL JavaDoc[] list)
1107        {
1108            mList = list;
1109        }
1110
1111        public void run ()
1112        {
1113            append (filter (mList));
1114            synchronized (mEnqueuers)
1115            {
1116                mEnqueuers.remove (this);
1117            }
1118        }
1119    }
1120    //
1121
// ItemListener interface
1122
//
1123

1124    /**
1125     * Handle checkbox events from the status bar.
1126     * Based on the thread toggles, activates or deactivates the
1127     * background thread processes.
1128     * @param event The event describing the checkbox event.
1129     */

1130    public void itemStateChanged (final ItemEvent JavaDoc event)
1131    {
1132        Object JavaDoc source;
1133        boolean checked;
1134
1135        source = event.getItemSelectable ();
1136        checked = ItemEvent.SELECTED == event.getStateChange ();
1137        if (source == mRunToggle)
1138            setSequencerActive (checked);
1139        else if (source == mBackgroundToggle)
1140            setBackgroundThreadActive (checked);
1141    }
1142
1143    //
1144
// ChangeListener interface
1145
//
1146

1147    /**
1148     * Handles the speed slider events.
1149     * @param event The event describing the slider activity.
1150     */

1151    public void stateChanged (final ChangeEvent JavaDoc event)
1152    {
1153        JSlider JavaDoc source;
1154
1155        source = (JSlider JavaDoc)event.getSource ();
1156        if (!source.getValueIsAdjusting ())
1157            setSpeed (source.getValue ());
1158    }
1159
1160    //
1161
// ListSelectionListener interface
1162
//
1163

1164    /**
1165     * Handles the history list events.
1166     * @param event The event describing the list activity.
1167     */

1168    public void valueChanged (final ListSelectionEvent JavaDoc event)
1169    {
1170        JList JavaDoc source;
1171        Object JavaDoc[] hrefs;
1172        Picture picture;
1173        URL JavaDoc url;
1174        Image JavaDoc image;
1175        Tracker tracker;
1176
1177        source = (JList JavaDoc)event.getSource ();
1178        if (source == mHistory && !event.getValueIsAdjusting ())
1179        {
1180            hrefs = source.getSelectedValues ();
1181            for (int i = 0; i < hrefs.length; i++)
1182            {
1183                picture = mPicturePanel.find ("http://" + (String JavaDoc)hrefs[i]);
1184                if (null != picture)
1185                    mPicturePanel.bringToTop (picture);
1186                else
1187                    try
1188                    {
1189                        url = new URL JavaDoc ("http://" + (String JavaDoc)hrefs[i]);
1190                        image = getToolkit ().createImage (url);
1191                        tracker = new Tracker (url);
1192                        image.getWidth (tracker);
1193                        System.out.println ("refetching " + hrefs[i]);
1194                    }
1195                    catch (MalformedURLException JavaDoc murle)
1196                    {
1197                        murle.printStackTrace ();
1198                    }
1199            }
1200        }
1201    }
1202
1203    /**
1204     * Adds the given url to the history list.
1205     * Also puts the URL in the url text of the status bar.
1206     * @param url The URL to add to the history list.
1207     */

1208    public void addHistory (String JavaDoc url)
1209    {
1210        int index;
1211        DefaultListModel JavaDoc model;
1212
1213        mUrlText.setText (url);
1214        // chop off the protocol
1215
index = url.indexOf ("http://");
1216        if (-1 != index)
1217            url = url.substring (index + 7);
1218        else
1219            System.out.println ("********* " + url + " ************");
1220        model = (DefaultListModel JavaDoc)mHistory.getModel ();
1221        model.addElement (url);
1222        // this doesn't friggin work:
1223
// mHistory.ensureIndexIsVisible (model.getSize ());
1224
}
1225
1226    /**
1227     * Open a URL.
1228     * Resets the urls list and appends the given url as the only item.
1229     * @param ref The URL to add.
1230     */

1231    public void open (String JavaDoc ref)
1232    {
1233        URL JavaDoc url;
1234
1235        try
1236        {
1237            if (!ref.startsWith ("http://"))
1238                ref = "http://" + ref;
1239            url = new URL JavaDoc (ref);
1240            reset ();
1241            append (url);
1242        }
1243        catch (Exception JavaDoc e)
1244        {
1245            System.out.println (e.getMessage ());
1246        }
1247    }
1248
1249    /**
1250     * Provide command line help.
1251     */

1252    protected static void help ()
1253    {
1254        System.out.println ("Thumbelina - Scan and display the images behind thumbnails.");
1255        System.out.println ("java -Xmx256M -jar thumbelina.jar [url]");
1256        System.out.println ("It is highly recommended that the maximum heap "
1257            + "size be increased with -Xmx switch.");
1258        System.exit (0);
1259    }
1260
1261    /**
1262     * Mainline.
1263     * @param args the command line arguments.
1264     * Can be one or more forms of -help to get command line help,
1265     * or a URL to prime the program with.
1266     * Checks for JDK 1.4 and if not found runs in crippled mode
1267     * (no ThumbelinaFrame).
1268     */

1269    public static void main (final String JavaDoc[] args)
1270    {
1271        URL JavaDoc url;
1272        String JavaDoc version;
1273        JFrame JavaDoc frame;
1274        Thumbelina thumbelina;
1275
1276        System.setProperty ("sun.net.client.defaultReadTimeout", "7000");
1277        System.setProperty ("sun.net.client.defaultConnectTimeout", "7000");
1278
1279        url = null;
1280        if (0 != args.length)
1281            try
1282            {
1283                if (args[0].equalsIgnoreCase ("help")
1284                    || args[0].equalsIgnoreCase ("-help")
1285                    || args[0].equalsIgnoreCase ("-h")
1286                    || args[0].equalsIgnoreCase ("?")
1287                    || args[0].equalsIgnoreCase ("-?"))
1288                    help ();
1289                else
1290                    url = new URL JavaDoc (args[0]);
1291            }
1292            catch (MalformedURLException JavaDoc murle)
1293            {
1294                System.err.println (murle.getMessage ());
1295                help ();
1296            }
1297
1298        version = System.getProperty ("java.version");
1299        if (version.startsWith ("1.4") || version.startsWith ("1.5"))
1300            frame = new ThumbelinaFrame (url);
1301        else
1302        {
1303            if (null == url)
1304                help ();
1305            System.out.println (
1306                "Java version is only "
1307                + version
1308                + ", entering crippled mode");
1309            frame = new JFrame JavaDoc ("Thumbelina");
1310            thumbelina = new Thumbelina (url);
1311            frame.getContentPane ().add (thumbelina, BorderLayout.CENTER);
1312            frame.setBounds (10, 10, 640, 480);
1313            frame.addWindowListener (new WindowAdapter JavaDoc ()
1314            {
1315                public void windowClosing (final WindowEvent JavaDoc event)
1316                {
1317                    System.exit (0);
1318                }
1319            });
1320        }
1321        frame.setVisible (true);
1322    }
1323
1324    /**
1325     * Getter for property queue.
1326     * @return List of URLs that are to be visited.
1327     */

1328    public ArrayList JavaDoc getQueue ()
1329    {
1330        return (mUrls);
1331    }
1332
1333    /**
1334     * Getter for property queue.
1335     * This is a bound property. Notifications are available via the
1336     * <code>PROP_URL_QUEUE_PROPERTY</code> property.
1337     * @return The size of the list of URLs that are to be visited.
1338     */

1339    public int getQueueSize ()
1340    {
1341        return (mUrls.size ());
1342    }
1343
1344    /**
1345     * Track incoming asynchronous image reception.
1346     * On completion, adds the image to the pending list.
1347     */

1348    class Tracker implements ImageObserver JavaDoc
1349    {
1350
1351        /**
1352         * The url the image is comming from.
1353         */

1354        protected URL JavaDoc mSource;
1355
1356        /**
1357         * Create an image tracker.
1358         * @param source The URL the image is being fetched from.
1359         */

1360        public Tracker (final URL JavaDoc source)
1361        {
1362            mSource = source;
1363        }
1364
1365        //
1366
// ImageObserver interface
1367
//
1368

1369        /**
1370         * This method is called when information about an image which was
1371         * previously requested using an asynchronous interface becomes
1372         * available. Asynchronous interfaces are method calls such as
1373         * getWidth(ImageObserver) and drawImage(img, x, y, ImageObserver)
1374         * which take an ImageObserver object as an argument. Those methods
1375         * register the caller as interested either in information about
1376         * the overall image itself (in the case of getWidth(ImageObserver))
1377         * or about an output version of an image (in the case of the
1378         * drawImage(img, x, y, [w, h,] ImageObserver) call).
1379         *
1380         * <p>This method
1381         * should return true if further updates are needed or false if the
1382         * required information has been acquired. The image which was being
1383         * tracked is passed in using the img argument. Various constants
1384         * are combined to form the infoflags argument which indicates what
1385         * information about the image is now available. The interpretation
1386         * of the x, y, width, and height arguments depends on the contents
1387         * of the infoflags argument.
1388         * <p>
1389         * The <code>infoflags</code> argument should be the bitwise inclusive
1390         * <b>OR</b> of the following flags: <code>WIDTH</code>,
1391         * <code>HEIGHT</code>, <code>PROPERTIES</code>, <code>SOMEBITS</code>,
1392         * <code>FRAMEBITS</code>, <code>ALLBITS</code>, <code>ERROR</code>,
1393         * <code>ABORT</code>.
1394         *
1395         * @param image the image being observed.
1396         * @param infoflags the bitwise inclusive OR of the following
1397         * flags: <code>WIDTH</code>, <code>HEIGHT</code>,
1398         * <code>PROPERTIES</code>, <code>SOMEBITS</code>,
1399         * <code>FRAMEBITS</code>, <code>ALLBITS</code>,
1400         * <code>ERROR</code>, <code>ABORT</code>.
1401         * @param x the <i>x</i> coordinate.
1402         * @param y the <i>y</i> coordinate.
1403         * @param width the width.
1404         * @param height the height.
1405         * @return <code>false</code> if the infoflags indicate that the
1406         * image is completely loaded; <code>true</code> otherwise.
1407         *
1408         * @see #WIDTH
1409         * @see #HEIGHT
1410         * @see #PROPERTIES
1411         * @see #SOMEBITS
1412         * @see #FRAMEBITS
1413         * @see #ALLBITS
1414         * @see #ERROR
1415         * @see #ABORT
1416         * @see Image#getWidth
1417         * @see Image#getHeight
1418         * @see java.awt.Graphics#drawImage
1419         */

1420        public synchronized boolean imageUpdate (
1421            final Image JavaDoc image,
1422            final int infoflags,
1423            final int x,
1424            final int y,
1425            final int width,
1426            final int height)
1427        {
1428            boolean done;
1429            boolean error;
1430            boolean abort;
1431            URL JavaDoc url;
1432
1433            done = (0 != (infoflags & ImageObserver.ALLBITS));
1434            abort = (0 != (infoflags & ImageObserver.ABORT));
1435            error = (0 != (infoflags & ImageObserver.ERROR));
1436            if (done || abort || error)
1437                synchronized (mTracked)
1438                {
1439                    url = (URL JavaDoc)mTracked.remove (mSource.toExternalForm ());
1440                    mTracked.notify ();
1441                    mQueueProgress.setValue (mTracked.size ());
1442                    if (done)
1443                        mSequencer.add (image, mSource, (null != url));
1444                }
1445
1446            return (!done);
1447        }
1448    }
1449}
1450
1451/*
1452 * Revision Control Modification History
1453 *
1454 * $Log: Thumbelina.java,v $
1455 * Revision 1.7 2005/02/13 20:36:00 derrickoswald
1456 * FilterBuilder
1457 *
1458 * Revision 1.6 2004/07/31 16:42:30 derrickoswald
1459 * Remove unused variables and other fixes exposed by turning on compiler warnings.
1460 *
1461 * Revision 1.5 2004/05/24 16:18:17 derrickoswald
1462 * Part three of a multiphase refactoring.
1463 * The three node types are now fronted by interfaces (program to the interface paradigm)
1464 * with concrete implementations in the new htmlparser.nodes package. Classes from the
1465 * lexer.nodes package are moved to this package, and obvious references to the concrete
1466 * classes that got broken by this have been changed to use the interfaces where possible.
1467 *
1468 * Revision 1.4 2004/05/16 17:59:56 derrickoswald
1469 * Alter bound property name constants to agree with section
1470 * 8.8 Capitalization of inferred names.
1471 * in the JavaBeans API specification.
1472 *
1473 * Revision 1.3 2003/11/04 01:25:02 derrickoswald
1474 * Made visiting order the same order as on the page.
1475 * The 'shouldRecurseSelf' boolean of NodeVisitor could probably
1476 * be removed since it doesn't make much sense any more.
1477 * Fixed StringBean, which was still looking for end tags with names starting with
1478 * a slash, i.e. "/SCRIPT", silly beany.
1479 * Added some debugging support to the lexer, you can easily base a breakpoint on
1480 * line number.
1481 *
1482 * Revision 1.2 2003/10/26 16:44:01 derrickoswald
1483 * Get thumbelina working again. The tag.getName() method doesn't include the / of end tags.
1484 *
1485 * Revision 1.1 2003/09/21 18:20:56 derrickoswald
1486 * Thumbelina
1487 * Created a lexer GUI application to extract images behind thumbnails.
1488 * Added a task in the ant build script - thumbelina - to create the jar file.
1489 * You need JDK 1.4.x to build it. It can be run on JDK 1.3.x in crippled mode.
1490 * Usage: java -Xmx256M thumbelina.jar [URL]
1491 *
1492 *
1493 */

1494
Popular Tags