Team blog

Dependency injection in Ceylon with Weld and Guice

I'm personally ambivalent about the benefits of dependency injection. On the one hand, I recognize its usefulness in certain container environments such as Java EE. (For the record, I was the author of the CDI 1.0 specification, with my JCP Expert Group.) On the other hand, given the nature of what I've been working on for the last few years, I don't really have a use for it in my own programs.

But there are plenty of folks out there who swear by dependency injection, and ask me what Ceylon offers in this area. The short answer is: nothing special; the Ceylon SDK is architected around the notion of modular libraries. It offers neither framework nor container. This makes the SDK as general purpose as possible, meaning it can be reused from any other container environment (say, Java EE, vert.x, OSGi, or whatever).

So if you want dependency injection in Ceylon today, you're going to have to use a container written in Java. Fortunately, Ceylon 1.2 features such excellent interoperation with Java that this results in barely any friction at all. Surely someone will write a dependency injection container in Ceylon some day, but, as we're about to see, there's no urgency at all.

I'm going to explore:

  • Weld, which is the reference implementation of CDI, developed by my colleagues at Red Hat, and,
  • in the interests of giving equal time to a "competitor", Google's Guice, originally written by my friend Bob Lee, which was one of the major influences on the CDI specification.

These are my favorite containers for Java, though of course Spring has legions of fans. Perhaps I'll find time to play with it some other day.

You can find the example code in the following Git repository:

https://github.com/ceylon/ceylon-examples-di

Weld

I found it extremely straightforward to use Weld in Ceylon, except for one relatively minor problem, which I'll mention below.

Module descriptor for Weld

Weld provides a fat jar in Maven Central, which makes it especially easy to use in Ceylon. I used the following module descriptor to download Weld from Maven Central and import it into my project:

native("jvm")
module weldelicious "1.0.0" {
    import "org.jboss.weld.se:weld-se" "2.3.1.Final";
    import ceylon.interop.java "1.2.0";
}

Where org.jboss.weld.se is the Maven group id, and weld-se is the Maven artifact id. (I have not the slightest clue what these things actually mean, I just know there are two of them.)

I also imported the Ceylon SDK module ceylon.interop.java because I'm going to use its javaClass() function.

Bootstrapping Weld

Though it's not part of the CDI specification, Weld offers a very simple API for creating a container. I copy/pasted the following code from stackoverflow:

import org.jboss.weld.environment.se { Weld }

shared void run() {

    value container = Weld().initialize();

    //do stuff with beans
    ...

    container.shutdown();

}

I tried to run this function.

Gotcha!

Just like every other CDI developer ever, I forgot the beans.xml file. Fortunately, Weld gave me a rather clear error message. Not quite as poetic as "se te escapó la tortuga", perhaps, but good enough to remind me of this requirement of the spec. (Yeah, the spec I wrote.)

To resolve the problem, I added an empty file named beans.xml to the directory resource/weldelicious/ROOT/META-INF, which is the magical location to use if you want Ceylon to put a file into the META-INF directory of a module archive.

Defining Weld beans

I defined the following interface, for a bean I hoped to inject:

interface Receiver {
    shared formal void accept(String message);
}

Next, I defined a bean which depends on an instance of this interface:

import javax.inject { inject }

inject class Sender(Receiver receiver) {
    shared void send() => receiver.accept("Hello!");
}

(The inject annotation is the thing you write @Inject in Java.)

Finally, we need a bean which implements Receiver:

class PrintingReceiver() satisfies Receiver {
    accept = print;
}

Obtaining and calling a bean

Going back to the run() function, I added some code to obtain a Sender from the container, and call send():

import org.jboss.weld.environment.se { Weld }
import ceylon.interop.java { type = javaClass }

shared void run() {

    value container = Weld().initialize();

    value sender 
            = container
                .select(type<Sender>())
                .get();

    sender.send();

    weld.shutdown();

}

Note that I'm using the javaClass() function to obtain an instance of java.lang.Class for the Ceylon type Sender. An alternative approach, which uses only a CDI API, and which also works for generic types, is to use javax.enterprise.inject.TypeLiteral:

value sender 
        = container
            .select(object extends TypeLiteral<Sender>(){})
            .get();

Unfortunately, that's a little more verbose.

Named constructor injection

Using a little quick fix in the IDE, we can transform the Sender class into a class with a default constructor:

class Sender {
    Receiver receiver;
    inject shared new (Receiver receiver) {
        this.receiver = receiver;
    }
    shared void send() => receiver.accept("Hello!");
}

As far as Weld is concerned, this is the same as what we had before.

