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
26package javax.management;
27
28import java.io.IOException;
29import java.io.StreamCorruptedException;
30import java.io.Serializable;
31import java.io.ObjectOutputStream;
32import java.io.ObjectInputStream;
33import java.lang.reflect.Method;
34import java.util.Arrays;
35import java.util.Map;
36import java.util.WeakHashMap;
37import java.security.AccessController;
38import java.security.PrivilegedAction;
39import java.util.Objects;
40
41import 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 */
107public 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