001/*
002 * Copyright 2011-2013 UnboundID Corp.
003 *
004 * This program is free software; you can redistribute it and/or modify
005 * it under the terms of the GNU General Public License (GPLv2 only)
006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
007 * as published by the Free Software Foundation.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program; if not, see <http://www.gnu.org/licenses>.
016 */
017
018package com.unboundid.scim.schema;
019
020import com.unboundid.scim.data.AttributeValueResolver;
021import com.unboundid.scim.data.Entry;
022import com.unboundid.scim.sdk.InvalidResourceException;
023import com.unboundid.scim.sdk.SCIMAttribute;
024import com.unboundid.scim.sdk.SCIMAttributeValue;
025import com.unboundid.scim.sdk.SCIMConstants;
026
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.Collection;
030import java.util.LinkedHashMap;
031import java.util.List;
032import java.util.Map;
033
034import static com.unboundid.scim.sdk.StaticUtils.toLowerCase;
035
036
037
038/**
039 * This class provides methods that describe the schema for a SCIM attribute.
040 * It may be used to help read and write SCIM attributes in their external XML
041 * and JSON representation, and to convert SCIM attributes to and from LDAP
042 * attributes.
043 */
044public final class AttributeDescriptor {
045
046  /**
047   * Defines the set of well known SCIM supported datatypes.
048   */
049  public static enum DataType {
050    /**
051     * String data type.
052     */
053    STRING,
054    /**
055     * Boolean data type.
056     */
057    BOOLEAN,
058    /**
059     * Date Time data type.
060     */
061    DATETIME,
062    /**
063     * Decimal data type.
064     */
065    DECIMAL,
066    /**
067     * Integer data type.
068     */
069    INTEGER,
070    /**
071     * Binary data type.
072     */
073    BINARY,
074    /**
075     * Complex data type.
076     */
077    COMPLEX;
078
079    /**
080     * Parses a supplied data type into a SCIM defined data type.
081     * @param type The type to convert
082     *
083     * @return The DataType or null if not supported
084     */
085    public static DataType parse(final String type) {
086      try {
087        return DataType.valueOf(type.toUpperCase());
088      } catch (Exception e) {
089        return null;
090      }
091    }
092
093    /**
094     * {@inheritDoc}
095     */
096    @Override
097    public String toString() {
098      return super.toString().toLowerCase();
099    }
100  }
101
102  static final class SubAttributeDescriptorResolver
103      extends AttributeValueResolver<AttributeDescriptor>
104  {
105    private final String schema;
106
107    /**
108     * Construct a new sub attribute resolver.
109     *
110     * @param schema The schema of the parent attribute.
111     */
112    SubAttributeDescriptorResolver(final String schema) {
113      this.schema = schema;
114    }
115
116    /**
117     * {@inheritDoc}
118     */
119    @Override
120    public SCIMAttributeValue fromInstance(
121        final AttributeDescriptor attributeDescriptor,
122        final AttributeDescriptor value) throws InvalidResourceException {
123
124      final List<SCIMAttribute> attributes = new ArrayList<SCIMAttribute>(9);
125
126      attributes.add(SCIMAttribute.create(
127          attributeDescriptor.getSubAttribute("name"),
128          SCIMAttributeValue.createStringValue(value.getName())));
129
130      attributes.add(SCIMAttribute.create(
131          attributeDescriptor.getSubAttribute("type"),
132          SCIMAttributeValue.createStringValue(
133              value.getDataType().toString())));
134
135
136      attributes.add(SCIMAttribute.create(
137          attributeDescriptor.getSubAttribute("multiValued"),
138          SCIMAttributeValue.createBooleanValue(value.isMultiValued())));
139
140      if (value.isMultiValued())
141      {
142        attributes.add(SCIMAttribute.create(
143          attributeDescriptor.getSubAttribute("multiValuedAttributeChildName"),
144          SCIMAttributeValue.createStringValue(
145                  value.getMultiValuedChildName())));
146      }
147
148      attributes.add(SCIMAttribute.create(
149          attributeDescriptor.getSubAttribute("description"),
150          SCIMAttributeValue.createStringValue(value.getDescription())));
151
152      attributes.add(SCIMAttribute.create(
153          attributeDescriptor.getSubAttribute("readOnly"),
154          SCIMAttributeValue.createBooleanValue(value.isReadOnly())));
155
156      attributes.add(SCIMAttribute.create(
157          attributeDescriptor.getSubAttribute("required"),
158          SCIMAttributeValue.createBooleanValue(value.isRequired())));
159
160      attributes.add(SCIMAttribute.create(
161          attributeDescriptor.getSubAttribute("caseExact"),
162          SCIMAttributeValue.createBooleanValue(value.isCaseExact())));
163
164      if(value.getCanonicalValues() != null)
165      {
166        final AttributeDescriptor canonicalValuesAttributeDescriptor =
167            attributeDescriptor.getSubAttribute("canonicalValues");
168        final SCIMAttributeValue[] canonicalValues =
169            new SCIMAttributeValue[value.getCanonicalValues().size()];
170        int i = 0;
171        for(Entry<String> canonicalValue : value.getCanonicalValues())
172        {
173          canonicalValues[i++] = Entry.STRINGS_RESOLVER.fromInstance(
174              canonicalValuesAttributeDescriptor, canonicalValue);
175        }
176        attributes.add(SCIMAttribute.create(
177            canonicalValuesAttributeDescriptor, canonicalValues));
178      }
179
180      return SCIMAttributeValue.createComplexValue(attributes);
181    }
182
183    /**
184     * {@inheritDoc}
185     */
186    @Override
187    public AttributeDescriptor toInstance(final SCIMAttributeValue value) {
188
189      DataType dataType = DataType.parse(value.getSubAttributeValue(
190              "type", AttributeValueResolver.STRING_RESOLVER));
191
192      return new AttributeDescriptor(
193
194          value.getSubAttributeValue(
195             "name", AttributeValueResolver.STRING_RESOLVER),
196
197          dataType,
198
199          value.getSubAttributeValue(
200              "multiValued", AttributeValueResolver.BOOLEAN_RESOLVER),
201
202          value.getSubAttributeValue(
203              "multiValuedAttributeChildName",
204                  AttributeValueResolver.STRING_RESOLVER),
205
206          value.getSubAttributeValue(
207              "description", AttributeValueResolver.STRING_RESOLVER),
208
209          schema,
210
211          value.getSubAttributeValue(
212              "readOnly", AttributeValueResolver.BOOLEAN_RESOLVER),
213
214          value.getSubAttributeValue(
215              "required", AttributeValueResolver.BOOLEAN_RESOLVER),
216
217          value.getSubAttributeValue(
218              "caseExact", AttributeValueResolver.BOOLEAN_RESOLVER),
219
220          value.getSubAttributeValues(
221              "canonicalValues", Entry.STRINGS_RESOLVER),
222
223          Arrays.asList(AttributeDescriptor.createSubAttribute(
224               "value", dataType, "The attribute's significant value",
225                SCIMConstants.SCHEMA_URI_CORE, false, true, false)));
226    }
227  }
228
229  /**
230   * The <code>AttributeValueResolver</code> that resolves SCIM attribute values
231   * to/from <code>AttributeDescriptor</code> instances.
232   */
233  public static final AttributeValueResolver<AttributeDescriptor>
234      ATTRIBUTE_DESCRIPTOR_RESOLVER = new AttributeDescriptorResolver(false);
235
236  static final class AttributeDescriptorResolver extends
237      AttributeValueResolver<AttributeDescriptor>
238  {
239    private final boolean allowNesting;
240
241    /**
242     * Create a new AttributeDescriptorResolver.
243     *
244     * @param allowNesting <code>true</code> to allow nesting of complex
245     *                     types or <code>false</code> otherwise.
246     */
247    AttributeDescriptorResolver(final boolean allowNesting) {
248      this.allowNesting = allowNesting;
249    }
250
251    /**
252     * {@inheritDoc}
253     */
254    @Override
255    public AttributeDescriptor toInstance(final SCIMAttributeValue value) {
256      String schemaValue = value.getSubAttributeValue(
257          "schema", AttributeValueResolver.STRING_RESOLVER);
258
259      boolean multiValued = value.getSubAttributeValue(
260          "multiValued", AttributeValueResolver.BOOLEAN_RESOLVER);
261
262      String multiValuedChildName = null;
263      if (multiValued)
264      {
265        multiValuedChildName = value.getSubAttributeValue(
266            "multiValuedAttributeChildName",
267            AttributeValueResolver.STRING_RESOLVER);
268      }
269
270      return new AttributeDescriptor(
271
272          value.getSubAttributeValue(
273              "name", AttributeValueResolver.STRING_RESOLVER),
274
275          DataType.parse(value.getSubAttributeValue(
276              "type", AttributeValueResolver.STRING_RESOLVER)),
277
278          multiValued, multiValuedChildName,
279
280          value.getSubAttributeValue(
281              "description", AttributeValueResolver.STRING_RESOLVER),
282
283          schemaValue,
284
285          value.getSubAttributeValue(
286              "readOnly", AttributeValueResolver.BOOLEAN_RESOLVER),
287
288          value.getSubAttributeValue(
289              "required", AttributeValueResolver.BOOLEAN_RESOLVER),
290
291          value.getSubAttributeValue(
292              "caseExact", AttributeValueResolver.BOOLEAN_RESOLVER),
293
294          null,
295
296          value.getSubAttributeValues(
297              allowNesting ? "attributes" : "subAttributes",
298              allowNesting ? this :
299                  new SubAttributeDescriptorResolver(schemaValue)));
300    }
301
302    /**
303     * {@inheritDoc}
304     */
305    @Override
306    public SCIMAttributeValue fromInstance(
307        final AttributeDescriptor attributeDescriptor,
308        final AttributeDescriptor value) throws InvalidResourceException {
309
310      final List<SCIMAttribute> attributes =
311          new ArrayList<SCIMAttribute>(10);
312
313      attributes.add(SCIMAttribute.create(
314          attributeDescriptor.getSubAttribute("name"),
315          SCIMAttributeValue.createStringValue(value.getName())));
316
317      attributes.add(SCIMAttribute.create(
318          attributeDescriptor.getSubAttribute("type"),
319          SCIMAttributeValue.createStringValue(
320              value.getDataType().toString())));
321
322      attributes.add(SCIMAttribute.create(
323          attributeDescriptor.getSubAttribute("multiValued"),
324          SCIMAttributeValue.createBooleanValue(value.isMultiValued())));
325
326      if(value.isMultiValued())
327      {
328        attributes.add(SCIMAttribute.create(
329          attributeDescriptor.getSubAttribute("multiValuedAttributeChildName"),
330          SCIMAttributeValue.createStringValue(
331              value.getMultiValuedChildName())));
332      }
333
334      attributes.add(SCIMAttribute.create(
335          attributeDescriptor.getSubAttribute("description"),
336          SCIMAttributeValue.createStringValue(value.getDescription())));
337
338      attributes.add(SCIMAttribute.create(
339          attributeDescriptor.getSubAttribute("schema"),
340          SCIMAttributeValue.createStringValue(value.getSchema())));
341
342      attributes.add(SCIMAttribute.create(
343          attributeDescriptor.getSubAttribute("readOnly"),
344          SCIMAttributeValue.createBooleanValue(value.isReadOnly())));
345
346      attributes.add(SCIMAttribute.create(
347          attributeDescriptor.getSubAttribute("required"),
348          SCIMAttributeValue.createBooleanValue(value.isRequired())));
349
350      attributes.add(SCIMAttribute.create(
351          attributeDescriptor.getSubAttribute("caseExact"),
352          SCIMAttributeValue.createBooleanValue(value.isCaseExact())));
353
354      if(value.getSubAttributes() != null)
355      {
356        final AttributeDescriptor subAttributesDescriptor =
357            allowNesting ? attributeDescriptor :
358            attributeDescriptor.getSubAttribute("subAttributes");
359        final SCIMAttributeValue[] subAttributeValues =
360            new SCIMAttributeValue[value.getSubAttributes().size()];
361        int i = 0;
362        for(AttributeDescriptor subAttribute : value.getSubAttributes())
363        {
364          subAttributeValues[i++] = (allowNesting ? this :
365              new SubAttributeDescriptorResolver(value.getSchema())).
366              fromInstance(subAttributesDescriptor, subAttribute);
367        }
368        attributes.add(SCIMAttribute.create(
369            subAttributesDescriptor, subAttributeValues));
370      }
371
372      return SCIMAttributeValue.createComplexValue(attributes);
373    }
374  }
375
376  private final String schema;
377
378  private final String name;
379
380  private final String description;
381
382  private final boolean readOnly;
383
384  private final boolean required;
385
386  private final boolean multiValued;
387
388  private final String multiValuedChildName;
389
390  private final boolean caseExact;
391
392  private final DataType dataType;
393
394  private final Map<String, AttributeDescriptor> subAttributes;
395
396  private final Collection<Entry<String>> canonicalValues;
397
398  /**
399   * Construct a new AttributeDescriptor instance with the provided info.
400   *
401   * @param name                 The attribute's name.
402   * @param dataType             The attribute's data type.
403   * @param multiValued          Whether the attribute is multiValued.
404   * @param multiValuedChildName The child XML element name for multi-valued
405   *                             attributes.
406   * @param description          The attribute's human readable description.
407   * @param schema               The attribute's associated schema.
408   * @param readOnly             Whether the attribute is mutable.
409   * @param required             Whether the attribute is required.
410   * @param caseExact            Whether the string attribute is case sensitive.
411   * @param canonicalValues      A list of canonical type values.
412   * @param subAttributes        A list specifying the contained attributes.
413   */
414  private AttributeDescriptor(final String name, final DataType dataType,
415                              final boolean multiValued,
416                              final String multiValuedChildName,
417                              final String description, final String schema,
418                              final boolean readOnly, final boolean required,
419                              final boolean caseExact,
420                              final Collection<Entry<String>> canonicalValues,
421                            final Collection<AttributeDescriptor> subAttributes)
422  {
423    this.name = name;
424    this.dataType = dataType;
425    this.multiValued = multiValued;
426    this.multiValuedChildName = multiValuedChildName;
427    this.description = description;
428    this.schema = schema;
429    this.readOnly = readOnly;
430    this.required = required;
431    this.caseExact = caseExact;
432
433    if(canonicalValues != null && !canonicalValues.isEmpty())
434    {
435      this.canonicalValues = canonicalValues;
436    }
437    else
438    {
439      this.canonicalValues = null;
440    }
441
442    if(subAttributes != null && !subAttributes.isEmpty())
443    {
444      this.subAttributes =
445          new LinkedHashMap<String, AttributeDescriptor>(subAttributes.size());
446      for(AttributeDescriptor attributeDescriptor : subAttributes)
447      {
448        this.subAttributes.put(toLowerCase(
449            attributeDescriptor.getName()),
450            attributeDescriptor);
451      }
452    }
453    else
454    {
455      this.subAttributes = null;
456    }
457  }
458
459  /**
460   * The URI for the schema that defines the SCIM attribute.
461   *
462   * @return The URI for the schema that defines the SCIM attribute.
463   *         It is never {@code null}.
464   */
465  public String getSchema() {
466    return schema;
467  }
468
469  /**
470   * The attribute name to be used in any external representation of the SCIM
471   * attribute.
472   *
473   * @return The attribute name to be used in any external representation of
474   *         the SCIM attribute. It is never {@code null}.
475   */
476  public String getName() {
477    return name;
478  }
479
480  /**
481   * Indicates whether the attribute is a multi-valued attribute.
482   *
483   * @return {@code true} if the attribute is multi-valued.
484   */
485  public boolean isMultiValued() {
486    return multiValued;
487  }
488
489  /**
490   * The child XML element name for multi-valued attributes; e.g., the
491   * 'emails' attribute value is 'email', 'phoneNumbers', is 'phoneNumber'.
492   *
493   * @return  The child XML element name or {@code null} if this attribute
494   *          is not multi-valued.
495   */
496  public String getMultiValuedChildName() {
497    return multiValuedChildName;
498  }
499
500  /**
501   * Retrieves the set of descriptors for subordinate attributes of a complex
502   * attribute.
503   *
504   * @return The set of descriptors for subordinate attributes of a complex
505   *         attribute, or {@code null} if the attribute is not a complex
506   *         attribute.
507   */
508  public Collection<AttributeDescriptor> getSubAttributes()
509  {
510    return subAttributes == null ? null : subAttributes.values();
511  }
512
513  /**
514   * Retrieves the attribute descriptor for a specified subordinate attribute
515   * of a complex attribute.
516   *
517   * @param externalName The external name of the subordinate attribute for
518   *                     which a descriptor is required.
519   * @return The attribute descriptor for the specified subordinate attribute.
520   * @throws InvalidResourceException if there is no such attribute.
521   */
522  public AttributeDescriptor getSubAttribute(final String externalName)
523      throws InvalidResourceException
524  {
525    // TODO: Should we have a strict and non strict mode?
526    AttributeDescriptor subAttribute =
527        subAttributes == null ? null :
528        subAttributes.get(toLowerCase(externalName));
529    if(subAttribute == null)
530    {
531      throw new InvalidResourceException("Sub-attribute " + externalName +
532          " is not defined for attribute " + schema +
533        SCIMConstants.SEPARATOR_CHAR_QUALIFIED_ATTRIBUTE + name);
534    }
535    return subAttribute;
536  }
537
538  /**
539   * Retrieves a list of canonical type values.
540   *
541   * @return A list of canonical type values or <code>null</code> if the
542   *         attribute is not a multi-valued attribute or if they are not
543   *         specified.
544   */
545  public Collection<Entry<String>> getCanonicalValues()
546  {
547    return canonicalValues;
548  }
549
550  /**
551   * Retrieve the data type for this attribute.
552   *
553   * @return  The data type for this attribute, or {@code null} if the attribute
554   *          is not a simple attribute.
555   */
556  public DataType getDataType()
557  {
558    return dataType;
559  }
560
561  /**
562   * Retrieves this attribute's human readable description.
563   *
564   * @return This attribute's human redable description.
565   */
566  public String getDescription()
567  {
568    return description;
569  }
570
571  /**
572   * Specifies if this attribute is mutable.
573   *
574   * @return <code>false</code> if this attribute is mutable or
575   *         <code>true</code> otherwise.
576   */
577  public boolean isReadOnly()
578  {
579    return readOnly;
580  }
581
582  /**
583   * Specifies if this attribute is required.
584   *
585   * @return <code>true</code> if this attribute is required for
586   *         <code>false</code> otherwise.
587   */
588  public boolean isRequired()
589  {
590    return required;
591  }
592
593  /**
594   * Specifies if the string attribute is case sensitive.
595   *
596   * @return <code>true</code> if this attribute is case sensitive or
597   *         <code>false</code> otherwise.
598   */
599  public boolean isCaseExact()
600  {
601    return caseExact;
602  }
603
604  @Override
605  public String toString()
606  {
607    return "AttributeDescriptor{" +
608        "schema='" + getSchema() + '\'' +
609        ", name='" + getName() + '\'' +
610        ", description='" + getDescription() + '\'' +
611        ", multiValued=" + isMultiValued() +
612        ", dataType=" + getDataType() +
613        ", isRequired=" + isRequired() +
614        ", isReadOnly=" + isReadOnly() +
615        ", isCaseExact=" + isCaseExact() +
616        '}';
617  }
618
619  /**
620   * {@inheritDoc}
621   */
622  @Override
623  public int hashCode()
624  {
625    int hashCode = 0;
626
627    hashCode += toLowerCase(schema).hashCode();
628    hashCode += toLowerCase(name).hashCode();
629
630    return hashCode;
631  }
632
633  /**
634   * {@inheritDoc}
635   */
636  @Override
637  public boolean equals(final Object obj)
638  {
639    if (this == obj)
640    {
641      return true;
642    }
643
644    if (!(obj instanceof AttributeDescriptor))
645    {
646      return false;
647    }
648
649    final AttributeDescriptor that = (AttributeDescriptor)obj;
650    if (this.schema == null && that.schema == null)
651    {
652      return this.name.equalsIgnoreCase(that.name);
653    }
654    else
655    {
656      return this.schema != null && that.schema != null &&
657          this.schema.equalsIgnoreCase(that.schema) &&
658          this.name.equalsIgnoreCase(that.name);
659    }
660  }
661
662  /**
663   * Create a new sub-attribute descriptor with the provided information.
664   *
665   * @param name            The attribute's name.
666   * @param dataType        The attribute's data type.
667   * @param description     The attribute's human readable description.
668   * @param schema          The attribute's associated schema.
669   * @param readOnly        Whether the attribute is mutable.
670   * @param required        Whether the attribute is required.
671   * @param caseExact       Whether the string attribute is case sensitive.
672   * @param canonicalValues A collection of canonical values.
673   * @return                A new singular sub-attribute descriptor with the
674   *                        provided information.
675   */
676  public static AttributeDescriptor createSubAttribute(
677      final String name, final DataType dataType, final String description,
678      final String schema, final boolean readOnly, final boolean required,
679      final boolean caseExact, final String... canonicalValues)
680  {
681    final Collection<Entry<String>> values;
682    if(canonicalValues != null && canonicalValues.length > 0)
683    {
684      values = new ArrayList<Entry<String>>(canonicalValues.length);
685      for(String canonicalValue : canonicalValues)
686      {
687        values.add(new Entry<String>(canonicalValue, null, false));
688      }
689    }
690    else
691    {
692      values = null;
693    }
694    return new AttributeDescriptor(name, dataType, false, null,
695        description, schema, readOnly, required, caseExact, values, null);
696  }
697
698  /**
699   * Create a new singular attribute descriptor with the provided information.
700   *
701   * @param name                 The attribute's name.
702   * @param dataType             The attribute's data type.
703   * @param description          The attribute's human readable description.
704   * @param schema               The attribute's associated schema.
705   * @param readOnly             Whether the attribute is mutable.
706   * @param required             Whether the attribute is required.
707   * @param caseExact            Whether the string attribute is case sensitive.
708   * @param subAttributes        A list specifying the contained attributes.
709   * @return                     A new singular attribute descriptor
710   *                             with the provided information.
711   */
712  public static AttributeDescriptor createAttribute(
713      final String name, final DataType dataType, final String description,
714      final String schema, final boolean readOnly, final boolean required,
715      final boolean caseExact, final AttributeDescriptor... subAttributes)
716  {
717    return newAttribute(name, null, dataType, description, schema, readOnly,
718        required, caseExact, subAttributes);
719  }
720
721  /**
722   * Create a new multi-valued attribute descriptor with the provided
723   * information. The normative sub-attributes for multi-valued attributes
724   * (ie. type, primary, display, operation, value) will be added.
725   *
726   * @param name                 The attribute's name.
727   * @param multiValuedChildName The child XML element name for multi-valued
728   *                             attributes.
729   * @param dataType             The attribute's data type.
730   * @param description          The attribute's human readable description.
731   * @param schema               The attribute's associated schema.
732   * @param readOnly             Whether the attribute is mutable.
733   * @param required             Whether the attribute is required.
734   * @param caseExact            Whether the string attribute is case sensitive.
735   * @param canonicalValues The list of canonical values for the type attribute.
736   * @param subAttributes        A list specifying the contained attributes.
737   * @return                     A new multi-valued attribute descriptor
738   *                             with the provided information.
739   */
740  public static AttributeDescriptor createMultiValuedAttribute(
741      final String name, final String multiValuedChildName,
742      final DataType dataType, final String description, final String schema,
743      final boolean readOnly, final boolean required, final boolean caseExact,
744      final String[] canonicalValues,
745      final AttributeDescriptor... subAttributes)
746  {
747    return newAttribute(name, multiValuedChildName, dataType,
748        description, schema, readOnly, required, caseExact,
749        CoreSchema.addCommonMultiValuedSubAttributes(
750            schema, dataType, canonicalValues, subAttributes));
751  }
752
753  /**
754   * Create a new attribute descriptor with the provided information.
755   *
756   * @param name                 The attribute's name.
757   * @param multiValuedChildName The child XML element name for multi-valued
758   *                             attributes.
759   * @param dataType             The attribute's data type.
760   * @param description          The attribute's human readable description.
761   * @param schema               The attribute's associated schema.
762   * @param readOnly             Whether the attribute is mutable.
763   * @param required             Whether the attribute is required.
764   * @param caseExact            Whether the string attribute is case sensitive.
765   * @param subAttributes        A list specifying the contained attributes.
766   * @return                     A new attribute descriptor with the provided
767   *                             information.
768   */
769  static AttributeDescriptor newAttribute(
770      final String name, final String multiValuedChildName,
771      final DataType dataType, final String description, final String schema,
772      final boolean readOnly, final boolean required, final boolean caseExact,
773      final AttributeDescriptor... subAttributes)
774  {
775    Collection<AttributeDescriptor> subAttrs = null;
776    if (subAttributes != null)
777    {
778      subAttrs = Arrays.asList(subAttributes);
779    }
780    return new AttributeDescriptor(name, dataType, multiValuedChildName != null,
781        multiValuedChildName, description, schema, readOnly, required,
782        caseExact, null, subAttrs);
783  }
784}