Jan 3, 2012

Java under .NET

There is an interesting project - IKVM.NET - which includes JVM implementation in .NET. So, it is very easy both to run Java application under .NET (Mono) environment or to compile Java libraries and applications into .NET executables.

For example, in order to run Java application instead of using:
java -jar Application.jar
you have to use:
ikvm -jar Application.jar

To compile Java library into .NET DLL use:
ikvmc -target:library Library.jar
And finally, in order to convert your Java application into .NET executable, run:
ikvmc - target:exe Application.jar

Dec 25, 2011

Custom CometTransport implementation for IE

In our GWT web application we use gwt-comet library for Comet support. Unfortunately, it is not reliable under IE. For some reason it gets stuck. So, the solution was to implement custom CometTransport for IE using XMLHttpRequest object.

While, gwt-comet contains CometTransport implementation based on XMLHttpRequest (HTTPRequestCometTransport), IEHTMLFileCometTransport is designed specifically for IE. HTTPRequestCometTransport doesn't work for IE, because XMLHttpRequest behavior is different from that expected in HTTPRequestCometTransport. Particularly, HTTPRequestCometTransport expects partial content loading and handling through onReadyStateChange event delivering with readyState = 3 (LOADING). IE doesn't support this, i.e. it doesn't return partially loaded content, but rather empty string.

Our custom implementation doesn't use partial loading. Once event is sent to the client, the connection is terminated. Then the transport initiates connection again waiting for ongoing messages.

There was also need to patch server side protocol. So, we have forced to override CometServlet class in order to create custom servlet response object - IERequestCometServletResponse. Below is a code:

IECometTransport.java
package com.j2start.webapp.client.comet;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import com.google.gwt.core.client.JavaScriptException;

import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;

import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.RequestException;

import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.regexp.shared.SplitResult;

import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.rpc.StatusCodeException;

import com.google.gwt.xhr.client.XMLHttpRequest;
import com.google.gwt.xhr.client.ReadyStateChangeHandler;

import net.zschech.gwt.comet.client.impl.RawDataCometTransport;

/**
 * 
 * @author y_plaksyuk
 */
public class IECometTransport extends RawDataCometTransport {
 private static final String SEPARATOR = "\n";
 private static RegExp separator;

 private boolean connected = false;

 static {
  Event.addNativePreviewHandler(new NativePreviewHandler() {
   @Override
   public void onPreviewNativeEvent(NativePreviewEvent e) {
    if (e.getTypeInt() == Event.getTypeInt(KeyDownEvent.getType().getName())) {
     NativeEvent nativeEvent = e.getNativeEvent();
     if (nativeEvent.getKeyCode() == KeyCodes.KEY_ESCAPE) {
      nativeEvent.preventDefault();
     }
    }
   }
  });
  separator = RegExp.compile(SEPARATOR);
 }

 private XMLHttpRequest xmlHttpRequest;

 @Override
 public void connect(int connectionCount) {
  super.connect(connectionCount);

  xmlHttpRequest = XMLHttpRequest.create();
  try {
   xmlHttpRequest.open("GET", getUrl(connectionCount));
   xmlHttpRequest.setRequestHeader("Accept", "application/comet+ie");
   xmlHttpRequest.setOnReadyStateChange(new ReadyStateChangeHandler() {
    @Override
    public void onReadyStateChange(XMLHttpRequest request) {
     if (!disconnecting) {
      if (!connected) {
       onReceiving(Response.SC_OK, "!15000\n"); // TODO: hardcoded default value
       connected = true;
      }

      if (request.getReadyState() == XMLHttpRequest.DONE)
       onLoaded(request.getStatus(), request.getResponseText());
     }
    }
   });
   xmlHttpRequest.send();
  }
  catch (JavaScriptException e) {
   cleanupHttpRequest(false);

   listener.onError(new RequestException(e.getMessage()), false);
  }
 }

 @Override
 public void disconnect() {
  super.disconnect();
  cleanupHttpRequest(true);
 }

 private void onLoaded(int statusCode, String responseText) {
  onReceiving(statusCode, responseText, false);
 }

 private void onReceiving(int statusCode, String responseText) {
  onReceiving(statusCode, responseText, true);
 }

 private void onReceiving(int statusCode, String responseText, boolean connected) {
  if (!connected)
   cleanupHttpRequest(false);

  if (statusCode != Response.SC_OK) {
   if (!connected) {
    super.disconnect();
    listener.onError(new StatusCodeException(statusCode, responseText), connected);
   }
  }
  else {
   List<serializable> messages = new ArrayList<serializable>();

   SplitResult data = separator.split(responseText);
   int length = data.length();
   for (int i = 0; i < length; i++) {
    if (disconnecting) {
     return;
    }

    String message = data.get(i);
    if (!message.isEmpty()) {
     parse(message, messages);
    }
   }

   if (!messages.isEmpty())
    listener.onMessage(messages);

   if (!connected) {
    super.disconnect();
    super.disconnected();
   }
  }
 }

