When using Apache HttpClient (now legacy), one needs to convert between Java Servlet cookies and the Apache HttpClient cookies. This basic operation is neither directly supported by the Apache HttpClient library, nor by any other open-source library. Hence, I wrote this class below to perform this utility conversions.

Grab the code from my Google Code repository.

Hope this helps.

/**
 * Copyright (C) 2010, Sandeep Gupta
 * http://www.sangupta.com
 * 
 * The file is licensed under the the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * 
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package com.sangupta.util;

import java.util.Date;

import javax.servlet.http.Cookie;

/**
 * Utility class to help convert Cookie objects between Java Servlet Cookie's
 * and Apache HttpClient Cookie's. 
 * 
 * @author sangupta
 * @version 1.0
 * @since 30 Oct 2010
 */
public class ApacheCookieUtils {
 
 /**
  * Method to convert an Apache HttpClient cookie to a Java Servlet cookie.
  * 
  * @param apacheCookie the source apache cookie
  * @return a java servlet cookie
  */
 public static Cookie servletCookieFromApacheCookie(org.apache.commons.httpclient.Cookie apacheCookie) {
  if(apacheCookie == null) {
   return null;
  }
  
  String name = apacheCookie.getName();
  String value = apacheCookie.getValue();
  
  Cookie cookie = new Cookie(name, value);

  // set the domain
  value = apacheCookie.getDomain();
  if(value != null) {
   cookie.setDomain(value);
  }
  
  // path
  value = apacheCookie.getPath();
  if(value != null) {
   cookie.setPath(value);
  }
  
  // secure
  cookie.setSecure(apacheCookie.getSecure());

  // comment
  value = apacheCookie.getComment();
  if(value != null) {
   cookie.setComment(value);
  }
  
  // version
  cookie.setVersion(apacheCookie.getVersion());
  
  // From the Apache source code, maxAge is converted to expiry date using the following formula
  // if (maxAge >= 0) {
        //     setExpiryDate(new Date(System.currentTimeMillis() + maxAge * 1000L));
        // }
  // Reverse this to get the actual max age
  
  Date expiryDate = apacheCookie.getExpiryDate();
  if(expiryDate != null) {
   long maxAge = (expiryDate.getTime() - System.currentTimeMillis()) / 1000;
   // we have to lower down, no other option
   cookie.setMaxAge((int) maxAge);
  }
  
  // return the servlet cookie
  return cookie;
 }
 
 /**
  * Method to convert a Java Servlet cookie to an Apache HttpClient cookie.
  * 
  * @param cookie the Java servlet cookie to convert
  * @return the Apache HttpClient cookie
  */
 public static org.apache.commons.httpclient.Cookie apacheCookieFromServletCookie(Cookie cookie) {
  if(cookie == null) {
   return null;
  }
  
  org.apache.commons.httpclient.Cookie apacheCookie = null;
  
  // get all the relevant parameters
     String domain = cookie.getDomain();
     String name = cookie.getName();
     String value = cookie.getValue();
     String path = cookie.getPath();
     int maxAge = cookie.getMaxAge();
     boolean secure = cookie.getSecure();
     
     // create the apache cookie
     apacheCookie = new org.apache.commons.httpclient.Cookie(domain, name, value, path, maxAge, secure);
     
     // set additional parameters
     apacheCookie.setComment(cookie.getComment());
     apacheCookie.setVersion(cookie.getVersion());

     // return the apache cookie
     return apacheCookie;
 }

}

written by Sandeep Gupta

Sunday, October 31, 2010 at 8:50 AM

Recently AIR 2.5 SDK was released by Adobe. One of the major changes in AIR 2.5 SDK is to the AIR Application Update Framework. This impacts the way, one specifies an application version in their AIR applications. The version tag has been removed, and two new tags, versionNumber and versionLabel have been added.

I had earlier posted code (see Ant task to update AIR application number) for an ANT task that helps update the application version in a continuous integration model. In this post, I update the ANT task to support the new attributes of AIR 2.5.

The task can be used as,








The code can also be downloaded from my Google Code repository.

