001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.welcome.content;
043:
044: import java.awt.BorderLayout;
045: import java.awt.Component;
046: import java.awt.Dimension;
047: import java.awt.FontMetrics;
048: import java.awt.Graphics2D;
049: import java.awt.GridBagConstraints;
050: import java.awt.GridBagLayout;
051: import java.awt.Insets;
052: import java.awt.event.ActionEvent;
053: import java.awt.event.ActionListener;
054: import java.beans.PropertyChangeEvent;
055: import java.beans.PropertyChangeListener;
056: import java.io.BufferedInputStream;
057: import java.io.BufferedOutputStream;
058: import java.io.File;
059: import java.io.FileInputStream;
060: import java.io.FileOutputStream;
061: import java.io.FilterInputStream;
062: import java.io.IOException;
063: import java.io.InputStream;
064: import java.io.OutputStream;
065: import java.net.HttpURLConnection;
066: import java.net.MalformedURLException;
067: import java.net.SocketException;
068: import java.net.URL;
069: import java.net.UnknownHostException;
070: import java.text.DateFormat;
071: import java.text.ParseException;
072: import java.text.SimpleDateFormat;
073: import java.util.ArrayList;
074: import java.util.Date;
075: import java.util.List;
076: import java.util.Locale;
077: import java.util.logging.Level;
078: import java.util.logging.Logger;
079: import java.util.prefs.Preferences;
080: import java.util.zip.GZIPInputStream;
081: import java.util.zip.Inflater;
082: import java.util.zip.InflaterInputStream;
083: import javax.swing.BorderFactory;
084: import javax.swing.JButton;
085: import javax.swing.JComponent;
086: import javax.swing.JLabel;
087: import javax.swing.JPanel;
088: import javax.swing.SwingUtilities;
089: import javax.xml.parsers.ParserConfigurationException;
090: import org.openide.ErrorManager;
091: import org.openide.awt.Mnemonics;
092: import org.openide.filesystems.FileUtil;
093: import org.openide.filesystems.Repository;
094: import org.openide.util.NbPreferences;
095: import org.openide.util.RequestProcessor;
096: import org.openide.util.WeakListeners;
097: import org.openide.xml.XMLUtil;
098: import org.w3c.dom.Node;
099: import org.xml.sax.Attributes;
100: import org.xml.sax.ContentHandler;
101: import org.xml.sax.InputSource;
102: import org.xml.sax.Locator;
103: import org.xml.sax.SAXException;
104: import org.xml.sax.XMLReader;
105:
106: public class RSSFeed extends JPanel implements Constants,
107: PropertyChangeListener {
108:
109: private String url;
110:
111: private boolean showProxyButton = true;
112:
113: private RequestProcessor.Task reloadTimer;
114: protected long lastReload = 0;
115:
116: public static final String FEED_CONTENT_PROPERTY = "feedContent";
117:
118: private static DateFormat parsingDateFormat = new SimpleDateFormat(
119: "EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH); // NOI18N
120: private static DateFormat parsingDateFormatShort = new SimpleDateFormat(
121: "EEE, dd MMM yyyy", Locale.ENGLISH); // NOI18N
122: private static DateFormat printingDateFormatShort = DateFormat
123: .getDateInstance(DateFormat.SHORT);
124:
125: private boolean isCached = false;
126:
127: private final Logger LOGGER = Logger.getLogger(RSSFeed.class
128: .getName());
129:
130: private int maxDescriptionChars = -1;
131:
132: /** Returns file for caching of content.
133: * Enclosing folder is created if it does not exist yet.
134: */
135: private static File initCacheStore(String path) throws IOException {
136: File cacheStore;
137: String userDir = System.getProperty("netbeans.user"); // NOI18N
138: if (userDir != null) {
139: cacheStore = new File(new File(new File(userDir, "var"),
140: "cache"), "welcome"); // NOI18N
141: } else {
142: File cachedir = FileUtil.toFile(Repository.getDefault()
143: .getDefaultFileSystem().getRoot());
144: cacheStore = new File(cachedir, "welcome"); // NOI18N
145: }
146: cacheStore = new File(cacheStore, path);
147: cacheStore.getParentFile().mkdirs();
148: cacheStore.createNewFile();
149: return cacheStore;
150: }
151:
152: public RSSFeed(String url, boolean showProxyButton) {
153: super (new BorderLayout());
154: this .url = url;
155: this .showProxyButton = showProxyButton;
156: setBorder(null);
157: setOpaque(false);
158:
159: add(buildContentLoadingLabel(), BorderLayout.CENTER);
160:
161: HttpProxySettings.getDefault().addPropertyChangeListener(
162: WeakListeners.propertyChange(this , HttpProxySettings
163: .getDefault()));
164: }
165:
166: public RSSFeed(boolean showProxyButton) {
167: this (null, showProxyButton);
168: }
169:
170: public void setContent(Component content) {
171: removeAll();
172: Dimension d = new Dimension();
173: add(content, BorderLayout.CENTER);
174: firePropertyChange(FEED_CONTENT_PROPERTY, null, content);
175: revalidate();
176: invalidate();
177: repaint();
178: }
179:
180: public Component getContent() {
181: return this ;
182: }
183:
184: public void reload() {
185: new Reload().start();
186: }
187:
188: protected int getMaxItemCount() {
189: return 5;
190: }
191:
192: protected List<FeedItem> buildItemList() throws SAXException,
193: ParserConfigurationException, IOException {
194: XMLReader reader = XMLUtil.createXMLReader(false, true);
195: FeedHandler handler = new FeedHandler(getMaxItemCount());
196: reader.setContentHandler(handler);
197: reader.setEntityResolver(org.openide.xml.EntityCatalog
198: .getDefault());
199: reader.setErrorHandler(new ErrorCatcher());
200:
201: InputSource is = findInputSource(new URL(url));
202: reader.parse(is);
203:
204: return handler.getItemList();
205: }
206:
207: private String url2path(URL u) {
208: StringBuilder pathSB = new StringBuilder(u.getHost());
209: if (u.getPort() != -1) {
210: pathSB.append(u.getPort());
211: }
212: pathSB.append(u.getPath());
213: return pathSB.toString();
214: }
215:
216: /** Searches either for localy cached copy of URL content of original.
217: */
218: protected InputSource findInputSource(URL u) throws IOException {
219: HttpURLConnection httpCon = (HttpURLConnection) u
220: .openConnection();
221: httpCon.setRequestProperty("Accept-Encoding", "gzip, deflate"); // NOI18N
222:
223: Preferences prefs = NbPreferences.forModule(RSSFeed.class);
224: String path = url2path(u);
225: String lastModified = prefs.get(path, null);
226: if (lastModified != null) {
227: httpCon.addRequestProperty("If-Modified-Since",
228: lastModified); // NOI18N
229: }
230:
231: httpCon.connect();
232: //if it returns Not modified then we already have the content, return
233: if (httpCon.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
234: //disconnect() should only be used when you won't
235: //connect to the same site in a while,
236: //since it disconnects the socket. Only losing
237: //the stream on an HTTP 1.1 connection will
238: //maintain the connection waiting and be
239: //faster the next time around
240: File cacheFile = initCacheStore(path);
241: LOGGER.log(Level.FINE, "Reading content of {0} from {1}", //NOI18N
242: new Object[] { u.toString(),
243: cacheFile.getAbsolutePath() });
244: isCached = true;
245: return new org.xml.sax.InputSource(new BufferedInputStream(
246: new FileInputStream(cacheFile)));
247: } else {
248: //obtain the encoding returned by the server
249: String encoding = httpCon.getContentEncoding();
250: LOGGER.log(Level.FINER, "Connection encoding: {0}",
251: encoding); //NOI18N
252:
253: LOGGER.log(Level.FINER, "ETag: {0}", httpCon
254: .getHeaderField("ETag")); //NOI18N
255:
256: InputStream is = null;
257: if ("gzip".equalsIgnoreCase(encoding)) { //NOI18N
258: is = new GZIPInputStream(httpCon.getInputStream());
259: } else if ("deflate".equalsIgnoreCase(encoding)) { //NOI18N
260: is = new InflaterInputStream(httpCon.getInputStream(),
261: new Inflater(true));
262: } else {
263: is = httpCon.getInputStream();
264: }
265: LOGGER
266: .log(
267: Level.FINE,
268: "Reading {0} from original source and caching",
269: url); //NOI18N
270: return new org.xml.sax.InputSource(new CachingInputStream(
271: is, path, httpCon.getHeaderField("Last-Modified"))); //NOI18N
272: }
273: }
274:
275: /** Inner class error catcher for handling SAXParseExceptions */
276: static class ErrorCatcher implements org.xml.sax.ErrorHandler {
277: private void message(Level level,
278: org.xml.sax.SAXParseException e) {
279: Logger l = Logger.getLogger(RSSFeed.class.getName());
280: l.log(level, "Line number:" + e.getLineNumber()); //NOI18N
281: l.log(level, "Column number:" + e.getColumnNumber()); //NOI18N
282: l.log(level, "Public ID:" + e.getPublicId()); //NOI18N
283: l.log(level, "System ID:" + e.getSystemId()); //NOI18N
284: l.log(level, "Error message:" + e.getMessage()); //NOI18N
285: }
286:
287: public void error(org.xml.sax.SAXParseException e) {
288: message(Level.SEVERE, e); //NOI18N
289: }
290:
291: public void warning(org.xml.sax.SAXParseException e) {
292: message(Level.WARNING, e); //NOI18N
293: }
294:
295: public void fatalError(org.xml.sax.SAXParseException e) {
296: message(Level.SEVERE, e); //NOI18N
297: }
298: } //end of inner class ErrorCatcher
299:
300: private class Reload extends Thread {
301: @Override
302: public void run() {
303: try {
304: lastReload = System.currentTimeMillis();
305: // System.err.println("reloading: " + lastReload + "url=" + url);
306:
307: List<FeedItem> itemList = buildItemList();
308: final JPanel contentPanel = new JPanel(
309: new GridBagLayout());
310: contentPanel.setOpaque(false);
311: int contentRow = 0;
312:
313: Component header = getContentHeader();
314: if (null != header) {
315: contentPanel.add(header, new GridBagConstraints(0,
316: contentRow++, 1, 1, 0.0, 0.0,
317: GridBagConstraints.CENTER,
318: GridBagConstraints.BOTH, new Insets(0, 0,
319: 0, 0), 0, 0));
320: }
321:
322: for (int i = 0; i < Math.min(itemList.size(),
323: getMaxItemCount()); i++) {
324: FeedItem item = itemList.get(i);
325:
326: if (null != item.title && null != item.link) {
327:
328: Component comp = createFeedItemComponent(item);
329:
330: contentPanel.add(comp, new GridBagConstraints(
331: 0, contentRow++, 1, 1, 1.0, 0.0,
332: GridBagConstraints.NORTHWEST,
333: GridBagConstraints.BOTH, new Insets(
334: contentRow == 1 ? 0/*UNDER_HEADER_MARGIN*/
335: : 0, 0, 16, 0), 0, 0));
336: }
337: }
338: contentPanel.add(new JLabel(), new GridBagConstraints(
339: 0, contentRow++, 1, 1, 0.0, 1.0,
340: GridBagConstraints.CENTER,
341: GridBagConstraints.VERTICAL, new Insets(0, 0,
342: 0, 0), 0, 0));
343:
344: SwingUtilities.invokeLater(new Runnable() {
345: public void run() {
346: // contentPanel.setMinimumSize( contentPanel.getPreferredSize() );
347: setContent(contentPanel);
348: }
349: });
350:
351: //schedule feed reload
352: reloadTimer = RequestProcessor.getDefault().post(this ,
353: RSS_FEED_TIMER_RELOAD_MILLIS);
354:
355: } catch (UnknownHostException uhE) {
356: SwingUtilities.invokeLater(new Runnable() {
357: public void run() {
358: setContent(buildProxyPanel());
359: }
360: });
361: } catch (SocketException sE) {
362: SwingUtilities.invokeLater(new Runnable() {
363: public void run() {
364: setContent(buildProxyPanel());
365: }
366: });
367: } catch (IOException ioE) {
368: SwingUtilities.invokeLater(new Runnable() {
369: public void run() {
370: setContent(buildProxyPanel());
371: }
372: });
373: } catch (Exception e) {
374: if (isContentCached()) {
375: try {
376: NbPreferences.forModule(RSSFeed.class).remove(
377: url2path(new URL(url)));
378: run();
379: return;
380: } catch (MalformedURLException mE) {
381: //ignore
382: }
383: }
384: SwingUtilities.invokeLater(new Runnable() {
385: public void run() {
386: setContent(buildErrorLabel());
387: }
388: });
389: ErrorManager.getDefault().notify(
390: ErrorManager.INFORMATIONAL, e);
391: }
392: }
393: }
394:
395: protected Component createFeedItemComponent(FeedItem item) {
396: JPanel panel = new JPanel(new GridBagLayout());
397: panel.setOpaque(false);
398: int row = 0;
399: if (item.dateTime != null) {
400: JLabel label = new JLabel();
401: label.setFont(RSS_DESCRIPTION_FONT);
402: label.setText(formatDateTime(item.dateTime));
403: panel.add(label, new GridBagConstraints(2, row, 1, 1, 0.0,
404: 0.0, GridBagConstraints.EAST,
405: GridBagConstraints.NONE,
406: new Insets(0, TEXT_INSETS_LEFT + 5, 2,
407: TEXT_INSETS_RIGHT), 0, 0));
408: }
409:
410: WebLink linkButton = new WebLink(item.title, item.link, true);
411: linkButton.getAccessibleContext().setAccessibleName(
412: BundleSupport.getAccessibilityName("WebLink",
413: item.title)); //NOI18N
414: linkButton.getAccessibleContext().setAccessibleDescription(
415: BundleSupport.getAccessibilityDescription("WebLink",
416: item.link)); //NOI18N
417: linkButton.setFont(BUTTON_FONT);
418: panel.add(linkButton, new GridBagConstraints(0, row++, 1, 1,
419: 1.0, 0.0, GridBagConstraints.WEST,
420: GridBagConstraints.NONE, new Insets(0, 5, 2,
421: TEXT_INSETS_RIGHT), 0, 0));
422:
423: if (item.description != null) {
424: JLabel label = new JLabel("<html>"
425: + trimHtml(item.description));
426: label.setFont(RSS_DESCRIPTION_FONT);
427: panel.add(label, new GridBagConstraints(0, row++, 4, 1,
428: 0.0, 0.0, GridBagConstraints.WEST,
429: GridBagConstraints.HORIZONTAL,
430: new Insets(0, TEXT_INSETS_LEFT + 5, 0,
431: TEXT_INSETS_RIGHT), 0, 0));
432: }
433: return panel;
434: }
435:
436: protected static String getTextContent(Node node) {
437: Node child = node.getFirstChild();
438: if (null == child)
439: return null;
440:
441: return child.getNodeValue();
442: }
443:
444: protected String formatDateTime(String strDateTime) {
445: try {
446: Date date = parsingDateFormat.parse(strDateTime);
447: return printingDateFormatShort.format(date);
448: } catch (NumberFormatException nfE) {
449: //ignore
450: } catch (ParseException pE) {
451: try {
452: Date date = parsingDateFormatShort.parse(strDateTime);
453: return printingDateFormatShort.format(date);
454: } catch (NumberFormatException nfE) {
455: //ignore
456: } catch (ParseException otherPE) {
457: //ignore
458: }
459: }
460: return strDateTime;
461: }
462:
463: private static final long serialVersionUID = 1L;
464:
465: @Override
466: public void removeNotify() {
467: stopReloading();
468: maxDescriptionChars = -1;
469: super .removeNotify();
470: }
471:
472: @Override
473: public void addNotify() {
474: super .addNotify();
475: getMaxDecsriptionLength();
476: startReloading();
477: }
478:
479: private boolean firstReload = true;
480:
481: protected void startReloading() {
482: if ( /*(isShowing() || firstReload) &&*/null == reloadTimer
483: && !Boolean.getBoolean("netbeans.full.hack")) {
484: firstReload = false;
485: if (System.currentTimeMillis() - lastReload >= RSS_FEED_TIMER_RELOAD_MILLIS) {
486: reload();
487: } else {
488: reloadTimer = RequestProcessor.getDefault().post(
489: new Reload(),
490: (int) (RSS_FEED_TIMER_RELOAD_MILLIS - (System
491: .currentTimeMillis() - lastReload)));
492: }
493: }
494: }
495:
496: protected void stopReloading() {
497: if (null != reloadTimer) {
498: reloadTimer.cancel();
499: reloadTimer = null;
500: }
501: }
502:
503: private String trimHtml(String htmlSnippet) {
504: String res = htmlSnippet.replaceAll("<[^>]*>", ""); // NOI18N // NOI18N
505: res = res.replaceAll(" ", " "); // NOI18N // NOI18N
506: res = res.trim();
507: int maxLen = getMaxDecsriptionLength();
508: if (maxLen > 0 && res.length() > maxLen) {
509: res = res.substring(0, maxLen) + "..."; // NOI18N
510: }
511: return res;
512: }
513:
514: protected int getMaxDecsriptionLength() {
515: if (maxDescriptionChars < 0 && getWidth() > 0) {
516: if (getWidth() <= 0) {
517: SwingUtilities.invokeLater(new Runnable() {
518: public void run() {
519: getMaxDecsriptionLength();
520: }
521: });
522: return 200;
523: }
524: try {
525: Graphics2D g = (Graphics2D) getGraphics();
526: FontMetrics fm = g.getFontMetrics(RSS_DESCRIPTION_FONT);
527: double charWidth = fm.getStringBounds("Ab c", g)
528: .getWidth() / 4;
529: double feedWidth = getWidth() - 30;
530: maxDescriptionChars = (int) (1.8 * feedWidth / charWidth);
531: } catch (Throwable e) {
532: maxDescriptionChars = 200;
533: }
534: }
535: return maxDescriptionChars;
536: }
537:
538: protected Component getContentHeader() {
539: return null;
540: }
541:
542: private JComponent buildProxyPanel() {
543: Component header = getContentHeader();
544: JPanel panel = new JPanel(new GridBagLayout());
545: panel.setOpaque(false);
546:
547: int row = 0;
548: if (null != header) {
549: panel.add(header, new GridBagConstraints(0, row++, 1, 1,
550: 1.0, 0.0, GridBagConstraints.CENTER,
551: GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0,
552: 0));
553: }
554:
555: panel.add(
556: new JLabel(BundleSupport.getLabel("ErrCannotConnect")), // NOI18N
557: new GridBagConstraints(0, row++, 1, 1, 0.0, 0.0,
558: GridBagConstraints.CENTER,
559: GridBagConstraints.NONE, new Insets(5, 10, 10,
560: 5), 0, 0));
561: if (showProxyButton) {
562: JButton button = new JButton();
563: Mnemonics.setLocalizedText(button, BundleSupport
564: .getLabel("ProxyConfig")); // NOI18N
565: button.setOpaque(false);
566: button.addActionListener(new ActionListener() {
567: public void actionPerformed(ActionEvent e) {
568: HttpProxySettings.getDefault()
569: .showConfigurationDialog();
570: }
571: });
572: panel.add(button, new GridBagConstraints(0, row++, 1, 1,
573: 0.0, 0.0, GridBagConstraints.CENTER,
574: GridBagConstraints.NONE, new Insets(5, 10, 10, 5),
575: 0, 0));
576: }
577: return panel;
578: }
579:
580: private JComponent buildContentLoadingLabel() {
581: JLabel label = new JLabel(BundleSupport
582: .getLabel("ContentLoading")); // NOI18N
583: label.setHorizontalAlignment(JLabel.CENTER);
584: label.setVerticalAlignment(JLabel.CENTER);
585: label.setOpaque(false);
586: Component header = getContentHeader();
587: if (null != header) {
588: JPanel panel = new JPanel(new GridBagLayout());
589: panel.setOpaque(false);
590: panel.add(header, new GridBagConstraints(0, 0, 1, 1, 1.0,
591: 1.0, GridBagConstraints.CENTER,
592: GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0,
593: 0));
594: panel.add(label, new GridBagConstraints(0, 1, 1, 1, 1.0,
595: 1.0, GridBagConstraints.CENTER,
596: GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0,
597: 0));
598: panel.setBorder(BorderFactory.createEmptyBorder(40, 0, 40,
599: 0));
600: return panel;
601: }
602: label.setBorder(BorderFactory.createEmptyBorder(40, 0, 40, 0));
603: return label;
604: }
605:
606: private JComponent buildErrorLabel() {
607: Component header = getContentHeader();
608: JPanel panel = new JPanel(new GridBagLayout());
609: panel.setOpaque(false);
610:
611: int row = 0;
612: if (null != header) {
613: panel.add(header, new GridBagConstraints(0, row++, 1, 1,
614: 1.0, 0.0, GridBagConstraints.CENTER,
615: GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0,
616: 0));
617: }
618:
619: panel.add(new JLabel(BundleSupport.getLabel("ErrLoadingFeed")), // NOI18N
620: new GridBagConstraints(0, row++, 1, 1, 0.0, 0.0,
621: GridBagConstraints.CENTER,
622: GridBagConstraints.NONE, new Insets(5, 10, 10,
623: 5), 0, 0));
624: JButton button = new JButton();
625: Mnemonics.setLocalizedText(button, BundleSupport
626: .getLabel("Reload")); // NOI18N
627: button.setOpaque(false);
628: button.addActionListener(new ActionListener() {
629: public void actionPerformed(ActionEvent e) {
630: lastReload = 0;
631: reload();
632: }
633: });
634: panel.add(button,
635: new GridBagConstraints(0, row++, 1, 1, 0.0, 0.0,
636: GridBagConstraints.CENTER,
637: GridBagConstraints.NONE, new Insets(5, 10, 10,
638: 5), 0, 0));
639: return panel;
640: }
641:
642: public void propertyChange(PropertyChangeEvent evt) {
643: if (HttpProxySettings.PROXY_SETTINGS.equals(evt
644: .getPropertyName())) {
645: removeAll();
646: add(buildContentLoadingLabel(), BorderLayout.CENTER);
647: lastReload = 0;
648: reload();
649: }
650: }
651:
652: public boolean isContentCached() {
653: return isCached;
654: }
655:
656: static class FeedHandler implements ContentHandler {
657: private FeedItem currentItem;
658: private StringBuffer textBuffer;
659: private int maxItemCount;
660: private ArrayList<FeedItem> itemList;
661:
662: public FeedHandler(int maxItemCount) {
663: this .maxItemCount = maxItemCount;
664: itemList = new ArrayList<FeedItem>(maxItemCount);
665: }
666:
667: public void setDocumentLocator(Locator locator) {
668: }
669:
670: public void startDocument() throws SAXException {
671: }
672:
673: public void endDocument() throws SAXException {
674: }
675:
676: public void startPrefixMapping(String prefix, String uri)
677: throws SAXException {
678: }
679:
680: public void endPrefixMapping(String prefix) throws SAXException {
681: }
682:
683: public void startElement(String uri, String localName,
684: String qName, Attributes atts) throws SAXException {
685: if (itemList.size() < maxItemCount) {
686: if ("item".equals(localName)) { // NOI18N
687: currentItem = new FeedItem();
688: } else if ("link".equals(localName) // NOI18N
689: || "pubDate".equals(localName) // NOI18N
690: || "date".equals(localName) // NOI18N
691: || "description".equals(localName) // NOI18N
692: || "title".equals(localName)) { // NOI18N
693: textBuffer = new StringBuffer(110);
694: } else if ("enclosure".equals(localName)
695: && null != currentItem) { //NOI18N
696: currentItem.enclosureUrl = atts.getValue("url"); //NOI18N
697: }
698: }
699: }
700:
701: public void endElement(String uri, String localName,
702: String qName) throws SAXException {
703: if (itemList.size() < maxItemCount) {
704: if ("item".equals(localName)) { // NOI18N
705: if (null != currentItem && currentItem.isValid()) {
706: itemList.add(currentItem);
707: }
708: currentItem = null;
709: } else if (null != currentItem && null != textBuffer) {
710: String text = textBuffer.toString().trim();
711: textBuffer = null;
712: if (0 == text.length())
713: text = null;
714:
715: if ("link".equals(localName)) { // NOI18N
716: currentItem.link = text;
717: } else if ("pubDate".equals(localName) // NOI18N
718: || "date".equals(localName)) { // NOI18N
719: currentItem.dateTime = text;
720: } else if ("title".equals(localName)) { // NOI18N
721: currentItem.title = text;
722: } else if ("description".equals(localName)) { // NOI18N
723: currentItem.description = text;
724: }
725: }
726: }
727: }
728:
729: public void characters(char[] ch, int start, int length)
730: throws SAXException {
731: if (null != textBuffer)
732: textBuffer.append(ch, start, length);
733: }
734:
735: public void ignorableWhitespace(char[] ch, int start, int length)
736: throws SAXException {
737: }
738:
739: public void processingInstruction(String target, String data)
740: throws SAXException {
741: }
742:
743: public void skippedEntity(String name) throws SAXException {
744: }
745:
746: public ArrayList<FeedItem> getItemList() {
747: return itemList;
748: }
749: }
750:
751: protected static class FeedItem {
752: public String title;
753: public String link;
754: public String description;
755: public String dateTime;
756: public String enclosureUrl;
757:
758: public boolean isValid() {
759: return null != title && null != link;
760: }
761: }
762:
763: static class CachingInputStream extends FilterInputStream {
764: private OutputStream os;
765: private String modTime;
766: private String path;
767:
768: CachingInputStream(InputStream is, String path, String time)
769: throws IOException {
770: super (is);
771: File storage = initCacheStore(path);
772: os = new BufferedOutputStream(new FileOutputStream(storage));
773: modTime = time;
774: this .path = path;
775: }
776:
777: @Override
778: public void close() throws IOException {
779: super .close();
780: NbPreferences.forModule(RSSFeed.class).put(path, modTime);
781: os.close();
782: }
783:
784: @Override
785: public int read() throws IOException {
786: int val = super .read();
787: os.write(val);
788: return val;
789: }
790:
791: @Override
792: public int read(byte[] b, int off, int len) throws IOException {
793: int res = super .read(b, off, len);
794: if (res != -1) {
795: os.write(b, off, res);
796: }
797: return res;
798: }
799: }
800: }
|