1 /*
2  * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */

25
26 package javax.management;
27
28 import java.io.IOException;
29 import java.io.StreamCorruptedException;
30 import java.io.Serializable;
31 import java.io.ObjectOutputStream;
32 import java.io.ObjectInputStream;
33 import java.lang.reflect.Method;
34 import java.util.Arrays;
35 import java.util.Map;
36 import java.util.WeakHashMap;
37 import java.security.AccessController;
38 import java.security.PrivilegedAction;
39 import java.util.Objects;
40
41 import static javax.management.ImmutableDescriptor.nonNullDescriptor;
42
43 /**
44  * <p>Describes the management interface exposed by an MBean; that is,
45  * the set of attributes and operations which are available for
46  * management operations.  Instances of this class are immutable.
47  * Subclasses may be mutable but this is not recommended.</p>
48  *
49  * <p id="info-changed">Usually the {@code MBeanInfo} for any given MBean does
50  * not change over the lifetime of that MBean.  Dynamic MBeans can change their
51  * {@code MBeanInfo} and in that case it is recommended that they emit a {@link
52  * Notification} with a {@linkplain Notification#getType() type} of {@code
53  * "jmx.mbean.info.changed"} and a {@linkplain Notification#getUserData()
54  * userData} that is the new {@code MBeanInfo}.  This is not required, but
55  * provides a conventional way for clients of the MBean to discover the change.
56  * See also the <a href="Descriptor.html#immutableInfo">immutableInfo</a> and
57  * <a href="Descriptor.html#infoTimeout">infoTimeout</a> fields in the {@code
58  * MBeanInfo} {@link Descriptor}.</p>
59  *
60  * <p>The contents of the {@code MBeanInfo} for a Dynamic MBean
61  * are determined by its {@link DynamicMBean#getMBeanInfo
62  * getMBeanInfo()} method.  This includes Open MBeans and Model
63  * MBeans, which are kinds of Dynamic MBeans.</p>
64  *
65  * <p>The contents of the {@code MBeanInfo} for a Standard MBean
66  * are determined by the MBean server as follows:</p>
67  *
68  * <ul>
69  *
70  * <li>{@link #getClassName()} returns the Java class name of the MBean
71  * object;
72  *
73  * <li>{@link #getConstructors()} returns the list of all public
74  * constructors in that object;
75  *
76  * <li>{@link #getAttributes()} returns the list of all attributes
77  * whose existence is deduced from the presence in the MBean interface
78  * of a <code>get<i>Name</i></code>, <code>is<i>Name</i></code>, or
79  * <code>set<i>Name</i></code> method that conforms to the conventions
80  * for Standard MBeans;
81  *
82  * <li>{@link #getOperations()} returns the list of all methods in
83  * the MBean interface that do not represent attributes;
84  *
85  * <li>{@link #getNotifications()} returns an empty array if the MBean
86  * does not implement the {@link NotificationBroadcaster} interface,
87  * otherwise the result of calling {@link
88  * NotificationBroadcaster#getNotificationInfo()} on it;
89  *
90  * <li>{@link #getDescriptor()} returns a descriptor containing the contents
91  * of any descriptor annotations in the MBean interface (see
92  * {@link DescriptorKey &#64;DescriptorKey}).
93  *
94  * </ul>
95  *
96  * <p>The description returned by {@link #getDescription()} and the
97  * descriptions of the contained attributes and operations are not specified.</p>
98  *
99  * <p>The remaining details of the {@code MBeanInfo} for a
100  * Standard MBean are not specified.  This includes the description of
101  * any contained constructors, and notifications; the names
102  * of parameters to constructors and operations; and the descriptions of
103  * constructor parameters.</p>
104  *
105  * @since 1.5
106  */

