1 /* |
|
2 * CDDL HEADER START |
|
3 * |
|
4 * The contents of this file are subject to the terms of the |
|
5 * Common Development and Distribution License (the "License"). |
|
6 * You may not use this file except in compliance with the License. |
|
7 * |
|
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
|
9 * or http://www.opensolaris.org/os/licensing. |
|
10 * See the License for the specific language governing permissions |
|
11 * and limitations under the License. |
|
12 * |
|
13 * When distributing Covered Code, include this CDDL HEADER in each |
|
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
|
15 * If applicable, add the following below this CDDL HEADER, with the |
|
16 * fields enclosed by brackets "[]" replaced with your own identifying |
|
17 * information: Portions Copyright [yyyy] [name of copyright owner] |
|
18 * |
|
19 * CDDL HEADER END |
|
20 */ |
|
21 |
|
22 /* |
|
23 * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. |
|
24 */ |
|
25 |
|
26 package com.oracle.solaris.vp.panel.common.model; |
|
27 |
|
28 import java.beans.*; |
|
29 import java.util.*; |
|
30 import com.oracle.solaris.vp.util.misc.event.*; |
|
31 import com.oracle.solaris.vp.util.misc.predicate.*; |
|
32 |
|
33 /** |
|
34 * Generic {@code ManagedObject} implementation that filters the children of |
|
35 * a {@code ManagedObject}. Only those children that satisfy a provided |
|
36 * {@code Predicate} appear to be children of the {@code FilterManagedObject}. |
|
37 */ |
|
38 public class FilterManagedObject<T extends ManagedObject> |
|
39 extends ManagedObjectMonitor<T> |
|
40 implements ManagedObject<T>, PropertyChangeListener |
|
41 { |
|
42 private Predicate<? super T> pred_; /** The selection predicate */ |
|
43 |
|
44 private IntervalListeners iListeners_ = new IntervalListeners(); |
|
45 |
|
46 /* |
|
47 * These four are protected by synchronizing on "children_" |
|
48 */ |
|
49 private List<Integer> childMap_ = new ArrayList<Integer>(); |
|
50 private final List<T> children_ = new ArrayList<T>(); |
|
51 private List<T> roChildren_ = Collections.unmodifiableList(children_); |
|
52 |
|
53 /** |
|
54 * Constructs a FilterManagedObject. |
|
55 * |
|
56 * @param wrappee the {@code ManagedObject} whose children are to |
|
57 * be filtered |
|
58 * @param predicate the {@code Predicate} that determines which children |
|
59 * are seen |
|
60 */ |
|
61 public FilterManagedObject(ManagedObject<T> wrappee, |
|
62 Predicate<? super T> predicate) |
|
63 { |
|
64 super(wrappee); |
|
65 |
|
66 pred_ = predicate; |
|
67 |
|
68 /* |
|
69 * Should probably be implemented in terms of intervalAdded(), like |
|
70 * setManagedObject() is. |
|
71 */ |
|
72 synchronized (getManagedObject().getChildrenLock()) { |
|
73 List<T> children = getManagedObject().getChildren(); |
|
74 int loc = 0; |
|
75 for (T child : children) { |
|
76 if (pred_.test(child)) { |
|
77 children_.add(child); |
|
78 childMap_.add(loc); |
|
79 loc++; |
|
80 } else { |
|
81 childMap_.add(null); |
|
82 } |
|
83 } |
|
84 |
|
85 getManagedObject().addPropertyChangeListener(this); |
|
86 } |
|
87 } |
|
88 |
|
89 public FilterManagedObject(ManagedObject<T> wrappee) { |
|
90 this(wrappee, new TruePredicate<T>()); |
|
91 } |
|
92 |
|
93 /** |
|
94 * Replaces the predicate used by the FilterManagedObject. |
|
95 * |
|
96 * @param predicate the new {@code Predicate} to use |
|
97 */ |
|
98 public void setPredicate(Predicate<? super T> predicate) |
|
99 { |
|
100 IntervalEventQueue ieq = new IntervalEventQueue(this, iListeners_); |
|
101 |
|
102 synchronized (getManagedObject().getChildrenLock()) { |
|
103 synchronized (children_) { |
|
104 List<T> children = getManagedObject().getChildren(); |
|
105 |
|
106 int loc = 0; |
|
107 int size = children.size(); |
|
108 for (int i = 0; i < size; i++) { |
|
109 T o = children.get(i); |
|
110 boolean matches = predicate.test(o); |
|
111 Integer mapping = childMap_.get(i); |
|
112 if (mapping != null) { |
|
113 if (matches) { |
|
114 if (mapping != loc) |
|
115 childMap_.set(i, loc); |
|
116 loc++; |
|
117 } else { |
|
118 ieq.addIndex(loc, IntervalEventQueue.Type.REMOVE); |
|
119 children_.remove(loc); |
|
120 childMap_.set(i, null); |
|
121 } |
|
122 } else { |
|
123 if (matches) { |
|
124 ieq.addIndex(loc, IntervalEventQueue.Type.ADD); |
|
125 children_.add(loc, o); |
|
126 childMap_.set(i, loc); |
|
127 loc++; |
|
128 } /* else do nothing */ |
|
129 } |
|
130 } |
|
131 ieq.flush(); |
|
132 pred_ = predicate; |
|
133 } |
|
134 } |
|
135 } |
|
136 |
|
137 /** |
|
138 * Refreshes the child view. Used if the behavior of the |
|
139 * predicate changes. |
|
140 */ |
|
141 public void refreshPredicate() |
|
142 { |
|
143 setPredicate(pred_); |
|
144 } |
|
145 |
|
146 /** |
|
147 * Given the index of an entry to add to the inbound children list, |
|
148 * find the appropriate location in the outbound children list. |
|
149 * |
|
150 * @param index the index of the new inbound child |
|
151 * @return the index of the corresponding outbound child |
|
152 */ |
|
153 private int findNewMapping(int index) |
|
154 { |
|
155 int oldsize = childMap_.size(); |
|
156 int result; |
|
157 |
|
158 assert (index <= oldsize); |
|
159 |
|
160 if (oldsize == 0) { |
|
161 /* Adding to an empty list; the first index must be 0 */ |
|
162 result = 0; |
|
163 } else { |
|
164 Integer mapping; |
|
165 result = -1; |
|
166 |
|
167 /* Find the last non-null entry... */ |
|
168 if (index > 0) { |
|
169 for (int i = index - 1; i >= 0; i--) |
|
170 if ((mapping = childMap_.get(i)) != null) { |
|
171 result = mapping + 1; |
|
172 break; |
|
173 } |
|
174 } |
|
175 |
|
176 /* ...failing that, the next non-null entry. */ |
|
177 if (result == -1 && index < oldsize) { |
|
178 for (int i = index; i < oldsize; i++) |
|
179 if ((mapping = childMap_.get(i)) != null) { |
|
180 result = mapping; |
|
181 break; |
|
182 } |
|
183 } |
|
184 |
|
185 /* Our mapping is empty; start at the beginning. */ |
|
186 if (result == -1) |
|
187 result = 0; |
|
188 } |
|
189 |
|
190 return (result); |
|
191 } |
|
192 |
|
193 /** |
|
194 * Shifts all non-null mappings after a point by the specified amount. |
|
195 * |
|
196 * @param start the first index whose value should be adjusted |
|
197 * @param delta the amount by which values should be adjusted |
|
198 */ |
|
199 private void adjustMappings(int start, int delta) |
|
200 { |
|
201 int size = childMap_.size(); |
|
202 for (int i = start; i < size; i++) { |
|
203 Integer mapping = childMap_.get(i); |
|
204 if (mapping != null) { |
|
205 assert (mapping + delta >= 0); |
|
206 childMap_.set(i, mapping + delta); |
|
207 } |
|
208 } |
|
209 } |
|
210 |
|
211 /* |
|
212 * ManagedObject methods |
|
213 */ |
|
214 |
|
215 @Override |
|
216 public void dispose() { |
|
217 } |
|
218 |
|
219 @Override |
|
220 public String getId() |
|
221 { |
|
222 return (getManagedObject().getId()); |
|
223 } |
|
224 |
|
225 @Override |
|
226 public String getDescription() |
|
227 { |
|
228 return (getManagedObject().getDescription()); |
|
229 } |
|
230 |
|
231 @Override |
|
232 public List<T> getChildren() |
|
233 { |
|
234 return (roChildren_); |
|
235 } |
|
236 |
|
237 @Override |
|
238 public Object getChildrenLock() |
|
239 { |
|
240 return (children_); |
|
241 } |
|
242 |
|
243 @Override |
|
244 public String getName() |
|
245 { |
|
246 return (getManagedObject().getName()); |
|
247 } |
|
248 |
|
249 @Override |
|
250 public ManagedObjectStatus getStatus() |
|
251 { |
|
252 return (getManagedObject().getStatus()); |
|
253 } |
|
254 |
|
255 @Override |
|
256 public String getStatusText() |
|
257 { |
|
258 return (getManagedObject().getStatusText()); |
|
259 } |
|
260 |
|
261 @Override |
|
262 public void addIntervalListener(IntervalListener l) |
|
263 { |
|
264 iListeners_.add(l); |
|
265 } |
|
266 |
|
267 @Override |
|
268 public boolean removeIntervalListener(IntervalListener l) |
|
269 { |
|
270 return (iListeners_.remove(l)); |
|
271 } |
|
272 |
|
273 /* |
|
274 * PropertyChangeListener methods |
|
275 */ |
|
276 |
|
277 @Override |
|
278 public void propertyChange(PropertyChangeEvent evt) |
|
279 { |
|
280 Object source = evt.getSource(); |
|
281 |
|
282 /* Throw away events from children we aren't exposing. */ |
|
283 synchronized (children_) { |
|
284 if (listenees_.contains(source) && !children_.contains(source)) |
|
285 return; |
|
286 |
|
287 pListeners_.propertyChange(evt); |
|
288 } |
|
289 } |
|
290 |
|
291 /* |
|
292 * IntervalListener methods |
|
293 */ |
|
294 |
|
295 @Override |
|
296 public void intervalAdded(IntervalEvent e) |
|
297 { |
|
298 /* synchronized (getManagedObject().getChildrenLock()) implied */ |
|
299 synchronized (children_) { |
|
300 super.intervalAdded(e); |
|
301 |
|
302 int f = e.getFirstIndex(); |
|
303 int l = e.getLastIndex(); |
|
304 int first = findNewMapping(f); |
|
305 int last = first; |
|
306 int i; |
|
307 |
|
308 List<T> adds = listenees_.subList(f, l + 1); |
|
309 i = f; |
|
310 for (T o : adds) { |
|
311 if (pred_.test(o)) { |
|
312 childMap_.add(i, last); |
|
313 children_.add(last, o); |
|
314 last++; |
|
315 } else { |
|
316 childMap_.add(i, null); |
|
317 } |
|
318 i++; |
|
319 } |
|
320 |
|
321 if (--last >= first) { |
|
322 adjustMappings(l + 1, last - first + 1); |
|
323 iListeners_.intervalAdded(new IntervalEvent(this, first, last)); |
|
324 } |
|
325 } |
|
326 } |
|
327 |
|
328 @Override |
|
329 public void intervalRemoved(IntervalEvent e) |
|
330 { |
|
331 /* synchronized (inner_.getChildrenLock()) implied */ |
|
332 synchronized (children_) { |
|
333 int f = e.getFirstIndex(); |
|
334 int l = e.getLastIndex(); |
|
335 |
|
336 super.intervalRemoved(e); |
|
337 |
|
338 /* Find mapping in codomain, remove from domain */ |
|
339 int first = children_.size(); |
|
340 int last = -1; |
|
341 for (int i = f; i <= l; i++) { |
|
342 Integer mapping = childMap_.get(f); |
|
343 childMap_.remove(f); |
|
344 if (mapping != null) { |
|
345 if (mapping < first) |
|
346 first = mapping; |
|
347 if (mapping > last) |
|
348 last = mapping; |
|
349 } |
|
350 } |
|
351 |
|
352 /* Remove from codomain */ |
|
353 children_.subList(first, last + 1).clear(); |
|
354 |
|
355 if (first <= last) { |
|
356 adjustMappings(f, - (last - first + 1)); |
|
357 iListeners_.intervalRemoved( |
|
358 new IntervalEvent(this, first, last)); |
|
359 } |
|
360 } |
|
361 } |
|
362 |
|
363 // |
|
364 // ManagedObjectMonitor methods |
|
365 // |
|
366 |
|
367 @Override |
|
368 public void setManagedObject(ManagedObject<T> mo) { |
|
369 synchronized (this) { |
|
370 getManagedObject().removePropertyChangeListener(this); |
|
371 super.setManagedObject(mo); |
|
372 mo.addPropertyChangeListener(this); |
|
373 } |
|
374 } |
|
375 |
|
376 /** |
|
377 * Called by the PropertyChangeListener registered with inbound |
|
378 * children. Makes appropriate change to outbound children list. |
|
379 */ |
|
380 @Override |
|
381 protected void managedObjectPropertyChange(T child, PropertyChangeEvent evt) |
|
382 { |
|
383 synchronized (children_) { |
|
384 int index = listenees_.indexOf(child); |
|
385 boolean match = pred_.test(child); |
|
386 Integer mapping = childMap_.get(index); |
|
387 if (match && mapping == null) { |
|
388 /* Needs to be added to exported model */ |
|
389 mapping = findNewMapping(index); |
|
390 children_.add(mapping, child); |
|
391 childMap_.set(index, mapping); |
|
392 adjustMappings(index + 1, 1); |
|
393 iListeners_.intervalAdded( |
|
394 new IntervalEvent(this, mapping, mapping)); |
|
395 } else if (!match && mapping != null) { |
|
396 /* Needs to be removed from exported model */ |
|
397 children_.remove(mapping.intValue()); |
|
398 childMap_.set(index, null); |
|
399 adjustMappings(index + 1, -1); |
|
400 iListeners_.intervalRemoved( |
|
401 new IntervalEvent(this, mapping, mapping)); |
|
402 } |
|
403 } |
|
404 } |
|
405 } |
|