Posted on 30 October 2010

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,

<!-- For projects up to AIR 2.0 --><versionappdescriptor="myproject-app.xml"buildnumber="1.0.0.${build.number}"></version><!-- For projects on AIR 2.5 --><versionappdescriptor="myproject-app.xml"versionnumber="1.0.0"versionlabel="1.0.0.${build.number}"></version>

/**
* 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.
*
*/packagecom.sangupta.ant.tasks;importjava.io.BufferedReader;importjava.io.BufferedWriter;importjava.io.File;importjava.io.FileReader;importjava.io.FileWriter;importjava.io.IOException;importjava.io.Writer;importorg.apache.tools.ant.BuildException;importorg.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 <code>version</code> tag. For AIR version 2.5, the task replaces <code>versionNumber</code>
* and <code>versionLabel</code> tags. In case, the <code>versionLabel</code> is not
* specified, the task replaces the same value as <code>versionNumber</code>. The
* <code>versionNumber</code> should be of the format <0-999>.<0-999>.<0-999>
*
* Works for my use cases, your mileage may vary.
*
*
* <b>Note: The application descriptor file must be write-enabled before invoking the task.</b>
*
* @author Sandeep Gupta <a href="mailto:[email protected]">[email]</a>
* @version 1.1
* @since 23 Oct 2010
*/publicclassAIRVersionTaskextendsTask{/**
* The location of the application descriptor XML file.
*/privateStringappDescriptor=null;/**
* The build number to replace the version with.
*/privateStringbuildNumber=null;/**
* AIR 2.5+ build number of the form x.y.z
*/privateStringversionNumber=null;/**
* AIR 2.5+ build label string that is shown to the user (optional).
*/privateStringversionLabel=null;/**
* Constant representing the platform dependent new-line character.
*/privatestaticStringnewline=System.getProperty("line.separator");/**
* Here goes the task execution code, pretty self-explanatory.
*/publicvoidexecute()throwsBuildException{// test for AIR versionif(isEmpty(this.buildNumber)&&isEmpty(this.versionNumber)){thrownewBuildException("Either buildNumber or versionNumber must be specified.");}if(!isEmpty(this.buildNumber)&&!isEmpty(this.versionNumber)){thrownewBuildException("Only one of buildNumber or versionNumber should be specified.");}// check the file has to be an XML fileif(!(this.appDescriptor!=null&&this.appDescriptor.toLowerCase().endsWith(".xml"))){thrownewBuildException("The application descriptor must be an XML file.");}// check if the file is actually presentFilexml=newFile(this.appDescriptor);if(!xml.exists()){thrownewBuildException("The provided application descriptor file does not exist.");}// check if this is AIR 2.5 buildbooleanisAir25=false;// check for build numberif(isEmpty(this.buildNumber)){isAir25=true;}// if there is not version label - put the version number inif(isAir25){if(isEmpty(this.versionLabel)){this.versionLabel=this.versionNumber;}}// read the file and modify the build numberStringBuilderbuilder=newStringBuilder();BufferedReaderreader=null;Writeroutput=null;try{reader=newBufferedReader(newFileReader(xml));Stringline=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 readerreader.close();// now we have the contents in string builder// just replace the file inoutput=newBufferedWriter(newFileWriter(xml));output.write(builder.toString());output.close();// all done}catch(IOExceptione){thrownewBuildException("Unable to set version string.",e);}finally{if(reader!=null){try{reader.close();}catch(Exceptionex){// do nothing}}if(output!=null){try{output.close();}catch(Exceptionex){// 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
*/privateStringreplaceTag(Stringline,StringtagName,Stringvalue){StringstartTag="<"+tagName+">";StringendTag="<!--"+tagName+"-->";StringoutLine=line.trim();if(outLine.startsWith(startTag)&&outLine.endsWith(endTag)){intindex=line.indexOf(outLine);line=line.substring(0,index)+startTag+value+endTag;}returnline;}/**
* Convenience function to test if a string contains anything except whitespaces.
*
* @param string string to test for.
* @return <code>true</code> if the string is <code>null</code> or empty, <code>false</code> otherwise.
*/privatebooleanisEmpty(Stringstring){if(string==null||string.trim().length()==0){returntrue;}returnfalse;}// Usual accessor's follow/**
* @return the appDescriptor
*/publicStringgetAppDescriptor(){returnappDescriptor;}/**
* @param appDescriptor the appDescriptor to set
*/publicvoidsetAppDescriptor(StringappDescriptor){this.appDescriptor=appDescriptor;}/**
* @return the buildNumber
*/publicStringgetBuildNumber(){returnbuildNumber;}/**
* @param buildNumber the buildNumber to set
*/publicvoidsetBuildNumber(StringbuildNumber){this.buildNumber=buildNumber;}/**
* @return the versionNumber
*/publicStringgetVersionNumber(){returnversionNumber;}/**
* @param versionNumber the versionNumber to set
*/publicvoidsetVersionNumber(StringversionNumber){this.versionNumber=versionNumber;}/**
* @return the versionLabel
*/publicStringgetVersionLabel(){returnversionLabel;}/**
* @param versionLabel the versionLabel to set
*/publicvoidsetVersionLabel(StringversionLabel){this.versionLabel=versionLabel;}}