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.openide.util.Utilities;
044:
045: import java.lang.ref.WeakReference;
046: import java.util.*;
047:
048: /**
049: * This class represents a document holding all components.
050: * <p>
051: * For creating document it is required to define a relation to a project when the document will exists.
052: * This is important resolving a repository of project-dependant component descriptors and classpath.
053: * <p>
054: * The document contains a transaction manager. It allows you to acquire a read or write access to the document.
055: * It is because almost all method in the model API is protected and you have to have an appropriate access to call them.
056: * The manager uses NetBeans MUTEX class that allows to multi-read or single-write access at the same time.
057: * <p>
058: * The document does not fire an event everytime you change something in the document and it does not allows you
059: * to register a listener on each component directly. Instead there is a listener manager which groups all events
060: * from the all parts of document and fires a single big event when a write transaction is finished.
061: * The listener manager allows you to register document-related event listeners with a specific event filter.
062: * <p>
063: * The document contain a tree of component. The tree is specified by a root component. The root component could be set
064: * only once.
065: *
066: * @author David Kaspar
067: */
068: public final class DesignDocument {
069:
070: // TODO - how to define: a component is removing?
071:
072: // TODO - document reloading using the same instance (all components must be destroyed (how?) - what about a flag indicating that a component is unusable anymore?
073:
074: // TODO - selection model - right now a developer has to change the selection manually when a selected component is removed
075:
076: // HINT - deserializer must have possibility to specify uid (serialized uid vs. created uid) - could cause problem with code generation
077:
078: // TODO - setSelectedComponents - check whether the selection is really changed; if not, do not fire an event
079:
080: private final DocumentInterface documentInterface;
081: private final DescriptorRegistry descriptorRegistry;
082: private final ListenerManager listenerManager;
083: private final TransactionManager transactionManager;
084:
085: private HashMap<Long, TimedWeakReference> uid2components;
086: private DesignComponent rootComponent;
087: private long componentIDCounter;
088:
089: private String selectionSourceID;
090: private Collection<DesignComponent> selectedComponents;
091:
092: /**
093: * Creates an instance of document.
094: * @param documentInterface the documentInterface interface
095: */
096: public DesignDocument(DocumentInterface documentInterface) {
097: this .documentInterface = documentInterface;
098: descriptorRegistry = DescriptorRegistry.getDescriptorRegistry(
099: documentInterface.getProjectType(), documentInterface
100: .getProjectID());
101: listenerManager = new ListenerManager(this );
102: transactionManager = new TransactionManager(this ,
103: descriptorRegistry, listenerManager);
104:
105: uid2components = new HashMap<Long, TimedWeakReference>(100);
106: componentIDCounter = 0;
107: selectedComponents = Collections.emptySet();
108:
109: descriptorRegistry
110: .addRegistryListener(new DescriptorRegistryListener() {
111: public void descriptorRegistryUpdated() {
112: updateDescriptorReferences();
113: }
114: });
115: }
116:
117: /**
118: * Returns document interface of the document.
119: * @return the document interface
120: */
121: // TODO - maybe it has to be replaced by another interface, since DocumentInterface contains more methods that required
122: public DocumentInterface getDocumentInterface() {
123: // assert Debug.isFriend (DescriptorRegistry.class, "getDescriptorRegistry") || Debug.isFriend (TransactionManager.class, "writeAccessRootEnd"); // NOI18N
124: return documentInterface;
125: }
126:
127: /**
128: * Returns a component descriptor registry related to the project when the document is placed.
129: * @return the descriptor registry
130: */
131: public DescriptorRegistry getDescriptorRegistry() {
132: return descriptorRegistry;
133: }
134:
135: /**
136: * Returns a listener manager of the document.
137: * @return the listener manager
138: */
139: public ListenerManager getListenerManager() {
140: return listenerManager;
141: }
142:
143: /**
144: * Returns a transaction manager of the document.
145: * @return the transaction manager
146: */
147: public TransactionManager getTransactionManager() {
148: return transactionManager;
149: }
150:
151: /**
152: * Returns a root component of components tree assigned to the document.
153: * @return the root component.
154: */
155: public DesignComponent getRootComponent() {
156: assert getTransactionManager().isAccess();
157: return rootComponent;
158: }
159:
160: /**
161: * Sets a root component of the document. This method could be called only once.
162: * <p>
163: * Warning: This method should be used by IO module (where the document is created) only.
164: * @param rootComponent the root component
165: */
166: public void setRootComponent(DesignComponent rootComponent) {
167: assert getTransactionManager().isWriteAccess();
168: assert this .rootComponent == null;
169: assert rootComponent != null;
170: this .rootComponent = rootComponent;
171: getTransactionManager().rootChangeHappened(rootComponent);
172: }
173:
174: /**
175: * Sets a preferred component ID.
176: * <p>
177: * Warning: This method is for deserialization purpose only (IO module). Do not use it directly.
178: * @param preferredComponentID the preferred component id
179: * @return true if preferred component id was used
180: */
181: public boolean setPreferredComponentID(long preferredComponentID) {
182: assert transactionManager.isWriteAccess();
183: if (componentIDCounter > preferredComponentID)
184: return false;
185: componentIDCounter = preferredComponentID;
186: return true;
187: }
188:
189: /**
190: * Creates a new component using a component descriptor with specified typeid.
191: * <p>
192: * Note: It does not add the component into a tree, you have to do it manually.
193: * @param componentType the component typeid
194: * @return the new component
195: */
196: public DesignComponent createComponent(TypeID componentType) {
197: DesignComponent component = createRawComponent(componentType);
198: assert component != null;
199: performComponentPostInit(component, component
200: .getComponentDescriptor());
201: PostInitializeProcessor.postInitializeComponent(
202: getDocumentInterface().getProjectType(), component);
203: return component;
204: }
205:
206: private void performComponentPostInit(DesignComponent component,
207: ComponentDescriptor descriptor) {
208: if (descriptor == null)
209: return;
210: performComponentPostInit(component, descriptor
211: .getSuperDescriptor());
212: descriptor.postInitialize(component);
213: }
214:
215: /**
216: * Creates a new raw component using a component descriptor with specified typeid.
217: * <p>
218: * Note: It does not add the component into a tree, you have to do it manually.
219: * <p>
220: * Note: it just allocates the component, it does not do any post-initializion.
221: * @param componentType the component typeid
222: * @return the new raw component
223: */
224: public DesignComponent createRawComponent(TypeID componentType) {
225: assert Debug.isFriend(DesignDocument.class, "createComponent")
226: || Debug.isFriend(
227: "org.netbeans.modules.vmd.io.DocumentLoad",
228: "loadDocumentCore"); // NOI18N
229: assert transactionManager.isWriteAccess();
230:
231: ComponentDescriptor componentDescriptor = descriptorRegistry
232: .getComponentDescriptor(componentType);
233: assert componentDescriptor != null : "Missing component descriptor for "
234: + componentType; // NOI18N
235: assert componentDescriptor.getTypeDescriptor()
236: .isCanInstantiate();
237:
238: DesignComponent component = new DesignComponent(this ,
239: componentIDCounter++, componentDescriptor);
240:
241: uid2components.put(component.getComponentID(),
242: new TimedWeakReference(component));
243: getListenerManager().notifyComponentCreated(component);
244:
245: return component;
246: }
247:
248: /**
249: * Removes a component from the component tree and removes references to this component from all property values
250: * in all components in the document.
251: * <p>
252: * Note: It does not allows to remove the root component.
253: * @param component the component
254: */
255: public void deleteComponent(DesignComponent component) {
256: assert transactionManager.isWriteAccess();
257: assert component != null && component != rootComponent;
258: assert component.getDocument() == this ;
259:
260: Collection<DesignComponent> components = component
261: .getComponents();
262: if (components.size() > 0)
263: Debug
264: .warning(
265: "Children has to be deleted before deleting the component",
266: component, components); // NOI18N
267:
268: component.removeFromParentComponent();
269: ComponentDescriptor descriptor = component
270: .getComponentDescriptor();
271: if (descriptor != null)
272: for (PropertyDescriptor property : descriptor
273: .getPropertyDescriptors())
274: component.resetToDefault(property.getName());
275:
276: if (selectedComponents.contains(component)) {
277: HashSet<DesignComponent> selected = new HashSet<DesignComponent>(
278: selectedComponents);
279: selected.remove(component);
280: setSelectedComponents("deleteComponent", selected); // NOI18N
281: }
282:
283: assert !Debug.isComponentReferencedInRootTree(component) : "Component ("
284: + component + ") is referenced still after deletion"; // NOI18N
285: }
286:
287: /**
288: * Removes components from the component tree and removes references to these components from all property values
289: * in all components in the document.
290: * <p>
291: * Note: It does not allows to remove the root component.
292: * @param components the components
293: */
294: public void deleteComponents(Collection<DesignComponent> components) {
295: assert transactionManager.isWriteAccess();
296: assert deleteComponentsPreAssert(components);
297:
298: for (DesignComponent component : components)
299: component.removeFromParentComponent();
300:
301: HashSet<DesignComponent> selected = null;
302:
303: for (DesignComponent component : components) {
304: Collection<DesignComponent> children = component
305: .getComponents();
306: if (children.size() > 0)
307: Debug
308: .warning(
309: "Children has to be deleted before deleting the component",
310: component, children); // NOI18N
311:
312: ComponentDescriptor descriptor = component
313: .getComponentDescriptor();
314: if (descriptor != null)
315: for (PropertyDescriptor property : descriptor
316: .getPropertyDescriptors())
317: component.resetToDefault(property.getName());
318:
319: if (selectedComponents.contains(component)) {
320: if (selected == null)
321: selected = new HashSet<DesignComponent>(
322: selectedComponents);
323: selected.remove(component);
324: }
325: }
326:
327: if (selected != null)
328: setSelectedComponents("deleteComponent", selected); // NOI18N
329:
330: assert deleteComponentsPostAssert(components);
331: }
332:
333: private boolean deleteComponentsPreAssert(
334: Collection<DesignComponent> components) {
335: for (DesignComponent component : components) {
336: assert component != null && component != rootComponent;
337: assert component.getDocument() == this ;
338: }
339: return true;
340: }
341:
342: private boolean deleteComponentsPostAssert(
343: Collection<DesignComponent> components) {
344: for (DesignComponent component : components)
345: assert !Debug.isComponentReferencedInRootTree(component) : "Component ("
346: + component
347: + ") is referenced still after deletion"; // NOI18N
348: return true;
349: }
350:
351: /**
352: * Returns a component with specified component id.
353: * @param componentID the component id
354: * @return the component
355: */
356: public DesignComponent getComponentByUID(long componentID) {
357: assert transactionManager.isAccess();
358: TimedWeakReference ref = uid2components.get(componentID);
359: return ref != null ? ref.get() : null;
360: }
361:
362: /**
363: * Returns an id of the last source that set the current selection.
364: * @return the source id
365: */
366: public String getSelectionSourceID() {
367: assert transactionManager.isAccess();
368: return selectionSourceID;
369: }
370:
371: /**
372: * Returns a set of selected components.
373: * @return the set of selected components.
374: */
375: public Collection<DesignComponent> getSelectedComponents() {
376: assert transactionManager.isAccess();
377: return selectedComponents;
378: }
379:
380: /**
381: * Sets selected components. It requires to specify a source id for identifying the source of the selection.
382: * <p>
383: * Note: Undo/redo of setSelectedComponents changed will use null as a selectionSourceID
384: * @param selectionSourceID the source id
385: * @param components the set of selected components.
386: */
387: public void setSelectedComponents(String selectionSourceID,
388: Collection<DesignComponent> components) {
389: assert transactionManager.isWriteAccess();
390: assert components != null;
391: assert setSelectedComponentsAssert(components);
392:
393: if (this .selectedComponents.containsAll(components)
394: && components.containsAll(this .selectedComponents))
395: return;
396:
397: Collection<DesignComponent> old = this .selectedComponents;
398: this .selectionSourceID = selectionSourceID;
399: this .selectedComponents = Collections
400: .unmodifiableCollection(new ArrayList<DesignComponent>(
401: components));
402:
403: transactionManager.selectComponentsHappened(old,
404: this .selectedComponents);
405: }
406:
407: private boolean setSelectedComponentsAssert(
408: Collection<DesignComponent> components) {
409: for (DesignComponent component : components) {
410: assert component != null;
411: assert component.getDocument() == this ;
412: }
413: return true;
414: }
415:
416: private void updateDescriptorReferences() {
417: transactionManager.writeAccess(new Runnable() {
418: public void run() {
419: updateDescriptorReferencesCore();
420: }
421: });
422: }
423:
424: private void updateDescriptorReferencesCore() {
425: for (TimedWeakReference reference : uid2components.values()) {
426: DesignComponent component = reference.get();
427: if (component == null)
428: continue;
429: ComponentDescriptor descriptor = descriptorRegistry
430: .getComponentDescriptor(component.getType());
431: component.setComponentDescriptor(descriptor, true);
432: }
433: }
434:
435: private final class TimedWeakReference extends
436: WeakReference<DesignComponent> implements Runnable {
437:
438: private final long componentID;
439:
440: public TimedWeakReference(DesignComponent referent) {
441: super (referent, Utilities.activeReferenceQueue());
442: this .componentID = referent.getComponentID();
443: }
444:
445: // HINT - optimalize
446: public void run() {
447: transactionManager.writeAccess(new Runnable() {
448: public void run() {
449: uid2components.remove(componentID);
450: }
451: });
452: }
453: }
454:
455: }
|