/**
 * Copyright (C) 2010, Sandeep Gupta
 * http://www.sangupta.com
 * 
 * The file is licensed under the the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * 
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package com.sangupta.ant.tasks;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;

/**
* A simple ANT task that takes in an Adobe AIR application's application descriptor
* XML file and replaces the <version> string with the given build number. The
* task comes handy when used in a continuous integration process. The task has been
* tested with AIR SDK version 1.0 to 2.5. For AIR version's up to 2.0 the task replaces
* the version tag. For AIR version 2.5, the task replaces versionNumber
* and versionLabel tags. In case, the versionLabel is not
* specified, the task replaces the same value as versionNumber. The 
* versionNumber should be of the format <0-999>.<0-999>.<0-999>
* 
* Works for my use cases, your mileage may vary.
* 
* 
* Note: The application descriptor file must be write-enabled before invoking the task.
* 
* @author Sandeep Gupta [email]
* @version 1.1
* @since 23 Oct 2010
*/
public class AIRVersionTask extends Task {
 
 /**
 * The location of the application descriptor XML file.
 */
 private String appDescriptor = null;
 
 /**
 * The build number to replace the version with.
 */
 private String buildNumber = null;
 
 /**
  * AIR 2.5+ build number of the form x.y.z
  */
 private String versionNumber = null;
 
 /**
  * AIR 2.5+ build label string that is shown to the user (optional).
  */
 private String versionLabel = null;
 
 /**
 * Constant representing the platform dependent new-line character.
 */
 private static String newline = System.getProperty("line.separator");
 
 /**
 * Here goes the task execution code, pretty self-explanatory.
 */
 public void execute() throws BuildException {
  // test for AIR version
  if(isEmpty(this.buildNumber) && isEmpty(this.versionNumber)) {
   throw new BuildException("Either buildNumber or versionNumber must be specified.");
  }
  
  if(!isEmpty(this.buildNumber) && !isEmpty(this.versionNumber)) {
   throw new BuildException("Only one of buildNumber or versionNumber should be specified.");
  }
  
  // check the file has to be an XML file
  if(!(this.appDescriptor != null && this.appDescriptor.toLowerCase().endsWith(".xml"))) {
   throw new BuildException("The application descriptor must be an XML file.");
  }
  
  // check if the file is actually present
  File xml = new File(this.appDescriptor);
  if(!xml.exists()) {
   throw new BuildException("The provided application descriptor file does not exist.");
  }
  
  // check if this is AIR 2.5 build
  boolean isAir25 = false;
  
  // check for build number
  if(isEmpty(this.buildNumber)) {
   isAir25 = true;
  }
  
  // if there is not version label - put the version number in
  if(isAir25) {
   if(isEmpty(this.versionLabel)) {
    this.versionLabel = this.versionNumber;
   }
  }
  
  // read the file and modify the build number
  StringBuilder builder = new StringBuilder();
  BufferedReader reader = null;
  Writer output = null;
  
  try {
   reader =  new BufferedReader(new FileReader(xml));
   String line = null;
   while((line = reader.readLine()) != null) {
    
    if(!isAir25) {
     line = replaceTag(line, "version", this.buildNumber);
    } else {
     line = replaceTag(line, "versionNumber", this.versionNumber);
     line = replaceTag(line, "versionLabel", this.versionLabel);
    }
    
    builder.append(line);
    builder.append(newline);
   }

   // gracefully close the reader
   reader.close();

   // now we have the contents in string builder
   // just replace the file in
   output = new BufferedWriter(new FileWriter(xml));
   output.write(builder.toString());
   output.close();
   
   // all done
  } catch(IOException e) {
   throw new BuildException("Unable to set version string.", e);
  } finally {
   if(reader != null) {
    try {
     reader.close();
    } catch(Exception ex) {
     // do nothing
    }
   }
   
   if(output != null) {
    try {
     output.close();
    } catch(Exception ex) {
     // do nothing
    }
   }
  }
 }
 
 /**
  * Given a tag name replace the tag value with the given value.
  * 
  * @param line the line to look for tag in
  * @param tagName the name of the tag to search for
  * @param value the value to be put as tag value
  * @return the modified/original line depending if the tag was replaced or not
  */
 private String replaceTag(String line, String tagName, String value) {
  String startTag = "<" + tagName + ">";
  String endTag = "";
  
  String outLine = line.trim();
  if(outLine.startsWith(startTag) && outLine.endsWith(endTag)) {
   int index = line.indexOf(outLine);
   line = line.substring(0, index) + startTag + value + endTag;
  }
  
  return line;
 }
 
 /**
  * Convenience function to test if a string contains anything except whitespaces.
  * 
  * @param string string to test for.
  * @return true if the string is null or empty, false otherwise.
  */
 private boolean isEmpty(String string) {
  if(string == null || string.trim().length() == 0) {
   return true;
  }
  
  return false;
 }

