Thursday, November 18, 2010

How to Create a Playlist in Mediascape application for Sony Ericsson Xperia X10

I recently just upgraded my Sony Ericsson Xperia X10 phone from Android 1.6 to Android 2.1 expecting that the Mediascape application would allow me to create a playlist from the phone itself. Unfortunately I was wrong. The only way to create a playlist in Mediascape is to install a Media Go that can be downloaded from Sony PC Companion. Since I don't need another media player and I'm mostly running on Linux, installing Media Go isn't an option for me. After couple of hours googling about this issue, I found that Mediascape is able to recognize the M3U playlist file. I then went to Wikipedia to see what M3U format really is. It turns out that M3U file basically just a plain text file with some information in it and it's pretty easy to create/parse.

In order to create M3U format, there are 4 things that we need.
- Track length in seconds
- Artist name from ID3 tag
- Title name from ID3 tag
- Absolute/relative path where the audio file is located

This feature to create/read/modify the M3U format has been incorporated into my ID3Tidy application. To create a playlist in my Mediascape, all we need to do is to open the MP3 files that we wish to be included inside the playlist from /sdcard/music into the ID3Tidy application. And then create a M3U file and save it into the /sdcard/music/Whatever.m3u

Enjoy! :)

How to Implement Groovy's File.eachLine in Java

One thing that I like from Groovy is that the closures concept. The method File.eachLine() in Groovy is indeed very useful and handy when reading a file line by line. Because of Java's lack of closures, there is a tendency to repeat the steps of reading each line of a file in every code. It is actually quite easy to implement Groovy's File.eachLine in Java.

FileFunction.java
public interface FileFunction {

    public void read(String line); 
}

M3UParserFileFunction.java
public class M3UParserFileFunction implements FileFunction {

    private List<File> mp3Files; 
    private File m3uFile; 
    
    public M3UParserFileFunction(File m3uFile, List<File> mp3Files) { 
        this.mp3Files = mp3Files; 
        this.m3uFile = m3uFile; 
    } 
    
    @Override 
    public void read(String line) { 
        if (!line.trim().startsWith("#")) { 
            if (line.trim().toLowerCase().endsWith(".mp3")) { 
                // Try relative path first, then absolute path. 
                File file = new File(m3uFile.getParentFile(), line.trim()); 
                if (file.exists()) { 
                    mp3Files.add(file); 
                } else if (!file.exists()) { // Use absolute path 
                    file = new File(line.trim()); 
                    if (file.exists()) { 
                        mp3Files.add(file); 
                    } 
                } 
            } 
        } 
    } 
}

FileUtils.java
public class FileUtils {

    private FileUtils() { 
    }

    public static void eachLine(File file, FileFunction fileFunction) throws IOException { 
        BufferedReader br = null; 
        try { 
            br = new BufferedReader(new FileReader(file)); 
            String line = ""; 
            while ((line = br.readLine()) != null) { 
                fileFunction.read(line); 
            } 
        } finally { 
            if (br != null) { 
                br.close(); 
            } 
        } 
    } 
}

List<File> files = new ArrayList<File>(); 
FileUtils.eachLine(m3uFile, new M3UParserFileFunction(m3uFile, files)); 
for (File file : files) { doWhatever(file); }

Friday, November 5, 2010

ID3Tidy

I've created a new open source project for tidying up the ID3 tags information normally found in MP3 files.

ID3Tidy

Enjoy! :)

Monday, September 27, 2010

How to Create an Executable WAR

Have you ever wondered how Hudson let you run a WAR file as if you run an executable JAR file? The answer is simple, i.e. embed a web container inside a WAR file and create a META-INF/MANIFEST.MF that has Main-Class attribute. For Hudson, it embeds Winstone. In this example, I'm gonna use Jetty instead.

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>org.fredy</groupId>
  <artifactId>exec-war</artifactId>
  <version>0.1</version>
  <packaging>war</packaging>
  <name>exec-war</name>
  <dependencies>
    <dependency>
      <groupId>org.mortbay.jetty</groupId>
      <artifactId>jetty</artifactId>
      <version>6.1.25</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <archive>
            <manifest>
              <mainClass>myproject.Main</mainClass>
            </manifest>
          </archive>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
        </configuration>
        <executions>
          <execution>
            <id>jar-with-dependencies</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <display-name>exec-war</display-name>
  <session-config>
    <session-timeout>30</session-timeout>
  </session-config>
  <servlet>
    <servlet-name>MyServlet</servlet-name>
    <servlet-class>myproject.servlet.MyServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>MyServlet</servlet-name>
    <url-pattern>/MyServlet</url-pattern>
  </servlet-mapping>
</web-app>

MyServlet.java
package myproject.servlet;
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyServlet extends HttpServlet {

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        process(req, resp);
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        process(req, resp);
    }

    private void process(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter pw = resp.getWriter();
        try {
            pw.print("<html><body><h1>Hello World</h1></body></html>");
        } finally {
            pw.close();
        }
    }
}

Main.class
package myproject;

import java.net.URL;

import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.webapp.WebAppContext;

public class Main {
    
    public static void main(String[] args) throws Exception {
        Server server = new Server();
        
        Connector connector = new SelectChannelConnector();
        connector.setPort(8888);
        connector.setHost("127.0.0.1");
        server.addConnector(connector);
        
        URL warUrl = Main.class.getClassLoader().getResource("WEB-INF");
        System.out.println(warUrl);
        String warUrlString = warUrl.toExternalForm();
        server.setHandler(new WebAppContext(warUrlString, "/exec-war"));
        server.setStopAtShutdown(true);
        
        server.start();
    }
}

