Posts in category Java

How to Setup Dependencies Aware Ant Project for Scala

During the past days, I was patching scalac ant task and some relative issues, and now, the dependencies aware ant scalac works (post Scala 2.8.0.r19724).

Below is an example build.xml with dependencies aware setting:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project name="ScalaAntTest" default="build" basedir=".">
 3     <description>Builds, tests, and runs the project ScalaAntTest.</description>
 4 
 5     <property name="src.dir" value="${basedir}/src"/>
 6     <property name="build.dir" value="${basedir}/build"/>
 7     <property name="build.classes.dir" value="${build.dir}/classes"/>
 8 
 9     <target name="init">
10         <property environment="env"/>
11         <condition property="scala.home" value="${env.SCALA_HOME}">
12             <isset property="env.SCALA_HOME"/>
13         </condition>
14         <fail unless="scala.home">set SCALA_HOME first</fail>
15 
16         <property name="scala-library.jar" value="${scala.home}/lib/scala-library.jar"/>
17         <property name="scala-compiler.jar" value="${scala.home}/lib/scala-compiler.jar"/>
18 
19         <path id="build.classpath">
20             <pathelement location="${scala-library.jar}"/>
21             <pathelement location="${scala-compiler.jar}"/>
22             <pathelement location="${build.classes.dir}"/>
23         </path>
24         <taskdef resource="scala/tools/ant/antlib.xml">
25             <classpath>
26                 <pathelement location="${scala-compiler.jar}"/>
27                 <pathelement location="${scala-library.jar}"/>
28             </classpath>
29         </taskdef>
30     </target>
31 
32     <target name="build" depends="init">
33         <mkdir dir="${build.dir}"/>
34         <mkdir dir="${build.classes.dir}"/>
35         <scalac srcdir="${src.dir}"
36                 destdir="${build.classes.dir}"
37                 classpathref="build.classpath"
38                 force="yes"
39                 addparams="-make:transitive -dependencyfile ${build.dir}/.scala_dependencies"
40                 >
41             <src path="${basedir}/src1"/> 
42             <!--include name="compile/**/*.scala"/-->
43             <!--exclude name="forget/**/*.scala"/-->
44         </scalac>
45     </target>
46 
47     <target name="clean" depends="init">
48         <delete dir="${build.dir}"/>
49     </target>
50 
51 </project>

There are some tips here, I'll give a concise explanation:

First, there will be a file call ".scala_dependencies" which is put under "build/" directory after you first clean-build, it will record all dependencies information. Since it's put under "build/", it will be removed automatically after an "ant clean". The "-dependencyfile ${build.dir}/.scala_dependencies" parameter of scalac at line 39 enables this.

Second, you should add "-make:transitive" as scalac's parameter (line 39), which will enable scalac to evaluate the dependencies transitive.

Third, add attribute "force='yes'" (line 38), which tells scalac to check all source files for dependencies and re-compile them if files that dependents on changed.

Forth, you should include "<pathelement location='${build.dir.classes}'>" as part of "build.classpath" (line 22), so scalac won't complain lack of already generated classes when rebuild upon parts of source files.

I've also re-write the project scheme that created by NetBeans plugin, that is, the next released NetBeans Scala plugin will automatically generate dependencies aware build.xml file for new created projects. Unfortunately, you'll have to copy/move your old project src to new created project directory if you want to benefit from it.

For some reasons, "fsc" is no longer supported as the default compiler task in NetBeans created project, which actually brought some annoyances for plugin users. Even without "fsc", the dependencies aware "scalac" should also work satisfiable in most cases.

  • Posted: 2009-11-19 08:00 (Updated: 2009-12-30 04:39)
  • Author: dcaoyuan
  • Categories: Java
  • Comments (0)

Scala Corner Case#3: "object" or "case object" Extends Case Class? It's a Different Story

Case class in Scala is a neat feature, it automatically generates apply, extract and equals functions. But I encountered a strange "equals" behavior today, which wasted me 2 hours to find why.

Define a simple case class "Symbol", then a "VarSymbol" which extends "Symbol" with one more field "name:String". We know you can compare the equality of instances of "VarSymbol" by this "name", Scala automatically gets it right. And of course, a direct instance from "Symbol()" should not equal to any "VarSymbol", since it lacks "name" field, that's right.

case class Symbol()
case class VarSymbol(name:String) extends Symbol

Then, I need a singleton object "NoSymbol", I defined it as:

object NoSymbol extends Symbol

I of course thought this "NoSymbol" should not equal any "VarSymbol" instance, or,