 private void cleanupHttpRequest(boolean abort) {
  if (xmlHttpRequest != null) {
   if (abort)
    xmlHttpRequest.abort();

   xmlHttpRequest.clearOnReadyStateChange();
   xmlHttpRequest = null;
  }

  connected = false;
 }
}

IERequestCometServletResponse.java
package com.j2start.webapp.server.comet;

import java.util.List;
import java.io.IOException;
import java.io.Serializable;
import java.io.OutputStream;

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

import com.google.gwt.rpc.server.ClientOracle;
import com.google.gwt.user.server.rpc.SerializationPolicy;

import net.zschech.gwt.comet.server.impl.AsyncServlet;
import net.zschech.gwt.comet.server.impl.HTTPRequestCometServletResponse;

/**
 *
 * @author y_plaksyuk
 */
public class IERequestCometServletResponse extends HTTPRequestCometServletResponse {

 public IERequestCometServletResponse(
   HttpServletRequest request,
   HttpServletResponse response,
   SerializationPolicy serializationPolicy,
   ClientOracle clientOracle,
   CometServlet servlet,
   AsyncServlet async,
   int heartbeat) {

  super(request, response, serializationPolicy, clientOracle, servlet, async, heartbeat);
 }

 @Override
 protected void doInitiate(int heartbeat) throws IOException {
  // client will imitate receiving 'initiate' command itself
 }

 @Override
 protected void doTerminate() throws IOException {
  // don't send anything, in some cases write stream is already closed
 }

 ////////////////////////////////////////////////////////////////////////////////////////////////

 @Override
 public synchronized void write(List messages, boolean flush) throws IOException {
  super.write(messages, flush);
  flushAndTerminate();
 }

 @Override
 public synchronized void heartbeat() throws IOException {
  super.heartbeat();
  flushAndTerminate();
 }

 private void flushAndTerminate() throws IOException {
  writer.flush();
  writer.close();

  OutputStream os = getAsyncOutputStream();
  os.flush();
  os.close();

  try {
   terminate();
  }
  catch (IOException ex) {
  }
 }
}

CometServlet.java
package com.j2start.webapp.server.comet;

import java.io.IOException;

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

import com.google.gwt.rpc.server.ClientOracle;
import com.google.gwt.user.server.rpc.SerializationPolicy;

import net.zschech.gwt.comet.server.impl.AsyncServlet;
import net.zschech.gwt.comet.server.impl.CometServletResponseImpl;
import net.zschech.gwt.comet.server.impl.EventSourceCometServletResponse;
import net.zschech.gwt.comet.server.impl.HTTPRequestCometServletResponse;
import net.zschech.gwt.comet.server.impl.IEHTMLFileCometServletResponse;
import net.zschech.gwt.comet.server.impl.OperaEventSourceCometServletResponse;

/**
 *
 * @author y_plaksyuk
 */
public class CometServlet extends net.zschech.gwt.comet.server.CometServlet {
 private transient AsyncServlet async;

