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