NoSymbol == VarSymbol("I'm var")

Should return false. But life is not so straightforward, it returns true in real life !!!

I finally got my code working, by adding a "case" before "object NoSymbol extends Symbol". This "NoSymbol" won't equal any "VarSymbol" now, I'm grad things finally go back in track.

I don't know if it's a Scala bug, or, that's what Scala thinks it should be: all "Symbol" and it's inherited instances should equal "NoSymbol" object. Anyway, here's the whole code for test:

case class Symbol()
case class VarSymbol(name:String) extends Symbol

object NoSymbol extends Symbol
case object CasedNoSymbol extends Symbol

object TestCaseObject {
    
    def test = {
        val noSym = NoSymbol
        val caseNoSym = CasedNoSymbol
        val varSym = VarSymbol("I'm var")

        if (noSym == varSym) println("NoSym equals varSym !") 
        else println("NoSym doesn't equal varSym")
        
        if (caseNoSym == varSym) println("CaseNoSym equals varSym !") 
        else println("CaseNoSym doesn't equal varSym")
    }
    
}

Run TestCaseObject.test, I got:

NoSym equals varSym !
CaseNoSym doesn't equal varSym

Scala Corner Case#1: Implement Method of Java Interface with Type Parameter Omitted by It's Sub-Class

The progress of rewriting Erlang plugin for NetBeans in Scala has reached a phase, that the Editor itself works smooth and better than ErlyBird now, the next step is to integrate an Erlang project management and index the modules/functions of OTP/project to provide smart auto-completion.

Now Scala has been proved that it can be integrated into an existed large Java based framework (NetBeans IDE here) without no much problems, I'll begin to rewrite Scala plugin in Scala soon.

Although in most cases, Scala can call existed Java code or classes smoothly, there are still some corner cases. I'll record these corner cases in blogs. Here's the first one which I also posted on scala-user mailing-list, but have not yet got final answer.

Let's begin with a detailed example:

There is a Java interface A:

public interface A<T extends String> {
   void run(T t);
}

Which has a type parameter <T extends String> and abstract method run(T t)

Then a Java abstract class B extended A. But, B, as it, omitted type parameter from A. This is unsafe but valid in Java:

public abstract class B implements A {
   public String me() {
       return "I'm B";
   }
}

Assume above classes A and B have been compiled under javac, and packed in a jar library, and I can not patch it anymore. Now I need to write a class S in Scala which should extend B:

class S extends B {
   override
   def run[T <: String](t:T) = {println(t)}
}

scalac will complain as:

/Users/dcaoyuan/NetBeansProjects/ScalaTestCase/src/S.scala:1: error:
class S needs to be abstract, since method run in trait A of type
(T)Unit is not defined
class S extends B {
/Users/dcaoyuan/NetBeansProjects/ScalaTestCase/src/S.scala:3: error:
method run overrides nothing
   def run[T <: String](t:T) = {println(t)}

I than tried "forSome" type:

class S extends B {
   override
   def run(t:T forSome {type T <: String}) = {println(t)}
}

The code still did not work.

It seems that, since B omitted A's type parameter T, I have no way to get what is T, and can not successfully implement "run(T t)" method.

I also tried other forms of "forSome" usages, and always failed.

But I think Scala can always be saved with mixed Java/Scala code in such corner case, that's what I believed. So, I thought about it later when my brain was spare, and finally got a solution:

I wrote another Java abstract class B1 which extends B and pretended to have implemented "run(T t)", but actually called another new abstract method "runImpl(String t)"

public abstract class B1 extends B {

    public void run(String t) {
        runImpl(t);
    }

