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.Lookup;
044: import org.openide.util.lookup.Lookups;
045:
046: import java.util.*;
047:
048: /**
049: * This class represents a component in a document. A component holds mainly its children, property values, and presenters.
050: * <p>
051: * A component can be created by a DesignDocument.createComponent method only. The component can be used only in the document
052: * that created the component. Each component has its component id that is unique inside the document. The component
053: * is a type id with component kind.
054: * <p>
055: * A component could be placed into tree hierarchy - each component has its parent component and its children.
056: * <p>
057: * A component is holding a map of property name/value pairs. When a component descriptor is assigned to the component
058: * it automatically loads up a default property values. In case that the component already contains values
059: * that are compatible with a new property descriptors, these values we stay unchanged. Otherwise they are overriden
060: * by its default value.
061: * <p>
062: * A component has a set of presenter. Presenters are a little bit smarter listeners. They also can be assigned/held
063: * by a component directly. Presenters are created each time a new component descriptor is assigned.
064: *
065: * @author David Kaspar
066: */
067: public final class DesignComponent {
068:
069: // TODO - addComponent and removeComponent does not pass index of affected component into transaction/undoableedit
070: // TODO - children could be implemented as a DesignComponent[] and the undoableedit could held old and new array.
071: // HINT - no undo/redo while setComponentDescriptor - requires to check writeProperty while doing PropertyChangedUndoableEdit
072:
073: private final DesignDocument document;
074: private final long componentID;
075: private final TypeID type;
076: private DesignComponent parentComponent;
077: private final ArrayList<DesignComponent> children;
078: private final HashMap<String, PropertyValue> properties;
079: private final HashMap<String, PropertyValue> defaultProperties;
080:
081: private ComponentDescriptor componentDescriptor;
082: private Lookup presenters;
083: private PropertyValue referencePropertyValue;
084:
085: DesignComponent(DesignDocument document, long componentID,
086: ComponentDescriptor componentDescriptor) {
087: assert Debug.isFriend(DesignDocument.class,
088: "createRawComponent"); // NOI18N
089: this .document = document;
090: this .componentID = componentID;
091: this .type = componentDescriptor.getTypeDescriptor()
092: .getThisType();
093: assert type.getKind() == TypeID.Kind.COMPONENT
094: && type.getDimension() == 0;
095: children = new ArrayList<DesignComponent>();
096: properties = new HashMap<String, PropertyValue>();
097: defaultProperties = new HashMap<String, PropertyValue>();
098: referencePropertyValue = PropertyValue
099: .createComponentReferenceCore(this );
100: presenters = Lookup.EMPTY;
101: setComponentDescriptor(componentDescriptor, false);
102: }
103:
104: /**
105: * Returns a component descriptor.
106: * @return the compatible descriptor, null if not assigned
107: */
108: public ComponentDescriptor getComponentDescriptor() {
109: assert document.getTransactionManager().isAccess();
110: return componentDescriptor;
111: }
112:
113: void setComponentDescriptor(
114: ComponentDescriptor componentDescriptor, boolean useUndo) {
115: assert Debug.isFriend(DesignDocument.class,
116: "updateDescriptorReferences")
117: || Debug.isFriend(DesignComponent.class, "<init>")
118: || Debug.isFriend(DesignDocument.class,
119: "updateDescriptorReferencesCore"); // NOI18N
120: if (this .componentDescriptor == componentDescriptor)
121: return;
122:
123: this .componentDescriptor = componentDescriptor;
124:
125: if (componentDescriptor != null) {
126: for (PropertyDescriptor propertyDescriptor : componentDescriptor
127: .getPropertyDescriptors()) {
128: String propertyName = propertyDescriptor.getName();
129: PropertyValue propertyValue = properties
130: .get(propertyName);
131: if (propertyValue == null
132: || !propertyValue
133: .isCompatible(propertyDescriptor
134: .getType())) {
135: propertyValue = propertyDescriptor
136: .createDefaultValue(this , propertyName);
137: if (!propertyValue.isCompatible(propertyDescriptor))
138: Debug
139: .warning(
140: "Default property value is not compatible",
141: componentID, propertyName,
142: propertyValue); // NOI18N
143: defaultProperties.put(propertyName, propertyValue);
144: if (properties.get(propertyName) != null)
145: writeProperty(propertyName, propertyValue);
146: else
147: properties.put(propertyName, propertyValue);
148: }
149: }
150:
151: Collection<? extends Presenter> presentersToRemove = this .presenters
152: .lookupAll(Presenter.class);
153:
154: ArrayList<Presenter> temp = new ArrayList<Presenter>();
155: gatherPresenters(temp, componentDescriptor);
156: PresentersProcessor.postProcessDescriptor(document
157: .getDocumentInterface().getProjectType(), document,
158: componentDescriptor, temp);
159: presenters = Lookups.fixed(temp.toArray());
160:
161: document.getTransactionManager()
162: .componentDescriptorChangeHappened(this ,
163: presentersToRemove, temp, useUndo);
164: } else
165: document.getTransactionManager()
166: .componentDescriptorChangeHappened(this , null,
167: null, useUndo);
168: }
169:
170: private void gatherPresenters(ArrayList<Presenter> tempPresenters,
171: ComponentDescriptor componentDescriptor) {
172: if (componentDescriptor == null)
173: return;
174: gatherPresenters(tempPresenters, componentDescriptor
175: .getSuperDescriptor());
176: componentDescriptor.gatherPresenters(tempPresenters);
177: }
178:
179: /**
180: * Returns a document where the component is living.
181: * @return the document
182: */
183: public DesignDocument getDocument() {
184: return document;
185: }
186:
187: /**
188: * Returns a component id.
189: * @return the component id
190: */
191: public long getComponentID() {
192: return componentID;
193: }
194:
195: /**
196: * Returns a component type id
197: * @return the component type id
198: */
199: public TypeID getType() {
200: return type;
201: }
202:
203: /**
204: * Returns a parent component of this component.
205: * @return the parent component
206: */
207: public DesignComponent getParentComponent() {
208: assert document.getTransactionManager().isAccess();
209: return parentComponent;
210: }
211:
212: /**
213: * Adds a child component into this component.
214: * @param component the child component
215: */
216: public void addComponent(DesignComponent component) {
217: assert document.getTransactionManager().isWriteAccess();
218: assert document == component.document;
219: assert component.parentComponent == null;
220: assert addComponentAssert(component);
221: children.add(component);
222: component.parentComponent = this ;
223: document.getTransactionManager().parentChangeHappened(null,
224: this , component);
225: }
226:
227: private boolean addComponentAssert(DesignComponent component) {
228: DesignComponent parent = this ;
229: while (parent != null) {
230: assert parent != component;
231: parent = parent.getParentComponent();
232: }
233: return true;
234: }
235:
236: /**
237: * Removes a child component from this component.
238: * @param component the child component
239: */
240: public void removeComponent(DesignComponent component) {
241: assert document.getTransactionManager().isWriteAccess();
242: assert document == component.document;
243: assert component.parentComponent == this ;
244: if (!children.remove(component))
245: throw Debug.error("Component is not a child of its parent",
246: "parent", this , "Child", component, "Children",
247: children); // NOI18N
248: component.parentComponent = null;
249: document.getTransactionManager().parentChangeHappened(this ,
250: null, component);
251: }
252:
253: /**
254: * Removes the component from its parent component.
255: */
256: public void removeFromParentComponent() {
257: assert document.getTransactionManager().isWriteAccess();
258: if (parentComponent != null)
259: parentComponent.removeComponent(this );
260: }
261:
262: /**
263: * Returns a collection of children components.
264: * @return the collection of children
265: */
266: public Collection<DesignComponent> getComponents() {
267: assert document.getTransactionManager().isAccess();
268: return Collections.unmodifiableCollection(children);
269: }
270:
271: /**
272: * Returns a property value of a specified property
273: * @param propertyName the property name
274: * @return the property value
275: */
276: public PropertyValue readProperty(String propertyName) {
277: assert document.getTransactionManager().isAccess();
278: PropertyValue value = properties.get(propertyName);
279: assert properties.containsKey(propertyName) : toString() + "."
280: + propertyName + " property is missing"; //NOI18N
281: assert value != null;
282: return value;
283: }
284:
285: /**
286: * Writes a property value.
287: * @param propertyName the property name
288: * @param propertyValue the property value
289: */
290: public void writeProperty(String propertyName,
291: PropertyValue propertyValue) {
292: assert document.getTransactionManager().isWriteAccess();
293: assert propertyValue != null : "Null property value"; // NOI18N
294: assert componentDescriptor != null;
295:
296: PropertyValue oldValue = properties.get(propertyName);
297: assert oldValue != null : "Missing old value in " + this + "."
298: + propertyName; // NOI18N
299: if (oldValue == propertyValue)
300: return;
301:
302: PropertyDescriptor propertyDescriptor = componentDescriptor
303: .getPropertyDescriptor(propertyName);
304: assert propertyDescriptor != null : "Missing property descriptor in "
305: + this + "." + propertyName; // NOI18N
306: assert !propertyDescriptor.isReadOnly() : "Cannot write read-only property "
307: + this + "." + propertyName; // NOI18N // TODO - allow writing during deserialization
308: assert propertyValue.isCompatible(propertyDescriptor);
309:
310: properties.put(propertyName, propertyValue);
311:
312: document.getTransactionManager().writePropertyHappened(this ,
313: propertyName, oldValue, propertyValue);
314: }
315:
316: /**
317: * Resolves whether a specified property has a default value as current value.
318: * Check is done on the PropertyValue object level. This method returns true only when DesignComponent
319: * default PropertyValue == current PropertyValue for given propertyName.
320: * @param propertyName the property name
321: * @return true if a current PropertyValue is the same PropertyValue object which has been created on the creation of the DesignComponent
322: */
323: public boolean isDefaultValue(String propertyName) {
324: assert document.getTransactionManager().isAccess();
325: assert propertyName != null;
326: PropertyValue defaultValue = defaultProperties
327: .get(propertyName);
328: PropertyValue currentValue = properties.get(propertyName);
329: assert defaultValue != null;
330: assert currentValue != null;
331: //This has to be changed in previous version, it should be resolved on the "equals" method level.
332: return defaultValue == currentValue;
333: }
334:
335: /**
336: * Reset a specific property to its default value.
337: * @param propertyName the property name
338: */
339: public void resetToDefault(String propertyName) {
340: assert document.getTransactionManager().isWriteAccess();
341: assert propertyName != null;
342: PropertyValue defaultValue = defaultProperties
343: .get(propertyName);
344: assert defaultValue != null;
345: writeProperty(propertyName, defaultValue);
346: }
347:
348: PropertyValue getReferenceValue() {
349: return referencePropertyValue;
350: }
351:
352: /**
353: * Returns a presenter for a specific presenter id.
354: * @param presenterClass the presenter class
355: * @return the presenter
356: */
357: public <T extends Presenter> T getPresenter(Class<T> presenterClass) {
358: assert document.getTransactionManager().isAccess();
359: assert presenterClass != null;
360: return presenters.lookup(presenterClass);
361: }
362:
363: /**
364: * Returns a list of presenters in a specific presenter category.
365: * A presenter matches when the specified presenter category is a prefix of the id of the presenter.
366: * @param presenterClass the presenter category class
367: * @return a collection of presenters, non-null value
368: */
369: public <T> Collection<? extends T> getPresenters(
370: Class<T> presenterClass) {
371: assert document.getTransactionManager().isAccess();
372: assert presenterClass != null;
373: return presenters.lookupAll(presenterClass);
374: }
375:
376: /**
377: * Returns a component debug string.
378: * @return the string
379: */
380: @Override
381: public String toString() {
382: return componentID + ":" + type; // NOI18N
383: }
384:
385: void dumpComponent(String indent) {
386: assert document.getTransactionManager().isAccess();
387:
388: System.out.println(indent + componentID + " : "
389: + componentDescriptor); // NOI18N
390: indent += " "; // NOI18N
391:
392: HashSet<String> undefinedProperties = new HashSet<String>(
393: properties.keySet());
394: Collection<PropertyDescriptor> propertyDescriptors = componentDescriptor
395: .getPropertyDescriptors();
396: for (PropertyDescriptor property : propertyDescriptors) {
397: String name = property.getName();
398: System.out.println(indent
399: + (properties.get(name) == defaultProperties
400: .get(name) ? "## " : ":: ") + name + " = "
401: + properties.get(name)); // NOI18N
402: undefinedProperties.remove(name);
403: }
404: for (String name : undefinedProperties)
405: System.out.println(indent + "?? " + name + " = "
406: + properties.get(name)); // NOI18N
407: for (Object presenter : presenters.lookupAll(Object.class))
408: System.out.println(indent + ">>" + presenter); // NOI18N
409:
410: for (DesignComponent child : children)
411: child.dumpComponent(indent);
412: }
413:
414: }
|