/* * Copyright (c) 2010-2012 Research In Motion Limited. All rights reserved. * * This program and the accompanying materials are made available * under the terms of the Eclipse Public License, Version 1.0, * which accompanies this distribution and is available at * * http://www.eclipse.org/legal/epl-v10.html * */ package net.rim.ejde.internal.packaging; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.util.Hashtable; import java.util.Map; import java.util.Vector; import net.rim.ejde.internal.model.BasicBlackBerryProperties.AlternateEntryPoint; import net.rim.ejde.internal.model.BasicBlackBerryProperties.Icon; import net.rim.ejde.internal.model.BlackBerryProject; import net.rim.ejde.internal.model.BlackBerryProperties; import net.rim.ejde.internal.util.Messages; import net.rim.ejde.internal.util.PackageUtils; import net.rim.ejde.internal.util.PackagingUtils; import net.rim.ejde.internal.util.ProjectUtils; import net.rim.ejde.internal.util.ProjectUtils.RRHFile; import net.rim.ejde.internal.util.StatusFactory; import net.rim.ide.core.IDEError; import net.rim.ide.core.Util; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.osgi.util.NLS; /** * An instance of this class represents the rapc file of a BlackBerry Project. * */ public class RAPCFile { static private final Logger _log = Logger.getLogger( RAPCFile.class ); /** * The maximum startup tiers available to a RIM project */ public static final int MAX_STARTUP_TIER = 7; BlackBerryProject _bbProject; File _rapcFile; Vector< String > _rapcContents = new Vector< String >(); /** * Constructs a RAPCFileContent instance. * * @param bbProject */ public RAPCFile( BlackBerryProject bbProject ) { this._bbProject = bbProject; } /** * Loads the content of the RAPC file. <b>This method must be called before calling {@link #flushToFile()}.</b> * * @throws CoreException * */ public void loadContent() throws CoreException { initialize(); } void initialize() throws CoreException { BlackBerryProperties properties = _bbProject.getProperties(); _rapcContents.addElement( "MIDlet-Name: " + _bbProject.getProject().getName() ); String version = properties._general.getVersion(); if( version == null || version.length() == 0 ) { version = "0.0"; } _rapcContents.addElement( "MIDlet-Version: " + version ); String vendor = properties._general.getVendor(); if( vendor == null || vendor.length() == 0 ) { vendor = "<unknown>"; } _rapcContents.addElement( "MIDlet-Vendor: " + vendor ); String description = properties._general.getDescription(); if( description != null && description.length() != 0 ) { _rapcContents.addElement( "MIDlet-Description: " + description ); } _rapcContents.addElement( "MIDlet-Jar-URL: " + _bbProject.getProject().getName() + ".jar" ); _rapcContents.addElement( "MIDlet-Jar-Size: 0" ); _rapcContents.addElement( "MicroEdition-Profile: MIDP-2.0" ); _rapcContents.addElement( "MicroEdition-Configuration: CLDC-1.1" ); // handle alternate entry points String type = properties._application.getType(); if( !type.equals( BlackBerryProject.LIBRARY ) ) { _rapcFile = getRapcFile( _bbProject ); addMidletEntries( _rapcFile, properties, _rapcContents, 1 ); AlternateEntryPoint[] entryPoints = properties.getAlternateEntryPoints(); for( int i = 0; i < entryPoints.length; ++i ) { addMidletEntries( _rapcFile, new BlackBerryProperties( entryPoints[ i ] ), _rapcContents, i + 2 ); } } else { _rapcContents.addElement( "RIM-Library-Flags: " + getFlags( properties, true ) ); } addRimOptionsEntries( properties ); } /** * Convert all forward slashes in the given <code>string</code> to back slashes. * * @param string * @return */ private String convertToBackSlash( String string ) { return string.replace( '\\', '/' ); } private void addMidletEntries( File projectFile, BlackBerryProperties properties, Vector< String > entries, int index ) throws CoreException { StringBuffer sb = new StringBuffer(); sb.append( "MIDlet-" ).append( index ); sb.append( ": " ); sb.append( properties._general.getTitle() ); sb.append( ',' ); Icon[] icons = properties._resources.getIconFiles(); for( int i = 0; i < icons.length; i++ ) { if( !icons[ i ].isFocus() ) { // need to remove the source folder segment IPath iconFilePath = getSourceFolderRelativePath( icons[ i ].getCanonicalFileName() ); sb.append( convertToBackSlash( iconFilePath.toOSString() ) ); } } sb.append( ',' ); if( !StringUtils.isBlank( properties._application.getMainMIDletName().trim() ) ) { sb.append( properties._application.getMainMIDletName() ); } else { sb.append( properties._application.getMainArgs() ); } entries.addElement( sb.toString() ); boolean hasFocusIcon = false; // set focus icon for( int i = 0; i < icons.length; ++i ) { if( icons[ i ].isFocus() ) { sb.setLength( 0 ); // we only allow one focus icon sb.append( "RIM-MIDlet-Icon-" ).append( index ).append( '-' ).append( 1 ); sb.append( ": " ); // need to remove the source folder segment IPath iconFilePath = getSourceFolderRelativePath( icons[ i ].getCanonicalFileName() ); sb.append( convertToBackSlash( iconFilePath.toOSString() ) ); sb.append( ",focused" ); entries.addElement( sb.toString() ); hasFocusIcon = true; } } if( hasFocusIcon ) { sb.setLength( 0 ); sb.append( "RIM-MIDlet-Icon-Count-" ).append( index ); sb.append( ": " ); sb.append( 1 ); entries.addElement( sb.toString() ); } sb.setLength( 0 ); sb.append( "RIM-MIDlet-Flags-" ).append( index ); sb.append( ": " ); sb.append( getFlags( properties, false ) ); entries.addElement( sb.toString() ); int ribbonPosition = properties._application.getHomeScreenPosition(); if( ribbonPosition != 0 ) { sb.setLength( 0 ); sb.append( "RIM-MIDlet-Position-" ).append( index ); sb.append( ": " ); sb.append( ribbonPosition ); entries.addElement( sb.toString() ); } // we need to check all resource related properties to make sure the resource is valid String titleResourceBundleClassName = properties._resources.getTitleResourceBundleClassName(); String titleResourceBundleKey = properties._resources.getTitleResourceBundleKey(); if( properties._resources.hasTitleResource() && !StringUtils.isEmpty( titleResourceBundleClassName ) && !StringUtils.isEmpty( titleResourceBundleKey ) ) { sb.setLength( 0 ); sb.append( "RIM-MIDlet-NameResourceBundle-" ).append( index ); sb.append( ": " ); sb.append( titleResourceBundleClassName ); entries.addElement( sb.toString() ); sb.setLength( 0 ); sb.append( "RIM-MIDlet-NameResourceId-" ).append( index ); sb.append( ": " ); sb.append( getTitleResourceId( properties ) ); entries.addElement( sb.toString() ); } // check kewword resources String keywordResourceBundleClassName = properties.getKeywordResources().getKeywordTitleResourceBundleClassName(); String keywordResourceBundleKey = properties.getKeywordResources().getKeywordResourceBundleKey(); if( !StringUtils.isEmpty( keywordResourceBundleClassName ) && !StringUtils.isEmpty( keywordResourceBundleKey ) ) { sb.setLength( 0 ); sb.append( "RIM-MIDlet-KeywordResourceBundle-" ).append( index ); sb.append( ": " ); sb.append( keywordResourceBundleClassName ); entries.addElement( sb.toString() ); sb.setLength( 0 ); sb.append( "RIM-MIDlet-KeywordResourceId-" ).append( index ); sb.append( ": " ); sb.append( keywordResourceBundleKey ); entries.addElement( sb.toString() ); } } /* * These are internal options. There is no UI presented for them in external eJDE. */ private void addRimOptionsEntries( BlackBerryProperties properties ) { StringBuffer sb = new StringBuffer(); String prefix = "RIM-Options: "; //$NON-NLS-1$ if( properties._application.isAddOn() ) { if( sb.length() == 0 ) { sb.append( prefix ); } else { sb.append( "," ); //$NON-NLS-1$ } sb.append( "add-on" ); //$NON-NLS-1$ } _rapcContents.addElement( sb.toString() ); } /** * Get the resource id for the given project. We look this up given the resource title key that has been chosen. We used to * store the id number but problems occur if people update their resources outside of the resource editor (which preserves the * id numbers). * * @throws CoreException */ private int getTitleResourceId( BlackBerryProperties properties ) throws CoreException { String key = properties._resources.getTitleResourceBundleKey(); Map< String, RRHFile > resourceMap = ProjectUtils.getProjectResources( _bbProject ); RRHFile rrhFile = resourceMap.get( properties._resources.getTitleResourceBundleClassName() ); if( rrhFile == null ) { String msg = "Could not find key information for " + properties._resources.getTitleResourceBundleClassName(); _log.error( msg ); throw new CoreException( StatusFactory.createErrorStatus( msg ) ); } Hashtable< String, String > headerKey2Id = rrhFile.getKeyTalbe(); if( headerKey2Id == null ) { String path; if( rrhFile.getFile() != null ) { path = rrhFile.getFile().getLocation().toOSString(); } else { path = properties._resources.getTitleResourceBundleClassName(); } String msg = NLS.bind( Messages.RAPCFIlE_NO_KEY_MSG, path ); _log.error( msg ); throw new CoreException( StatusFactory.createErrorStatus( msg ) ); } String resourceId = headerKey2Id.get( key ); if( resourceId == null ) { String msg = NLS.bind( Messages.RAPCFIlE_RESOURCE_KEY_NOT_FOUND_MSG, key ); _log.error( msg ); throw new CoreException( StatusFactory.createErrorStatus( msg ) ); } try { return Integer.parseInt( resourceId ); } catch( NumberFormatException nfe ) { _log.error( nfe ); throw new CoreException( StatusFactory.createErrorStatus( nfe.getMessage() ) ); } } /** * Get the path relative to the source folder of the given <code>canonicalFileName</code>. * * @param canonicalFileName * @return */ private IPath getSourceFolderRelativePath( String canonicalFileName ) { IPath iconFilePath = new Path( canonicalFileName ); IFile iconFile = _bbProject.getProject().getFile( iconFilePath ); IContainer folder = PackageUtils.getSrcFolder( iconFile ); if( folder == null ) { return iconFilePath; } return iconFilePath.removeFirstSegments( folder.getProjectRelativePath().segmentCount() ); } private int getFlags( BlackBerryProperties properties, boolean forceSystemModule ) { // The flags are encoded into a single byte: // bit 0 - run on startup // bit 1 - system module // bit 2 - auto restart // bit 3 - unused // bit 4 - unused // bit 5 - startup tier (0 is 111b, 7 is 000b) // bit 6 - startup tier // bit 7 - startup tier boolean isAutostartup = properties._application.isAutostartup(); boolean isSystemModule = properties._application.isSystemModule() || properties._application.getType().equals( BlackBerryProject.LIBRARY );// a lib has to be run as system module boolean isAutoRestart = properties._application.isAutoRestart(); int flags = ( isAutostartup ? 1 : 0 ) + ( ( isSystemModule || forceSystemModule ) ? 2 : 0 ) + ( isAutoRestart ? 4 : 0 ) + ( ( MAX_STARTUP_TIER - properties._application.getStartupTier() ) << 5 ); return flags; } final static public File getRapcFile( BlackBerryProject project ) { IPath outputFilePath = project.getProject().getLocation(); outputFilePath = outputFilePath.append( getRelativeRapcFilePath( project ) ); return outputFilePath.toFile(); } /** * Get the relative path of the rapc file of the given <code>project</code>. * * @param project * @return */ final static public IPath getRelativeRapcFilePath( BlackBerryProject project ) { IPath outputFilePath = PackagingUtils.getRelativeStandardOutputFilePath( project ); outputFilePath = outputFilePath.removeLastSegments( 1 ); outputFilePath = outputFilePath.append( project.getProperties()._packaging.getOutputFileName() + ".rapc" ); return outputFilePath; } /** * Collects a resource file content (like .rapc) * * @param relativeTo * @return * @throws IDEError */ private byte[] collectResourceInformation() throws IDEError { ByteArrayOutputStream bout = new ByteArrayOutputStream(); PrintStream out; try { out = new PrintStream( bout, false, "UTF-8" ); } catch( UnsupportedEncodingException uee ) { out = new PrintStream( bout ); } for( int i = 0; i < _rapcContents.size(); ++i ) { out.println( _rapcContents.elementAt( i ) ); } out.close(); return bout.toByteArray(); } public void flushToFile() { // TODO Clean up old generated jad files unless it is specifically listed in // project // String type = _bbProject.getProperties()._application.getType(); // if( type.equals( "MIDLET" ) || type.equals( "MIDLET_ENTRY" ) ) { // File jadFile = new File( _bbProject.getProperties()._packaging.getOutputFolder() + ".jad" ); // if( jadFile.exists() ) { // boolean canDelete = true; // TODO how to handle the case if the jad file is part of the // project // for ( int j=0; j < _files.size(); ++j ) { // WorkspaceFile fn = // (WorkspaceFile/*Node*/)_files.elementAt(j); // if ( fn.getIsJad() ) { // if ( fn.getFile().equals( jadFile ) ) { // canDelete = false; // jad file is part of project, don't // delete! // } // } // } // if( canDelete ) { // jad file was auto-generated by rapc, delete // // it // jadFile.delete(); // } // } // } File outputFile = getRapcFile( _bbProject ); byte[] newBytes; try { newBytes = collectResourceInformation(); if( !Util.isFileIdentical( outputFile, newBytes ) ) { // either old file doesn't exist or it isn't the same write out // the new data FileOutputStream fout = new FileOutputStream( outputFile ); fout.write( newBytes ); fout.close(); } } catch( IDEError e ) { _log.error( e ); } catch( FileNotFoundException e ) { _log.error( e ); } catch( IOException e ) { _log.error( e ); } } }