/*
* 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.IOException;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import net.rim.ejde.external.sourceMapper.SourceMapperAccess;
import net.rim.ejde.internal.core.ContextManager;
import net.rim.ejde.internal.core.IConstants;
import net.rim.ejde.internal.core.IRIMMarker;
import net.rim.ejde.internal.model.BasicBlackBerryProperties.PreprocessorTag;
import net.rim.ejde.internal.model.BlackBerryProject;
import net.rim.ejde.internal.model.BlackBerryProperties;
import net.rim.ejde.internal.model.preferences.PreprocessorPreferences;
import net.rim.ejde.internal.util.EnvVarUtils;
import net.rim.ejde.internal.util.ImportUtils;
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.ejde.internal.util.VMUtils;
import net.rim.ide.RIA;
import net.rim.ide.Workspace;
import net.rim.tools.javapp.delegate.IOutputFileCallbackDelegate;
import net.rim.tools.javapp.delegate.IPreprocessingListenerDelegate;
import net.rim.tools.javapp.delegate.JavaPPDelegate;
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.core.runtime.QualifiedName;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.osgi.util.NLS;
/**
* An incremental builder implementation that provides preprocess support as defined for the Antenna Ant tools.<br/>
*/
public class PreprocessingBuilder extends IncrementalProjectBuilder {
public static final String BUILDER_ID = "net.rim.ejde.internal.builder.BlackBerryPreprocessBuilder"; //$NON-NLS-1$
public static final String PREPROCESSED_FILE_FOLDER_NAME = "project_preprocessed_folder"; //$NON-NLS-1$
private static final Logger _log = Logger.getLogger( PreprocessingBuilder.class );
final static public QualifiedName NOT_BUILD_BY_JAVA_BUILDER_FLAG_QUALIFIED_NAME = new QualifiedName(
ContextManager.PLUGIN_ID, "NotBuiltByJavaBuilders" ); //$NON-NLS-1$
private JavaPPDelegate _javaPP = new JavaPPDelegate();
private IFolder _preprocessedFolder;
/**
* Construct a new builder instance.
*/
public PreprocessingBuilder() {
super();
}
/**
* @see org.eclipse.core.resources.IncrementalProjectBuilder#build(int, java.util.Map,
* org.eclipse.core.runtime.IProgressMonitor)
*/
protected IProject[] build( int kind, Map args, IProgressMonitor monitor ) throws CoreException {
_log.trace( "Entering PreprocessingBuilder build();kind=" + kind ); //$NON-NLS-1$
IProject project = getProject();
_preprocessedFolder = createPreprocessedFolder( getProject(), new NullProgressMonitor() );
// fixed MKS516466, after importing an existing project, the .locale_interface and .preprocessed folder are not marked as
// derived. we should mark them as derived.
if( !_preprocessedFolder.isDerived() ) {
try {
_preprocessedFolder.setDerived( true );
} catch( CoreException e ) {
_log.error( e );
}
}
IFolder localeInterfaceFolderRoot = project.getProject().getFolder(
ImportUtils.getImportPref( ResourceBuilder.LOCALE_INTERFACES_FOLDER_NAME ) );
if( localeInterfaceFolderRoot.exists() && !localeInterfaceFolderRoot.isDerived() ) {
try {
localeInterfaceFolderRoot.setDerived( true );
} catch( CoreException e ) {
_log.error( e );
}
}
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 ) {
// if is is a full build, we build all java files
ResourceDeltaVisitor resourceVisitor = new ResourceDeltaVisitor( monitor );
project.accept( resourceVisitor );
} else {
// if it is not a full build, we only build changed java files
if( delta != null ) {
ResourceDeltaVisitor deltaVisitor = new ResourceDeltaVisitor( monitor );
delta.accept( deltaVisitor );
}
}
_preprocessedFolder.refreshLocal( IResource.DEPTH_INFINITE, monitor );
_log.trace( "Leaving PreprocessingBuilder build()" ); //$NON-NLS-1$
return null;
}
/**
* @see org.eclipse.core.resources.IncrementalProjectBuilder#clean(org.eclipse.core.runtime.IProgressMonitor)
*/
protected void clean( IProgressMonitor monitor ) throws CoreException {
removePreprocessingMarkers( getProject(), IResource.DEPTH_INFINITE );
IFolder preprocessedFolder = getProject().getFolder( ImportUtils.getImportPref( PREPROCESSED_FILE_FOLDER_NAME ) );
if( !preprocessedFolder.exists() )
return;
IResource[] members = preprocessedFolder.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$
}
}
/**
* Return an appropriate output file for the specified resource. This file is not guaranteed to exist.
*
* @param resource
* @return
*/
public static IFile getOutputFile( IResource resource ) {
IContainer sourceFolder = PackageUtils.getSrcFolder( resource );
if( sourceFolder == null ) {
return (IFile) resource;
}
IPath sourceFolderPath = sourceFolder.getProjectRelativePath();
IPath resourcePath = resource.getProjectRelativePath();
IPath firstSegment = resourcePath.removeLastSegments( resourcePath.segmentCount() - 1 );
IPath projectRelativePath = null;
if( firstSegment.toString().equals( ImportUtils.getImportPref( PREPROCESSED_FILE_FOLDER_NAME ) ) )
return (IFile) resource;
projectRelativePath = new Path( ImportUtils.getImportPref( PREPROCESSED_FILE_FOLDER_NAME ) ).append( resource
.getProjectRelativePath().removeFirstSegments( sourceFolderPath.segmentCount() ) );
return resource.getProject().getFile( projectRelativePath );
}
void deletePreprocessedFile( IFile file, IProgressMonitor monitor ) throws CoreException {
IFile preprocessedFile = getOutputFile( file );
// eclipse resource might not be refreshed, we need double check the filesystem
if( preprocessedFile.exists() )
preprocessedFile.delete( true, monitor );
}
/**
* Clears previous preprocessor markers from the specified resource.
*
* @param resource
* @param depth
*
* @throws CoreException
*/
private void removePreprocessingMarkers( IResource resource, int depth ) throws CoreException {
ResourceBuilderUtils.cleanProblemMarkers( resource, new String[] { IRIMMarker.PREPROCESSING_PROBLEM_MARKER }, depth );
}
/**
* Mark the given <code>resource</code> as need to be built by java compiler.
*
* @param resource
* @param needBuild
* <code>true</code> mark the given <code>resource</code> as need build, otherwise, mark the given
* <code>resource</code> as not need build.
*/
static synchronized public void setShouldBuiltByJavaBuilder( IResource resource, boolean needBuild ) {
try {
_log.trace( "File " + resource.getName() + " is marked as" //$NON-NLS-1$ //$NON-NLS-2$
+ ( needBuild ? " need to be built by java compiler." : " not need to be built by java compiler." ) ); //$NON-NLS-1$ //$NON-NLS-2$
String newBuildFlag = needBuild ? "true" : "false";
String oldBuildFlag = resource.getPersistentProperty( NOT_BUILD_BY_JAVA_BUILDER_FLAG_QUALIFIED_NAME );
if( ( oldBuildFlag == null ) || !oldBuildFlag.equals( newBuildFlag ) ) {
resource.setPersistentProperty( NOT_BUILD_BY_JAVA_BUILDER_FLAG_QUALIFIED_NAME, newBuildFlag );
}
} catch( CoreException e ) {
_log.error( e );
}
}
/**
* Check if the given <code>resource</code> needs to be built.
*
* @param file
* @return
*/
static synchronized public boolean shouldBuiltByJavaBuilder( IResource resource ) {
try {
String value = resource.getPersistentProperty( NOT_BUILD_BY_JAVA_BUILDER_FLAG_QUALIFIED_NAME );
if( ( value == null ) || value.equals( "true" ) ) {
return true;
}
} catch( CoreException e ) {
_log.error( e );
return true;
}
return false;
}
/**
* Preprocess the specified java resource.
*
* @param resource
* @param symbols
* @param monitor
* @throws CoreException
*/
private void preprocessResource( final IResource resource, IProgressMonitor monitor ) throws CoreException {
// remove the old markers and preprocessed file
removePreprocessingMarkers( resource, IResource.DEPTH_ONE );
deletePreprocessedFile( (IFile) resource, monitor );
//
BlackBerryProperties properties = ContextManager.PLUGIN.getBBProperties( getProject().getName(), false );
if( properties == null ) {
_log.error( "Could not find the correspond BlackBerry properties." );
return;
}
// get defined directives
Vector< String > defines = getDefines( new BlackBerryProject( JavaCore.create( getProject() ), properties ), true );
// if there is no directive defined, we do not do preprocessing
if( defines == null || defines.size() == 0 )
return;
// check preprocess hook
if( !SourceMapperAccess.isHookCodeInstalled() && PreprocessorPreferences.getPopForPreprocessHookMissing() ) {
_log.error( "Preprocessing hook was not installed." ); //$NON-NLS-1$
ProjectUtils.setPreprocessorHook();
return;
} else {
_log.trace( "Preprocessing file : " + resource.getLocation() ); //$NON-NLS-1$
}
// remove the fake preprocess derive
defines.remove( Workspace.getDefineOptNull() );
// get java file
File javaFile = resource.getLocation().toFile();
Vector< File > javaFiles = new Vector< File >();
javaFiles.add( javaFile );
IPreprocessingListenerDelegate listener = new PreprocessingListener( resource );
IOutputFileCallbackDelegate callback = new OutputFileCallback();
_javaPP.setPreprocessingListener( listener );
_javaPP.setOutputFileCallback( callback );
try {
_javaPP.preProcess( javaFiles, defines, _preprocessedFolder.getLocation().toFile() );
resource.touch( monitor );
} catch( IOException e ) {
// Is handled by PreprocessingListener
} catch( Exception e ) {
try {
ResourceBuilderUtils.createProblemMarker( resource, IRIMMarker.PREPROCESSING_PROBLEM_MARKER, e.getMessage(), -1,
IMarker.SEVERITY_ERROR );
} catch( CoreException e1 ) {
_log.error( e1 );
}
// if we got any exception, delete the proprocessed file.
_log.debug( e.getMessage(), e );
deletePreprocessedFile( (IFile) resource, monitor );
}
setShouldBuiltByJavaBuilder( resource, javaFiles.size() != 0 );
}
/**
* Get all preprocess defines: JRE leve, workspace level and project level.
*
* @param BBProject
* @param ignoreInActive
* @return
*/
static public Vector< String > getDefines( BlackBerryProject BBProject, boolean ignoreInActive ) {
List< PreprocessorTag > workspaceDefines = PreprocessorPreferences.getPreprocessDefines();
PreprocessorTag[] projectDefines = BBProject.getProperties()._compile.getPreprocessorDefines();
Vector< String > newDefines = new Vector< String >();
for( PreprocessorTag ppDefine : workspaceDefines ) {
if( !ignoreInActive || ppDefine.isActive() ) {
newDefines.add( EnvVarUtils.replaceRIAEnvVars( ppDefine.getPreprocessorDefine() ) );
}
}
for( int i = 0; i < projectDefines.length; i++ ) {
if( !ignoreInActive || projectDefines[ i ].isActive() ) {
newDefines.add( EnvVarUtils.replaceRIAEnvVars( projectDefines[ i ].getPreprocessorDefine() ) );
}
}
String cpDefine = VMUtils.getJREDirective( BBProject );
if( !StringUtils.isBlank( cpDefine ) ) {
newDefines.add( cpDefine );
}
return newDefines;
}
private IFolder createPreprocessedFolder( IProject project, IProgressMonitor monitor ) throws CoreException {
IContainer sourceContainer = project.getFolder( ImportUtils.getImportPref( PREPROCESSED_FILE_FOLDER_NAME ) );
// refresh the project to reveal the .preprocessed folder
sourceContainer.refreshLocal( IResource.DEPTH_ZERO, monitor );
if( !sourceContainer.exists() ) {
// if the .BlackBerryPreprocessed folder does not exist, create it
( (IFolder) sourceContainer ).create( IResource.DERIVED, true, monitor );
}
return (IFolder) sourceContainer;
}
/**
* 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.PREPROCESSING_PROBLEM_MARKER, message, lineNumber,
severity );
} catch( Exception e ) {
_log.error( e );
}
}
protected class PreprocessingListener implements IPreprocessingListenerDelegate {
IResource _resource;
public PreprocessingListener( IResource resource ) {
_resource = resource;
}
public void setResource( IResource resource ) {
_resource = resource;
}
public void error( String message, int lineNumber ) {
createResourceMarker( _resource, message, lineNumber, IMarker.SEVERITY_ERROR );
}
public void warning( String message, int lineNumber ) {
createResourceMarker( _resource, message, lineNumber, IMarker.SEVERITY_WARNING );
}
};
protected class OutputFileCallback implements IOutputFileCallbackDelegate {
public File getOutputFile( File parent, File javaFile ) {
String packageName = RIA.getPackage( javaFile );
if( packageName != null && !packageName.trim().equals( IConstants.EMPTY_STRING ) ) {
IPath parentPath = new Path( parent.getPath() );
IPath packagePath = new Path( packageName.replace( '.', '/' ) );
IFolder packageFolder = _preprocessedFolder.getFolder( packagePath );
if( !packageFolder.exists() )
try {
ImportUtils.createFolders( getProject(), packageFolder.getProjectRelativePath(), IResource.DERIVED );
} catch( CoreException e ) {
_log.error( e );
}
parentPath = parentPath.append( packagePath );
File parentDir = parentPath.toFile();
return new File( parentDir, javaFile.getName() );
}
return new File( parent, javaFile.getName() );
}
}
/**
* Delta visitor for visiting changed resource files for resource builder.
*/
private class ResourceDeltaVisitor extends BasicBuilderResourceDeltaVisitor {
public ResourceDeltaVisitor( IProgressMonitor monitor ) {
super( monitor );
}
@Override
protected void removeResource( IResource resource, IProgressMonitor monitor ) throws CoreException {
if( !( resource instanceof IFile ) )
return;
deletePreprocessedFile( (IFile) resource, monitor );
}
@Override
protected void buildResource( IResource resource, IProgressMonitor monitor ) throws CoreException {
preprocessResource( resource, monitor );
}
/**
* Checks if the given <code>resource</code> is a java file which needs to be proprocessed.
*
* @param resource
* @return
*/
protected boolean needBuild( IResource resource ) {
if( resource == null ) {
return false;
}
if( !resource.exists() ) {
return false;
}
// check if it's an IFile
if( !( resource instanceof IFile ) )
return false;
// check if it's a java file
if( !PackageUtils.hasJavaExtension( resource.getName() ) ) {
return false;
}
// we do not process a resource which is not on the classpath
if( !JavaCore.create( resource.getProject() ).isOnClasspath( resource ) ) {
try {
deletePreprocessedFile( (IFile) resource, new NullProgressMonitor() );
} catch( CoreException e ) {
_log.error( e.getMessage() );
}
return false;
}
return true;
}
}
}