1. Package a WAR file
mvn package
2. Rename the package name from JAR to WAR. The JAR still has WAR structure. The file has a JAR extension because of the maven-assembly-plugin. So, it's basically a JAR file with a WAR structure inside.
3. Run it.
java -jar exec-war.war
4. Or deploy it in a web container, such as Tomcat.

My Open Source Projects

I've created two new open source projects.

FileRenamer - A small tool to do bulk renaming.
JUGen - A small tool to generate JUnit.

Do give a feedback! :)

Wednesday, September 22, 2010

Applying a License into Source Code

Below is a script to apply a license into source code.

ApplyLicense.groovy
validateArgs(this.args)

def projectDir = new File(this.args[0])
def licenseText = new File(this.args[1]).text
def fileExtension = this.args[2]

projectDir.eachFileRecurse { file ->
    if (file.name.endsWith(fileExtension)) {
        def tmpFile = new File(file.path + ".tmp")
        tmpFile.text = licenseText + file.text
        tmpFile.renameTo(file) 
    }
}

def validateArgs(def args) {
    if (args.size() < 3) {
        printUsage()
        System.exit(1)
    }
    if (!new File(args[0]).isDirectory()) {
        printUsage()
        System.exit(1)
    }
    if (!new File(args[1]).isFile()) {
        printUsage()
        System.exit(1)
    }
}

def printUsage() {
    println "Usage: groovy ApplyLicense.groovy <project_dir> <license_file> <file_extension>"
}

Getting Started with JAX-RS

The example below shows how to get started with JAX-RS with Jersey (JAX-RS RI). The client side uses jQuery 1.4.2 and jQuery UI 1.8.4. The nice thing about jQuery UI 1.8.4 is that it can style the buttons according to theme specified.

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>myproject</groupId> 
  <artifactId>jersey-app</artifactId> 
  <version>0.1</version> 
  <name>jersey-app</name> 
  <packaging>war</packaging> 
  <repositories> 
    <repository> 
      <id>maven2-repository.dev.java.net</id> 
      <name>Java.net Repository for Maven</name> 
      <url>http://download.java.net/maven/2/</url> 
      <layout>default</layout> 
    </repository> 
  </repositories> 
  <dependencies> 
    <dependency> 
      <groupId>com.sun.jersey</groupId> 
      <artifactId>jersey-server</artifactId> 
      <version>1.4</version> 
    </dependency> 
  </dependencies> 
  <build> 
    <plugins> 
      <plugin> 
        <groupId>org.apache.maven.plugins</groupId> 
        <artifactId>maven-compiler-plugin</artifactId> 
        <configuration> 
          <source>1.6</source> 
          <target>1.6</target> 
        </configuration> 
      </plugin> 
      <plugin> 
        <groupId>org.mortbay.jetty</groupId> 
        <artifactId>maven-jetty-plugin</artifactId> 
        <version>6.1.25</version> 
      </plugin> 
    </plugins> 
  </build> 
</project>

web.xml
<?xml version="1.0" encoding="UTF-8"?> 
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" 
  xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  id="WebApp_ID" version="2.5"> 
  <display-name>jersey-app</display-name> 
  <servlet> 
    <servlet-name>Jersey Web Application</servlet-name> 
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> 
    <init-param> 
      <param-name>com.sun.jersey.config.property.packages</param-name> 
      <param-value>myproject</param-value> 
    </init-param> 
    <load-on-startup>1</load-on-startup> 
  </servlet> 
  <servlet-mapping> 
    <servlet-name>Jersey Web Application</servlet-name> 
    <url-pattern>/ws/*</url-pattern> 
  </servlet-mapping> 
  <welcome-file-list> 
    <welcome-file>index.jsp</welcome-file> 
  </welcome-file-list> 
</web-app> 
The com.sun.jersey.config.property.packages tells the ServletContainer to scan which package that contains the JAX-RS classes.

HelloResource.java
package myproject;

import javax.ws.rs.GET; 
import javax.ws.rs.Path; 
import javax.ws.rs.PathParam; 
import javax.ws.rs.Produces;

@Path("/hello/{user}") 
public class HelloResource { 
    
    @Produces("text/plain") 
    @GET 
    public String getMessage(@PathParam("user") String user) { 
        return "Hello World, " + user; 
    } 
}

DefaultHelloResource.java
package myproject;

import javax.ws.rs.GET; 
import javax.ws.rs.Path; 
import javax.ws.rs.Produces;

@Path("/hello") 
public class DefaultHelloResource {

    @GET 
    @Produces("text/plain") 
    public String getMessage() { 
        return "Hello World"; 
    } 
}

index.jsp
<?xml version="1.0" encoding="ISO-8859-1" ?> 
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" 
    pageEncoding="ISO-8859-1"%> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"> 
  <head> 
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> 
    <title>JAX-RS with Jersey</title> 
    <link type="text/css" href="css/ui-lightness/jquery-ui-1.8.4.custom.css" rel="stylesheet" /> 
    <link type="text/css" href="css/ui.jqgrid.css" rel="stylesheet" /> 
    <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script> 
    <script type="text/javascript" src="js/jquery-ui-1.8.4.custom.min.js"></script> 
    <script type="text/javascript"> 
      $(function() { 
          $(".buttonStyle").button(); 
      });

      $(function() { 
          $("#button").click(function() { 
              $("#message").load("/jersey-app/ws/hello/" + $("#name").val()); 
          }); 
      }); 
    </script> 
    <style type="text/css"> 
        body { 
          font: 62.5% "Trebuchet MS", sans-serif; 
          margin: 50px; 
        } 
    </style> 
  </head> 
  <body> 
    <h1>Welcome to JAX-RS with Jersey Demo!</h1> 
    Name: <input id="name" type="text"/> 
    <button id="button" class="buttonStyle">Get Message</button> 
    <p></p> 
    <div id="message"/> 
  </body> 
</html>