/*
* 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.builders;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Map;
import java.util.Vector;
import net.rim.ejde.internal.core.IConstants;
import net.rim.ejde.internal.core.IRIMMarker;
import net.rim.ejde.internal.util.ImportUtils;
import net.rim.ejde.internal.util.Messages;
import net.rim.ejde.internal.util.PackageUtils;
import net.rim.ejde.internal.util.ProjectUtils;
import net.rim.ejde.internal.util.ResourceBuilderUtils;
import net.rim.ejde.internal.util.StatusFactory;
import net.rim.sdk.CompilerException;
import net.rim.sdk.rc.ConvertUtil;
import net.rim.sdk.rc.ResourceCompiler;
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.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
/**
* This class is used to create interface java files for resource files.
*/
public class ResourceBuilder extends IncrementalProjectBuilder {
static private Logger log = Logger.getLogger( ResourceBuilder.class );
public static final String RESOURCE_BUILD_TMP_FOLDER_HEAD = "resourceBuilder_rc_";
public static final String BUILDER_ID = "net.rim.ejde.internal.builder.BlackBerryResourcesBuilder"; //$NON-NLS-1$
public static final String LOCALE_INTERFACES_FOLDER_NAME = "project_locale_interfaces_folder"; //$NON-NLS-1$
static private String _tmpOutputFolder;
private Hashtable< String, String > _resourceBuilderOptions = null;
private Vector< IFile > _filesNeedToReBuild = new Vector< IFile >();
/**
* (non-javadoc)
*
* @see IncrementalProjectBuilder#build(int, Map, IProgressMonitor)
*/
protected IProject[] build( int kind, Map args, IProgressMonitor monitor ) throws CoreException {
log.trace( "Entering ResourcesBuilder build();kind=" + kind ); //$NON-NLS-1$
IProject project = getProject();
IResourceDelta delta = getDelta( getProject() );
IResourceDelta classpathFile = null;
if( delta != null ) {
classpathFile = delta.findMember( new Path( IConstants.CLASSPATH_FILE_NAME ) );
}
if( kind == IncrementalProjectBuilder.FULL_BUILD || classpathFile != null ) {
ResourceDeltaVisitor resourceVisitor = new ResourceDeltaVisitor( monitor );
project.accept( resourceVisitor );
} else {
_filesNeedToReBuild = getFilesNeedRebuild();
if( delta != null ) {
ResourceDeltaVisitor deltaVisitor = new ResourceDeltaVisitor( monitor );
delta.accept( deltaVisitor );
}
if( _filesNeedToReBuild.size() > 0 ) {
for( IFile file : _filesNeedToReBuild ) {
compile( file, monitor );
}
}
}
log.trace( "Leaving ResourcesBuilder build()" ); //$NON-NLS-1$
return null;
}
static public void cleanTmpDir() {
// synchronizlly delete the tmp folder and files
Display.getDefault().syncExec( new Runnable() {
@Override
public void run() {
cleanupTempSubdir( _tmpOutputFolder );
}
} );
}
/**
* Get rrh/rrc files which need to be rebuilt.
*
* @return
*/
private Vector< IFile > getFilesNeedRebuild() {
Vector< IFile > vector = new Vector< IFile >();
IMarker[] markers;
try {
markers = getProject().findMarkers( IRIMMarker.RESOURCE_BUILD_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE );
for( int i = 0; i < markers.length; i++ ) {
IResource resource = markers[ i ].getResource();
if( resource instanceof IFile
&& ( PackageUtils.hasRRCExtension( resource.getName() ) || PackageUtils.hasRRHExtension( resource
.getName() ) ) ) {
vector.add( (IFile) resource );
}
}
} catch( CoreException e ) {
log.error( e.getMessage() );
}
return vector;
}
protected void clean( IProgressMonitor monitor ) throws CoreException {
removeResourceBuildMarkers( getProject(), IResource.DEPTH_INFINITE );
IFolder tmpFolder = getProject().getFolder( ImportUtils.getImportPref( ResourceBuilder.LOCALE_INTERFACES_FOLDER_NAME ) );
if( !tmpFolder.exists() )
return;
IResource[] members = tmpFolder.members();
for( int i = 0; i < members.length; i++ ) {
if( members[ i ] instanceof IFile ) {
members[ i ].delete( true, monitor );
} else if( members[ i ] instanceof IFolder )
( (IFolder) members[ i ] ).delete( true, false, monitor );
else
throw new CoreException( StatusFactory.createErrorStatus( NLS.bind( "", members[ i ].getName() ) ) ); //$NON-NLS-1$
}
}
/**
* Compiles given <code>iFile</code>. <code>iFIle</code> should be either of rrc or rrh file.
*
* @param resource
* @param monitor
* @throws CoreException
*/
void compile( IResource iFile, IProgressMonitor monitor ) throws CoreException {
log.trace( "ResourcesBuilder compile(IResource); Resource: " //$NON-NLS-1$
+ iFile.getLocation().toOSString() );
// remove the old markers
removeResourceBuildMarkers( iFile, IResource.DEPTH_ONE );
File resourceFile = null;
// get the absolute path of the file
resourceFile = ResourceBuilderUtils.getFile( iFile );
if( resourceFile == null )
log.error( NLS.bind( "", //$NON-NLS-1$
iFile.getLocationURI() ) );
// get the parent directory of the resource file
Vector< String > fileList = new Vector< String >();
try {
ResourceCompiler.compile( resourceFile.getPath(), null, getResourceBuilderOptions(), fileList );
} catch( CompilerException e ) {
log.error( NLS.bind( Messages.RIMResourcesBuilder_COMPILE_FILE_ERROR_MSG, new String[] { e.getMessage() } ) );
createResourceMarker( iFile,
NLS.bind( Messages.RIMResourcesBuilder_COMPILE_FILE_ERROR_MSG, new String[] { e.getMessage() } ), 0,
IMarker.SEVERITY_ERROR );
return;
} catch( IOException e ) {
log.error( NLS.bind( Messages.RIMResourcesBuilder_COMPILE_FILE_ERROR_MSG, new String[] { e.getMessage() } ) );
createResourceMarker( iFile,
NLS.bind( Messages.RIMResourcesBuilder_COMPILE_FILE_ERROR_MSG, new String[] { e.getMessage() } ), 0,
IMarker.SEVERITY_ERROR );
return;
}
// parse the result file list
for( String string : fileList ) {
File processedFile = new File( string );
IFolder tmpParentFolder = getParentFolder( iFile.getRawLocation().toFile(), processedFile, iFile.getProject(),
monitor );
// get the name of the compiled resource file
String fileName = processedFile.getName();
// get the IFile handle of the compiled resource file
IFile file = tmpParentFolder.getFile( fileName );
if( file.exists() )
file.delete( IResource.DERIVED | IResource.FORCE, monitor );
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream( processedFile );
file.create( fileInputStream, IResource.DERIVED | IResource.FORCE, monitor );
} catch( FileNotFoundException e ) {
log.error( e );
continue;
} finally {
try {
if( fileInputStream != null ) {
fileInputStream.close();
}
} catch( IOException e ) {
log.error( e.getMessage() );
}
}
}
}
/**
* Gets the parent IFolder of the given <code>compildedFile</code>.
* <p>
* If the <code>compiledFile</code> is a java file, we return the folder which is .locale_interfaces + package;
* </p>
* <p>
* If the <code>compiledFile</code> is a crb file, we return the folder which is .locale_interfaces because crb files are
* supposed to be put in the root of the jar file.
*
* @param compiledFile
* @param originalFile
* @param project
* @param monitor
* @return
* @throws CoreException
*/
private IFolder getParentFolder( File compiledFile, File originalFile, IProject project, IProgressMonitor monitor )
throws CoreException {
// get the resource builder output root folder
IFolder localeInterfaceFolderRoot = project.getFolder( ImportUtils
.getImportPref( ResourceBuilder.LOCALE_INTERFACES_FOLDER_NAME ) );
// create the .locale_interface folder if it does not exist
ResourceBuilderUtils.createResourcesOutputRoot( project, monitor );
if( PackageUtils.hasRRCExtension( originalFile.getName() ) ) {
return localeInterfaceFolderRoot;
}
// get the package info of the file
String packageName = null;
packageName = PackageUtils.getFilePackageString( originalFile, null );
IFolder localeInterfaceParentFolder;
if( StringUtils.isBlank( packageName ) )
localeInterfaceParentFolder = localeInterfaceFolderRoot;
else {
IPath parentFolderPath = new Path( packageName );
localeInterfaceParentFolder = localeInterfaceFolderRoot.getFolder( parentFolderPath );
}
if( !localeInterfaceParentFolder.exists() )
ImportUtils.createFolders( project, localeInterfaceParentFolder.getProjectRelativePath(), IResource.DERIVED );
if( !localeInterfaceParentFolder.exists() ) {
log.error( NLS.bind( Messages.RIMResourcesBuilder_ResourceInterfaceFolderMissingMessage,
localeInterfaceParentFolder.getProjectRelativePath() ) );
return null;
}
return localeInterfaceParentFolder;
}
/**
* Create a new marker in the specified resource.
*
* @param resource
* @param message
* @param lineNumber
* @param severity
* @throws CoreException
* @throws BadLocationException
*/
void createResourceMarker( IResource resource, String message, int lineNumber, int severity ) {
try {
ResourceBuilderUtils.createProblemMarker( resource, IRIMMarker.RESOURCE_BUILD_PROBLEM_MARKER, message, lineNumber,
severity );
} catch( CoreException e ) {
log.error( e );
}
}
/**
* Clears previous preprocessor markers from the specified resource.
*
* @param resource
* @param depth
*
* @throws CoreException
*/
void removeResourceBuildMarkers( IResource resource, int depth ) throws CoreException {
ResourceBuilderUtils.cleanProblemMarkers( resource, new String[] { IRIMMarker.RESOURCE_BUILD_PROBLEM_MARKER }, depth );
}
private static int removeFiles( File dir ) throws IOException {
int count = 0;
String[] inDir = dir.list();
if( inDir != null ) {
for( int j = 0; j < inDir.length; ++j ) {
File f = new File( dir.getPath(), inDir[ j ] );
if( f.isDirectory() ) {
count += removeFiles( f );
} else {
count += (int) f.length();
if( !f.delete() ) {
throw new IOException();
}
}
}
}
if( !dir.delete() ) {
throw new IOException();
}
return count;
}
private Hashtable< String, String > getResourceBuilderOptions() {
if( _resourceBuilderOptions == null ) {
_resourceBuilderOptions = new Hashtable< String, String >();
_resourceBuilderOptions.put( ResourceCompiler.OPT_OUTPUT_FOLDER, getTempOutputFolder() );
// For BB OS older than 6.0.0, UTF-16BE doesn't work well in localizing strings for Asian simulators based on that OS
}
String vmver = ProjectUtils.getVMVersionForProject( getProject() );
_resourceBuilderOptions.put( ResourceCompiler.OPT_USE_UTF8, vmver.compareTo( "6.0.0" ) < 0 ? ResourceCompiler.OPT_TRUE
: ResourceCompiler.OPT_FALSE );
return _resourceBuilderOptions;
}
static private String getTempOutputFolder() {
if( StringUtils.isBlank( _tmpOutputFolder ) ) {
_tmpOutputFolder = getTemporaryWorkingDir();
}
return _tmpOutputFolder;
}
private static synchronized String getTemporaryWorkingDir() {
String tmpDir = System.getProperty( "java.io.tmpdir" );
if( tmpDir == null ) {
// Shouldn't happen
tmpDir = ".";
}
File subDir = null;
for( int retry = 0; retry < 10; retry++ ) {
subDir = new File( tmpDir, RESOURCE_BUILD_TMP_FOLDER_HEAD + System.currentTimeMillis() + ConvertUtil.ext_dir );
if( !subDir.exists() && subDir.mkdir() ) {
return subDir.toString();
}
}
throw new RuntimeException( "unable to create temporary subdirectory: " + subDir.getPath() );
}
private static void cleanupTempSubdir( String name ) {
if( name == null )
return;
try {
File dir = new File( name );
String[] inDir = dir.list();
if( inDir != null ) {
for( int i = 0; i < inDir.length; ++i ) {
File subDir = new File( dir, inDir[ i ] );
if( subDir.isDirectory() ) {
removeFiles( subDir );
} else {
subDir.delete();
}
}
}
dir.delete();
} catch( IOException ioe ) {
log.error( ioe );
}
}
void removeResourceInterface( IFile rrhFile ) throws CoreException {
if( rrhFile == null )
return;
// get the resource builder output root folder
IProject project = rrhFile.getProject();
IFolder tmpRoot = project.getFolder( ImportUtils.getImportPref( ResourceBuilder.LOCALE_INTERFACES_FOLDER_NAME ) );
IPath interfacePath = getPackagePath( rrhFile );
String rrhFileName = rrhFile.getName();
String interfaceFileName = rrhFileName.substring( 0, rrhFileName.length() - 4 ) + "Resource.java"; //$NON-NLS-1$
interfacePath = interfacePath.append( interfaceFileName );
IFile iFile = tmpRoot.getFile( interfacePath );
if( iFile.exists() ) {
iFile.refreshLocal( IResource.DEPTH_ONE, new NullProgressMonitor() );
iFile.delete( true, new NullProgressMonitor() );
}
}
/**
* Get the package path of the given <code>file</code>, e.g. net/rim/api The given <code>file</code> should be a file in a
* source folder.
*
* @param file
* @return
*/
IPath getPackagePath( IFile file ) {
IContainer sourceFolder = PackageUtils.getSrcFolder( file );
IPath sourceFolderPath = sourceFolder.getProjectRelativePath();
IPath filePath = file.getProjectRelativePath();
IPath packagePath = filePath.removeFirstSegments( sourceFolderPath.segmentCount() );
return packagePath.removeLastSegments( 1 );
}
void removeCorrespondingCRBFile( IFile rrcFile ) throws CoreException {
if( rrcFile == null )
return;
IFile crbFile = getCorrespondingCRBFile( rrcFile );
if( crbFile != null && crbFile.exists() ) {
crbFile.delete( true, new NullProgressMonitor() );
}
}
IFile getCorrespondingCRBFile( IFile rrcFile ) {
if( rrcFile == null )
return null;
// get the resource builder output root folder
IProject project = rrcFile.getProject();
IFolder tmpRoot = project.getFolder( ImportUtils.getImportPref( ResourceBuilder.LOCALE_INTERFACES_FOLDER_NAME ) );
IPath packagePath = getPackagePath( rrcFile );
String fileName = rrcFile.getName().replace( IConstants.RRC_FILE_EXTENSION_WITH_DOT, IConstants.EMPTY_STRING );
String packageID = PackageUtils.convertPkgStringToID( packagePath.toString() );
packageID = packageID.replace( File.separator, IConstants.DOT_MARK );
String crbFileName = PackageUtils.getCRBFileName( fileName, packageID, false );
IFile crbFile = tmpRoot.getFile( crbFileName );
String crbAltFileName;
if( !crbFile.exists() ) {
if( !( crbAltFileName = PackageUtils.getCRBFileName( fileName, packageID, true ) ).equals( crbFileName ) ) {
crbFile = tmpRoot.getFile( crbAltFileName );
if( !crbFile.exists() ) {
return null;
}
} else {
return null;
}
}
return crbFile;
}
/**
* Checks if the given <code>file</code> is in the right package structure.
*
* @param file
* @return
*/
private boolean hasValidPackage( IFile file ) {
try {
String packageID = PackageUtils.getFilePackageString( file.getLocation().toFile(), null );
IPath packagePath = new Path( packageID );
IPath currentPackagePath = getPackagePath( file );
if( currentPackagePath.equals( packagePath ) ) {
return true;
}
String message = Messages.ResourceBuilder_WRONG_PACKAGE_MSG;
createResourceMarker( file, NLS.bind( message, file.getName() ), -1, IMarker.SEVERITY_ERROR );
return false;
} catch( CoreException e ) {
log.error( e.getMessage() );
createResourceMarker( file, e.getMessage(), -1, IMarker.SEVERITY_ERROR );
return false;
}
}
/**
* Delta visitor for visiting changed resource files for preprocessing.
*/
private class ResourceDeltaVisitor extends BasicBuilderResourceDeltaVisitor {
public ResourceDeltaVisitor( IProgressMonitor monitor ) {
super( monitor );
}
protected void buildResource( IResource resource, IProgressMonitor monitor ) throws CoreException {
if( PackageUtils.hasRRHExtension( resource.getName() ) ) {
removeResourceInterface( (IFile) resource );
} else {
removeCorrespondingCRBFile( (IFile) resource );
}
compile( resource, monitor );
// remove the file from the need rebuild file list
if( _filesNeedToReBuild != null ) {
_filesNeedToReBuild.remove( resource );
}
}
@Override
protected void removeResource( IResource resource, IProgressMonitor monitor ) throws CoreException {
if( PackageUtils.hasRRHExtension( resource.getName() ) ) {
removeResourceInterface( (IFile) resource );
} else if( PackageUtils.hasRRCExtension( resource.getName() ) ) {
removeCorrespondingCRBFile( (IFile) resource );
}
// remove the file from the need rebuild file list
if( _filesNeedToReBuild != null ) {
_filesNeedToReBuild.remove( resource );
}
}
@Override
protected boolean needBuild( IResource resource ) {
if( resource == null ) {
return false;
}
if( !resource.exists() ) {
return false;
}
if( !PackageUtils.hasRRHExtension( resource.getName() ) && !PackageUtils.hasRRCExtension( resource.getName() ) ) {
return false;
}
if( !hasValidPackage( (IFile) resource ) ) {
return false;
}
// we do not process a resource which is not on the classpath
if( !JavaCore.create( resource.getProject() ).isOnClasspath( resource ) ) {
try {
if( PackageUtils.hasRRHExtension( resource.getName() ) ) {
removeResourceInterface( (IFile) resource );
} else {
removeCorrespondingCRBFile( (IFile) resource );
}
} catch( CoreException e ) {
log.error( "needBuild Error: " + e.getMessage() );
}
return false;
}
return true;
}
}
}