 // Usual accessor's follow

 /**
  * @return the appDescriptor
  */
 public String getAppDescriptor() {
  return appDescriptor;
 }

 /**
  * @param appDescriptor the appDescriptor to set
  */
 public void setAppDescriptor(String appDescriptor) {
  this.appDescriptor = appDescriptor;
 }

 /**
  * @return the buildNumber
  */
 public String getBuildNumber() {
  return buildNumber;
 }

 /**
  * @param buildNumber the buildNumber to set
  */
 public void setBuildNumber(String buildNumber) {
  this.buildNumber = buildNumber;
 }

 /**
  * @return the versionNumber
  */
 public String getVersionNumber() {
  return versionNumber;
 }

 /**
  * @param versionNumber the versionNumber to set
  */
 public void setVersionNumber(String versionNumber) {
  this.versionNumber = versionNumber;
 }

 /**
  * @return the versionLabel
  */
 public String getVersionLabel() {
  return versionLabel;
 }

 /**
  * @param versionLabel the versionLabel to set
  */
 public void setVersionLabel(String versionLabel) {
  this.versionLabel = versionLabel;
 }
 
}

Hope this helps.

written by Sandeep Gupta

Saturday, October 30, 2010 at 8:48 PM

Adobe AIR includes a rich HTML control based on the open-source WebKit engine. This allows AIR applications to display rich HTML control and provide custom functionality around those web pages. Thus, an application can allow a user to display an HTML control and add functionalities like spell-check, dictionary support etc. One of the most required functionalities when making rich Web Application is the use of custom context-sensitive menus. Context-sensitive menus is a list of menu options, that open when you right click on an element of a user-interface (subject that the element supports one).

In this post we will try and look on how to add a custom context-sensitive menu on hyperlinks present in an HTML page loaded inside an AIR HTML control. We would two menu options called, ‘Open link in Browser’ and ‘Copy Link’ to the context-sensitive menu. To achieve the same adopt the following procedure,
  1. Create an AIR application consisting of an HTML control.
  2. Capture the Event.COMPLETE event handler so as to figure out when an HTML page has completely loaded inside the HTML control.
  3. Wire a context-sensitive menu event listener that is invoked when a user right-clicks inside the HTML control.
  4. In the event handler for the same, generate a context sensitive menu and display it at the right location.
Done. A snapshot of how the application looks like is,


Below is the code that makes use of the above approach in a simple application. When executing the example, wait for the page to load completely. Once, all Javascript’s on the page have loaded, try clicking on the hyperlinks in the page displayed.

The code for the application is also available in my Google Code repository.

The main MXML code is as,

 
 
 
  
 

 
 


The SCRIPT block should contain the following code,
import mx.controls.Menu;
   import mx.events.MenuEvent;
   
   /**
    * Instance of the menu being rendered.
    */
   private var myMenu:Menu = null;
   
   /**
    * Initialize the application
    */
   private function onInit():void {
    this.html.addEventListener(Event.COMPLETE, htmlLoadCompleteHandler);
    this.html.addEventListener(MouseEvent.MOUSE_DOWN, htmlMouseDownHandler);
    
    // shameless publicity
    this.html.location = "http://blog.sangupta.com";
   }
   
   private function htmlLoadCompleteHandler(event:Event):void {
    const document:Object = this.html.htmlLoader.window.document;
    document.body.oncontextmenu = contextMenuHandler;
    document.body.onclick = clickHandler;
   }
   
   /**
    * Remove menu when you click on Flex surface than document body
    */
   private function htmlMouseDownHandler(event:MouseEvent):void {
    hideContextMenu();
   }

   /**
    * Remove menu when you click on document body
    */
   private function clickHandler(event:Object = null):void {
    hideContextMenu();
   }
   
   /**
    * Hide the context menu
    */
   private function hideContextMenu():void {
    if(myMenu != null) {
     myMenu.hide();
    }
   }
   
   /**
    * Function that builds the context menu
    */
   private function contextMenuHandler(event:Object = null):* {
    var target:Object = event.srcElement;

    if(target.nodeType == '1' && String(target.nodeName).toLowerCase() == 'a') {
     // the URL of the anchor element
     var url:String = target.href as String;

     var x:Number = event.x;
     var y:Number = event.y;
     var p:Point = new Point(x,y);
     p = this.localToGlobal(p);

     if(myMenu != null) {
      hideContextMenu();
      myMenu = null;
     }

     var items:Array = new Array();
     var object:Object = null;
     
     // open link in browser
     object = new Object();
     object.label = 'Open In Browser';
     object.eventName = 'browser';
     items.push(object);
     
     // separator
     object = new Object();
     object.type = 'separator';
     items.push(object);
     
     // copy link
     object = new Object();
     object.label = 'Copy';
     object.eventName = 'copy';
     items.push(object);
     
     // create the menu object
     myMenu = Menu.createMenu(parent, items, false);  

     // add menu item click handler
     myMenu.addEventListener(MenuEvent.ITEM_CLICK, menuItemClickHandler);
     
     // show the menu
     myMenu.show(p.x, p.y);

     // disable default context sensitive menu handler
     return false;
    }
   }
   
   private function menuItemClickHandler(event:MenuEvent):void {
    // handle the menu item click event here
   }

