001/*
002 * $Id$
003 *
004 * Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 *
021 */
022package org.jdesktop.swingx.hyperlink;
023
024import java.awt.Desktop;
025import java.awt.GraphicsEnvironment;
026import java.awt.HeadlessException;
027import java.awt.Desktop.Action;
028import java.awt.event.ActionEvent;
029import java.io.IOException;
030import java.net.URI;
031import java.util.logging.Logger;
032
033/**
034 * A implementation wrapping <code>Desktop</code> actions BROWSE and MAIL, that is
035 * URI-related. 
036 * 
037 * @author Jeanette Winzenburg
038 */
039public class HyperlinkAction extends AbstractHyperlinkAction<URI> {
040    @SuppressWarnings("unused")
041    private static final Logger LOG = Logger.getLogger(HyperlinkAction.class
042            .getName());
043    
044    private Action desktopAction;
045    private URIVisitor visitor;
046    
047    /**
048     * Factory method to create and return a HyperlinkAction for the given uri. Tries
049     * to guess the appropriate type from the uri. If uri is not null and has a
050     * scheme of mailto, create one of type Mail. In all other cases, creates one
051     * for BROWSE.
052     * 
053     * @param uri to uri to create a HyperlinkAction for, maybe null.
054     * @return a HyperlinkAction for the given URI.
055     * @throws HeadlessException if {@link
056     * GraphicsEnvironment#isHeadless()} returns {@code true}
057     * @throws UnsupportedOperationException if the current platform doesn't support
058     *   Desktop
059     */
060    public static HyperlinkAction createHyperlinkAction(URI uri) {
061        Action type = isMailURI(uri) ? Action.MAIL : Action.BROWSE;
062        return createHyperlinkAction(uri, type);
063    }
064    
065    /**
066     * Creates and returns a HyperlinkAction with the given target and action type.
067     * @param uri the target uri, maybe null.
068     * @param desktopAction the type of desktop action this class should perform, must be
069     *    BROWSE or MAIL
070     * @return a HyperlinkAction
071     * @throws HeadlessException if {@link
072     * GraphicsEnvironment#isHeadless()} returns {@code true}
073     * @throws UnsupportedOperationException if the current platform doesn't support
074     *   Desktop
075     * @throws IllegalArgumentException if unsupported action type 
076     */
077    public static HyperlinkAction createHyperlinkAction(URI uri, Action type) {
078        return new HyperlinkAction(uri, type);
079    }
080
081    /**
082     * @param uri
083     * @return
084     */
085    private static boolean isMailURI(URI uri) {
086        return uri != null && "mailto".equalsIgnoreCase(uri.getScheme());
087    }
088
089    /** 
090     * Instantiates a HyperlinkAction with action type BROWSE.
091     * 
092     * @throws HeadlessException if {@link
093     * GraphicsEnvironment#isHeadless()} returns {@code true}
094     * @throws UnsupportedOperationException if the current platform doesn't support
095     *   Desktop
096     * @throws IllegalArgumentException if unsupported action type 
097     */
098    public HyperlinkAction() {
099        this(Action.BROWSE);
100    }
101    
102    /**
103     * Instantiates a HyperlinkAction with the given action type.
104     * 
105     * @param desktopAction the type of desktop action this class should perform, must be
106     *    BROWSE or MAIL
107     * @throws HeadlessException if {@link
108     * GraphicsEnvironment#isHeadless()} returns {@code true}
109     * @throws UnsupportedOperationException if the current platform doesn't support
110     *   Desktop
111     * @throws IllegalArgumentException if unsupported action type 
112     */ 
113    public HyperlinkAction(Action desktopAction) {
114        this(null, desktopAction);
115    }
116
117    /**
118     * 
119     * @param uri the target uri, maybe null.
120     * @param desktopAction the type of desktop action this class should perform, must be
121     *    BROWSE or MAIL
122     * @throws HeadlessException if {@link
123     * GraphicsEnvironment#isHeadless()} returns {@code true}
124     * @throws UnsupportedOperationException if the current platform doesn't support
125     *   Desktop
126     * @throws IllegalArgumentException if unsupported action type 
127     */
128    public HyperlinkAction(URI uri, Action desktopAction) {
129        super();
130        if (!Desktop.isDesktopSupported()) {
131            throw new UnsupportedOperationException("Desktop API is not " +
132                                                    "supported on the current platform");
133        }
134        if (desktopAction != Desktop.Action.BROWSE && desktopAction != Desktop.Action.MAIL) {
135           throw new IllegalArgumentException("Illegal action type: " + desktopAction + 
136                   ". Must be BROWSE or MAIL");
137        }
138        this.desktopAction = desktopAction;
139        getURIVisitor();
140        setTarget(uri);
141    }
142    
143    /**
144     * {@inheritDoc} <p>
145     * 
146     * Implemented to perform the appropriate Desktop action if supported on the current
147     * target. Sets the visited property to true if the desktop action doesn't throw
148     * an exception or to false if it did. 
149     * 
150     * Does nothing if the action isn't supported.  
151     */
152    @Override
153    public void actionPerformed(ActionEvent e) {
154        if (!getURIVisitor().isEnabled(getTarget())) return;
155        try {
156            getURIVisitor().visit(getTarget());
157            setVisited(true);
158        } catch (IOException e1) {
159            setVisited(false);
160            LOG.fine("cant visit Desktop " + e);
161        }
162    }
163    
164    /**
165     * @return
166     */
167    public Action getDesktopAction() {
168        return desktopAction;
169    }
170    
171    
172    @Override
173    protected void installTarget() {
174        // doohh ... this is called from super's constructor before we are 
175        // fully initialized
176        if (visitor == null) return;
177        super.installTarget();
178        updateEnabled();
179    }
180    
181    /**
182     * 
183     */
184    private void updateEnabled() {
185        setEnabled(getURIVisitor().isEnabled(getTarget()));
186    }
187
188    /**
189     * @return
190     */
191    private URIVisitor getURIVisitor() {
192        if (visitor == null) {
193            visitor = createURIVisitor();
194        }
195        return visitor;
196    }
197
198    /**
199     * @return
200     */
201    private URIVisitor createURIVisitor() {
202        return getDesktopAction() == Action.BROWSE ?
203                new BrowseVisitor() : new MailVisitor();
204    }
205
206    /**
207     * Thin wrapper around Desktop functionality to allow uniform handling of
208     * different actions in HyperlinkAction.
209     * 
210     */
211    private abstract class URIVisitor {
212        protected boolean desktopSupported = Desktop.isDesktopSupported();
213        
214        /**
215         * Returns a boolean indicating whether the action is supported on the
216         * given URI. This implementation returns true if both the Desktop is 
217         * generally supported and <code>isActionSupported()</code>.
218         * 
219         * PENDING JW: hmm ... which class exactly has to check for valid combination
220         * of Action and URI?
221         * 
222         * @param uri
223         * @return
224         * 
225         * @see #isActionSupported()
226         */
227        public boolean isEnabled(URI uri) {
228            return desktopSupported && isActionSupported();
229        }
230        
231        /**
232         * Visits the given URI via Desktop functionality. Must not be called if not
233         * enabled.
234         * 
235         * @param uri the URI to visit
236         * @throws IOException if the Desktop method throws IOException.
237         * 
238         */
239        public abstract void visit(URI uri) throws IOException;
240        
241        /**
242         * Returns a boolean indicating if the action is supported by the current 
243         * Desktop.
244         * 
245         * @return true if the Action is supported by the current desktop, false
246         * otherwise.
247         */
248        protected abstract boolean isActionSupported();
249    }
250    
251    private class BrowseVisitor extends URIVisitor {
252
253        /**
254         * {@inheritDoc} <p>
255         * 
256         * Implemented to message the browse method of Desktop.
257         */
258        @Override
259        public void visit(URI uri) throws IOException {
260            Desktop.getDesktop().browse(uri);
261        }
262
263        /**
264         * {@inheritDoc} <p>
265         * 
266         * Implemented to query the Desktop for support of BROWSE action.
267         */
268        @Override
269        protected boolean isActionSupported() {
270            return Desktop.getDesktop().isSupported(Desktop.Action.BROWSE);
271        }
272
273        /**
274         * {@inheritDoc} <p>
275         * 
276         * Implemented to guard against null URI in addition to super.
277         */
278        @Override
279        public boolean isEnabled(URI uri) {
280            return uri != null && super.isEnabled(uri);
281        }
282        
283        
284    }
285    
286    private class MailVisitor extends URIVisitor {
287
288        /**
289         * {@inheritDoc} <p>
290         * 
291         * Implemented to message the mail function of Desktop.
292         */
293        @Override
294        public void visit(URI uri) throws IOException {
295            if (uri == null) {
296                Desktop.getDesktop().mail();
297            } else {
298                Desktop.getDesktop().mail(uri);
299            }
300        }
301        /**
302         * {@inheritDoc} <p>
303         * 
304         * Implemented to query the Desktop for support of MAIL action.
305         */
306        @Override
307        protected boolean isActionSupported() {
308            return Desktop.getDesktop().isSupported(Desktop.Action.MAIL);
309        }
310        
311    }
312}