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:
042: package org.netbeans.modules.java.ui;
043:
044: import java.util.BitSet;
045: import javax.swing.*;
046: import javax.swing.event.*;
047:
048: /** Model that can compute its values lazily and moreover handle some
049: * kind of filtering.
050: *
051: * Made public just to test an impl of Children.EntrySource based on this
052: * filtering list.
053: */
054: public final class LazyListModel extends Object implements ListModel,
055: Runnable, javax.swing.event.ListDataListener {
056: /** means that the value has not yet been assigned */
057: private static int NOT_TESTED = Short.MIN_VALUE - 1;
058: private static int EMPTY_VALUE = Short.MIN_VALUE - 2;
059: /** skips extensive asserts - needed for performance tests */
060: private static final boolean skipExpensiveAsserts = Boolean
061: .getBoolean("org.openide.explorer.view.LazyListModel.skipExpensiveAsserts"); // NOI18N
062:
063: private boolean log;
064: private ListModel listModel;
065: private Filter filter;
066: /** the value to return when nothing else can be returned */
067: private Object defaultValue;
068: /** simple event listener list */
069: private javax.swing.event.EventListenerList list = new javax.swing.event.EventListenerList();
070:
071: /** the size of the original list we now know it has */
072: private int originalSize;
073: /** the size we currently pretend to have */
074: private int size;
075: /** maps an external index to our internal one
076: * (NOT_TESTED, means it has not been tested yet, EMPTY_VALUE means for this external index
077: * we have returned default value)
078: */
079: private int[] external;
080: /** set with marked values where external is different than EMPTY_VALUE or NOT_TESTED */
081: private BitSet checked;
082: /** counts number of refused elements */
083: private int refused;
084: /** contains 1 if the bit mask if the index in listModel has already been tested */
085: private BitSet tested;
086: /** dirty means that we should really update assumptions */
087: private boolean markDirty;
088:
089: private LazyListModel(ListModel m, Filter f, double expectedRadio,
090: Object defaultValue) {
091: this .listModel = m;
092: this .filter = f;
093: this .defaultValue = defaultValue;
094:
095: // JST-PENDING: Weak or not?
096: m.addListDataListener(this );
097: }
098:
099: final Filter getFilter() {
100: return filter;
101: }
102:
103: final boolean isComputed(int index) {
104: return tested != null && tested.get(index);
105: }
106:
107: /** Makes itself dirty and schedules an update.
108: */
109: private void markDirty() {
110: this .markDirty = true;
111: getFilter().scheduleUpdate(this );
112: }
113:
114: /** When executed, updateYourAssumeptions.
115: */
116: public void run() {
117: if (!markDirty) {
118: return;
119: }
120:
121: markDirty = false;
122: if (log) {
123: System.err.println("updateYourAssumeptions ();"); // NOI18N
124: }
125: updateYourAssumeptions();
126: }
127:
128: /** Notifies removal of inteval from (inclusive) to (exclusive) and
129: * updates its structures.
130: *
131: * !!! as a side effect updates size !!!
132: *
133: * @return s - number of removals
134: */
135: private void notifyRemoval(int from, int to) {
136: ListDataEvent ev = new ListDataEvent(this ,
137: ListDataEvent.INTERVAL_REMOVED, from, to - 1);
138: removeInterval(external, from, to);
139: int cnt = to - from;
140: size -= cnt;
141:
142: regenerateCheckedBitSet();
143: fireChange(ev);
144: }
145:
146: private void regenerateCheckedBitSet() {
147: checked = new BitSet(size);
148: for (int i = 0; i < size; i++) {
149: if (external[i] >= 0) {
150: checked.set(i);
151: }
152: }
153: }
154:
155: private int getExternal(int index) {
156: if (index == size) {
157: return originalSize;
158: }
159: if (index < 0) {
160: return -1;
161: }
162: return external[index];
163: }
164:
165: /** Can be called to ask the LazyListModel to update its assumptions,
166: * especially assumptions about the size to match its current knowledge.
167: */
168: final void updateYourAssumeptions() {
169: if (external == null) {
170: return;
171: }
172:
173: int sizeBefore = size;
174: int notifiedRemovals = 0;
175:
176: int i = 0;
177: LOOP: while (i < size) {
178: while (getExternal(i) >= 0 && i < size) {
179: i++;
180: }
181: if (i == size) {
182: break;
183: }
184:
185: if (getExternal(i) == NOT_TESTED) {
186: int minusOneIndex = i - 1;
187: while (i < size && getExternal(i) == NOT_TESTED) {
188: i++;
189: }
190:
191: int count = i - minusOneIndex - 1;
192: int from = getExternal(minusOneIndex) + 1;
193: int to = getExternal(i);
194:
195: assert from >= 0 : "Value at " + minusOneIndex + "("
196: + from + ") must be greater than minus one"; // NOI18N
197: assert to >= 0 : "Value at " + i
198: + "must be greater than minus one but was: "
199: + to; // NOI18N
200: assert to >= from : "Must be true: " + to + " >= "
201: + from; // NOI18N
202:
203: int howMuch = count - (to - from);
204: if (howMuch > 0) {
205: // we need to notify some kind of removal
206: notifyRemoval(i - howMuch, i);
207: i -= howMuch;
208: }
209: } else {
210: int minusTwoIndex = i;
211: while (i < size && getExternal(i) == EMPTY_VALUE) {
212: i++;
213: }
214: notifyRemoval(minusTwoIndex, i);
215: i = minusTwoIndex;
216: }
217: }
218:
219: assert externalContraints() : "Constraints failed"; // NOI18N
220: }
221:
222: private boolean externalContraints() {
223: assert external != null : "Not null"; // NOI18N
224: assert external.length >= size : "Length " + external.length
225: + " >= " + size; // NOI18N
226: if (!skipExpensiveAsserts) {
227: for (int i = 1; i < size; i++) {
228: assert external[i - 1] != NOT_TESTED
229: || external[i] != EMPTY_VALUE : "There cannot be empty value after not tested value"; // NOI18N
230: assert external[i - 1] != EMPTY_VALUE
231: || external[i] != NOT_TESTED : "Not tested cannot immediatelly follow empty value"; // NOI18N
232: assert external[i] < 0 || external[i] > external[i - 1] : "If valid index it has to be greater: "
233: + i; // NOI18N
234: assert external[i] < 0 == !checked.get(i) : "external and checked must be consistent: "
235: + i; // NOI18N
236: }
237: }
238: return true;
239: }
240:
241: /** Removes an interval from array */
242: private static void removeInterval(int[] array, int index0,
243: int index1) {
244: assert index0 < index1 : "Index1 must be bigger than index0: "
245: + index1 + " > " + index0; // NOI18N
246: System.arraycopy(array, index1, array, index0, array.length
247: - index1);
248: }
249:
250: /** Factory method to create new filtering lazy model.
251: */
252: public static LazyListModel create(ListModel listModel, Filter f,
253: double expectedRadio, Object defValue) {
254: return create(listModel, f, expectedRadio, defValue, false);
255: }
256:
257: /** Model with enabled logging.
258: */
259: static LazyListModel create(ListModel listModel, Filter f,
260: double expectedRadio, Object defValue, boolean log) {
261: LazyListModel lazy = new LazyListModel(listModel, f,
262: expectedRadio, defValue);
263: lazy.log = log;
264: return lazy;
265: }
266:
267: /** This method is currently called from org.openide.nodes.EntrySupport to create lazy filtering list
268: * for nodes, excludes everything that is not node, as default value it return null
269: * @param model model to filter
270: */
271: public static LazyListModel create(
272: final org.openide.nodes.Children.Keys ch, ListModel model) {
273: class NodeF implements Filter {
274: private java.lang.reflect.Method m;
275:
276: public boolean accept(Object obj) {
277: return obj instanceof org.openide.nodes.Node;
278: }
279:
280: public void scheduleUpdate(Runnable run) {
281: try {
282: if (m == null) {
283: m = org.openide.nodes.Children.Keys.class
284: .getDeclaredMethod(
285: "updateMyAssumptions", // NOI18N
286: new Class[] { Runnable.class });
287: m.setAccessible(true);
288: }
289: m.invoke(ch, new Object[] { run });
290: } catch (Exception ex) {
291: IllegalStateException i = new IllegalStateException(
292: ex.getMessage());
293: i.initCause(ex);
294: throw i;
295: }
296: }
297: }
298:
299: return create(model, new NodeF(), 1.0, null);
300: }
301:
302: //
303: // Model methods.
304: //
305:
306: public void addListDataListener(ListDataListener l) {
307: list.add(ListDataListener.class, l);
308: }
309:
310: public void removeListDataListener(ListDataListener l) {
311: list.remove(ListDataListener.class, l);
312: }
313:
314: private void fireChange(ListDataEvent ev) {
315: if (list.getListenerCount() == 0)
316: return;
317:
318: Object[] arr = list.getListenerList();
319: for (int i = arr.length - 1; i >= 0; i -= 2) {
320: ListDataListener l = (ListDataListener) arr[i];
321: switch (ev.getType()) {
322: case ListDataEvent.CONTENTS_CHANGED:
323: l.contentsChanged(ev);
324: break;
325: case ListDataEvent.INTERVAL_ADDED:
326: l.intervalAdded(ev);
327: break;
328: case ListDataEvent.INTERVAL_REMOVED:
329: l.intervalRemoved(ev);
330: break;
331: default:
332: throw new IllegalArgumentException("Unknown type: "
333: + ev.getType());
334: }
335: }
336: }
337:
338: /** Is this index accepted.
339: */
340: private boolean accepted(int indx, Object[] result) {
341: Object v = listModel.getElementAt(indx);
342: tested.set(indx);
343: if (filter.accept(v)) {
344: result[0] = v;
345: return true;
346: }
347:
348: markDirty();
349: return false;
350: }
351:
352: /** Initialize the bitsets to sizes of the listModel.
353: */
354: private void initialize() {
355: if (tested == null) {
356: originalSize = listModel.getSize();
357: size = listModel.getSize();
358: tested = new BitSet(size);
359: external = new int[size];
360: for (int i = 0; i < size; i++) {
361: external[i] = NOT_TESTED;
362: }
363: checked = new BitSet(size);
364: }
365: assert externalContraints() : "Constraints failed"; // NOI18N
366: }
367:
368: /** this variable is used from tests to prevent creation of elements in
369: * certain cases.
370: */
371: static Boolean CREATE;
372:
373: /** If value is not know for given index and CREATE.get() is Boolean.FALSE it returns defaultValue.
374: */
375: public Object getElementAt(int index) {
376: initialize();
377:
378: if (log) {
379: System.err.println("model.getElementAt (" + index + ");"); // NOI18N
380: }
381:
382: if (external[index] >= 0) {
383: // we have computed the index, so return it
384: return listModel.getElementAt(external[index]);
385: }
386:
387: if (external[index] == EMPTY_VALUE) {
388: // default value needs to be used
389: return defaultValue;
390: }
391:
392: if (CREATE != null && !CREATE.booleanValue()) {
393: assert Thread.holdsLock(CREATE) : "Only one thread (from tests) can access this"; // NOI18N
394: return defaultValue;
395: }
396:
397: // JST: Why there is no BitSet.previousSetBit!!!???
398: int minIndex = index;
399: while (minIndex >= 0 && getExternal(minIndex) < 0) {
400: minIndex--;
401: }
402: int maxIndex;
403: if (checked.get(index)) {
404: maxIndex = index;
405: } else {
406: maxIndex = checked.nextSetBit(index);
407: if (maxIndex == -1 || maxIndex > size) {
408: maxIndex = size;
409: }
410: }
411:
412: int myMinIndex = getExternal(minIndex) + 1; // one after the index of the first non-1 index
413: int myMaxIndex = getExternal(maxIndex);
414:
415: assert myMaxIndex >= myMaxIndex : "Must be greater"; // NOI18N
416: if (myMaxIndex != myMinIndex) {
417: int myIndex = myMinIndex + (index - minIndex) - 1;
418: if (myIndex >= myMaxIndex) {
419: myIndex = myMaxIndex - 1;
420: }
421:
422: Object[] result = new Object[1];
423: if (accepted(myIndex, result)) {
424: assert external[index] == NOT_TESTED : "External index "
425: + index
426: + " still needs to be unset: "
427: + external[index];
428: external[index] = myIndex;
429: checked.set(index);
430: return result[0];
431: }
432:
433: boolean checkBefore = true;
434: boolean checkAfter = true;
435: for (int i = 1; checkAfter || checkBefore; i++) {
436: if (checkBefore) {
437: checkBefore = index - i >= minIndex
438: && myIndex - i >= myMinIndex
439: && getExternal(index - i) == NOT_TESTED;
440: if (checkBefore && accepted(myIndex - i, result)) {
441: external[index] = myIndex - i;
442: checked.set(index);
443: return result[0];
444: }
445: }
446: if (checkAfter) {
447: checkAfter = index + i < maxIndex
448: && myIndex + i < myMaxIndex
449: && getExternal(index + i) == NOT_TESTED;
450: if (checkAfter && accepted(myIndex + i, result)) {
451: external[index] = myIndex + i;
452: checked.set(index);
453: return result[0];
454: }
455: }
456: }
457: }
458:
459: markDirty();
460:
461: // set default value for all objects in the interval
462: for (int i = minIndex + 1; i < maxIndex; i++) {
463: assert external[i] == NOT_TESTED : i
464: + " should not be set: " + external[i]; // NOI18N
465: external[i] = EMPTY_VALUE;
466: }
467: checked.clear(minIndex + 1, maxIndex);
468:
469: assert external[index] == EMPTY_VALUE : "Should be asigned in the cycle above"; // NOI18N
470: return defaultValue;
471: }
472:
473: public int getSize() {
474: initialize();
475: return size;
476: }
477:
478: //
479: // Notifications from the underlaying model
480: //
481:
482: /** Inserts specified amount of bits into the set.
483: */
484: private static BitSet insertAt(BitSet b, int at, int len, int size) {
485: BitSet before = b.get(0, at);
486:
487: BitSet res = new BitSet(size);
488: res.or(before);
489:
490: int max = b.length();
491: while (at < max) {
492: res.set(at + len, b.get(at));
493: at++;
494: }
495: return res;
496: }
497:
498: /** Removes specified amount of bits from the set.
499: */
500: private static BitSet removeAt(BitSet b, int at, int len,
501: int newSize) {
502: BitSet clone = (BitSet) b.clone();
503:
504: int max = b.length();
505: while (at < max) {
506: clone.set(at, b.get(at + len));
507: at++;
508: }
509: clone.set(newSize, b.size(), false);
510: return clone;
511: }
512:
513: public void contentsChanged(ListDataEvent listDataEvent) {
514: throw new java.lang.UnsupportedOperationException(
515: "Not yet implemented");
516: }
517:
518: public void intervalAdded(ListDataEvent listDataEvent) {
519: if (external == null) {
520: return;
521: }
522:
523: updateYourAssumeptions();
524:
525: int first = listDataEvent.getIndex0();
526: int end = listDataEvent.getIndex1() + 1;
527: int len = end - first;
528:
529: int newOriginalSize = originalSize + len;
530: int newSize = size + len;
531:
532: tested = insertAt(tested, first, len, newOriginalSize);
533:
534: int insert = findExternalIndex(first);
535: int[] newExternal = new int[newSize];
536: System.arraycopy(external, 0, newExternal, 0, insert);
537: for (int i = 0; i < len; i++) {
538: newExternal[insert + i] = NOT_TESTED;
539: }
540: for (int i = insert + len; i < newExternal.length; i++) {
541: int v = external[i - len];
542: newExternal[i] = v < 0 ? v : v + len;
543: }
544: external = newExternal;
545: size = newSize;
546: originalSize = newOriginalSize;
547:
548: regenerateCheckedBitSet();
549:
550: fireChange(new ListDataEvent(this ,
551: ListDataEvent.INTERVAL_ADDED, insert, insert + len - 1));
552: assert externalContraints() : "Constraints failed"; // NOI18N
553: }
554:
555: /** Finds the appropriate index of given internal index. The state is
556: * supposed to be after updateYourAssumeptions => no EMPTY_VALUE
557: */
558: private int findExternalIndex(int myIndex) {
559: int outIndex = 0;
560: for (int i = -1; i < size; i++) {
561: if (getExternal(i) == NOT_TESTED) {
562: outIndex++;
563: } else {
564: outIndex = getExternal(i);
565: }
566:
567: if (outIndex >= myIndex) {
568: return i;
569: }
570: }
571: return size;
572: }
573:
574: public void intervalRemoved(ListDataEvent listDataEvent) {
575: if (external == null) {
576: return;
577: }
578:
579: updateYourAssumeptions();
580:
581: int first = listDataEvent.getIndex0();
582: int end = listDataEvent.getIndex1() + 1;
583: int len = end - first;
584:
585: int newOriginalSize = originalSize - len;
586:
587: int f = findExternalIndex(first);
588: int e = findExternalIndex(end);
589:
590: assert f >= 0 : "First index must be above zero: " + f; // NOI18N
591: assert e >= f : "End index must be above first: " + f + " <= "
592: + e; // NOI18N
593:
594: int outLen = e - f;
595:
596: int[] newExternal = (int[]) external.clone();
597: for (int i = e; i < size; i++) {
598: int v = external[i];
599: newExternal[i - outLen] = v < 0 ? v : v - len;
600: checked.set(i - outLen, v >= 0);
601: }
602: external = newExternal;
603: size -= outLen;
604: originalSize = newOriginalSize;
605:
606: if (outLen != 0) {
607: fireChange(new ListDataEvent(this ,
608: ListDataEvent.INTERVAL_REMOVED, f, e - 1));
609: }
610: assert externalContraints() : "Constraints failed"; // NOI18N
611: }
612:
613: /** Interface for those that wish to filter content of the list.
614: * This filter is expected to always return the same result for
615: * the same object - e.g. either always exclude or include it.
616: */
617: public interface Filter {
618: public boolean accept(Object obj);
619:
620: /** This method is called when the list needs update. It's goal is
621: * usually to do SwingUtilities.invokeLater, even more rafined
622: * methods are allowed.
623: */
624: public void scheduleUpdate(Runnable run);
625: }
626: }
|