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 java.util.*;
044:
045: /**
046: * This immutable class represents a property value.
047: * <p>
048: * This class holds information about kind, type id, user code, component, value, array.
049: * The kind could be user code, null, component reference, non-array value, enum value, array value.
050: * This type id represents the type id of the property value for all kinds expect user code and null value.
051: * This user code holds the text of the user code in target language. The component is a reference to a component in case
052: * of reference kind. The value holds the value of enum or a non-array value property. The array holds a value of an array.
053: * <p>
054: * @author David Kaspar
055: */
056: public final class PropertyValue {
057:
058: private static final char USER_CODE_ID = 'U'; // NOI18N
059: private static final char NULL_ID = 'N'; // NOI18N
060: private static final char REFERENCE_ID = 'R'; // NOI18N
061: private static final char VALUE_ID = 'V'; // NOI18N
062: private static final char ENUM_ID = 'E'; // NOI18N
063: private static final char ARRAY_ID = 'A'; // NOI18N
064: private static final char ARRAY_SIZE_SEPARATOR = ':'; // could be any characted except digits // NOI18N
065: private static final char ENCODED_LENGTH_SEPARATOR = '_'; // could be any characted except digits // NOI18N
066: private static final PropertyValue NULL = new PropertyValue(
067: Kind.NULL);
068: private static final List<PropertyValue> EMPTY_ARRAY = new ArrayList<PropertyValue>(
069: 0);
070:
071: /**
072: * The property value kind.
073: */
074: public enum Kind {
075:
076: // WARNING - when changing this enum, review DesignComponent.setComponentDescriptor also
077: /**
078: * The user code.
079: */
080: USERCODE /**
081: * The null value.
082: */
083: , NULL /**
084: * Component reference.
085: */
086: , REFERENCE /**
087: * Non-array value.
088: */
089: , VALUE /**
090: * Enum value.
091: */
092: , ENUM /**
093: * Array value.
094: */
095: , ARRAY
096: }
097:
098: private final Kind kind;
099: private TypeID type;
100: private String userCode;
101: private DesignComponent component; // HINT - maybe it should be UID
102: private PrimitiveDescriptor descriptor;
103: private Object value;
104: private List<PropertyValue> array;
105:
106: /**
107: * Create a new property value representing a user code.
108: * @param userCode the user code in target language
109: * @return the propert value
110: */
111: public static PropertyValue createUserCode(String userCode) {
112: assert userCode != null;
113:
114: PropertyValue value = new PropertyValue(Kind.USERCODE);
115: value.userCode = userCode;
116: return value;
117: }
118:
119: /**
120: * Creates a new property value representing a component reference.
121: * @param component the component
122: * @return the property value
123: */
124: public static PropertyValue createComponentReference(
125: DesignComponent component) {
126: assert component != null;
127: return component.getReferenceValue();
128: }
129:
130: static PropertyValue createComponentReferenceCore(
131: DesignComponent component) {
132: assert Debug.isFriend(DesignComponent.class, "<init>"); // NOI18N
133: assert component != null;
134: TypeID type = component.getType();
135: assert type.getKind() == TypeID.Kind.COMPONENT;
136:
137: PropertyValue val = new PropertyValue(Kind.REFERENCE);
138: val.type = type;
139: val.component = component;
140: return val;
141: }
142:
143: /**
144: * Creates a new property value representing a null.
145: * @return the property value
146: */
147: public static PropertyValue createNull() {
148: return NULL;
149: }
150:
151: /**
152: * Creates a property value representing a primitive value.
153: * @param descriptor the primitive descriptor
154: * @param type the non-array type id that is primitive
155: * @param value the object that represents the value in the design time
156: * @return the property value
157: */
158: public static PropertyValue createValue(
159: PrimitiveDescriptor descriptor, TypeID type, Object value) {
160: assert descriptor != null;
161: assert type != null;
162: assert type.getKind() == TypeID.Kind.PRIMITIVE;
163: assert type.getDimension() == 0;
164: assert value != null;
165: assert descriptor.isValidInstance(value);
166:
167: PropertyValue val = new PropertyValue(Kind.VALUE);
168:
169: val.descriptor = descriptor;
170: val.type = type;
171: val.value = value;
172: return val;
173: }
174:
175: /**
176: * Creates a property value representing a primitive or an enum value.
177: * @param projectType the project type
178: * @param type the non-array type id that is primitive
179: * @param value the object that represents the value in the design time
180: * @return the property value
181: */
182: public static PropertyValue createValue(String projectType,
183: TypeID type, Object value) {
184: if (type.getKind() == TypeID.Kind.ENUM) {
185: return createEnumValue(EnumDescriptorFactoryRegistry
186: .getDescriptor(projectType, type), type, value);
187: } else {
188: return createValue(PrimitiveDescriptorFactoryRegistry
189: .getDescriptor(projectType, type), type, value);
190: }
191: }
192:
193: /**
194: * Creates a property value representing a enum value.
195: * @param descriptor the enum descriptor
196: * @param type the non-array type id that is enum
197: * @param value the object that represents the value in the design time
198: * @return the property value
199: */
200: public static PropertyValue createEnumValue(
201: EnumDescriptor descriptor, TypeID type, Object value) {
202: assert descriptor != null;
203: assert type != null;
204: assert type.getKind() == TypeID.Kind.ENUM;
205: assert type.getDimension() == 0;
206: assert value != null;
207: assert descriptor.isValidInstance(value);
208:
209: PropertyValue val = new PropertyValue(Kind.ENUM);
210: assert descriptor.values().contains(value);
211:
212: val.descriptor = descriptor;
213: val.type = type;
214: val.value = value;
215: return val;
216: }
217:
218: /**
219: * Creates a property value representing an array value.
220: * @param componentType the type id of held objects (not array type id)
221: * @param array the list of property values that represent the array of values in the design time
222: * @return the property value
223: */
224: public static PropertyValue createArray(TypeID componentType,
225: List<PropertyValue> array) {
226: assert componentType != null;
227: assert array != null;
228: assert createArrayAssert(array, componentType);
229:
230: PropertyValue val = new PropertyValue(Kind.ARRAY);
231: val.type = componentType.getArrayType();
232: val.array = Collections
233: .unmodifiableList(new ArrayList<PropertyValue>(array));
234: return val;
235: }
236:
237: private static boolean createArrayAssert(List<PropertyValue> array,
238: TypeID componentType) {
239: for (PropertyValue propertyValue : array) {
240: assert propertyValue != null;
241: assert propertyValue.isCompatible(componentType);
242: }
243: return true;
244: }
245:
246: /**
247: * Create a property value representng an empty array value.
248: * @param componentType the type id of held objects (not array type id)
249: * @return the property value
250: */
251: public static PropertyValue createEmptyArray(TypeID componentType) {
252: return createArray(componentType, EMPTY_ARRAY);
253: }
254:
255: private PropertyValue(Kind valueType) {
256: this .kind = valueType;
257: }
258:
259: /**
260: * Returns a kind.
261: * @return the kind
262: */
263: public Kind getKind() {
264: return kind;
265: }
266:
267: /**
268: * Returns a type id.
269: * @return the type id
270: */
271: public TypeID getType() {
272: // assert kind != Kind.USERCODE && kind != Kind.NULL;
273: return type;
274: }
275:
276: /**
277: * Returns a user code.
278: * @return the user code
279: */
280: public String getUserCode() {
281: // assert kind == Kind.USERCODE;
282: return userCode;
283: }
284:
285: /**
286: * Returns a component.
287: * @return the component
288: */
289: public DesignComponent getComponent() {
290: // assert kind == Kind.REFERENCE;
291: return component;
292: }
293:
294: /**
295: * Returns primitive or enum value (not component, not array).
296: * @return the value
297: */
298: public Object getPrimitiveValue() {
299: // assert kind == Kind.VALUE || kind == Kind.ENUM;
300: return value;
301: }
302:
303: /**
304: * Returns a list of property values.
305: * @return the list
306: */
307: public List<PropertyValue> getArray() {
308: // assert kind == Kind.ARRAY;
309: return array;
310: }
311:
312: void collectAllComponentReferences(
313: Collection<DesignComponent> references) {
314: assert Debug.isFriend(DesignComponent.class, "writeProperty")
315: || Debug.isFriend(DesignComponent.class,
316: "setComponentDescriptor")
317: || Debug.isFriend(PropertyValue.class,
318: "collectAllComponentReferences")
319: || Debug.isFriend(Debug.class,
320: "collectAllComponentReferences"); // NOI18N
321: switch (kind) {
322: case USERCODE:
323: case NULL:
324: case VALUE:
325: case ENUM:
326: return;
327: case REFERENCE:
328: references.add(component);
329: return;
330: case ARRAY:
331: if (type.getKind() == TypeID.Kind.COMPONENT) {
332: for (PropertyValue propertyValue : array) {
333: propertyValue
334: .collectAllComponentReferences(references);
335: }
336: }
337: return;
338: }
339: Debug.error("Invalid state", this ); // NOI18N
340: }
341:
342: /**
343: * Returns whether the required type id is compatible with this type id. Compatible means the required type equals
344: * to the one stored inside this property value. In Reference case, it checks its compatibility using descriptor registry.
345: * @param requiredType the required type
346: * @return true if compatible
347: */
348: public boolean isCompatible(TypeID requiredType) {
349: if (type == null) {
350: return true;
351: }
352: if (kind == Kind.REFERENCE) {
353: return component.getDocument().getDescriptorRegistry()
354: .isInHierarchy(requiredType, component.getType());
355: }
356: return type.equals(requiredType);
357: }
358:
359: /**
360: * Returns whether the property value is compatible with a specified property descriptor.
361: * @param propertyDescriptor the property descriptor
362: * @return true, if the property value is allowed for the property descriptor
363: */
364: public boolean isCompatible(PropertyDescriptor propertyDescriptor) {
365: if (propertyDescriptor == null) {
366: return false;
367: }
368: if (kind == Kind.NULL) {
369: if (!propertyDescriptor.isAllowNull()) {
370: PropertyValue defaultValue = propertyDescriptor
371: .getDefaultValue();
372: if (defaultValue == null
373: || defaultValue.getKind() != PropertyValue.Kind.NULL) {
374: // HACK for PropertyDescriptor for disallowing to use null values
375: return false;
376: }
377: }
378: } else if (kind == Kind.USERCODE) {
379: if (!propertyDescriptor.isAllowUserCode()) {
380: return false;
381: }
382: }
383: return isCompatible(propertyDescriptor.getType());
384: }
385:
386: /**
387: * Returns an encoded string of the property value.
388: * @return the encoded string
389: */
390: public String serialize() {
391: switch (kind) {
392: case USERCODE:
393: return new StringBuilder().append(USER_CODE_ID).append(
394: userCode).toString();
395: case NULL:
396: return Character.toString(NULL_ID);
397: case REFERENCE:
398: return new StringBuilder().append(REFERENCE_ID).append(
399: component.getComponentID()).toString();
400: case VALUE: {
401: return new StringBuilder().append(VALUE_ID).append(
402: descriptor.serialize(value)).toString();
403: }
404: case ENUM: {
405: return new StringBuilder().append(ENUM_ID).append(
406: descriptor.serialize(value)).toString();
407: }
408: case ARRAY: {
409: StringBuilder sb = new StringBuilder();
410: sb.append(ARRAY_ID).append(array.size()).append(
411: ARRAY_SIZE_SEPARATOR);
412: for (PropertyValue propertyValue : array) {
413: String serialized = propertyValue.serialize();
414: sb.append(serialized.length()).append(
415: ENCODED_LENGTH_SEPARATOR).append(serialized);
416: }
417: return sb.toString();
418: }
419: }
420:
421: throw Debug.error("Cannot serialize property value", type); // NOI18N
422: }
423:
424: /**
425: * Creates a property value from the encoded string that represents the value.
426: * @param serialized the encoded string
427: * @param document the document for resolving component references
428: * @param type the type id of the encoded string
429: * @return the property value
430: */
431: public static PropertyValue deserialize(String serialized,
432: DesignDocument document, TypeID type) throws Exception {
433: assert serialized != null && serialized.length() >= 1;
434: assert document != null && type != null;
435:
436: switch (serialized.charAt(0)) {
437: case USER_CODE_ID:
438: if (serialized.substring(1) == null) {
439: throw new IllegalArgumentException();
440: }
441: return createUserCode(serialized.substring(1));
442: case NULL_ID:
443: return createNull();
444: case REFERENCE_ID: {
445: int componentID;
446: componentID = Integer.parseInt(serialized.substring(1));
447: if (document.getComponentByUID(componentID) == null) {
448: throw new IllegalArgumentException(
449: "No component for given serilezed value"); // NOI18N
450: }
451: return createComponentReference(document
452: .getComponentByUID(componentID));
453: }
454: case VALUE_ID: {
455: PrimitiveDescriptor descriptor = PrimitiveDescriptorFactoryRegistry
456: .getDescriptor(document.getDocumentInterface()
457: .getProjectType(), type);
458: if (descriptor == null) {
459: throw new IllegalArgumentException();
460: }
461: return createValue(descriptor, type, descriptor
462: .deserialize(serialized.substring(1)));
463: }
464: case ENUM_ID: {
465: EnumDescriptor descriptor = EnumDescriptorFactoryRegistry
466: .getDescriptor(document.getDocumentInterface()
467: .getProjectType(), type);
468: if (descriptor == null) {
469: throw new IllegalArgumentException();
470: }
471: return createEnumValue(descriptor, type, descriptor
472: .deserialize(serialized.substring(1)));
473: }
474: case ARRAY_ID: {
475: int pos = 1;
476: int arrayLengthIndex = serialized.indexOf(
477: ARRAY_SIZE_SEPARATOR, pos);
478: assert arrayLengthIndex > pos;
479:
480: int arrayLength;
481: arrayLength = Integer.parseInt(serialized.substring(pos,
482: arrayLengthIndex));
483: assert arrayLength >= 0;
484: pos = arrayLengthIndex + 1;
485:
486: TypeID componentType = type.getComponentType();
487: ArrayList<PropertyValue> propertyValues = new ArrayList<PropertyValue>();
488:
489: for (int a = 0; a < arrayLength; a++) {
490: int index = serialized.indexOf(
491: ENCODED_LENGTH_SEPARATOR, pos);
492: assert index > pos;
493:
494: int elementLength;
495: elementLength = Integer.parseInt(serialized.substring(
496: pos, index));
497: assert elementLength >= 0;
498: pos = index + 1;
499: propertyValues.add(deserialize(serialized.substring(
500: pos, pos + elementLength), document,
501: componentType));
502: pos += elementLength;
503: }
504:
505: return createArray(componentType, propertyValues);
506: }
507: }
508: Debug.warning("Cannot deserialize property value", type,
509: serialized); // NOI18N
510: throw new IllegalArgumentException(
511: "Cannot deserialize property value " + type + " "
512: + serialized); //NOI18N
513: }
514:
515: /**
516: * Returns the encoded string.
517: * @return the encoded string
518: */
519: @Override
520: public String toString() {
521: return serialize();
522: }
523: }
|