Tuesday, 20 August 2013

Jigsaw launcher for Felix

This post will show a simple launcher module for Felix. This launcher takes advantage of the exported packages list that can be obtained from Jigsaw's modules to fill in the list of JRE's packages, thus eliminating the need to hard code that list in Felix defaults.

Setting up the environement:

  1. Build Jigsaw
  2. Build the Maven plugin for Jigsaw 
  3. Download and extract Maven 3(.0.5) in ~/dev
  4. Export the variables:
    export M2_HOME=~/dev/apache-maven-3.0.5/
    export MAVEN_HOME=~/dev/apache-maven-3.0.5/
    export JAVA_HOME=~/dev/jigsaw/build/linux-x86_64-normal-server-release/images/jdk-module-image
    export PATH=$JAVA_HOME/bin:$M2_HOME/bin:$PATH

Create the launcher
  1.  Create the directories
    mkdir -p ~/dev/felix/felix-launcher/src/main/java/lh.jigsaw.felix.launcher/lh/jigsaw/felix/launcher
    mkdir ~/dev/felix/bundles
    mkdir ~/dev/felix/nonbundles
    mkdir ~/dev/felix/modules
  2. Create the modules library
    jmod create -L ~/dev/felix/modules
  3. Download Felix
    wget -O ~/dev/felix/nonbundles/org.apache.felix.framework-4.2.1.jar http://mirror.tcpdiag.net/apache//felix/org.apache.felix.framework-4.2.1.jar
    (although Felix framework is a bundle it is used as a non bundle)
  4. Add the pom
    gedit ~/dev/felix/felix-launcher/pom.xml &
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>lh.jigsaw</groupId>
        <artifactId>jigsaw-felix-launcher</artifactId>
        <version>0.1-SNAPSHOT</version>
        <name>Jigsaw Felix launcher</name>
        <packaging>jmod</packaging>
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
      </properties>
        <build>     
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>2.3.2</version>
                </plugin>
                <plugin>
                    <groupId>lh.jigsaw</groupId>
                    <artifactId>jigsaw-maven-plugin</artifactId>
                    <version>0.1-SNAPSHOT</version>
                    <extensions>true</extensions>
                    <configuration>
                      <libraryDirectory>/home/ludovic/dev/felix/modules</libraryDirectory>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
    change the libraryDirectory to your appropriate path
  5. Add the module-info
    gedit ~/dev/felix/felix-launcher/src/main/java/lh.jigsaw.felix.launcher/module-info.java &
    module lh.jigsaw.felix.launcher @ 0.1
    {
      requires jdk.jre;
      class lh.jigsaw.felix.launcher.Main;
    }
    (depends on jdk.jre to expose the JRE)
  6. Add the Main class
    gedit ~/dev/felix/felix-launcher/src/main/java/lh.jigsaw.felix.launcher/lh/jigsaw/felix/launcher/Main.java &
    package lh.jigsaw.felix.launcher;

    import java.io.File;
    import java.io.IOException;
    import java.lang.module.ModuleId;
    import java.lang.module.ModuleInfo;
    import java.lang.module.ModuleView;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.net.URLClassLoader;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.ServiceLoader;
    import org.openjdk.jigsaw.SimpleLibrary;

    /**
     *
     * @author ludovic
     */
    public class Main
    {
      public static void main(final String[] args)
      {
        try
        {    
          // Extract information from Jigsaw
          //
         
          final String libraryPath = System.getProperty("sun.java.launcher.module.library");
         
          final SimpleLibrary library = SimpleLibrary.open(new File(libraryPath));
         
          final StringBuilder packages = new StringBuilder();
          final List<URL> urls = new ArrayList<>(); // 'classpath' for launching Felix
         
          for (ModuleId id:  library.listModuleIds())
          {
            if (isJreModule(id.name()))
            {
              urls.add(library.classPath(id).toURI().toURL());
             
              final ModuleInfo info = library.readModuleInfo(id);
              appendExportedPackages(info, packages);
            }
          }
         
          packages.deleteCharAt(packages.length()-1);
         
          final File userDir = new File(System.getProperty("user.dir"));
         
          // Additional "classpath" (Felix's jar is added there)
          //     
          addNonOsgiJars(userDir, urls);
         
          // Initialise and start OSGi
          //     
          startOsgi(userDir, urls, packages.toString());
               
        }
        catch (IOException | ClassNotFoundException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | IllegalAccessException ex)
        {
          throw new RuntimeException("error initialising", ex);
        }
       
        System.out.println("started.");
       
      }

      private static boolean isJreModule(final String moduleName)
      {
        return moduleName.startsWith("java") || moduleName.startsWith("javax");
      }
     
      private static void appendExportedPackages(final ModuleInfo info, final StringBuilder packages)
      {
        final ModuleView view = info.defaultView();
        for (String exportedPackage: view.exports())
        {
          if (!isJavaPackage(exportedPackage))
          {
            packages.append(exportedPackage).append(';');
          }
        }
      } 
     
      private static boolean isJavaPackage(final String packageName)
      {
        // java.* are always available to bundles
        return packageName.startsWith("java.");
      }
     
      private static void addNonOsgiJars(final File userDir, final List<URL> urls) throws MalformedURLException
      {   
        final File bundlesDir = new File(userDir, "nonbundles");
        for (File file : bundlesDir.listFiles())
        {
          urls.add(file.toURI().toURL());
        }
      }
     
      // As Jigsaw currently identificate itself as Java 1.8, and as Felix already has Java 1.8 package,
      // override the full system packages, including Felix's own
      private static final String FELIX_SYSTEM_PACKAGES =
        "org.osgi.framework; version=1.7.0," +
        " org.osgi.framework.hooks.bundle; version=1.1.0," +
        " org.osgi.framework.hooks.resolver; version=1.0.0," +
        " org.osgi.framework.hooks.service; version=1.1.0," +
        " org.osgi.framework.hooks.weaving; version=1.0.0," +
        " org.osgi.framework.launch; version=1.1.0," +
        " org.osgi.framework.namespace; version=1.0.0," +
        " org.osgi.framework.startlevel; version=1.0.0," +
        " org.osgi.framework.wiring; version=1.1.0," +
        " org.osgi.resource; version=1.0.0," +
        " org.osgi.service.packageadmin; version=1.2.0," +
        " org.osgi.service.startlevel; version=1.1.0," +
        " org.osgi.service.url; version=1.0.0," +
        " org.osgi.util.tracker; version=1.5.1 ";
     
      private static Map<String, Object> getFrameworkProperties(final String packages)
      {
        final Map config = new HashMap<>();
        config.put("org.osgi.framework.system.packages", FELIX_SYSTEM_PACKAGES + "," + packages);
    //    config.put("org.osgi.framework.system.packages.extra", packages);
        return config;
      }
     
     
      // we use reflection to not depend on OSGi core (and to not make a module of it)
     
      private static final Class<?>[] NO_ARG_SIG = null;
      private static final Object[] NO_ARG_INV = null;
     
      private static void startOsgi(final File userDir, final List<URL> urls, final String packages) throws ClassNotFoundException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, IllegalAccessException
      {     
          final URLClassLoader cl = getClassLoaderFor(urls);
         
          final Class<?> bundleClass = cl.loadClass("org.osgi.framework.Bundle");     
          final Class<?> bundleContextClass = cl.loadClass("org.osgi.framework.BundleContext");     
          final Class<?> frameworkFactoryClass = cl.loadClass("org.osgi.framework.launch.FrameworkFactory");
         
          final Map<String, Object> properties = getFrameworkProperties(packages);
         
          final Object framework = getFrameworkFor(frameworkFactoryClass, cl, properties);     
          final Class<?> frameworkClass = framework.getClass();
         
          initialiseFramework(frameworkClass, framework);
         
          final Object bundleContext =  getBundleContextFor(frameworkClass, framework);
         
          final List<Object> installedBundles = new ArrayList<>();     
          installBundles(installedBundles, userDir, bundleContextClass, bundleContext);      
          startBundles(installedBundles, bundleClass);
         
          startFramework(frameworkClass, framework);
         
      }

      private static URLClassLoader getClassLoaderFor(final List<URL> urls)
      {
        final URL[] urlsArray = urls.toArray(new URL[urls.size()]);
       
        return new URLClassLoader(urlsArray);
      }

      private static Object getFrameworkFor(final Class<?> frameworkFactoryClass, final URLClassLoader cl, final Map<String, Object> properties) throws IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, IllegalAccessException
      {
        final ServiceLoader<?> sl = ServiceLoader.load(frameworkFactoryClass, cl);
        final Object factory = sl.iterator().next();
       
        final Method newFramework = factory.getClass().getMethod("newFramework", Map.class);
        return newFramework.invoke(factory, properties);
      }

      private static Object getBundleContextFor(final Class<?> frameworkClass, final Object framework) throws NoSuchMethodException, IllegalAccessException, SecurityException, IllegalArgumentException, InvocationTargetException
      {
        final Method getBundleContext = frameworkClass.getDeclaredMethod("getBundleContext", NO_ARG_SIG);
        final Object bundleContext =  getBundleContext.invoke(framework, NO_ARG_INV);
        return bundleContext;
      }

      private static void initialiseFramework(final Class<?> frameworkClass, final Object framework) throws InvocationTargetException, SecurityException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException
      {
        final Method init = frameworkClass.getDeclaredMethod("init", NO_ARG_SIG);
        init.invoke(framework, NO_ARG_INV);
      }
     
      private static void installBundles(final List<Object> installedBundles, final File userDir, final Class<?> bundleContextClass, final Object bundleContext) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
      {   
        final Method installBundle = bundleContextClass.getDeclaredMethod("installBundle", String.class);
       
        final File bundlesDir = new File(userDir, "bundles");
        for (File file : bundlesDir.listFiles())
        {
          final String name = file.toURI().toString();
          final Object bundle = installBundle.invoke(bundleContext, name);
          installedBundles.add(bundle);
        }
      }

      private static void startBundles(final List<Object> installedBundles, final Class<?> bundleClass) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
      {  
        final Method start = bundleClass.getDeclaredMethod("start", NO_ARG_SIG);
       
        for (Object bundle: installedBundles)
        {
          start.invoke(bundle, NO_ARG_INV);
        }
      }

      private static void startFramework(final Class<?> frameworkClass, final Object framework) throws IllegalAccessException, SecurityException, NoSuchMethodException, InvocationTargetException, IllegalArgumentException
      {
        final Method start = frameworkClass.getDeclaredMethod("start",  NO_ARG_SIG);
        start.invoke(framework, NO_ARG_INV);
      }
    }
  7. Make
    cd ~/dev/felix/felix-launcher/
    mvn install
     
 Test with a sample bundle
  1. Create the directories
    mkdir -p ~/dev/felix/simplebundle/src/main/java/lh/jigsaw/test/simplebundle
  2.  Add the pom
    gedit ~/dev/felix/simplebundle/pom.xml &
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>

        <groupId>lh.jigsaw.test</groupId>
        <artifactId>simplebundle</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>bundle</packaging>

        <name>simplebundle OSGi Bundle</name>

        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>

        <dependencies>
            <dependency>
                <groupId>org.osgi</groupId>
                <artifactId>org.osgi.core</artifactId>
                <version>4.3.0</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>

        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.felix</groupId>
                    <artifactId>maven-bundle-plugin</artifactId>
                    <version>2.3.7</version>
                    <extensions>true</extensions>
                    <configuration>
                        <instructions>
                            <Bundle-Activator>lh.jigsaw.test.simplebundle.Activator</Bundle-Activator>
                            <Export-Package/>
                        </instructions>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
  3. Add the bundle activator
    gedit ~/dev/felix/simplebundle/src/main/java/lh/jigsaw/test/simplebundle/Activator.java &
    package lh.jigsaw.test.simplebundle;

    import javax.swing.JOptionPane;
    import org.osgi.framework.BundleActivator;
    import org.osgi.framework.BundleContext;

    public class Activator implements BundleActivator
    {
      public void start(BundleContext context) throws Exception
      {
        System.out.println("Hello Jigsaw/Felix!");
        JOptionPane.showMessageDialog(null, "Hello Jigsaw/Felix!");
      }

      public void stop(BundleContext context) throws Exception
      {
      }
    }
  4. Make
    (as the bundle is compiled with a compiler that does not know about Jigsaw, we must use the standard Java 7)
    export JAVA_HOME='/usr/lib/jvm/java-7-openjdk-amd64'
    cd ~/dev/felix/simplebundle/
    mvn install

  5. Copy to the bundle directory
    cp ~/dev/felix/simplebundle/target/simplebundle-1.0-SNAPSHOT.jar ~/dev/felix/bundles
 Run
cd ~/dev/felix/
java -L ~/dev/felix/modules -m lh.jigsaw.felix.launcher

Wednesday, 29 May 2013

WorldClock and Lambdas

The second Java 8 feature to use in WorldClock are the Lambdas.
The simplest way to start using them seems to apply Netbeans refactoring hints.
These hints are of two kinds for WorldClock:

  • Iterations:
PreJava8:
for (City city : cities)
{
   city.paint(g, width, height, isFullScreen);
}
Java8:
cities.stream().forEach((city) ->
{
    city.paint(g, width, height, isFullScreen);
});
It may not bring much in this instance though.



  • Single method anonymous inner classes:
PreJava8:
mnuiShow.addActionListener(new ActionListener()
{
      @Override
      public void actionPerformed(ActionEvent e)
      {
        showWindow();
      }
});
Java8:
mnuiShow.addActionListener((ActionEvent e) ->
{
   showWindow();
});
There it does reduce the boiler plate code.

Sunday, 19 May 2013

WorldClock and JSR310

With JSR310 (Date and Time API) in Java 8 and the small 0.7 release of WorldClock done, I can update WorldClock to the new API.

First the time used for day and night drawing:
(in WorldClockBoard)
PreJava8:
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
int day = cal.get(Calendar.DAY_OF_MONTH);
int month = cal.get(Calendar.MONTH) + 1;
int year = cal.get(Calendar.YEAR);
int hours = cal.get(Calendar.HOUR_OF_DAY);
int minutes = cal.get(Calendar.MINUTE);
int seconds = cal.get(Calendar.SECOND);
Java8:
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
int day = now.getDayOfMonth();
int month = now.getMonthValue();
int year = now.getYear();
int hours = now.getHour();
int minutes = now.getMinute();
int seconds = now.getSecond();

Next the timezone and time formatting for a city:
(in City)
PreJava8:
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
//...

private final TimeZone tz;
private final Calendar cal;
private final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
//...

// in the constructor
tz = TimeZone.getTimeZone(tzName);
cal = Calendar.getInstance(tz);
sdf.setTimeZone(tz);
//...

// in the paint method
cal.setTime(new Date());
final String time = sdf.format(cal.getTime());
Java8:
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
//...

private final DateTimeFormatter formatter;
//...

// in the constructor
formatter = DateTimeFormatter.ofPattern("HH:mm").withZone(ZoneId.of(tzName));
//...

// in the paint method
final String time = formatter.format(Instant.now());

And the refresh ticker:
(in WorldClockPanel)
PreJava8:
private long last = 0;
//...

long now = System.currentTimeMillis() / 60000;
if (now > last)
Java8:
//(a bit verbose):
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
//...

private final Clock minuteClock = Clock.tickMinutes(ZoneId.systemDefault());
private Instant last = Instant.EPOCH;
//...

Instant now = minuteClock.instant();
if (now.isAfter(last))

Sunday, 21 April 2013

Worldclock 0.7, Worldclock Saver 0.8, Worldclock editor 1.0

Changes:
  • Built with Maven
  • Source and runtime upped to Java 7
  • Packaged as both zip archives and Windows setup program
  • First attempt at visually repositioning a city if it goes out of view
Download from http://worldclock-application.java.net/

Sunday, 24 March 2013

Building Jigsaw on Windows

Building Jigsaw on Windows is the same as building OpenJDK8 except that the URL to use in step 13 to get the root source is http://hg.openjdk.java.net/jigsaw/jigsaw/
hg clone http://hg.openjdk.java.net/jigsaw/jigsaw/
Post build define a JIG enviroment variable to prefix your java command:
 set JIG=C:\dev\jigsaw\build\windows-x86_64-normal-server-release\images\jdk-module-image
A few commands:
List the modules in the JDK system library:
 %JIG%/bin/jmod list
Launch a modular java application:
%JIG%/bin/java -m jdk.jconsole

Sunday, 20 January 2013

Building Java 8 on Windows

This post is a step by step to build Java 8 using the new build infrastructure on Window (8 64bits). It combines instructions from Volker Simonis, Stanislav Kobylansky and from the OpenJDK8 Build ReadMe.
A different version of these steps initiated by Patrick Reinhart can be found on the Adopt OpenJDK website.
One more version with screenshots by Roberto Coelho.

Last updated: 2013-10-11: added the direct link to VC++2010 web installer in English (for some reason it isn't available from the download page from France)
  1. Download Visual C++ 2010 Express (only C++ is needed, direct link), install it without optional products (ie without Silverlight or MS SQL 2008 Express SP1).
    (it is only needed to build Freetype)
  2. Download and install Windows SDK 7.1 (don't install the samples and .Net tools)
  3. Download and install Microsoft DirectX 9.0 SDK (the ReadMe says the version from summer 2004, however this does not seem to still be available from Microsoft, the version from June 2010 seems to be a working replacement)
  4. Download and install TortoiseHg (Mercurial client)
  5. Download and install Java 7 (if not already done)
  6. Download and install Cygwin in C:\cygwin64, select the following packages:
    • [Archive] unzip
    • [Archive] zip
    • [Base] gawk
    • [Devel] binutils
    • [Devel] make
    • [Interpreters] m4
    • [Utils] cpio
    • [Utils] files
    • [System] procps
  7. Create a C:\dev\ directory 
  8. Freetype
    1. Download the source
    2. Extract it to C:\dev\freetype-2.5.0.1
    3. Either
      • Download prebuilt binaries from bintray and extract them so that the lib directory goes into C:\dev\freetype-2.5.0.1
        or
      • Build it yourself
        1. Go to C:\dev\freetype-2.5.0.1\builds\win32\vc2010 and open freetype.sln
        2. Create a 64 bits configuration
          1. In the toolbar, click on the arrow by Win32, then on "Configuration Manager", then again click on Win32 below "Active solution platform", then on <New...>
          2. In the new dialog, call the new platform "x64" and copy its settings from Win32, click OK
          3. Back in the previous dialog select "Release Multithreaded" from "Active solution configuration" and Close
          4. Right click on the freetype project, then select Properties
          5. In the dialog adjust:
            • "Output Directory" from ".\..\..\..\objs\win32\vc2010\" to ".\..\..\..\lib\"
            •  "Intermediate Directory" from ".\..\..\..\objs\release_mt\" to ".\..\..\..\objs\release_mt_64\"
            •  "Target Name" from "freetype2411MT" to "freetype"
            •  "Platform Toolset" from "v100" to "Windows7.1SDK"
          6. Ok to save
          7. Right click on the freetype project then Build or press <F7> to build
          8. Once built, return to the properties dialog
            Change
            • "Configuration Type" from "Static library (.lib)" to "Dynamic Library (.dll)"
          9. Build again
     
  9. Copy msvcr100.dll to its own directory C:\dev\vcrt from either C:\Windows\System32 or C:\Program Files\Java\jdk1.7.0_XX\bin (this because the build process seem to have difficulties copying the file properly from C:\Windows\System32, and the JDK bin directory is in a path with spaces)
  10. Adjust the PATH by appending ";C:\cygwin64\bin"
  11. Open Cygwin terminal (from link on the desktop)
  12. cd /cygdrive/c/dev
  13. Get the root source:
    hg clone http://hg.openjdk.java.net/jdk8/jdk8
  14. Move to the new directory
    cd /cygdrive/c/dev/jdk8
  15. Get the remainder of the source
    ./get_source.sh 
  16. Force the permissions
    chmod -R u+rwxs /cygdrive/c/dev/jdk8
    (otherwise some files get access denied errors)
  17. Run the auto-conf script
    bash configure --with-freetype=/cygdrive/c/dev/freetype-2.5.0.1 --with-msvcr-dll=/cygdrive/c/dev/vcrt/msvcr100.dll
  18. Time for cooking
    make
  19. Once done, open a new standard Windows console and navigate to
    C:\dev\jdk8\build\windows-x86_64-normal-server-release\jdk
    check that java runs:
    bin\java -version
    or for something a bit more visual:
    bin\jconsole
  20. If needed make the images (ie the directory layouts that are created when installing a jdk/jre from the Oracle installers - or something pretty close to them)
    make images
  21. enjoy!
For day to day refresh do the following (in the cygwin shell):
  1. Go to the JDK directory
    cd /cygdrive/c/dev/jdk8
  2. Get the sources updates
    ./get_source.sh
  3. Force the permissions
    chmod -R u+rwxs /cygdrive/c/dev/jdk8
  4. Re-heat
    make
    or
    make images
  5. If it fails, try
    make clean
    (it tends to fail in hotspot, in which case a
    make clean-hotspot
    is faster)
    then re-heat
  6. If it fails again, try to redo the configuration
    make dist-clean
    bash configure --with-freetype=/cygdrive/c/dev/freetype-2.5.0.1 --with-msvcr-dll=/cygdrive/c/dev/vcrt/msvcr100.dll
    then re-heat