But we can even give our constructor a name:

class Sender {
    Receiver receiver;
    inject shared new inject(Receiver receiver) {
        this.receiver = receiver;
    }
    shared void send() => receiver.accept("Hello!");
}

Due to unanticipated serendipity, this actually Just Works.

Method and field injection

I don't think that method or field injection is a very natural thing to do in Ceylon, and so I don't recommend it. However, it does work, just as long as you mark any fields initialized by injection with the late annotation:

This works, but doesn't feel very Ceylonic:

class Sender() {
    inject late Receiver receiver;
    shared void send() => receiver.accept("Hello!");
}

This works too:

class Sender() {
    late Receiver receiver;
    inject void init(Receiver receiver) {
        this.receiver = receiver;
    }
    shared void send() => receiver.accept("Hello!");
}

Using a CDI producer

One nice thing about using Ceylon with Weld is that you can use the produces annotation on a toplevel function.

import javax.enterprise.inject { produces }

produces Receiver createReceiver() 
        => object satisfies Receiver {
            accept = print;
        };

CDI qualifiers

We can define CDI qualifier annotations in Ceylon:

import javax.inject { qualifier }

annotation Fancy fancy() => Fancy();
final qualifier annotation class Fancy() 
        satisfies OptionalAnnotation<Fancy> {}

A qualifier annotation must be applied at both the injection point and to the bean or producer function. First, I annotated the bean class:

fancy class FancyReceiver() satisfies Receiver {
    accept(String message) 
            => print(message + " \{BALLOON}\{PARTY POPPER}");
}

Next, I tried annotating an injected initializer parameter:

//this doesn't work!
inject class Sender(fancy Receiver receiver) {
    shared void send() => receiver.accept("Hello!");
}

Unfortunately, this didn't work. When compiled to Java bytecode, Ceylon actually places this fancy annotation on a generated getter method of Sender, not on the parameter, and Weld only looks for qualifier annotations on injected parameters. I had to use constructor injection to make the qualifier work right:

//this does work
class Sender {
    Receiver receiver;
    inject shared new (fancy Receiver receiver) {
        this.receiver = receiver;
    }
    shared void send() => receiver.accept("Hello!");
}

For the record, qualifier annotations also work with method injection. They don't work with field injection.

This was the only disappointment I had using Weld with Ceylon, and I believe I already know how to solve this in Ceylon 1.2.1.

Scoped beans

You can define scoped beans (beans with what the CDI spec calls a normal scope) in Ceylon, just by applying a scope annotation to the bean:

import javax.enterprise.context { applicationScoped }

applicationScoped
class PrintingReceiver() satisfies Receiver {
    accept = print;
}

However, there's something to be careful of here: CDI creates proxies for scoped beans, and since the operations of a Ceylon class are "final" by default, you have a choice between:

  • annotating all operations of the bean default, or
  • injecting an interface instead of the concrete bean class.

I think the second option is a much better path to go down, and is probably even the best approach in Java.

Surely the same caveat applies to beans with CDI interceptors or decorators, though I did not test that.

Weld offers lots of additional functionality which I did not have time to test, but that I anticipate will work in Ceylon.

Guice

Guice was also pretty easy to get set up, though I wasted a bit of time on the Maven side of things.

Module overrides for Guice

Guice doesn't come in a fat jar, so we'll have to deal with a common problem when using Maven modules from Ceylon. Maven is designed for a flat Java classpath, so a Maven module doesn't come with metadata about which of its dependencies are re-exported via its public API. There are three basic strategies for solving this problem:

  1. Compile and run with a flat classpath by using --flat-classpath. This makes Ceylon work like Java, and robs us of module isolation.
  2. Use --export-maven-dependencies to re-export all dependencies of every Maven module.
  3. Use an overrides.xml file to explicitly specify which dependencies are re-exported.

We're going to go with option 3, since it's the hardest.

But wait—you must be thinking—XML?! And yeah, don't worry, we hate XML just as much as you do. This is a stopgap measure until Ceylon has real assemblies. Once we have assemblies, you'll be able to override module dependencies in a Ceylon assembly descriptor.

Anyway, after that longwinded preamble, all I had to do was mark javax.inject as a shared dependency:

<overrides xmlns="http://www.ceylon-lang.org/xsd/overrides">
    <module groupId="com.google.inject" 
         artifactId="guice" 
            version="4.0">
        <share groupId="javax.inject" 
            artifactId="javax.inject"/>
    </module>
</overrides>

You're very welcome to copy and paste the above bit of boilerplate into your own Ceylon and Guice projects.

Module descriptor for Guice

