Overview | Package | Class | Tree | Deprecated | Index | Help
PREV CLASS NEXT CLASS | FRAMES NO FRAMES
SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD

## wogroovy
Class WOGroovyCompilerProxy

    java.lang.Object
      wogroovy.WOGroovyCompilerProxy

public class WOGroovyCompilerProxy
extends Object

ERXCompilerProxy is a class that creates a rapid-turnaround mode for WebObjects 5 for Java similar to how you use WebScript with WO 4.5.1.

Instead of recompiling your Application or Frameworks, you simply change the source code in question and reload your page. In some cases, you may also have to restart your Application but you won't have to rebuild it.

ERXCompilerProxy is especially well suited for DirectToWeb applications. The restrictions of Java's classloading are partially compensated by the highly dynamic nature of D2W apps.

## Installation

Check if you have `jikes` installed in `/usr/bin`. On Windows, install `jikes.exe` in `$NEXT_ROOT/Local/Library/Executables/`

## Usage

To use this framework you must complete a few easy steps and you will very seldom have to rebuild or even restart your project again:

- Add ERExtensions.framework and ERJars.framework to your project. You do not need to add the ERExtensions.framework to your framework projects if you only want to use the ERXCompilerProxy, but you need to add it to the application.
- In your `~/WebObjects.properties` file add a line

  `er.extensions.ERXCompilerProxyEnabled=true`

- Then either create a file named `CPFileList.txt` in your project's and your framework's root directory. This file should contain lines with "`./project_relative_path_to_Someclass.java:package_of_SomeClass_or_empty<return>`" for every file you want to get recompiled. On Windows, this is currently your only option unless someone writes a shell script that mimics what the supplied unix script does (find all java classes under the project root, grep for package and class names and put it into a `CPFileList.txt` file)
- or create a new shell build phase right after the "Frameworks & Libraries":
  1. Set the current target to your app
  2. Click on "Files & Build Phases" tab
  3. Choose "Project/New Build Phase/New Shell Script Build Phase" from the Menu and drag the build phase right after the "Frameworks & Libraries"
  4. Insert the code `

         sh "${TARGET_BUILD_DIR}/ERExtensions.framework/Resources/InstallCompilerProxySupport.sh"

     `
  Now, everytime you build this project, every source file directly under the project directory will be put into the `CPFileList.txt` file.

That's all. Now build your app, make a change in Main.java and reload. You should see the changed version and a message from ERXCompilerProxy in the console.

## What it does

The ERXCompilerProxy does several things to works it's magic. First, on startup, it registers for the `ApplicationDidFinishLaunching` Notification. When the App starts, it reads the `CPFileList.txt` files from your currently opened projects to find out about the files to watch. So remember to have your framework projects open in PB.

It also registers for the `ApplicationWillDispatchRequest` Notification, which gets triggered whenever a page is requested. It tries to compile the files that have changed and throws an Exception describing the errors if there where any and then aborts further handling of the request - you will get an error from your browser. However, if everything went well, it creates a new ClassLoader and throws away the component definition cache and a few other things. Then, when the next "pageWithName" is called, the updated class will be used.

This is slightly different from how WebScript handled things. In Java, you simply can't change an Object's class implementation at runtime - so if you already have a page (or any other Object) this object will not use the newer class. Only Objects created after the compilation will have new behaviour.

The freshly compiled files will be put into your App's `Resources/Java` directory into the proper package hierarchy. So, if you simply restart your App, the newer files will be used. When you re-build your project, your normal `.jar` will contains the correct classes anyway.

NOTE: Since all new class files end up in `Resources/Java`, when you clean-build your App, you also need to recompile your frameworks when you have changed classes there! A normal build will not delete the files, so recompilation of the frameworks is not needed.

## Knows bugs and limitations