107 public class MBeanInfo implements Cloneable, Serializable, DescriptorRead {
108
109     /* Serial version */
110     static final long serialVersionUID = -6451021435135161911L;
111
112     /**
113      * @serial The Descriptor for the MBean.  This field
114      * can be null, which is equivalent to an empty Descriptor.
115      */

116     private transient Descriptor descriptor;
117
118     /**
119      * @serial The human readable description of the class.
120      */

121     private final String description;
122
123     /**
124      * @serial The MBean qualified name.
125      */

126     private final String className;
127
128     /**
129      * @serial The MBean attribute descriptors.
130      */

131     private final MBeanAttributeInfo[] attributes;
132
133     /**
134      * @serial The MBean operation descriptors.
135      */

136     private final MBeanOperationInfo[] operations;
137
138      /**
139      * @serial The MBean constructor descriptors.
140      */

141     private final MBeanConstructorInfo[] constructors;
142
143     /**
144      * @serial The MBean notification descriptors.
145      */

146     private final MBeanNotificationInfo[] notifications;
147
148     private transient int hashCode;
149
150     /**
151      * <p>True if this class is known not to override the array-valued
152      * getters of MBeanInfo.  Obviously true for MBeanInfo itself, and true
153      * for a subclass where we succeed in reflecting on the methods
154      * and discover they are not overridden.</p>
155      *
156      * <p>The purpose of this variable is to avoid cloning the arrays
157      * when doing operations like {@link #equals} where we know they
158      * will not be changed.  If a subclass overrides a getter, we
159      * cannot access the corresponding array directly.</p>
160      */

161     private final transient boolean arrayGettersSafe;
162
163     /**
164      * Constructs an {@code MBeanInfo}.
165      *
166      * @param className The name of the Java class of the MBean described
167      * by this {@code MBeanInfo}.  This value may be any
168      * syntactically legal Java class name.  It does not have to be a
169      * Java class known to the MBean server or to the MBean's
170      * ClassLoader.  If it is a Java class known to the MBean's
171      * ClassLoader, it is recommended but not required that the
172      * class's public methods include those that would appear in a
173      * Standard MBean implementing the attributes and operations in
174      * this MBeanInfo.
175      * @param description A human readable description of the MBean (optional).
176      * @param attributes The list of exposed attributes of the MBean.
177      * This may be null with the same effect as a zero-length array.
178      * @param constructors The list of public constructors of the
179      * MBean.  This may be null with the same effect as a zero-length
180      * array.
181      * @param operations The list of operations of the MBean.  This
182      * may be null with the same effect as a zero-length array.
183      * @param notifications The list of notifications emitted.  This
184      * may be null with the same effect as a zero-length array.
185      */

186     public MBeanInfo(String className,
187                      String description,
188                      MBeanAttributeInfo[] attributes,
189                      MBeanConstructorInfo[] constructors,
190                      MBeanOperationInfo[] operations,
191                      MBeanNotificationInfo[] notifications)
192             throws IllegalArgumentException {
193         this(className, description, attributes, constructors, operations,
194              notifications, null);
195     }
196
197     /**
198      * Constructs an {@code MBeanInfo}.
199      *
200      * @param className The name of the Java class of the MBean described
201      * by this {@code MBeanInfo}.  This value may be any
202      * syntactically legal Java class name.  It does not have to be a
203      * Java class known to the MBean server or to the MBean's
204      * ClassLoader.  If it is a Java class known to the MBean's
205      * ClassLoader, it is recommended but not required that the
206      * class's public methods include those that would appear in a
207      * Standard MBean implementing the attributes and operations in
208      * this MBeanInfo.
209      * @param description A human readable description of the MBean (optional).
210      * @param attributes The list of exposed attributes of the MBean.
211      * This may be null with the same effect as a zero-length array.
212      * @param constructors The list of public constructors of the
213      * MBean.  This may be null with the same effect as a zero-length
214      * array.
215      * @param operations The list of operations of the MBean.  This
216      * may be null with the same effect as a zero-length array.
217      * @param notifications The list of notifications emitted.  This
218      * may be null with the same effect as a zero-length array.
219      * @param descriptor The descriptor for the MBean.  This may be null
220      * which is equivalent to an empty descriptor.
221      *
222      * @since 1.6
223      */

224     public MBeanInfo(String className,
225                      String description,
226                      MBeanAttributeInfo[] attributes,
227                      MBeanConstructorInfo[] constructors,
228                      MBeanOperationInfo[] operations,
229                      MBeanNotificationInfo[] notifications,
230                      Descriptor descriptor)
231             throws IllegalArgumentException {
232
233         this.className = className;
234
235         this.description = description;
236
237         if (attributes == null)
238             attributes = MBeanAttributeInfo.NO_ATTRIBUTES;
239         this.attributes = attributes;
240
241         if (operations == null)
242             operations = MBeanOperationInfo.NO_OPERATIONS;
243         this.operations = operations;
244
245         if (constructors == null)
246             constructors = MBeanConstructorInfo.NO_CONSTRUCTORS;
247         this.constructors = constructors;
248
249         if (notifications == null)
250             notifications = MBeanNotificationInfo.NO_NOTIFICATIONS;
251         this.notifications = notifications;
252
253         if (descriptor == null)
254             descriptor = ImmutableDescriptor.EMPTY_DESCRIPTOR;
255         this.descriptor = descriptor;
256
257         this.arrayGettersSafe =
258                 arrayGettersSafe(this.getClass(), MBeanInfo.class);
259     }
260
261     /**
262      * <p>Returns a shallow clone of this instance.
263      * The clone is obtained by simply calling {@code super.clone()},
264      * thus calling the default native shallow cloning mechanism
265      * implemented by {@code Object.clone()}.
266      * No deeper cloning of any internal field is made.</p>
267      *
268      * <p>Since this class is immutable, the clone method is chiefly of
269      * interest to subclasses.</p>
270      */

271      @Override
272      public Object clone () {
273          try {
274              return super.clone() ;
275          } catch (CloneNotSupportedException e) {
276              // should not happen as this class is cloneable
277              return null;
278          }
279      }
280
281
282     /**
283      * Returns the name of the Java class of the MBean described by
284      * this {@code MBeanInfo}.
285      *
286      * @return the class name.
287      */

288     public String getClassName()  {
289         return className;
290     }
291
292     /**
293      * Returns a human readable description of the MBean.
294      *
295      * @return the description.
296      */

297     public String getDescription()  {
298         return description;
299     }
300
301     /**
302      * Returns the list of attributes exposed for management.
303      * Each attribute is described by an {@code MBeanAttributeInfo} object.
304      *
305      * The returned array is a shallow copy of the internal array,
306      * which means that it is a copy of the internal array of
307      * references to the {@code MBeanAttributeInfo} objects
308      * but that each referenced {@code MBeanAttributeInfo} object is not copied.
309      *
310      * @return  An array of {@code MBeanAttributeInfo} objects.
311      */

312     public MBeanAttributeInfo[] getAttributes()   {
313         MBeanAttributeInfo[] as = nonNullAttributes();
314         if (as.length == 0)
315             return as;
316         else
317             return as.clone();
318     }
319
320     private MBeanAttributeInfo[] fastGetAttributes() {
321         if (arrayGettersSafe)
322             return nonNullAttributes();
323         else
324             return getAttributes();
325     }
326
327     /**
328      * Return the value of the attributes field, or an empty array if
329      * the field is null.  This can't happen with a
330      * normally-constructed instance of this class, but can if the
331      * instance was deserialized from another implementation that
332      * allows the field to be null.  It would be simpler if we enforced
333      * the class invariant that these fields cannot be null by writing
334      * a readObject() method, but that would require us to define the
335      * various array fields as non-final, which is annoying because
336      * conceptually they are indeed final.
337      */

338     private MBeanAttributeInfo[] nonNullAttributes() {
339         return (attributes == null) ?
340             MBeanAttributeInfo.NO_ATTRIBUTES : attributes;
341     }
342
343     /**
344      * Returns the list of operations  of the MBean.
345      * Each operation is described by an {@code MBeanOperationInfo} object.
346      *
347      * The returned array is a shallow copy of the internal array,
348      * which means that it is a copy of the internal array of
349      * references to the {@code MBeanOperationInfo} objects
350      * but that each referenced {@code MBeanOperationInfo} object is not copied.
351      *
352      * @return  An array of {@code MBeanOperationInfo} objects.
353      */

354     public MBeanOperationInfo[] getOperations()  {
355         MBeanOperationInfo[] os = nonNullOperations();
356         if (os.length == 0)
357             return os;
358         else
359             return os.clone();
360     }
361
362     private MBeanOperationInfo[] fastGetOperations() {
363         if (arrayGettersSafe)
364             return nonNullOperations();
365         else
366             return getOperations();
367     }
368
369     private MBeanOperationInfo[] nonNullOperations() {
370         return (operations == null) ?
371             MBeanOperationInfo.NO_OPERATIONS : operations;
372     }
373
374     /**
375      * <p>Returns the list of the public constructors of the MBean.
376      * Each constructor is described by an
377      * {@code MBeanConstructorInfo} object.</p>
378      *
379      * <p>The returned array is a shallow copy of the internal array,
380      * which means that it is a copy of the internal array of
381      * references to the {@code MBeanConstructorInfo} objects but
382      * that each referenced {@code MBeanConstructorInfo} object
383      * is not copied.</p>
384      *
385      * <p>The returned list is not necessarily exhaustive.  That is,
386      * the MBean may have a public constructor that is not in the
387      * list.  In this case, the MBean server can construct another
388      * instance of this MBean's class using that constructor, even
389      * though it is not listed here.</p>
390      *
391      * @return  An array of {@code MBeanConstructorInfo} objects.
392      */

393     public MBeanConstructorInfo[] getConstructors()  {
394         MBeanConstructorInfo[] cs = nonNullConstructors();
395         if (cs.length == 0)
396             return cs;
397         else
398             return cs.clone();
399     }
400
401     private MBeanConstructorInfo[] fastGetConstructors() {
402         if (arrayGettersSafe)
403             return nonNullConstructors();
404         else
405             return getConstructors();
406     }
407
408     private MBeanConstructorInfo[] nonNullConstructors() {
409         return (constructors == null) ?
410             MBeanConstructorInfo.NO_CONSTRUCTORS : constructors;
411     }
412
413     /**
414      * Returns the list of the notifications emitted by the MBean.
415      * Each notification is described by an {@code MBeanNotificationInfo} object.
416      *
417      * The returned array is a shallow copy of the internal array,
418      * which means that it is a copy of the internal array of
419      * references to the {@code MBeanNotificationInfo} objects
420      * but that each referenced {@code MBeanNotificationInfo} object is not copied.
421      *
422      * @return  An array of {@code MBeanNotificationInfo} objects.
423      */

424     public MBeanNotificationInfo[] getNotifications()  {
425         MBeanNotificationInfo[] ns = nonNullNotifications();
426         if (ns.length == 0)
427             return ns;
428         else
429             return ns.clone();
430     }
431
432     private MBeanNotificationInfo[] fastGetNotifications() {
433         if (arrayGettersSafe)
434             return nonNullNotifications();
435         else
436             return getNotifications();
437     }
438
439     private MBeanNotificationInfo[] nonNullNotifications() {
440         return (notifications == null) ?
441             MBeanNotificationInfo.NO_NOTIFICATIONS : notifications;
442     }
443
444     /**
445      * Get the descriptor of this MBeanInfo.  Changing the returned value
446      * will have no affect on the original descriptor.
447      *
448      * @return a descriptor that is either immutable or a copy of the original.
449      *
450      * @since 1.6
451      */

452     public Descriptor getDescriptor() {
453         return (Descriptor) nonNullDescriptor(descriptor).clone();
454     }
455
456     @Override
457     public String toString() {
458         return
459             getClass().getName() + "[" +
460             "description=" + getDescription() + ", " +
461             "attributes=" + Arrays.asList(fastGetAttributes()) + ", " +
462             "constructors=" + Arrays.asList(fastGetConstructors()) + ", " +
463             "operations=" + Arrays.asList(fastGetOperations()) + ", " +
464             "notifications=" + Arrays.asList(fastGetNotifications()) + ", " +
465             "descriptor=" + getDescriptor() +
466             "]";
467     }
468
469     /**
470      * <p>Compare this MBeanInfo to another.  Two MBeanInfo objects
471      * are equal if and only if they return equal values for {@link
472      * #getClassName()}, for {@link #getDescription()}, and for
473      * {@link #getDescriptor()}, and the
474      * arrays returned by the two objects for {@link
475      * #getAttributes()}, {@link #getOperations()}, {@link
476      * #getConstructors()}, and {@link #getNotifications()} are
477      * pairwise equal.  Here "equal" means {@link
478      * Object#equals(Object)}, not identity.</p>
479      *
480      * <p>If two MBeanInfo objects return the same values in one of
481      * their arrays but in a different order then they are not equal.</p>
482      *
483      * @param o the object to compare to.
484      *
485      * @return true if and only if {@code o} is an MBeanInfo that is equal
486      * to this one according to the rules above.
487      */

488     @Override
489     public boolean equals(Object o) {
490         if (o == this)
491             return true;
492         if (!(o instanceof MBeanInfo))
493             return false;
494         MBeanInfo p = (MBeanInfo) o;
495         if (!isEqual(getClassName(),  p.getClassName()) ||
496                 !isEqual(getDescription(), p.getDescription()) ||
497                 !getDescriptor().equals(p.getDescriptor())) {
498             return false;
499         }
500
501         return
502             (Arrays.equals(p.fastGetAttributes(), fastGetAttributes()) &&
503              Arrays.equals(p.fastGetOperations(), fastGetOperations()) &&
504              Arrays.equals(p.fastGetConstructors(), fastGetConstructors()) &&
505              Arrays.equals(p.fastGetNotifications(), fastGetNotifications()));
506     }
507
508     @Override
509     public int hashCode() {
510         /* Since computing the hashCode is quite expensive, we cache it.
511            If by some terrible misfortune the computed value is 0, the
512            caching won't work and we will recompute it every time.
513
514            We don't bother synchronizing, because, at worst, n different
515            threads will compute the same hashCode at the same time.  */

516         if (hashCode != 0)
517             return hashCode;
518
519         hashCode = Objects.hash(getClassName(), getDescriptor())
520                 ^ Arrays.hashCode(fastGetAttributes())
521                 ^ Arrays.hashCode(fastGetOperations())
522                 ^ Arrays.hashCode(fastGetConstructors())
523                 ^ Arrays.hashCode(fastGetNotifications());
524
525         return hashCode;
526     }
527
528     /**
529      * Cached results of previous calls to arrayGettersSafe.  This is
530      * a WeakHashMap so that we don't prevent a class from being
531      * garbage collected just because we know whether it's immutable.
532      */

533     private static final Map<Class<?>, Boolean> arrayGettersSafeMap =
534         new WeakHashMap<Class<?>, Boolean>();
535
536     /**
537      * Return true if {@code subclass} is known to preserve the
538      * immutability of {@code immutableClass}.  The class
539      * {@code immutableClass} is a reference class that is known
540      * to be immutable.  The subclass {@code subclass} is
541      * considered immutable if it does not override any public method
542      * of {@code immutableClass} whose name begins with "get".
543      * This is obviously not an infallible test for immutability,
544      * but it works for the public interfaces of the MBean*Info classes.
545     */

546     static boolean arrayGettersSafe(Class<?> subclass, Class<?> immutableClass) {
547         if (subclass == immutableClass)
548             return true;
549         synchronized (arrayGettersSafeMap) {
550             Boolean safe = arrayGettersSafeMap.get(subclass);
551             if (safe == null) {
552                 try {
553                     ArrayGettersSafeAction action =
554                         new ArrayGettersSafeAction(subclass, immutableClass);
555                     safe = AccessController.doPrivileged(action);
556                 } catch (Exception e) { // e.g. SecurityException
557                     /* We don't know, so we assume it isn't.  */
558                     safe = false;
559                 }
560                 arrayGettersSafeMap.put(subclass, safe);
561             }
562             return safe;
563         }
564     }
565
566     /*
567      * The PrivilegedAction stuff is probably overkill.  We can be
568      * pretty sure the caller does have the required privileges -- a
569      * JMX user that can't do reflection can't even use Standard
570      * MBeans!  But there's probably a performance gain by not having
571      * to check the whole call stack.
572      */

573     private static class ArrayGettersSafeAction
574             implements PrivilegedAction<Boolean> {
575
576         private final Class<?> subclass;
577         private final Class<?> immutableClass;
578
579         ArrayGettersSafeAction(Class<?> subclass, Class<?> immutableClass) {
580             this.subclass = subclass;
581             this.immutableClass = immutableClass;
582         }
583
584         public Boolean run() {
585             Method[] methods = immutableClass.getMethods();
586             for (int i = 0; i < methods.length; i++) {
587                 Method method = methods[i];
588                 String methodName = method.getName();
589                 if (methodName.startsWith("get") &&
590                         method.getParameterTypes().length == 0 &&
591                         method.getReturnType().isArray()) {
592                     try {
593                         Method submethod =
594                             subclass.getMethod(methodName);
595                         if (!submethod.equals(method))
596                             return false;
597                     } catch (NoSuchMethodException e) {
598                         return false;
599                     }
600                 }
601             }
602             return true;
603         }
604     }
605
606     private static boolean isEqual(String s1, String s2) {
607         boolean ret;
608
609         if (s1 == null) {
610             ret = (s2 == null);
611         } else {
612             ret = s1.equals(s2);
613         }
614
615         return ret;
616     }
617
618     /**
619      * Serializes an {@link MBeanInfo} to an {@link ObjectOutputStream}.
620      * @serialData
621      * For compatibility reasons, an object of this class is serialized as follows.
622      * <p>
623      * The method {@link ObjectOutputStream#defaultWriteObject defaultWriteObject()}
624      * is called first to serialize the object except the field {@code descriptor}
625      * which is declared as transient. The field {@code descriptor} is serialized
626      * as follows:
627      *     <ul>
628      *     <li> If {@code descriptor} is an instance of the class
629      *        {@link ImmutableDescriptor}, the method {@link ObjectOutputStream#write
630      *        write(int val)} is called to write a byte with the value {@code 1},
631      *        then the method {@link ObjectOutputStream#writeObject writeObject(Object obj)}
632      *        is called twice to serialize the field names and the field values of the
633      *        {@code descriptor}, respectively as a {@code String[]} and an
634      *        {@code Object[]};</li>
635      *     <li> Otherwise, the method {@link ObjectOutputStream#write write(int val)}
636      *        is called to write a byte with the value {@code 0}, then the method
637      *        {@link ObjectOutputStream#writeObject writeObject(Object obj)} is called
638      *        to serialize the field {@code descriptor} directly.
639      *     </ul>
640      *
641      * @since 1.6
642      */

643     private void writeObject(ObjectOutputStream out) throws IOException {
644         out.defaultWriteObject();
645
646         if (descriptor.getClass() == ImmutableDescriptor.class) {
647             out.write(1);
648
649             final String[] names = descriptor.getFieldNames();
650
651             out.writeObject(names);
652             out.writeObject(descriptor.getFieldValues(names));
653         } else {
654             out.write(0);
655
656             out.writeObject(descriptor);
657         }
658     }
659
660     /**
661      * Deserializes an {@link MBeanInfo} from an {@link ObjectInputStream}.
662      * @serialData
663      * For compatibility reasons, an object of this class is deserialized as follows.
664      * <p>
665      * The method {@link ObjectInputStream#defaultReadObject defaultReadObject()}
666      * is called first to deserialize the object except the field
667      * {@code descriptor}, which is not serialized in the default way. Then the method
668      * {@link ObjectInputStream#read read()} is called to read a byte, the field
669      * {@code descriptor} is deserialized according to the value of the byte value:
670      *    <ul>
671      *    <li>1. The method {@link ObjectInputStream#readObject readObject()}
672      *       is called twice to obtain the field names (a {@code String[]}) and
673      *       the field values (an {@code Object[]}) of the {@code descriptor}.
674      *       The two obtained values then are used to construct
675      *       an {@link ImmutableDescriptor} instance for the field
676      *       {@code descriptor};</li>
677      *    <li>0. The value for the field {@code descriptor} is obtained directly
678      *       by calling the method {@link ObjectInputStream#readObject readObject()}.
679      *       If the obtained value is null, the field {@code descriptor} is set to
680      *       {@link ImmutableDescriptor#EMPTY_DESCRIPTOR EMPTY_DESCRIPTOR};</li>
681      *    <li>-1. This means that there is no byte to read and that the object is from
682      *       an earlier version of the JMX API. The field {@code descriptor} is set to
683      *       {@link ImmutableDescriptor#EMPTY_DESCRIPTOR EMPTY_DESCRIPTOR}.</li>
684      *    <li>Any other value. A {@link StreamCorruptedException} is thrown.</li>
685      *    </ul>
686      *
687      * @since 1.6
688      */

689
690     private void readObject(ObjectInputStream in)
691         throws IOException, ClassNotFoundException {
692
693         in.defaultReadObject();
694
695         switch (in.read()) {
696         case 1:
697             final String[] names = (String[])in.readObject();
698
699             final Object[] values = (Object[]) in.readObject();
700             descriptor = (names.length == 0) ?
701                 ImmutableDescriptor.EMPTY_DESCRIPTOR :
702                 new ImmutableDescriptor(names, values);
703
704             break;
705         case 0:
706             descriptor = (Descriptor)in.readObject();
707
708             if (descriptor == null) {
709                 descriptor = ImmutableDescriptor.EMPTY_DESCRIPTOR;
710             }
711
712             break;
713         case -1: // from an earlier version of the JMX API
714             descriptor = ImmutableDescriptor.EMPTY_DESCRIPTOR;
715
716             break;
717         default:
718             throw new StreamCorruptedException("Got unexpected byte.");
719         }
720     }
721 }
722