 @Override
 public void init() throws ServletException {
  ServletConfig servletConfig = getServletConfig();
  String heartbeatValue = servletConfig.getInitParameter("heartbeat");
  if (heartbeatValue != null)
   setHeartbeat(Integer.parseInt(heartbeatValue));

  async = AsyncServlet.initialize(getServletContext());
 }

 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  try {
   int requestHeartbeat = getHeartbeat();
   String requestedHeartbeat = request.getParameter("heartbeat");
   if (requestedHeartbeat != null) {
    try {
     requestHeartbeat = Integer.parseInt(requestedHeartbeat);
     if (requestHeartbeat <= 0) {
      throw new IOException("invalid heartbeat parameter");
     }
    }
    catch (NumberFormatException ex) {
     throw new IOException("invalid heartbeat parameter");
    }
   }

   ClientOracle clientOracle = getClientOracle(request);
   SerializationPolicy serializationPolicy = clientOracle == null ? createSerializationPolicy() : null;
   CometServletResponseImpl cometServletResponse = createCometServletResponse(request, response, serializationPolicy, clientOracle, requestHeartbeat);
   doCometImpl(cometServletResponse);
  }
  catch (IOException e) {
   CometServletResponseImpl cometServletResponse = createCometServletResponse(request, response, null, null, 0);
   cometServletResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
  }
 }

 private CometServletResponseImpl createCometServletResponse(
   HttpServletRequest request,
   HttpServletResponse response,
   SerializationPolicy serializationPolicy,
   ClientOracle clientOracle,
   int requestHeartbeat) {

  String accept = request.getHeader("Accept");
  String userAgent = request.getHeader("User-Agent");
  if ("text/event-stream".equals(accept)) {
   return new EventSourceCometServletResponse(request, response, serializationPolicy,
     clientOracle, this, async, requestHeartbeat);
  }
  else if ("application/comet+ie".equals(accept)) {
   return new IERequestCometServletResponse(request, response, serializationPolicy,
     clientOracle, this, async, requestHeartbeat);
  }
  else if ("application/comet".equals(accept)) {
   return new HTTPRequestCometServletResponse(request, response, serializationPolicy,
     clientOracle, this, async, requestHeartbeat);
  }
  else if (userAgent != null && userAgent.contains("Opera")) {
   return new OperaEventSourceCometServletResponse(request, response, serializationPolicy,
     clientOracle, this, async, requestHeartbeat);
  }
  else {
   return new IEHTMLFileCometServletResponse(request, response, serializationPolicy,
     clientOracle, this, async, requestHeartbeat);
  }
 }

 private void doCometImpl(CometServletResponseImpl response) throws IOException {
  try {
   // setup the request
   response.initiate();

   // call the application code
   doComet(response);
  }
  catch (IOException e) {
   log("Error calling doComet()", e);
   response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
  }
  catch (ServletException e) {
   log("Error calling doComet()", e);
   response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
  }

  try {
   // at this point the application may have spawned threads to process this response
   // so we have to be careful about concurrency from here on
   response.suspend();
  }
  catch (Exception ex) {
   log("Error calling response.suspend(): " + ex.getMessage());
//   response.terminate();
  }
 }
}

You also need to update your GWT module XML file:

 ...
 
 
  
  
   
   
  
 

And substitute CometServlet with our own implementation in web.xml:

    
        30
    
    
        index.html
    
    
        Listener for shutting down the comet processor when the ServletContext is destroyed.
        net.zschech.gwt.comet.server.CometServletContextListener
    
    
        Listener for invalidating CometSessions when HTTPSessions are invalidated.
        net.zschech.gwt.comet.server.CometHttpSessionListener
    
    
        cometServlet
        com.j2start.webapp.server.comet.CometServlet
    
    
        cometServlet
        /com.j2start.webapp.Main/comet
    


Dec 19, 2011

Steps to sign your MIDlet

When you decide to obtain your own certificate to sign your code, you have to make the following steps:

Step 1. Create a keystore:
keytool -genkey -keyalg rsa -keystore keystore -alias alias
Step 2. Generate a Certificate Signing Request (CSR) for enrollment process:
keytool -certreq -file certreq.csr -keystore keystore -alias alias
Step 3: Import digital ID (certificate) into the keystore:
keytool -import -trustcacerts -keystore keystore -alias alias -file cert.cer
Once you have the certificate in the keystore, register it in your IDE in order to sign your projects. You may also perform signing manually:
jadtool -addcert -alias alias [ -keystore keystore ] [ -storepass password ] -inputjad inputJadFile -outputjad outputJadFile
jadtool -addjarsig [ -jarfile jarFile ] -alias alias [ -keystore keystore ] -storepass password -keypass password -inputjad inputJadFile -outputjad outputJadFile

Notice, you may also sign your MIDlet free of charge here.

Dec 17, 2011

Against LWUIT

In the Oracle's LWUIT Datasheet we read that LWUIT provides:


  • Smart User Interface Technology for Everyone
  • Portability and Broad Device Support
  • Small Footprint API
  • Web Support
  • Themes
  • ... and so on, and so on...

An existence of LWUIT conflicts with a Java slogan "Write once, Run everywhere"! Why Java ME developers need to use LWUIT instead of using raw UI API? It looks like LWUIT goes the same way Swing did against native AWT.

However, in case of Swing there was possible to have a few LAF implementations that make Swing app look natively under host OS. So, app user couldn't see difference. It is not a case for LWUIT. They even do not declare it as a goal. With LWUIT Java ME apps will be like colorful fish in the aquarium...

I'd better insist developers to use native UI API and develop techniques to make apps look better and consistent with native headset UI and theme.

Dec 9, 2011

How to track location

I would like to publish a sample code for tracking location on Java ME platform.

import javax.microedition.location.*;

public class LocationManager implements Runnable, LocationListener {
    private static final double DISTANCE = 100;

    private LocationProvider provider;
    private Location location;
    private Coordinates recentCoordinates;

    private int state = LocationProvider.TEMPORARILY_UNAVAILABLE;

    public LocationManager() {
        Thread thread = new Thread(this);
        thread.start();
    }