- Since Java can't redefine objects, the new code will get used only for new objects. You may either have to reload multiple times or start a new session to get things to work.
- You may see an error `"IllegalAccess in KeyValueCodingProtectedAccessor"`. You will probably have defined a new variable in your component with protected access. Either set the scope of the variable to public or restart your app. The number of these messages is greatly reduced if your components and EOs are in a package hierarchy and you have a KeyValueCodingProtectedAccessor subclass for this package.
- You must have the respective projects opened in Project Builder. If you don't, ERXCompilerProxy can't find the sources. Alternately, you can set the NSProjectSearchPath argument.
- You must reload after making changes and before stopping your application, because the file modification times are read only at start. If you stop the application before reloading, your last changes will not be recognized.
- If you change a class in a framework, the compiled file still ends up in your app's Java/ directory. This is intentional, but means that you should clean-rebuild your app if you rebuild your framework.
- Changes in the Application class (among others) will not be recognized before a restart. They are only read once, due to the nature of Java's class loading and can't be fixed. If you cache your classes, you need to register for `CompilerProxyDidCompileClassesNotification` and reset the cache when you recieve it.
- If you make changes in your EO's classes, you should definitely restart your application after pressing reload. You may seriously mess up your data if you have a mix of previously instaniated Objects and new ones.
- Try not to use casts, use WOComponents and KeyValueCoding instead. Say you have a List page and a Detail page and you change the Detail page. Further you have code like:

          public WOComponent detailsAction() {
          Details newPage = (Details)pageWithName("Details");
          newPage.setObject(currentObject);
          return newPage;
      }

  This doesn't work because List and the reference to Details is in the old class loader, but pageWithName() instantiates from the new class loader. Rather use this code:

          public WOComponent detailsAction() {
          WOComponent newPage = pageWithName("Details");
          newPage.takeValueForKey(currentObject, "object");
          return newPage;
      }

Field Summary
`protected String` | `_className`
`protected static String` | `_classPath` Holds the classpath of the current app.
`protected static WOGroovyCompilerProxy` | `_defaultProxy` Holds the Compilerproxy singleton
`protected String` | `_destinationPath` Holds the path where the compiled `.class` files go.
`protected NSMutableDictionary` | `_filesToWatch` Holds the files to watch.
`protected static String` | `_jikesPath` Path to the jikes binary.
`protected NSArray` | `_projectSearchPath`
`protected static boolean` | `_raiseOnError` Holds a boolean that tells wether an error should raise an Exception or only log the error.
`protected NSMutableSet` | `classFiles` Currently compiled classes.
`protected static ERXLogger` | `classLoaderLog`
`static String` | `CompilerProxyDidCompileClassesNotification` Notification you can register to when the Compiler Proxy reloads classes.
`protected static String` | `CPFileList` denotes the name of file which describes in each line a path to java class to watch for. The format of this file is: name.of.package:unix_path_to_java_file_relative_to_this_file<return>
`protected boolean` | `initialized`
`protected static ERXLogger` | `log`

Constructor Summary
`WOGroovyCompilerProxy()` Contructor - does nothing special.

Method Summary
`void` | `checkAndCompileOnNotification(NSNotification theNotification)` Method that will be called upon `ApplicationWillDispatchRequest`. Checks if the request is not a resource request and then calls `#checkAndCompileOnNotification()`
`Class` | `classForName(String className)` Returns the class registered for the name `className`. Uses the private WebObjects class cache.
`static WOGroovyCompilerProxy` | `defaultProxy()` Returns the Compiler Proxy Singleton. Creates one if needed.
`void` | `initialize()` Initializes the CopilerProxy singleton. Registers for ApplicationWillDispatchRequest notification.
`static boolean` | `isClassContainedBySet(String classNameWithPackage, NSSet cacheEntries)` Tests whether or not the class name with package is contained in the set of CacheEntry objects.
`protected String` | `pathForCPFileList(String directory)`
`String` | `projectInSearchPath(String bundleName)` Returns an array of paths to the opened projects that have a `CPFileList.txt`. This code is pretty fragile and subject to changes between versions of the dev-tools.
`protected NSArray` | `projectPaths()`
`void` | `setClassForName(Class clazz, String className)` Sets the class registered for the name `className` to the given class. Changes the private WebObjects class cache.
`static void` | `setDefaultProxy(WOGroovyCompilerProxy p)`

Methods inherited from class java.lang.Object
`clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`

Field Detail

### log

    protected static final ERXLogger log

### classLoaderLog

    protected static final ERXLogger classLoaderLog

