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-2007 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: package org.netbeans.modules.visualweb.propertyeditors;
042:
043: import java.awt.Component;
044: import java.net.MalformedURLException;
045: import java.net.URL;
046: import javax.faces.component.UIComponent;
047:
048: /**
049: * An editor for string properties that represent URLs. URLs may be typed directly
050: * in-line, or created in a custom editor (see {@link UrlPropertyPanel}). Note
051: * that URLs should be either absolute (beginning with a protocol) or
052: * context-relative (beginning with "/"). Characters not allowed in URLs are
053: * converted to escape sequences when the URL property value is saved.
054: *
055: * A custom editor is also supplied, which presents a tree of project resources
056: * to which links may be made. These links are created as context-relative URLs.
057: * The custom editor may be configured by overriding a number of property editor
058: * methods:
059: *
060: * <ul>
061: * <li>To provide support for components that render URL anchors within a page.
062: * If <code>isTargetComponent()</code> returns true for a component, the custom
063: * editor will show the component as a target node within its containing page
064: * node. If selected, it results in a URL with <pre>#</pre> plus whatever is
065: * returned by <code>getTargetComponentName()</code> added at the end.
066: * <li>To limit the types of files that may be selected. By default, the method
067: * <code>getFileFilter()</code> returns null, which results in all files being
068: * selectable as URL targets. If a file filter is returned, it will be used to
069: * determine which files are shown. There is a convenience method for creating
070: * file filters, <code>createFileFilter(String,String)</code>.
071: * </ul>
072: *
073: * @author gjmurphy
074: */
075: //TODO Utility methods encodeUrl() and decodeUrl() should convert non-ASCII chars to hex UTF8
076: public class UrlPropertyEditor extends PropertyEditorBase implements
077: com.sun.rave.propertyeditors.UrlPropertyEditor {
078:
079: public boolean isEditableAsText() {
080: return true;
081: }
082:
083: protected String getPropertyHelpId() {
084: return "projrave_ui_elements_propeditors_url_prop_ed";
085: }
086:
087: public String getAsText() {
088: Object value = super .getValue();
089: if (value == null || value.equals(super .unsetValue))
090: return "";
091: return decodeUrl((String) value);
092: }
093:
094: public void setAsText(String text) throws IllegalArgumentException {
095: if (text == null) {
096: super .setValue(null);
097: } else if (text.trim().length() == 0) {
098: super .setValue(super .unsetValue);
099: } else {
100: super .setValue(encodeUrl(text.trim()));
101: }
102: }
103:
104: public Component getCustomEditor() {
105: return new UrlPropertyPanel(this );
106: }
107:
108: public boolean supportsCustomEditor() {
109: return true;
110: }
111:
112: /**
113: * Returns true if the component specified will generate an HTML URL anchor
114: * when rendered. By default, returns <code>false</code>. This method shoud
115: * be overriden by implementing classes.
116: */
117: public boolean isTargetComponent(UIComponent component) {
118: return false;
119: }
120:
121: /**
122: * Returns the value of the <code>name</code> attribute that the generated
123: * HTML URL anchor will have. By default, returns the value of the component's
124: * <code>id</code> property. This method is intended to be overriden by
125: * implementing classes.
126: */
127: public String getTargetComponentName(UIComponent component) {
128: return component.getId();
129: }
130:
131: /**
132: * Returns a file filter to use in determining which files are suitable for
133: * selection by this URL editor. By default, returns null, to indicate that
134: * all files are suitable.
135: */
136: public UrlFileFilter getFileFilter() {
137: return null;
138: }
139:
140: /**
141: * Convert a file system path to a URL by converting unsafe characters into
142: * numeric character entity references. The unsafe characters are listed in
143: * in the IETF specification of URLs
144: * (<a href="http://www.ietf.org/rfc/rfc1738.txt">RFC 1738</a>). Safe URL
145: * characters are all printable ASCII characters, with the exception of the
146: * space characters, '#', <', '>', '%', '[', ']', '{', '}', and '~'. This
147: * method differs from {@link java.net.URLEncoder#encode(String)}, in that
148: * it is intended for encoding the path portion of a URL, not the query
149: * string. This method also attempts to recognize value binding expressions
150: * within the string, as any sequence of characters matching the regular
151: * expression {@code #{[^{]*}). Value binding expressions are <emph>not</emph>
152: * escaped.
153: */
154: public static String encodeUrl(String url) {
155: if (url == null || url.length() == 0)
156: return url;
157: StringBuffer buffer = new StringBuffer();
158: String anchor = null;
159: int index = url.lastIndexOf('#');
160: if (index >= 0) {
161: if (index == url.length() - 1
162: || !(url.charAt(index + 1) == '{' && url
163: .lastIndexOf('}') > index)) {
164: anchor = url.substring(index + 1);
165: url = url.substring(0, index);
166: }
167: }
168: char[] chars = url.toCharArray();
169: for (int i = 0; i < chars.length; i++) {
170: if (chars[i] <= '\u0020') {
171: buffer.append('%');
172: buffer.append(Integer.toHexString((int) chars[i]));
173: } else {
174: switch (chars[i]) {
175: case '\u0009': // Tab
176: buffer.append("%09");
177: break;
178: case '\u0020': // Space
179: buffer.append("%20");
180: break;
181: case '#':
182: if (i < chars.length - 1 && chars[i + 1] == '{') {
183: // Pass over value binding expressions
184: int j = i + 2;
185: while (j < chars.length && chars[j] != '}')
186: j++;
187: if (j < chars.length && chars[j] == '}') {
188: buffer.append(chars, i, (j - i) + 1);
189: i = j;
190: } else {
191: buffer.append("%23");
192: }
193: } else {
194: buffer.append("%23");
195: }
196: break;
197: case '%':
198: buffer.append("%25");
199: break;
200: case '<':
201: buffer.append("%3C");
202: break;
203: case '>':
204: buffer.append("%3E");
205: break;
206: case '[':
207: buffer.append("%5B");
208: break;
209: case ']':
210: buffer.append("%5D");
211: break;
212: case '{':
213: buffer.append("%7B");
214: break;
215: case '}':
216: buffer.append("%7D");
217: break;
218: case '~':
219: buffer.append("%7E");
220: break;
221: default:
222: buffer.append(chars[i]);
223: }
224: }
225: }
226: if (anchor != null) {
227: buffer.append('#');
228: buffer.append(anchor);
229: }
230: if (buffer.length() == url.length())
231: return url;
232: return buffer.toString();
233: }
234:
235: /**
236: * Convert a URL to a file system path by intrepreting all two-character
237: * sequences of the form <code>%xx</code> as a hexadecimal character
238: * reference in UTF8.
239: */
240: public static String decodeUrl(String url) {
241: // Optimized for case where URL is not encoded
242: if (url == null || url.indexOf('%') == -1)
243: return url;
244: StringBuffer buffer = new StringBuffer();
245: char[] chars = url.toCharArray();
246: for (int i = 0; i < chars.length; i++) {
247: if (chars[i] == '%' && i + 2 < chars.length) {
248: buffer.append((char) Integer.parseInt(String
249: .copyValueOf(chars, i + 1, 2), 16));
250: i += 2;
251: } else {
252: buffer.append(chars[i]);
253: }
254: }
255: return buffer.toString();
256: }
257:
258: }
|