    public double getLatitude() {
        return location != null ? location.getQualifiedCoordinates().getLatitude() : 0;
    }

    public double getLongitude() {
        return location != null ? location.getQualifiedCoordinates().getLongitude() : 0;
    }

    ////////////////////////////////////////////////////////////////////////////

    public void locationUpdated(LocationProvider lp, Location location) {
        Coordinates currentCoordinates = location.getQualifiedCoordinates();
        if (recentCoordinates == null || currentCoordinates.distance(recentCoordinates) > DISTANCE) {
            // TODO: handle new location

            recentCoordinates = currentCoordinates;
        }
  
        this.location = location;
    }

    public void providerStateChanged(LocationProvider lp, int state) {
        this.state = state;
    }

    ////////////////////////////////////////////////////////////////////////////

    public void run() {
        try {
            init();
        }
        catch(LocationException e) {
            // TODO: handle me
        }
    }

    ////////////////////////////////////////////////////////////////////////////

    private void init() throws LocationException {
        Criteria criteria = new Criteria();
        criteria.setPreferredPowerConsumption(Criteria.POWER_USAGE_LOW);

        provider = LocationProvider.getInstance(criteria);
        provider.setLocationListener(this, -1, -1, -1);
    }

}

Nov 21, 2011

Java ME SDK 3.0.5 is released!

Java ME SDK 3.0.5 went live! For many months, we have been working hard to fix bugs from previous version, and add a lot of new features demanded by Java ME community.
  • NetBeans Integration - All Java ME tools are implemented as NetBeans plugins. 
  • Device Manager - Java ME SDK now supports multiple device managers. You can switch between different versions of device managers.
  • LWUIT 1.5 Support - The Resource Editor is available from the Java ME menu to help you design and organize resources for LWUIT applications.
  • Network Monitor - Integrated with NetBeans profiling tools, the Network Monitor now supports WMA, SIP, Bluetooth and OBEX, SATSA APDU and JCRMI, and server sockets.
  • CPU Profiler - Now uses standard NetBeans profiling facilities to view snapshots. Profiling of VM classes can also be toggled on or off.
  • WURFL Device Database - The database has been updated with more than 1000 new devices.
  • Tracing - New tracing functionality now includes CLDC VM events, and monitors events such as exceptions, class loading, garbage collection, and methods invocation.
  • New or updated JSR support - Includes support for JSR 234 (Advanced Multimedia Supplements), JSR 253 (Mobile Telephony API), JSR 257 (Contactless Communication API), JSR 258 (Mobile User Interface Customization API), and JSR 293 (XML API for Java ME). 
You can download the new version from this link.

Nov 18, 2011

Pro Java ME Apps


Pro Java ME Apps gives you, the developer, the know-how required for writing sophisticated Java ME applications and for taking advantage of this huge potential market. Java ME is the largest mobile software platform in the world, supported by over 80% of all phones. You’ll cover what Java ME is and how it compares to other mobile software platforms, how to properly design and structure Java ME applications, how to think like an experienced Java ME developer, what common problems and pitfalls you may run into, how to optimize your code, and many other key topics.

Unlike other Java ME books out there, which only teach the reader the basics of Java ME by way of a few simple examples, this book presents a broader, eagle-eye picture of a complete Java ME application and what writing one involves. From there, the book presents, explains, and helps you to implement all the essential aspects of Java ME development, from the user interface to client-server communication. As this unfolds, the decisions and reasoning behind the code are also presented.

The book assumes that the reader is already familiar with Java ME and Java applications in general.
  • Based on and geared towards real-life Java ME scenarios
  • Guides the reader through the entire process of developing a high-quality Java ME application
  • Explains the decisions made at each step, gives advice and examples of good practices vs. bad practices
What you’ll learn
  • What makes mobile software different from desktop software
  • How to design and implement your Java ME application
  • What the most common problems affecting Java ME development (such as device fragmentation) and how to solve them
  • What are the proper Java ME programming techniques and how to think like an experienced Java ME developer
  • How to optimize your code
  • How to develop store-quality apps
Who this book is for
  • Intermediate to advanced Java ME developers looking to learn proper Java ME development.
  • Mobile developers looking for a good source of information regarding proper mobile application development
Table of Contents
  1. Getting Started
  2. A Java ME Framework
  3. Defining Our Data
  4. The Networking Module
  5. The Persistence Module
  6. The UI Module
  7. The Localization Module
  8. Putting It All Together
  9. Device Fragmentation
  10. Optimizing Your Code
  11. Adding Fine Touches and User Interaction Improvements
  12. Java ME Application Testing
  13. Advanced Java ME Graphics
  14. The Proper Java ME Mindset
  15. Java ME and the Future
  16. Final Words
Download