001/*
002 * $Id: LinkModel.java 2951 2008-06-17 10:07:49Z kleopatra $
003 *
004 * Copyright 2004 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.beans.PropertyChangeListener;
025import java.beans.PropertyChangeSupport;
026import java.net.MalformedURLException;
027import java.net.URL;
028import java.util.logging.Logger;
029
030/**
031 * An bean which represents an URL link.
032 * 
033 * Text, URL and visited are bound properties. Compares by Text.
034 * 
035 * @author Mark Davidson
036 * @author Jeanette Winzenburg
037 */
038public class LinkModel implements Comparable {
039
040    private static final Logger LOG = Logger.getLogger(LinkModel.class
041            .getName());
042
043    private String text; // display text
044
045    private URL url; // url of the link
046
047    private String target; // url target frame
048
049    private boolean visited = false;
050
051    private PropertyChangeSupport propertyChangeSupport;
052
053    public static final String VISITED_PROPERTY = "visited";
054
055    // hack - this class assumes that the url always != null
056    // need to cleanup
057    private static String defaultURLString = "https://jdnc.dev.java.net";
058
059    private static URL defaultURL;
060
061    /**
062     * 
063     * @param text
064     * @param target
065     * @param url
066     */
067    public LinkModel(String text, String target, URL url) {
068        setText(text);
069        setTarget(target);
070        setURL(url != null ? url : getDefaultURL());
071    }
072
073    public LinkModel() {
074        this(" ", null, null);
075    }
076    
077    public LinkModel(String text) {
078        this(text, null, null);
079    }
080
081    /**
082     * @param text text to that a renderer would display
083     * @param target the target that a URL should load into.
084     * @param template a string that represents a URL with
085     * &{N} place holders for string substitution
086     * @param args an array of strings which will be used for substitition
087     */
088    public LinkModel(String text, String target, String template, String[] args) {
089        setText(text);
090        setTarget(target);
091        setURL(createURL(template, args));
092    }
093
094    /**
095     * Set the display text.
096     */
097    public void setText(String text) {
098        String old = getText();
099        this.text = text;
100        firePropertyChange("text", old, getText());
101    }
102
103    public String getText() {
104        if (text != null) {
105            return text;
106        } else if (url != null) {
107            return getURL().toString();
108        }
109        return null;
110    }
111
112    public void setURLString(String howToURLString) {
113        URL url = null;
114        try {
115            url = new URL(howToURLString);
116        } catch (MalformedURLException e) {
117            url = getDefaultURL();
118            LOG.warning("the given urlString is malformed: " + howToURLString + 
119                    "\n falling back to default url: " + url);
120        }
121        setURL(url);
122    }
123
124    private URL getDefaultURL() {
125        if (defaultURL == null) {
126            try {
127                defaultURL = new URL(defaultURLString);
128            } catch (MalformedURLException e) {
129                LOG.fine("should not happen - defaultURL is wellFormed: "
130                        + defaultURLString);
131            }
132        }
133        return defaultURL;
134    }
135
136    /**
137     * Set the url and resets the visited flag.
138     * 
139     * Think: keep list of visited urls here?
140     */
141    public void setURL(URL url) {
142        if (url == null) {
143            throw new IllegalArgumentException("URL for link cannot be null");
144        }
145        if (url.equals(getURL()))
146            return;
147        URL old = getURL();
148        this.url = url;
149        firePropertyChange("URL", old, url);
150        setVisited(false);
151    }
152
153    public URL getURL() {
154        return url;
155    }
156
157    /**
158     * Create a URL from a template string that has place holders and an array
159     * of strings which will be substituted into the place holders. The place
160     * holders are represented as
161     * 
162     * @{N} where N = { 1..n }
163     *      <p>
164     *      For example, if the template contains a string like:
165     *      http://bugz.sfbay/cgi-bin/showbug?cat=@{1}&sub_cat=@{2} and a two
166     *      arg array contains: java, classes_swing The resulting URL will be:
167     *      http://bugz.sfbay/cgi-bin/showbug?cat=java&sub_cat=classes_swing
168     *      <p>
169     * @param template a url string that contains the placeholders
170     * @param args an array of strings that will be substituted
171     */
172    private URL createURL(String template, String[] args) {
173        URL url = null;
174        try {
175            String urlStr = template;
176            for (int i = 0; i < args.length; i++) {
177                urlStr = urlStr.replaceAll("@\\{" + (i + 1) + "\\}", args[i]);
178            }
179            url = new URL(urlStr);
180        } catch (MalformedURLException ex) {
181            //
182        }
183        return url;
184    }
185
186    /**
187     * Set the target that the URL should load into. This can be a uri
188     * representing another control or the name of a window or special targets.
189     * See: http://www.w3c.org/TR/html401/present/frames.html#adef-target
190     */
191    public void setTarget(String target) {
192        this.target = target;
193    }
194
195    /**
196     * Return the target for the URL.
197     * 
198     * @return value of the target. If null then "_blank" will be returned.
199     */
200    public String getTarget() {
201        if (target != null) {
202            return target;
203        } else {
204            return "_blank";
205        }
206    }
207
208    /**
209     * Sets a flag to indicate if the link has been visited. The state of this
210     * flag can be used to render the color of the link.
211     */
212    public void setVisited(boolean visited) {
213        boolean old = getVisited();
214        this.visited = visited;
215        firePropertyChange(VISITED_PROPERTY, old, getVisited());
216    }
217
218    public boolean getVisited() {
219        return visited;
220    }
221
222    // ---------------------- property change notification
223
224    public void addPropertyChangeListener(PropertyChangeListener l) {
225        getPropertyChangeSupport().addPropertyChangeListener(l);
226
227    }
228
229    public void removePropertyChangeListener(PropertyChangeListener l) {
230        if (propertyChangeSupport == null)
231            return;
232        propertyChangeSupport.removePropertyChangeListener(l);
233    }
234
235    protected void firePropertyChange(String property, Object oldValue,
236            Object newValue) {
237        if (propertyChangeSupport == null)
238            return;
239        propertyChangeSupport.firePropertyChange(property, oldValue, newValue);
240    }
241
242    protected void firePropertyChange(String property, boolean oldValue,
243            boolean newValue) {
244        if (propertyChangeSupport == null)
245            return;
246        propertyChangeSupport.firePropertyChange(property, oldValue, newValue);
247
248    }
249
250    private PropertyChangeSupport getPropertyChangeSupport() {
251        if (propertyChangeSupport == null) {
252            propertyChangeSupport = new PropertyChangeSupport(this);
253        }
254        return propertyChangeSupport;
255    }
256
257    // Comparable interface for sorting.
258    public int compareTo(Object obj) {
259        if (obj == null) {
260            return 1;
261        }
262        if (obj == this) {
263            return 0;
264        }
265        return text.compareTo(((LinkModel) obj).text);
266    }
267
268    @Override
269    public boolean equals(Object obj) {
270        if (this == obj) {
271            return true;
272        }
273        if (obj != null && obj instanceof LinkModel) {
274            LinkModel other = (LinkModel) obj;
275            if (!getText().equals(other.getText())) {
276                return false;
277            }
278
279            if (!getTarget().equals(other.getTarget())) {
280                return false;
281            }
282
283            return getURL().equals(other.getURL());
284        }
285        return false;
286    }
287
288    @Override
289    public int hashCode() {
290        int result = 7;
291
292        result = 37 * result + ((getText() == null) ? 0 : getText().hashCode());
293        result = 37 * result
294                + ((getTarget() == null) ? 1 : getTarget().hashCode());
295        result = 37 * result + ((getURL() == null) ? 2 : getURL().hashCode());
296
297        return result;
298    }
299
300    @Override
301    public String toString() {
302
303        StringBuffer buffer = new StringBuffer("[");
304        // RG: Fix for J2SE 5.0; Can't cascade append() calls because
305        // return type in StringBuffer and AbstractStringBuilder are different
306        buffer.append("url=");
307        buffer.append(url);
308        buffer.append(", target=");
309        buffer.append(target);
310        buffer.append(", text=");
311        buffer.append(text);
312        buffer.append("]");
313
314        return buffer.toString();
315    }
316
317}