1 /*
   2  * Copyright (c) 2000, 2008, 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.openmbean;
  27 
  28 import com.sun.jmx.mbeanserver.GetPropertyAction;
  29 import java.io.IOException;
  30 import java.io.InvalidObjectException;
  31 import java.io.ObjectInputStream;
  32 import java.io.Serializable;
  33 import java.security.AccessController;
  34 import java.security.PrivilegedAction;
  35 import java.util.Arrays;
  36 import java.util.Collections;
  37 import java.util.List;
  38 import javax.management.Descriptor;
  39 import javax.management.ImmutableDescriptor;
  40 
  41 /**
  42  * The <code>OpenType</code> class is the parent abstract class of all classes which describe the actual <i>open type</i>
  43  * of open data values.
  44  * <p>
  45  * An <i>open type</i> is defined by:
  46  * <ul>
  47  *  <li>the fully qualified Java class name of the open data values this type describes;
  48  *      note that only a limited set of Java classes is allowed for open data values
  49  *      (see {@link #ALLOWED_CLASSNAMES_LIST ALLOWED_CLASSNAMES_LIST}),</li>
  50  *  <li>its name,</li>
  51  *  <li>its description.</li>
  52  * </ul>
  53  *
  54  * @param <T> the Java type that instances described by this type must
  55  * have.  For example, {@link SimpleType#INTEGER} is a {@code
  56  * SimpleType<Integer>} which is a subclass of {@code OpenType<Integer>},
  57  * meaning that an attribute, parameter, or return value that is described
  58  * as a {@code SimpleType.INTEGER} must have Java type
  59  * {@link Integer}.
  60  *
  61  * @since 1.5
  62  */
  63 public abstract class OpenType<T> implements Serializable {
  64 
  65     /* Serial version */
  66     static final long serialVersionUID = -9195195325186646468L;
  67 
  68 
  69     /**
  70      * List of the fully qualified names of the Java classes allowed for open
  71      * data values. A multidimensional array of any one of these classes or
  72      * their corresponding primitive types is also an allowed class for open
  73      * data values.
  74      *
  75        <pre>ALLOWED_CLASSNAMES_LIST = {
  76         "java.lang.Void",
  77         "java.lang.Boolean",
  78         "java.lang.Character",
  79         "java.lang.Byte",
  80         "java.lang.Short",
  81         "java.lang.Integer",
  82         "java.lang.Long",
  83         "java.lang.Float",
  84         "java.lang.Double",
  85         "java.lang.String",
  86         "java.math.BigDecimal",
  87         "java.math.BigInteger",
  88         "java.util.Date",
  89         "javax.management.ObjectName",
  90         CompositeData.class.getName(),
  91         TabularData.class.getName() } ;
  92        </pre>
  93      *
  94      */
  95     public static final List<String> ALLOWED_CLASSNAMES_LIST =
  96       Collections.unmodifiableList(
  97         Arrays.asList(
  98           "java.lang.Void",
  99           "java.lang.Boolean",
 100           "java.lang.Character",
 101           "java.lang.Byte",
 102           "java.lang.Short",
 103           "java.lang.Integer",
 104           "java.lang.Long",
 105           "java.lang.Float",
 106           "java.lang.Double",
 107           "java.lang.String",
 108           "java.math.BigDecimal",
 109           "java.math.BigInteger",
 110           "java.util.Date",
 111           "javax.management.ObjectName",
 112           CompositeData.class.getName(),        // better refer to these two class names like this, rather than hardcoding a string,
 113           TabularData.class.getName()) );       // in case the package of these classes should change (who knows...)
 114 
 115 
 116     /**
 117      * @deprecated Use {@link #ALLOWED_CLASSNAMES_LIST ALLOWED_CLASSNAMES_LIST} instead.
 118      */
 119     @Deprecated
 120     public static final String[] ALLOWED_CLASSNAMES =
 121         ALLOWED_CLASSNAMES_LIST.toArray(new String[0]);
 122 
 123 
 124     /**
 125      * @serial The fully qualified Java class name of open data values this
 126      *         type describes.
 127      */
 128     private String className;
 129 
 130     /**
 131      * @serial The type description (should not be null or empty).
 132      */
 133     private String description;
 134 
 135     /**
 136      * @serial The name given to this type (should not be null or empty).
 137      */
 138     private String typeName;
 139 
 140     /**
 141      * Tells if this type describes an array (checked in constructor).
 142      */
 143     private transient boolean isArray = false;
 144 
 145     /**
 146      * Cached Descriptor for this OpenType, constructed on demand.
 147      */
 148     private transient Descriptor descriptor;
 149 
 150     /* *** Constructor *** */
 151 
 152     /**
 153      * Constructs an <code>OpenType</code> instance (actually a subclass instance as <code>OpenType</code> is abstract),
 154      * checking for the validity of the given parameters.
 155      * The validity constraints are described below for each parameter.
 156      * <br>&nbsp;
 157      * @param  className  The fully qualified Java class name of the open data values this open type describes.
 158      *                    The valid Java class names allowed for open data values are listed in
 159      *                    {@link #ALLOWED_CLASSNAMES_LIST ALLOWED_CLASSNAMES_LIST}.
 160      *                    A multidimensional array of any one of these classes
 161      *                    or their corresponding primitive types is also an allowed class,
 162      *                    in which case the class name follows the rules defined by the method
 163      *                    {@link Class#getName() getName()} of <code>java.lang.Class</code>.
 164      *                    For example, a 3-dimensional array of Strings has for class name
 165      *                    &quot;<code>[[[Ljava.lang.String;</code>&quot; (without the quotes).
 166      * <br>&nbsp;
 167      * @param  typeName  The name given to the open type this instance represents; cannot be a null or empty string.
 168      * <br>&nbsp;
 169      * @param  description  The human readable description of the open type this instance represents;
 170      *                      cannot be a null or empty string.
 171      * <br>&nbsp;
 172      * @throws IllegalArgumentException  if <var>className</var>, <var>typeName</var> or <var>description</var>
 173      *                                   is a null or empty string
 174      * <br>&nbsp;
 175      * @throws OpenDataException  if <var>className</var> is not one of the allowed Java class names for open data
 176      */
 177     protected OpenType(String  className,
 178                        String  typeName,
 179                        String  description) throws OpenDataException {
 180         checkClassNameOverride();
 181         this.typeName = valid("typeName", typeName);
 182         this.description = valid("description", description);
 183         this.className = validClassName(className);
 184         this.isArray = (this.className != null && this.className.startsWith("["));
 185     }
 186 
 187     /* Package-private constructor for callers we trust to get it right. */
 188     OpenType(String className, String typeName, String description,
 189              boolean isArray) {
 190         this.className   = valid("className",className);
 191         this.typeName    = valid("typeName", typeName);
 192         this.description = valid("description", description);
 193         this.isArray     = isArray;
 194     }
 195 
 196     private void checkClassNameOverride() throws SecurityException {
 197         if (this.getClass().getClassLoader() == null)
 198             return;  // We trust bootstrap classes.
 199         if (overridesGetClassName(this.getClass())) {
 200             final GetPropertyAction getExtendOpenTypes =
 201                 new GetPropertyAction("jmx.extend.open.types");
 202             if (AccessController.doPrivileged(getExtendOpenTypes) == null) {
 203                 throw new SecurityException("Cannot override getClassName() " +
 204                         "unless -Djmx.extend.open.types");
 205             }
 206         }
 207     }
 208 
 209     private static boolean overridesGetClassName(final Class<?> c) {
 210         return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
 211             public Boolean run() {
 212                 try {
 213                     return (c.getMethod("getClassName").getDeclaringClass() !=
 214                             OpenType.class);
 215                 } catch (Exception e) {
 216                     return true;  // fail safe
 217                 }
 218             }
 219         });
 220     }
 221 
 222     private static String validClassName(String className) throws OpenDataException {
 223         className   = valid("className", className);
 224 
 225         // Check if className describes an array class, and determines its elements' class name.
 226         // (eg: a 3-dimensional array of Strings has for class name: "[[[Ljava.lang.String;")
 227         //
 228         int n = 0;
 229         while (className.startsWith("[", n)) {
 230             n++;
 231         }
 232         String eltClassName; // class name of array elements
 233         boolean isPrimitiveArray = false;
 234         if (n > 0) {
 235             if (className.startsWith("L", n) && className.endsWith(";")) {
 236                 // removes the n leading '[' + the 'L' characters
 237                 // and the last ';' character
 238                 eltClassName = className.substring(n+1, className.length()-1);
 239             } else if (n == className.length() - 1) {
 240                 // removes the n leading '[' characters
 241                 eltClassName = className.substring(n, className.length());
 242                 isPrimitiveArray = true;
 243             } else {
 244                 throw new OpenDataException("Argument className=\"" + className +
 245                         "\" is not a valid class name");
 246             }
 247         } else {
 248             // not an array
 249             eltClassName = className;
 250         }
 251 
 252         // Check that eltClassName's value is one of the allowed basic data types for open data
 253         //
 254         boolean ok = false;
 255         if (isPrimitiveArray) {
 256             ok = ArrayType.isPrimitiveContentType(eltClassName);
 257         } else {
 258             ok = ALLOWED_CLASSNAMES_LIST.contains(eltClassName);
 259         }
 260         if ( ! ok ) {
 261             throw new OpenDataException("Argument className=\""+ className +
 262                                         "\" is not one of the allowed Java class names for open data.");
 263         }
 264 
 265         return className;
 266     }
 267 
 268     /* Return argValue.trim() provided argValue is neither null nor empty;
 269        otherwise throw IllegalArgumentException.  */
 270     private static String valid(String argName, String argValue) {
 271         if (argValue == null || (argValue = argValue.trim()).isEmpty())
 272             throw new IllegalArgumentException("Argument " + argName +
 273                                                " cannot be null or empty");
 274         return argValue;
 275     }
 276 
 277     /* Package-private access to a Descriptor containing this OpenType. */
 278     synchronized Descriptor getDescriptor() {
 279         if (descriptor == null) {
 280             descriptor = new ImmutableDescriptor(new String[] {"openType"},
 281                                                  new Object[] {this});
 282         }
 283         return descriptor;
 284     }
 285 
 286     /* *** Open type information methods *** */
 287 
 288     /**
 289      * Returns the fully qualified Java class name of the open data values
 290      * this open type describes.
 291      * The only possible Java class names for open data values are listed in
 292      * {@link #ALLOWED_CLASSNAMES_LIST ALLOWED_CLASSNAMES_LIST}.
 293      * A multidimensional array of any one of these classes or their
 294      * corresponding primitive types is also an allowed class,
 295      * in which case the class name follows the rules defined by the method
 296      * {@link Class#getName() getName()} of <code>java.lang.Class</code>.
 297      * For example, a 3-dimensional array of Strings has for class name
 298      * &quot;<code>[[[Ljava.lang.String;</code>&quot; (without the quotes),
 299      * a 3-dimensional array of Integers has for class name
 300      * &quot;<code>[[[Ljava.lang.Integer;</code>&quot; (without the quotes),
 301      * and a 3-dimensional array of int has for class name
 302      * &quot;<code>[[[I</code>&quot; (without the quotes)
 303      *
 304      * @return the class name.
 305      */
 306     public String getClassName() {
 307         return className;
 308     }
 309 
 310     // A version of getClassName() that can only be called from within this
 311     // package and that cannot be overridden.
 312     String safeGetClassName() {
 313         return className;
 314     }
 315 
 316     /**
 317      * Returns the name of this <code>OpenType</code> instance.
 318      *
 319      * @return the type name.
 320      */
 321     public String getTypeName() {
 322 
 323         return typeName;
 324     }
 325 
 326     /**
 327      * Returns the text description of this <code>OpenType</code> instance.
 328      *
 329      * @return the description.
 330      */
 331     public String getDescription() {
 332 
 333         return description;
 334     }
 335 
 336     /**
 337      * Returns <code>true</code> if the open data values this open
 338      * type describes are arrays, <code>false</code> otherwise.
 339      *
 340      * @return true if this is an array type.
 341      */
 342     public boolean isArray() {
 343 
 344         return isArray;
 345     }
 346 
 347     /**
 348      * Tests whether <var>obj</var> is a value for this open type.
 349      *
 350      * @param obj the object to be tested for validity.
 351      *
 352      * @return <code>true</code> if <var>obj</var> is a value for this
 353      * open type, <code>false</code> otherwise.
 354      */
 355     public abstract boolean isValue(Object obj) ;
 356 
 357     /**
 358      * Tests whether values of the given type can be assigned to this open type.
 359      * The default implementation of this method returns true only if the
 360      * types are equal.
 361      *
 362      * @param ot the type to be tested.
 363      *
 364      * @return true if {@code ot} is assignable to this open type.
 365      */
 366     boolean isAssignableFrom(OpenType<?> ot) {
 367         return this.equals(ot);
 368     }
 369 
 370     /* *** Methods overriden from class Object *** */
 371 
 372     /**
 373      * Compares the specified <code>obj</code> parameter with this
 374      * open type instance for equality.
 375      *
 376      * @param obj the object to compare to.
 377      *
 378      * @return true if this object and <code>obj</code> are equal.
 379      */
 380     public abstract boolean equals(Object obj) ;
 381 
 382     public abstract int hashCode() ;
 383 
 384     /**
 385      * Returns a string representation of this open type instance.
 386      *
 387      * @return the string representation.
 388      */
 389     public abstract String toString() ;
 390 
 391     /**
 392      * Deserializes an {@link OpenType} from an {@link java.io.ObjectInputStream}.
 393      */
 394     private void readObject(ObjectInputStream in)
 395             throws IOException, ClassNotFoundException {
 396         checkClassNameOverride();
 397         ObjectInputStream.GetField fields = in.readFields();
 398         final String classNameField;
 399         final String descriptionField;
 400         final String typeNameField;
 401         try {
 402             classNameField =
 403                 validClassName((String) fields.get("className", null));
 404             descriptionField =
 405                 valid("description", (String) fields.get("description", null));
 406             typeNameField =
 407                 valid("typeName", (String) fields.get("typeName", null));
 408         } catch (Exception e) {
 409             IOException e2 = new InvalidObjectException(e.getMessage());
 410             e2.initCause(e);
 411             throw e2;
 412         }
 413         className = classNameField;
 414         description = descriptionField;
 415         typeName = typeNameField;
 416         isArray = (className.startsWith("["));
 417     }
 418 }