Hope this helps.
~ Sandeep

written by Sandeep Gupta

Sunday, October 24, 2010 at 6:46 PM

A few days back, Apple announced that they are deprecating Java on the Mac OS X operating system.The Mac App Store review guidelines, Section 2.24 mentions,
apps that use deprecated or optionally installed technologies will be rejected.
This essentially translates that no Java application would hit the Mac App Store. Is this it? I don’t think so. As per various posts flooding over blogs/twitter, there seems to be a little discomfort in the community about the announcement. Steve Jobs quoted in favor of this decision as,
Sun (now Oracle) supplies Java for all other platforms. They have their own release schedules, which are almost always different than ours, so the Java we ship is always a version behind. This may not be the best way to do it.
As James Gosling mentions in his blog post that it is not Sun providing Java for all other platforms. The platform developers are the ones doing so. This argument does not make much difference in this context. As users are still free to install any JVM that supports for Mac OS X, the world of Java would still be available on Mac. This would make sure that Java developers can continue to develop their applications independent of the platform, and others can continue to use them. The only setback to the developers is that their application can not be posted to Mac Store.

In the last paragraph of his post, Gosling also mentions how Oracle forced Apple to implement aliased rendering in Mac, which as per Mac standards was awful to implement. With Oracle coming in conflict with many other Java enthusiasts such as, Apache and Google, it could be well timed for Apple to pay back to Oracle in its own way.

Is it only Java at which Apple would stop, or will Python, Perl, PHP, Ruby, etc.. be the next ones in line of fire. Do note that Google is also enthusiastic about Python. And none, of these have their schedules parallel to Apple schedules.

It would be interesting to see how the Java world shapes up, considering other recent developments such as,
  • Oracle suing Google over patent infringement – read more with this search.
  • Schedule and release change in Java 7 – read details on Mark Reinhold’s blog post (Mark is Chief Architect of Java Platform Group at Oracle).
  • Too much noise in the recent JCP elections – read Stephen Colebourne’s blog post.

Hoping to see that silver lining to this dark cloud.

written by Sandeep Gupta

Saturday, October 23, 2010 at 4:51 PM

NOTE (30 Oct 2010): The ANT task has been updated to support changes in AIR 2.5. Read more in the updated entry.

Using Continuous Integration in our AIR projects involves updating the build number of the AIR application with each build. The version number is stored in an application descriptor XML. Working with ANT updating the token involves adding a token value to the version attribute and then calling the replace task to update the value at build time. For example,


  @@@ANT_VERSION_TOKEN@@@

and then using an ANT command as,


Simple enough. But, this approach has a downside. When working with Flash Builder (formerly, Flex Builder) the version number comes as a messed up string of, @@@ANT_VERSION_TOKEN@@@. This is not a very good scenario, as one may display this string in an About Box, or use the versionto check for update of applications.

I came out with a very simple ANT task that can help you automate the version number. The task can be used as,

and you application descriptor XML can stay as original,

  1.0.0
You can of course use the ANT build number task to generate a continuous running sequence and use it as,


This has an advantage that when working in Flash Builder, you would get the version number as 1.0.0 and when using ANT to build, you get the updated version number.

The code for the ANT task is attached below. You can also pick the code from my Google Code repository.

/**
 * Copyright (C) 2010, Sandeep Gupta
 * http://www.sangupta.com
 * 
 * The file is licensed under the the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * 
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package com.sangupta.ant.tasks;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;

/**
* A simple ANT task that takes in an Adobe AIR application's application descriptor
* XML file and replaces the <version> string with the given build number. The
* task comes handy when used in a continuous integration process. The task has been
* tested with AIR SDK version 1.0 to 2.0. Your mileage may vary.
* 
* Note: The application descriptor file must be write-enabled before invoking the task.
* 
* @author Sandeep Gupta [email]
* @version 1.0
* @since 23 Oct 2010
*/
public class AIRVersionTask extends Task {
 
