/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2002 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.loaders; import java.lang.reflect.*; import java.io.*; import org.openide.ErrorManager; import org.openide.cookies.InstanceCookie; import org.openide.filesystems.FileObject; import org.openide.util.HelpCtx; // imports for findHelp: import java.beans.*; import java.security.PermissionCollection; import java.security.Permissions; import java.util.PropertyPermission; import javax.swing.JComponent; import org.openide.WizardDescriptor; import org.openide.util.Utilities; import org.openide.util.SharedClassObject; import org.openide.util.Lookup; // Encapsulates working with classes and optimize it. /** An instance cookie implementation that works with files or entries. * * @author Jan Jancura, Jaroslav Tulach */ public class InstanceSupport extends Object implements InstanceCookie.Of { /** entry to work with */ private MultiDataObject.Entry entry; /** throw exception during loading of the class */ private Throwable clazzException; /** the class of the instance */ private Class clazz; /** the class is applet */ private Boolean applet; /** the class is bean */ private Boolean bean; /** New support for given entry. The file is taken from the * entry and is updated if the entry moves or renames itself. * @param entry entry to create instance from */ public InstanceSupport(MultiDataObject.Entry entry) { this.entry = entry; } /** Accessor for the entry. Needed in InstanceDataObject.Ser * @return the entry */ MultiDataObject.Entry entry () { return entry; } // main methods ......................................................................................................... /* The bean name for the instance. * @return the name for the instance */ public String instanceName () { return instanceOrigin ().getPackageName ('.'); } /* The class of the instance represented by this cookie. * Can be used to test whether the instance is of valid * class before it is created. * * <p>Note that <code>SecurityException</code> could be thrown * if an attempt was made e.g. to create an instance of a class * in a <code>java.*</code> package. Clients of <code>InstanceSupport</code> * which expect that this might happen (e.g. creating instances of * freeform user classes) should explicitly catch security exceptions * and convert them into whatever else as needed. * * @return the class of the instance * @exception IOException an I/O error occured * @exception ClassNotFoundException the class has not been found */ public Class instanceClass () throws java.io.IOException, ClassNotFoundException { return instanceClass(null); } final Class instanceClass (ClassLoader cl) throws java.io.IOException, ClassNotFoundException { if (clazzException != null) { if (clazzException instanceof IOException) throw (IOException)clazzException; else if (clazzException instanceof ClassNotFoundException) throw (ClassNotFoundException)clazzException; else throw (ThreadDeath)clazzException; } if (clazz != null) return clazz; //System.out.println ("getClass " + fileName ); // NOI18N try { if (isSerialized ()) { // NOI18N // read class from ser file InputStream is = instanceOrigin ().getInputStream (); try { clazz = readClass (is); return clazz; } finally { is.close (); } } else { // find class by class loader clazz = findClass (instanceName (), cl); if (clazz == null) throw new ClassNotFoundException (instanceName()); return clazz; } } catch (IOException ex) { ErrorManager.getDefault().annotate (ex, ErrorManager.UNKNOWN, "From file: " + entry.getFile(), null, null, null); // NOI18N clazzException = ex; throw ex; } catch (ClassNotFoundException ex) { ErrorManager.getDefault().annotate (ex, ErrorManager.UNKNOWN, "From file: " + entry.getFile(), null, null, null); // NOI18N clazzException = ex; throw ex; } catch (RuntimeException re) { // turn other throwables into class not found ex. clazzException = new ClassNotFoundException("From file: " + entry.getFile() + " due to: " + re.toString()); // NOI18N ErrorManager.getDefault ().annotate (clazzException, re); throw (ClassNotFoundException) clazzException; } catch (LinkageError le) { clazzException = new ClassNotFoundException("From file: " + entry.getFile() + " due to: " + le.toString()); // NOI18N ErrorManager.getDefault ().annotate (clazzException, le); throw (ClassNotFoundException) clazzException; } } /*Query to found out if the object created by this cookie is * instance of given type. Does: * <pre> * Class actualClass = instanceClass (); * result = type.isAsignableFrom (actualClass); * </pre> * * @param type the class type we want to check * @return true if this cookie can produce object of given type */ public boolean instanceOf (Class type) { try { return type.isAssignableFrom (instanceClass ()); } catch (IOException ex) { return false; } catch (ClassNotFoundException ex) { return false; } } /** Returns the origin of the instance. * @see org.openide.cookies.InstanceCookie.Origin#instanceOrigin * @return the origin */ public FileObject instanceOrigin () { // return getEntry ().getFile (); return entry.getFile (); } /* * @return an object to work with * @exception IOException an I/O error occured * @exception ClassNotFoundException the class has not been found */ public Object instanceCreate () throws java.io.IOException, ClassNotFoundException { try { if (isSerialized ()) { // create from ser file BufferedInputStream bis = new BufferedInputStream(instanceOrigin().getInputStream(), 1024); org.openide.util.io.NbObjectInputStream nbis = new org.openide.util.io.NbObjectInputStream(bis); Object o = nbis.readObject(); nbis.close(); return o; } else { Class c = instanceClass (); if (SharedClassObject.class.isAssignableFrom (c)) { // special support return SharedClassObject.findObject (c, true); } else { // create new instance return c.newInstance(); } } } catch (IOException ex) { // [PENDING] annotate with localized message ErrorManager.getDefault ().annotate (ex, instanceName ()); throw ex; } catch (ClassNotFoundException ex) { throw ex; } catch (Exception e) { // turn other throwables into class not found ex. throw new ClassNotFoundException(e.toString(), e); } catch (LinkageError e) { throw new ClassNotFoundException(e.toString(), e); } } /** Is this an applet? * @return <code>true</code> if this class is an {@link java.applet.Applet} * @deprecated This method probably should not be used, as it catches a variety of potentially * serious exceptions and errors, and swallows them so as to produce a simple boolean * result. (Notifying them all would be inappropriate as they typically come from user * code.) Better to directly parse the bytecode, using e.g. the classfile module, * which is immune to this class of errors. */ public boolean isApplet () { if (applet != null) return applet.booleanValue (); boolean b = instanceOf (java.applet.Applet.class); applet = b ? Boolean.TRUE : Boolean.FALSE; return b; } /** Is this a standalone executable? * @return <code>true</code> if this class has main method * (e.g., <code>public static void main (String[] arguments)</code>). * @deprecated This method probably should not be used, as it catches a variety of potentially * serious exceptions and errors, and swallows them so as to produce a simple boolean * result. (Notifying them all would be inappropriate as they typically come from user * code.) Better to directly parse the bytecode, using e.g. the classfile module, * which is immune to this class of errors. */ public boolean isExecutable () { try { Method main = instanceClass ().getDeclaredMethod ("main", new Class[] { // NOI18N String[].class }); int m = main.getModifiers (); return Modifier.isPublic (m) && Modifier.isStatic (m) && Void.TYPE.equals ( main.getReturnType () ); } catch (Exception ex) { return false; } catch (LinkageError re) { // false when other errors occur (NoClassDefFoundError etc...) return false; } } /** Is this a JavaBean? * @return <code>true</code> if this class represents JavaBean (is public and has a public default constructor). * @deprecated This method probably should not be used, as it catches a variety of potentially * serious exceptions and errors, and swallows them so as to produce a simple boolean * result. (Notifying them all would be inappropriate as they typically come from user * code.) Better to directly parse the bytecode, using e.g. the classfile module, * which is immune to this class of errors. */ public boolean isJavaBean () { if (bean != null) return bean.booleanValue (); // if from ser file => definitely it is a java bean if (isSerialized ()) { bean = Boolean.TRUE; return true; } // try to find out... try { Class clazz = instanceClass(); int modif = clazz.getModifiers (); if (!Modifier.isPublic (modif) || Modifier.isAbstract (modif)) { bean = Boolean.FALSE; return false; } Constructor c; try { c = clazz.getConstructor (new Class [0]); } catch (NoSuchMethodException e) { bean = Boolean.FALSE; return false; } if ((c == null) || !Modifier.isPublic (c.getModifiers ())) { bean = Boolean.FALSE; return false; } // check: if the class is an inner class, all outer classes have // to be public and in the static context: for (Class outer = clazz.getDeclaringClass(); outer != null; outer = outer.getDeclaringClass()) { // check if the enclosed class is static if (!Modifier.isStatic(modif)) return false; modif = outer.getModifiers(); // ... and the enclosing class is public if (!Modifier.isPublic(modif)) return false; } } catch (Exception ex) { bean = Boolean.FALSE; return true; } catch (LinkageError e) { // false when other errors occur (NoClassDefFoundError etc...) bean = Boolean.FALSE; return false; } // okay, this is bean... // return isBean = java.io.Serializable.class.isAssignableFrom (clazz); bean = Boolean.TRUE; return true; } /** Is this an interface? * @return <code>true</code> if the class is an interface * @deprecated This method probably should not be used, as it catches a variety of potentially * serious exceptions and errors, and swallows them so as to produce a simple boolean * result. (Notifying them all would be inappropriate as they typically come from user * code.) Better to directly parse the bytecode, using e.g. the classfile module, * which is immune to this class of errors. */ public boolean isInterface () { try { return instanceClass ().isInterface (); } catch (IOException ex) { return false; } catch (ClassNotFoundException cnfe) { return false; } } public String toString () { return instanceName (); } /** Find context help for some instance. * Helper method useful in nodes or data objects that provide an instance cookie; * they may choose to supply their own help context based on this. * All API classes which can provide help contexts will be tested for * (including <code>HelpCtx</code> instances themselves). * <code>JComponent</code>s are checked for an attached help ID property, * as with {@link HelpCtx#findHelp} (but not traversing parents). * <p>Also, partial compliance with the JavaHelp section on JavaBeans help is implemented--i.e., * if a Bean in its <code>BeanInfo</code> provides a <code>BeanDescriptor</code> which * has the attribute <code>helpID</code>, this will be returned. The value is not * defaulted (because it would usually be nonsense and would mask a useful default * help for the instance container), nor is the help set specification checked, * since someone should have installed the proper help set anyway, and the APIs * cannot add a new reference to a help set automatically. * See <code>javax.help.HelpUtilities.getIDStringFromBean</code> for details. * <p>Special IDs are added, corresponding to the class name, for all standard visual components. * @param instance the instance to check for help (it is permissible for the {@link InstanceCookie#instanceCreate} to return <code>null</code>) * @return the help context found on the instance or inferred from a Bean, * or <code>null</code> if none was found (or it was {@link HelpCtx#DEFAULT_HELP}) */ public static HelpCtx findHelp (InstanceCookie instance) { Class clazz = null; try { clazz = instance.instanceClass (); // First try known API classes. if ( HelpCtx.Provider.class.isAssignableFrom(clazz) || WizardDescriptor.Panel.class.isAssignableFrom (clazz) || //ManifestSection.FileSystemSection.class.isAssignableFrom (clazz) || HelpCtx.class.isAssignableFrom (clazz)) { HelpCtx test; Object obj = instance.instanceCreate (); if (obj instanceof HelpCtx.Provider) test = ((HelpCtx.Provider)obj).getHelpCtx(); else if (obj instanceof WizardDescriptor.Panel) test = ((WizardDescriptor.Panel) obj).getHelp (); /* else if (obj instanceof ManifestSection.FileSystemSection) test = ((ManifestSection.FileSystemSection) obj).getHelpCtx (); */ else if (obj instanceof HelpCtx) test = (HelpCtx) obj; else test = null; // obj==null or bad cookie if (test != null && ! test.equals (HelpCtx.DEFAULT_HELP)) return test; } // If a component, look for attached help. if (JComponent.class.isAssignableFrom (clazz)) { JComponent comp = (JComponent) instance.instanceCreate (); if (comp != null) { String hid = (String) comp.getClientProperty ("HelpID"); // NOI18N if (hid != null) return new HelpCtx (hid); } } // [a.n] I have moved the code here as those components's BeanInfo do not contain helpID // - it is faster // Help on some standard components. Note that borders/layout managers do not really work here. if (java.awt.Component.class.isAssignableFrom (clazz) || java.awt.MenuComponent.class.isAssignableFrom (clazz)) { String name = clazz.getName (); String[] pkgs = new String[] { "java.awt.", "javax.swing.", "javax.swing.border." }; // NOI18N for (int i = 0; i < pkgs.length; i++) { if (name.startsWith (pkgs[i]) && name.substring (pkgs[i].length ()).indexOf ('.') == -1) return new HelpCtx (name); } } // Look for Bean help. Also works on components not found above. BeanDescriptor desc = Utilities.getBeanInfo (clazz).getBeanDescriptor (); if (desc != null) { // [PENDING] ideally would also look for a help set and add that to the system // set if found, but there is no API for this at the moment String val = (String) desc.getValue ("helpID"); // NOI18N if (val != null) return new HelpCtx (val); } // All failed. return null; } catch (Exception e) { ErrorManager.getDefault().notify(e); return null; } } /** Test whether the instance represents serialized version of a class * or not. * @return true if the file entry extension is ser */ private boolean isSerialized () { return instanceOrigin ().getExt ().equals ("ser"); // NOI18N } /** Reads a class from input stream. Expects a serialized object to be stored * in the stream and reads only a class from it. * @param is input stream to read from * @return the class of that stream * @exception IOException if something fails */ private Class readClass (InputStream is) throws IOException, ClassNotFoundException { /** object input stream */ class OIS extends ObjectInputStream { public OIS (InputStream iss) throws IOException { super (iss); } /** Throws exception to signal the kind of class found. */ public Class resolveClass (ObjectStreamClass osc) throws IOException, ClassNotFoundException { Class c = findClass (osc.getName (), null); if (c == writeRepl) { // if this is write replace of shared object then // continue in reading return c; } // stop the reading expecting that we have read the class // of the primary object throw new ClassEx (c); } }; ObjectInputStream ois = new OIS (new BufferedInputStream (is)); try { ois.readObject (); // should not happen throw new ClassNotFoundException (); } catch (ClassEx ex) { // good, we found the class return ex.clazz; } } /** the variable for access to SharedClassObject$WriteReplace */ private static Class writeRepl; static { try { writeRepl = Class.forName ("org.openide.util.SharedClassObject$WriteReplace"); // NOI18N } catch (Exception ex) { ex.printStackTrace(); } } /** Finds a class for given name. * @param name name of the class * @return the class for the name * @exception ClassNotFoundException if the class cannot be found */ private Class findClass (String name, ClassLoader customLoader) throws ClassNotFoundException { try { Class c; try { if (customLoader != null) { c = customLoader.loadClass(name); } else { // to save the space with wasting classloaders, try the system first c = ((ClassLoader)Lookup.getDefault().lookup(ClassLoader.class)).loadClass (name); } } catch (ClassNotFoundException ex) { // ok, ignore and try our class loader c = createClassLoader().loadClass(name); } return c; } catch (ClassNotFoundException ex) { throw ex; } catch (RuntimeException ex) { throw ex; } catch (LinkageError le) { throw new ClassNotFoundException(le.toString(), le); } } /** Creates new NbClassLoader with restricted PermissionCollection * that contains only: * java.io.FilePermission("<<ALL FILES>>", "read") * java.util.PropertyPermission("*", "read") * * @return ClassLoader */ protected ClassLoader createClassLoader() { ClassLoader l = (ClassLoader)Lookup.getDefault().lookup(ClassLoader.class); try { Class c = Class.forName("org.openide.execution.NbClassLoader", true, l); // NOI18N ClassLoader nbcl = (ClassLoader)c.newInstance(); Method m = c.getMethod("setDefaultPermissions", new Class[] {PermissionCollection.class}); // NOI18N Permissions perms = new Permissions(); perms.add(new FilePermission("<<ALL FILES>>", "read")); // NOI18N perms.add(new PropertyPermission("*", "read")); // NOI18N perms.setReadOnly(); m.invoke(nbcl, new Object[] {perms}); return nbcl; } catch (ClassNotFoundException cnfe) { // OK, use systemClassLoader. } catch (Exception e) { ErrorManager.getDefault().notify(e); } return l; } /** Enhanced instance cookie support that also knows the file it * has been created from and can be serialized back to. * Note that <code>InstanceSupport</code> already does; this class * only declares the interface. */ public static class Origin extends InstanceSupport implements InstanceCookie.Origin { /** New support for a given entry. The file is taken from the * entry and is updated if the entry moves or renames itself. * @param entry entry to create instance from */ public Origin (MultiDataObject.Entry entry) { super (entry); } } /** Trivial supporting instance cookie for already-existing objects. */ public static class Instance extends Object implements InstanceCookie.Of { /** the object to represent */ private Object obj; /** Create a new instance cookie. * @param obj the object to represent in this cookie */ public Instance (Object obj) { this.obj = obj; } /* The bean name for the instance. * @return the name for the instance */ public String instanceName () { return obj.getClass ().getName (); } /* The class of the instance represented by this cookie. * Can be used to test whether the instance is of valid * class before it is created. * * @return the class of the instance */ public Class instanceClass () { return obj.getClass (); } /* * @return an object to work with */ public Object instanceCreate () { return obj; } public boolean instanceOf (Class type) { return type.isAssignableFrom (instanceClass ()); } } /** The exception to use to signal succesful find of a class. * Used in method readClass. */ private class ClassEx extends IOException { /** founded class */ public Class clazz; static final long serialVersionUID =4810039297880922426L; /** @param c the class */ public ClassEx (Class c) { clazz = c; } } }