001: /*
002: * Copyright (c) 2007, Sun Microsystems, Inc. All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * * Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * * Neither the name of Sun Microsystems, Inc. nor the names of its contributors
015: * may be used to endorse or promote products derived from this software without
016: * specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
022: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
028: * THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package org.myorg.feedreader;
032:
033: import com.sun.syndication.feed.synd.SyndEntry;
034: import com.sun.syndication.feed.synd.SyndFeed;
035: import java.awt.Image;
036: import java.awt.event.ActionEvent;
037: import java.beans.IntrospectionException;
038: import java.io.IOException;
039: import java.io.InputStream;
040: import java.io.ObjectOutputStream;
041: import java.net.MalformedURLException;
042: import java.net.URL;
043: import javax.swing.AbstractAction;
044: import javax.swing.Action;
045: import org.openide.DialogDisplayer;
046: import org.openide.NotifyDescriptor;
047: import org.openide.actions.DeleteAction;
048: import org.openide.actions.OpenAction;
049: import org.openide.cookies.InstanceCookie;
050: import org.openide.cookies.OpenCookie;
051: import org.openide.filesystems.FileLock;
052: import org.openide.filesystems.FileObject;
053: import org.openide.filesystems.Repository;
054: import org.openide.loaders.DataFolder;
055: import org.openide.loaders.DataObject;
056: import org.openide.loaders.DataObjectNotFoundException;
057: import org.openide.nodes.BeanNode;
058: import org.openide.nodes.FilterNode;
059: import org.openide.nodes.Node;
060: import org.openide.util.Exceptions;
061: import org.openide.util.Lookup;
062: import org.openide.util.NbBundle;
063: import org.openide.util.Utilities;
064: import org.openide.util.actions.SystemAction;
065: import org.openide.util.lookup.Lookups;
066: import org.openide.util.lookup.ProxyLookup;
067:
068: public class RssNode extends FilterNode {
069:
070: /** Declaring the children of the root RSS node */
071: public RssNode(Node folderNode) {
072: super (folderNode, new RssFolderChildren(folderNode));
073: }
074:
075: /** Declaring the Add Feed action and Add Folder action */
076: public Action[] getActions(boolean popup) {
077: DataFolder df = (DataFolder) getLookup().lookup(
078: DataFolder.class);
079: return new Action[] { new AddRssAction(df),
080: new AddFolderAction(df) };
081: }
082:
083: /** Getting the root node */
084: public static class RootRssNode extends RssNode {
085: public RootRssNode() throws DataObjectNotFoundException {
086: super (DataObject.find(
087: Repository.getDefault().getDefaultFileSystem()
088: .getRoot().getFileObject("RssFeeds"))
089: .getNodeDelegate());
090: }
091:
092: public String getDisplayName() {
093: return NbBundle.getMessage(RssNode.class, "FN_title");
094: }
095: }
096:
097: /** Getting the children of the root node */
098: private static class RssFolderChildren extends FilterNode.Children {
099: RssFolderChildren(Node rssFolderNode) {
100: super (rssFolderNode);
101: }
102:
103: protected Node[] createNodes(Object key) {
104: Node n = (Node) key;
105: if (n.getLookup().lookup(DataFolder.class) != null) {
106: return new Node[] { new RssNode(n) };
107: } else {
108: Feed feed = getFeed(n);
109: if (feed != null) {
110: try {
111: return new Node[] { new OneFeedNode(n, feed
112: .getSyndFeed()) };
113: } catch (IOException ioe) {
114: Exceptions.printStackTrace(ioe);
115: }
116: }
117: }
118: // best effort
119: return new Node[] { new FilterNode(n) };
120: }
121: }
122:
123: /** Getting the feed node and wrapping it in a FilterNode */
124: private static class OneFeedNode extends FilterNode {
125:
126: OneFeedNode(Node feedFileNode, SyndFeed feed) {
127: super (feedFileNode, new FeedChildren(feed),
128: new ProxyLookup(new Lookup[] {
129: Lookups.fixed(new Object[] { feed }),
130: feedFileNode.getLookup() }));
131: }
132:
133: public String getDisplayName() {
134: SyndFeed feed = (SyndFeed) getLookup().lookup(
135: SyndFeed.class);
136: return feed.getTitle();
137: }
138:
139: public Image getIcon(int type) {
140: return Utilities
141: .loadImage("org/myorg/feedreader/rss16.gif");
142: }
143:
144: public Image getOpenedIcon(int type) {
145: return getIcon(type);
146: }
147:
148: public Action[] getActions(boolean context) {
149: return new Action[] { SystemAction.get(DeleteAction.class) };
150: }
151:
152: }
153:
154: /** Defining the children of a feed node */
155: private static class FeedChildren extends Children.Keys {
156: private final SyndFeed feed;
157:
158: public FeedChildren(SyndFeed feed) {
159: this .feed = feed;
160: }
161:
162: protected void addNotify() {
163: setKeys(feed.getEntries());
164: }
165:
166: public Node[] createNodes(Object key) {
167: try {
168: return new Node[] { new EntryBeanNode((SyndEntry) key) };
169: } catch (IntrospectionException ex) {
170: assert false : ex;
171: return new Node[0];
172: }
173: }
174: }
175:
176: /** Wrapping the children in a FilterNode */
177: private static class EntryBeanNode extends FilterNode {
178:
179: private final SyndEntry entry;
180:
181: public EntryBeanNode(SyndEntry entry)
182: throws IntrospectionException {
183: super (new BeanNode(entry), Children.LEAF, Lookups
184: .fixed(new Object[] { entry,
185: new EntryOpenCookie(entry) }));
186: this .entry = entry;
187: }
188:
189: /** Using HtmlDisplayName ensures any HTML in RSS entry titles are properly handled, escaped, entities resolved, etc. */
190: public String getHtmlDisplayName() {
191: return entry.getTitle();
192: }
193:
194: /** Making a tooltip out of the entry's description */
195: public String getShortDescription() {
196: StringBuffer sb = new StringBuffer();
197: sb.append("Author: " + entry.getAuthor() + "; ");
198: if (entry.getPublishedDate() != null) {
199: sb.append("Published: ").append(
200: entry.getPublishedDate().toString());
201: }
202: return sb.toString();
203: }
204:
205: /** Providing the Open action on a feed entry */
206: public Action[] getActions(boolean popup) {
207: return new Action[] { SystemAction.get(OpenAction.class) };
208: }
209:
210: public Action getPreferredAction() {
211: return (SystemAction) getActions(false)[0];
212: }
213:
214: }
215:
216: /** Specifying what should happen when the user invokes the Open action */
217: private static class EntryOpenCookie implements OpenCookie {
218:
219: private final SyndEntry entry;
220:
221: EntryOpenCookie(SyndEntry entry) {
222: this .entry = entry;
223: }
224:
225: public void open() {
226: BrowserTopComponent btc = BrowserTopComponent
227: .getBrowserComponent(entry.getTitle());
228: btc.open();
229: btc.requestActive();
230: btc.setPage(entry.getUri());
231: }
232:
233: }
234:
235: /** Looking up a feed */
236: private static Feed getFeed(Node node) {
237: InstanceCookie ck = (InstanceCookie) node
238: .getCookie(InstanceCookie.class);
239: if (ck == null) {
240: throw new IllegalStateException(
241: "Bogus file in feeds folder: "
242: + node.getLookup().lookup(FileObject.class));
243: }
244: try {
245: return (Feed) ck.instanceCreate();
246: } catch (ClassNotFoundException ex) {
247: Exceptions.printStackTrace(ex);
248: } catch (IOException ex) {
249: Exceptions.printStackTrace(ex);
250: }
251: return null;
252: }
253:
254: /** An action for adding a folder to organize feeds into groups */
255: private static class AddFolderAction extends AbstractAction {
256:
257: private final DataFolder folder;
258:
259: public AddFolderAction(DataFolder df) {
260: super (NbBundle.getMessage(RssNode.class,
261: "FN_addfolderbutton"));
262: folder = df;
263: }
264:
265: public void actionPerformed(ActionEvent ae) {
266: NotifyDescriptor.InputLine nd = new NotifyDescriptor.InputLine(
267: NbBundle.getMessage(RssNode.class,
268: "FN_askfolder_msg"), NbBundle.getMessage(
269: RssNode.class, "FN_askfolder_title"),
270: NotifyDescriptor.OK_CANCEL_OPTION,
271: NotifyDescriptor.PLAIN_MESSAGE);
272:
273: Object result = DialogDisplayer.getDefault().notify(nd);
274:
275: if (result.equals(NotifyDescriptor.OK_OPTION)) {
276: final String folderString = nd.getInputText();
277: try {
278: DataFolder.create(folder, folderString);
279: } catch (IOException ex) {
280: Exceptions.printStackTrace(ex);
281: }
282: }
283: }
284: }
285:
286: /** An action for adding a feed */
287: private static class AddRssAction extends AbstractAction {
288:
289: private final DataFolder folder;
290:
291: public AddRssAction(DataFolder df) {
292: super (NbBundle.getMessage(RssNode.class, "FN_addbutton"));
293: folder = df;
294: }
295:
296: public void actionPerformed(ActionEvent ae) {
297: NotifyDescriptor.InputLine nd = new NotifyDescriptor.InputLine(
298: NbBundle.getMessage(RssNode.class, "FN_askurl_msg"),
299: NbBundle.getMessage(RssNode.class,
300: "FN_askurl_title"),
301: NotifyDescriptor.OK_CANCEL_OPTION,
302: NotifyDescriptor.PLAIN_MESSAGE);
303:
304: Object result = DialogDisplayer.getDefault().notify(nd);
305:
306: if (result.equals(NotifyDescriptor.OK_OPTION)) {
307: String urlString = nd.getInputText();
308: URL url;
309: try {
310: url = new URL(urlString);
311: } catch (MalformedURLException e) {
312: String message = NbBundle.getMessage(RssNode.class,
313: "FN_askurl_err", urlString);
314: Exceptions.attachLocalizedMessage(e, message);
315: Exceptions.printStackTrace(e);
316: return;
317: }
318: try {
319: checkConnection(url);
320: } catch (IOException e) {
321: String message = NbBundle.getMessage(RssNode.class,
322: "FN_cannotConnect_err", urlString);
323: Exceptions.attachLocalizedMessage(e, message);
324: Exceptions.printStackTrace(e);
325: return;
326: }
327: Feed f = new Feed(url);
328: FileObject fld = folder.getPrimaryFile();
329: String baseName = "RssFeed";
330: int ix = 1;
331: while (fld.getFileObject(baseName + ix, "ser") != null) {
332: ix++;
333: }
334: try {
335: FileObject writeTo = fld.createData(baseName + ix,
336: "ser");
337: FileLock lock = writeTo.lock();
338: try {
339: ObjectOutputStream str = new ObjectOutputStream(
340: writeTo.getOutputStream(lock));
341: try {
342: str.writeObject(f);
343: } finally {
344: str.close();
345: }
346: } finally {
347: lock.releaseLock();
348: }
349: } catch (IOException ioe) {
350: Exceptions.printStackTrace(ioe);
351: }
352: }
353: }
354:
355: private static void checkConnection(final URL url)
356: throws IOException {
357: InputStream is = url.openStream();
358: is.close();
359: }
360:
361: }
362:
363: }
|