The following module descriptor fetches Guice and its dependencies from Maven Central, and imports Guice into the project:

native("jvm")
module guicy "1.0.0" {
    import "com.google.inject:guice" "4.0";
    import ceylon.interop.java "1.2.0";
}

Code we can reuse from the Weld example

Since Guice recognizes the inject annotation defined in javax.inject, we can reuse the definitions of Sender, Receiver, and PrintingReceiver we started out with above.

import javax.inject { inject }

inject class Sender(Receiver receiver) {
    shared void send() => receiver.accept("Hello!");
}

interface Receiver {
    shared formal void accept(String message);
}

class PrintingReceiver() satisfies Receiver {
    accept = print;
}

Bootstrapping Guice

Guice has the notion of a module object, which has a collection of bindings of types to objects. Unlike Weld, which automatically scans our module archive looking for beans, bindings must be registered explicitly in Guice.

import ceylon.interop.java {
    type = javaClass
}
import com.google.inject {
    AbstractModule,
    Guice {
        createInjector
    },
    Injector
}

Injector injector
        = createInjector(
    object extends AbstractModule() {
        shared actual void configure() {
            bind(type<Receiver>()).to(type<PrintingReceiver>());
        }
    });

This code binds the implementation PrintingReceiver to the interface Receiver.

Obtaining and calling an object

Now it's easy to obtain and call a container-bound instance of Sender:

import ceylon.interop.java {
    type = javaClass
}

shared void run() {
    value sender = injector.getInstance(type<Sender>());
    sender.send();
}

We're again using javaClass(), but Guice has its own TypeLiteral. (For the record, CDI stole TypeLiteral from Guice.)

import com.google.inject {
    Key,
    TypeLiteral
}

shared void run() {
    value key = Key.get(object extends TypeLiteral<Sender>(){});
    value sender = injector.getInstance(key);
    sender.send();
} 

Constructor injection

Injection into default constructors works, and looks exactly like what it looks like for Weld. However, injection into named constructors doesn't work with Ceylon 1.2.0 and Guice 4.0. This is pretty easy to fix on our side, and so it should work in Ceylon 1.2.1.

Method and field injection

The creators of Guice strongly prefer constructor injection, which is, as we have observed, also more natural in Ceylon. But method and field injection works fine, as with Weld, if you mark injected field late.

Provider methods

Guice scans the module object for methods annotated provides.

import com.google.inject {
    AbstractModule,
    Guice {
        createInjector
    },
    Injector,
    provides
}

Injector injector
        = createInjector(
    object extends AbstractModule() {
        shared actual void configure() {}
        provides Receiver createReceiver()
                => object satisfies Receiver {
                    accept = print;
                };
    });

I find this significantly inferior to the approach in CDI where producer methods can be defined as toplevel functions.

Binding annotations

Guice's binding annotations work almost exactly like CDI qualifier annotations (since that's where CDI copied them from). The code to define a binding annotation is exactly the same as for Weld.

import javax.inject { qualifier }

annotation Fancy fancy() => Fancy();
final binding annotation class Fancy() 
        satisfies OptionalAnnotation<Fancy> {}

The qualifier annotation must be specified when defining a binding:

Injector injector
        = createInjector(
    object extends AbstractModule() {
        shared actual void configure() {
            bind(type<Receiver>())
                .to(type<PrintingReceiver>());
            bind(type<Receiver>())
                .annotatedWith(Fancy()) //binding annotation
                .to(type<FancyReceiver>());
        }
    });

Just like in Weld, qualifier annotations work with constructor or method injection, but don't currently work with initializer parameter or field injection.

Scoped beans

Like CDI, Guice has scoped objects.

import com.google.inject { singleton }

singleton
class PrintingReceiver() satisfies Receiver {
    accept = print;
}

I didn't have time to test this feature of Guice extensively, but I happen to know that Guice doesn't use proxies, so it's not necessary to use an interface instead of a concrete class.

Conclusion

If you want dependency injection in Ceylon, it's clear that you have at least two excellent options.

One Big Repository

Over the last year and a half it has happened quite frequently that users on our mailing lists or Gitter channels asked: "What is this OBR you people keep talking about?". It was something that would most often be mentioned when we were frustrated with the current project setup making things more difficult than necessary.

How it all started

To understand what the OBR really is you first need to know how our project was organized all these years. Ceylon started out with nothing but the parser and the code that checks and models our unique typesystem, we call it the "typechecker". When we started working on the compilers new modules got added, first for the Java Virtual Machine, later for JavaScript. Then we added a module that handles downloading and caching of external modules retrieved from the Herd. This went on to the point that we wound up with 9 separate but very interdependent projects.

