Friday, June 28, 2013

CDI and Guice with jsr-300 javax.inject In Same Webapp

Things can get weird when java SE code developed with IOC is deployed into a java EE container that implements CDI. For example - the littleware code I work with implements simple modules and life cycle management with guice, so when I deploy that code in a webapp to a glassfish server I want guice's runtime to manage littleware's dependency injection, but I might also want CDI to inject a glassfish-managed JPA EntityManager into a servlet for me, and both guice and CDI are scanning for the same javax.inject runtime annotations. Fortunately - this kind of thing worked fine for me until recently, since CDI 1 as introduced in java EE 6 ignored jars that did not include a META-INF/beans.xml file.

Java Enterprise Edition 6 introduced JSR-330 (javax.inject annotations and provider interfaces) and JSR-299 (CDI - contexts and dependency injection) to the java-ee platform. The javax.inject package standardized the annotations and interfaces used by java's various inversion of control and dependency-injection systems (guice, spring, dagger, pico container, ...), so that code written with one IOC system in mind could be more easily used in an application that deployed to another system.

CDI introduced an IOC implementation for java enterprise edition (EE) technologies (servlets, JSF, EJB, JPA, ...) deployed in EE containers like glassfish, jboss, weblogic, and websphere.

I discovered CDI's behavior changed slightly in the new java EE 7 runtime when a webapp that ran fine in glassfish version 3 (a java EE 6 container) failed to deploy under glassfish 4 (the EE-7 reference implementation). The errors in the glassfish-4 logs lead me to this ticket in guava's bug database. It turns out that CDI's new version 1.1 runtime automatically scans all jars deployed with an enterprise application, and guava-14+ includes a couple classes with javax.inject annotations that CDI processed in an unintended way (littleware still uses guice's com.google.inject package annotations and interfaces, so CDI ignored those).

I eventually found one easy solution to this javax.inject - CDI 1.1 mix-up is to add a META-INF/beans.xml CDI config file to jars that include javax.inject annotations that require special or no handling by CDI. I added the following META-INF/beans.xml with a bean-discover-mode attribute set to "none" to the guava jar in my webapp, and that solved the problem. Hopefully guava's maintainers will add a similar beans.xml file to guava's binary distribution on maven central.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
       bean-discovery-mode="none">
</beans>

Friday, June 07, 2013

javase Hibernate: goodbye Ejb3Configuration, hello JNDI Context mock

I've been cleaning up littleware's asset database code this week, and was dismayed to discover upon updating the hibernate dependencies that the latest hibernate releases have deprecated and removed the Ejb3Configuration mechanism for bootstrapping a JPA environment and allocating an EntityManagerFactory in a javase (no javaee container) application. Fortunately - I came up with a hacky way to trick hibernate into using the DataSource instance I want it to use.

Somehow when I started working with JPA I already had code in littleware's guice-based IOC setup to initialize and inject DataSource instances in traditional JDBC code either directly or via JNDI lookup. When I started using JPA in littleware I wanted to wire things up so that JPA used the same DataSource as the rest of the code. When running in a web container like Tomcat or Glassfish the app can let the container manage the DataSource, and both JPA and the guice-runtime access the same DataSource via a directory (JNDI) lookup, but when running a standalone application, I had things coded up to use hibernate, and used Ejb3Configuration to tell hibernate to use littleware's DataSource.

Anyway, with Ejb3Configuration going away I had to specify the database connection parameters in a JPA persistence.xml file in one of two ways. The first option was to specify properties for a JDBC Driver that JPA (hibernate) would wrap with its own connection pool manager, and the first hack I tried implemented a JDBC Driver (it's just a one method interface) that pulled connections from littleware's DataSource. That actually worked fine - I'm always amazed when these things work. I setup persistence.xml like this:

<?xml version="1.0" encoding="UTF-8"?>

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">

  <persistence-unit name="littlewarePU" transaction-type="RESOURCE_LOCAL">
        <description>This JPA persistence unit tracks littleware assets
        </description>

        <!-- jndi datasource lookup        
        <non-jta-data-source>jdbc/littleDB</non-jta-data-source>
        -->
        
        <class>littleware.asset.server.db.jpa.AssetTypeEntity</class>
        <class>littleware.asset.server.db.jpa.TransactionEntity</class>
        <class>littleware.asset.server.db.jpa.AssetAttribute</class>
        <class>littleware.asset.server.db.jpa.AssetDate</class>
        <class>littleware.asset.server.db.jpa.AssetLink</class>
        <class>littleware.asset.server.db.jpa.AssetEntity</class>

    <properties>

      <property name="eclipselink.target-database" value="DERBY"/>
      <property name="eclipselink.ddl-generation" value="create-tables" />
      <property name="hibernate.hbm2ddl.auto" value="create"/>
      <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect" />
            
      <property name="javax.persistence.jdbc.driver" value="littleware.asset.server.db.jpa.LittleDriver"/>
      <property name="javax.persistence.jdbc.url" value="jdbc:littleware://ignore/this/stuff"/>
      <property name="javax.persistence.jdbc.user" value="APP"/>
      <property name="javax.persistence.jdbc.password" value="APP"/>

    </properties> 
    
  </persistence-unit>
</persistence>

And I wrote a bogus littleware.asset.server.db.jpa.LittleDriver that some initialization code configured via its setDataSource() method:


/**
 * JDBC driver that just delegates to the active DataSource defined in the
 * active littleware runtime.
 * Register this driver with JPA persistence.xml to plug into the
 * littleware managed DataSource.
 */
public class LittleDriver implements Driver {
    private static  DataSource dataSource = null;
    /**
     * HibernateProvider injects littleware data source at startup time as needed
     */
    public static void setDataSource( DataSource value ) {
        dataSource = value;
    }
    
    
    @Override
    public Connection connect(String string, Properties prprts) throws SQLException {
        Whatever.get().check( "LittleDriver requires data source injection",  null != dataSource );
        return dataSource.getConnection();
    }

    @Override
    public boolean acceptsURL(String string) throws SQLException {
        return true;
    }

    private static final DriverPropertyInfo[] empty = new DriverPropertyInfo[0];
    @Override
    public DriverPropertyInfo[] getPropertyInfo(String string, Properties prprts) throws SQLException {
        return empty;
    }

    @Override
    public int getMajorVersion() {
        return 0;
    }

    @Override
    public int getMinorVersion() {
        return 0;
    }

    @Override
    public boolean jdbcCompliant() {
        return false;
    }

    private static final Logger log = Logger.getLogger( LittleDriver.class.getName() );
    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return log;
    }
    
}

Anyway - that worked great, and it's actually good enough for my current needs (mostly just junit tests), but it made me itch thinking about hibernate wrapping a connection pool around a mock driver that pulls connections from another connection pool. Ugh. So I started thinking about setting up an in-memory JNDI directory where initialization code could stuff the DataSource before allocating the JPA (hibernate or whatever) EntityManagerFactory. I found this cool little SimpleJNDI JNDI implementation, but it wasn't registered with Maven central, and it was a little bigger than I would like to copy into my code base, and anyway - I didn't need a whole JNDI implementation - I just needed to trick hibernate into using my DataSource, so I tried just wiring up a mock JNDI Context, and it worked! It took a few tries to figure out which methods hibernate calls to do its directory lookup, but in the end I wound up with a persistence.xml file like this:

<?xml version="1.0" encoding="UTF-8"?>

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">

  <persistence-unit name="littlewarePU" transaction-type="RESOURCE_LOCAL">
        <description>This JPA persistence unit tracks littleware assets
        </description>

        <non-jta-data-source>jdbc/littleDB</non-jta-data-source>
        
        <class>littleware.asset.server.db.jpa.AssetTypeEntity</class>
        <class>littleware.asset.server.db.jpa.TransactionEntity</class>
        <class>littleware.asset.server.db.jpa.AssetAttribute</class>
        <class>littleware.asset.server.db.jpa.AssetDate</class>
        <class>littleware.asset.server.db.jpa.AssetLink</class>
        <class>littleware.asset.server.db.jpa.AssetEntity</class>

    <properties>
    </properties> 
    
  </persistence-unit>
</persistence>

The mock JNDI Context looks like this:

/**
 * Mock JNDI context that is just NOOPs except
 * lookup always returns the DataSource injected via
 * the setDataSource static method.
 * Similar to LittleDriver - just a hack to try to get
 * hibernate to use our DataSource
 */
public class LittleContext implements javax.naming.Context {
    private static  DataSource dataSource = null;
    /**
     * HibernateProvider injects littleware data source at startup time as needed
     */
    public static void setDataSource( DataSource value ) {
        dataSource = value;
    }

    @Override
    public Object lookup(Name name) throws NamingException {
        return dataSource;
    }

    @Override
    public Object lookup(String string) throws NamingException {
        return dataSource;
    }

    @Override
    public void bind(Name name, Object o) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public void bind(String string, Object o) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public void rebind(Name name, Object o) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public void rebind(String string, Object o) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public void unbind(Name name) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public void unbind(String string) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public void rename(Name name, Name name1) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public void rename(String string, String string1) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public NamingEnumeration<NameClassPair> list(Name name) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public NamingEnumeration<NameClassPair> list(String string) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public NamingEnumeration<Binding> listBindings(Name name) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public NamingEnumeration<Binding> listBindings(String string) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public void destroySubcontext(Name name) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public void destroySubcontext(String string) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public Context createSubcontext(Name name) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public Context createSubcontext(String string) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public Object lookupLink(Name name) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public Object lookupLink(String string) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public NameParser getNameParser(Name name) throws NamingException {
        return getNameParser("");
    }