### CompilerProxyDidCompileClassesNotification

    public static final String CompilerProxyDidCompileClassesNotification

  Notification you can register to when the Compiler Proxy reloads classes.
  The object is an array of classes that did recompiled since the last time the notification was sent.

  See Also:
    Constant Field Values

### CPFileList

    protected static final String CPFileList

  denotes the name of file which describes in each line a path to java class to watch for.
  The format of this file is:
  name.of.package:unix_path_to_java_file_relative_to_this_file<return>

  See Also:
    Constant Field Values

### _jikesPath

    protected static String _jikesPath

  Path to the jikes binary.

### _defaultProxy

    protected static WOGroovyCompilerProxy _defaultProxy

  Holds the Compilerproxy singleton

### _classPath

    protected static String _classPath

  Holds the classpath of the current app.

### _raiseOnError

    protected static boolean _raiseOnError

  Holds a boolean that tells wether an error should raise an Exception or only log the error.

### initialized

    protected boolean initialized

### _filesToWatch

    protected NSMutableDictionary _filesToWatch

  Holds the files to watch.

### _className

    protected String _className

### _destinationPath

    protected String _destinationPath

  Holds the path where the compiled `.class` files go. Default is `Contents/Resources/Java`.

### classFiles

    protected NSMutableSet classFiles

  Currently compiled classes.

### _projectSearchPath

    protected NSArray _projectSearchPath

Constructor Detail

### WOGroovyCompilerProxy

    public WOGroovyCompilerProxy()

  Contructor - does nothing special.

Method Detail

### defaultProxy

    public static WOGroovyCompilerProxy defaultProxy()

  Returns the Compiler Proxy Singleton.
  Creates one if needed.

  Returns:
    compiler proxy singleton

### setDefaultProxy

    public static void setDefaultProxy(WOGroovyCompilerProxy p)

### pathForCPFileList

    protected String pathForCPFileList(String directory)

### projectInSearchPath

    public String projectInSearchPath(String bundleName)

  Returns an array of paths to the opened projects that have a `CPFileList.txt`.
  This code is pretty fragile and subject to changes between versions of the dev-tools. You can get around it by setting `NSProjectSearchPath` to the paths to your projects: `(/Users/ak/Wonder/Common/Frameworks,/Users/Work)` will look for directories with a CPFileList.txt in all loaded bundles. So when you link to `ERExtensions.framework`, `/Users/ak/Wonder/Common/Frameworks/ERExtensions` will get found.

  Returns:
    paths to opened projects

### projectPaths

    protected NSArray projectPaths()

### classForName

    public Class classForName(String className)

  Returns the class registered for the name `className`.
  Uses the private WebObjects class cache.

  Parameters:
    `className` - class name
  Returns:
    class for the registered name or null

### setClassForName

    public void setClassForName(Class clazz,
                                String className)

  Sets the class registered for the name `className` to the given class.
  Changes the private WebObjects class cache.

  Parameters:
    `clazz` - class object
    `className` - name for the class - normally clazz.getName()

### initialize

    public void initialize()

  Initializes the CopilerProxy singleton.
  Registers for ApplicationWillDispatchRequest notification.

### checkAndCompileOnNotification

    public void checkAndCompileOnNotification(NSNotification theNotification)

  Method that will be called upon `ApplicationWillDispatchRequest`.
  Checks if the request is not a resource request and then calls `#checkAndCompileOnNotification()`

  Parameters:
    `theNotification` - notification sent upon ApplicationWillDispatchRequest

### isClassContainedBySet

    public static boolean isClassContainedBySet(String classNameWithPackage,
                                                NSSet cacheEntries)

  Tests whether or not the class name with package is contained in the set of CacheEntry objects. Convenient to check if a specific class was recompiled in a CompilerProxyDidCompileClassesNotification.

  Parameters:
    `classNameWithPackage` - string of the class name
    `cacheEntries` - NSSet contains CacheEntry objects; typically obtained by a notification's object() method
  Returns:
    if the classNameWithPackage is contained by cacheEntries

Overview | Package | Class | Tree | Deprecated | Index | Help
PREV CLASS NEXT CLASS | FRAMES NO FRAMES
SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD