Using libgdx for cross-platform app development

I am looking for a framework that allows me to develop modern apps (mobile, web, desktop) all from one Java codebase. I prefer Java because I know it very well, it is already cross-platform and a statically typed language that allows IntelliJ, Eclipse and Netbeans to be better than any dynamically typed scripting language editor could ever be.

Currently my favorite is libgdx. I am planning to use it with IntelliJ Community Edition and with Maven.

By using RoboVM, libgdx even supports iOS.

For user input (forms) libgdx provides the scene2d.ui widgets. I hope that will be sufficient for most of my UIs. Now I just have to get OpenGL to work on my Linux box …

Java map comparison with detailed error messages using Guava

When you compare two Java maps that are supposed to be equal, i.e. contain the same name/value pairs, you might want to give some details about potential mismatches, for example in your log output.

The Guava library from Google provides a convenient tool for that, namely the class com.google.common.collect.MapDifference.

In the sample code below I have implemented a simple utitily method that compares two maps and logs detailed error messages if they are not equal.

This code is also the first item in my guava-based github project.

Maven dependencies

Put this into pom.xml :

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>16.0</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.5</version>
</dependency>

You will also need an slf4j implementation, like logback. For testing, we can use this :

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.5</version>
</dependency>

And for the JUnit tests further below you will need this :

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.10</version>
    <scope>test</scope>
</dependency>

Java class

Save this as src/main/java/net/doepner/util/MapDiffUtil.java :

package net.doepner.util;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;

import static com.google.common.collect.MapDifference.ValueDifference;

/**
 * Map comparison with detailed log messages
 */
public class MapDiffUtil {

    private static final Logger log =
        LoggerFactory.getLogger(MapDiffUtil.class);

    public static <K, V> boolean validateEqual(
               Map<K, V> map1, Map<K, V> map2,
               String map1Name, String map2Name) {

        final MapDifference<K, V> diff = Maps.difference(map1, map2);

        if (diff.areEqual()) {
            log.info("Maps '{}' and '{}' contain exactly the same "
                   + "name/value pairs", map1Name, map2Name);
            return true;

        } else {
            logKeys(diff.entriesOnlyOnLeft(), map1Name, map2Name);
            logKeys(diff.entriesOnlyOnRight(), map2Name, map1Name);
            logEntries(diff.entriesDiffering(), map1Name, map2Name);
            return false;
        }
    }

    private static <K, V> void logKeys(
                Map<K, V> mapSubset, String n1, String n2) {
        if (not(mapSubset.isEmpty())) {
            log.error("Keys found in {} but not in {}: {}",
                n1, n2, mapSubset.keySet());
        }
    }

    private static <K, V> void logEntries(
                Map<K, ValueDifference<V>> differing, 
                String n1, String n2) {
        if (not(differing.isEmpty())) {
            log.error("Differing values found {key={}-value,{}-value}: {}",
                        n1, n2, differing);
        }
    }

    private static boolean not(boolean b) {
        return !b;
    }
}

Unit tests

Save this as src/main/java/net/doepner/util/MapDiffUtilTest.java :

package net.doepner.util;

import java.util.HashMap;
import java.util.Map;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
 * Tests MapDiffUtil
 */
public class MapDiffUtilTest {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Rule
    public TestName testName = new TestName();

    @Before
    public void logTestName() {
        log.info("Executing {}", testName.getMethodName());
    }

    @Test
    public void testEqual() {
        final Map<String, Integer> map1 = new HashMap<String, Integer>();
        map1.put("A", 1);
        map1.put("B", 2);

        final Map<String, Integer> map2 = new HashMap<String, Integer>();
        map2.put("B", 2);
        map2.put("A", 1);

        assertTrue("Maps should be equal", MapDiffUtil.validateEqual(
            map1, map2, "map1", "map2"));
    }

    @Test
    public void testSubset() {
        final Map<String, Integer> map1 = new HashMap<String, Integer>();
        map1.put("A", 1);

        final Map<String, Integer> map2 = new HashMap<String, Integer>();
        map2.put("B", 2);
        map2.put("A", 1);

        assertFalse("Maps should be unequal", MapDiffUtil.validateEqual(
            map1, map2, "map1", "map2"));

    }

    @Test
    public void testSeparate() {
        final Map<String, Integer> map1 = new HashMap<String, Integer>();
        map1.put("A", 1);

        final Map<String, Integer> map2 = new HashMap<String, Integer>();
        map2.put("B", 2);

        assertFalse("Maps should be unequal", MapDiffUtil.validateEqual(
            map1, map2, "map1", "map2"));
    }

    @Test
    public void testMismatches() {
        final Map<String, Integer> map1 = new HashMap<String, Integer>();
        map1.put("A", 1);
        map1.put("B", 2);

        final Map<String, Integer> map2 = new HashMap<String, Integer>();
        map2.put("B", 20);
        map2.put("C", 3);

        assertFalse("Maps should be unequal", MapDiffUtil.validateEqual(
            map1, map2, "map1", "map2"));

    }
}

Minimal Debian VM upgraded to wheezy / Jenkins with OpenJDK 7

I have upgraded my minimal Debian VM to current Debian stable (“wheezy”). It comes in OVA format which is deployable to Virtualbox or VMware.

The Debian system JDK, i.e. the location of java, javac, etc. commands in the PATH, is still OpenJDK 6 because that is the default-jdk on wheezy and the Jenkins deb packages from jenkins-ci.org depend on it. In particular this means that the Tomcat / Jenkins process itself is executed by OpenJDK 6.

But my Jenkins installer now also installs OpenJDK 7 and pre-configures it as the default JDK for Jenkins jobs. That means you can now use this Jenkins instance to build your Java 7 projects, as well as older Java projects.

Please follow the step-by-step installation instructions if you want to use the VM. It consists completely of Free / Open Source software. I provide it for download “as is” without any warranty of any kind.

My current IntelliJ code inspections profile for Java projects

I recently exported my IntelliJ code inspections profile for Java projects from IntelliJ Community Edition to share it with whoever might be interested.

These highly customized code inspections are based on industry standards like the official Java code conventions, various best practices from the Java community and my experience over many years as Java developer and team lead, trying to ensure code quality and maintainability.

Feel free to download and save it as a local XML file. Then you can import it into any of your IntelliJ projects via Analyze – Inspect Code – Inspection profile – […] button – Import:

Transparently improve Java 7 mime-type recognition with Apache Tika

Java 7 comes with the method java.nio.file.Files#probeContentType(path) to determine the content type of a file at the given path. It returns a mime type identifier. The implementation actually looks at the file content and inspects so-called “magic” byte sequences, which is more reliable than just trusting filename extensions.

However, the default implementation included in Java 7 seems to be platform dependent and not very complete. For example, for me it did not even recognize an mp3 file as audio/mpeg. Fortunately, the Open Source library Apache Tika provides more comprehensive mime type detection and seems to be platform independent.

As shown below, you can register a simple Tika based FileTypeDetector implementation with the Java Service Provider Interface (SPI) to transparently enhance the behaviour of java.nio.file.Files#probeContentType(path). As soon as the resulting jar is in your classpath, the SPI mechanism wil pick up our implementation class and Files.probeContentType(..) will automatically use it behind the scenes.

Maven dependency

        <dependency>
            <groupId>org.apache.tika</groupId>
            <artifactId>tika-core</artifactId>
            <version>1.4</version>
        </dependency>

FileTypeDetector.java

package net.doepner.file;

import java.io.IOException;
import java.nio.file.Path;

import org.apache.tika.Tika;

/**
 * Detects the mime type of files (ideally based on marker in file content)
 */
public class FileTypeDetector extends java.nio.file.spi.FileTypeDetector {

    private final Tika tika = new Tika();

    @Override
    public String probeContentType(Path path) throws IOException {
        return tika.detect(path.toFile());
    }
}

Service Provider registration

To register the implementation with the Java Service Provider Interface (SPI), you need to have a plaintext file /META-INF/services/java.nio.file.spi.FileTypeDetector in the same jar that contains the class net.doepner.file.FileTypeDetector. The text file contains just one line with the fully qualified name of the implementing class:

net.doepner.file.FileTypeDetector

With Maven, you simply create the file src/main/resources/META-INF/services/java.nio.file.spi.FileTypeDetector containing the line shown above.

See the ServiceLoader documentation for details about Java SPI.

Subversion 1.8 released

The new Subversion 1.8 features look quite good for a centralized Version Control System (VCS).

But note that the Subversion 1.8 working copy format is backwards-incompatible. Some tools like recent TortoiseSVN versions will use the 1.8 format by default which will cause compatibility problems for IntelliJ and any other tools that do not yet support it.

