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 com.sun.rave.propertyeditors.domains;
042:
043: import com.sun.rave.designtime.DesignProperty;
044: import com.sun.rave.designtime.DesignContext;
045: import java.text.DateFormat;
046: import java.text.ParseException;
047: import java.util.ArrayList;
048: import java.util.Date;
049: import java.util.StringTokenizer;
050:
051: /**
052: * Specialized domain which may be extended by the addition and re-ordering of
053: * elements. Added elements and information about order are stored in one of
054: * three contexts: the design context, the project context, or the IDE context.
055: * By default, an editable domain contains only its pre-defined elements in
056: * their pre-defined order. Any changes made are "permanent" within the context
057: * defined by the domain. A storage context must be specified when an instance
058: * of this class is created.
059: *
060: * <p>Editable domains impose the restriction that their elements must all
061: * contain values of the same final type. This is in part to facilitate user
062: * editing of values in the IDE, in part to make storage of elements more
063: * predictable. A value class must be specified when an instance of this class
064: * is created. Elements added whose value is not of this type will be rejected.
065: *
066: * <p><strong>Nota Bene:</strong> Currently only the following value classes
067: * will be stored correctly, due to limitations in the design-time support for
068: * data storage:
069: * <ul>
070: * <li><code>java.lang.String</code></li>
071: * <li><code>java.lang.Integer</code></li>
072: * <li><code>java.util.Date</code></li>
073: * </ul>
074: *
075: */
076:
077: //TODO Storage proxy and saving/retrieving logic should be moved to editors, so that mods can be canceled
078: public abstract class EditableDomain extends AttachedDomain {
079:
080: protected static int DESIGN_CONTEXT_STORAGE = 0;
081: protected static int PROJECT_STORAGE = 1;
082: protected static int IDE_STORAGE = 2;
083:
084: private int storageScope;
085: private Class elementValueClass;
086: protected ArrayList elements;
087:
088: /**
089: * Creates a new instance of ExtensibleDomain, for elements whose values
090: * are of type <code>java.lang.String</code>, for which elements will be
091: * stored in the scope specified. If the storage scope constant is not
092: * recognized, storage will default to the design context.
093: */
094: protected EditableDomain(int storageScope) {
095: this (storageScope, String.class);
096: }
097:
098: /**
099: * Creates a new instance of ExtensibleDomain, for elements whose values
100: * are of the class specified, and for which elements will be stored in the
101: * scope specified. If the storage scope constant is not recognized, storage
102: * will default to the design context.
103: */
104: protected EditableDomain(int storageScope, Class elementValueClass) {
105: this .elementValueClass = elementValueClass;
106: if (storageScope == DESIGN_CONTEXT_STORAGE
107: || storageScope == PROJECT_STORAGE
108: || storageScope == IDE_STORAGE)
109: this .storageScope = storageScope;
110: else
111: this .storageScope = DESIGN_CONTEXT_STORAGE;
112: this .elements = new ArrayList();
113: }
114:
115: /**
116: * Returns the class of all element values in this domain. Normally, a domain
117: * may contain elements of any value. Editable domains require that their
118: * elements' values be all of the same type.
119: */
120: public Class getElementValueClass() {
121: return this .elementValueClass;
122: }
123:
124: /**
125: * Returns an array of the elements currently contained in this domain.
126: */
127: public Element[] getElements() {
128: return (Element[]) elements.toArray(Element.EMPTY_ARRAY);
129: }
130:
131: /**
132: * Returns the element at the index specified. If the index is out of bounds
133: * or no element exists at the index specified, returns null.
134: */
135: public Element getElementAt(int index) {
136: if (index < 0 || index > elements.size())
137: return null;
138: return (Element) elements.get(index);
139: }
140:
141: /**
142: * Returns the number of elements currently in this domain.
143: */
144: public int getSize() {
145: return elements.size();
146: }
147:
148: /**
149: * Replace the element at the index specified with the element specified.
150: * Returns the element previously at the index specified, null if there
151: * wasn't one.
152: */
153: public Element setElementAt(int index, Element element) {
154: Element previousElement = (Element) elements
155: .set(index, element);
156: refreshStorageProxy();
157: return previousElement;
158: }
159:
160: /**
161: * Add an element to this domain at the index specified. Elements at this
162: * and subsequent indexes are all shifted "down" one to make room for the
163: * new element.
164: */
165: public void addElementAt(int index, Element element) {
166: elements.add(index, element);
167: refreshStorageProxy();
168: }
169:
170: /**
171: * Add an element to the end of this domain's list of elements.
172: */
173: public void addElement(Element element) {
174: elements.add(element);
175: refreshStorageProxy();
176: }
177:
178: /**
179: * Remove the element at the index specified, and return it. Subsequent
180: * elements are shifted "up" one to vill the void. Returns null if there
181: * is no element at the index specified, or if the index is out of bounds.
182: */
183: public Element removeElementAt(int index) {
184: Element previousElement = (Element) elements.remove(index);
185: refreshStorageProxy();
186: return previousElement;
187: }
188:
189: /**
190: * Set the {@link DesignProperty} with which this domain is associated,
191: * and check for a previously stored proxy for this domain's elements. If
192: * one is found, then update this domain's elements to reflect what was
193: * found in storage.
194: *
195: * @param designProperty The new associated {@link DesignProperty}
196: */
197: public void setDesignProperty(DesignProperty designProperty) {
198: super .setDesignProperty(designProperty);
199: // Check for a stored proxy list of elements
200: DesignContext context = designProperty.getDesignBean()
201: .getDesignContext();
202: Object proxyObject = null;
203: if (this .storageScope == EditableDomain.DESIGN_CONTEXT_STORAGE)
204: proxyObject = context.getContextData(this .getClass()
205: .getName());
206: else if (this .storageScope == EditableDomain.PROJECT_STORAGE)
207: proxyObject = context.getProject().getProjectData(
208: this .getClass().getName());
209: else
210: proxyObject = context.getProject().getGlobalData(
211: this .getClass().getName());
212: // If no stored proxy list, then there is no saved domain state
213: if (proxyObject == null
214: || (proxyObject instanceof String && ((String) proxyObject)
215: .length() == 0))
216: return;
217: // If stored proxy list is of type StorageProxy, it was saved in memory,
218: // so just retrieve the list of elements
219: if (proxyObject instanceof StorageProxy) {
220: this .elements = ((StorageProxy) proxyObject).getElements();
221: // If stored proxy list is of type String, it was saved to file, so
222: // restore it and then retrieve the list of elements
223: } else if (proxyObject instanceof String) {
224: this .storageProxy = StorageProxy.restoreInstance(
225: (String) proxyObject, this .elementValueClass);
226: this .elements = this .storageProxy.getElements();
227: refreshStorageProxy();
228: }
229: }
230:
231: private StorageProxy storageProxy;
232:
233: void refreshStorageProxy() {
234: DesignContext context = designProperty.getDesignBean()
235: .getDesignContext();
236: if (storageProxy == null)
237: storageProxy = StorageProxy.newInstance(this .elements,
238: this .elementValueClass);
239: if (this .storageScope == EditableDomain.DESIGN_CONTEXT_STORAGE)
240: context.setContextData(this .getClass().getName(),
241: storageProxy);
242: else if (this .storageScope == EditableDomain.PROJECT_STORAGE)
243: // TO DO - Change string storage to object storage
244: context.getProject().setProjectData(
245: this .getClass().getName(), storageProxy.toString());
246: else
247: // TO DO - Change string storage to object storage
248: context.getProject().setGlobalData(
249: this .getClass().getName(), storageProxy.toString());
250: }
251:
252: /** An abstract utility class used to wrap the domain's list of elements,
253: * when passed to the design context for storage. If the storage proxy
254: * needs to be saved, its <code>toString()</code> method will be called. A
255: * new proxy is created using the static method <code>newIsntance()</code>.
256: * If the proxy was stored to file, later calls to retrieve the storage
257: * proxy will return the string instead. In this case, the storage proxy
258: * must be restored, using the static method <code>restoreInstance()</code>.
259: *
260: * <p>There are implementations of StorageProxy for all the value object
261: * types supported. These implementations take care of converting their
262: * values to and from strings.
263: */
264: static abstract class StorageProxy {
265:
266: protected ArrayList elementList;
267:
268: private StorageProxy() {
269: }
270:
271: public ArrayList getElements() {
272: return elementList;
273: }
274:
275: public static StorageProxy newInstance(ArrayList elementList,
276: Class valueClass) {
277: StorageProxy proxy;
278: if (valueClass == String.class)
279: proxy = new StringStorageProxy();
280: else if (valueClass == Integer.class)
281: proxy = new IntegerStorageProxy();
282: else if (valueClass == Date.class)
283: proxy = new DateStorageProxy();
284: else
285: return null;
286: proxy.elementList = elementList;
287: return proxy;
288: }
289:
290: public static StorageProxy restoreInstance(String str,
291: Class valueClass) {
292: StorageProxy proxy;
293: if (valueClass == String.class)
294: proxy = new StringStorageProxy();
295: else if (valueClass == Integer.class)
296: proxy = new IntegerStorageProxy();
297: else if (valueClass == Date.class)
298: proxy = new DateStorageProxy();
299: else
300: return null;
301: proxy.fromString(str);
302: return proxy;
303: }
304:
305: public String toString() {
306: StringBuffer buffer = new StringBuffer();
307: for (int i = 0; i < elementList.size(); i++) {
308: Element e = (Element) elementList.get(i);
309: appendEncoded(buffer, e.getLabel());
310: buffer.append('=');
311: appendEncoded(buffer, valueToString(e.getValue()));
312: buffer.append(',');
313: }
314: buffer.setLength(buffer.length() - 1);
315: return buffer.toString();
316: }
317:
318: public void fromString(String str) {
319: StringTokenizer tokenizer = new StringTokenizer(str, ",");
320: ArrayList elements = new ArrayList();
321: DateFormat format = DateFormat.getDateTimeInstance();
322: while (tokenizer.hasMoreTokens()) {
323: String token = tokenizer.nextToken();
324: int i = token.indexOf('=');
325: String label = decode(token.substring(0, i));
326: Object value = stringToValue(decode(token
327: .substring(i + 1)));
328: elements.add(new Element(value, label));
329: }
330: this .elementList = elements;
331: }
332:
333: abstract protected String valueToString(Object value);
334:
335: abstract protected Object stringToValue(String str);
336:
337: }
338:
339: static class StringStorageProxy extends StorageProxy {
340: protected String valueToString(Object value) {
341: return value.toString();
342: }
343:
344: protected Object stringToValue(String str) {
345: return str;
346: }
347: }
348:
349: static class IntegerStorageProxy extends StorageProxy {
350: protected String valueToString(Object value) {
351: return value.toString();
352: }
353:
354: protected Object stringToValue(String str) {
355: return new Integer(Integer.parseInt(str));
356: }
357: }
358:
359: static class DateStorageProxy extends StorageProxy {
360: DateFormat format = DateFormat.getDateTimeInstance();
361:
362: protected String valueToString(Object value) {
363: return format.format((Date) value);
364: }
365:
366: protected Object stringToValue(String str) {
367: try {
368: return format.parse(str);
369: } catch (ParseException e) {
370: return null;
371: }
372: }
373: }
374:
375: /**
376: * Utility method to encode a string by escaping "=", "," and "%" using HTTP-style
377: * escape sequences.
378: */
379: static void appendEncoded(StringBuffer buffer, String str) {
380: char[] chars = str.toCharArray();
381: for (int j = 0; j < chars.length; j++) {
382: if (chars[j] == '=')
383: buffer.append("%3D");
384: else if (chars[j] == ',')
385: buffer.append("%2C");
386: else if (chars[j] == '%')
387: buffer.append("%25");
388: else
389: buffer.append(chars[j]);
390: }
391: }
392:
393: /**
394: * Utility method to decode a string by unescaping "=", "," and "%".
395: */
396: static String decode(String str) {
397: return str.replaceAll("%3D", "=").replaceAll("%2C", ",")
398: .replaceAll("%25", "%");
399: }
400:
401: }
|