001    /* CompositeType.java -- Type descriptor for CompositeData instances.
002       Copyright (C) 2006, 2007 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    package javax.management.openmbean;
039    
040    import java.util.Collections;
041    import java.util.Iterator;
042    import java.util.Map;
043    import java.util.Set;
044    import java.util.TreeMap;
045    
046    /**
047     * The open type descriptor for instances of the
048     * {@link CompositeData} class.
049     * 
050     * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
051     * @since 1.5
052     */
053    public class CompositeType
054      extends OpenType<CompositeData>
055    {
056    
057      /**
058       * Compatible with JDK 1.5
059       */
060      private static final long serialVersionUID = -5366242454346948798L;
061    
062      /**
063       * A map of item names to their descriptions.
064       */
065      private TreeMap<String,String> nameToDescription;
066    
067      /**
068       * A map of item names to their types.
069       */
070      private TreeMap<String,OpenType<?>> nameToType;
071    
072      /**
073       * The hash code of this instance.
074       */
075      private transient Integer hashCode;
076    
077      /**
078       * The <code>toString()</code> result of this instance.
079       */
080      private transient String string;
081    
082      /**
083       * <p>
084       * Constructs a new {@link CompositeType} instance for the given
085       * type name with the specified field names, descriptions and types.
086       * All parameters, and the elements of the array parameters, must be
087       * non-null and {@link java.lang.String} values must be something other
088       * than the empty string.  The arrays must be non-empty, and be of
089       * equal size.
090       * </p>
091       * <p>
092       * The result of <code>CompositeData.class.getName()</code> is adopted
093       * as the class name (see {@link OpenType}) and changes to the array
094       * elements following construction of the {@link CompositeType} instance
095       * will <strong>not</strong> affect the values used by the instance.
096       * The field names are sorted in to ascending alphanumeric order internally,
097       * and so ordering can not be used to differentiate between two instances.
098       * </p>
099       *
100       * @param name the name of this composite type.
101       * @param desc a description of this composite type.
102       * @param names the names of each field within the composite type.
103       * @param descs the descriptions of each field within the composite type.
104       * @param types the types of each field within the composite type.
105       * @throws IllegalArgumentException if any validity constraint listed above
106       *                                  is broken.
107       * @throws OpenDataException if duplicate item names are provided.  Item names
108       *                           are case-sensitive, but whitespace is removed
109       *                           before comparison.
110       */
111      public CompositeType(String name, String desc, String[] names,
112                           String[] descs, OpenType<?>[] types)
113        throws OpenDataException
114      {
115        super(CompositeData.class.getName(), name, desc);
116        if (names.length == 0 
117            || names.length != descs.length
118            || names.length != types.length)
119          throw new IllegalArgumentException("Arrays must be non-empty " +
120                                             "and of equal size.");
121        nameToDescription = new TreeMap<String,String>();
122        for (int a = 0; a < names.length; ++a)
123          {
124            if (names[a] == null)
125              throw new IllegalArgumentException("Name " + a + " is null.");
126            if (descs[a] == null)
127              throw new IllegalArgumentException("Description " + a + 
128                                                 " is null.");
129            String fieldName = names[a].trim();
130            if (fieldName.length() == 0)
131              throw new IllegalArgumentException("Name " + a + " is " +
132                                                 "the empty string.");
133            if (descs[a].length() == 0)
134              throw new IllegalArgumentException("Description " + a + " is " +
135                                                 "the empty string.");
136            if (nameToDescription.containsKey(fieldName))
137              throw new OpenDataException(fieldName + " appears more " +
138                                          "than once.");
139            nameToDescription.put(fieldName, descs[a]);
140          }
141        nameToType = new TreeMap<String,OpenType<?>>();
142        for (int a = 0; a < names.length; ++a)
143          nameToType.put(names[a].trim(), types[a]);
144      }
145    
146      /**
147       * Returns true if this composite data type has a field
148       * with the given name.
149       *
150       * @param name the name of the field to check for.
151       * @return true if a field of that name exists.
152       */
153      public boolean containsKey(String name)
154      {
155        return nameToDescription.containsKey(name);
156      }
157    
158      /**
159       * <p>
160       * Compares this composite data type with another object
161       * for equality.  The objects are judged to be equal if:
162       * </p>
163       * <ul>
164       * <li><code>obj</code> is not null.</li>
165       * <li><code>obj</code> is an instance of
166       * {@link CompositeType}.</li>
167       * <li>The type names are equal.</li>
168       * <li>The fields and their types match.</li>
169       * </ul>
170       * 
171       * @param obj the object to compare with.
172       * @return true if the conditions above hold.
173       */
174      public boolean equals(Object obj)
175      {
176        if (!(obj instanceof CompositeType))
177          return false;
178        CompositeType ctype = (CompositeType) obj;
179        if (!(ctype.getTypeName().equals(getTypeName())))
180          return false;
181        Set<String> keys = keySet();
182        if (!(ctype.keySet().equals(keys)))
183          return false;
184        for (String key : keys)
185        {
186          if (!(ctype.getType(key).equals(getType(key))))
187            return false;
188        }
189        return true;
190      }
191    
192      /**
193       * Returns the description for the given field name,
194       * or <code>null</code> if the field name does not
195       * exist within this composite data type.
196       *
197       * @param name the name of the field whose description
198       *             should be returned.
199       * @return the description, or <code>null</code> if the
200       *         field doesn't exist.
201       */
202      public String getDescription(String name)
203      {
204        return nameToDescription.get(name);
205      }
206    
207      /**
208       * Returns the type for the given field name,
209       * or <code>null</code> if the field name does not
210       * exist within this composite data type.
211       *
212       * @param name the name of the field whose type
213       *             should be returned.
214       * @return the type, or <code>null</code> if the
215       *         field doesn't exist.
216       */
217      public OpenType<?> getType(String name)
218      {
219        return nameToType.get(name);
220      }
221    
222      /**
223       * <p>
224       * Returns the hash code of the composite data type.
225       * This is computed as the sum of the hash codes of 
226       * each field name and its type, together with the hash
227       * code of the type name.  These are the same elements
228       * of the type that are compared as part of the
229       * {@link #equals(java.lang.Object)} method, thus ensuring
230       * that the hashcode is compatible with the equality
231       * test.
232       * </p>
233       * <p>
234       * As instances of this class are immutable, the hash code
235       * is computed just once for each instance and reused
236       * throughout its life.
237       * </p>
238       *
239       * @return the hash code of this instance.
240       */
241      public int hashCode()
242      {
243        if (hashCode == null)
244          {
245            int elementTotal = 0;
246            for (Map.Entry<String,OpenType<?>> entry : nameToType.entrySet())
247              {
248                elementTotal += (entry.getKey().hashCode() +
249                                 entry.getValue().hashCode());
250              }
251            hashCode = Integer.valueOf(elementTotal 
252                                       + getTypeName().hashCode());
253          }
254        return hashCode.intValue();
255      }
256                                   
257      /**
258       * Returns true if the specified object is a member of this
259       * composite type.  The object is judged to be so if it is
260       * an instance of {@link CompositeData} with an equivalent
261       * type, according to the definition of
262       * {@link #equals(java.lang.Object)} for {@link CompositeType}.
263       *
264       * @param obj the object to test for membership.
265       * @return true if the object is a member of this type.
266       */
267      public boolean isValue(Object obj)
268      {
269        if (obj instanceof CompositeData)
270          {
271            CompositeData data = (CompositeData) obj;
272            return equals(data.getCompositeType());
273          }
274        return false;
275      }
276    
277      /**
278       * Returns an unmodifiable {@link java.util.Set}-based
279       * view of the field names that form part of this 
280       * {@link CompositeType} instance.  The names are stored
281       * in ascending alphanumeric order.
282       *
283       * @return a unmodifiable set containing the field
284       *         name {@link java.lang.String}s.
285       */
286      public Set<String> keySet()
287      {
288        return Collections.unmodifiableSet(nameToDescription.keySet());
289      }
290    
291      /**
292       * <p>
293       * Returns a textual representation of this instance.  This
294       * is constructed using the class name
295       * (<code>javax.management.openmbean.CompositeType</code>)
296       * and each element of the instance which is relevant to
297       * the definition of {@link equals(java.lang.Object)} and
298       * {@link hashCode()} (i.e. the type name, and the name
299       * and type of each field).
300       * </p>
301       * <p>
302       * As instances of this class are immutable, the return value
303       * is computed just once for each instance and reused
304       * throughout its life.
305       * </p>
306       *
307       * @return a @link{java.lang.String} instance representing
308       *         the instance in textual form.
309       */
310      public String toString()
311      {
312        if (string == null)
313          string = getClass().getName()
314            + "[name=" + getTypeName()
315            + ", fields=" + nameToType
316            + "]";
317        return string;
318      }
319    
320    }