So for now, it is probably better to stick with 1.7 and wait until all your tools fully support 1.8. For IntelliJ you might want to watch [IDEA-94942] for status updates.

Personally, I am more interested in Git anyway because it offers all the flexibility of a decentralized VCS. I am reading the free “Pro Git” ebook on my Kobo eReader (epub format).

Play MP3 or OGG using javax.sound.sampled, mp3spi, vorbisspi

I tried to come up with the simplest possible way of writing a Java class that can play mp3 and ogg files, using standard Java Sound APIs, with purely Open Source libraries from the public Maven Central repositories.

The LGPL-licensed mp3spi and vorbisspi libraries from javazoom.net satisfy these requirements and worked for me right away. As service provider implementations (SPI), they transparently add support for the mp3 and ogg audio formats to javax.sound.sampled, simply by being in the classpath.

For my AudioFilePlayer class below I basically took the example code from javazoom and simplified it as much as possible. Please note that it requires Java 7 as it uses try-with-resources.

Maven dependencies

  <!-- 
    We have to explicitly instruct Maven to use tritonus-share 0.3.7-2 
    and NOT 0.3.7-1, otherwise vorbisspi won't work.
   -->
<dependency>
  <groupId>com.googlecode.soundlibs</groupId>
  <artifactId>tritonus-share</artifactId>
  <version>0.3.7-2</version>
</dependency>
<dependency>
  <groupId>com.googlecode.soundlibs</groupId>
  <artifactId>mp3spi</artifactId>
  <version>1.9.5-1</version>
</dependency>
<dependency>
  <groupId>com.googlecode.soundlibs</groupId>
  <artifactId>vorbisspi</artifactId>
  <version>1.0.3-1</version>
</dependency>

AudioFilePlayer.java

package net.doepner.audio;

import java.io.File;
import java.io.IOException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine.Info;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

import static javax.sound.sampled.AudioSystem.getAudioInputStream;
import static javax.sound.sampled.AudioFormat.Encoding.PCM_SIGNED;

public class AudioFilePlayer {

    public static void main(String[] args) {
        final AudioFilePlayer player = new AudioFilePlayer ();
        player.play("something.mp3");
        player.play("something.ogg");
    }

    public void play(String filePath) {
        final File file = new File(filePath);

        try (final AudioInputStream in = getAudioInputStream(file)) {
            
            final AudioFormat outFormat = getOutFormat(in.getFormat());
            final Info info = new Info(SourceDataLine.class, outFormat);

            try (final SourceDataLine line =
                     (SourceDataLine) AudioSystem.getLine(info)) {

                if (line != null) {
                    line.open(outFormat);
                    line.start();
                    stream(getAudioInputStream(outFormat, in), line);
                    line.drain();
                    line.stop();
                }
            }

        } catch (UnsupportedAudioFileException 
               | LineUnavailableException 
               | IOException e) {
            throw new IllegalStateException(e);
        }
    }

    private AudioFormat getOutFormat(AudioFormat inFormat) {
        final int ch = inFormat.getChannels();
        final float rate = inFormat.getSampleRate();
        return new AudioFormat(PCM_SIGNED, rate, 16, ch, ch * 2, rate, false);
    }

    private void stream(AudioInputStream in, SourceDataLine line) 
        throws IOException {
        final byte[] buffer = new byte[65536];
        for (int n = 0; n != -1; n = in.read(buffer, 0, buffer.length)) {
            line.write(buffer, 0, n);
        }
    }
}

My standard tools on a Windows workstation

If I have to work on a Windows machine, these are the tools I usually install and use:

Free/Libre/Open Source

Free/Libre/Open Source software (“FLOSS”) with OSI certified Open Source license

Cross-platform:

  • Latest JDK
  • Intellij (Community Edition)
  • 7zip
  • Eclipse (Platform Runtime Binary + plugins required for my work)
  • Apache Tomcat
  • Apache Maven
  • Git (from git-scm.org)
  • Cygwin (with openssh, wget, nano)
  • Firefox (with Adblock, Secure Login, Firebug, Uppity)
  • Filezilla
  • LibreOffice
  • Pidgin
  • Gimp
  • Gvim
  • Wireshark
  • OpenVPN

Windows-only:

  • Notepad++
  • PdfCreator
  • Infrarecorder
  • CamStudio
  • Link Shell Extension

Closed source

Binaries currently available for download without license fees:

  • Adobe Reader
  • Sysinternals
  • Deskpins
  • MWSnap