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.netbeans.tax;
042:
043: import java.util.Collection;
044: import java.util.List;
045: import java.util.Iterator;
046: import java.util.ListIterator;
047: import java.util.LinkedList;
048:
049: import java.beans.PropertyChangeListener;
050:
051: import org.netbeans.tax.event.TreeEventManager;
052: import org.netbeans.tax.event.TreeNodeContentEventModel;
053:
054: /**
055: *
056: * @author Libor Kramolis
057: * @version 0.1
058: */
059: public class TreeObjectList extends TreeObject implements
060: TreeNodeContentEventModel, List {
061: // toDo:
062: // + set inserted object readOnly value as object list has
063:
064: /** Name of property for insert node to node content. */
065: public static final String PROP_CONTENT_INSERT = "contentInsert"; // NOI18N
066:
067: /** Name of property for remove node from node content. */
068: public static final String PROP_CONTENT_REMOVE = "contentRemove"; // NOI18N
069:
070: /** Name of property for change order in node content. */
071: public static final String PROP_CONTENT_ORDER = "contentOrder"; // NOI18N
072:
073: /** */
074: private ContentManager contentManager;
075:
076: /** */
077: private List list;
078:
079: //
080: // init
081: //
082:
083: /**
084: * Creates new TreeObjectList.
085: */
086: protected TreeObjectList(ContentManager contentManager) {
087: super ();
088:
089: this .contentManager = contentManager;
090: this .list = new LinkedList();
091: }
092:
093: /** Creates new TreeObjectList -- copy constructor. */
094: protected TreeObjectList(TreeObjectList objectList) {
095: super (objectList);
096:
097: this .contentManager = null;
098:
099: this .list = new LinkedList();
100: Iterator it = objectList.iterator();
101:
102: boolean wasReadOnly = this .isReadOnly();
103: if (wasReadOnly) {
104: this .setReadOnly(false);
105: }
106: while (it.hasNext()) {
107: this .add(((TreeObject) it.next()).clone());
108: }
109: if (wasReadOnly) {
110: this .setReadOnly(true);
111: }
112: }
113:
114: //
115: // from TreeObject
116: //
117:
118: /**
119: */
120: public Object clone() {
121: return new TreeObjectList(this );
122: }
123:
124: /**
125: */
126: public boolean equals(Object object, boolean deep) {
127: if (!!!super .equals(object, deep))
128: return false;
129:
130: TreeObjectList peer = (TreeObjectList) object;
131: if (this .list.size() != peer.list.size())
132: return false;
133:
134: Iterator this It = this .list.iterator();
135: Iterator peerIt = peer.list.iterator();
136: while (this It.hasNext()) {
137: Object this Next = this It.next();
138: Object peerNext = peerIt.next();
139: if (!!!Util.equals(this Next, peerNext))
140: return false;
141: }
142:
143: return true;
144: }
145:
146: /*
147: * Merge list content by merging instances using findMergeCandidate().
148: * Let list's ContentManagers update structural links.
149: */
150: public void merge(TreeObject treeObject)
151: throws CannotMergeException {
152: super .merge(treeObject);
153:
154: TreeObjectList peer = (TreeObjectList) treeObject;
155:
156: // merge nodeList;
157: // try to reuse old instances by type in FIFO order
158: // note that in tree editor (and anyone manipulating this structure directly)
159: // it is ok so no explorer node collapsing would occure
160: // see children package from related details
161:
162: // I think no optimalization can be performend since
163: // there is not uniq id of element
164:
165: boolean wasReadOnly = this .isReadOnly();
166: if (wasReadOnly) {
167: this .setReadOnly(false);
168: }
169:
170: TreeObject[] backupArray = (TreeObject[]) list
171: .toArray(new TreeObject[0]);
172: list.clear(); //do not use content manager, do not fire
173:
174: short policy = TreeEventManager.FIRE_LATER;
175: TreeEventManager manager = getEventManager();
176: if (manager != null) {
177: policy = manager.getFirePolicy();
178: manager.setFirePolicy(TreeEventManager.FIRE_LATER);
179: }
180:
181: // if the size is the same => order may be changed
182:
183: boolean reordered = true;
184: int permutation[] = null;
185: int originalIndex = 0;
186:
187: if (backupArray.length == peer.list.size()) {
188: permutation = new int[backupArray.length];
189: } else {
190: reordered = false;
191: }
192:
193: // MERGE
194:
195: for (Iterator it = peer.list.iterator(); it.hasNext(); originalIndex++) {
196: TreeObject peerNode = (TreeObject) it.next();
197: TreeObject suitableNode = null;
198:
199: // search backup array for suitable element and place it in suitableNode
200: // then mark the backup array entry by null as used
201:
202: int suitableIndex = findMergeCandidate(peerNode,
203: backupArray);
204: if (suitableIndex >= 0) {
205: suitableNode = backupArray[suitableIndex];
206: backupArray[suitableIndex] = null;
207: }
208:
209: // add to current list and let list's ContentManagers
210: // update their structural references
211:
212: if (suitableNode != null) { // if exist merge it
213:
214: suitableNode.merge(peerNode);
215: addNoFire(suitableNode); // we were there before clear()
216: if (permutation != null)
217: permutation[originalIndex] = suitableIndex;
218:
219: } else {
220:
221: suitableNode = peerNode;
222: add(suitableNode);
223: reordered = false;
224:
225: }
226:
227: }
228:
229: // cache events to be fired
230:
231: if (reordered) {
232:
233: // check the permutation, ignore identical one
234:
235: reordered = false;
236:
237: for (int i = 0; i < permutation.length; i++) {
238: if (permutation[i] != i) {
239: reordered = true;
240: break;
241: }
242: }
243: if (reordered)
244: firePropertyOrder(permutation);
245:
246: } else {
247:
248: // fire removal events for rest of backup array
249:
250: for (int i = 0; i < backupArray.length; i++) {
251: if (backupArray[i] == null)
252: continue;
253: contentManagerObjectRemoved(backupArray[i]);
254: firePropertyRemove(backupArray[i]);
255: }
256: }
257:
258: if (wasReadOnly) {
259: this .setReadOnly(true);
260: }
261:
262: if (manager != null)
263: manager.setFirePolicy(policy);
264:
265: }
266:
267: /**
268: * Defines algorithm used for searching for must suitable merge peer.
269: * @param original we search suitable candidate for this
270: * @param candidates array of candidates and nulls
271: * @return index of suitable candidate or -1
272: */
273: protected int findMergeCandidate(final TreeObject original,
274: final TreeObject[] candidates) {
275:
276: // suitable if first member of the same class
277:
278: // System.err.println("Looking for peer candidate:" + System.identityHashCode(original) + " " + original.getClass()); // NOI18N
279:
280: for (int i = 0; i < candidates.length; i++) {
281: TreeObject candidate = candidates[i];
282: if (candidate == null)
283: continue;
284:
285: // System.err.println("Inspecting: " + System.identityHashCode(candidate) + " " + candidate.getClass()); // NOI18N
286: if (original.getClass().equals(candidate.getClass())) {
287: return i;
288: }
289: }
290:
291: return -1;
292: }
293:
294: //
295: // read only
296: //
297:
298: /**
299: */
300: protected void setReadOnly(boolean newReadOnly) {
301: super .setReadOnly(newReadOnly);
302:
303: Iterator it = this .list.iterator();
304: while (it.hasNext()) {
305: TreeObject obj = (TreeObject) it.next();
306: obj.setReadOnly(newReadOnly);
307: }
308: }
309:
310: //
311: // context
312: //
313:
314: /**
315: */
316: public final boolean isInContext() {
317: return true; //???
318: }
319:
320: /**
321: */
322: public final void removeFromContext() throws ReadOnlyException {
323: if (isInContext()) {
324: this .clear(); //???
325: }
326: }
327:
328: //
329: // itself
330: //
331:
332: /**
333: */
334: public final ContentManager getContentManager() {
335: return contentManager;
336: }
337:
338: // /**
339: // */
340: // protected final void setContentManager (ContentManager newContentManager) {
341: // this.contentManager = newContentManager;
342: // }
343:
344: /**
345: */
346: public final boolean isAssignableObject(Object obj) {
347: try {
348: getContentManager().checkAssignableObject(obj);
349: return true;
350: } catch (ClassCastException exc) {
351: return false;
352: }
353: }
354:
355: /**
356: * @throws ReadOnlyException
357: * @throws InvalidArgumentException
358: */
359: public final void reorder(int[] perm) throws ReadOnlyException,
360: InvalidArgumentException {
361: //
362: // check new value
363: //
364: if (equals(perm)) {
365: return;
366: }
367: checkReadOnly();
368: checkReorder(perm);
369:
370: List newList = new LinkedList();
371: int len = size();
372: int[] newPerm = new int[len];
373: for (int i = 0; i < len; i++) {
374: newPerm[perm[i]] = i;
375: }
376: for (int i = 0; i < len; i++) {
377: newList.add(list.get(newPerm[i]));
378: }
379: list = newList;
380:
381: contentManagerOrderChanged(perm);
382: firePropertyOrder(perm);
383: }
384:
385: /**
386: */
387: protected final void checkReorder(int[] perm)
388: throws InvalidArgumentException {
389: if (perm == null) {
390: throw new InvalidArgumentException(Util.THIS
391: .getString("EXC_invalid_reorder_permutation"),
392: new NullPointerException());
393: }
394: if (perm.length != size()) {
395: throw new InvalidArgumentException(perm.length + " != "
396: + size(), Util.THIS
397: .getString("EXC_invalid_reorder_permutation")); // NOI18N
398: }
399: }
400:
401: /** @return true if <code>perm</code> is identical permutaion. */
402: protected final boolean equals(int[] perm) {
403: for (int i = 0; i < perm.length; i++) {
404: if (perm[i] != i)
405: return false;
406: }
407: return true;
408: }
409:
410: /**
411: * @throws ReadOnlyException
412: * @throws InvalidArgumentException
413: */
414: public final void switchObjects(int fromIndex, int toIndex)
415: throws ReadOnlyException, InvalidArgumentException {
416: int len = size();
417: int[] perm = new int[len];
418:
419: for (int i = 0; i < perm.length; i++) {
420: perm[i] = i;
421: }
422: perm[fromIndex] = toIndex;
423: perm[toIndex] = fromIndex;
424:
425: reorder(perm);
426: }
427:
428: /**
429: */
430: protected final void checkUnsupportedOperation()
431: throws TreeUnsupportedOperationException {
432: try {
433: checkReadOnly();
434: } catch (TreeException exc) {
435: throw new TreeUnsupportedOperationException(exc);
436: }
437: }
438:
439: //
440: // java.util.List
441: //
442:
443: /**
444: */
445: public final void clear() {
446: checkUnsupportedOperation();
447:
448: // list.clear();
449: Iterator it = (new LinkedList(list)).iterator(); // new LinkedList => fixed ConcurentModificationException
450: while (it.hasNext()) {
451: Object obj = it.next();
452: remove(obj);
453: }
454: }
455:
456: /**
457: */
458: public final boolean removeAll(Collection collection)
459: throws UnsupportedOperationException {
460: checkUnsupportedOperation();
461:
462: // return list.removeAll (collection);
463: throw new UnsupportedOperationException();
464: }
465:
466: /**
467: */
468: public final Object get(int index) {
469: return list.get(index);
470: }
471:
472: /**
473: */
474: public final int hashCode() {
475: return list.hashCode();
476: }
477:
478: /**
479: */
480: public final int size() {
481: return list.size();
482: }
483:
484: /**
485: */
486: public final boolean retainAll(Collection collection)
487: throws UnsupportedOperationException {
488: checkUnsupportedOperation();
489:
490: // return list.retainAll (collection);
491: throw new UnsupportedOperationException();
492: }
493:
494: /**
495: */
496: protected boolean removeImpl(Object obj) {
497: return list.remove(obj);
498: }
499:
500: /**
501: */
502: public final boolean remove(Object obj) {
503: checkUnsupportedOperation();
504:
505: boolean removed = removeImpl(obj);
506: if (removed) {
507: contentManagerObjectRemoved((TreeObject) obj);
508: firePropertyRemove((TreeObject) obj);
509: }
510: return removed;
511: }
512:
513: /**
514: */
515: public final int indexOf(Object obj) {
516: return list.indexOf(obj);
517: }
518:
519: /**
520: */
521: public final boolean contains(Object obj) {
522: return list.contains(obj);
523: }
524:
525: /**
526: */
527: public final int lastIndexOf(Object obj) {
528: return list.lastIndexOf(obj);
529: }
530:
531: /**
532: */
533: protected Object setImpl(int index, Object obj) {
534: return list.set(index, obj);
535: }
536:
537: /**
538: */
539: public final Object set(int index, Object obj) {
540: checkUnsupportedOperation();
541: contentManagerCheckAssignableObject(obj);
542:
543: Object oldObj = setImpl(index, obj);
544: // if (obj != oldObj) {
545: contentManagerObjectRemoved((TreeObject) oldObj);
546: firePropertyRemove((TreeObject) oldObj);
547: // }
548: contentManagerObjectInserted((TreeObject) obj);
549: firePropertyInsert((TreeObject) obj);
550:
551: return oldObj;
552: }
553:
554: /**
555: */
556: public final ListIterator listIterator(int index)
557: throws UnsupportedOperationException {
558: // return list.listIterator (index);
559: throw new UnsupportedOperationException();
560: }
561:
562: /**
563: */
564: public final boolean containsAll(Collection collection) {
565: return list.containsAll(collection);
566: }
567:
568: /**
569: */
570: public final Iterator iterator() {
571: return (new LinkedList(list)).iterator(); //!!! TEMPORARY : fixed ConcurrentModificationException but iterator is not active
572: }
573:
574: /**
575: */
576: public final boolean addAll(Collection collection) {
577: checkUnsupportedOperation();
578:
579: boolean changed = false;
580: Iterator it = collection.iterator();
581: while (it.hasNext()) {
582: this .add(it.next());
583: changed = true;
584: }
585: return changed;
586: }
587:
588: /**
589: */
590: protected Object removeImpl(int index) {
591: return list.remove(index);
592: }
593:
594: /**
595: */
596: public final Object remove(int index) {
597: checkUnsupportedOperation();
598:
599: Object oldObj = removeImpl(index);
600: if (oldObj != null) {
601: contentManagerObjectRemoved((TreeObject) oldObj);
602: firePropertyRemove((TreeObject) oldObj);
603: }
604: return (oldObj);
605: }
606:
607: /**
608: */
609: public final boolean isEmpty() {
610: return list.isEmpty();
611: }
612:
613: /**
614: */
615: protected void addImpl(int index, Object obj) {
616: list.add(index, obj);
617: }
618:
619: /**
620: */
621: public final void add(int index, Object obj) {
622: checkUnsupportedOperation();
623: contentManagerCheckAssignableObject(obj);
624:
625: addImpl(index, obj);
626: contentManagerObjectInserted((TreeObject) obj);
627: firePropertyInsert((TreeObject) obj);
628: }
629:
630: /**
631: */
632: public final boolean equals(Object obj) {
633: if (!!!(obj instanceof TreeObjectList))
634: return false;
635: return list.equals(((TreeObjectList) obj).list);
636: }
637:
638: /**
639: */
640: public final boolean addAll(int index, Collection collection)
641: throws UnsupportedOperationException {
642: checkUnsupportedOperation();
643:
644: // return list.addAll (index, collection);
645: throw new UnsupportedOperationException();
646: }
647:
648: /**
649: */
650: protected boolean addImpl(Object obj) {
651: return list.add(obj);
652: }
653:
654: /*
655: * Add to list, Do NOT notify ContentManager and do not fire
656: * the added object already was in the list.
657: * Used during merge process.
658: */
659: private boolean addNoFire(Object obj) {
660: checkUnsupportedOperation();
661: contentManagerCheckAssignableObject(obj);
662:
663: return addImpl(obj);
664: }
665:
666: /**
667: */
668: public final boolean add(Object obj) {
669: boolean added = addNoFire(obj);
670: if (added) {
671: contentManagerObjectInserted((TreeObject) obj);
672: firePropertyInsert((TreeObject) obj);
673: }
674: return added;
675: }
676:
677: /**
678: */
679: public final Object[] toArray(Object[] array) {
680: return list.toArray(array);
681: }
682:
683: /**
684: */
685: public final Object[] toArray() {
686: return list.toArray();
687: }
688:
689: /**
690: */
691: public final ListIterator listIterator()
692: throws UnsupportedOperationException {
693: // return list.listIterator();
694: throw new UnsupportedOperationException();
695: }
696:
697: /**
698: */
699: public final List subList(int fromIndex, int toIndex)
700: throws UnsupportedOperationException {
701: // return list.subList (fromIndex, toIndex);
702: throw new UnsupportedOperationException();
703: }
704:
705: //
706: // toString
707: //
708:
709: /**
710: */
711: public String toString() {
712: return list.toString();
713: }
714:
715: //
716: // event model
717: //
718:
719: /** Get assigned event manager which is assigned to ownerNode.
720: * @return assigned event manager (may be null).
721: */
722: public final TreeEventManager getEventManager() {
723: TreeNode ownerNode = contentManagerGetOwnerNode();
724:
725: if (ownerNode == null)
726: return null;
727:
728: return ownerNode.getEventManager();
729: }
730:
731: /**
732: * @param listener The listener to add.
733: */
734: public final void addContentChangeListener(
735: PropertyChangeListener listener) {
736: getEventChangeSupport().addPropertyChangeListener(
737: PROP_CONTENT_INSERT, listener);
738: getEventChangeSupport().addPropertyChangeListener(
739: PROP_CONTENT_REMOVE, listener);
740: getEventChangeSupport().addPropertyChangeListener(
741: PROP_CONTENT_ORDER, listener);
742: }
743:
744: /**
745: * @param listener The listener to remove.
746: */
747: public final void removeContentChangeListener(
748: PropertyChangeListener listener) {
749: getEventChangeSupport().removePropertyChangeListener(
750: PROP_CONTENT_INSERT, listener);
751: getEventChangeSupport().removePropertyChangeListener(
752: PROP_CONTENT_REMOVE, listener);
753: getEventChangeSupport().removePropertyChangeListener(
754: PROP_CONTENT_ORDER, listener);
755: }
756:
757: /**
758: */
759: public final boolean hasContentChangeListeners() {
760: return getEventChangeSupport().hasPropertyChangeListeners(
761: PROP_CONTENT_INSERT)
762: || getEventChangeSupport().hasPropertyChangeListeners(
763: PROP_CONTENT_REMOVE)
764: || getEventChangeSupport().hasPropertyChangeListeners(
765: PROP_CONTENT_ORDER);
766: }
767:
768: /**
769: */
770: public final void firePropertyInsert(TreeObject newNode) {
771: firePropertyChange(getEventChangeSupport().createEvent(
772: PROP_CONTENT_INSERT, null, newNode));
773: }
774:
775: /**
776: */
777: public final void firePropertyRemove(TreeObject oldNode) {
778: firePropertyChange(getEventChangeSupport().createEvent(
779: PROP_CONTENT_REMOVE, oldNode, null));
780: }
781:
782: /**
783: */
784: public final void firePropertyOrder(int[] permutation) {
785: firePropertyChange(getEventChangeSupport().createEvent(
786: PROP_CONTENT_ORDER, null, permutation));
787: }
788:
789: //
790: // class ContentManager
791: //
792:
793: /**
794: */
795: protected final TreeNode contentManagerGetOwnerNode() {
796: if (contentManager != null) {
797: return contentManager.getOwnerNode();
798: } else {
799: return null;
800: }
801: }
802:
803: /**
804: */
805: protected final void contentManagerCheckAssignableObject(
806: Object object) {
807: if (contentManager != null) {
808: contentManager.checkAssignableObject(object);
809: }
810: }
811:
812: /**
813: */
814: protected final void contentManagerObjectInserted(
815: TreeObject treeObject) {
816: if (contentManager != null) {
817: contentManager.objectInserted(treeObject);
818: }
819: }
820:
821: /**
822: */
823: protected final void contentManagerObjectRemoved(
824: TreeObject treeObject) {
825: if (contentManager != null) {
826: contentManager.objectRemoved(treeObject);
827: }
828: }
829:
830: /**
831: */
832: protected final void contentManagerOrderChanged(int[] permutation) {
833: if (contentManager != null) {
834: contentManager.orderChanged(permutation);
835: }
836: }
837:
838: /**
839: *
840: */
841: public static abstract class ContentManager {
842:
843: /**
844: * @return reference to the particular object list owner that created us
845: */
846: public abstract TreeNode getOwnerNode();
847:
848: /** @throws ClassCastException
849: */
850: public void checkAssignableObject(Object obj)
851: throws ClassCastException {
852: if (!!!(obj instanceof TreeObject)) {
853: String msg = Util.THIS
854: .getString("EXC_invalid_instance_of_TreeObject"); //,obj.getClass().getName());
855: throw new ClassCastException(msg);
856: }
857: }
858:
859: /** @throws ClassCastException
860: */
861: protected final void checkAssignableClass(Class cls, Object obj)
862: throws ClassCastException {
863: if (!!!cls.isInstance(obj)) {
864: String msg = Util.THIS.getString(
865: "EXC_is_not_assignable_to", cls.getName()); //,obj.getClass().getName(), cls.getName());
866: throw new ClassCastException(msg);
867: }
868: }
869:
870: /**
871: * Called AFTER object insertion.
872: */
873: public abstract void objectInserted(TreeObject obj);
874:
875: /**
876: * Called AFTER object removal before firing.
877: */
878: public abstract void objectRemoved(TreeObject obj);
879:
880: /** */
881: public abstract void orderChanged(int[] permutation);
882:
883: } // end: interface ContentManager
884:
885: }
|