    @Override
    public NameParser getNameParser( String string) throws NamingException {
        return new NameParser(){

            @Override
            public Name parse( final String string) throws NamingException {
                return new Name(){
                    @Override
                    public Object clone() { return this; }
                    
                    @Override
                    public int compareTo(Object o) {
                        throw new UnsupportedOperationException("Not supported yet."); 
                    }

                    @Override
                    public int size() {
                        return 1;
                    }

                    @Override
                    public boolean isEmpty() {
                        return false;
                    }

                    @Override
                    public Enumeration<String> getAll() {
                        throw new UnsupportedOperationException("Not supported yet."); 
                    }

                    @Override
                    public String get(int i) {
                        return string;
                    }

                    @Override
                    public Name getPrefix(int i) {
                        throw new UnsupportedOperationException("Not supported yet."); 
                    }

                    @Override
                    public Name getSuffix(int i) {
                        throw new UnsupportedOperationException("Not supported yet."); 
                    }

                    @Override
                    public boolean startsWith(Name name) {
                        throw new UnsupportedOperationException("Not supported yet."); 
                    }

                    @Override
                    public boolean endsWith(Name name) {
                        throw new UnsupportedOperationException("Not supported yet."); 
                    }

                    @Override
                    public Name addAll(Name name) throws InvalidNameException {
                        throw new UnsupportedOperationException("Not supported yet."); 
                    }

                    @Override
                    public Name addAll(int i, Name name) throws InvalidNameException {
                        throw new UnsupportedOperationException("Not supported yet."); 
                    }

                    @Override
                    public Name add(String string) throws InvalidNameException {
                        throw new UnsupportedOperationException("Not supported yet."); 
                    }

                    @Override
                    public Name add(int i, String string) throws InvalidNameException {
                        throw new UnsupportedOperationException("Not supported yet."); 
                    }

                    @Override
                    public Object remove(int i) throws InvalidNameException {
                        throw new UnsupportedOperationException("Not supported yet."); 
                    }
                };

            }
        };
    }

    @Override
    public Name composeName(Name name, Name name1) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public String composeName(String string, String string1) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public Object addToEnvironment(String string, Object o) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public Object removeFromEnvironment(String string) throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public Hashtable<?, ?> getEnvironment() throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public void close() throws NamingException {}

    @Override
    public String getNameInNamespace() throws NamingException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    
    //---------------------------
    
    /**
     * Assign this to the "java.naming.factory.initial" system property
     * to make the LittleContext mock the initial context.
     */
    public static class Factory implements javax.naming.spi.InitialContextFactory {

        @Override
        public Context getInitialContext(Hashtable<?, ?> hshtbl) throws NamingException {
            return new LittleContext();
        }
        
    }
}

This is the JPA EntityManager guice Provider used in a standalone application:

@Singleton
public class HibernateProvider implements Provider<EntityManagerFactory> {

    private final DataSource dataSource;
    private final String dataSourceURL;
    private EntityManagerFactory emFactory = null;


    @Inject
    public HibernateProvider(@Named("datasource.littleware") DataSource dsource,
            @Named("datasource.littleware") String sDatasourceUrl) {
        dataSource = dsource;
        dataSourceURL = sDatasourceUrl;
    }


    @Override
    public EntityManagerFactory get() {
        if (null == emFactory) {
            LittleDriver.setDataSource( dataSource );
            LittleContext.setDataSource( dataSource );
            if ( null == System.getProperty(  "java.naming.factory.initial" ) ) {
                System.setProperty( "java.naming.factory.initial", LittleContext.Factory.class.getName() );
            }
            
            emFactory = Persistence.createEntityManagerFactory( "littlewarePU" );
        }
        return emFactory;
    }
}

