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-2006 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.openide.nodes;
042:
043: import java.beans.PropertyChangeEvent;
044: import java.beans.PropertyChangeListener;
045: import java.beans.PropertyChangeSupport;
046:
047: import java.util.*;
048:
049: /** Support for creation of property sets. Allows easy
050: * addition, modification, and deletion of properties. Also
051: * permits listening on changes of contained properties.
052: *
053: * @author Jaroslav Tulach, Dafe Simonek
054: */
055: public final class Sheet extends Object {
056: /** Name for regular Bean property set. */
057: public static final String PROPERTIES = "properties"; // NOI18N
058:
059: /** Name for expert Bean property set. */
060: public static final String EXPERT = "expert"; // NOI18N
061:
062: /** list of sets (Sheet.Set) */
063: private ArrayList<Set> sets;
064:
065: /** array of sets */
066: private Node.PropertySet[] array;
067:
068: /** support for changes */
069: private PropertyChangeSupport supp = new PropertyChangeSupport(this );
070:
071: /** change listener that is attached to each set added to this object */
072: private PropertyChangeListener propL = new PropertyChangeListener() {
073: public void propertyChange(PropertyChangeEvent ev) {
074: supp.firePropertyChange(null, null, null);
075: }
076: };
077:
078: /** Default constructor.
079: */
080: public Sheet() {
081: this (new ArrayList<Set>(2));
082: }
083:
084: /** Copy constrcutor.
085: * @param ar array to use
086: */
087: private Sheet(ArrayList<Set> ar) {
088: sets = ar;
089: }
090:
091: /** Obtain the array of property sets.
092: * @return the array
093: */
094: public final Node.PropertySet[] toArray() {
095: Node.PropertySet[] l = array;
096:
097: if (l != null) {
098: return l;
099: }
100:
101: synchronized (this ) {
102: if (array != null) {
103: return array;
104: }
105:
106: array = new Node.PropertySet[sets.size()];
107: sets.toArray(array);
108:
109: return array;
110: }
111: }
112:
113: /** Create a deep copy of the sheet. Listeners are not copied.
114: * @return the cloned object */
115: public synchronized Sheet cloneSheet() {
116: int len = sets.size();
117: ArrayList<Set> l = new ArrayList<Set>(len);
118:
119: for (int i = 0; i < len; i++) {
120: l.add(sets.get(i).cloneSet());
121: }
122:
123: return new Sheet(l);
124: }
125:
126: /** Find the property set with a given name.
127: * @param name name of the set
128: * @return the property set, or <code>null</code> if no such set exists
129: */
130: public synchronized Set get(String name) {
131: int indx = findIndex(name);
132:
133: return (indx == -1) ? null : sets.get(indx);
134: }
135:
136: /** Add a property set. If the set does not yet exist in the sheet,
137: * inserts a new set with the implied name. Otherwise the old set is replaced
138: * by the new one.
139: *
140: * @param set to add
141: * @return the previous set with the same name, or <code>null</code> if this is a fresh insertion
142: */
143: public synchronized Set put(Set set) {
144: int indx = findIndex(set.getName());
145: Set removed;
146:
147: if (indx == -1) {
148: sets.add(set);
149: removed = null;
150: } else {
151: removed = sets.set(indx, set);
152: }
153:
154: set.removePropertyChangeListener(propL);
155:
156: if (removed == null) {
157: set.addPropertyChangeListener(propL);
158: }
159:
160: refresh();
161:
162: return removed;
163: }
164:
165: /** Remove a property set from the sheet.
166: * @param set name of set to remove
167: * @return removed set, or <code>null</code> if the set could not be found
168: */
169: public synchronized Set remove(String set) {
170: int indx = findIndex(set);
171:
172: if (indx != -1) {
173: Set s = sets.remove(indx);
174: s.removePropertyChangeListener(propL);
175: refresh();
176:
177: return s;
178: } else {
179: return null;
180: }
181: }
182:
183: /** Convenience method to create new sheet with only one empty set, named {@link #PROPERTIES}.
184: * Display name and hint are settable via the appropriate bundle.
185: *
186: * @return a new sheet with default property set
187: */
188: public static Sheet createDefault() {
189: Sheet newSheet = new Sheet();
190:
191: // create default property set
192: newSheet.put(createPropertiesSet());
193:
194: return newSheet;
195: }
196:
197: /** Convenience method to create new sheet set named {@link #PROPERTIES}.
198: *
199: * @return a new properties sheet set
200: */
201: public static Sheet.Set createPropertiesSet() {
202: Sheet.Set ps = new Sheet.Set();
203: ps.setName(PROPERTIES);
204: ps.setDisplayName(Node.getString("Properties"));
205: ps.setShortDescription(Node.getString("HINT_Properties"));
206:
207: return ps;
208: }
209:
210: /** Convenience method to create new sheet set named {@link #EXPERT}.
211: *
212: * @return a new expert properties sheet set
213: */
214: public static Sheet.Set createExpertSet() {
215: Sheet.Set ps = new Sheet.Set();
216: ps.setExpert(true);
217: ps.setName(EXPERT);
218: ps.setDisplayName(Node.getString("Expert"));
219: ps.setShortDescription(Node.getString("HINT_Expert"));
220:
221: return ps;
222: }
223:
224: /** Add a change listener.
225: * @param l the listener */
226: public void addPropertyChangeListener(PropertyChangeListener l) {
227: supp.addPropertyChangeListener(l);
228: }
229:
230: /** Remove a change listener.
231: * @param l the listener */
232: public void removePropertyChangeListener(PropertyChangeListener l) {
233: supp.removePropertyChangeListener(l);
234: }
235:
236: /** Finds index for property set for given name.
237: * @param name of the property
238: * @return the index or -1 if not found
239: */
240: private int findIndex(String name) {
241: int s = sets.size();
242:
243: for (int i = 0; i < s; i++) {
244: Node.PropertySet p = (Node.PropertySet) sets.get(i);
245:
246: if (p.getName().equals(name)) {
247: return i;
248: }
249: }
250:
251: return -1;
252: }
253:
254: /** Refreshes and fire info about the set
255: */
256: private void refresh() {
257: array = null;
258: supp.firePropertyChange(null, null, null);
259: }
260:
261: /********* Inner classes **********/
262: /** A set of Bean properties.
263: */
264: public static final class Set extends Node.PropertySet {
265: /** list of properties (Node.Property) */
266: private List<Node.Property<?>> props;
267:
268: /** array of properties */
269: private Node.Property<?>[] array;
270:
271: /** change listeners listening on this set */
272: private PropertyChangeSupport supp = new PropertyChangeSupport(
273: this );
274:
275: /** Default constructor.
276: */
277: public Set() {
278: this (new ArrayList<Node.Property<?>>());
279: }
280:
281: /** @param al array list to use for this property set
282: */
283: private Set(List<Node.Property<?>> al) {
284: props = al;
285: }
286:
287: /** Clone the property set.
288: * @return the clone
289: */
290: public synchronized Set cloneSet() {
291: return new Set(new ArrayList<Node.Property<?>>(props));
292: }
293:
294: /** Get a property by name.
295: * @param name name of the property
296: * @return the first property in the list that has this name, <code>null</code> if not found
297: */
298: public Node.Property<?> get(String name) {
299: int indx = findIndex(name);
300:
301: return (indx == -1) ? null : props.get(indx);
302: }
303:
304: /** Get all properties in this set.
305: * @return the properties
306: */
307: synchronized public Node.Property<?>[] getProperties() {
308: if (array == null) {
309: array = new Node.Property<?>[props.size()];
310: props.toArray(array);
311: }
312: return array;
313: }
314:
315: /** Add a property to this set, replacing any old one with the same name.
316: * @param p the property to add
317: * @return the property with the same name that was replaced, or <code>null</code> for a fresh insertion
318: */
319: public synchronized Node.Property<?> put(Node.Property<?> p) {
320: int indx = findIndex(p.getName());
321: Node.Property<?> removed;
322:
323: if (indx != -1) {
324: // replaces the original one
325: removed = props.set(indx, p);
326: } else {
327: // adds this to the end
328: props.add(p);
329: removed = null;
330: }
331:
332: // clears computed array and fires into about change of properties
333: refresh();
334:
335: return removed;
336: }
337:
338: /** Add several properties to this set, replacing old ones with the same names.
339: *
340: * @param ar properties to add
341: */
342: public synchronized void put(Node.Property<?>[] ar) {
343: for (int i = 0; i < ar.length; i++) {
344: Node.Property<?> p = ar[i];
345: p = ar[i];
346:
347: int indx = findIndex(p.getName());
348:
349: if (indx != -1) {
350: // replaces the original one
351: props.set(indx, p);
352: } else {
353: // adds this to the end
354: props.add(p);
355: }
356: }
357:
358: // clears computed array and fires into about change of properties
359: refresh();
360: }
361:
362: /** Remove a property from the set.
363: * @param name name of the property to remove
364: * @return the removed property, or <code>null</code> if it was not there to begin with
365: */
366: public synchronized Node.Property<?> remove(String name) {
367: int indx = findIndex(name);
368:
369: if (indx != -1) {
370: try {
371: return props.remove(indx);
372: } finally {
373: // clears computed array and fires into about change of properties
374: refresh();
375: }
376: } else {
377: return null;
378: }
379: }
380:
381: /** Add a property change listener.
382: * @param l the listener to add */
383: public void addPropertyChangeListener(PropertyChangeListener l) {
384: supp.addPropertyChangeListener(l);
385: }
386:
387: /** Remove a property change listener.
388: * @param l the listener to remove */
389: public void removePropertyChangeListener(
390: PropertyChangeListener l) {
391: supp.removePropertyChangeListener(l);
392: }
393:
394: /** Finds index for property with specified name.
395: * @param name of the property
396: * @return the index or -1 if not found
397: */
398: private int findIndex(String name) {
399: int s = props.size();
400:
401: for (int i = 0; i < s; i++) {
402: Node.Property<?> p = props.get(i);
403:
404: if (p.getName().equals(name)) {
405: return i;
406: }
407: }
408:
409: return -1;
410: }
411:
412: /** Notifies change of properties.
413: */
414: private void refresh() {
415: array = null;
416: supp.firePropertyChange(null, null, null);
417: }
418: }
419: // end of Set
420: }
|