The problem

Now of course modularization is a good software engineering practise so there was no problem there. The problem was with the way we had created multiple Git repositories for each of the projects. We started noticing that for certain types of cross-module issues we often had to make changes to multiple repositories at a time. And when you do that, let's say you change 6 repositories for a single bug report or for a single new feature, you lose cohesion. The changes are all separate and you lose sight of the fact that they belong together. So if you need to go back in time looking for a specific problem and when it was introduced and by which code commit it suddenly becomes very hard to synchronize all the repositories in such a way that you get a working system. The famous git bisect becomes impossible to use.

It also caused minor problems when managing issues on GitHub because users, not knowing, would often open issues on the wrong project which meant manually copying the issue from one project to another (and sometimes even we didn't know where to put an issue and it would get moved several times).

Looking for a solution

So at a certain moment it became obvious that our project setup was working against us, that its structure made certain operations way too hard. A single repository on the other hand wouldn't have those problems. A single big repository, the only one for the entire distribution. So we started calling it the "One Big Repository", quickly shortened to "OBR".

But there were doubts that a single repository would become too big and unwieldy. And how would we go from 9 repositories to just 1? Are there tools to do that? And what about our GitHub issues?

Instead we first looked at tools like git submodule and git subtree which would allow us to keep our current projects but still treat them as a one. But after researching it for a while we came to the conclusion that although it might work it does make things more complex for the developer. And given the fact that most of us aren't Git experts and sometimes have trouble enough with it as it is we decided that the added complexity would not work in our favor. Besides the problem with the GitHub issues being separate would still remain.

The execution

One Big Repository it would be then, merging all 9 Ceylon distribution repositories into a single one. But how? No ready-made tools seemed to exist, just fragments of scripts and examples of complex Git commands. And for the GitHub issues we could only rely on their remote API.

I will spare you all the nitty gritty details on the many dozens of practise merges we did to get to a point where we were happy enough with the result but finally, last monday on November 16th, we made public the new OBR, the One Big Repository!

How to use it

Setting up a Ceylon environment for development used to be a big hassle. So much in fact that we created some pretty complex build scripts to do that all for you. So fortunately as a contributor you'd never notice, you'd just type ant setup and everything would be done for you.

But now things have become still easier, for you as a potential contributor the only that is needed is:

$ git clone https://github.com/ceylon/ceylon
$ cd ceylon
$ ant clean dist

For a more thorough explanation on how to set up a development environment and how to go from there to submitting your first contribution please visit Contributing to the compiler backend

Running Ceylon on OpenShift

This year we released three different ways you can run your Ceylon code on OpenShift:

  • Bare-bones, using the Ceylon cartridge,
  • Writing a verticle in Ceylon, using the Vert.x cartridge, or
  • Packaging your Ceylon application as a .war file and running it on the WildFly cartridge.

In this post we will see how you can write and publish a bare-bones application on OpenShift Online using the OpenShift Ceylon cartridge. The Vert.x and WildFly methods will be described in a later blog post.

The OpenShift Ceylon cartridge is for OpenShift V2. Yes I know that's old, as it is now V3, but the online version of OpenShift is still V2, so it's still relevant. We are working on the V3 cartridge too, and it should be out soon.

Writing a bare-bones web application with Ceylon

Let's start by creating a new Ceylon project:

$ ceylon new hello-world ceylon-blog-openshift
Enter module name [com.example.helloworld]: openshift.bare  
Enter module version [1.0.0]: 1
Would you like to generate Eclipse project files? (y/n) [y]: n
Would you like to generate an ant build.xml? (y/n) [y]: n
$ cd ceylon-blog-openshift

Now compile and run it to check that everything is under control:

$ ceylon compile
Note: Created module openshift.bare/1
$ ceylon run openshift.bare/1
Hello, World!

Now let's make it start an HTTP server, by using the ceylon.net module and adapting its documentation code sample.

First import that module in source/openshift/bare/module.ceylon:

native("jvm")
module openshift.bare "1" {
  import ceylon.net "1.2.0";
}

Then use it in source/openshift/bare/run.ceylon:

import ceylon.io { SocketAddress }
import ceylon.net.http.server { ... }

shared void start(String host, Integer port){
    //create a HTTP server
    value server = newServer {
        //an endpoint, on the path /hello
        Endpoint {
            path = startsWith("/");
            //handle requests to this path
            service(Request request, Response response)
                    => response.writeString("hello world");
        }
    };
    //start the server
    server.start(SocketAddress(host, port));
}

shared void run(){
    start("127.0.0.1", 8080);
}

Let's run it:

$ ceylon compile
Note: Created module openshift.bare/1
$ ceylon run openshift.bare/1
Starting on 127.0.0.1:8080
Debug: XNIO version 3.3.0.Final 
Debug: XNIO NIO Implementation Version 3.3.0.Final 
Httpd started.

And try it locally at http://localhost:8080, it should show a web page with hello world.

Adapt our application for running on OpenShift

Now let's adapt it to run on OpenShift, where the host name and port are specified by OpenShift, by using the ceylon.openshift module to see if we are running on OpenShift and if yes, bind to the right address.

First import the OpenShift module in source/openshift/bare/module.ceylon:

native("jvm")
module openshift.bare "1" {
  import ceylon.net "1.2.0";
  import ceylon.openshift "1.2.0";
}

And use it in in source/openshift/bare/run.ceylon:

import ceylon.openshift { openshift }
import ceylon.io { SocketAddress }
import ceylon.net.http.server { ... }

shared void start(String host, Integer port){
    //create a HTTP server
    value server = newServer {
        //an endpoint, on the path /hello
        Endpoint {
            path = startsWith("/");
            //handle requests to this path
            service(Request request, Response response)
                    => response.writeString("hello world");
        }
    };
    //start the server
    server.start(SocketAddress(host, port));
}

shared void run(){
    if(openshift.running){
        start(openshift.ceylon.ip, openshift.ceylon.port);
    }else{
        start("127.0.0.1", 8080);
    }
}

So now it can run either locally as before, or in OpenShift.

Configuring our application for the OpenShift Ceylon cartridge

Let's create the required OpenShift structure to tell the OpenShift Ceylon cartridge how to run our module. We do this by installing the OpenShift Ceylon command-line plugin:

$ ceylon plugin install ceylon.openshift/1.2.0
Scripts for ceylon.openshift installed in /home/stephane/.ceylon/bin/ceylon.openshift

And now we run it:

$ ceylon openshift init openshift.bare/1
Installing file .openshift/config/ceylon.properties: Generated
...

For those who want more information, or tune how the application is deployed by the OpenShift Ceylon cartridge, the documentation has a lot more information.

Our application is now ready to be run on OpenShift.

Deploying our application to OpenShift Online

Now, assuming you already have an OpenShift Online account, and the rhc command installed, you can proceed to create an OpenShift application with the Ceylon cartridge:

$ rhc create-app --no-git -a test https://raw.github.com/ceylon/openshift-cartridge/master/metadata/manifest.yml
The cartridge 'https://raw.github.com/ceylon/openshift-cartridge/master/metadata/manifest.yml' will be downloaded and installed

Application Options
-------------------
Domain:     fromage
Cartridges: https://raw.github.com/ceylon/openshift-cartridge/master/metadata/manifest.yml
Gear Size:  default
Scaling:    no

Creating application 'test' ... done


Waiting for your DNS name to be available ... done

Your application 'test' is now available.

  URL:        http://test-fromage.rhcloud.com/
  SSH to:     ...@test-fromage.rhcloud.com
  Git remote: ssh://...@test-fromage.rhcloud.com/~/git/test.git/

Run 'rhc show-app test' for more details about your app.

This created our application on OpenShift Online, and gave us a URL at which we can access it (http://test-fromage.rhcloud.com/), as well as a Git repository where we can push our application (ssh://...@test-fromage.rhcloud.com/~/git/test.git/).

Now we just have to turn our application into a Git repository and add the openshift remote Url that rhc gave us just above:

$ git init
Initialised empty Git repository in /home/stephane/src/java-eclipse/ceylon-blog-openshift/.git/
$ git remote add openshift ssh://...@test-fromage.rhcloud.com/~/git/test.git/

The Ceylon OpenShift cartridge includes a demo sample app that we can get rid of by forcing a push of our current application to OpenShift:

$ git add source .openshift
$ git commit -m "Initial commit"
...
$ git push -f openshift master
Counting objects: 23, done.
Delta compression using up to 16 threads.
Compressing objects: 100% (18/18), done.
Writing objects: 100% (23/23), 3.79 KiB | 0 bytes/s, done.
Total 23 (delta 1), reused 0 (delta 0)
remote: Stopping Ceylon cart
remote: Application is already stopped
remote: Repairing links for 1 deployments
remote: Building git ref 'master', commit 58ab35c
remote: 
remote: Building Ceylon app...
remote: Compiling every module in /var/lib/openshift/../app-root/runtime/repo//source for the JVM:
remote: Note: Created module openshift.bare/1
remote: Ceylon build done.
remote: Preparing build for deployment
remote: Deployment id is ...
remote: Activating deployment
remote: TODO
remote: Starting Ceylon cart
remote: Executing /var/lib/openshift/.../ceylon/usr/ceylon-1.2.0/bin/ceylon
remote: With params: run   --rep=/var/lib/openshift/.../app-root/runtime/repo/.openshift/config/modules --cacherep=/var/lib/openshift/.../app-root/runtime/repo//cache --rep=https://modules.ceylon-lang.org/repo/1/ --rep=/var/lib/openshift/.../app-root/runtime/repo//modules openshift.bare/1 
remote: With JAVA_OPTS:  -Dcom.redhat.ceylon.common.tool.terminal.width=9999 -Dceylon.cache.repo=/var/lib/openshift/.../app-root/runtime/repo//cache
remote: Ceylon started with pid: 350715
remote: Waiting for http server to boot on 127.5.184.1:8080 ... (1/30)
remote: Waiting for http server to boot on 127.5.184.1:8080 ... (2/30)
remote: Waiting for http server to boot on 127.5.184.1:8080 ... (3/30)
remote: Waiting for http server to boot on 127.5.184.1:8080 ... (4/30)
remote: Found 127.5.184.1:8080 listening port
remote: 
remote: -------------------------
remote: Git Post-Receive Result: success
remote: Activation status: success
remote: Deployment completed with status: success
To ssh://...@test-fromage.rhcloud.com/~/git/test.git/
   2a29bdf..58ab35c  master -> master

That's it, you can now go and check your application online at http://test-fromage.rhcloud.com/.

Congratulations!

Now you can also publish your code online, at GitHub or elsewhere, and every time you push your modifications to the openshift remote, your application will be restarted with your changes.

Stay tuned for the Vert.x and WildFly Ceylon OpenShift deployment guides on this blog.

Porting Ceylon IDE to IntelliJ

We've had many questions about developing Ceylon in IntelliJ IDEA, so I thought it would be worth a quick status update.

TL;DR: The screenshots are below.

As you might know, Ceylon already has the most feature rich IDE of any modern language for the JVM, with some features that even the Java IDE for Eclipse doesn't have. But IntelliJ users don't like having to switch to Eclipse when they code Ceylon, so a few months ago we got serious about porting Ceylon IDE to IntelliJ. Bastien Jansen is working on this fulltime, together with David Festal from SERLI.

The approach they're taking is to refactor reusable functionality of Ceylon IDE out into a separate project ceylon-ide-common. Simultaneously they're rewriting the common code in Ceylon (which David reports is really helping simplify and improve the code). Then this "abstracted" code is reused in the ceylon-ide-intellij project—which is also being written in Ceylon—and in ceylon-ide-eclipse. Thus, ceylon-ide-common gives us a common foundation for both IDEs, and enables us to get some really sophisticated functionality into the IntelliJ IDE very quickly.

Even better, once ceylon-ide-common is stabilized, we can reuse it elsewhere, for example, in the Web IDE, or in the new (experimental) plugin for NetBeans. Bastien was able to add autocompletion to the experimental Netbeans plugin in about 2-3 hours.

This also all demonstrates just how well Ceylon's Java interop works in practice. Here we have Java calling Ceylon and Ceylon calling back to Java all over the place!

The IntelliJ plugin isn't really usable just yet, since David is still working on abstraction of the Ceylon IDE incremental builder, but we expect to have a first release in a handful of months.

Screenshots

Ceylon IDE for IntelliJ already features completion:

completion

Including linked mode argument completion:

linked mode

Outline view and hover:

outline     hover

Live error reporting:

errors

And execution:

run

Much more functionality is coming soon!

Ceylon 1.2.0 is now available

After a full year in development, and with more than 1500 issues closed, Ceylon 1.2.0 brings new language features, including:

  • named constructors,
  • serialization,
  • native declarations,
  • improved flow-sensitive typing,
  • destructuring for tuples and entries
  • let, switch, if, and object expressions, and
  • more powerful annotation constraints.

Furthermore, the typechecker and JavaScript backend now support type functions as an experimental feature.

Also part of this release are enhancements to the tooling, such as:

  • a new debugger for Ceylon, and
  • the Java EE packaging command, ceylon war.

As always, this release incorporates hundreds of other bugfixes and enhancements.

About Ceylon

Ceylon is a modern, modular, statically typed programming language for the Java and JavaScript virtual machines. The language features a flexible and very readable syntax, a unique and uncommonly elegant static type system, a powerful module architecture, and excellent tooling, including an awesome Eclipse-based IDE.

Ceylon enables the development of cross-platform modules that execute portably in both virtual machine environments. Alternatively, a Ceylon module may target one or the other platform, in which case it may interoperate with native code written for that platform.

In the box

This release includes:

  • a complete language specification that defines the syntax and semantics of Ceylon in language accessible to the professional developer,
  • a command line toolset including compilers for Java and JavaScript, a documentation compiler, a test runner, a WAR archive packager, and support for executing modular programs on the JVM and Node.js,
  • a powerful module architecture for code organization, dependency management, and module isolation at runtime,
  • the language module, our minimal, cross-platform foundation of the Ceylon SDK, and
  • a full-featured Eclipse-based integrated development environment.

Language

Ceylon is a highly understandable object-oriented language with static typing. The language features:

  • an emphasis upon readability and a strong bias toward omission or elimination of potentially-harmful or potentially-ambiguous constructs and toward highly disciplined use of static types,
  • an extremely powerful and uncommonly elegant type system combining subtype and parametric polymorphism with:
    • first-class union and intersection types,
    • both declaration-site and use-site variance, and
    • the use of principal types for local type inference and flow-sensitive typing,
  • a unique treatment of function and tuple types, enabling powerful abstractions, along with the most elegant approach to null of any modern language,
  • first-class constructs for defining modules and dependencies between modules,
  • a very flexible syntax including comprehensions and support for expressing tree-like structures,
  • fully-reified generic types, on both the JVM and JavaScript virtual machines, and a unique typesafe metamodel.

More information about these language features may be found in the feature list and quick introduction.

This release introduces the following new language features and improvements:

  • named constructors,
  • support for serialization libraries in the metamodel,
  • the native annotation, which allows the use of platform-dependent code in cross-platform modules
  • improvements to flow-sensitive typing,
  • destructuring for tuples and entries,
  • let, switch, and if expressions,
  • inline object expressions,
  • more powerful annotation constraints,
  • type argument inference for function references,
  • an improved algorithm for type argument inference in invocation expressions,
  • improvements to analysis of disjointness for sequence types,
  • new type abbreviations, T[N] and T(*A),
  • an abbreviated syntax for identifying the containing `package`, `module`, `class`, or `interface`,
  • inline variable defininition in switch
  • the ability to directly import members of a singleton object,
  • relaxation of type constraint checking where unnecessary to ensure soundness, and
  • experimental support for type functions (higher-order generics) and references to generic functions (higher-rank polymorphism).

Language module

For Ceylon 1.2, the following new APIs were introduced to the language module:

  • the map() and set() functions allow creation of immutable Maps and Sets with no dependency to ceylon.collection,
  • distinct, frequences(), group(), tabulate(), and summarize() were added to Iterable,
  • getOrDefault(), defaultNullItems(), and coalescedMap were added to Map,
  • Collection.permutations() was added,
  • formatFloat() was added,
  • the Contextual interface was added, a cross-platform abstraction of thread-local values,
  • some operations of List were split out onto the new SearchableList interface, and
  • arrayOfSize() was deperecated and replaced with the constructor Array.ofSize().

Furthermore, some native implementation code has been rewritten in Ceylon using native.

Compiler and command line tools

Enhancements to the Java compiler include:

  • much improved interoperation with Maven, including support for overriding module metadata with overrides.xml, and --flat-classpath and --auto-export-maven-dependencies,
  • all compiled classes are now Serializable and have default constructors, allowing much smoother interoperation with certain Java frameworks,
  • improved interoperation with Java annotations, and
  • basic support for interoperation with libraries written in Scala.

The JavaScript compiler now supports type functions, allowing the use of higher-order and higher-rank polymorphism in Ceylon. These experimental features are not yet supported by the Java compiler.

There are several new features and improvements to the command line toolset:

  • the ceylon war command repackages a module as a Java EE WAR archive,
  • the ceylon browse command opens module documentation in the browser,
  • multiple commands can be given simultaneously, for example ceylon compile,doc,run com.redhat.hello,
  • ceylon help command and ceylon --help now page output by default, and
  • the ceylon command architecture now supports writing plugins in Ceylon.

IDE

Ceylon IDE now features the following improvements, along with many bugfixes and a number of performance enhancements:

  • a brand new debugger for Ceylon,
  • extensive support for new language features including constructors and native,
  • improvements to the powerful Change Parameter List refactoring,
  • the Inline refactoring can now inline a type alias,
  • filtering of packages from searches and completions,
  • many new quick fixes and assists,
  • Paste Java as Ceylon,
  • the popup Outline can now show inherited members,
  • the redesigned Open Declaration dialog now shows documentation,
  • keyboard shortcuts were added for certain quick assists,
  • support for Eclipse's new dark theme,
  • refactored preferences pages, with much greater customizability, including
  • two new alternative syntax highlighting themes, along with an alternative icon set.

A number of important subsystems have been abstracted and rewritten in Ceylon, to support the ongoing development of the new IntelliJ-based IDE for Ceylon.

SDK

The platform modules, recompiled for 1.2.0, are available in the shared community repository, Ceylon Herd.

This release introduces two new platform modules:

  • ceylon.transaction provides support for distributed transaction processing, and
  • ceylon.regex provides regular expressions.

Along with several API enhancements and bugfixes, including:

  • ceylon.time now has functions for parsing ISO 8601 formatted dates, times, and datetimes,
  • ceylon.locale now supports formatting zoned times, and parsing dates and times,
  • ceylon.interop.java now has javaClassFromDeclaration(),
  • ceylon.net now has redirect(), and its Uri is now immutable, and
  • the collection types in ceylon.collection now offer additional named constructors.

OpenShift cartridge

The Ceylon cartridge for OpenShift has been improved and updated to support Ceylon 1.2.

Web IDE

You can try Ceylon using the redesigned Web IDE, now rewritten in Ceylon, and featuring syntax highlighting, interactive error reporting, autocompletion, online documentation, and persistence and code sharing via Gist.

The Web IDE serves a dual purpose as a standard example demonstrating the use of Ceylon for web application development and deployment to the OpenShift cloud platform.

Community

The Ceylon community site, http://ceylon-lang.org, includes documentation, and information about getting involved.

Source code

The source code for Ceylon, its specification, and its website, is freely available from GitHub.

Information about Ceylon's open source licenses is available here.

Issues

Bugs and suggestions may be reported in GitHub's issue tracker.

Migrating from Ceylon 1.1

Migration from Ceylon 1.1 is easy. To recompile a module for 1.2:

  • First ensure that its dependencies have also been recompiled.
  • If it imports a Ceylon SDK platform module, upgrade the version number specified by the module import statement from "1.1.0" to "1.2.0" .
  • If it imports any platform-native module, annotate its module declaration native("jvm") or native("js"), depending upon the target platform. This step does not apply to cross-platform modules.
  • If, when recompiling, you encounter errors on assert statements, try removing the assertion (the improvements to flow typing now make some type assertions redundant).

Acknowledgement

As always, we're deeply grateful to the community volunteers who contributed a substantial part of the current Ceylon codebase, working in their own spare time. The following people have contributed to Ceylon:

Gavin King, Stéphane Épardaud, Tako Schotanus, Tom Bentley, David Festal, Enrique Zamudio, Bastien Jansen, Emmanuel Bernard, Aleš Justin, Tomáš Hradec, James Cobb, Ross Tate, Max Rydahl Andersen, Mladen Turk, Lucas Werkmeister, Roland Tepp, Diego Coronel, Matej Lazar, John Vasileff, Toby Crawley, Julien Viet, Loic Rouchon, Stephane Gallès, Ivo Kasiuk, Corbin Uselton, Paco Soberón, Michael Musgrove, Daniel Rochetti, Henning Burdack, Luke deGruchy, Rohit Mohan, Griffin DeJohn, Casey Dahlin, Alexander Altman, Alexander Zolotko, Alex Szczuczko, Andrés G. Aragoneses, Anh Nhan Nguyen, Brice Dutheil, Carlos Augusto Mar, Charles Gould, Chris Gregory, klinger, Martin Voelkle, Mr. Arkansas, Paŭlo Ebermann, Vorlent, Akber Choudhry, Renato Athaydes, Flavio Oliveri, Michael Brackx, Brent Douglas, Lukas Eder, Markus Rydh, Julien Ponge, Pete Muir, Nicolas Leroux, Brett Cannon, Geoffrey De Smet, Guillaume Lours, Gunnar Morling, Jeff Parsons, Jesse Sightler, Oleg Kulikov, Raimund Klein, Sergej Koščejev, Chris Marshall, Simon Thum, Maia Kozheva, Shelby, Aslak Knutsen, Fabien Meurisse, Sjur Bakka, Xavier Coulon, Ari Kast, Dan Allen, Deniz Türkoglu, F. Meurisse, Jean-Charles Roger, Johannes Lehmann, allentc, Nikolay Tsankov, Chris Horne, Gabriel Mirea, Georg Ragaller, Harald Wellmann, klinger, Oliver Gondža, Stephen Crawley.