001/** A class representing the type of a UnionToken.
002
003 Copyright (c) 2006-2015 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026 */
027package ptolemy.data.type;
028
029import java.util.HashMap;
030import java.util.HashSet;
031import java.util.Iterator;
032import java.util.Map;
033import java.util.Set;
034
035import ptolemy.data.Token;
036import ptolemy.data.UnionToken;
037import ptolemy.graph.CPO;
038import ptolemy.graph.InequalityTerm;
039import ptolemy.kernel.util.IllegalActionException;
040import ptolemy.kernel.util.InternalErrorException;
041
042///////////////////////////////////////////////////////////////////
043//// UnionType
044
045/**
046 A class representing the type of a UnionToken.
047 To set the type of a typeable object (such as a port or parameter)
048 to a union with particular fields, create an instance of this
049 class and call setTypeEquals() with that instance as an argument.
050 <p>
051 The depth subtyping is similar to that of <code>RecordTypes</code>. However,
052 the width subtyping for <code>UnionType</code> is opposite compared to
053 <code>RecordType</code> i.e., a <code>UnionType</code> with more fields
054 is a supertype of a <code>UnionType</code> with a subset of the fields.
055 For example, {|x = double, y = int} is a supertype of {|x = double}.
056
057 @author Yuhong Xiong, Elaine Cheong and Steve Neuendorffer
058 @version $Id$
059 @since Ptolemy II 5.2
060 @Pt.ProposedRating Red (yuhongx)
061 @Pt.AcceptedRating Red (cxh)
062 */
063public class UnionType extends AssociativeType implements Cloneable {
064    /** Construct a new UnionType with the specified labels and types.
065     *  To leave the types of some fields undeclared, use BaseType.UNKNOWN.
066     *  The labels and the types are specified in two arrays. These two
067     *  arrays must have the same length, and their elements have one to
068     *  one correspondence. That is, the i'th entry in the types array is
069     *  the type for the i'th label in the labels array. To construct the
070     *  empty record type, set the length of the argument arrays to 0.
071     *  @param labels An array of String.
072     *  @param types An array of Type.
073     *  @exception IllegalArgumentException If the two arrays do not have
074     *   the same size.
075     *  @exception NullPointerException If one of the arguments is null.
076     */
077    public UnionType(String[] labels, Type[] types) {
078        if (labels.length != types.length) {
079            throw new IllegalArgumentException("UnionType: the labels "
080                    + "and types arrays do not have the same size.");
081        }
082
083        for (int i = 0; i < labels.length; i++) {
084            FieldType fieldType = new FieldType(types[i]);
085            _fields.put(labels[i], fieldType);
086        }
087    }
088
089    ///////////////////////////////////////////////////////////////////
090    ////                         public methods                    ////
091
092    /** Return a deep copy of this UnionType if it is a variable, or
093     *  itself if it is a constant.
094     *  @return A UnionType.
095     */
096    @Override
097    public Object clone() {
098        if (isConstant()) {
099            return this;
100        }
101
102        // empty union is a constant, so this record type is not empty.
103        // construct the labels and declared types array
104        Object[] labelsObj = _fields.keySet().toArray();
105        String[] labels = new String[labelsObj.length];
106        Type[] types = new Type[labelsObj.length];
107
108        for (int i = 0; i < labels.length; i++) {
109            labels[i] = (String) labelsObj[i];
110
111            FieldType fieldType = _fields.get(labels[i]);
112            types[i] = fieldType._declaredType;
113        }
114
115        UnionType newObj = new UnionType(labels, types);
116
117        try {
118            newObj.updateType(this);
119        } catch (IllegalActionException ex) {
120            throw new InternalErrorException("UnionType.clone: Cannot "
121                    + "update new instance. " + ex.getMessage());
122        }
123
124        return newObj;
125    }
126
127    /** Convert the argument token into a UnionToken having this
128     *  type, if lossless conversion can be done.  The argument must
129     *  be a UnionToken, and its type must be a subtype of this
130     *  record type.  The argument token must have at least the fields
131     *  of this type.  Extra fields in the argument token that are not
132     *  in this type are removed.
133     *  @param token A token.
134     *  @return An UnionToken.
135     *  @exception IllegalActionException If lossless conversion
136     *   cannot be done.
137     */
138    @Override
139    public Token convert(Token token) throws IllegalActionException {
140        if (!isCompatible(token.getType())) {
141            throw new IllegalArgumentException(Token
142                    .notSupportedConversionMessage(token, this.toString()));
143        }
144
145        UnionToken unionToken = (UnionToken) token;
146
147        // The converted token has the same label as this one.
148        String label = unionToken.label();
149        Type newType = get(label);
150        Token newValue = newType.convert(unionToken.value());
151        return new UnionToken(label, newValue);
152    }
153
154    /** Return the depth of a union type. The depth of a
155     *  union type is the number of times it
156     *  contains other structured types.
157     *  @return the depth of a union type.
158     */
159    @Override
160    public int depth() {
161        Object[] labelsObj = _fields.keySet().toArray();
162        String[] labels = new String[labelsObj.length];
163        int[] depth = new int[labelsObj.length];
164        int maxDepth = 1;
165        for (int i = 0; i < labels.length; i++) {
166            labels[i] = (String) labelsObj[i];
167            Type fieldType = get(labels[i]);
168            depth[i] = 1;
169            if (fieldType instanceof StructuredType) {
170                depth[i] += ((StructuredType) fieldType).depth();
171            }
172            if (depth[i] > maxDepth) {
173                maxDepth = depth[i];
174            }
175        }
176        return maxDepth;
177    }
178
179    /** Determine if the argument represents the same UnionType as this
180     *  object.  Two record types are equal if they have the same field names
181     *  and the type of each field is the same.
182     *  @param object Another object.
183     *  @return True if the argument represents the same UnionType as
184     *  this object.
185     */
186    @Override
187    public boolean equals(Object object) {
188        if (!(object instanceof UnionType)) {
189            return false;
190        }
191
192        UnionType unionType = (UnionType) object;
193
194        // Check that the label sets are equal
195        Set myLabelSet = _fields.keySet();
196        Set argLabelSet = unionType._fields.keySet();
197
198        if (!myLabelSet.equals(argLabelSet)) {
199            return false;
200        }
201
202        Iterator fieldNames = myLabelSet.iterator();
203
204        while (fieldNames.hasNext()) {
205            String label = (String) fieldNames.next();
206            Type myType = this.get(label);
207            Type argType = unionType.get(label);
208
209            if (!myType.equals(argType)) {
210                return false;
211            }
212        }
213
214        return true;
215    }
216
217    /** Return the type of the specified label. If this type does not
218     *  contain the specified label, return null.
219     *  @param label The specified label.
220     *  @return a Type.
221     */
222    @Override
223    public Type get(String label) {
224        FieldType fieldType = _fields.get(label);
225
226        if (fieldType == null) {
227            return null;
228        }
229
230        return fieldType._resolvedType;
231    }
232
233    /** Return the class for tokens that this type represents.
234     *  @return The class for tokens that this type represents.
235     */
236    @Override
237    public Class getTokenClass() {
238        return UnionToken.class;
239    }
240
241    /** Return the InequalityTerm representing the type of the specified
242     *  label.
243     *  @param label The specified label.
244     *  @return An InequalityTerm.
245     *  @see ptolemy.graph.InequalityTerm
246     */
247    public InequalityTerm getTypeTerm(String label) {
248        return _fields.get(label);
249    }
250
251    /** Return a hash code value for this object.
252     */
253    @Override
254    public int hashCode() {
255        return _fields.keySet().hashCode() + 2917;
256    }
257
258    /** Set the elements that have declared type BaseType.UNKNOWN (the leaf
259     *  type variable) to the specified type.
260     *  @param type the type to set the leaf type variable to.
261     */
262    @Override
263    public void initialize(Type type) {
264        try {
265            for (Map.Entry<String, FieldType> fields : _fields.entrySet()) {
266                FieldType fieldType = fields.getValue();
267                if (fieldType.isSettable()) {
268                    fieldType.initialize(type);
269                }
270            }
271        } catch (IllegalActionException iae) {
272            throw new InternalErrorException("UnionType.initialize: Cannot "
273                    + "initialize the element type to " + type + " "
274                    + iae.getMessage());
275        }
276    }
277
278    /** Test if this type corresponds to an abstract token
279     *  class. A UnionType is abstract if any field is abstract
280     *  @return True if this type is instantiable.
281     */
282    @Override
283    public boolean isAbstract() {
284        // Loop through all of the fields.
285        Iterator fieldNames = _fields.keySet().iterator();
286
287        while (fieldNames.hasNext()) {
288            String label = (String) fieldNames.next();
289            Type type = this.get(label);
290
291            // Return false if the field is not instantiable.
292            if (type.isAbstract()) {
293                return true;
294            }
295        }
296
297        return false;
298    }
299
300    /** Test if the argument type is compatible with this type.  The
301     *  given type will be compatible with this type if it is
302     *  BaseType.UNKNOWN, or a UnionType that contains at most as
303     *  many fields.
304     *  @param type An instance of Type.
305     *  @return True if the argument is compatible with this type.
306     */
307    @Override
308    public boolean isCompatible(Type type) {
309        if (type.equals(BaseType.UNKNOWN)) {
310            return true;
311        }
312
313        if (!(type instanceof UnionType)) {
314            return false;
315        }
316
317        UnionType argumentUnionType = (UnionType) type;
318
319        // Loop through all of the fields of argument type...
320        Iterator iterator = argumentUnionType.labelSet().iterator();
321
322        while (iterator.hasNext()) {
323            String label = (String) iterator.next();
324
325            // The given type cannot be losslessly converted to this type
326            // if it contains extra fields
327            Type myFieldType = this.get(label);
328
329            if (myFieldType == null) {
330                // This token does not contain a label in the argument
331                return false;
332            }
333
334            // The given type cannot be losslessly converted to this type
335            // if the individual fields are not compatible.
336            Type argumentFieldType = argumentUnionType.get(label);
337
338            if (!myFieldType.isCompatible(argumentFieldType)) {
339                return false;
340            }
341        }
342
343        return true;
344    }
345
346    /** Test if this UnionType is a constant. A UnionType is a constant if
347     *  the declared type of all of its fields are constant.
348     *  @return True if this type is a constant.
349     */
350    @Override
351    public boolean isConstant() {
352        // Loop through all of the fields.
353        Iterator fieldTypes = _fields.values().iterator();
354
355        while (fieldTypes.hasNext()) {
356            FieldType fieldType = (FieldType) fieldTypes.next();
357            Type type = fieldType._declaredType;
358
359            // Return false if the field is not constant.
360            if (!type.isConstant()) {
361                return false;
362            }
363        }
364
365        return true;
366    }
367
368    /** Test if this type corresponds to an instantiable token
369     *  class. A UnionType is instantiable if all of its fields are
370     *  instantiable.
371     *  @return True if this type is instantiable.
372     */
373    @Override
374    public boolean isInstantiable() {
375        // Loop through all of the fields.
376        Iterator fieldNames = _fields.keySet().iterator();
377
378        while (fieldNames.hasNext()) {
379            String label = (String) fieldNames.next();
380            Type type = this.get(label);
381
382            // Return false if the field is not instantiable.
383            if (!type.isInstantiable()) {
384                return false;
385            }
386        }
387
388        return true;
389    }
390
391    /** Test if the specified type is a substitution instance of this
392     *  type.  One union is a substitution instance of another if they
393     *  have fields with the same names and each field of the given type is
394     *  a substitution instance of the corresponding field in this type.
395     *  @param type A Type.
396     *  @return True if the argument is a substitution instance of this type.
397     *  @see Type#isSubstitutionInstance
398     */
399    @Override
400    public boolean isSubstitutionInstance(Type type) {
401        if (!(type instanceof UnionType)) {
402            return false;
403        }
404
405        UnionType unionType = (UnionType) type;
406
407        // Check if this union type and the argument have the same
408        // label set.
409        Set myLabelSet = _fields.keySet();
410        Set argLabelSet = unionType._fields.keySet();
411
412        if (!myLabelSet.equals(argLabelSet)) {
413            return false;
414        }
415
416        // Loop over all the labels.
417        for (Map.Entry<String, FieldType> fields : _fields.entrySet()) {
418            FieldType fieldType = fields.getValue();
419
420            Type myDeclaredType = fieldType._declaredType;
421            Type argType = unionType.get(fields.getKey());
422
423            if (!myDeclaredType.isSubstitutionInstance(argType)) {
424                return false;
425            }
426        }
427
428        return true;
429    }
430
431    /** Return the labels of this record type as a Set.
432     *  @return A Set containing strings.
433     */
434    public Set labelSet() {
435        return _fields.keySet();
436    }
437
438    /** Return the string representation of this type. The format is
439     *  {|<i>label</i> = <i>type</i>, <i>label</i> = <i>type</i>, ...|}.
440     *  The record fields are listed in the lexicographical order of the
441     *  labels determined by the java.lang.String.compareTo() method.
442     *  @return A String.
443     */
444    @Override
445    public String toString() {
446        Object[] labelArray = _fields.keySet().toArray();
447
448        // Order the labels
449        int size = labelArray.length;
450
451        for (int i = 0; i < size - 1; i++) {
452            for (int j = i + 1; j < size; j++) {
453                String labeli = (String) labelArray[i];
454                String labelj = (String) labelArray[j];
455
456                if (labeli.compareTo(labelj) >= 0) {
457                    Object temp = labelArray[i];
458                    labelArray[i] = labelArray[j];
459                    labelArray[j] = temp;
460                }
461            }
462        }
463
464        // construct the string representation of this token.
465        StringBuffer results = new StringBuffer("{|");
466
467        for (int i = 0; i < size; i++) {
468            String label = (String) labelArray[i];
469            String type = this.get(label).toString();
470
471            if (i != 0) {
472                results.append(", ");
473            }
474
475            results.append(label + " = " + type);
476        }
477
478        return results.toString() + "|}";
479    }
480
481    /** Update this Type to the specified UnionType.
482     *  The specified type must be a UnionType and have the same structure
483     *  as this one.
484     *  This method will only update the component whose declared type is
485     *  BaseType.UNKNOWN, and leave the constant part of this type intact.
486     *  @param newType A StructuredType.
487     *  @exception IllegalActionException If the specified type is not a
488     *   UnionType or it does not have the same structure as this one.
489     */
490    @Override
491    public void updateType(StructuredType newType)
492            throws IllegalActionException {
493        if (this.isConstant()) {
494            if (this.equals(newType)) {
495                return;
496            }
497
498            throw new IllegalActionException("UnionType.updateType: "
499                    + "This type is a constant and the argument is not the"
500                    + " same as this type. This type: " + this.toString()
501                    + " argument: " + newType.toString());
502        }
503
504        // This type is a variable.
505        if (!this.isSubstitutionInstance(newType)) {
506            throw new IllegalActionException("UnionType.updateType: "
507                    + "Cannot update this type to the new type.");
508        }
509
510        for (Map.Entry<String, FieldType> fields : _fields.entrySet()) {
511            FieldType fieldType = fields.getValue();
512            if (fieldType.isSettable()) {
513                //Type newFieldType = ((UnionType) newType).get(label);
514                Type newFieldType = ((UnionType) newType).get(fields.getKey());
515                fieldType.setValue(newFieldType);
516            }
517        }
518    }
519
520    ///////////////////////////////////////////////////////////////////
521    ////                         protected methods                 ////
522
523    /** Compare this type with the specified type. The specified type
524     *  must be a UnionType, otherwise an exception will be thrown.
525     *
526     *  This method returns one of ptolemy.graph.CPO.LOWER,
527     *  ptolemy.graph.CPO.SAME, ptolemy.graph.CPO.HIGHER,
528     *  ptolemy.graph.CPO.INCOMPARABLE, indicating this type is lower
529     *  than, equal to, higher than, or incomparable with the
530     *  specified type in the type hierarchy, respectively.
531     *  @param type a UnionType.
532     *  @return An integer.
533     *  @exception IllegalArgumentException If the specified type is
534     *   not a UnionType.
535     */
536    @Override
537    protected int _compare(StructuredType type) {
538        if (!(type instanceof UnionType)) {
539            throw new IllegalArgumentException("UnionType._compare: "
540                    + "The argument is not a UnionType.");
541        }
542
543        if (this.equals(type)) {
544            return CPO.SAME;
545        }
546
547        if (_isLessThanOrEqualTo(this, (UnionType) type)) {
548            return CPO.LOWER;
549        }
550
551        if (_isLessThanOrEqualTo((UnionType) type, this)) {
552            return CPO.HIGHER;
553        }
554
555        return CPO.INCOMPARABLE;
556    }
557
558    /** Return a static instance of RecordType.
559     *  @return a UnionType.
560     */
561    @Override
562    protected StructuredType _getRepresentative() {
563        return _representative;
564    }
565
566    /** Return the greatest lower bound of this type with the specified
567     *  type. The specified type must be a UnionType, otherwise an
568     *  exception will be thrown.
569     *  @param type a UnionType.
570     *  @return a UnionType.
571     *  @exception IllegalArgumentException If the specified type is
572     *   not a UnionType.
573     */
574    @Override
575    protected StructuredType _greatestLowerBound(StructuredType type) {
576        if (!(type instanceof UnionType)) {
577            throw new IllegalArgumentException(
578                    "UnionType.greatestLowerBound: The argument is not a "
579                            + "UnionType.");
580        }
581
582        UnionType unionType = (UnionType) type;
583
584        // the label set of the GLB is the intersection of the two label sets.
585        Set intersectionSet = new HashSet();
586        Set myLabelSet = _fields.keySet();
587        Set argLabelSet = unionType._fields.keySet();
588
589        intersectionSet.addAll(myLabelSet);
590        intersectionSet.retainAll(argLabelSet);
591
592        // construct the GLB UnionToken
593        Object[] labelArray = intersectionSet.toArray();
594        int size = labelArray.length;
595        String[] labels = new String[size];
596        Type[] types = new Type[size];
597
598        for (int i = 0; i < size; i++) {
599            labels[i] = (String) labelArray[i];
600
601            Type type1 = this.get(labels[i]);
602            Type type2 = unionType.get(labels[i]);
603            types[i] = (Type) TypeLattice.lattice().greatestLowerBound(type1,
604                    type2);
605        }
606
607        return new UnionType(labels, types);
608    }
609
610    /** Return the least Upper bound of this type with the specified
611     *  type. The specified type must be a UnionType, otherwise an
612     *  exception will be thrown.
613     *  @param type a UnionType.
614     *  @return a UnionType.
615     *  @exception IllegalArgumentException If the specified type is
616     *   not a UnionType.
617     */
618    @Override
619    protected StructuredType _leastUpperBound(StructuredType type) {
620        if (!(type instanceof UnionType)) {
621            throw new IllegalArgumentException("UnionType.leastUpperBound: "
622                    + "The argument is not a UnionType.");
623        }
624
625        UnionType unionType = (UnionType) type;
626
627        // the label set of the LUB is the union of the two label sets.
628        Set unionSet = new HashSet();
629        Set myLabelSet = _fields.keySet();
630        Set argLabelSet = unionType._fields.keySet();
631
632        unionSet.addAll(myLabelSet);
633        unionSet.addAll(argLabelSet);
634
635        // construct the GLB UnionToken
636        Object[] labelArray = unionSet.toArray();
637        int size = labelArray.length;
638        String[] labels = new String[size];
639        Type[] types = new Type[size];
640
641        for (int i = 0; i < size; i++) {
642            labels[i] = (String) labelArray[i];
643
644            Type type1 = this.get(labels[i]);
645            Type type2 = unionType.get(labels[i]);
646
647            if (type1 == null) {
648                types[i] = type2;
649            } else if (type2 == null) {
650                types[i] = type1;
651            } else {
652                types[i] = (Type) TypeLattice.lattice().leastUpperBound(type1,
653                        type2);
654            }
655        }
656
657        return new UnionType(labels, types);
658    }
659
660    ///////////////////////////////////////////////////////////////////
661    ////                         private methods                   ////
662    // Test if the first UnionType is less than or equal to the second
663    private boolean _isLessThanOrEqualTo(UnionType t1, UnionType t2) {
664        Set labelSet1 = t1._fields.keySet();
665        Set labelSet2 = t2._fields.keySet();
666
667        if (!labelSet2.containsAll(labelSet1)) {
668            return false;
669        }
670
671        // iterate over the labels of the first type
672        Iterator iter = labelSet1.iterator();
673
674        while (iter.hasNext()) {
675            String label = (String) iter.next();
676            Type type1 = t1.get(label);
677            Type type2 = t2.get(label);
678            int result = TypeLattice.compare(type1, type2);
679
680            if (result == CPO.HIGHER || result == CPO.INCOMPARABLE) {
681                return false;
682            }
683        }
684
685        return true;
686    }
687
688    ///////////////////////////////////////////////////////////////////
689    ////                         private variables                 ////
690    // Mapping from label to field information.
691    private Map<String, FieldType> _fields = new HashMap<String, FieldType>();
692
693    // the representative in the type lattice is the empty record.
694    private static UnionType _representative = new UnionType(new String[0],
695            new Type[0]);
696
697    ///////////////////////////////////////////////////////////////////
698    ////                         inner class                       ////
699    // A class that encapsulates the declared and resolved types of a
700    // field and implements the InequalityTerm interface.
701    private class FieldType implements InequalityTerm {
702        // Construct an instance of FieldType.
703        private FieldType(Type declaredType) {
704            try {
705                _declaredType = (Type) declaredType.clone();
706                _resolvedType = _declaredType;
707            } catch (CloneNotSupportedException cnse) {
708                throw new InternalErrorException("UnionType.FieldType: "
709                        + "The specified type cannot be cloned.");
710            }
711        }
712
713        ///////////////////////////////////////////////////////////////
714        ////                   public inner methods                ////
715
716        /** Return this UnionType.
717         *  @return a UnionType.
718         */
719        @Override
720        public Object getAssociatedObject() {
721            return UnionType.this;
722        }
723
724        /** Return the resolved type.
725         *  @return a Type.
726         */
727        @Override
728        public Object getValue() {
729            return _resolvedType;
730        }
731
732        /** Return this FieldType in an array if it represents a type
733         *  variable. Otherwise, return an array of size zero.
734         *  @return An array of InequalityTerm.
735         */
736        @Override
737        public InequalityTerm[] getVariables() {
738            if (isSettable()) {
739                InequalityTerm[] variable = new InequalityTerm[1];
740                variable[0] = this;
741                return variable;
742            }
743
744            return new InequalityTerm[0];
745        }
746
747        /** Reset the variable part of the element type to the specified
748         *  type.
749         *  @param e A Type.
750         *  @exception IllegalActionException If this type is not settable,
751         *   or the argument is not a Type.
752         */
753        @Override
754        public void initialize(Object e) throws IllegalActionException {
755            if (!isSettable()) {
756                throw new IllegalActionException("UnionType$FieldType."
757                        + "initialize: The type is not settable.");
758            }
759
760            if (!(e instanceof Type)) {
761                throw new IllegalActionException("FieldType.initialize: "
762                        + "The argument is not a Type.");
763            }
764
765            if (_declaredType == BaseType.UNKNOWN) {
766                _resolvedType = (Type) e;
767            } else {
768                // this field type is a structured type.
769                ((StructuredType) _resolvedType).initialize((Type) e);
770            }
771        }
772
773        /** Test if this field type is a type variable.
774         *  @return True if this field type is a type variable.
775         */
776        @Override
777        public boolean isSettable() {
778            return !_declaredType.isConstant();
779        }
780
781        /** Check whether the current element type is acceptable.
782         *  The element type is acceptable if it represents an
783         *  instantiable object.
784         *  @return True if the element type is acceptable.
785         */
786        @Override
787        public boolean isValueAcceptable() {
788            return _resolvedType.isInstantiable();
789        }
790
791        /** Set the element type to the specified type.
792         *  @param e a Type.
793         *  @exception IllegalActionException If the specified type violates
794         *   the declared field type.
795         */
796        @Override
797        public void setValue(Object e) throws IllegalActionException {
798            if (!isSettable()) {
799                throw new IllegalActionException(
800                        "UnionType$FieldType.setValue: The type is not "
801                                + "settable.");
802            }
803
804            if (!_declaredType.isSubstitutionInstance((Type) e)) {
805                throw new IllegalActionException("FieldType.setValue: "
806                        + "Cannot update the field type of this UnionType "
807                        + "to the new type." + " Field type: "
808                        + _declaredType.toString() + ", New type: "
809                        + e.toString());
810            }
811
812            if (_declaredType == BaseType.UNKNOWN) {
813                try {
814                    _resolvedType = (Type) ((Type) e).clone();
815                } catch (CloneNotSupportedException cnse) {
816                    throw new InternalErrorException(
817                            "UnionType$FieldType.setValue: "
818                                    + "The specified type cannot be cloned.");
819                }
820            } else {
821                ((StructuredType) _resolvedType).updateType((StructuredType) e);
822            }
823        }
824
825        /** Return a string representation of this term.
826         *  @return A String.
827         */
828        @Override
829        public String toString() {
830            return "(UnionFieldType, " + getValue() + ")";
831        }
832
833        ///////////////////////////////////////////////////////////////
834        ////                  private inner variables              ////
835        private Type _declaredType = null;
836
837        private Type _resolvedType = null;
838    }
839}