package bluej.extmgr;
import java.awt.Graphics2D;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Properties;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import threadchecker.OnThread;
import threadchecker.Tag;
import bluej.Config;
import bluej.extensions.BClassTarget;
import bluej.extensions.BlueJ;
import bluej.extensions.Extension;
import bluej.extensions.ExtensionBridge;
import bluej.extensions.PreferenceGenerator;
import bluej.extensions.event.ExtensionEvent;
import bluej.extensions.painter.ExtensionClassTargetPainter;
import bluej.pkgmgr.Project;
import bluej.pkgmgr.Layer;
import bluej.utility.Debug;
| This is the wrapper for an extension. Its duties are:
| <ul>
| <li>Keep track of an extension class, this is to allow loading and unloading
| <li>Given a jar try to load an extension that is in it (if any)
| <li>Hold all state that is needed to get the whole system working
| </ul>
|
| <p>Note: When an extension is loaded a BlueJ object is given to it. This object MUST
| be fully usable by the extension AND all associated components !
|
| <p>The creation of an extension Wrapper is disjoint from the loading of an extension.
|
| @author Damiano Bolla, 2002,2003,2004
|
public class ExtensionWrapper
{
private final ExtensionPrefManager prefManager;
private File extensionJarFileName;
private Class<?> extensionClass;
private Extension extensionInstance;
private BlueJ extensionBluej;
private String extensionStatusString;
private Project project;
| Construct a new ExtensionWrapper for the given jar file.
|
| <p>This may fail; in that case isJarValid() will return false.
|
| <p>The extension is not actually loaded: call newExtension() for that.
|
public ExtensionWrapper(ExtensionPrefManager prefManager, File jarFile)
{
this.prefManager = prefManager;
extensionClass = getExtensionClass(jarFile);
if (extensionClass == null) {
return;
}
extensionJarFileName = jarFile;
}
| Get the extension class for an extension .jar file. Return the Class or
| null if none is found.
|
| <p>Some messages are logged in case of failure since otherwise a user may never
| understand why his lovely extension was not loaded.
|
| @param jarFileName The name of the (potential) extension jar file name to load
| @return The extension class.
|
private Class> getExtensionClass(File jarFileName)
{
Class<?> extensionClass = null;
extensionStatusString = Config.getString("extmgr.status.loading");
if (jarFileName == null) return null;
if (!jarFileName.getName().endsWith(".jar")) return null;
String errorPrefix = "getExtensionsClass: jarFile="+jarFileName.getName()+" ";
try {
JarFile jarFile = new JarFile(jarFileName);
Manifest manifest = jarFile.getManifest();
if (manifest == null) {
Debug.message(errorPrefix+Config.getString("extmgr.error.nomanifest"));
return null;
}
String className = manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);
if (className == null) {
Debug.message(errorPrefix+Config.getString("extmgr.error.nomain"));
return null;
}
URL url = jarFileName.toURI().toURL();
URL[] urlList=new URL[]{url
};
FirewallLoader fireLoader = new FirewallLoader(getClass().getClassLoader());
URLClassLoader ucl = new URLClassLoader(urlList,fireLoader);
extensionClass = ucl.loadClass(className);
if (!Extension.class.isAssignableFrom(extensionClass)) {
Debug.message(errorPrefix+Config.getString("extmgr.error.notsubclass"));
return null;
}
} catch (Throwable exc) {
Debug.message(errorPrefix+"Exception="+exc.getMessage());
exc.printStackTrace();
return null;
}
return extensionClass;
}
| A ClassLoader which only finds bluej.* classes and system classes. This is used to
| prevent extensions from seeing other libraries which might be bundled with and used by
| BlueJ, so that they can use their own versions of those libraries if they wish.
|
@OnThread(Tag.Any)
static class FirewallLoader
extends ClassLoader
{
ClassLoader myParent;
| Constructor. Note that this classloader breaks from the delegation model; the parent
| is not the actual parent classloader, it is only used by findClass() below.
|
FirewallLoader ( ClassLoader parent )
{
myParent = parent;
}
public Class> findClass(String name) throws ClassNotFoundException
{
if ( name.startsWith("bluej.") || name.startsWith("rmiextension.") || name.startsWith("greenfoot.")) {
return myParent.loadClass(name);
}
throw new ClassNotFoundException();
}
}
| Now, assume you have the class and you want to "instantiate" the
|* extension You have to call this. NOTE that the extension wrapper is
* ALREADY UP and running. I do not return a value, you may check
* how this went by using the isValid() method...
*
* @param project The project this extension is linked to, null if none
p.public void newExtension(Project aProject)
{
if (extensionClass == null) return;
project = aProject;
extensionBluej = ExtensionBridge.newBluej(this, prefManager);
try {
extensionInstance = (Extension)extensionClass.getDeclaredConstructor().newInstance();
} catch (Throwable ex) {
extensionInstance = null;
extensionStatusString = "newExtension: Exception=" + ex.getMessage();
return;
}
if ( ! safeIsCompatible() ) {
extensionStatusString = Config.getString("extmgr.status.badversion");
extensionInstance = null;
return;
}
safeStartup(extensionBluej);
extensionStatusString = Config.getString("extmgr.status.loaded");
}
| Gets the project this extension is associated with.
| This happens in case of extensions loaded with a Project.
| If it is a system wide extension this will be null.
|
| @return the project owning this extension.
|
Project getProject()
{
return project;
}
| Checks if a this extension is valid
|
| @return true if it is instantiated, false if it is not.
|
public boolean isValid()
{
return (extensionInstance != null);
}
| Gets the jarValid attribute of the ExtensionWrapper object
|
| @return The jarValid value
|
boolean isJarValid()
{
return (extensionClass != null);
}
| Kills off this extension as much as possible
| items and making access to BlueJ no longer possible.
| Not only ! we are even going to release the wrapper after this.
| So it can be loaded again, hopefully from a clean environment
|
void terminate()
{
safeTerminate();
extensionInstance = null;
prefManager.panelRevalidate();
}
| Gets the current status of this extension.
|
| @return something like 'Loaded' or 'Error'.
|
public String getExtensionStatus()
{
return extensionStatusString;
}
| Gets the fully-qualified name of this extension class.
|
| @return This extension class name or null if nothing is loaded
|
public String getExtensionClassName()
{
if (extensionClass == null) {
return null;
}
return extensionClass.getName();
}
| Tries to return a reasonable Properties instance of the extension labels
| It may return null if nothing reasonable can be found in the extension jar
|
| @return the properties or null if nothing can be found
|
public Properties getLabelProperties()
{
String localLanguage = Config.getPropString("bluej.language", Config.DEFAULT_LANGUAGE);
Properties extensionsProps = getLabelProperties (localLanguage);
if ( extensionsProps != null ) return extensionsProps;
extensionsProps = getLabelProperties (Config.DEFAULT_LANGUAGE);
return extensionsProps;
}
| Returns the label that are language dependents as a Properties instance
|
| @return the equivalent properties if found, null if nothing
|
private Properties getLabelProperties(String language)
{
if ( extensionClass == null ) {
return null;
}
String languageFileName = "lib/" + language + "/labels";
InputStream inStream = extensionClass.getClassLoader().getResourceAsStream (languageFileName);
if ( inStream == null ) return null;
Properties props = new Properties();
try {
props.load(inStream);
} catch(Exception ex) {
Debug.message("ExtensionWrapper.getLabelProperties(): Exception="+ex.getMessage());
}
closeInputStream ( inStream );
return props;
}
| UFF, this is here but it really ought to be in a public util.
| Simply close a stream without complaining too much.
| Just to avoid the Nth level of try catch with no value added
|
public static void closeInputStream(InputStream aStream)
{
try {
aStream.close();
} catch ( Exception ee ) {
}
}
| Gets a String representation of the path to the <CODE>.jar</CODE> file
| containing the extension.
|
| @return String like <CODE>C:/bluej/lib/extensions/fun.jar</CODE> or null
|
public String getExtensionFileName()
{
if (extensionJarFileName == null) return null;
return extensionJarFileName.getPath();
}
| Convenience method to ensure uniformity of settings items.
|
public String getSettingsString( String key)
{
return "extensions." + getExtensionClassName() + ".settings." + key;
}
| Returns useful information about this wrapper
|
public String toString()
{
if (! isValid()) {
return "ExtensionWrapper: invalid";
}
return "ExtensionWrapper: "+ extensionClass.getName();
}
|
| ====================== ERROR WRAPPED CALLS HERE =========================
| We need to wrap all calls from BlueJ to the Extension into a try/catch;
| otherwise an error in the extension will render BlueJ unusable.
|
| Informs any registered listeners that an event has occurred.
|
public void safeEventOccurred(ExtensionEvent event)
{
if (!isValid()) {
return;
}
try {
ExtensionBridge.delegateEvent(extensionBluej,event);
}
catch (Exception exc) {
Debug.message("ExtensionWrapper.safeEventOccurred: Class="+getExtensionClassName()+" Exception="+exc.getMessage());
exc.printStackTrace();
return;
}
}
| Returns the extension's description.
|
public String safeGetExtensionDescription()
{
if (extensionInstance == null) {
return null;
}
try {
return extensionInstance.getDescription();
}
catch (Exception exc) {
Debug.message("ExtensionWrapper.safeGetExtensionDescription: Class="+getExtensionClassName()+" Exception="+exc.getMessage());
exc.printStackTrace();
return null;
}
}
| Returns the extension's name.
|
public String safeGetExtensionName()
{
if (extensionInstance == null)
return "";
try {
return extensionInstance.getName();
}
catch (Exception exc) {
Debug.message("ExtensionWrapper.safeGetExtensionName: Class="+getExtensionClassName()+" Exception="+exc.getMessage());
exc.printStackTrace();
return "";
}
}
| Gets the extension's 'further information' URL
|
| @return the extension's URL, or <CODE>null</CODE>.
|
public URL safeGetURL()
{
if (extensionInstance == null)
return null;
try {
return extensionInstance.getURL();
}
catch (Exception exc) {
Debug.message("ExtensionWrapper.safeGetURL: Class="+getExtensionClassName()+" Exception="+exc.getMessage());
exc.printStackTrace();
return null;
}
}
| Gets the formal version of this extension.
|
| @return the version of the extension
|
public String safeGetExtensionVersion()
{
if (extensionInstance == null)
return null;
try {
return extensionInstance.getVersion();
}
catch (Exception exc) {
Debug.message("ExtensionWrapper.safeGetExtensionVersion: Class="+getExtensionClassName()+" Exception="+exc.getMessage());
exc.printStackTrace();
return null;
}
}
| Ask the extension if it thinks it is compatible.
|
| @return true if it is, false otherwise
|
private boolean safeIsCompatible()
{
if (extensionInstance == null)
return false;
try {
return extensionInstance.isCompatible();
}
catch (Exception exc) {
Debug.message("ExtensionWrapper.safeIsCompatible: Class="+getExtensionClassName()+" Exception="+exc.getMessage());
exc.printStackTrace();
return false;
}
}
| Call the startup method in a safe way
|
private void safeStartup(BlueJ bluejProxy)
{
if (extensionInstance == null) {
return;
}
try {
extensionInstance.startup(bluejProxy);
}
catch (Exception exc) {
Debug.message("ExtensionWrapper.safeStartup: Class="+getExtensionClassName()+" Exception="+exc.getMessage());
exc.printStackTrace();
}
}
| Call the terminate method in a safe way
|
private void safeTerminate()
{
if (extensionInstance == null) {
return;
}
try {
extensionInstance.terminate();
}
catch (Exception exc) {
Debug.message("ExtensionWrapper.safeTerminate: Class="+getExtensionClassName()+" Exception="+exc.getMessage());
exc.printStackTrace();
}
}
| Calls the EXTENSION preference panel loadValues in a safe way
|
public void safePrefGenLoadValues()
{
if (extensionBluej == null) {
return;
}
PreferenceGenerator aPrefGen = extensionBluej.getPreferenceGenerator();
if (aPrefGen == null) {
return;
}
try {
aPrefGen.loadValues();
}
catch (Exception exc) {
Debug.message("ExtensionWrapper.safePrefGenLoadValues: Class="+getExtensionClassName()+" Exception="+exc.getMessage());
exc.printStackTrace();
}
}
| Calls the EXTENSION preference panel saveValues in a safe way
|
public void safePrefGenSaveValues()
{
if (extensionBluej == null)
return;
PreferenceGenerator aPrefGen = extensionBluej.getPreferenceGenerator();
if (aPrefGen == null)
return;
try {
aPrefGen.saveValues();
}
catch (Exception exc) {
Debug.message("ExtensionWrapper.safePrefGenSaveValues: Class="+getExtensionClassName()+" Exception="+exc.getMessage());
exc.printStackTrace();
}
}
| Calls the EXTENSION preference panel getPanel in a sfe way
|
public JPanel safePrefGenGetPanel()
{
if (extensionBluej == null)
return null;
PreferenceGenerator aPrefGen = extensionBluej.getPreferenceGenerator();
if (aPrefGen == null)
return null;
try {
return aPrefGen.getPanel();
}
catch (Exception exc) {
Debug.message("ExtensionWrapper.safePrefGenGetPanel: Class="+getExtensionClassName()+" Exception="+exc.getMessage());
exc.printStackTrace();
return null;
}
}
| Calls the EXTENSION getMenuItem in a safe way
|
public JMenuItem safeGetMenuItem(ExtensionMenu attachedObject)
{
if (extensionBluej == null)
return null;
try {
return ExtensionBridge.getMenuItem(extensionBluej, attachedObject);
}
catch (Exception exc) {
Debug.message("ExtensionWrapper.safeMenuGenGetMenuItem: Class="+getExtensionClassName()+" Exception="+exc.getMessage());
exc.printStackTrace();
return null;
}
}
| Calls the EXTENSION postMenuItem in a safe way
|
public void safePostMenuItem(ExtensionMenu attachedObject, JMenuItem onThisItem)
{
if (extensionBluej == null)
return;
try {
ExtensionBridge.postMenuItem(extensionBluej, attachedObject, onThisItem );
}
catch (Exception exc) {
Debug.message("ExtensionWrapper.safePostGenGetMenuItem: Class="+getExtensionClassName()+" Exception="+exc.getMessage());
exc.printStackTrace();
}
}
| Calls the extension drawExtensionClassTarget in a safe way.
|
| @param layer
| The layer of the drawing which causes the different methods of
| the {}link ExtensionClassTargetPainter} instance to be called.
| @param bClassTarget
| The class target that will be painted.
| @param graphics
| The {}link Graphics2D} instance to draw on.
| @param width
| The width of the area to paint.
| @param height
| The height of the area to paint.
|
public void safeDrawExtensionClassTarget(Layer layer, BClassTarget bClassTarget,
Graphics2D graphics, int width, int height)
{
if (extensionBluej == null) {
return;
}
try {
ExtensionBridge.drawExtensionClassTarget(extensionBluej, layer, bClassTarget, graphics,
width, height);
} catch (Exception exc) {
Debug.message("ExtensionWrapper.safeDrawExtensionClassTarget: Class="+getExtensionClassName()+" Exception="+exc.getMessage());
exc.printStackTrace();
}
}
}
top,
use,
map,
class ExtensionWrapper
. ExtensionWrapper
. getExtensionClass
top,
use,
map,
class FirewallLoader
. findClass
. newExtension
. isValid
. terminate
. getExtensionStatus
. getExtensionClassName
. getLabelProperties
. getLabelProperties
. closeInputStream
. getExtensionFileName
. getSettingsString
. toString
. safeEventOccurred
. safeGetExtensionDescription
. safeGetExtensionName
. safeGetURL
. safeGetExtensionVersion
. safeIsCompatible
. safeStartup
. safeTerminate
. safePrefGenLoadValues
. safePrefGenSaveValues
. safePrefGenGetPanel
. safeGetMenuItem
. safePostMenuItem
. safeDrawExtensionClassTarget
681 neLoCode
+ 86 LoComm