 /**
 * The location of the application descriptor XML file.
 */
 private String appDescriptor = null;
 
 /**
 * The build number to replace the version with.
 */
 private String buildNumber = null;
 
 /**
 * Constant represnting the platform dependent new-line character.
 */
 private static String newline = System.getProperty("line.separator");
 
 /**
 * Here goes the task execution code, pretty self-explanatory.
 */
 public void execute() throws BuildException {
  // check for build number
  if(this.buildNumber == null || "".equals(this.buildNumber.trim())) {
   throw new BuildException("Build number cannot be empty.");
  }
  
  // check the file has to be an XML file
  if(!(this.appDescriptor != null && this.appDescriptor.toLowerCase().endsWith(".xml"))) {
   throw new BuildException("The application descriptor must be an XML file.");
  }
  
  // check if the file is actually present
  File xml = new File(this.appDescriptor);
  if(!xml.exists()) {
   throw new BuildException("The provided application descriptor file does not exist.");
  }
  
  // read the file and modify the build number
  StringBuilder builder = new StringBuilder();
  BufferedReader reader = null;
  Writer output = null;
  
  try {
   reader =  new BufferedReader(new FileReader(xml));
   String line = null;
   while((line = reader.readLine()) != null) {
    String l = line.trim();
    if(l.startsWith("") && l.endsWith("")) {
     int index = line.indexOf(l);
     line = line.substring(0, index) + "" + this.buildNumber + "";
     
    }
    builder.append(line);
    builder.append(newline);
   }

   // gracefully close the reader
   reader.close();

   // now we have the contents in string builder
   // just replace the file in
   output = new BufferedWriter(new FileWriter(xml));
   output.write(builder.toString());
   output.close();
   
   // all done
  } catch(IOException e) {
   throw new BuildException("Unable to set version string.", e);
  } finally {
   if(reader != null) {
    try {
     reader.close();
    } catch(Exception ex) {
     // do nothing
    }
   }
   
   if(output != null) {
    try {
     output.close();
    } catch(Exception ex) {
     // do nothing
    }
   }
  }
 }
 
 // Usual accessor's follow

 public String getAppDescriptor() {
  return appDescriptor;
 }

 public void setAppDescriptor(String appDescriptor) {
  this.appDescriptor = appDescriptor;
 }

 public String getBuildNumber() {
  return buildNumber;
 }

 public void setBuildNumber(String buildNumber) {
  this.buildNumber = buildNumber;
 }

}


Hope this helps.
~ Sandeep

written by Sandeep Gupta

at 12:53 PM

atoi() in Java

with 8 comments

Problem: Write a function to convert an ASCII string to integer, similar to atoi() function of C++.

Solution: The solution is too simple, it's simple checks for erroneous inputs that makes writing such a function fun.

Update: You may also want to refer the implementation of parseDouble() method in Java

Here is my attempt at this classic problem.

package com.sangupta.keepwalking;

public class AsciiToInteger {
 
 public static void main(String[] args) {
  AsciiToInteger instance = new AsciiToInteger();
  int x = instance.atoi("-683");
  System.out.println("Conversion is: " + x);
 }

 private int atoi(String number) {
  // check for NULL or empty
  if(number == null || number.trim().length() == 0) {
   throw new IllegalArgumentException("Number cannot be null/empty.");
  }

  // create a variable to store the result
  int result = 0;
  
  // trim the number
  number = number.trim();
  
  // check for sign as the first character
  boolean negate = false;
  char sign = number.charAt(0);
  
  if(sign == '+' || sign == '-') {
   if(sign == '-') {
    negate = true;
   }
   
   number = number.substring(1);
  }
  
  int length = number.length();
  for(int index = 0; index < length; index++) {
   char digit = number.charAt(index);
   
   // sanitize the digit
   if(!(digit >= '0' && digit <= '9')) {
    throw new IllegalArgumentException("Number contains characters other than digits at index " + index);
   }
   
   digit = (char) (digit - '0');
   
   result += (digit * Math.pow(10, (length - index - 1)));
  }
  
  // if negative, do it
  if(negate) {
   result = 0 - result;
  }
  
  // return the final result
  return result;
 }

}

written by Sandeep Gupta

Saturday, October 2, 2010 at 11:05 PM