Anyway - I barely know how to use JPA, so duplicate these hacks at your own risk. The code is online here for now (I'm always moving things around in that repo).

Tuesday, June 04, 2013

typescript syntax highlighting in jedit and netbeans

Typescript syntax-highlighting is available in a few editors (vim, emacs, sublime) thanks to editor plugins published by Microsoft, and deeper type-aware language support with code-completion and refactoring is available for Visual Studio. Of course the code editors I usually use (jedit and netbeans) don't include typescript support, so I took a little time to see if I could get something working.

Adding basic typescipt support to jedit was easy. JEdit's syntax highlighting and indentation logic for a particular file is driven by the "mode" xml file associated with the file's extension (.js, .ts, .java, whatever). JEdit extracts lexical rules for syntax-coloring from the lists of keywords, operators, and regular expressions in the mode file. For typescript I associated .ts files with jedit's javascript mode, and added a few items to the keyword lists in javascript's mode xml file (declare, export, ...), and I was pretty happy with the result. This thread on stackoverflow describes where to find mode xml files, and the mode "catalog" file. I also disabled the typoscript (European CMS - who knew?) entry in the catalog, because typoscript also uses the ".ts" file extension:

<MODE NAME="javascript"  FILE="javascript.xml"
    FILE_NAME_GLOB="{*.{js,ts},Buildsub}" />

...
<MODE NAME="typoscript"  FILE="typoscript.xml"
    FILE_NAME_GLOB="*.tsXXX"/<


I tried a similar trick with netbeans, and followed these instructions to configure netbeans to treat ".ts" files as javascript. Unfortunately the resulting behavior in a netbeans editor is not that great. Netbeans builds its language support around a parser of the full language grammar - it doesn't just highlight keywords. The various syntax structures in typescript that are not legal in javascript are highlighted as errors in netbeans. Ugh! Anyway - it's better than nothing. This page has instructions for coding in java a netbeans module for a new language. It would be a fun project to code up a typescript extension for netbeans using the typescript tsc compiler via the java-based rhino javascript engine. This entry describes running tsc with rhino on the command line. I'll save that project for another day ...

Monday, June 03, 2013

compiling typescript with rhino javascript engine

I spent way too long the last couple days patching the typescript compiler (tsc) to run with java's rhino javascript engine. I mentioned in an earlier post how my dell laptop died on me. I wound up buying an hp envy m4 laptop on clearance at BestBuy to replace it, but before that I did that I was goofing around for a couple days getting by with my Android phone and the old PowerBook G4 I pulled off the shelf that amazingly boot up for me. Anyway - I thought I'd try to get tsc running from the command-line. The compiler is javascript compiled from typescript code, so I first tried to build node from source, but node's build setup requires newer versions of python, make, and gcc than what the powerbook had, so then I got the idea to try to run tsc with rhino, since the powerbook did have java 1.5 installed (java 1.6+ ships with rhino and a jrunscript command line tool). Of course it didn't "just work", and I let myself get sucked into banging my head on it - even after I got the new laptop. Ugh.

Anyway - I eventually got tsc running with rhino (the patch listing is further below). The easy part of the project was implementing a rhino version of tsc's IIO interface for file IO. The rhino implementation just calls through to java.io classes, and tsc runs a few feature tests to figure out which javascript engine it's running under:

    if (typeof ActiveXObject === "function")
        return getWindowsScriptHostIO();
    else if (typeof require === "function")
        return getNodeIO();
    else if ( typeof java != "undefined" )
        return getRhinoIO();
    else
        return null; // Unsupported host

The IO code was straight forward; the painful part was working around rhino's quirks. The first quirk I ran into was that rhino appears to treat the names of java's primitive types as reserved words, so things like:
var byte = 0;
or option.short = "v";
are illegal. Fortunately - that only popped up a couple places in the tsc code, but it's an unfortunate "feature" for rhino to have.

Another problem I ran into was invoking "delete" on an instance of java.io.File. Javascript includes delete in its collection of reserved words, but it should be legal to include a "delete()" method on some class. Rhino's javascript grammar probably just needs some love. The workaround was to access the method via f["delete"]() instead of f.delete().

C:\Users\Reuben\Documents\Code\typescript\src\compiler
> node
> var Foo = function() { return this; }
undefined
> Foo.prototype.delete = function() { return "bla"; }
[Function]
> (new Foo()).delete();
'bla'

...


> C:\Users\Reuben\Documents\Code\typescript\src\compiler
> jrunscript
js> var Foo = function() { return this; }
js> Foo.prototype.delete = function() { return "bla"; }
script error: sun.org.mozilla.javascript.internal.EvaluatorException: missing name after . operator (<STN> at line number 1
js>

Another problematic feature of rhino is that it does not hide the distinction between javascript's string type (which tsc expects), and java's java.lang.String. I discovered that Rhino has methods for converting between java types and javascript types - including the String() method mentioned here:

    resolvePath: function (path) {
        return <string> String( (new java.io.File(path)).getCanonicalPath() );
    },

There were one or two other small rhino quirks to work out, but the big one that surprised me was that rhino's regular expression objects apparently don't respect javascript's normal scoping rules. The tsc compiler was failing under rhino with various undefined types that weren't properly pulled in via the file-reference comments (see section 9.1.1 in the typescript spec). I eventually had a test setup, and found rhino would load every other referenced file, so a tsc run with rhino had this output:

(1)Reading code from C:/Users/Reuben/Documents/Code/typescript/src/compiler/typescript.ts
Found code at C:/Users/Reuben/Documents/Code/typescript/src/compiler/typescript.ts
 file reference: diagnostics.ts
 file reference: nodeTypes.ts
 file reference: ast.ts
 file reference: astWalkerCallback.ts
 file reference: astLogger.ts
 file reference: base64.ts
 file reference: emitter.ts
 file reference: parser.ts
 file reference: scanner.ts
 file reference: scopeWalk.ts
 file reference: symbols.ts
 file reference: tokens.ts
 file reference: typeCollection.ts
 file reference: types.ts
 file reference: referenceResolution.ts
 file reference: incrementalParser.ts

The output with nodejs was:

   (1)Reading code from C:/Users/Reuben/Documents/Code/typescript/src/compiler/typescript.ts
   Found code at C:/Users/Reuben/Documents/Code/typescript/src/compiler/typescript.ts
    file reference: diagnostics.ts
    file reference: flags.ts
    file reference: nodeTypes.ts
    file reference: hashTable.ts
    file reference: ast.ts
    file reference: astWalker.ts
    file reference: astWalkerCallback.ts
    file reference: astPath.ts
    file reference: astLogger.ts
    file reference: binder.ts
    file reference: base64.ts
    file reference: sourceMapping.ts
    file reference: emitter.ts
    file reference: errorReporter.ts
    file reference: parser.ts
    file reference: printContext.ts
    file reference: scanner.ts
    file reference: scopeAssignment.ts
    file reference: scopeWalk.ts
    file reference: signatures.ts
    file reference: symbols.ts
    file reference: symbolScope.ts
    file reference: tokens.ts
    file reference: typeChecker.ts
    file reference: typeCollection.ts
    file reference: typeFlow.ts
    file reference: types.ts
    file reference: pathUtils.ts
    file reference: referenceResolution.ts
    file reference: precompile.ts
    file reference: incrementalParser.ts
    file reference: declarationEmitter.ts

Anyway, long story short, it turned out that the reference strings were each processed by a function with a regular expression, and rhino had this crazy behavior where regular expression objects appear to be global.

with rhino:

js> function doTest( s ) { var rx =  /^\s*(\/\/\/\s*/gim;
return (rx.exec(s) == null); }

js> doTest(comment);
false
js> doTest(comment);
true
js> doTest(comment);
false
js> doTest(comment);
true

with node:
> function doTest(s) {
... var rx =  /^\s*(\/\/\/\s*/gim;
... return (rx.exec(s) == null);
... }
undefined
> comment
'///<reference path=\'sourceMapping.ts\' />'
> doTest(comment);
false
> doTest(comment);
false
> doTest(comment);
false
> doTest(comment);
false
> doTest(comment);
false

Unbelievable. Anyway - the work around is to reset the rx.lastIndex before each run, so:

    function getFileReferenceFromReferencePath(comment: string): IFileReference {
        var referencesRegEx = /^(\/\/\/\s*<reference\s+path=)('|")(.+?)\2\s*(static=('|")(.+?)\2\s*)*\/>/gim;
        referencesRegEx.lastIndex = 0;  // work around ridiculous bug in rhino ...
        var match = referencesRegEx.exec(comment);
        ...

Finally - I just tested this stuff by running the compiler on itself. The typescript repo on codeplex includes a bunch of test cases and an nmake based Makefile, but I was too lazy to download visual studio and get that working. In the end - rhino compiled tsc in 2 minutes, and node did it in 3 seconds. Ugh!

C:\Users\Reuben\Documents\Code\typescript\src\compiler
> date; jrunscript tsc.js --out tsc2.js tsc.ts; date

Monday, June 3, 2013 12:49:41 PM
Monday, June 3, 2013 12:51:39 PM


C:\Users\Reuben\Documents\Code\typescript\src\compiler
> date; node tsc2.js --out tsc3.js tsc.ts; date

Monday, June 3, 2013 12:58:40 PM
Monday, June 3, 2013 12:58:43 PM

Update 2013/06/29: I tried java 8's new nashorn javascript engine (in a jdk8 pre-release) to see how it did. Nashorn currently runs the tsc compile about 10% faster than rhino - still a lot slower than node. Doh!

> date; & 'C:\Program Files\Java\jdk1.8.0\bin\jrunscript.exe' .\tsc2.js --out tsc2.js tsc.ts; date;

Saturday, June 29, 2013 5:29:11 PM
Saturday, June 29, 2013 5:30:57 PM

Anyway - I'll check to see if the typescript maintainers will accept this patch, but I'll be surprised if they want anything to do with rhino after reading this sad tale ...


diff --git a/src/compiler/base64.ts b/src/compiler/base64.ts
index ee2d3c5..b4fc315 100644
--- a/src/compiler/base64.ts
+++ b/src/compiler/base64.ts
@@ -67,20 +67,21 @@ module TypeScript {
 
             var shift = 0;
             for (var i = 0; i < inString.length; i++) {
-                var byte = Base64Format.decodeChar(inString[i]);
+                // note: "byte" is reserved in java Rhino javascript environment - ugh
+                var bite = Base64Format.decodeChar(inString[i]);
                 if (i === 0) {
                     // Sign bit appears in the LSBit of the first value
-                    if ((byte & 1) === 1) {
+                    if ((bite & 1) === 1) {
                         negative = true;
                     }
-                    result = (byte >> 1) & 15; // 1111x
+                    result = (bite >> 1) & 15; // 1111x
                 } else {
-                    result = result | ((byte & 31) << shift); // 11111
+                    result = result | ((bite & 31) << shift); // 11111
                 }
 
                 shift += (i == 0) ? 4 : 5;
 
-                if ((byte & 32) === 32) {
+                if ((bite & 32) === 32) {
                     // Continue
                 } else {
                     return { value: negative ? -(result) : result, rest: inString.substr(i + 1) };
diff --git a/src/compiler/io.ts b/src/compiler/io.ts
index a5eb1ad..6e75bf2 100644
--- a/src/compiler/io.ts
+++ b/src/compiler/io.ts
@@ -13,6 +13,9 @@
 // limitations under the License.
 //
 
+declare var arguments:any;
+var javaArgs:any = arguments; // Rhino sets global arguments ... bla
+
 interface IResolvedFile {
     content: string;
     path: string;
@@ -105,7 +108,10 @@ declare class Enumerator {
     constructor (o: any);
 }
 declare function setTimeout(callback: () =>void , ms?: number);
+
 declare var require: any;
+declare var java: any;
+
 declare module process {
     export var argv: string[];
     export var platform: string;
@@ -123,7 +129,7 @@ declare module process {
 }
 
 var IO = (function() {
-
+    
     // Create an IO object for use inside WindowsScriptHost hosts
     // Depends on WSCript and FileSystemObject
     function getWindowsScriptHostIO(): IIO {
@@ -533,12 +539,251 @@ var IO = (function() {
             },
             quit: process.exit
         }
-    };
+    }
+    ;
+        
+    
+    function getRhinoIO():IIO {
+        var utf8 = java.nio.charset.Charset.forName( "UTF-8" );
+        var jscriptArgs = [];
+   
+        for( var i=0; i < javaArgs.length; ++i ) {
+            //
+            // convert java string to javascript string (so javascript string methods work - ugh!)
+            // see https://groups.google.com/forum/?fromgroups#!topic/mozilla.dev.tech.js-engine.rhino/FV15_KJVLGM
+            //
+            jscriptArgs.push( String( javaArgs[i] ) );
+        }
+        
+        
+        /**
+         * Byte-order-mark detector - ugh.
+         * @param streamInn java.io.InputStream
+         * @return java.io.Reader
+         * @see http://blog.publicobject.com/2010/08/handling-byte-order-mark-in-java.html
+         */
+       function inputStreamToReader(streamIn) {
+         // buffered stream supports mark and reset
+         var stream = new java.io.BufferedInputStream( streamIn );
+         stream.mark(3);
+         var byte1 = stream.read();
+         var byte2 = stream.read();
+         if (byte1 == 0xFF && byte2 == 0xFE) {
+           return new java.io.InputStreamReader(stream, "UTF-16LE");
+         } else if (byte1 == 0xFF && byte2 == 0xFF) {
+           return new java.io.InputStreamReader(stream, "UTF-16BE");
+         } else {
+           var byte3 = stream.read();
+           if (byte1 == 0xEF && byte2 == 0xBB && byte3 == 0xBF) {
+             return new java.io.InputStreamReader(stream, "UTF-8");
+           } else {
+             stream.reset();
+             return new java.io.InputStreamReader(stream);
+           }
+         }
+       };
+
+          return {
+            readFile: function (file):string {
+                try  {
+                    var f = new java.io.File( file );
+                    if( (! f.exists()) || (! f.isFile()) ) { return ""; }
+                    var buffer = java.lang.reflect.Array.newInstance( java.lang.Character.TYPE, f.length() + 128 ); // 128 fudge
+                    var reader = new java.io.BufferedReader( 
+                       inputStreamToReader(
+                              new java.io.FileInputStream( f ) 
+                         ) 
+                    );
+                    try {
+                     var offset = 0;
+                        for( var step = reader.read( buffer, offset, buffer.length - offset ); 
+                             step >= 0; step = reader.read( buffer, offset, buffer.length - offset ) ) {
+                             offset += step;
+                             //java.lang.System.out.println( "Just read num bytes: " + step );
+                        }
+                        var javaString = new java.lang.String( buffer, 0, offset )
+                        //java.lang.System.out.println( "Read: " + javaString );
+                        // convert java string to javascript string ... ugh
+                        return <string> String( new java.lang.String( buffer, 0, offset ) );
+                    } catch (ex) { 
+                        reader.close();
+                        ex.printStackTrace( java.lang.System.err );
+                        throw ex; 
+                    }
+                } catch (e) {
+                    IOUtils.throwIOError("Error reading file \"" + file + "\" - " + e.toString(), e );
+                }
+            },
+            writeFile: function( path, content ) {
+               var f = new java.io.File( path );
+               if( f.exists() && f.isFile() ) {
+                   var writer = new java.io.OutputStreamWriter(
+                         new java.io.FileOutputStream( f ), utf8
+                    );
+                    writer.write( content );
+                    writer.close();
+               }
+            },
+            deleteFile: function (path) {
+               var f = new java.io.File( path );
+               if( f.exists() && f.isFile() ) {
+                   // delete is reserved in javascript - confused Rhino parser - ugh
+                   f["delete"]();
+                }
+            },
+            fileExists: function (path) {
+                var result:bool = (new java.io.File( path )).exists();
+                return result;
+            },
+            createFile: function (path, useUTF8?) {
+                var f = new java.io.File( path );
+                if ( f.exists() && (! f.isFile()) ) {
+                    IOUtils.throwIOError("Error creating file \"" + path + "\".", null ); 
+                } else if ( ! f.exists() ) {
+                    var dir = f.getParentFile();
+                    dir.mkdirs();
+                }
+                try  {
+                    var writer = new java.io.OutputStreamWriter(
+                             new java.io.FileOutputStream( f ), utf8
+                            );
+                } catch (e) {
+                    IOUtils.throwIOError("Couldn't write to file '" + path + "'.", e);
+                }
+                return new IOUtils.BufferedTextWriter( {
+                    Write: function (str:string ) {
+                        writer.write( str );
+                    },
+                    WriteLine: function (str:string) {
+                        writer.write( str + "\n" );
+                    },
+                    Close: function () {
+                        writer.close();
+                        writer = null;
+                    }
+                } );
+            },
+            dir: function dir(path, spec?, options?):string[] {
+                options = options || {
+                };
+                function filesInFolder(folder:any):string[] {
+                    var paths = [];
+                    var files = folder.listFiles();
+                    for(var i = 0; i < files.length; i++) {
+                        var f = files[i];
+                        if(options.recursive && f.isDirectory()) {
+                            paths = paths.concat(filesInFolder(f));
+                        } else if(f.isFile() && (!spec || f.getName().match(spec))) {
+                            paths.push( String( f.getPath() ) ); // convert to javascript String
+                        }
+                    }
+                    return paths;
+                }
+                return filesInFolder( new java.io.File( path ) );
+            },
+            createDirectory: function (path) {
+                try  {
+                    if(!this.directoryExists(path)) {
+                       (new java.io.File( path )).mkdirs();
+                    }
+                } catch (e) {
+                    IOUtils.throwIOError("Couldn't create directory '" + path + "'.", e);
+                }
+            },
+            directoryExists: function (path) {
+                var f = new java.io.File( path );
+                var result:bool = f.exists() && f.isDirectory();
+                return result;
+            },
+            resolvePath: function (path) {
+                return <string> String( (new java.io.File(path)).getCanonicalPath() );
+            },
+            dirName: function (path) {
+                return <string> String( (new java.io.File( path )).getCanonicalFile().getParent() );
+            },
+            findFile: function (rootPath, partialFilePath) {
+                var scan = new java.io.File( rootPath + "/" + partialFilePath ).getCanonicalFile();
+                while(true) {
+                    if( scan.exists() ) {
+                        try  {
+                            var content = this.readFile( scan.getPath() );
+                            return {
+                                content: <string> content,
+                                path: <string> String( scan.getPath() )
+                            };
+                        } catch (err) {
+                        }
+                    } else {
+                        // climb up the file system ... ?
+                        var parent = (new java.io.File( rootPath )).getParent();
+                        if( parent == null ) {
+                            return null;
+                        } else {
+                            scan = new java.io.File( parent, partialFilePath );
+                        }
+                    }
+                }
+            },
+            print: function (str) {
+                java.lang.System.out.print( str );
+            },
+            printLine: function (str) {
+                java.lang.System.out.println( str );
+            },
+            arguments: <string[]> jscriptArgs, 
+            stderr: {
+                Write: function (str) {
+                    java.lang.System.err.print(str);
+                },
+                WriteLine: function (str) {
+                    java.lang.System.err.println(str );
+                },
+                Close: function () {
+                }
+            },
+            stdout: {
+                Write: function (str) {
+                    java.lang.System.out.print(str);
+                },
+                WriteLine: function (str) {
+                    java.lang.System.out.println(str );
+                },
+                Close: function () {
+                }
+            },
+            
+            /**
+             * Could implement watchFile() with java-7 nio2 code, but too lazy to bother,
+             * since WindowsScriptHost skips this method too ... :)
+             * @see http://docs.oracle.com/javase/tutorial/essential/io/notification.html 
+             */
+            watchFile: null,
+            run: function(source, filename) {
+                try {
+                    eval(source);
+                } catch (e) {
+                    IOUtils.throwIOError("Error while executing file '" + filename + "'.", e);
+                }
+            },
+            getExecutingFilePath: function () {
+                return this.arguments[0];
+            },
+            quit: function (exitCode? : number = 0) {
+                try {
+                    java.lang.System.lang.exit(exitCode);
+                } catch (e) {
+                }
+            }
+        };
+    }
+    ;
 
     if (typeof ActiveXObject === "function")
         return getWindowsScriptHostIO();
     else if (typeof require === "function")
         return getNodeIO();
+    else if ( typeof java != "undefined" )
+        return getRhinoIO();
     else
         return null; // Unsupported host
 })();
diff --git a/src/compiler/optionsParser.ts b/src/compiler/optionsParser.ts
index a10fb8f..7a7eb73 100644
--- a/src/compiler/optionsParser.ts
+++ b/src/compiler/optionsParser.ts
@@ -18,7 +18,7 @@
 interface IOptions {
     name?: string;
     flag?: bool;
-    short?: string;
+    shorty?: string;  // note: "short" is reserved in java
     usage?: string;
     set?: (s: string) => void;
     type?: string;
@@ -34,7 +34,7 @@ class OptionsParser {
 
         for (var i = 0; i < this.options.length; i++) {
 
-            if (arg === this.options[i].short || arg === this.options[i].name) {
+            if (arg === this.options[i].shorty || arg === this.options[i].name) {
                 return this.options[i];
             }
         }
@@ -89,8 +89,8 @@ class OptionsParser {
             var usageString = "  ";
             var type = option.type ? " " + option.type.toUpperCase() : "";
 
-            if (option.short) {
-                usageString += this.DEFAULT_SHORT_FLAG + option.short + type + ", ";
+            if (option.shorty) {
+                usageString += this.DEFAULT_SHORT_FLAG + option.shorty + type + ", ";
             }
 
             usageString += this.DEFAULT_LONG_FLAG + option.name + type;
@@ -110,27 +110,27 @@ class OptionsParser {
         }
     }
 
-    public option(name: string, config: IOptions, short?: string) {
+    public option(name: string, config: IOptions, shorty?: string) {
         if (!config) {
-            config = <any>short;
-            short = null;
+            config = <any>shorty;
+            shorty = null;
         }
 
         config.name = name;
-        config.short = short;
+        config.shorty = shorty;
         config.flag = false;
 
         this.options.push(config);
     }
 
-    public flag(name: string, config: IOptions, short?: string) {
+    public flag(name: string, config: IOptions, shorty?: string) {
         if (!config) {
-            config = <any>short;
-            short = null;
+            config = <any>shorty;
+            shorty = null;
         }
 
         config.name = name;
-        config.short = short;
+        config.shorty = shorty;
         config.flag = true
 
         this.options.push(config);
diff --git a/src/compiler/precompile.ts b/src/compiler/precompile.ts
index 88adf32..c66f937 100644
--- a/src/compiler/precompile.ts
+++ b/src/compiler/precompile.ts
@@ -131,6 +131,7 @@ module TypeScript {
 
     function getFileReferenceFromReferencePath(comment: string): IFileReference {
         var referencesRegEx = /^(\/\/\/\s*<reference\s+path=)('|")(.+?)\2\s*(static=('|")(.+?)\2\s*)*\/>/gim;
+        referencesRegEx.lastIndex = 0;  // work around ridiculous bug in rhino ...
         var match = referencesRegEx.exec(comment);
 
         if (match) {
@@ -294,6 +295,7 @@ module TypeScript {
             
             if (!comment.isBlock) {
                 var referencedCode = getFileReferenceFromReferencePath(comment.getText());
+                //CompilerDiagnostics.debugPrint( "Considering comment as possible reference (" + (referencedCode ? "ok" : "no") + "): " + comment.getText() );
                 if (referencedCode) {
                     referencedCode.minChar = comment.startPos;
                     referencedCode.limChar = referencedCode.minChar + comment.value.length;
diff --git a/src/compiler/referenceResolution.ts b/src/compiler/referenceResolution.ts
index 442d8ec..52ae47c 100644
--- a/src/compiler/referenceResolution.ts
+++ b/src/compiler/referenceResolution.ts
@@ -102,7 +102,7 @@ module TypeScript {
                 // if the path is relative, or came from a reference tag, we don't perform a search
                 if (isRelativePath || isRootedPath || !performSearch) {
                     try {
-                        CompilerDiagnostics.debugPrint("   Reading code from " + normalizedPath);
+                        CompilerDiagnostics.debugPrint("   (1)Reading code from " + normalizedPath);
                             
                         // Look for the .ts file first - if not present, use the .ts, the .d.str and the .d.ts
                         try {
@@ -116,19 +116,19 @@ module TypeScript {
                                 else if (isTSFile(normalizedPath)) {
                                     normalizedPath = changePathToSTR(normalizedPath);
                                 }
-                                CompilerDiagnostics.debugPrint("   Reading code from " + normalizedPath);
+                                CompilerDiagnostics.debugPrint("   (2)Reading code from " + normalizedPath);
                                 resolvedFile.content = ioHost.readFile(normalizedPath);
                             }
                             catch (err) {
                                 normalizedPath = changePathToDSTR(normalizedPath);
-                                CompilerDiagnostics.debugPrint("   Reading code from " + normalizedPath);
+                                CompilerDiagnostics.debugPrint("   (3)Reading code from " + normalizedPath);
 
                                 try {
                                     resolvedFile.content = ioHost.readFile(normalizedPath);
                                 }
                                 catch (err) {
                                     normalizedPath = changePathToDTS(normalizedPath);
-                                    CompilerDiagnostics.debugPrint("   Reading code from " + normalizedPath);
+                                    CompilerDiagnostics.debugPrint("   (4)Reading code from " + normalizedPath);
                                     resolvedFile.content = ioHost.readFile(normalizedPath);
                                 }
                             }
@@ -148,7 +148,10 @@ module TypeScript {
 
                     // if the path is non-relative, we should attempt to search on the relative path
                     resolvedFile = ioHost.findFile(parentPath, normalizedPath);
-
+                    CompilerDiagnostics.debugPrint("   Attempting to resolve (" + parentPath + ", " + normalizedPath + ") got: " + 
+                         (resolvedFile == null) ? "null" : resolvedFile.path 
+                    );
+                    
                     if (!resolvedFile) {
                         if (isSTRFile(normalizedPath)) {
                             normalizedPath = changePathToTS(normalizedPath);
@@ -187,6 +190,10 @@ module TypeScript {
                     var resolvedFilePath = ioHost.resolvePath(resolvedFile.path);
                     sourceUnit.referencedFiles = preProcessedFileInfo.referencedFiles;
 
+                    for (var i = 0; i < preProcessedFileInfo.referencedFiles.length; i++) {
+                        var fileReference = preProcessedFileInfo.referencedFiles[i];
+                        CompilerDiagnostics.debugPrint("    file reference: " + fileReference.path);
+                    }
                     // resolve explicit references
                     for (var i = 0; i < preProcessedFileInfo.referencedFiles.length; i++) {
                         var fileReference = preProcessedFileInfo.referencedFiles[i];

Wednesday, May 29, 2013

6 beeps of death!

My old dell laptop that I bought in December, 2009 has finally reached its pathetic end - the laptop beeps 6 times at boot (video card test failure), then sits there like a lump. It looks like there's no easy fix, so it's time to buy a new laptop. Ugh!

The timing is both good and bad. Good - because my last freelance contract ended a couple weeks ago, so I'm between projects right now, and not having a laptop for a few days forces me to disconnect (or borrow the wife's computer to add to the bLog ...). The timing is bad, because Intel's 4th generation Haswell chips aren't due for release until next week (June 4th) at Computex. Apple's WWDC opens June 10, so I need to wait at least one week to order a Haswell Windows laptop or two weeks for a Haswell Mac.

I still haven't decided exactly how I want to spec out the new box. I know I want at least 8GB RAM and a Core-i5 processor - preferably 4th gen. I'd rather have 128 GB SSD (really 256 GB would be great) than a 556 GB disk, and I prefer an ultrabook (no ethernet, firewire, DVD, whatever - just wireless and ports for USB-3 and display) than a traditional laptop. I think I'd rather have a Mac than Windows this time around - especially since people force Apple to monitor its environment impact and labor conditions in China (I'd be surprised if Lenovo or Asus care about that).

I'd also rather spend $1000 than $1500. I'm not sure if I can satisfy all my constraints! Anyway - I'll wait till next week - hopefully some new systems are released at Computex.

Monday, May 27, 2013

ant rules for typescript compile

I extended littleware's ant template with rules to compile typescript (*.ts) files in web projects using this ant-macro posted on stackoverflow. The typescript and yuidoc rules are listed further below.

Littleware's build system just extends netbeans' ant templates with IVY support. Netbeans' ant scripts are designed to support extension hooks that run before and after the different build stages (initialization, compile, package, ...). This is from the comment at the top of a new netbeans project's build.xml file:


    There exist several targets which are by default empty and which can be 
    used for execution of your tasks. These targets are usually executed 
    before and after some main targets. They are: 

      -pre-init:                 called before initialization of project properties 
      -post-init:                called after initialization of project properties 
      -pre-compile:              called before javac compilation 
      -post-compile:             called after javac compilation 
      -pre-compile-single:       called before javac compilation of single file
      -post-compile-single:      called after javac compilation of single file
      -pre-compile-test:         called before javac compilation of JUnit tests
      -post-compile-test:        called after javac compilation of JUnit tests
      -pre-compile-test-single:  called before javac compilation of single JUnit test
      -post-compile-test-single: called after javac compilation of single JUunit test
      -pre-dist:                 called before archive building 
      -post-dist:                called after archive building 
      -post-clean:               called after cleaning build products 
      -pre-run-deploy:           called before deploying
      -post-run-deploy:          called after deploying

...

Anyway - I'm glad for netbeans' extensible ant templates. Littleware's build system could benefit from another rework to better support scala and IVY, but it's good enough for now.

Finally - I also wanted to mention that Ian Obermiller posted on github a typescript brush for Alex Gorbatchev's syntax highlighter. It seems to work great ...

Here are those ant rules I mentioned earlier ...

<target name="config-check">
     <!--
    <available file="${ivy.jar.dir}/littlesettings.xml" property="settings.exists"/>
    -->
    <available file="${ivy.jar.file}" property="ivyjar.exists"/>
    <condition property="skip.download">
        <and>
            <isset property="ivyjar.exists" />
        </and>
    </condition>
    <available file="ivy/test" property="ivy.resolve.test.exists" />
    <available file="ivy/compile" property="ivy.resolve.compile.exists" />

    ...
    
    <condition property="ivy.resolve.exists">
        <and>
            <isset property="ivy.resolve.test.exists" />
            <isset property="ivy.resolve.compile.exists" />
        </and>
    </condition>
    
    <condition property="typescript.ready">
        <and>
            <isset property="build.web.dir" />
            <isset property="typescript.compiler.path" />
            <available file="${typescript.compiler.path}" />
        </and>
    </condition>    
    
    <condition property="ydoc.ready">
        <and>
            <isset property="build.web.dir" />
            <isset property="ydoc.path" />
            <available file="${ydoc.path}" />
        </and>
    </condition>    
    
</target>

<!-- web project stuff -->

<property name="typescript.compiler.path" value="${user.home}/AppData/Roaming/npm/tsc.cmd" />
<property name="ydoc.path" value="${user.home}/Documents/Code/yuidoc/bin/ydoc.bat" />


<target name="ydoc" depends="config-check" if="ydoc.ready" 
    description="run ydoc on build/ js"
    >
  <exec executable="${ydoc.path}">
    <arg value="${build.web.dir}/resources/js/littleware"/>
 </exec>    
</target>

    
<!--
    Recursively read a source directory for TypeScript files, generate a compile list in the
    format needed by the TypeScript compiler adding every parameters it take.
    Thanks Tekool.net:
       http://stackoverflow.com/questions/12799237/how-to-watch-and-compile-all-typescript-sources
-->
<macrodef name="TypeScriptCompileDir">

    <!-- required attribute -->
    <attribute name="src" />

    <!-- optional attributes -->
    <attribute name="out" default="" />
    <attribute name="module" default="" />
    <attribute name="comments" default="" />
    <attribute name="declarations" default="" />
    <attribute name="nolib" default="" />
    <attribute name="target" default="" />

    <sequential>

        <!-- local properties -->
        <local name="out.arg"/>
        <local name="module.arg"/>
        <local name="comments.arg"/>
        <local name="declarations.arg"/>
        <local name="nolib.arg"/>
        <local name="target.arg"/>
        <local name="typescript.file.list"/>
        <local name="tsc.compile.file"/>

        <property name="tsc.compile.file" value="@{src}compile.list" />

        <!-- Optional arguments are not written to compile file when attributes not set -->
        <condition property="out.arg" value="" else='--out "@{out}"'>
            <equals arg1="@{out}" arg2="" />
        </condition>

        <condition property="module.arg" value="" else="--module @{module}">
            <equals arg1="@{module}" arg2="" />
        </condition>

        <condition property="comments.arg" value="" else="--comments">
            <equals arg1="@{comments}" arg2="" />
        </condition>

        <condition property="declarations.arg" value="" else="--declarations">
            <equals arg1="@{declarations}" arg2="" />
        </condition>

        <condition property="nolib.arg" value="" else="--nolib">
            <equals arg1="@{nolib}" arg2="" />
        </condition>

        <!-- Could have been defaulted to ES3 but let the compiler uses its own default is quite better -->
        <condition property="target.arg" value="" else="--target @{target}">
            <equals arg1="@{target}" arg2="" />
        </condition>

        <!-- Recursively read TypeScript source directory and generate a compile list -->
        <pathconvert property="typescript.file.list" dirsep="\" pathsep="${line.separator}">

            <fileset dir="@{src}">
                <include name="**/*.ts" />
                <exclude name="**/*.d.ts" />
            </fileset>

            <!-- In case regexp doesn't work on your computer, comment <mapper /> and uncomment <regexpmapper /> -->
            <mapper type="regexp" from="^(.*)$" to='"\1"' />
            <!--regexpmapper from="^(.*)$" to='"\1"' /-->

        </pathconvert>


        <!-- Write to the file -->
        <echo message="Writing tsc command line arguments to : ${tsc.compile.file}" />
        <echo file="${tsc.compile.file}" message="${typescript.file.list}${line.separator}${out.arg}${line.separator}${module.arg}${line.separator}${comments.arg}${line.separator}${declarations.arg}${line.separator}${nolib.arg}${line.separator}${target.arg}${line.separator}--sourcemap" append="false" />

        <!-- Compile using the generated compile file --> 
        <echo message="Calling ${typescript.compiler.path} with ${tsc.compile.file}" />
        <exec dir="." executable="${typescript.compiler.path}">
            <arg value="@${tsc.compile.file}"/>
        </exec>

        <!-- Finally delete the compile file 
        <echo message="${tsc.compile.file} deleted" />
        <delete file="${tsc.compile.file}" />
        -->

    </sequential>

</macrodef>


<target name="typescript" depends="config-check" if="typescript.ready" 
    description="compile typescript files build/*.ts"
    >
      <TypeScriptCompileDir
          src="${build.web.dir}/resources/js/"
          module="amd"
      />      
      
    <!-- out="${build.web.dir}/resources/js" -->
</target>


<!-- post-compile rule kicks in if build.web.dir netbeans property is defined -->
<target name="-post-compile" depends="ydoc,typescript" if="build.web.dir">
      <copy todir="${build.web.dir}/WEB-INF/lib">
        <fileset dir="ivy/test"/>
      </copy>
</target>

Sunday, May 26, 2013

YUI modules with Typescript

I finally made time this week to play with typescript - a statically typed javascript extension developed at Microsoft. I like Typescript a lot. I prefer working with statically typed code for most applications, and Typescript also improves on javascript's syntax. I also think Typescript's approach to extending javascript while maintaining direct interoperability with existing javascript code (typescript can call javascript code directly, and vice versa) is better than the "rebuild the universe" strategy taken by dart and gwt.

I've enjoyed using yui in the few small javascript modules I've written (tracked in google code), so one of my first typescript tasks was to publish typescript code as a yui module, and to use other yui modules from typescript. I'm new to typescript, so the following explanation may be wrong, but here it goes.

Typescript supports modules via module, export, and import keywords. A typescript module compiles to either an AMD or CommonJS style javascript module depending on a compiler flag (the typescript specification covers all this). So a typescript file like this:


/// <reference path="yui" />
/// <reference path="toyA" />


export module littleware.toy.b {
    
    export import modA = module( "toyA" );

    export class Greeter {
        delegate:modA.littleware.toy.a.Greeter;
    
        constructor(message: string) {
            this.delegate = new modA.littleware.toy.a.Greeter( message );
        }
        greet() {
            return this.delegate.greet();
        }
    }
    
    export function runGreeter( greeter:Greeter ) {
        var button = document.createElement('button');
        button.innerText = "Say Hello";
        button.onclick = function() {
            alert(greeter.greet());
        }
        
        document.body.appendChild(button);
    }

}

Compiles as commonjs like this:


(function (littleware) {
    (function (toy) {
        (function (b) {
            var modA = require("./toyA")
            var Greeter = (function () {
                function Greeter(message) {
                    this.delegate = new modA.littleware.toy.a.Greeter(message);
                }
                Greeter.prototype.greet = function () {
                    return this.delegate.greet();
                };
                return Greeter;
            })();
            b.Greeter = Greeter;            
            function runGreeter(greeter) {
                var button = document.createElement('button');
                button.innerText = "Say Hello";
                button.onclick = function () {
                    alert(greeter.greet());
                };
                document.body.appendChild(button);
            }
            b.runGreeter = runGreeter;
        })(toy.b || (toy.b = {}));
        var b = toy.b;
    })(littleware.toy || (littleware.toy = {}));
    var toy = littleware.toy;
})(exports.littleware || (exports.littleware = {}));
var littleware = exports.littleware;

This is the amd output for the same code:

define(["require", "exports", "toyA"], function(require, exports, __modA__) {
    (function (littleware) {
        (function (toy) {
            (function (b) {
                var modA = __modA__;

                var Greeter = (function () {
                    function Greeter(message) {
                        this.delegate = new modA.littleware.toy.a.Greeter(message);
                    }
                    Greeter.prototype.greet = function () {
                        return this.delegate.greet();
                    };
                    return Greeter;
                })();
                b.Greeter = Greeter;                
                function runGreeter(greeter) {
                    var button = document.createElement('button');
                    button.innerText = "Say Hello";
                    button.onclick = function () {
                        alert(greeter.greet());
                    };
                    document.body.appendChild(button);
                }
                b.runGreeter = runGreeter;
            })(toy.b || (toy.b = {}));
            var b = toy.b;
        })(littleware.toy || (littleware.toy = {}));
        var toy = littleware.toy;
    })(exports.littleware || (exports.littleware = {}));
    var littleware = exports.littleware;
})

The yui module system expects a module to register itself via a call to YUI.add with a closure function that accepts a YUI instance variable "Y" to use as the root of the module namespace. A YUI module for our typescript code could be implemented using the amd definition something like this:


YUI.add('littleware-littleUtil', function(Y) {
    // call the lambda passed as 2nd argument to amd define
    lambda( Y, Y, Y );
}, '0.1.1', { 'requires' : [ 'littleware-toy-a' ] } );

I was able to hack something to incorporate the amd module-definitions output from the typescript compiler into a YUI module system - there are just a few tricks.

The first trick is to introduce our own global define method that connects the amd module definitions with the YUI module system:


function define( argNames, moduleThunk ) {
  var name = null;
  // hacky way to get the YUI module name ...
  try { moduleThunk( null, null ); } catch ( v ) { name = v; }
  YUI.add(name, function(Y) {
    var thunkArgs = [];
    for( var i=0; i < argNames.length; ++i ) {
      thunkArgs.push( Y );
    }
    moduleThunk.apply( Y, thunkArgs );
  }, '0.1.1' );

Second trick - the YUI.add call that registers a module's closure with YUI requires a module name. The hack I came up with is to call into the amd module-definition with null arguments, and add a block of code to the typescript that checks for nulls, and throws a "module name" exception:


declare var exports:Y;

//declare module littleware.toy.a;
if ( null == exports ) {
    // Hook to communicate out to YUI module system a YUI module-name for this typescript file
    throw "littleware-toy-toyB";
}

var Y:Y = exports;

This is a complete hack that exposes the javascript compiler output to the typescript code, but it works. The
var Y:Y = exports;
exposes the YUI instance "Y" to the typescript code. Juan Dopazo posted his node script that generates typescript type-definitions for YUI. I saved the output file he posted as "yui.d.ts", cleaned it up a bit, and included that in my build, so the type-script compiler can perform type-checking on YUI calls. I modified the typescript toy to take advantage of YUI DOM utilities just to verify all this module magic worked:

  var counter = 0;

  export function runGreeter( greeter:Greeter ) {
      var toyNode = Y.one( "div.toy" );
      var messageNode = toyNode.one( "p" );
      var button = document.createElement('button');
      button.innerText = "Say Hello";
      button.onclick = function() {
          messageNode.setHTML( "" + counter + ": " + greeter.greet()  );
          counter += 1;
      }

      toyNode.append(button);
  }

The "YUI.add" module registration includes options for specifying a module's dependencies on other YUI modules, but I prefer to specify that information in the YUI-load configuration that tells YUI which javascript file contains which module. I include that code along with my global "define" amd-hack function in a little bootstrap.js file that I include in the html host file via a <script> tag.

/*
 * Copyright 2011 catdogboy at yahoo.com
 *
 * The contents of this file are subject to the terms of the
 * Lesser GNU General Public License (LGPL) Version 2.1.
 * http://www.gnu.org/licenses/lgpl-2.1.html.
 */

if( window.littleware == undefined ) {
    window.littleware = {};
}

/**
 * littleYUI module, see http://yuiblog.com/blog/2007/06/12/module-pattern/
 * YUI doc comments: http://developer.yahoo.com/yui/yuidoc/
 * YUI extension mechanism: http://developer.yahoo.com/yui/3/yui/#yuiadd
 * Provides convenience method for loading YUI with the littleware extension modules,
 * so client javascript code just invokes:
 *      littleware.littleYUI.bootstrap()... 
 *
 * @module littleware.littleYUI
 * @namespace auburn.library
 */
littleware.littleYUI = (function() {
    
    /**
     * Get the YUI.config groups entry that registers the littleware javascript
     * modules with - YUI( { ..., groups: { littleware: getLittleModules(), ... } )
     * @method getLittleModules
     * @return dictionary ready to add to YUI config's groups dictionary
     */
    var getLittleModules = function() {
        return {
            combine: false,
            base: '/btrack/resources/js/littleware/',
            modules:  { 
                'littleware-littleUtil': {
                    path: "littleUtil.js",
                    requires: [ "array-extras" ]
                },
                'littleware-littleId': {
                    path: "littleId.js",
                    requires: ['anim', 'base', 'node-base', 'node']
                },
                'littleware-littleTree': {
                    path: "littleTree.js",
                    requires: ['anim', 'base', 'node', 'node-base', 'test']
                },
                'littleware-littleMessage': {
                    path: "littleMessage.js",
                    requires: [ 'io-base', 'node', 'node-base', 
                           'littleware-littleUtil', 'test']
                },
                'littleware-feedback-model': {
                    path: "feedback/littleFeedback.js",
                    requires: [ 'node', 'base', 'littleware-littleUtil', 'test']
                },
                'littleware-feedback-view': {
                    path: "feedback/FbWidget.js",
                    requires: [ 'node', 'base', 'littleware-littleUtil', 
                       'littleware-feedback-model', 'test']
                },
                'littleware-toy-toyA': {
                    path: "toy/toyA.js",
                    requires: [ 'node', 'base', 'littleware-littleUtil', 'test']
                },
                'littleware-toy-toyB': {
                    path: "toy/toyB.js",
                    requires: [ 'littleware-toy-toyA']
                }                                                
            }
        }    ;
    };

    
    /**
     * littleYUI - wrapper around YUI3 YUI() method that
     * registers local littleware modules.
     */
    var bootstrap = function () {
        return YUI({
            //lang: 'ko-KR,en-GB,zh-Hant-TW', // languages in order of preference
            //base: '../../build/', // the base path to the YUI install.  Usually not needed because the default is the same base path as the yui.js include file
            //charset: 'utf-8', // specify a charset for inserted nodes, default is utf-8
            //loadOptional: true, // automatically load optional dependencies, default false
            //combine: true, // use the Yahoo! CDN combo service for YUI resources, default is true unless 'base' has been changed
            filter: 'raw', // apply a filter to load the raw or debug version of YUI files
            timeout: 10000, // specify the amount of time to wait for a node to finish loading before aborting
            insertBefore: 'yuiInsertBeforeMe', // The insertion point for new nodes

            // one or more groups of modules which share the same base path and
            // combo service specification.
            groups: {
                // Note, while this is a valid way to load YUI2, 3.1.0 has intrinsic
                // YUI 2 loading built in.  See the examples to learn how to use
                // this feature.
                littleware: getLittleModules()
            }
        });
    };
    return {
        bootstrap: bootstrap,
        getLittleModules: getLittleModules
    };
})();


/**
 * AMB module hook - still needs work ...
 * @param argNames {Array[String]}
 * @param moduleThunk {function(requires,exports,import1, import2, ...)}
 */
function define( argNames, moduleThunk ) {
  var name = null;
  // hacky way to get the YUI module name ...
  try { moduleThunk( null, null ); } catch ( v ) { name = v; }
  YUI.add(name, function(Y) {
    var thunkArgs = [];
    for( var i=0; i < argNames.length; ++i ) {
      thunkArgs.push( Y );
    }
    moduleThunk.apply( Y, thunkArgs );
  }, '0.1.1' );
}

Anyway - that's a typical javascript mess, but it lets me bootstrap a javascript "main" in a new page with code like this:

   littleware.littleYUI.bootstrap().use( 
                'node', 'node-base', 'event', 'test', 'scrollview',
                'littleware-toy-toyB',
                function (Y) {
                    var util = Y.littleware.littleUtil;
                    var log = new util.Logger( "events.html" );
                    
                    log.log( "main() running" );
                    var toy = Y.littleware.toy.b
                    var greeter = new toy.Greeter( "Dude" );
                    toy.runGreeter( greeter );
                }
        );

Hopefully that makes some sense. I pushed the code up to the littleware repo I've been committing to lately. That repo is a mess, but feel free to take a look.

Friday, May 17, 2013

Updating Web-login for Javascript Webapp

I've been coding away for several days on some old web-login logic I had implemented in littleware to work in web applications with JSF-managed UX. I'm updating the code for use in apps where a javascript program manages the user experience. I've been thinking lately about the similarities and differences between the two application architectures.

The first difference between the two designs is in deployment. The old JSF architecture relied on JSF and JSP templates managed by the server, so I would usually bundle up the entire application in a .war file, and deploy it to some glassfish or tomcat or whatever container. The user interacted with the application by clicking links and submitting forms that moved the browser between web pages rendered by the server.

I'm trying to structure the newer code so that most client-side assets (javascript, CSS, hadlebar templates, ...) are served as static files from S3. The user's in-browser interactions with the application trigger events that are handled by javascript event-handlers. The javascript code running in the user's browser can access remote services via cross-origin AJAX requests. There are various CORS servlet filters open sourced online that take care of setting the HTTP response headers that browsers look for; today I grabbed this one posted by some Ebay coders to github - hopefully it works.

In the login code, both the old and new setups rely on a request filter (javax.servlet.Filter) to intercept unauthenticated client requests. The old code would intercept an unauthenticated page-load request, and forward the request on the server to a login page via RequestDispatcher.forward.

The new code intercepts unauthenticated AJAX requests to JSON services, and responds to the javascript client with an HTTP 401 (unauthorized) response, and relies upon the javascript code on the client to initiate a login process that eventually authenticates with the server by submitting credentials to a login AJAX service.

Finally - in the older applications I had the bad habit of tracking session state on the server via beans stashed in container-managed in-memory session. Tracking user data in-memory on a particular server required that a client continued to interact with the same server it began a session with. In-memory session state also made it difficult to partition a server's functionality between different endpoints. In other words - if I had a server that implemented some functionality "ABC", and I later decided that I would like to split the functionality on that server (or server cluster) between separate "A", "B", and "C" servers (or clusters), then it would be hard to do that if the code relied on shared in-memory session state.

Anyway, the new code avoids session state on the server - instead it relies on the client-side javascript code to track session state in most cases, and transient cookies in a few others.

In the end - I get to throw away a bunch of crazy server-side JSF beans and templates, and replace them with javascript modules and REST services.

Saturday, May 04, 2013

scala REPL classpath hack

While writing code I often want to take a quick look at the behavior of some class or module, and might wind up writing a short test program just to verify that some method does what I expect or that I'm obeying some DSL's state machine or whatever. One of the great things about Scala (and groovy, javascript, clojure, ...) is its REPL, which allows me to test little blocks of code without the hassle of compiling a test file.

Unfortunately, Scala's REPL launch scripts (scala.bat, scala.sh) do not provide command-line options for manipulating the REPL's classpath, so the REPL can't see the jars IVY assembles for my project ( javasimon, joda-time, guava, gson, guice, ... ).

Anyway - it's easy to just copy %SCALA_HOME%/bin/scala.bat (or ${SCALA_HOME}/scala if not on Windows - whatever), and modify it, so the REPL runs with the same classpath as your project, and at the same time set the heap size, logging config-file system property, and whatever else is needed. For example, here's the shell.bat launch-script from a project I was playing with recently.

@echo off

if not defined JAVACMD set JAVACMD=java.exe
if defined JAVA_HOME set JAVACMD="%JAVA_HOME%\bin\java.exe"

@rem echo "mojo:  .\runTestCase.bat 2>&1 | out-file output.txt utf8"


%JAVACMD% -Xmx1024m -Xms1024m 
   "-Djava.util.logging.config.file=%~dps0logging.properties"  
   -cp "%SCALA_HOME%\lib\*;%~dps0\lib\*;%~dps0\*;." 
   scala.tools.nsc.MainGenericRunner -usejavacp %*

The "mojo" line is a reminder to me that
bla 2>&1 | out-file output.txt utf8
is the Windows powersell equivalent of
bla > output.txt 2>&1
in bash shell - something like that. I'm terrible in bash, and worse in powershell, but I try to at least be able to send output to a file, grep, and run commands in a loop ... ugh!

Anyway - once the classpath is all set, then it's REPL playtime!

> .\cli\shell.bat
Welcome to Scala version 2.10.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_0
9).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import org.joda.{time => jtime}
import org.joda.{time=>jtime}
                                                    ^

scala> val t = jtime.DateTime.parse( jtime.DateTime.now().toString() )
t: org.joda.time.DateTime = 2013-05-03T16:34:48.496-05:00

scala> import com.google.common.{io => gio}
import com.google.common.{io=>gio}


scala> val UTF8 = java.nio.charset.Charset.forName( "UTF-8" )
UTF8: java.nio.charset.Charset = UTF-8

scala> gio.Files.write( jtime.DateTime.now().toString(), 
         new java.io.File( "bla.txt" ), UTF8 )

scala> val t = jtime.DateTime.parse( 
      gio.Files.toString( new java.io.File( "bla.txt" ), UTF8 ) 
    )
t: org.joda.time.DateTime = 2013-05-03T16:37:35.561-05:00

scala>

Saturday, April 20, 2013

511 labor contraction counter app

I coded up a little 511 app to help track the duration and period of labor contractions. Childbirth extends over several hours for most women, so the rule of thumb for a woman trying to decide when to head into the hospital (or whatever) is to wait until after contractions occur every 5 minutes averaging one minute in length for one hour - the "5-1-1" rule.

Anyway - the "5-1-1" rule was news to me when I learned about it watching this goofy video where the expecting parents were tracking contractions on a pad of paper. I thought, "why don't they track the contractions on their smart phone?". Such a genius!

I poked around looking for a 511 app, and didn't find one, so I decided to take it on as a project. After I started a friend mentioned that there is an iPhone app - doh! By then I was already committed to the project. There's one for Android now too - ugh.

Anyway - in addition to demonstrating my complete incompetence in web design, the app allowed me to play around with some "HTML5" tech that is pretty cool. I used YUI - which I like a lot. The app is wired with a manifest, so our poor pregnant woman can pull up the app even when she's offline, and an apple-touch icon, so she can "pin" the app to her start screen as a standalone app.

Unfortunately, I made some newb mistakes too. I designed the app to just maintain its state in memory - assuming that when a user starts having contractions, then she'll pull up the app, use it until she goes into full labor or not, and that's that. I expected a webapp launched from the start-screen would maintain its state if the user switched between apps - the same way a browser tab maintains its state if the user switches tabs. Bad assumption! It turns out that a pinned webapp loses its in-memory state whenever the user switches between apps - at least the iPhone behaves that way. I went back and added code to save and restore the apps state to local cache, but it would have been better if I had designed the code to work that way from the start.

The app could of course benefit from several improvements. First, I currently have the thing wired to save state to local cache after every UX event, because I couldn't figure out how to detect a "user is leaving this page" event, and only save then. I recently found this thread on stackoverflow that describes the "leaving page" event, so I'd like to give that a try.

I'd also like to wire-up the app to somehow auto-suggest that the user pin the app to her home screen, and make that easy to do (it's kind of a hassle in Android Chrome for example). I'm pretty sure some apps do that kind of thing, but it may be more trouble than I'm willing to go through - we'll see.

Anyway - I'm pretty happy with how the project came out - even though the woman I built it for didn't use it, because her water broke (doh!), but the baby came out healthy, so all's well that ends well. The source code is online under my dev-clone of my littleware mercurial repo on google code - mixed in with a bunch of other code I've been playing with. At some point I started making breaking changes to the littleware code, so I forked off to that clone, but still haven't merged back to the main repo - which is stupid, because nobody uses that code but me. Anyway - I need to clean up the repo, and probably setup a clone on github - which is where all the cool kids track code these days, but I'll save that project for another day ...

Tuesday, April 16, 2013

paging s3 listObjects as a scala stream

One of Scala's fun features is its lazy list Stream class. I guess for a Haskell programmer a lazy list is not a big deal, but it's cool for the rest of us java, ruby, python, javascript, C#, ... programmers.

Anyway - I plan to host some web content on S3, so I've been writing some scala code using S3's java API to automate some tasks. One of the patterns S3 employs is to "page" large result sets, and scala streams provide a natural way to load the pages on demand - something like this:

      lazy val s3Listing:Stream[s3.model.ObjectListing] = 
          s3Client.listObjects( 
            new s3.model.ListObjectsRequest( folder.getHost, queryFolderPath, null, "/", null ) 
          ) #:: s3Listing.takeWhile( _.isTruncated ).map( (part) => s3Client.listNextBatchOfObjects( part ) )

Pretty cool, but, unfortunately, Stream has some quirk where its flatMap method can easily overflow the stack. For example - the following code for assembling the directories in a file system explodes:

          lazy val folderStream:Stream[jio.File] = new java.io.File( folder.path.getPath ).getCanonicalFile #:: 
            folderStream.flatMap( (d) => { d.listFiles().filter( (sd) => sd.isDirectory ) } )

We can construct a stream, and avoid calling flatMap with a recursive method that manages its own stack.

          def depthFirst( stack:List[jio.File] ):Stream[jio.File] = {
            stack match {
              case head :: tail => {
                  head #:: depthFirst( tail ++ head.listFiles.filter( _.isDirectory ) )
              }
              case Nil => Stream.empty
            }
          }

          depthFirst( List( root ) )

Saturday, January 05, 2013

Smart Phone, Dumb Person

I joined the community of annoying smartphone people with the purchase of my Motorola Atrix HD Android phone.

I'm very happy with the phone, but I was surprised by the weird world of phone marketing I found when trying to decide which phone I should get. I started my search with a small set of requirements for the phone:

  • Android Jelly Bean
  • AT&T
  • preferably Motorola - just because they're based in my old home state of Illinois. I guess Apple and Motorola are the only American phone manufacturers these days.

Anyway - pretty simple, right ? What I expected to find was some web pages on the Motorola or AT&T web sites with a lineup of Motorola's Android phones something like the Apple Store's iPhone page. The iPhone has a great approach to marketing - there's just one iPhone product line with the flagship iPhone 5, and the older 4s and 4 available at a discount. I expected Motorola might similarly have a couple different phone lines based on size or feature set, and one or two older generations available at a discount. Sadly for Motorola - it turns out Motorola offers separate product lines for each carrier! I just found this page that nicely lays out their product lines, but I somehow didn't stumble upon that page during my phone search, and I was amazed by how many different (but very similar) phones Motorola designed and manufactured. Motorola's phone branding is crazy - RAZR is exclusive to Verizon (Verizon apparently owns the "DROID" brand trademark too from the old days where iPhone was AT&T exclusive), ATRIX is AT&T, Electrify for U.S. Cellular, Sprint gets the Admiral and Photon, Triumph for Virgin Mobile ... I think these are all actually different phones - not just the same no-brand phone with a different carrier logo.

Motorola's per-carrier marketing is just crazy. It's already hard for Motorola to distinguish itself from the other Android handset manufacturers (htc, Sony, Samsung, LG, ...); it's crazy that Motorola has to muddy down its own brand in some misguided strategy by the carriers to differentiate themselves from each other. Nobody switches from Verizon to AT&T (or vice versa) because one has a slightly better Android phone than the other.

Actually - it looks like Samsung might be in the same messed up phone-per-carrier boat. This carrier-branding mess must drive Google crazy. The iPhone was trail blazing in so many little ways that people forget (visual voice mail, apps, ...) - the unified (across carriers) "iPhone" brand was one I never considered.

scala 2.10 netbeans ant fix

I just upgraded the scala install on my laptop from 2.9.x to the latest 2.10.0 release candidate, and was depressed to discover that broke my crazy netbeans derived ant build scripts (ugh!).

> ant clean
Buildfile: C:\Users\pasquini\Documents\Code\littleware\WIP\littleware\webapp\lit
tleId\littleId\build.xml

config-check:

download-ivy:

install-ivy:

resolveIfNecessary:

-pre-init:

-init-private:

BUILD FAILED
C:\Users\pasquini\Documents\Code\littleware\WIP\littleware\webapp\littleId\littl
eId\nbproject\build-impl.xml:50: The following error occurred while executing th
is line:
jar:file:/C:/Program%20Files/Scala/scala-2.10.0-RC5/lib/scala-compiler.jar!/scal
a/tools/ant/antlib.xml:5: taskdef A class needed by class scala.tools.ant.FastSc
alac cannot be found: scala/reflect/internal/settings/MutableSettings$SettingVal
ue
 using the classloader AntClassLoader[C:\Program Files\Scala\scala-2.10.0-RC5\li
b\scala-compiler.jar;C:\Program Files\Scala\scala-2.10.0-RC5\lib\scala-library.j
ar]

Total time: 2 seconds

Fortunately - the fix was easy. Scala 2.10.x pushes some classes that its ant task depends on to a new scala-reflect jar file, so I added that to the classpath in one of the xml blocks in nbproject/build-impl.xml, and I was back in business:

        <property name="scala.compiler" value="${scala.home}/lib/scala-compiler.jar"/>
        <property name="scala.library" value="${scala.home}/lib/scala-library.jar"/>
        <property name="scala.lib" value="${scala.home}/lib"/>
        <taskdef resource="scala/tools/ant/antlib.xml">
            <classpath>
                <pathelement location="${scala.compiler}"/>
                <pathelement location="${scala.library}"/>
                <pathelement location="${scala.lib}/scala-reflect.jar"/>
            </classpath>
        </taskdef>

Anyway - scala 2.10 looks to be a great release. A lot of people are excited about the "macro" system, but I've been working with the new akka actors, and also look forward to playing around with the futures and promises APIs.