    public abstract void runImpl(String t);
}

Now I can let Scala S extends B1 and implement "runImpl(String t)" instead of extending B and implementing "run(T t)".

class S extends B1 {
    override
    def runImpl(t:String) = {println(t)}
}

Yes, scalac won't complain about "runImpl(t:String)" at all, and I got S successfully extends B by bridge class B1.

But I still hope scalac can resolve it directly, with a warning message instead of failing to compile it.

FOR, WHILE Is Too Easy, Let's Go Looping

With several 10k code in Erlang, I'm familiar with functional style coding, and I found I can almost rewrite any functions in Erlang to Scala, in syntax meaning.

Now, I have some piece of code written in Java, which I need to translate them to Scala. Since "for", "while", or "do" statement is so easy in Java, I can find a lot of them in Java code. The problem is, should I keep them in the corresponding "for", "while", "do" in Scala, or, as what I do in Erlang, use recursive function call, or, "loop"?

I sure choose to loop, and since Scala supports recursive function call on functions defined in function body (Erlang doesn't), I choose define these functions' name as "loop", and I tried to write code let "loop" looks like a replacement of "for", "while" etc.

Here's a piece of code that is used to read number string and convert to double, only piece of them.

The Java code:

public class ReadNum {

    private double readNumber(int fstChar, boolean isNeg) {
        StringBuilder out = new StringBuilder(22);
        out.append(fstChar);
        
        double v = '0' - fstChar;
        // the maxima length of number stirng won't exceed 22
        for (int i = 0; i < 22; i++) {
            int c = getChar();
            switch (c) {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    v = v * 10 - (c - '0');
                    out.append(c);
                    continue;
                case '.':
                    out.append('.');
                    return readFrac(out, 22 - i);
                case 'e':
                case 'E':
                    out.append(c);
                    return readExp(out, 22 - i);
                default:
                    if (c != -1) backup(1);
                    if (!isNeg) return v; else return -v
            }
        }
        return 0;
    }
}

The Scala code:

class ReadNum {
   private
   def readNumber(fstChar:Char, isNeg:Boolean) :Double = {
      val out = new StringBuilder(22)
      out.append(fstChar)

      val v:Double = '0' - fstChar
      def loop(c:Char, v:Double, i:Int) :Double = c match {
         // the maxima length of number stirng won't exceed 22
         case _ if i > 21 =>
            0
         case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' =>
            out.append(c)
            val v1 = v * 10 - (c - '0')
            loop(getChar, v1, i + 1)
         case '.' =>
            out.append('.')
            readFrac(out, 22 - i)
         case 'e' | 'E' =>
            out.append(c)
            readExp(out, 22 - i)
         case _ =>
            if (c != -1) backup(1)
            if (isNeg) v else -v
      }; loop(getChar, v, 1)
   }
}

As you can see in line 25, the loop call is put at the position immediately after the "loop" definition, following "}; ", I don't put it to another new line, it makes me aware of the "loop" function is just used for this call.

And yes, I named all these embedded looping function as "loop", every where.

RPC Server for Erlang, In Java

We are using Erlang to do some serious things, one of them is indeed part of a banking system. Erlang is a perfect language in concurrent and syntax (yes, I like its syntax), but lacks static typing (I hope new added -spec and -type attributes may be a bit helping), and, is not suitable for processing massive data (performance, memory etc). I tried parsing a 10M size XML file with xmerl, the lib for XML in OTP/Erlang, which causes terrible memory disk-swap and I can never get the parsed tree out.

It's really a need to get some massive data processed in other languages, for example, C, Java etc. That's why I tried to write RPC server for Erlang, in Java.

There is a jinterface lib with OTP/Erlang, which is for communication between Erlang and Java. And there are docs for how to get it to work. But, for a RPC server that is called from Erlang, there are still some tips for real world:

1. When you send back the result to caller, you need set the result as a tuple, with caller's tag Ref as the first element, and the destination should be the caller's Pid. It's something like:

OtpErlangTuple msg = new OtpErlangTuple(new OtpErlangObject[] {call.tag, tResult});
sConnection.send(call.to, msg); 

where, call.tag is a OtpErlangRef?, and tResult can be any OtpErlangObject?, call.to is a OtpErlangPid?.

2. If you need to send back a massive data back to caller, the default buffer size of OtpErlangOutputStream? is not good, I set it to 1024 * 1024 * 10

3. Since there may be a lot of concurrent callers call your RPC server, you have to consider the concurrent performance of your server, I choose using thread pool here.

The RPC server in Java has two class, RpcNode?.java, and RpcMsg?.java:

package net.lightpole.rpcnode;

import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangList;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangPid;
import com.ericsson.otp.erlang.OtpErlangRef;
import com.ericsson.otp.erlang.OtpErlangTuple;

/**
 *
 * @author Caoyuan Deng
 */
public class RpcMsg {

    public OtpErlangAtom call;
    public OtpErlangAtom mod;
    public OtpErlangAtom fun;
    public OtpErlangList args;
    public OtpErlangPid user;
    public OtpErlangPid to;
    public OtpErlangRef tag;

    public RpcMsg(OtpErlangTuple from, OtpErlangTuple request) throws IllegalArgumentException {
        if (request.arity() != 5) {
            throw new IllegalArgumentException("Not a rpc call");
        }

        /* {call, Mod, Fun, Args, userPid} */
        if (request.elementAt(0) instanceof OtpErlangAtom && ((OtpErlangAtom) request.elementAt(0)).atomValue().equals("call") &&
                request.elementAt(1) instanceof OtpErlangAtom &&
                request.elementAt(2) instanceof OtpErlangAtom &&
                request.elementAt(3) instanceof OtpErlangList &&
                request.elementAt(4) instanceof OtpErlangPid &&
                from.elementAt(0) instanceof OtpErlangPid &&
                from.elementAt(1) instanceof OtpErlangRef) {

            call = (OtpErlangAtom) request.elementAt(0);
            mod = (OtpErlangAtom) request.elementAt(1);
            fun = (OtpErlangAtom) request.elementAt(2);
            args = (OtpErlangList) request.elementAt(3);
            user = (OtpErlangPid) request.elementAt(4);
            to = (OtpErlangPid) from.elementAt(0);
            tag = (OtpErlangRef) from.elementAt(1);

        } else {
            throw new IllegalArgumentException("Not a rpc call.");
        }
    }

    /* {'$gen_call', {To, Tag}, {call, Mod, Fun, Args, User}} */
    public static RpcMsg tryToResolveRcpCall(OtpErlangObject msg) {
        if (msg instanceof OtpErlangTuple) {
            OtpErlangTuple tMsg = (OtpErlangTuple) msg;
            if (tMsg.arity() == 3) {
                OtpErlangObject[] o = tMsg.elements();
                if (o[0] instanceof OtpErlangAtom && ((OtpErlangAtom) o[0]).atomValue().equals("$gen_call") &&
                        o[1] instanceof OtpErlangTuple && ((OtpErlangTuple) o[1]).arity() == 2 &&
                        o[2] instanceof OtpErlangTuple && ((OtpErlangTuple) o[2]).arity() == 5) {
                    OtpErlangTuple from = (OtpErlangTuple) o[1];
                    OtpErlangTuple request = (OtpErlangTuple) o[2];

                    try {
                        return new RpcMsg(from, request);
                    } catch (IllegalArgumentException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }
        
        return null;
    }
}
package net.lightpole.rpcnode;

import com.ericsson.otp.erlang.OtpAuthException;
import com.ericsson.otp.erlang.OtpConnection;
import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangExit;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangString;
import com.ericsson.otp.erlang.OtpErlangTuple;
import com.ericsson.otp.erlang.OtpSelf;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * Usage:
 *   $ erl -sname clientnode -setcookie mycookie
 *   (clientnode@cmac)> rpc:call(xnodename@cmac, 'System', currentTimeMillis, []).
 * 
 * @author Caoyuan Deng
 */
public abstract class RpcNode {

    public static final OtpErlangAtom OK = new OtpErlangAtom("ok");
    public static final OtpErlangAtom ERROR = new OtpErlangAtom("error");
    public static final OtpErlangAtom STOPED = new OtpErlangAtom("stoped");
    private static final int THREAD_POOL_SIZE = 100;
    private OtpSelf xSelf;
    private OtpConnection sConnection;
    private ExecutorService execService;

    public RpcNode(String xnodeName, String cookie) {
        this(xnodeName, cookie, THREAD_POOL_SIZE);
    }

    public RpcNode(String xnodeName, String cookie, int threadPoolSize) {
        execService = Executors.newFixedThreadPool(threadPoolSize);

        startServerConnection(xnodeName, cookie);
        loop();
    }

    private void startServerConnection(String xnodeName, String cookie) {
        try {
            xSelf = new OtpSelf(xnodeName, cookie);
            boolean registered = xSelf.publishPort();
            if (registered) {
                System.out.println(xSelf.node() + " is ready.");
                /**
                 * Accept an incoming connection from a remote node. A call to this
                 * method will block until an incoming connection is at least
                 * attempted.
                 */
                sConnection = xSelf.accept();
            } else {
                System.out.println("There should be an epmd running, start an epmd by running 'erl'.");
            }
        } catch (IOException ex) {
            Logger.getLogger(RpcNode.class.getName()).log(Level.SEVERE, null, ex);
        } catch (OtpAuthException ex) {
            Logger.getLogger(RpcNode.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void loop() {
        while (true) {
            try {
                final int[] flag = {0};

                final OtpErlangTuple msg = (OtpErlangTuple) sConnection.receive();

                Runnable task = new Runnable() {

                    public void run() {
                        RpcMsg call = RpcMsg.tryToResolveRcpCall(msg);

                        if (call != null) {
                            long t0 = System.currentTimeMillis();

                            flag[0] = processRpcCall(call);

                            System.out.println("Rpc time: " + (System.currentTimeMillis() - t0) / 1000.0);
                        } else {
                            try {
                                sConnection.send(sConnection.peer().node(), new OtpErlangString("unknown request"));
                            } catch (IOException ex) {
                                Logger.getLogger(RpcNode.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }
                    }
                };

                execService.execute(task);

                if (flag[0] == -1) {
                    System.out.println("Exited");
                    break;
                }

            } catch (OtpErlangExit ex) {
                Logger.getLogger(RpcNode.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(RpcNode.class.getName()).log(Level.SEVERE, null, ex);
            } catch (OtpAuthException ex) {
                Logger.getLogger(RpcNode.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    protected void sendRpcResult(RpcMsg call, OtpErlangAtom head, OtpErlangObject result) throws IOException {
        OtpErlangTuple tResult = new OtpErlangTuple(new OtpErlangObject[] {head, result});

        // Should specify call.tag here
        OtpErlangTuple msg = new OtpErlangTuple(new OtpErlangObject[]{call.tag, tResult});
        // Should specify call.to here
        sConnection.send(call.to, msg, 1024 * 1024 * 10); 
    }

    public abstract int processRpcCall(RpcMsg call);
    

    // ------ helper
    public static String getShortLocalHost() {
        return getLocalHost(false);
    }

    public static String getLongLocalHost() {
        return getLocalHost(true);
    }

    private static String getLocalHost(boolean longName) {
        String localHost;
        try {
            localHost = InetAddress.getLocalHost().getHostName();
            if (!longName) {
                /* Make sure it's a short name, i.e. strip of everything after first '.' */
                int dot = localHost.indexOf(".");
                if (dot != -1) {
                    localHost = localHost.substring(0, dot);
                }
            }
        } catch (UnknownHostException e) {
            localHost = "localhost";
        }

        return localHost;
    }
}

As you can see, the RpcNode? is an abstract class, by implement int processRpcCall(RpcMsg? call), you can get your what ever wanted features. For example:

package net.lightpole.xmlnode;

import basexnode.Main;
import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangList;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangString;
import java.io.IOException;
import net.lightpole.rpcnode.RpcMsg;
import net.lightpole.rpcnode.RpcNode;

/**
 *
 * @author dcaoyuan
 */
public class MyNode extends RpcNode {

    public MyNode(String xnodeName, String cookie, int threadPoolSize) {
        super(xnodeName, cookie, threadPoolSize);
    }

    @Override
    public int processRpcCall(RpcMsg call) {
        final String modStr = call.mod.atomValue();
        final String funStr = call.fun.atomValue();
        final OtpErlangList args = call.args;

        try {
            OtpErlangAtom head = ERROR;
            OtpErlangObject result = null;

            if (modStr.equals("xnode") && funStr.equals("stop")) {
                head = OK;
                sendRpcResult(call, head, STOPED);
                return -1;
            }

            if (modStr.equals("System") && funStr.equals("currentTimeMillis")) {
                head = OK;
                long t = System.currentTimeMillis();
                result = new OtpErlangLong(t);
            } else {
                result = new OtpErlangString("{undef,{" + modStr + "," + funStr + "}}");
            }

            if (result == null) {
                result = new OtpErlangAtom("undefined");
            }

            sendRpcResult(call, head, result);
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (Exception ex) {
        }

        return 0;
    }
}

I tested MyNode? by:

$ erl -sname clientnode -setcookie mycookie
...
(clientnode@cmac)> rpc:call(xnodename@cmac, 'System', currentTimeMillis, []).

And you can try to test its concurrent performance by:

%% $ erl -sname clientnode -setcookie mycookie
%% > xnode_test:test(10000)
-module(xnode_test).

-export([test/1]).

test(ProcN) ->
    Workers = [spawn_worker(self(), fun rpc_parse/1, {})        
               || I <- lists:seq(0, ProcN - 1)],
    Results = [wait_result(Worker) || Worker <- Workers].

rpc_parse({}) ->
    rpc:call(xnodename@cmac, 'System', currentTimeMillis, []).

spawn_worker(Parent, F, A) ->
    erlang:spawn_monitor(fun() -> Parent ! {self(), F(A)} end).

wait_result({Pid, Ref}) ->
    receive
        {'DOWN', Ref, _, _, normal} -> receive {Pid, Result} -> Result end;
        {'DOWN', Ref, _, _, Reason} -> exit(Reason)
    end.

I spawned 10000 calls to it, and it run smoothly.

I'm also considering to write a more general-purpose RPC server in Java, which can dynamically call any existed methods of Java class.

Things To Do in Coming Months

As the beta of Scala for NetBeans released, I found I have several things to do in the coming months.

First, I'll keep the Scala plugins going on, I'll try to re-implement the Project supporting, which, may be an extension of current NetBeans' plain Java Project, that is, you just create plain JSE or JEE project, then add Scala source files to this project, you may mix Java/Scala in one project. Another perception is, it's time to re-write whole things in Scala itself? I have a featured Scala IDE now, or, the chicken, I should make eggs via this chicken instead of duck.

Second, we get some contracts on mobile application for Banking, which, will be implemented via our current Atom/Atom Publish Protocol web service platform. The platform is written in Erlang, but, with more and more business logical requirements, maybe we should consider some Scala things?

Third, oh, it's about AIOTrade, I'v left it at corner for almost one and half year, I said it would be re-written in Scala someday, I really hope I have time. I got some requests to support drawing charts for web application, it actually can, if you understand the source code, I just wrote an example recently, I may post an article on how to do that.

Which Programming Language J. Gosling Would Use Now, Except Java?

>>> Updated later
Maybe we can get completeness of J. Gosling's opinions about Java/Scaka/JVM from here
===

According to Adam Bien's blog from JavaOne

During a meeting in the Community Corner (java.net booth) with James Gosling, a participant asked an interesting question: "Which Programming Language would you use *now* on top of JVM, except Java?". The answer was surprisingly fast and very clear: - Scala.

I think Fortress will also be a very good future choice when it gets mature.

Some Tips for Upgrading to Rails 1.2.x

Bellow are some issues that I met when upgraded from rails 1.1.6 to 1.2.x:

1.About enrivonment.rb
Make sure your application configuration behind:

Rails::Initializer.run do |config|
  ...
end

I met this issue when I put following code:

require 'environments/localization_environment'
require 'localization'
Localization::load_localized_strings
require 'environments/user_environment'

before that closure, which works under 1.1.6, but don't under 1.2.x

2.About ActionMailer::Base.server_settings
If you get errors like:

uninitialized constant ActiveSupport::Deprecation::RAILS_DEFAULT_LOGGER (NameError)

try to change your ActionMailer::Base.server_settings to ActionMailer::Base.smtp_settings

3.Put all "include" statements inside class definitions
You must put include statements inside class/module definitions instead of outside a class/module definition in Rails 1.2.x. Otherwise, you'll get:

Error calling Dispatcher.dispatch #<NameError: cannot remove Object::COPYRIGHT>

Functinal Style Ruby

After playing with Ruby for weeks, I found Ruby is yet interesting. I try to write my code a bit in the likeness of Erlang, where symbol vs atom, array vs list. And the most important syntax that I like are:

  • everything return a value
  • may return multiple values
  • begin-end clause is lambda that may be directly applied
  • parallel assignment

Now, let's write some code before and after (functional):

Example1:

Before

   1. cond = {}  
   2. if par[:id]  
   3.   feed = Feed.find(par[:id])  
   4.   if feed  
   5.     cond[:feed] = feed.id  
   6.   end  
   7. end  
   8. if par[:m]
   9.   limit = par[:m].to_i  
  10. else  
  11.   limit = 20  
  12. end  
  13. if limit >= 4096  
  14.   limit = 4096  
  15. end  
  16. cond[:limit] = limit  
  17. if par[:d]  
  18.   days = par[:d].to_f  
  19.   if days <= 0 || days >= 365  
  20.     days = 365  
  21.   end  
  22.   cond[:time] = Time.now - days*86400  
  23. end  
After
   1. cond = {  
   2.   :feed   => if par[:id]  
   3.                feed = Feed.find(par[:id])  
   4.                feed ? feed.id : nil  
   5.              end,  
   6.   :limit  => begin  
   7.                 limit = par[:m] ? par[:m].to_i : 20  
   8.                 limit >= 4096 ? 4096 : limit  
   9.              end,  
  10.   :time   => if par[:d]  
  11.                days = par[:d].to_f  
  12.                days = days <= 0 || days >= 365 ? 365 : days  
  13.                Time.now - days * 86400  
  14.              end,  
  15. }.delete_if { |k, v| v.nil? } # delete all nil elements of cond  

Example2:

Before

   1.  if f[:mode] == "rss"  
   2.   rss = f[:feed]  
   3.   params[:feed][:channel] = rss.channel.title  
   4.   params[:feed][:description] = rss.channel.description  
   5.   params[:feed][:link] = rss.channel.link  
   6.   params[:feed][:copyright] = rss.channel.copyright  
   7. else  
   8.   atom = f[:feed]  
   9.   params[:feed][:channel] = atom.title  
  10.   params[:feed][:description] = atom.subtitle  
  11.   params[:feed][:link] = atom.links.join  
  12.   params[:feed][:copyright] = atom.rights  
  13. end  
After
   1. params[:feed][:channel],    
   2. params[:feed][:description],    
   3. params[:feed][:link],    
   4. params[:feed][:copyright] = if f[:mode] == "rss"    
   5.                               rss = f[:feed]   
   6.   
   7.                               [rss.channel.title,    
   8.                                rss.channel.description,    
   9.                                rss.channel.link,    
  10.                                rss.channel.copyright]  
  11.                             else    
  12.                               atom = f[:feed]  
  13.     
  14.                               [atom.title,    
  15.                                atom.subtitle,    
  16.                                atom.links.join,    
  17.                                atom.rights]    
  18.                             end    

Example3

   1. # grp_str: p -> public(0) , u -> user(1), f -> friends(2)   
   2. def privilege_cond(user, grp_str)  
   3.   grp_str ||= 'puf'  
   4.   cond = {:pre => "", :sub => []}  
   5.   cond = if loggedin?(user)  
   6.            frds = grp_str.include?('f') ? user.friends.find(:all) : []  
   7.            frd_ids = frds.collect { |frd| frd.friend_id.to_i }  
   8.              
   9.            cond = if grp_str.include?('u')  
  10.                     {:pre => cond[:pre] + (cond[:pre] == "" ? "" : "OR") +   
  11.                              " user_id  = ? ",  
  12.                      :sub => cond[:sub] + [user.id]}  
  13.                   else  
  14.                     cond  
  15.                   end  
  16.       
  17.            cond = if grp_str.include?('f') && !frd_ids.empty?  
  18.                     {:pre => cond[:pre] + (cond[:pre] == "" ? "" : "OR") +   
  19.                              " user_id in (?) AND privilege in (?) ",  
  20.                      :sub => cond[:sub] + [frd_ids, [0, 2]]}  
  21.                   else  
  22.                     cond  
  23.                   end  
  24.       
  25.            cond = if grp_str.include?('p')  
  26.                     {:pre => cond[:pre] + (cond[:pre] == "" ? "" : "OR") +   
  27.                              " user_id != ? AND privilege  = ? ",  
  28.                      :sub => cond[:sub] + [user.id, 0]}  
  29.                   else  
  30.                      cond  
  31.                   end  
  32.          else  
  33.            {:pre => "privilege = ?",  
  34.             :sub => [0]}  
  35.          end  
  36. end  

Java + Ruby + Erlang = JRE (Just Running Environment)

I'm recently doing a project under Ruby on Rail. It seems to be a reasonable programmer today, one should take at least > 3 languages.

Personally,

I like Erlang: lightweight process + message passing + functional programming + dynamic. It exactly matches my philosophy of looking the real world, and I think it's what functional programming should be.

Are there really Objects exist? I'm not sure. Instead, talking about OO, Object Oriented may be more sense. That is, an Object makes sense only when you orient it. All states look like being "in" an object, are with meaning only when you measure them. But, doesn't "measure" mean applying a "Function" on it? So, the states should always be carried only by functions rather than 'object', and the states are time streaming, they will be transfered from one function to another function, another function ..., so you catch the meaning of them when you track the functions chain, the meaning is based on the functions rather than the name of a Class as a member of. When you want to take a snapshot on them, you save them some where, such as showing on screen, stored in database, printed on paper what ever.

I like Java: tons of APIs + open source code base + Swing + NetBeans. So far, it has the best cross-platform UI tool kit to my eye. I like Swing, I can change or extend it easily to whatever I want. But things go easy because so many people have taken extremely efforts on it. It's bound too Objected, people split world to objects, then try to composite them or inherit something called super to make them together again. I feel pain when doing this, I have to split them, composite them in a way, then things change (or, the real world is still there), I split them, composite them in another way, again and again, it's called re-factor, but people may never catch the real Factor of the real world.

I, have to learn Ruby. Ruby and Rails are very good. For developers term, you should always know things are not so philosophy as yours, you will have guys thinking in different ways, of the real world. So Ruby is there, everyone can think the real world according to his understanding, yeah, in different ways, and, to make them not going too far away, you need rails.

So, I have to learn Java, as a tool make me doing many things interesting and painful; I have to learn Ruby, as a tool make my guys doing many things interesting and on rails; And I'll keep Erlang (Lisp/Scheme) as a tool make me not only doing but also thinking with interesting.

No Static Method in Interface, So I Write Code As ...

Java does not support static method in interface. But sometimes, I just want a static method to say: PersistenceManager.getDefault(), where PersistenceManager is going to be an interface. I don't like to add one more class named PersistenceManagerFactory, with a method:

public static PersistenceManager PersistenceManagerFactory.getDefault()

So I write code like:

public class PersistenceManager {
    private static I i;

    public static I getDefault() {
        return i == null ? i = ServiceLoader.load(I.class).iterator().next() : i;
    }
    
    /** The interface I, which is actually the PersistenceManager should be: */
    public static interface I {
        
        void saveQuotes(String symbol, Frequency freq, List quotes);
        List restoreQuotes(String symbol, Frequency freq);
        void deleteQuotes(String symbol, Frequency freq, long fromTime);
        void dropAllQuoteTables(String symbol);
        
        void shutdown();
        
        QuotePool getQuotePool();
        TickerPool getTickerPool();
    }
    
}

Then implement the PersistenceManager.I in another package, like:

public class NetBeansPersistenceManager implements PersistenceManager.I {
   ...
}

And declare it under the META-INF as:

core/src/META-INF/services/org.aiotrade.math.PersistenceManager$I

which contains one line:

org.aiotrade.platform.core.netbeans.NetBeansPersistenceManager

I can call PersistenceManager.getDefault().showdown() now.

A Regress Bug in java.awt.geom.Path2D (JDK 6.0 beta 2)

I tested AIOTrade on newly downloaded JDK 6.0 beta 2, and got an exception instantly:

java.lang.ArrayIndexOutOfBoundsException: 0
    at java.awt.geom.Path2D$Float.moveTo(Path2D.java:322)
    at java.awt.geom.Path2D$Float.append(Path2D.java:643)
    at java.awt.geom.Path2D.append(Path2D.java:1780)

The code run good in JDK 5.0, so will it be a regress bug in JDK 6.0?

I then checked the source code: 6.0 vs 5.0, and found there were likely a bit of code omited wrongly. That is, in method body of void needRoom(boolean needMove, int newCoords), should add

                if (grow < 1) {
                    grow = 1;
                }
at the next of:
            int size = pointTypes.length;
            if (numTypes >= size) {
                int grow = size;
                if (grow > EXPAND_MAX) {
                    grow = EXPAND_MAX;
                }

The following is the proper code I've tested OK:

        void needRoom(boolean needMove, int newCoords) {
            if (needMove && numTypes == 0) {
                throw new IllegalPathStateException("missing initial moveto "+
                        "in path definition");
            }
            int size = pointTypes.length;
            if (numTypes >= size) {
                int grow = size;
                if (grow > EXPAND_MAX) {
                    grow = EXPAND_MAX;
                }
                /** fix bug:
                 * java.lang.ArrayIndexOutOfBoundsException: 0
                 *     at java.awt.geom.Path2D$Float.moveTo(Path2D.java:322)
                 *     at java.awt.geom.Path2D$Float.append(Path2D.java:643)
                 *     at java.awt.geom.Path2D.append(Path2D.java:1780)
                 */
                if (grow < 1) {
                    grow = 1;
                }
                pointTypes = Arrays.copyOf(pointTypes, size+grow);
            }
            size = floatCoords.length;
            if (numCoords + newCoords > size) {
                int grow = size;
                if (grow > EXPAND_MAX * 2) {
                    grow = EXPAND_MAX * 2;
                }
                if (grow < newCoords) {
                    grow = newCoords;
                }
                floatCoords = Arrays.copyOf(floatCoords, size+grow);
            }
        }

As I can not wait for it be fixed in JDK, so I wrote another org.aiotrade.util.awt.geom.Path2D and org.aiotrade.util.awt.geom.GeneralPath, and replaced the java.awt.geom.GeneralPath in my source tree. you can get the code at:

GeneralPath.java
Path2D.java

Install XWiki on Glassfish and Derby

I've upgraded blogtrade.org to glassfish b48, with XWiki and javadb (Apache Derby) integrated. Here is a short guide I posted:

http://blogtrader.org/wiki/bin/view/KnowledgeBase/XWikiGlassfishDerby

Based on Glassfish b42

The blogtrader.org and blogtrader.net are now based on the newest Glassfish Application Server b42. As Glassfish is also the default bundled J2EE server of NetBeans IDE, with a database: javadb, it's an ideal environment for the developing of BlogTrader Project. I hope to provide stock symbols updating service via a web site to BlogTrader Platform, thus I can add the information of each market, such as local time, open, close hours ect. and you'll need not to input symbol for each stock.

And, the modules updating centre could also be set.