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.modules.vmd.api.model;
042:
043: import org.netbeans.modules.vmd.api.model.common.ValidatorPresenter;
044: import org.openide.ErrorManager;
045:
046: import java.util.*;
047:
048: /**
049: * This class managers listeners and cares about firing an events.
050: * <p>
051: * The approach behind the listener manager are group listeners. It is not possible to add a listener directly to a component.
052: * It is just possible to attach a listener that will be notified each time when a write transaction is finished and
053: * there is a change in the document.
054: * <p>
055: * It is also possible to filter events that are not important for partical listener by specifying an event filter.
056: * A event filter is mutable class, so the filter could be changed during the life-time of the listener. the listener
057: * manager is working with the state that the filter was at the time of write transaction is finished.
058: *
059: * @author David Kaspar
060: */
061: public final class ListenerManager {
062:
063: // WARNING - only one filter per a listener
064:
065: private static final boolean INVOKE_VALIDATORS = true;
066:
067: private volatile long eventID = 0;
068:
069: /**
070: * Returns a document state. The state is a non-negative number that is increased each time a document is modified.
071: * @return the state
072: */
073: public long getDocumentState() {
074: return eventID;
075: }
076:
077: // HINT - affectedComponentDescriptor event is not fired - replaced by Presenter.notifyAdded, Presenter.notifyRemoved
078: // HINT - Does this cover all usages? - UPDATE - it is fired right now
079:
080: private static final class PresenterItem {
081:
082: private final DesignComponent component;
083: private final Collection<? extends Presenter> presentersToRemove;
084:
085: PresenterItem(DesignComponent designComponent,
086: Collection<? extends Presenter> presentersToRemove) {
087: this .component = designComponent;
088: this .presentersToRemove = presentersToRemove;
089: }
090:
091: }
092:
093: private final DesignDocument document;
094:
095: private final WeakHashMap<DesignListener, DesignEventFilter> listeners = new WeakHashMap<DesignListener, DesignEventFilter>();
096:
097: private HashSet<DesignComponent> descriptorChangedComponents;
098: private ArrayList<PresenterItem> presenterItems;
099: private HashSet<DesignComponent> fullyComponents;
100: private HashSet<DesignComponent> fullyHierarchies;
101:
102: private HashSet<DesignComponent> partlyComponents;
103: private HashSet<DesignComponent> partlyHieararchies;
104:
105: private boolean selectionChanged;
106:
107: private HashMap<DesignComponent, HashMap<String, PropertyValue>> oldPropertyValues;
108:
109: private Set<DesignComponent> createdComponents;
110:
111: private List<AccessController> controllers;
112:
113: private PresenterEventManager presenterEventManager;
114:
115: ListenerManager(DesignDocument document) {
116: this .document = document;
117: clearCaches();
118: controllers = AccessControllerFactoryRegistry
119: .createAccessControllers(document);
120: presenterEventManager = new PresenterEventManager();
121: }
122:
123: private void clearCaches() {
124: // HINT - check capacity statistics
125: descriptorChangedComponents = new HashSet<DesignComponent>();
126: presenterItems = new ArrayList<PresenterItem>(100);
127: fullyComponents = new HashSet<DesignComponent>(100);
128: fullyHierarchies = new HashSet<DesignComponent>(100);
129: partlyComponents = new HashSet<DesignComponent>(100);
130: partlyHieararchies = new HashSet<DesignComponent>(100);
131: oldPropertyValues = new HashMap<DesignComponent, HashMap<String, PropertyValue>>(
132: 100);
133: createdComponents = new HashSet<DesignComponent>(100);
134: selectionChanged = false;
135: }
136:
137: /**
138: * Returns a access controller by a specific controller id
139: * @param controllerClass the access controller class
140: * @return the access controller
141: */
142: @SuppressWarnings("unchecked")
143: // NOI18N
144: public <T extends AccessController> T getAccessController(
145: Class<T> controllerClass) {
146: if (controllerClass != null)
147: for (AccessController controller : controllers)
148: if (controllerClass.isInstance(controller))
149: return (T) controller;
150: return null;
151: }
152:
153: /**
154: * Adds a design listener with a specified filter.
155: * <p>
156: * Note: Each listener could be registered only once, otherwise it just reassign its filter to new one.
157: * @param listener the listener
158: * @param filter the event filter
159: */
160: public void addDesignListener(DesignListener listener,
161: DesignEventFilter filter) {
162: assert listener != null && filter != null;
163: listeners.put(listener, filter);
164: }
165:
166: /**
167: * Removes a design listener.
168: * @param listener the listener
169: */
170: public void removeDesignListener(DesignListener listener) {
171: assert listener != null;
172: listeners.remove(listener);
173: }
174:
175: void addComponentDescriptorChanged(DesignComponent component,
176: Collection<? extends Presenter> presentersToRemove,
177: Collection<Presenter> presentersToAdd) {
178: assert Debug.isFriend(TransactionManager.class,
179: "componentDescriptorChangeHappened"); // NOI18N
180: descriptorChangedComponents.add(component);
181: if (presentersToAdd != null)
182: for (Presenter presenter : presentersToAdd)
183: presenter.setNotifyAttached(component);
184: presenterItems.add(new PresenterItem(component,
185: presentersToRemove/*, presentersToAdd*/));
186: }
187:
188: void addAffectedDesignComponent(DesignComponent component,
189: String propertyName, PropertyValue oldPropertyValue) {
190: assert Debug.isFriend(TransactionManager.class,
191: "writePropertyHappened"); // NOI18N
192: fullyComponents.add(component);
193: HashMap<String, PropertyValue> properties = oldPropertyValues
194: .get(component);
195: if (properties == null) {
196: properties = new HashMap<String, PropertyValue>(100);
197: oldPropertyValues.put(component, properties);
198: }
199: if (!properties.containsKey(propertyName))
200: properties.put(propertyName, oldPropertyValue);
201: }
202:
203: void addAffectedComponentHierarchy(DesignComponent component) {
204: assert Debug.isFriend(TransactionManager.class,
205: "rootChangeHappened")
206: || Debug.isFriend(TransactionManager.class,
207: "parentChangeHappened"); // NOI18N
208: fullyHierarchies.add(component);
209: }
210:
211: void notifyComponentCreated(DesignComponent component) {
212: assert Debug.isFriend(DesignDocument.class,
213: "createRawComponent"); // NOI18N
214: createdComponents.add(component);
215: }
216:
217: void setSelectionChanged() {
218: assert Debug.isFriend(TransactionManager.class,
219: "selectComponentsHappened"); // NOI18N
220: selectionChanged = true;
221: }
222:
223: long getEventID() {
224: // TODO - missing Debug.isFriend check
225: return eventID;
226: }
227:
228: DesignEvent fireEvent() {
229: assert Debug.isFriend(TransactionManager.class,
230: "writeAccessRootEnd"); // NOI18N
231:
232: if (!selectionChanged && descriptorChangedComponents.isEmpty()
233: && fullyComponents.isEmpty()
234: && fullyHierarchies.isEmpty())
235: return null;
236:
237: for (DesignComponent component : fullyComponents) {
238: while (component != null) {
239: if (!partlyComponents.add(component))
240: break;
241: component = component.getParentComponent();
242: }
243: }
244: for (DesignComponent component : fullyHierarchies) {
245: while (component != null) {
246: if (!partlyHieararchies.add(component))
247: break;
248: component = component.getParentComponent();
249: }
250: }
251:
252: Set<DesignComponent> fullyComponentsUm = Collections
253: .unmodifiableSet(fullyComponents);
254: Set<DesignComponent> partlyComponentsUm = Collections
255: .unmodifiableSet(partlyComponents);
256: Set<DesignComponent> fullyHierarchiesUm = Collections
257: .unmodifiableSet(fullyHierarchies);
258: Set<DesignComponent> partlyHierarchiesUm = Collections
259: .unmodifiableSet(partlyHieararchies);
260: Set<DesignComponent> descriptorChangedComponentsUm = Collections
261: .unmodifiableSet(descriptorChangedComponents);
262: Set<DesignComponent> createdComponentsUm = Collections
263: .unmodifiableSet(createdComponents);
264:
265: final DesignEvent event = new DesignEvent(++eventID,
266: fullyComponentsUm, partlyComponentsUm,
267: fullyHierarchiesUm, partlyHierarchiesUm,
268: descriptorChangedComponentsUm, createdComponentsUm,
269: oldPropertyValues, selectionChanged);
270:
271: fireEventInWriteAccess(event, new Runnable() {
272: public void run() {
273: fireEventCore(event);
274: }
275: });
276:
277: return event;
278: }
279:
280: private void fireEventInWriteAccess(DesignEvent event,
281: final Runnable runnable) {
282: final boolean runStatus[] = new boolean[1];
283: final int index[] = new int[1];
284: final Runnable exec[] = new Runnable[1];
285:
286: runStatus[0] = false;
287: index[0] = 0;
288: exec[0] = new Runnable() {
289: public void run() {
290: if (runStatus[0]) {
291: Debug
292: .warning(
293: "AccessController.writeAccess must run the runnable no more than once",
294: controllers.get(index[0] - 1)); // NOI18N
295: return;
296: }
297: if (index[0] >= controllers.size()) {
298: runStatus[0] = true;
299: runnable.run();
300: return;
301: }
302: AccessController current = controllers.get(index[0]);
303: index[0]++;
304: try {
305: current.writeAccess(exec[0]);
306: } catch (ThreadDeath td) {
307: throw td;
308: } catch (Throwable th) {
309: ErrorManager.getDefault().notify(th);
310: }
311: if (!runStatus[0]) {
312: Debug
313: .warning(
314: "AccessController.writeAccess must run the runnable once",
315: current); // NOI18N
316: index[0]++;
317: run();
318: }
319: }
320: };
321:
322: for (AccessController controller : controllers) {
323: try {
324: controller.notifyEventFiring(event);
325: } catch (ThreadDeath td) {
326: throw td;
327: } catch (Throwable th) {
328: ErrorManager.getDefault().notify(th);
329: }
330: }
331:
332: exec[0].run();
333:
334: for (AccessController controller : controllers) {
335: try {
336: controller.notifyEventFired(event);
337: } catch (ThreadDeath td) {
338: throw td;
339: } catch (Throwable th) {
340: ErrorManager.getDefault().notify(th);
341: }
342: }
343: }
344:
345: private void fireEventCore(DesignEvent designEvent) {
346: for (PresenterItem item : presenterItems) {
347: DesignComponent component = item.component;
348: if (item.presentersToRemove != null)
349: for (Presenter presenter : item.presentersToRemove) {
350: try {
351: presenter.setNotifyDetached(component);
352: } catch (ThreadDeath td) {
353: throw td;
354: } catch (Throwable th) {
355: ErrorManager.getDefault().notify(th);
356: }
357: }
358: }
359:
360: Collection<DesignComponent> addedComponentsUm = Collections
361: .unmodifiableCollection(createdComponents);
362: for (AccessController controller : controllers) {
363: try {
364: controller.notifyComponentsCreated(addedComponentsUm);
365: } catch (ThreadDeath td) {
366: throw td;
367: } catch (Throwable th) {
368: ErrorManager.getDefault().notify(th);
369: }
370: }
371:
372: ArrayList<DesignListener> affectedListeners = new ArrayList<DesignListener>();
373: for (Map.Entry<DesignListener, DesignEventFilter> entry : listeners
374: .entrySet()) {
375: DesignListener listener = entry.getKey();
376: if (listener == null)
377: continue;
378: DesignEventFilter filter = entry.getValue();
379: if (filter.isAffected(document, designEvent))
380: affectedListeners.add(listener);
381: }
382:
383: boolean forcePresenterEventManagerUpdate = !presenterItems
384: .isEmpty();
385: clearCaches();
386:
387: if (INVOKE_VALIDATORS)
388: invokeValidators(designEvent);
389:
390: presenterEventManager.prepare(forcePresenterEventManagerUpdate);
391:
392: for (DesignListener designListener : affectedListeners) {
393: try {
394: designListener.designChanged(designEvent);
395: } catch (ThreadDeath td) {
396: throw td;
397: } catch (Throwable th) {
398: ErrorManager.getDefault().notify(th);
399: }
400: }
401:
402: presenterEventManager.execute();
403: }
404:
405: private static void invokeValidators(DesignEvent event) {
406: HashSet<DesignComponent> validated = new HashSet<DesignComponent>();
407:
408: // TODO - "inside-main-tree" validator test is not covered completely
409:
410: invokeValidatorsCore(event.getPartlyAffectedComponents(),
411: validated);
412: invokeValidatorsCore(event.getPartlyAffectedHierarchies(),
413: validated);
414: invokeValidatorsCore(event.getDescriptorChangedComponents(),
415: validated);
416: }
417:
418: private static void invokeValidatorsCore(
419: Set<DesignComponent> components,
420: HashSet<DesignComponent> validated) {
421: for (DesignComponent component : components) {
422: if (validated.contains(component))
423: continue;
424: ValidatorPresenter presenter = component
425: .getPresenter(ValidatorPresenter.class);
426: try {
427: if (presenter != null)
428: presenter.checkValidity();
429: } catch (ThreadDeath td) {
430: throw td;
431: } catch (Throwable t) {
432: }
433: validated.add(component);
434: }
435: }
436:
437: /**
438: * Adds a presenter listener on a presenter of a component.
439: * @param component the component
440: * @param presenterClass the presenter class
441: * @param listener the listener
442: */
443: public void addPresenterListener(DesignComponent component,
444: Class<? extends Presenter> presenterClass,
445: PresenterListener listener) {
446: presenterEventManager.addPresenterListener(component,
447: presenterClass, listener);
448: }
449:
450: /**
451: * Removes a presenter listener on a presenter of a component.
452: * @param component the component
453: * @param presenterClass the presenter class
454: * @param listener the listener
455: */
456: public void removePresenterListener(DesignComponent component,
457: Class<? extends Presenter> presenterClass,
458: PresenterListener listener) {
459: presenterEventManager.removePresenterListener(component,
460: presenterClass, listener);
461: }
462:
463: void firePresenterChanged(DynamicPresenter presenter) {
464: assert Debug.isFriend(DynamicPresenter.class,
465: "firePresenterChanged"); // NOI18N
466: presenterEventManager.firePresenterChanged(presenter
467: .getPresenterListener());
468: }
469:
470: }
|