1 /*
   2  * Copyright (c) 2000, 2017, 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  * @author    IBM Corp.
  27  *
  28  * Copyright IBM Corp. 1999-2000.  All rights reserved.
  29  */
  30 
  31 package javax.management.modelmbean;
  32 
  33 import static com.sun.jmx.defaults.JmxProperties.MODELMBEAN_LOGGER;
  34 import static com.sun.jmx.mbeanserver.Util.cast;
  35 import com.sun.jmx.mbeanserver.GetPropertyAction;
  36 import com.sun.jmx.mbeanserver.Util;
  37 
  38 import java.io.IOException;
  39 import java.io.ObjectInputStream;
  40 import java.io.ObjectOutputStream;
  41 import java.io.ObjectStreamField;
  42 
  43 import java.lang.reflect.Constructor;
  44 
  45 import java.security.AccessController;
  46 import java.util.HashMap;
  47 import java.util.Iterator;
  48 import java.util.Locale;
  49 import java.util.Map;
  50 import java.util.Set;
  51 import java.util.SortedMap;
  52 import java.util.StringTokenizer;
  53 import java.util.TreeMap;
  54 import java.lang.System.Logger.Level;
  55 
  56 import javax.management.Descriptor;
  57 import javax.management.ImmutableDescriptor;
  58 import javax.management.MBeanException;
  59 import javax.management.RuntimeOperationsException;
  60 
  61 import sun.reflect.misc.ReflectUtil;
  62 
  63 /**
  64  * This class represents the metadata set for a ModelMBean element.  A
  65  * descriptor is part of the ModelMBeanInfo,
  66  * ModelMBeanNotificationInfo, ModelMBeanAttributeInfo,
  67  * ModelMBeanConstructorInfo, and ModelMBeanParameterInfo.
  68  * <P>
  69  * A descriptor consists of a collection of fields.  Each field is in
  70  * fieldname=fieldvalue format.  Field names are not case sensitive,
  71  * case will be preserved on field values.
  72  * <P>
  73  * All field names and values are not predefined. New fields can be
  74  * defined and added by any program.  Some fields have been predefined
  75  * for consistency of implementation and support by the
  76  * ModelMBeanInfo, ModelMBeanAttributeInfo, ModelMBeanConstructorInfo,
  77  * ModelMBeanNotificationInfo, ModelMBeanOperationInfo and ModelMBean
  78  * classes.
  79  *
  80  * <p>The <b>serialVersionUID</b> of this class is <code>-6292969195866300415L</code>.
  81  *
  82  * @since 1.5
  83  */
  84 @SuppressWarnings("serial")  // serialVersionUID not constant
  85 public class DescriptorSupport
  86          implements javax.management.Descriptor
  87 {
  88 
  89     // Serialization compatibility stuff:
  90     // Two serial forms are supported in this class. The selected form depends
  91     // on system property "jmx.serial.form":
  92     //  - "1.0" for JMX 1.0
  93     //  - any other value for JMX 1.1 and higher
  94     //
  95     // Serial version for old serial form
  96     private static final long oldSerialVersionUID = 8071560848919417985L;
  97     //
  98     // Serial version for new serial form
  99     private static final long newSerialVersionUID = -6292969195866300415L;
 100     //
 101     // Serializable fields in old serial form
 102     private static final ObjectStreamField[] oldSerialPersistentFields =
 103     {
 104       new ObjectStreamField("descriptor", HashMap.class),
 105       new ObjectStreamField("currClass", String.class)
 106     };
 107     //
 108     // Serializable fields in new serial form
 109     private static final ObjectStreamField[] newSerialPersistentFields =
 110     {
 111       new ObjectStreamField("descriptor", HashMap.class)
 112     };
 113     //
 114     // Actual serial version and serial form
 115     private static final long serialVersionUID;
 116     /**
 117      * @serialField descriptor HashMap The collection of fields representing this descriptor
 118      */
 119     private static final ObjectStreamField[] serialPersistentFields;
 120     private static final String serialForm;
 121     static {
 122         String form = null;
 123         boolean compat = false;
 124         try {
 125             GetPropertyAction act = new GetPropertyAction("jmx.serial.form");
 126             form = AccessController.doPrivileged(act);
 127             compat = "1.0".equals(form);  // form may be null
 128         } catch (Exception e) {
 129             // OK: No compat with 1.0
 130         }
 131         serialForm = form;
 132         if (compat) {
 133             serialPersistentFields = oldSerialPersistentFields;
 134             serialVersionUID = oldSerialVersionUID;
 135         } else {
 136             serialPersistentFields = newSerialPersistentFields;
 137             serialVersionUID = newSerialVersionUID;
 138         }
 139     }
 140     //
 141     // END Serialization compatibility stuff
 142 
 143     /* Spec says that field names are case-insensitive, but that case
 144        is preserved.  This means that we need to be able to map from a
 145        name that may differ in case to the actual name that is used in
 146        the HashMap.  Thus, descriptorMap is a TreeMap with a Comparator
 147        that ignores case.
 148 
 149        Previous versions of this class had a field called "descriptor"
 150        of type HashMap where the keys were directly Strings.  This is
 151        hard to reconcile with the required semantics, so we fabricate
 152        that field virtually during serialization and deserialization
 153        but keep the real information in descriptorMap.
 154     */
 155     private transient SortedMap<String, Object> descriptorMap;
 156 
 157     private static final String currClass = "DescriptorSupport";
 158 
 159 
 160     /**
 161      * Descriptor default constructor.
 162      * Default initial descriptor size is 20.  It will grow as needed.<br>
 163      * Note that the created empty descriptor is not a valid descriptor
 164      * (the method {@link #isValid isValid} returns <CODE>false</CODE>)
 165      */
 166     public DescriptorSupport() {
 167         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 168             MODELMBEAN_LOGGER.log(Level.TRACE, "Constructor");
 169         }
 170         init(null);
 171     }
 172 
 173     /**
 174      * Descriptor constructor.  Takes as parameter the initial
 175      * capacity of the Map that stores the descriptor fields.
 176      * Capacity will grow as needed.<br> Note that the created empty
 177      * descriptor is not a valid descriptor (the method {@link
 178      * #isValid isValid} returns <CODE>false</CODE>).
 179      *
 180      * @param initNumFields The initial capacity of the Map that
 181      * stores the descriptor fields.
 182      *
 183      * @exception RuntimeOperationsException for illegal value for
 184      * initNumFields (&lt;= 0)
 185      * @exception MBeanException Wraps a distributed communication Exception.
 186      */
 187     public DescriptorSupport(int initNumFields)
 188             throws MBeanException, RuntimeOperationsException {
 189         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 190             MODELMBEAN_LOGGER.log(Level.TRACE,
 191                     "Descriptor(initNumFields = " + initNumFields + ") " +
 192                     "Constructor");
 193         }
 194         if (initNumFields <= 0) {
 195             if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 196                 MODELMBEAN_LOGGER.log(Level.TRACE,
 197                         "Illegal arguments: initNumFields <= 0");
 198             }
 199             final String msg =
 200                 "Descriptor field limit invalid: " + initNumFields;
 201             final RuntimeException iae = new IllegalArgumentException(msg);
 202             throw new RuntimeOperationsException(iae, msg);
 203         }
 204         init(null);
 205     }
 206 
 207     /**
 208      * Descriptor constructor taking a Descriptor as parameter.
 209      * Creates a new descriptor initialized to the values of the
 210      * descriptor passed in parameter.
 211      *
 212      * @param inDescr the descriptor to be used to initialize the
 213      * constructed descriptor. If it is null or contains no descriptor
 214      * fields, an empty Descriptor will be created.
 215      */
 216     public DescriptorSupport(DescriptorSupport inDescr) {
 217         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 218             MODELMBEAN_LOGGER.log(Level.TRACE,
 219                     "Descriptor(Descriptor) Constructor");
 220         }
 221         if (inDescr == null)
 222             init(null);
 223         else
 224             init(inDescr.descriptorMap);
 225     }
 226 
 227 
 228     /**
 229      * <p>Descriptor constructor taking an XML String.</p>
 230      *
 231      * <p>The format of the XML string is not defined, but an
 232      * implementation must ensure that the string returned by
 233      * {@link #toXMLString() toXMLString()} on an existing
 234      * descriptor can be used to instantiate an equivalent
 235      * descriptor using this constructor.</p>
 236      *
 237      * <p>In this implementation, all field values will be created
 238      * as Strings.  If the field values are not Strings, the
 239      * programmer will have to reset or convert these fields
 240      * correctly.</p>
 241      *
 242      * @param inStr An XML-formatted string used to populate this
 243      * Descriptor.  The format is not defined, but any
 244      * implementation must ensure that the string returned by
 245      * method {@link #toXMLString toXMLString} on an existing
 246      * descriptor can be used to instantiate an equivalent
 247      * descriptor when instantiated using this constructor.
 248      *
 249      * @exception RuntimeOperationsException If the String inStr
 250      * passed in parameter is null
 251      * @exception XMLParseException XML parsing problem while parsing
 252      * the input String
 253      * @exception MBeanException Wraps a distributed communication Exception.
 254      */
 255     /* At some stage we should rewrite this code to be cleverer.  Using
 256        a StringTokenizer as we do means, first, that we accept a lot of
 257        bogus strings without noticing they are bogus, and second, that we
 258        split the string being parsed at characters like > even if they
 259        occur in the middle of a field value. */
 260     public DescriptorSupport(String inStr)
 261             throws MBeanException, RuntimeOperationsException,
 262                    XMLParseException {
 263         /* parse an XML-formatted string and populate internal
 264          * structure with it */
 265         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 266             MODELMBEAN_LOGGER.log(Level.TRACE,
 267                     "Descriptor(String = '" + inStr + "') Constructor");
 268         }
 269         if (inStr == null) {
 270             if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 271                 MODELMBEAN_LOGGER.log(Level.TRACE,
 272                         "Descriptor(String = null) Illegal arguments");
 273             }
 274             final String msg = "String in parameter is null";
 275             final RuntimeException iae = new IllegalArgumentException(msg);
 276             throw new RuntimeOperationsException(iae, msg);
 277         }
 278 
 279         final String lowerInStr = inStr.toLowerCase(Locale.ENGLISH);
 280         if (!lowerInStr.startsWith("<descriptor>")
 281             || !lowerInStr.endsWith("</descriptor>")) {
 282             throw new XMLParseException("No <descriptor>, </descriptor> pair");
 283         }
 284 
 285         // parse xmlstring into structures
 286         init(null);
 287         // create dummy descriptor: should have same size
 288         // as number of fields in xmlstring
 289         // loop through structures and put them in descriptor
 290 
 291         StringTokenizer st = new StringTokenizer(inStr, "<> \t\n\r\f");
 292 
 293         boolean inFld = false;
 294         boolean inDesc = false;
 295         String fieldName = null;
 296         String fieldValue = null;
 297 
 298 
 299         while (st.hasMoreTokens()) {  // loop through tokens
 300             String tok = st.nextToken();
 301 
 302             if (tok.equalsIgnoreCase("FIELD")) {
 303                 inFld = true;
 304             } else if (tok.equalsIgnoreCase("/FIELD")) {
 305                 if ((fieldName != null) && (fieldValue != null)) {
 306                     fieldName =
 307                         fieldName.substring(fieldName.indexOf('"') + 1,
 308                                             fieldName.lastIndexOf('"'));
 309                     final Object fieldValueObject =
 310                         parseQuotedFieldValue(fieldValue);
 311                     setField(fieldName, fieldValueObject);
 312                 }
 313                 fieldName = null;
 314                 fieldValue = null;
 315                 inFld = false;
 316             } else if (tok.equalsIgnoreCase("DESCRIPTOR")) {
 317                 inDesc = true;
 318             } else if (tok.equalsIgnoreCase("/DESCRIPTOR")) {
 319                 inDesc = false;
 320                 fieldName = null;
 321                 fieldValue = null;
 322                 inFld = false;
 323             } else if (inFld && inDesc) {
 324                 // want kw=value, eg, name="myname" value="myvalue"
 325                 int eq_separator = tok.indexOf('=');
 326                 if (eq_separator > 0) {
 327                     String kwPart = tok.substring(0,eq_separator);
 328                     String valPart = tok.substring(eq_separator+1);
 329                     if (kwPart.equalsIgnoreCase("NAME"))
 330                         fieldName = valPart;
 331                     else if (kwPart.equalsIgnoreCase("VALUE"))
 332                         fieldValue = valPart;
 333                     else {  // xml parse exception
 334                         final String msg =
 335                             "Expected `name' or `value', got `" + tok + "'";
 336                         throw new XMLParseException(msg);
 337                     }
 338                 } else { // xml parse exception
 339                     final String msg =
 340                         "Expected `keyword=value', got `" + tok + "'";
 341                     throw new XMLParseException(msg);
 342                 }
 343             }
 344         }  // while tokens
 345         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 346             MODELMBEAN_LOGGER.log(Level.TRACE,
 347                     "Descriptor(XMLString) Exit");
 348         }
 349     }
 350 
 351     /**
 352      * Constructor taking field names and field values.  Neither array
 353      * can be null.
 354      *
 355      * @param fieldNames String array of field names.  No elements of
 356      * this array can be null.
 357      * @param fieldValues Object array of the corresponding field
 358      * values.  Elements of the array can be null. The
 359      * <code>fieldValue</code> must be valid for the
 360      * <code>fieldName</code> (as defined in method {@link #isValid
 361      * isValid})
 362      *
 363      * <p>Note: array sizes of parameters should match. If both arrays
 364      * are empty, then an empty descriptor is created.</p>
 365      *
 366      * @exception RuntimeOperationsException for illegal value for
 367      * field Names or field Values.  The array lengths must be equal.
 368      * If the descriptor construction fails for any reason, this
 369      * exception will be thrown.
 370      *
 371      */
 372     public DescriptorSupport(String[] fieldNames, Object[] fieldValues)
 373             throws RuntimeOperationsException {
 374         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 375             MODELMBEAN_LOGGER.log(Level.TRACE,
 376                     "Descriptor(fieldNames,fieldObjects) Constructor");
 377         }
 378 
 379         if ((fieldNames == null) || (fieldValues == null) ||
 380             (fieldNames.length != fieldValues.length)) {
 381             if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 382                 MODELMBEAN_LOGGER.log(Level.TRACE,
 383                         "Descriptor(fieldNames,fieldObjects)" +
 384                         " Illegal arguments");
 385             }
 386 
 387             final String msg =
 388                 "Null or invalid fieldNames or fieldValues";
 389             final RuntimeException iae = new IllegalArgumentException(msg);
 390             throw new RuntimeOperationsException(iae, msg);
 391         }
 392 
 393         /* populate internal structure with fields */
 394         init(null);
 395         for (int i=0; i < fieldNames.length; i++) {
 396             // setField will throw an exception if a fieldName is be null.
 397             // the fieldName and fieldValue will be validated in setField.
 398             setField(fieldNames[i], fieldValues[i]);
 399         }
 400         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 401             MODELMBEAN_LOGGER.log(Level.TRACE,
 402                     "Descriptor(fieldNames,fieldObjects) Exit");
 403         }
 404     }
 405 
 406     /**
 407      * Constructor taking fields in the <i>fieldName=fieldValue</i>
 408      * format.
 409      *
 410      * @param fields String array with each element containing a
 411      * field name and value.  If this array is null or empty, then the
 412      * default constructor will be executed. Null strings or empty
 413      * strings will be ignored.
 414      *
 415      * <p>All field values should be Strings.  If the field values are
 416      * not Strings, the programmer will have to reset or convert these
 417      * fields correctly.
 418      *
 419      * <p>Note: Each string should be of the form
 420      * <i>fieldName=fieldValue</i>.  The field name
 421      * ends at the first {@code =} character; for example if the String
 422      * is {@code a=b=c} then the field name is {@code a} and its value
 423      * is {@code b=c}.
 424      *
 425      * @exception RuntimeOperationsException for illegal value for
 426      * field Names or field Values.  The field must contain an
 427      * "=". "=fieldValue", "fieldName", and "fieldValue" are illegal.
 428      * FieldName cannot be null.  "fieldName=" will cause the value to
 429      * be null.  If the descriptor construction fails for any reason,
 430      * this exception will be thrown.
 431      *
 432      */
 433     public DescriptorSupport(String... fields)
 434     {
 435         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 436             MODELMBEAN_LOGGER.log(Level.TRACE,
 437                     "Descriptor(String... fields) Constructor");
 438         }
 439         init(null);
 440         if (( fields == null ) || ( fields.length == 0))
 441             return;
 442 
 443         init(null);
 444 
 445         for (int i=0; i < fields.length; i++) {
 446             if ((fields[i] == null) || (fields[i].equals(""))) {
 447                 continue;
 448             }
 449             int eq_separator = fields[i].indexOf('=');
 450             if (eq_separator < 0) {
 451                 // illegal if no = or is first character
 452                 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 453                     MODELMBEAN_LOGGER.log(Level.TRACE,
 454                             "Descriptor(String... fields) " +
 455                             "Illegal arguments: field does not have " +
 456                             "'=' as a name and value separator");
 457                 }
 458                 final String msg = "Field in invalid format: no equals sign";
 459                 final RuntimeException iae = new IllegalArgumentException(msg);
 460                 throw new RuntimeOperationsException(iae, msg);
 461             }
 462 
 463             String fieldName = fields[i].substring(0,eq_separator);
 464             String fieldValue = null;
 465             if (eq_separator < fields[i].length()) {
 466                 // = is not in last character
 467                 fieldValue = fields[i].substring(eq_separator+1);
 468             }
 469 
 470             if (fieldName.equals("")) {
 471                 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 472                     MODELMBEAN_LOGGER.log(Level.TRACE,
 473                             "Descriptor(String... fields) " +
 474                             "Illegal arguments: fieldName is empty");
 475                 }
 476 
 477                 final String msg = "Field in invalid format: no fieldName";
 478                 final RuntimeException iae = new IllegalArgumentException(msg);
 479                 throw new RuntimeOperationsException(iae, msg);
 480             }
 481 
 482             setField(fieldName,fieldValue);
 483         }
 484         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 485             MODELMBEAN_LOGGER.log(Level.TRACE,
 486                     "Descriptor(String... fields) Exit");
 487         }
 488     }
 489 
 490     private void init(Map<String, ?> initMap) {
 491         descriptorMap =
 492                 new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER);
 493         if (initMap != null)
 494             descriptorMap.putAll(initMap);
 495     }
 496 
 497     // Implementation of the Descriptor interface
 498 
 499 
 500     public synchronized Object getFieldValue(String fieldName)
 501             throws RuntimeOperationsException {
 502 
 503         if ((fieldName == null) || (fieldName.equals(""))) {
 504             if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 505                 MODELMBEAN_LOGGER.log(Level.TRACE,
 506                         "Illegal arguments: null field name");
 507             }
 508             final String msg = "Fieldname requested is null";
 509             final RuntimeException iae = new IllegalArgumentException(msg);
 510             throw new RuntimeOperationsException(iae, msg);
 511         }
 512         Object retValue = descriptorMap.get(fieldName);
 513         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 514             MODELMBEAN_LOGGER.log(Level.TRACE,
 515                     "getFieldValue(String fieldName = " + fieldName + ") " +
 516                     "Returns '" + retValue + "'");
 517         }
 518         return(retValue);
 519     }
 520 
 521     public synchronized void setField(String fieldName, Object fieldValue)
 522             throws RuntimeOperationsException {
 523 
 524         // field name cannot be null or empty
 525         if ((fieldName == null) || (fieldName.equals(""))) {
 526             if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 527                 MODELMBEAN_LOGGER.log(Level.TRACE,
 528                         "Illegal arguments: null or empty field name");
 529             }
 530 
 531             final String msg = "Field name to be set is null or empty";
 532             final RuntimeException iae = new IllegalArgumentException(msg);
 533             throw new RuntimeOperationsException(iae, msg);
 534         }
 535 
 536         if (!validateField(fieldName, fieldValue)) {
 537             if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 538                 MODELMBEAN_LOGGER.log(Level.TRACE,
 539                         "Illegal arguments");
 540             }
 541 
 542             final String msg =
 543                 "Field value invalid: " + fieldName + "=" + fieldValue;
 544             final RuntimeException iae = new IllegalArgumentException(msg);
 545             throw new RuntimeOperationsException(iae, msg);
 546         }
 547 
 548         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 549             MODELMBEAN_LOGGER.log(Level.TRACE, "Entry: setting '"
 550                     + fieldName + "' to '" + fieldValue + "'");
 551         }
 552 
 553         // Since we do not remove any existing entry with this name,
 554         // the field will preserve whatever case it had, ignoring
 555         // any difference there might be in fieldName.
 556         descriptorMap.put(fieldName, fieldValue);
 557     }
 558 
 559     public synchronized String[] getFields() {
 560         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 561             MODELMBEAN_LOGGER.log(Level.TRACE, "Entry");
 562         }
 563         int numberOfEntries = descriptorMap.size();
 564 
 565         String[] responseFields = new String[numberOfEntries];
 566         Set<Map.Entry<String, Object>> returnedSet = descriptorMap.entrySet();
 567 
 568         int i = 0;
 569 
 570         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 571             MODELMBEAN_LOGGER.log(Level.TRACE,
 572                     "Returning " + numberOfEntries + " fields");
 573         }
 574         for (Iterator<Map.Entry<String, Object>> iter = returnedSet.iterator();
 575              iter.hasNext(); i++) {
 576             Map.Entry<String, Object> currElement = iter.next();
 577 
 578             if (currElement == null) {
 579                 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 580                     MODELMBEAN_LOGGER.log(Level.TRACE,
 581                             "Element is null");
 582                 }
 583             } else {
 584                 Object currValue = currElement.getValue();
 585                 if (currValue == null) {
 586                     responseFields[i] = currElement.getKey() + "=";
 587                 } else {
 588                     if (currValue instanceof java.lang.String) {
 589                         responseFields[i] =
 590                             currElement.getKey() + "=" + currValue.toString();
 591                     } else {
 592                         responseFields[i] =
 593                             currElement.getKey() + "=(" +
 594                             currValue.toString() + ")";
 595                     }
 596                 }
 597             }
 598         }
 599 
 600         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 601             MODELMBEAN_LOGGER.log(Level.TRACE, "Exit");
 602         }
 603 
 604         return responseFields;
 605     }
 606 
 607     public synchronized String[] getFieldNames() {
 608         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 609             MODELMBEAN_LOGGER.log(Level.TRACE, "Entry");
 610         }
 611         int numberOfEntries = descriptorMap.size();
 612 
 613         String[] responseFields = new String[numberOfEntries];
 614         Set<Map.Entry<String, Object>> returnedSet = descriptorMap.entrySet();
 615 
 616         int i = 0;
 617 
 618         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 619             MODELMBEAN_LOGGER.log(Level.TRACE,
 620                     "Returning " + numberOfEntries + " fields");
 621         }
 622 
 623         for (Iterator<Map.Entry<String, Object>> iter = returnedSet.iterator();
 624              iter.hasNext(); i++) {
 625             Map.Entry<String, Object> currElement = iter.next();
 626 
 627             if (( currElement == null ) || (currElement.getKey() == null)) {
 628                 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 629                     MODELMBEAN_LOGGER.log(Level.TRACE, "Field is null");
 630                 }
 631             } else {
 632                 responseFields[i] = currElement.getKey().toString();
 633             }
 634         }
 635 
 636         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 637             MODELMBEAN_LOGGER.log(Level.TRACE, "Exit");
 638         }
 639 
 640         return responseFields;
 641     }
 642 
 643 
 644     public synchronized Object[] getFieldValues(String... fieldNames) {
 645         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 646             MODELMBEAN_LOGGER.log(Level.TRACE, "Entry");
 647         }
 648         // if fieldNames == null return all values
 649         // if fieldNames is String[0] return no values
 650 
 651         final int numberOfEntries =
 652             (fieldNames == null) ? descriptorMap.size() : fieldNames.length;
 653         final Object[] responseFields = new Object[numberOfEntries];
 654 
 655         int i = 0;
 656 
 657         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 658             MODELMBEAN_LOGGER.log(Level.TRACE,
 659                     "Returning " + numberOfEntries + " fields");
 660         }
 661 
 662         if (fieldNames == null) {
 663             for (Object value : descriptorMap.values())
 664                 responseFields[i++] = value;
 665         } else {
 666             for (i=0; i < fieldNames.length; i++) {
 667                 if ((fieldNames[i] == null) || (fieldNames[i].equals(""))) {
 668                     responseFields[i] = null;
 669                 } else {
 670                     responseFields[i] = getFieldValue(fieldNames[i]);
 671                 }
 672             }
 673         }
 674 
 675         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 676             MODELMBEAN_LOGGER.log(Level.TRACE, "Exit");
 677         }
 678 
 679         return responseFields;
 680     }
 681 
 682     public synchronized void setFields(String[] fieldNames,
 683                                        Object[] fieldValues)
 684             throws RuntimeOperationsException {
 685 
 686         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 687             MODELMBEAN_LOGGER.log(Level.TRACE, "Entry");
 688         }
 689 
 690         if ((fieldNames == null) || (fieldValues == null) ||
 691             (fieldNames.length != fieldValues.length)) {
 692             if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 693                 MODELMBEAN_LOGGER.log(Level.TRACE,
 694                         "Illegal arguments");
 695             }
 696 
 697             final String msg = "fieldNames and fieldValues are null or invalid";
 698             final RuntimeException iae = new IllegalArgumentException(msg);
 699             throw new RuntimeOperationsException(iae, msg);
 700         }
 701 
 702         for (int i=0; i < fieldNames.length; i++) {
 703             if (( fieldNames[i] == null) || (fieldNames[i].equals(""))) {
 704                 if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 705                     MODELMBEAN_LOGGER.log(Level.TRACE,
 706                             "Null field name encountered at element " + i);
 707                 }
 708                 final String msg = "fieldNames is null or invalid";
 709                 final RuntimeException iae = new IllegalArgumentException(msg);
 710                 throw new RuntimeOperationsException(iae, msg);
 711             }
 712             setField(fieldNames[i], fieldValues[i]);
 713         }
 714         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 715             MODELMBEAN_LOGGER.log(Level.TRACE, "Exit");
 716         }
 717     }
 718 
 719     /**
 720      * Returns a new Descriptor which is a duplicate of the Descriptor.
 721      *
 722      * @exception RuntimeOperationsException for illegal value for
 723      * field Names or field Values.  If the descriptor construction
 724      * fails for any reason, this exception will be thrown.
 725      */
 726 
 727     @Override
 728     public synchronized Object clone() throws RuntimeOperationsException {
 729         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 730             MODELMBEAN_LOGGER.log(Level.TRACE, "Entry");
 731         }
 732         return(new DescriptorSupport(this));
 733     }
 734 
 735     public synchronized void removeField(String fieldName) {
 736         if ((fieldName == null) || (fieldName.equals(""))) {
 737             return;
 738         }
 739 
 740         descriptorMap.remove(fieldName);
 741     }
 742 
 743     /**
 744      * Compares this descriptor to the given object.  The objects are equal if
 745      * the given object is also a Descriptor, and if the two Descriptors have
 746      * the same field names (possibly differing in case) and the same
 747      * associated values.  The respective values for a field in the two
 748      * Descriptors are equal if the following conditions hold:
 749      *
 750      * <ul>
 751      * <li>If one value is null then the other must be too.</li>
 752      * <li>If one value is a primitive array then the other must be a primitive
 753      * array of the same type with the same elements.</li>
 754      * <li>If one value is an object array then the other must be too and
 755      * {@link java.util.Arrays#deepEquals(Object[],Object[]) Arrays.deepEquals}
 756      * must return true.</li>
 757      * <li>Otherwise {@link Object#equals(Object)} must return true.</li>
 758      * </ul>
 759      *
 760      * @param o the object to compare with.
 761      *
 762      * @return {@code true} if the objects are the same; {@code false}
 763      * otherwise.
 764      *
 765      */
 766     // Note: this Javadoc is copied from javax.management.Descriptor
 767     //       due to 6369229.
 768     @Override
 769     public synchronized boolean equals(Object o) {
 770         if (o == this)
 771             return true;
 772         if (! (o instanceof Descriptor))
 773             return false;
 774         if (o instanceof ImmutableDescriptor)
 775             return o.equals(this);
 776         return new ImmutableDescriptor(descriptorMap).equals(o);
 777     }
 778 
 779     /**
 780      * <p>Returns the hash code value for this descriptor.  The hash
 781      * code is computed as the sum of the hash codes for each field in
 782      * the descriptor.  The hash code of a field with name {@code n}
 783      * and value {@code v} is {@code n.toLowerCase().hashCode() ^ h}.
 784      * Here {@code h} is the hash code of {@code v}, computed as
 785      * follows:</p>
 786      *
 787      * <ul>
 788      * <li>If {@code v} is null then {@code h} is 0.</li>
 789      * <li>If {@code v} is a primitive array then {@code h} is computed using
 790      * the appropriate overloading of {@code java.util.Arrays.hashCode}.</li>
 791      * <li>If {@code v} is an object array then {@code h} is computed using
 792      * {@link java.util.Arrays#deepHashCode(Object[]) Arrays.deepHashCode}.</li>
 793      * <li>Otherwise {@code h} is {@code v.hashCode()}.</li>
 794      * </ul>
 795      *
 796      * @return A hash code value for this object.
 797      *
 798      */
 799     // Note: this Javadoc is copied from javax.management.Descriptor
 800     //       due to 6369229.
 801     @Override
 802     public synchronized int hashCode() {
 803         final int size = descriptorMap.size();
 804         // descriptorMap is sorted with a comparator that ignores cases.
 805         //
 806         return Util.hashCode(
 807                 descriptorMap.keySet().toArray(new String[size]),
 808                 descriptorMap.values().toArray(new Object[size]));
 809     }
 810 
 811     /**
 812      * Returns true if all of the fields have legal values given their
 813      * names.
 814      * <P>
 815      * This implementation does not support  interoperating with a directory
 816      * or lookup service. Thus, conforming to the specification, no checking is
 817      * done on the <i>"export"</i> field.
 818      * <P>
 819      * Otherwise this implementation returns false if:
 820      * <UL>
 821      * <LI> name and descriptorType fieldNames are not defined, or
 822      * null, or empty, or not String
 823      * <LI> class, role, getMethod, setMethod fieldNames, if defined,
 824      * are null or not String
 825      * <LI> persistPeriod, currencyTimeLimit, lastUpdatedTimeStamp,
 826      * lastReturnedTimeStamp if defined, are null, or not a Numeric
 827      * String or not a Numeric Value {@literal >= -1}
 828      * <LI> log fieldName, if defined, is null, or not a Boolean or
 829      * not a String with value "t", "f", "true", "false". These String
 830      * values must not be case sensitive.
 831      * <LI> visibility fieldName, if defined, is null, or not a
 832      * Numeric String or a not Numeric Value {@literal >= 1 and <= 4}
 833      * <LI> severity fieldName, if defined, is null, or not a Numeric
 834      * String or not a Numeric Value {@literal >= 0 and <= 6}<br>
 835      * <LI> persistPolicy fieldName, if defined, is null, or not one of
 836      * the following strings:<br>
 837      *   "OnUpdate", "OnTimer", "NoMoreOftenThan", "OnUnregister", "Always",
 838      *   "Never". These String values must not be case sensitive.<br>
 839      * </UL>
 840      *
 841      * @exception RuntimeOperationsException If the validity checking
 842      * fails for any reason, this exception will be thrown.
 843      */
 844 
 845     public synchronized boolean isValid() throws RuntimeOperationsException {
 846         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 847             MODELMBEAN_LOGGER.log(Level.TRACE, "Entry");
 848         }
 849         // verify that the descriptor is valid, by iterating over each field...
 850 
 851         Set<Map.Entry<String, Object>> returnedSet = descriptorMap.entrySet();
 852 
 853         if (returnedSet == null) {   // null descriptor, not valid
 854             if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 855                 MODELMBEAN_LOGGER.log(Level.TRACE,
 856                         "isValid() Returns false (null set)");
 857             }
 858             return false;
 859         }
 860         // must have a name and descriptor type field
 861         String thisName = (String)(this.getFieldValue("name"));
 862         String thisDescType = (String)(getFieldValue("descriptorType"));
 863 
 864         if ((thisName == null) || (thisDescType == null) ||
 865             (thisName.equals("")) || (thisDescType.equals(""))) {
 866             return false;
 867         }
 868 
 869         // According to the descriptor type we validate the fields contained
 870 
 871         for (Map.Entry<String, Object> currElement : returnedSet) {
 872             if (currElement != null) {
 873                 if (currElement.getValue() != null) {
 874                     // validate the field valued...
 875                     if (validateField((currElement.getKey()).toString(),
 876                                       (currElement.getValue()).toString())) {
 877                         continue;
 878                     } else {
 879                         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 880                             MODELMBEAN_LOGGER.log(Level.TRACE,
 881                                     "Field " + currElement.getKey() + "=" +
 882                                     currElement.getValue() + " is not valid");
 883                         }
 884                         return false;
 885                     }
 886                 }
 887             }
 888         }
 889 
 890         // fell through, all fields OK
 891         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
 892             MODELMBEAN_LOGGER.log(Level.TRACE,
 893                     "isValid() Returns true");
 894         }
 895         return true;
 896     }
 897 
 898 
 899     // worker routine for isValid()
 900     // name is not null
 901     // descriptorType is not null
 902     // getMethod and setMethod are not null
 903     // persistPeriod is numeric
 904     // currencyTimeLimit is numeric
 905     // lastUpdatedTimeStamp is numeric
 906     // visibility is 1-4
 907     // severity is 0-6
 908     // log is T or F
 909     // role is not null
 910     // class is not null
 911     // lastReturnedTimeStamp is numeric
 912 
 913 
 914     private boolean validateField(String fldName, Object fldValue) {
 915         if ((fldName == null) || (fldName.equals("")))
 916             return false;
 917         String SfldValue = "";
 918         boolean isAString = false;
 919         if ((fldValue != null) && (fldValue instanceof java.lang.String)) {
 920             SfldValue = (String) fldValue;
 921             isAString = true;
 922         }
 923 
 924         boolean nameOrDescriptorType =
 925             (fldName.equalsIgnoreCase("Name") ||
 926              fldName.equalsIgnoreCase("DescriptorType"));
 927         if (nameOrDescriptorType ||
 928             fldName.equalsIgnoreCase("SetMethod") ||
 929             fldName.equalsIgnoreCase("GetMethod") ||
 930             fldName.equalsIgnoreCase("Role") ||
 931             fldName.equalsIgnoreCase("Class")) {
 932             if (fldValue == null || !isAString)
 933                 return false;
 934             if (nameOrDescriptorType && SfldValue.equals(""))
 935                 return false;
 936             return true;
 937         } else if (fldName.equalsIgnoreCase("visibility")) {
 938             long v;
 939             if ((fldValue != null) && (isAString)) {
 940                 v = toNumeric(SfldValue);
 941             } else if (fldValue instanceof java.lang.Integer) {
 942                 v = ((Integer)fldValue).intValue();
 943             } else return false;
 944 
 945             if (v >= 1 &&  v <= 4)
 946                 return true;
 947             else
 948                 return false;
 949         } else if (fldName.equalsIgnoreCase("severity")) {
 950 
 951             long v;
 952             if ((fldValue != null) && (isAString)) {
 953                 v = toNumeric(SfldValue);
 954             } else if (fldValue instanceof java.lang.Integer) {
 955                 v = ((Integer)fldValue).intValue();
 956             } else return false;
 957 
 958             return (v >= 0 && v <= 6);
 959         } else if (fldName.equalsIgnoreCase("PersistPolicy")) {
 960             return (((fldValue != null) && (isAString)) &&
 961                     ( SfldValue.equalsIgnoreCase("OnUpdate") ||
 962                       SfldValue.equalsIgnoreCase("OnTimer") ||
 963                       SfldValue.equalsIgnoreCase("NoMoreOftenThan") ||
 964                       SfldValue.equalsIgnoreCase("Always") ||
 965                       SfldValue.equalsIgnoreCase("Never") ||
 966                       SfldValue.equalsIgnoreCase("OnUnregister")));
 967         } else if (fldName.equalsIgnoreCase("PersistPeriod") ||
 968                    fldName.equalsIgnoreCase("CurrencyTimeLimit") ||
 969                    fldName.equalsIgnoreCase("LastUpdatedTimeStamp") ||
 970                    fldName.equalsIgnoreCase("LastReturnedTimeStamp")) {
 971 
 972             long v;
 973             if ((fldValue != null) && (isAString)) {
 974                 v = toNumeric(SfldValue);
 975             } else if (fldValue instanceof java.lang.Number) {
 976                 v = ((Number)fldValue).longValue();
 977             } else return false;
 978 
 979             return (v >= -1);
 980         } else if (fldName.equalsIgnoreCase("log")) {
 981             return ((fldValue instanceof java.lang.Boolean) ||
 982                     (isAString &&
 983                      (SfldValue.equalsIgnoreCase("T") ||
 984                       SfldValue.equalsIgnoreCase("true") ||
 985                       SfldValue.equalsIgnoreCase("F") ||
 986                       SfldValue.equalsIgnoreCase("false") )));
 987         }
 988 
 989         // default to true, it is a field we aren't validating (user etc.)
 990         return true;
 991     }
 992 
 993 
 994 
 995     /**
 996      * <p>Returns an XML String representing the descriptor.</p>
 997      *
 998      * <p>The format is not defined, but an implementation must
 999      * ensure that the string returned by this method can be
1000      * used to build an equivalent descriptor when instantiated
1001      * using the constructor {@link #DescriptorSupport(String)
1002      * DescriptorSupport(String inStr)}.</p>
1003      *
1004      * <p>Fields which are not String objects will have toString()
1005      * called on them to create the value. The value will be
1006      * enclosed in parentheses.  It is not guaranteed that you can
1007      * reconstruct these objects unless they have been
1008      * specifically set up to support toString() in a meaningful
1009      * format and have a matching constructor that accepts a
1010      * String in the same format.</p>
1011      *
1012      * <p>If the descriptor is empty the following String is
1013      * returned: &lt;Descriptor&gt;&lt;/Descriptor&gt;</p>
1014      *
1015      * @return the XML string.
1016      *
1017      * @exception RuntimeOperationsException for illegal value for
1018      * field Names or field Values.  If the XML formatted string
1019      * construction fails for any reason, this exception will be
1020      * thrown.
1021      */
1022     public synchronized String toXMLString() {
1023         final StringBuilder buf = new StringBuilder("<Descriptor>");
1024         Set<Map.Entry<String, Object>> returnedSet = descriptorMap.entrySet();
1025         for (Map.Entry<String, Object> currElement : returnedSet) {
1026             final String name = currElement.getKey();
1027             Object value = currElement.getValue();
1028             String valueString = null;
1029             /* Set valueString to non-null if and only if this is a string that
1030                cannot be confused with the encoding of an object.  If it
1031                could be so confused (surrounded by parentheses) then we
1032                call makeFieldValue as for any non-String object and end
1033                up with an encoding like "(java.lang.String/(thing))".  */
1034             if (value instanceof String) {
1035                 final String svalue = (String) value;
1036                 if (!svalue.startsWith("(") || !svalue.endsWith(")"))
1037                     valueString = quote(svalue);
1038             }
1039             if (valueString == null)
1040                 valueString = makeFieldValue(value);
1041             buf.append("<field name=\"").append(name).append("\" value=\"")
1042                 .append(valueString).append("\"></field>");
1043         }
1044         buf.append("</Descriptor>");
1045         return buf.toString();
1046     }
1047 
1048     private static final String[] entities = {
1049         "  ",
1050         "\"&quot;",
1051         "<&lt;",
1052         ">&gt;",
1053         "&&amp;",
1054         "\r
",
1055         "\t&#9;",
1056         "\n
",
1057         "\f",
1058     };
1059     private static final Map<String,Character> entityToCharMap =
1060         new HashMap<String,Character>();
1061     private static final String[] charToEntityMap;
1062 
1063     static {
1064         char maxChar = 0;
1065         for (int i = 0; i < entities.length; i++) {
1066             final char c = entities[i].charAt(0);
1067             if (c > maxChar)
1068                 maxChar = c;
1069         }
1070         charToEntityMap = new String[maxChar + 1];
1071         for (int i = 0; i < entities.length; i++) {
1072             final char c = entities[i].charAt(0);
1073             final String entity = entities[i].substring(1);
1074             charToEntityMap[c] = entity;
1075             entityToCharMap.put(entity, c);
1076         }
1077     }
1078 
1079     private static boolean isMagic(char c) {
1080         return (c < charToEntityMap.length && charToEntityMap[c] != null);
1081     }
1082 
1083     /*
1084      * Quote the string so that it will be acceptable to the (String)
1085      * constructor.  Since the parsing code in that constructor is fairly
1086      * stupid, we're obliged to quote apparently innocuous characters like
1087      * space, <, and >.  In a future version, we should rewrite the parser
1088      * and only quote " plus either \ or & (depending on the quote syntax).
1089      */
1090     private static String quote(String s) {
1091         boolean found = false;
1092         for (int i = 0; i < s.length(); i++) {
1093             if (isMagic(s.charAt(i))) {
1094                 found = true;
1095                 break;
1096             }
1097         }
1098         if (!found)
1099             return s;
1100         final StringBuilder buf = new StringBuilder();
1101         for (int i = 0; i < s.length(); i++) {
1102             char c = s.charAt(i);
1103             if (isMagic(c))
1104                 buf.append(charToEntityMap[c]);
1105             else
1106                 buf.append(c);
1107         }
1108         return buf.toString();
1109     }
1110 
1111     private static String unquote(String s) throws XMLParseException {
1112         if (!s.startsWith("\"") || !s.endsWith("\""))
1113             throw new XMLParseException("Value must be quoted: <" + s + ">");
1114         final StringBuilder buf = new StringBuilder();
1115         final int len = s.length() - 1;
1116         for (int i = 1; i < len; i++) {
1117             final char c = s.charAt(i);
1118             final int semi;
1119             final Character quoted;
1120             if (c == '&'
1121                 && (semi = s.indexOf(';', i + 1)) >= 0
1122                 && ((quoted = entityToCharMap.get(s.substring(i, semi+1)))
1123                     != null)) {
1124                 buf.append(quoted);
1125                 i = semi;
1126             } else
1127                 buf.append(c);
1128         }
1129         return buf.toString();
1130     }
1131 
1132     /**
1133      * Make the string that will go inside "..." for a value that is not
1134      * a plain String.
1135      * @throws RuntimeOperationsException if the value cannot be encoded.
1136      */
1137     private static String makeFieldValue(Object value) {
1138         if (value == null)
1139             return "(null)";
1140 
1141         Class<?> valueClass = value.getClass();
1142         try {
1143             valueClass.getConstructor(String.class);
1144         } catch (NoSuchMethodException e) {
1145             final String msg =
1146                 "Class " + valueClass + " does not have a public " +
1147                 "constructor with a single string arg";
1148             final RuntimeException iae = new IllegalArgumentException(msg);
1149             throw new RuntimeOperationsException(iae,
1150                                                  "Cannot make XML descriptor");
1151         } catch (SecurityException e) {
1152             // OK: we'll pretend the constructor is there
1153             // too bad if it's not: we'll find out when we try to
1154             // reconstruct the DescriptorSupport
1155         }
1156 
1157         final String quotedValueString = quote(value.toString());
1158 
1159         return "(" + valueClass.getName() + "/" + quotedValueString + ")";
1160     }
1161 
1162     /*
1163      * Parse a field value from the XML produced by toXMLString().
1164      * Given a descriptor XML containing <field name="nnn" value="vvv">,
1165      * the argument to this method will be "vvv" (a string including the
1166      * containing quote characters).  If vvv begins and ends with parentheses,
1167      * then it may contain:
1168      * - the characters "null", in which case the result is null;
1169      * - a value of the form "some.class.name/xxx", in which case the
1170      * result is equivalent to `new some.class.name("xxx")';
1171      * - some other string, in which case the result is that string,
1172      * without the parentheses.
1173      */
1174     private static Object parseQuotedFieldValue(String s)
1175             throws XMLParseException {
1176         s = unquote(s);
1177         if (s.equalsIgnoreCase("(null)"))
1178             return null;
1179         if (!s.startsWith("(") || !s.endsWith(")"))
1180             return s;
1181         final int slash = s.indexOf('/');
1182         if (slash < 0) {
1183             // compatibility: old code didn't include class name
1184             return s.substring(1, s.length() - 1);
1185         }
1186         final String className = s.substring(1, slash);
1187 
1188         final Constructor<?> constr;
1189         try {
1190             ReflectUtil.checkPackageAccess(className);
1191             final ClassLoader contextClassLoader =
1192                 Thread.currentThread().getContextClassLoader();
1193             final Class<?> c =
1194                 Class.forName(className, false, contextClassLoader);
1195             constr = c.getConstructor(new Class<?>[] {String.class});
1196         } catch (Exception e) {
1197             throw new XMLParseException(e,
1198                                         "Cannot parse value: <" + s + ">");
1199         }
1200         final String arg = s.substring(slash + 1, s.length() - 1);
1201         try {
1202             return constr.newInstance(new Object[] {arg});
1203         } catch (Exception e) {
1204             final String msg =
1205                 "Cannot construct instance of " + className +
1206                 " with arg: <" + s + ">";
1207             throw new XMLParseException(e, msg);
1208         }
1209     }
1210 
1211     /**
1212      * Returns a human readable string representing the
1213      * descriptor.  The string will be in the format of
1214      * "fieldName=fieldValue,fieldName2=fieldValue2,..."<br>
1215      *
1216      * If there are no fields in the descriptor, then an empty String
1217      * is returned.<br>
1218      *
1219      * If a fieldValue is an object then the toString() method is
1220      * called on it and its returned value is used as the value for
1221      * the field enclosed in parenthesis.
1222      *
1223      * @exception RuntimeOperationsException for illegal value for
1224      * field Names or field Values.  If the descriptor string fails
1225      * for any reason, this exception will be thrown.
1226      */
1227     @Override
1228     public synchronized String toString() {
1229         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
1230             MODELMBEAN_LOGGER.log(Level.TRACE, "Entry");
1231         }
1232 
1233         String respStr = "";
1234         String[] fields = getFields();
1235 
1236         if ((fields == null) || (fields.length == 0)) {
1237             if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
1238                 MODELMBEAN_LOGGER.log(Level.TRACE, "Empty Descriptor");
1239             }
1240             return respStr;
1241         }
1242 
1243         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
1244             MODELMBEAN_LOGGER.log(Level.TRACE,
1245                     "Printing " + fields.length + " fields");
1246         }
1247 
1248         for (int i=0; i < fields.length; i++) {
1249             if (i == (fields.length - 1)) {
1250                 respStr = respStr.concat(fields[i]);
1251             } else {
1252                 respStr = respStr.concat(fields[i] + ", ");
1253             }
1254         }
1255 
1256         if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
1257             MODELMBEAN_LOGGER.log(Level.TRACE, "Exit returning " + respStr);
1258         }
1259 
1260         return respStr;
1261     }
1262 
1263     // utility to convert to int, returns -2 if bogus.
1264 
1265     private long toNumeric(String inStr) {
1266         try {
1267             return java.lang.Long.parseLong(inStr);
1268         } catch (Exception e) {
1269             return -2;
1270         }
1271     }
1272 
1273 
1274     /**
1275      * Deserializes a {@link DescriptorSupport} from an {@link
1276      * ObjectInputStream}.
1277      */
1278     private void readObject(ObjectInputStream in)
1279             throws IOException, ClassNotFoundException {
1280         ObjectInputStream.GetField fields = in.readFields();
1281         Map<String, Object> descriptor = cast(fields.get("descriptor", null));
1282         init(null);
1283         if (descriptor != null) {
1284             descriptorMap.putAll(descriptor);
1285         }
1286     }
1287 
1288 
1289     /**
1290      * Serializes a {@link DescriptorSupport} to an {@link ObjectOutputStream}.
1291      */
1292     /* If you set jmx.serial.form to "1.2.0" or "1.2.1", then we are
1293        bug-compatible with those versions.  Specifically, field names
1294        are forced to lower-case before being written.  This
1295        contradicts the spec, which, though it does not mention
1296        serialization explicitly, does say that the case of field names
1297        is preserved.  But in 1.2.0 and 1.2.1, this requirement was not
1298        met.  Instead, field names in the descriptor map were forced to
1299        lower case.  Those versions expect this to have happened to a
1300        descriptor they deserialize and e.g. getFieldValue will not
1301        find a field whose name is spelt with a different case.
1302     */
1303     private void writeObject(ObjectOutputStream out) throws IOException {
1304         ObjectOutputStream.PutField fields = out.putFields();
1305         boolean compat = "1.0".equals(serialForm);
1306         if (compat)
1307             fields.put("currClass", currClass);
1308 
1309         /* Purge the field "targetObject" from the DescriptorSupport before
1310          * serializing since the referenced object is typically not
1311          * serializable.  We do this here rather than purging the "descriptor"
1312          * variable below because that HashMap doesn't do case-insensitivity.
1313          * See CR 6332962.
1314          */
1315         SortedMap<String, Object> startMap = descriptorMap;
1316         if (startMap.containsKey("targetObject")) {
1317             startMap = new TreeMap<String, Object>(descriptorMap);
1318             startMap.remove("targetObject");
1319         }
1320 
1321         final HashMap<String, Object> descriptor;
1322         if (compat || "1.2.0".equals(serialForm) ||
1323                 "1.2.1".equals(serialForm)) {
1324             descriptor = new HashMap<String, Object>();
1325             for (Map.Entry<String, Object> entry : startMap.entrySet())
1326                 descriptor.put(entry.getKey().toLowerCase(), entry.getValue());
1327         } else
1328             descriptor = new HashMap<String, Object>(startMap);
1329 
1330         fields.put("descriptor", descriptor);
1331         out.writeFields();
1332     }
1333 
1334 }