src/share/classes/java/beans/Beans.java
author malenkov
Thu, 05 Feb 2009 17:00:57 +0300
changeset 1077 27dabbdfdcac
parent 0 37a05a11f281
child 1238 57914fd9382f
child 2395 00cd9dc3c2b5
permissions -rw-r--r--
6669869: Beans.isDesignTime() and other queries should be per-AppContext
Reviewed-by: peterz, rupashka
     1 /*
     2  * Copyright 1996-2009 Sun Microsystems, Inc.  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.  Sun designates this
     8  * particular file as subject to the "Classpath" exception as provided
     9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
    23  * have any questions.
    24  */
    25 
    26 package java.beans;
    27 
    28 import com.sun.beans.finder.ClassFinder;
    29 
    30 import java.applet.Applet;
    31 import java.applet.AppletContext;
    32 import java.applet.AppletStub;
    33 import java.applet.AudioClip;
    34 
    35 import java.awt.GraphicsEnvironment;
    36 import java.awt.Image;
    37 
    38 import java.beans.beancontext.BeanContext;
    39 
    40 import java.io.IOException;
    41 import java.io.InputStream;
    42 import java.io.ObjectInputStream;
    43 import java.io.ObjectStreamClass;
    44 import java.io.StreamCorruptedException;
    45 
    46 import java.net.URL;
    47 
    48 import java.security.AccessController;
    49 import java.security.PrivilegedAction;
    50 
    51 import java.util.Enumeration;
    52 import java.util.Hashtable;
    53 import java.util.Iterator;
    54 import java.util.Vector;
    55 
    56 import sun.awt.AppContext;
    57 
    58 /**
    59  * This class provides some general purpose beans control methods.
    60  */
    61 
    62 public class Beans {
    63     private static final Object DESIGN_TIME = new Object();
    64     private static final Object GUI_AVAILABLE = new Object();
    65 
    66     /**
    67      * <p>
    68      * Instantiate a JavaBean.
    69      * </p>
    70      *
    71      * @param     cls         the class-loader from which we should create
    72      *                        the bean.  If this is null, then the system
    73      *                        class-loader is used.
    74      * @param     beanName    the name of the bean within the class-loader.
    75      *                        For example "sun.beanbox.foobah"
    76      *
    77      * @exception ClassNotFoundException if the class of a serialized
    78      *              object could not be found.
    79      * @exception IOException if an I/O error occurs.
    80      */
    81 
    82     public static Object instantiate(ClassLoader cls, String beanName) throws IOException, ClassNotFoundException {
    83         return Beans.instantiate(cls, beanName, null, null);
    84     }
    85 
    86     /**
    87      * <p>
    88      * Instantiate a JavaBean.
    89      * </p>
    90      *
    91      * @param     cls         the class-loader from which we should create
    92      *                        the bean.  If this is null, then the system
    93      *                        class-loader is used.
    94      * @param     beanName    the name of the bean within the class-loader.
    95      *                        For example "sun.beanbox.foobah"
    96      * @param     beanContext The BeanContext in which to nest the new bean
    97      *
    98      * @exception ClassNotFoundException if the class of a serialized
    99      *              object could not be found.
   100      * @exception IOException if an I/O error occurs.
   101      */
   102 
   103     public static Object instantiate(ClassLoader cls, String beanName, BeanContext beanContext) throws IOException, ClassNotFoundException {
   104         return Beans.instantiate(cls, beanName, beanContext, null);
   105     }
   106 
   107     /**
   108      * Instantiate a bean.
   109      * <p>
   110      * The bean is created based on a name relative to a class-loader.
   111      * This name should be a dot-separated name such as "a.b.c".
   112      * <p>
   113      * In Beans 1.0 the given name can indicate either a serialized object
   114      * or a class.  Other mechanisms may be added in the future.  In
   115      * beans 1.0 we first try to treat the beanName as a serialized object
   116      * name then as a class name.
   117      * <p>
   118      * When using the beanName as a serialized object name we convert the
   119      * given beanName to a resource pathname and add a trailing ".ser" suffix.
   120      * We then try to load a serialized object from that resource.
   121      * <p>
   122      * For example, given a beanName of "x.y", Beans.instantiate would first
   123      * try to read a serialized object from the resource "x/y.ser" and if
   124      * that failed it would try to load the class "x.y" and create an
   125      * instance of that class.
   126      * <p>
   127      * If the bean is a subtype of java.applet.Applet, then it is given
   128      * some special initialization.  First, it is supplied with a default
   129      * AppletStub and AppletContext.  Second, if it was instantiated from
   130      * a classname the applet's "init" method is called.  (If the bean was
   131      * deserialized this step is skipped.)
   132      * <p>
   133      * Note that for beans which are applets, it is the caller's responsiblity
   134      * to call "start" on the applet.  For correct behaviour, this should be done
   135      * after the applet has been added into a visible AWT container.
   136      * <p>
   137      * Note that applets created via beans.instantiate run in a slightly
   138      * different environment than applets running inside browsers.  In
   139      * particular, bean applets have no access to "parameters", so they may
   140      * wish to provide property get/set methods to set parameter values.  We
   141      * advise bean-applet developers to test their bean-applets against both
   142      * the JDK appletviewer (for a reference browser environment) and the
   143      * BDK BeanBox (for a reference bean container).
   144      *
   145      * @param     cls         the class-loader from which we should create
   146      *                        the bean.  If this is null, then the system
   147      *                        class-loader is used.
   148      * @param     beanName    the name of the bean within the class-loader.
   149      *                        For example "sun.beanbox.foobah"
   150      * @param     beanContext The BeanContext in which to nest the new bean
   151      * @param     initializer The AppletInitializer for the new bean
   152      *
   153      * @exception ClassNotFoundException if the class of a serialized
   154      *              object could not be found.
   155      * @exception IOException if an I/O error occurs.
   156      */
   157 
   158     public static Object instantiate(ClassLoader cls, String beanName, BeanContext beanContext, AppletInitializer initializer)
   159                         throws IOException, ClassNotFoundException {
   160 
   161         InputStream ins;
   162         ObjectInputStream oins = null;
   163         Object result = null;
   164         boolean serialized = false;
   165         IOException serex = null;
   166 
   167         // If the given classloader is null, we check if an
   168         // system classloader is available and (if so)
   169         // use that instead.
   170         // Note that calls on the system class loader will
   171         // look in the bootstrap class loader first.
   172         if (cls == null) {
   173             try {
   174                 cls = ClassLoader.getSystemClassLoader();
   175             } catch (SecurityException ex) {
   176                 // We're not allowed to access the system class loader.
   177                 // Drop through.
   178             }
   179         }
   180 
   181         // Try to find a serialized object with this name
   182         final String serName = beanName.replace('.','/').concat(".ser");
   183         final ClassLoader loader = cls;
   184         ins = (InputStream)AccessController.doPrivileged
   185             (new PrivilegedAction() {
   186                 public Object run() {
   187                     if (loader == null)
   188                         return ClassLoader.getSystemResourceAsStream(serName);
   189                     else
   190                         return loader.getResourceAsStream(serName);
   191                 }
   192         });
   193         if (ins != null) {
   194             try {
   195                 if (cls == null) {
   196                     oins = new ObjectInputStream(ins);
   197                 } else {
   198                     oins = new ObjectInputStreamWithLoader(ins, cls);
   199                 }
   200                 result = oins.readObject();
   201                 serialized = true;
   202                 oins.close();
   203             } catch (IOException ex) {
   204                 ins.close();
   205                 // Drop through and try opening the class.  But remember
   206                 // the exception in case we can't find the class either.
   207                 serex = ex;
   208             } catch (ClassNotFoundException ex) {
   209                 ins.close();
   210                 throw ex;
   211             }
   212         }
   213 
   214         if (result == null) {
   215             // No serialized object, try just instantiating the class
   216             Class cl;
   217 
   218             try {
   219                 cl = ClassFinder.findClass(beanName, cls);
   220             } catch (ClassNotFoundException ex) {
   221                 // There is no appropriate class.  If we earlier tried to
   222                 // deserialize an object and got an IO exception, throw that,
   223                 // otherwise rethrow the ClassNotFoundException.
   224                 if (serex != null) {
   225                     throw serex;
   226                 }
   227                 throw ex;
   228             }
   229 
   230             /*
   231              * Try to instantiate the class.
   232              */
   233 
   234             try {
   235                 result = cl.newInstance();
   236             } catch (Exception ex) {
   237                 // We have to remap the exception to one in our signature.
   238                 // But we pass extra information in the detail message.
   239                 throw new ClassNotFoundException("" + cl + " : " + ex, ex);
   240             }
   241         }
   242 
   243         if (result != null) {
   244 
   245             // Ok, if the result is an applet initialize it.
   246 
   247             AppletStub stub = null;
   248 
   249             if (result instanceof Applet) {
   250                 Applet  applet      = (Applet) result;
   251                 boolean needDummies = initializer == null;
   252 
   253                 if (needDummies) {
   254 
   255                     // Figure our the codebase and docbase URLs.  We do this
   256                     // by locating the URL for a known resource, and then
   257                     // massaging the URL.
   258 
   259                     // First find the "resource name" corresponding to the bean
   260                     // itself.  So a serialzied bean "a.b.c" would imply a
   261                     // resource name of "a/b/c.ser" and a classname of "x.y"
   262                     // would imply a resource name of "x/y.class".
   263 
   264                     final String resourceName;
   265 
   266                     if (serialized) {
   267                         // Serialized bean
   268                         resourceName = beanName.replace('.','/').concat(".ser");
   269                     } else {
   270                         // Regular class
   271                         resourceName = beanName.replace('.','/').concat(".class");
   272                     }
   273 
   274                     URL objectUrl = null;
   275                     URL codeBase  = null;
   276                     URL docBase   = null;
   277 
   278                     // Now get the URL correponding to the resource name.
   279 
   280                     final ClassLoader cloader = cls;
   281                     objectUrl = (URL)
   282                         AccessController.doPrivileged
   283                         (new PrivilegedAction() {
   284                             public Object run() {
   285                                 if (cloader == null)
   286                                     return ClassLoader.getSystemResource
   287                                                                 (resourceName);
   288                                 else
   289                                     return cloader.getResource(resourceName);
   290                             }
   291                     });
   292 
   293                     // If we found a URL, we try to locate the docbase by taking
   294                     // of the final path name component, and the code base by taking
   295                     // of the complete resourceName.
   296                     // So if we had a resourceName of "a/b/c.class" and we got an
   297                     // objectURL of "file://bert/classes/a/b/c.class" then we would
   298                     // want to set the codebase to "file://bert/classes/" and the
   299                     // docbase to "file://bert/classes/a/b/"
   300 
   301                     if (objectUrl != null) {
   302                         String s = objectUrl.toExternalForm();
   303 
   304                         if (s.endsWith(resourceName)) {
   305                             int ix   = s.length() - resourceName.length();
   306                             codeBase = new URL(s.substring(0,ix));
   307                             docBase  = codeBase;
   308 
   309                             ix = s.lastIndexOf('/');
   310 
   311                             if (ix >= 0) {
   312                                 docBase = new URL(s.substring(0,ix+1));
   313                             }
   314                         }
   315                     }
   316 
   317                     // Setup a default context and stub.
   318                     BeansAppletContext context = new BeansAppletContext(applet);
   319 
   320                     stub = (AppletStub)new BeansAppletStub(applet, context, codeBase, docBase);
   321                     applet.setStub(stub);
   322                 } else {
   323                     initializer.initialize(applet, beanContext);
   324                 }
   325 
   326                 // now, if there is a BeanContext, add the bean, if applicable.
   327 
   328                 if (beanContext != null) {
   329                     beanContext.add(result);
   330                 }
   331 
   332                 // If it was deserialized then it was already init-ed.
   333                 // Otherwise we need to initialize it.
   334 
   335                 if (!serialized) {
   336                     // We need to set a reasonable initial size, as many
   337                     // applets are unhappy if they are started without
   338                     // having been explicitly sized.
   339                     applet.setSize(100,100);
   340                     applet.init();
   341                 }
   342 
   343                 if (needDummies) {
   344                   ((BeansAppletStub)stub).active = true;
   345                 } else initializer.activate(applet);
   346 
   347             } else if (beanContext != null) beanContext.add(result);
   348         }
   349 
   350         return result;
   351     }
   352 
   353 
   354     /**
   355      * From a given bean, obtain an object representing a specified
   356      * type view of that source object.
   357      * <p>
   358      * The result may be the same object or a different object.  If
   359      * the requested target view isn't available then the given
   360      * bean is returned.
   361      * <p>
   362      * This method is provided in Beans 1.0 as a hook to allow the
   363      * addition of more flexible bean behaviour in the future.
   364      *
   365      * @param bean        Object from which we want to obtain a view.
   366      * @param targetType  The type of view we'd like to get.
   367      *
   368      */
   369     public static Object getInstanceOf(Object bean, Class<?> targetType) {
   370         return bean;
   371     }
   372 
   373     /**
   374      * Check if a bean can be viewed as a given target type.
   375      * The result will be true if the Beans.getInstanceof method
   376      * can be used on the given bean to obtain an object that
   377      * represents the specified targetType type view.
   378      *
   379      * @param bean  Bean from which we want to obtain a view.
   380      * @param targetType  The type of view we'd like to get.
   381      * @return "true" if the given bean supports the given targetType.
   382      *
   383      */
   384     public static boolean isInstanceOf(Object bean, Class<?> targetType) {
   385         return Introspector.isSubclass(bean.getClass(), targetType);
   386     }
   387 
   388 
   389     /**
   390      * Test if we are in design-mode.
   391      *
   392      * @return  True if we are running in an application construction
   393      *          environment.
   394      *
   395      * @see DesignMode
   396      */
   397     public static boolean isDesignTime() {
   398         Object value = AppContext.getAppContext().get(DESIGN_TIME);
   399         return (value instanceof Boolean) && (Boolean) value;
   400     }
   401 
   402     /**
   403      * Determines whether beans can assume a GUI is available.
   404      *
   405      * @return  True if we are running in an environment where beans
   406      *     can assume that an interactive GUI is available, so they
   407      *     can pop up dialog boxes, etc.  This will normally return
   408      *     true in a windowing environment, and will normally return
   409      *     false in a server environment or if an application is
   410      *     running as part of a batch job.
   411      *
   412      * @see Visibility
   413      *
   414      */
   415     public static boolean isGuiAvailable() {
   416         Object value = AppContext.getAppContext().get(GUI_AVAILABLE);
   417         return (value instanceof Boolean) ? (Boolean) value : !GraphicsEnvironment.isHeadless();
   418     }
   419 
   420     /**
   421      * Used to indicate whether of not we are running in an application
   422      * builder environment.
   423      *
   424      * <p>Note that this method is security checked
   425      * and is not available to (for example) untrusted applets.
   426      * More specifically, if there is a security manager,
   427      * its <code>checkPropertiesAccess</code>
   428      * method is called. This could result in a SecurityException.
   429      *
   430      * @param isDesignTime  True if we're in an application builder tool.
   431      * @exception  SecurityException  if a security manager exists and its
   432      *             <code>checkPropertiesAccess</code> method doesn't allow setting
   433      *              of system properties.
   434      * @see SecurityManager#checkPropertiesAccess
   435      */
   436 
   437     public static void setDesignTime(boolean isDesignTime)
   438                         throws SecurityException {
   439         SecurityManager sm = System.getSecurityManager();
   440         if (sm != null) {
   441             sm.checkPropertiesAccess();
   442         }
   443         AppContext.getAppContext().put(DESIGN_TIME, Boolean.valueOf(isDesignTime));
   444     }
   445 
   446     /**
   447      * Used to indicate whether of not we are running in an environment
   448      * where GUI interaction is available.
   449      *
   450      * <p>Note that this method is security checked
   451      * and is not available to (for example) untrusted applets.
   452      * More specifically, if there is a security manager,
   453      * its <code>checkPropertiesAccess</code>
   454      * method is called. This could result in a SecurityException.
   455      *
   456      * @param isGuiAvailable  True if GUI interaction is available.
   457      * @exception  SecurityException  if a security manager exists and its
   458      *             <code>checkPropertiesAccess</code> method doesn't allow setting
   459      *              of system properties.
   460      * @see SecurityManager#checkPropertiesAccess
   461      */
   462 
   463     public static void setGuiAvailable(boolean isGuiAvailable)
   464                         throws SecurityException {
   465         SecurityManager sm = System.getSecurityManager();
   466         if (sm != null) {
   467             sm.checkPropertiesAccess();
   468         }
   469         AppContext.getAppContext().put(GUI_AVAILABLE, Boolean.valueOf(isGuiAvailable));
   470     }
   471 }
   472 
   473 /**
   474  * This subclass of ObjectInputStream delegates loading of classes to
   475  * an existing ClassLoader.
   476  */
   477 
   478 class ObjectInputStreamWithLoader extends ObjectInputStream
   479 {
   480     private ClassLoader loader;
   481 
   482     /**
   483      * Loader must be non-null;
   484      */
   485 
   486     public ObjectInputStreamWithLoader(InputStream in, ClassLoader loader)
   487             throws IOException, StreamCorruptedException {
   488 
   489         super(in);
   490         if (loader == null) {
   491             throw new IllegalArgumentException("Illegal null argument to ObjectInputStreamWithLoader");
   492         }
   493         this.loader = loader;
   494     }
   495 
   496     /**
   497      * Use the given ClassLoader rather than using the system class
   498      */
   499     protected Class resolveClass(ObjectStreamClass classDesc)
   500         throws IOException, ClassNotFoundException {
   501 
   502         String cname = classDesc.getName();
   503         return ClassFinder.resolveClass(cname, this.loader);
   504     }
   505 }
   506 
   507 /**
   508  * Package private support class.  This provides a default AppletContext
   509  * for beans which are applets.
   510  */
   511 
   512 class BeansAppletContext implements AppletContext {
   513     Applet target;
   514     Hashtable imageCache = new Hashtable();
   515 
   516     BeansAppletContext(Applet target) {
   517         this.target = target;
   518     }
   519 
   520     public AudioClip getAudioClip(URL url) {
   521         // We don't currently support audio clips in the Beans.instantiate
   522         // applet context, unless by some luck there exists a URL content
   523         // class that can generate an AudioClip from the audio URL.
   524         try {
   525             return (AudioClip) url.getContent();
   526         } catch (Exception ex) {
   527             return null;
   528         }
   529     }
   530 
   531     public synchronized Image getImage(URL url) {
   532         Object o = imageCache.get(url);
   533         if (o != null) {
   534             return (Image)o;
   535         }
   536         try {
   537             o = url.getContent();
   538             if (o == null) {
   539                 return null;
   540             }
   541             if (o instanceof Image) {
   542                 imageCache.put(url, o);
   543                 return (Image) o;
   544             }
   545             // Otherwise it must be an ImageProducer.
   546             Image img = target.createImage((java.awt.image.ImageProducer)o);
   547             imageCache.put(url, img);
   548             return img;
   549 
   550         } catch (Exception ex) {
   551             return null;
   552         }
   553     }
   554 
   555     public Applet getApplet(String name) {
   556         return null;
   557     }
   558 
   559     public Enumeration getApplets() {
   560         Vector applets = new Vector();
   561         applets.addElement(target);
   562         return applets.elements();
   563     }
   564 
   565     public void showDocument(URL url) {
   566         // We do nothing.
   567     }
   568 
   569     public void showDocument(URL url, String target) {
   570         // We do nothing.
   571     }
   572 
   573     public void showStatus(String status) {
   574         // We do nothing.
   575     }
   576 
   577     public void setStream(String key, InputStream stream)throws IOException{
   578         // We do nothing.
   579     }
   580 
   581     public InputStream getStream(String key){
   582         // We do nothing.
   583         return null;
   584     }
   585 
   586     public Iterator getStreamKeys(){
   587         // We do nothing.
   588         return null;
   589     }
   590 }
   591 
   592 /**
   593  * Package private support class.  This provides an AppletStub
   594  * for beans which are applets.
   595  */
   596 class BeansAppletStub implements AppletStub {
   597     transient boolean active;
   598     transient Applet target;
   599     transient AppletContext context;
   600     transient URL codeBase;
   601     transient URL docBase;
   602 
   603     BeansAppletStub(Applet target,
   604                 AppletContext context, URL codeBase,
   605                                 URL docBase) {
   606         this.target = target;
   607         this.context = context;
   608         this.codeBase = codeBase;
   609         this.docBase = docBase;
   610     }
   611 
   612     public boolean isActive() {
   613         return active;
   614     }
   615 
   616     public URL getDocumentBase() {
   617         // use the root directory of the applet's class-loader
   618         return docBase;
   619     }
   620 
   621     public URL getCodeBase() {
   622         // use the directory where we found the class or serialized object.
   623         return codeBase;
   624     }
   625 
   626     public String getParameter(String name) {
   627         return null;
   628     }
   629 
   630     public AppletContext getAppletContext() {
   631         return context;
   632     }
   633 
   634     public void appletResize(int width, int height) {
   635         // we do nothing.
   636     }
   637 }