net.rim.ejde.internal.builders.PreprocessingBuilder.java Source code

Java tutorial

Introduction

Here is the source code for net.rim.ejde.internal.builders.PreprocessingBuilder.java

Source

/*
* 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." //$NON-NLS-1$
                            : " not need to be built by java compiler.")); //$NON-NLS-1$
            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;
        }
    }
}