Package org.bndtools.builder

Source Code of org.bndtools.builder.NewBuilder$ProjectDeltaVisitor

package org.bndtools.builder;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.concurrent.atomic.AtomicBoolean;

import org.bndtools.api.BndtoolsConstants;
import org.bndtools.api.ILogger;
import org.bndtools.api.IValidator;
import org.bndtools.api.Logger;
import org.bndtools.build.api.BuildErrorDetailsHandler;
import org.bndtools.build.api.BuildErrorDetailsHandlers;
import org.bndtools.build.api.DefaultBuildErrorDetailsHandler;
import org.bndtools.build.api.MarkerData;
import org.bndtools.builder.classpath.BndContainerInitializer;
import org.bndtools.utils.Predicate;
import org.bndtools.utils.swt.SWTConcurrencyUtil;
import org.bndtools.utils.workspace.FileUtils;
import org.bndtools.utils.workspace.WorkspaceUtils;
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.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaModelMarker;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
import org.osgi.framework.Version;

import aQute.bnd.build.Project;
import aQute.bnd.build.Workspace;
import aQute.bnd.header.Attrs;
import aQute.bnd.osgi.Builder;
import aQute.bnd.osgi.Descriptors.PackageRef;
import aQute.bnd.service.RepositoryListenerPlugin;
import aQute.lib.collections.SortedList;
import aQute.lib.io.IO;
import aQute.service.reporter.Report.Location;
import bndtools.central.Central;
import bndtools.preferences.BndPreferences;
import bndtools.preferences.CompileErrorAction;
import bndtools.preferences.EclipseClasspathPreference;

public class NewBuilder extends IncrementalProjectBuilder {

    public static final String PLUGIN_ID = "bndtools.builder";
    public static final String BUILDER_ID = BndtoolsConstants.BUILDER_ID;

    private static final ILogger logger = Logger.getLogger(NewBuilder.class);

    private static final int LOG_FULL = 2;
    private static final int LOG_BASIC = 1;
    private static final int LOG_NONE = 0;

    private Project model;
    private BuildListeners listeners;
    private Collection< ? extends Builder> subBuilders;

    private List<String> classpathErrors;
    private MultiStatus validationResults;

    private List<String> buildLog;
    private int logLevel = LOG_NONE;
    private ScopedPreferenceStore projectPrefs;

    private int nrFilesBuilt = 0;

    @Override
    protected IProject[] build(int kind, @SuppressWarnings("rawtypes") Map args, IProgressMonitor monitor) throws CoreException {
        BndPreferences prefs = new BndPreferences();
        logLevel = prefs.getBuildLogging();
        projectPrefs = new ScopedPreferenceStore(new ProjectScope(getProject()), BndtoolsConstants.CORE_PLUGIN_ID);

        // Prepare validations
        classpathErrors = new LinkedList<String>();
        validationResults = new MultiStatus(PLUGIN_ID, 0, "Validation errors in bnd project", null);
        buildLog = new ArrayList<String>(5);

        nrFilesBuilt = 0;

        try {
            // Prepare build listeners and build error handlers
            listeners = new BuildListeners();

            // Get the initial project
            IProject myProject = getProject();
            reportDelta(myProject);

            listeners.fireBuildStarting(myProject);
            Project model = null;
            try {
                model = Central.getProject(myProject.getLocation().toFile());
            } catch (Exception e) {
                clearBuildMarkers();
                addBuildMarkers(e.getMessage(), IMarker.SEVERITY_ERROR);
            }
            if (model == null)
                return null;
            this.model = model;
            model.setDelayRunDependencies(true);

            // Main build section
            IProject[] dependsOn = calculateDependsOn(model);

            // Clear errors and warnings
            model.clear();

            // CASE 1: CNF changed
            if (isCnfChanged()) {
                log(LOG_BASIC, "cnf project changed");
                model.refresh();
                if (BndContainerInitializer.resetClasspaths(model, myProject, classpathErrors)) {
                    log(LOG_BASIC, "classpaths were changed");
                } else {
                    log(LOG_FULL, "classpaths did not need to change");
                }

                // Notify the repository listeners that ALL repo contents may have changed.
                List<RepositoryListenerPlugin> repoListeners = Central.getWorkspace().getPlugins(RepositoryListenerPlugin.class);
                for (RepositoryListenerPlugin repoListener : repoListeners) {
                    repoListener.repositoriesRefreshed();
                }

                return dependsOn;
            }

            // CASE 2: local Bnd file changed, or Eclipse asks for full build
            boolean localChange = false;
            if (kind == FULL_BUILD) {
                localChange = true;
                log(LOG_BASIC, "Eclipse requested full build");
            } else if (isLocalBndFileChange()) {
                localChange = true;
                log(LOG_BASIC, "local bnd files changed");
            }
            if (localChange) {
                model.refresh();
                if (BndContainerInitializer.resetClasspaths(model, myProject, classpathErrors)) {
                    log(LOG_BASIC, "classpaths were changed");
                    return dependsOn;
                }
                log(LOG_FULL, "classpaths were not changed");
                this.subBuilders = model.getSubBuilders();
                rebuildIfLocalChanges(dependsOn, true);
                return dependsOn;
            }
            // (NB: from now on the delta cannot be null, due to the check in
            // isLocalBndFileChange)

            // CASE 3: JAR file in dependency project changed
            Project changedDependency = getDependencyTargetChange();
            if (changedDependency != null) {
                log(LOG_BASIC, "target files in dependency project %s changed", changedDependency.getName());
                model.propertiesChanged();
                if (BndContainerInitializer.resetClasspaths(model, myProject, classpathErrors)) {
                    log(LOG_BASIC, "classpaths were changed");
                    return dependsOn;
                }
                log(LOG_FULL, "classpaths were not changed");
            }

            // CASE 4: local file changes
            this.subBuilders = model.getSubBuilders();
            rebuildIfLocalChanges(dependsOn, false);

            return dependsOn;
        } catch (Exception e) {
            throw new CoreException(new Status(IStatus.ERROR, PLUGIN_ID, 0, "Build Error!", e));
        } finally {
            listeners.release();
            if (!buildLog.isEmpty() && logLevel > 0) {
                StringBuilder builder = new StringBuilder();
                builder.append(String.format("BUILD LOG %2s %s", (nrFilesBuilt > 0 ? nrFilesBuilt : ""), getProject()));
                for (String message : buildLog) {
                    builder.append("\n -> ").append(message);
                }
                logger.logInfo(builder.toString(), null);
            }

            model = null;
            subBuilders = null;
        }
    }

    /*
     * Report the full delta in the log
     */
    private void reportDelta(IProject project) throws CoreException {
        IResourceDelta delta = getDelta(project);
        if (delta == null) {
            log(LOG_FULL, "No delta, is null");
            return;
        }

        delta.accept(new IResourceDeltaVisitor() {

            @Override
            public boolean visit(IResourceDelta delta) throws CoreException {
                log(LOG_FULL, delta.toString() + " " + delta.getResource().getModificationStamp() + " " + delta.getKind());
                return true;
            }
        });

    }

    @Override
    protected void clean(IProgressMonitor monitor) throws CoreException {
        try {
            IProject myProject = getProject();
            Project model = Central.getProject(myProject.getLocation().toFile());
            if (model == null)
                return;

            // Delete everything in the target directory
            File target = model.getTarget();
            if (target.isDirectory() && target.getParentFile() != null) {
                IO.delete(target);
                if (!target.exists() && !target.mkdirs()) {
                    throw new IOException("Could not create directory " + target);
                }
            }

            // Tell Eclipse what we did...
            IFolder targetFolder = myProject.getFolder(calculateTargetDirPath(model));
            targetFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
        } catch (Exception e) {
            throw new CoreException(new Status(IStatus.ERROR, PLUGIN_ID, 0, "Build Error!", e));
        }
    }

    boolean isCnfChanged() throws Exception {
        IProject cnfProject = WorkspaceUtils.findCnfProject();
        if (cnfProject == null) {
            logger.logError("Bnd configuration project (cnf) is not available in the Eclipse workspace.", null);
            return false;
        }

        IResourceDelta cnfDelta = getDelta(cnfProject);
        if (cnfDelta == null) {
            log(LOG_FULL, "no delta available for cnf project, ignoring");
            return false;
        }

        final AtomicBoolean result = new AtomicBoolean(false);
        cnfDelta.accept(new IResourceDeltaVisitor() {
            @Override
            public boolean visit(IResourceDelta delta) throws CoreException {
                if (!isChangeDelta(delta))
                    return false;

                if (IResourceDelta.MARKERS == delta.getFlags())
                    return false;

                IResource resource = delta.getResource();
                if (resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT)
                    return true;

                if (resource.getType() == IResource.FOLDER && resource.getName().equals("ext")) {
                    log(LOG_FULL, "detected change in cnf due to resource %s, kind=0x%x, flags=0x%x", resource.getFullPath(), delta.getKind(), delta.getFlags());
                    result.set(true);
                }

                if (resource.getType() == IResource.FILE) {
                    if (Workspace.BUILDFILE.equals(resource.getName())) {
                        result.set(true);
                        log(LOG_FULL, "detected change in cnf due to resource %s, kind=0x%x, flags=0x%x", resource.getFullPath(), delta.getKind(), delta.getFlags());
                    } else {
                        // Check files included by the -include directive in build.bnd
                        List<File> includedFiles = model.getWorkspace().getIncluded();
                        if (includedFiles == null) {
                            return false;
                        }
                        for (File includedFile : includedFiles) {
                            IPath location = resource.getLocation();
                            if (location != null && includedFile.equals(location.toFile())) {
                                result.set(true);
                                return false;
                            }
                        }
                    }
                }

                return false;
            }
        });

        return result.get();
    }

    private boolean isLocalBndFileChange() throws CoreException {
        IResourceDelta myDelta = getDelta(getProject());
        if (myDelta == null) {
            log(LOG_BASIC, "local project delta is null, assuming changes exist", model.getName());
            return true;
        }

        final AtomicBoolean result = new AtomicBoolean(false);
        myDelta.accept(new IResourceDeltaVisitor() {
            @Override
            public boolean visit(IResourceDelta delta) throws CoreException {
                if (!isChangeDelta(delta))
                    return false;

                IResource resource = delta.getResource();

                if (resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT)
                    return true;

                if (resource.getType() == IResource.FOLDER)
                    return true;

                String extension = ((IFile) resource).getFileExtension();
                if (resource.getType() == IResource.FILE && "bnd".equalsIgnoreCase(extension)) {
                    log(LOG_FULL, "detected change due to resource %s, kind=0x%x, flags=0x%x", resource.getFullPath(), delta.getKind(), delta.getFlags());
                    result.set(true);
                    return false;
                }

                if (resource.getType() == IResource.FILE) {
                    // Check files included by the -include directive in bnd.bnd
                    List<File> includedFiles = model.getIncluded();
                    if (includedFiles == null) {
                        return false;
                    }
                    for (File includedFile : includedFiles) {
                        IPath location = resource.getLocation();
                        if (location != null && includedFile.equals(location.toFile())) {
                            result.set(true);
                            return false;
                        }
                    }
                }

                return false;
            }

        });

        return result.get();
    }

    private Project getDependencyTargetChange() throws Exception {
        IWorkspaceRoot wsroot = ResourcesPlugin.getWorkspace().getRoot();
        Collection<Project> dependson = model.getDependson();
        log(LOG_FULL, "project depends on: %s", dependson);

        for (Project dep : dependson) {
            File targetDir = dep.getTarget();
            // Does not exist... was it deleted?
            if (targetDir == null || !(targetDir.isDirectory()))
                return dep;

            IProject project = WorkspaceUtils.findOpenProject(wsroot, dep);
            if (project == null) {
                logger.logWarning(String.format("Dependency project '%s' from project '%s' is not in the Eclipse workspace.", dep.getName(), model.getName()), null);
                return null;
            }

            IFile buildFile = project.getFolder(targetDir.getName()).getFile(Workspace.BUILDFILES);
            IPath buildFilePath = buildFile.getProjectRelativePath();
            IResourceDelta delta = getDelta(project);

            if (delta == null) {
                // May have changed
                log(LOG_FULL, "null delta in dependency project %s", dep.getName());
                return dep;
            } else if (!isChangeDelta(delta)) {
                continue;
            } else {
                IResourceDelta buildFileDelta = delta.findMember(buildFilePath);
                if (buildFileDelta != null && isChangeDelta(buildFileDelta)) {
                    log(LOG_FULL, "detected change due to file %s, kind=0x%x, flags=0x%x", buildFile, delta.getKind(), delta.getFlags());
                    return dep;
                }
            }
            // this dependency project did not change, move on to next
        }

        // no dependencies changed
        return null;
    }

    /**
     * @return Whether any files were built
     */
    private boolean rebuildIfLocalChanges(IProject[] dependsOn, boolean forceBuild) throws Exception {
        log(LOG_FULL, "calculating local changes...");

        final Set<File> changedFiles = new HashSet<File>();

        final Set<File> targetJars = findJarsInTarget();

        boolean force = forceBuild;
        IResourceDelta delta;

        ProjectDeltaVisitor deltaVisitor = new ProjectDeltaVisitor(getProject(), changedFiles);

        // Get delta on local project
        delta = getDelta(getProject());
        if (delta != null) {
            delta.accept(deltaVisitor);

            log(LOG_FULL, "%d files in local project (outside target) changed or removed: %s, forced=%s", changedFiles.size(), changedFiles, deltaVisitor.force);

            if (deltaVisitor.force || changedFiles.size() > 0) {
                log(LOG_FULL, "Project changed: files=%s, force = %s", changedFiles, deltaVisitor.force);
                force = true;
            }
        } else {
            log(LOG_BASIC, "no info on local changes, doing a full build");
            force = true;
        }

        if (!force) {
            // Get deltas on dependency projects
            for (IProject depProject : dependsOn) {
                delta = getDelta(depProject);
                if (delta != null) {
                    Set<File> changedByProject = new HashSet<File>();
                    ProjectDeltaVisitor depVisitor = new ProjectDeltaVisitor(depProject, changedByProject);
                    delta.accept(depVisitor);

                    changedFiles.addAll(changedByProject);

                    //
                    // If the visitor detected a project that we depend on
                    // and that has a change in its output folder then we
                    // must rebuild. no use checking any further
                    //

                    if (depVisitor.force) {
                        log(LOG_FULL, "Project %s, which we depend on has changed its output, forcing rebuild, files changed are %s", depProject, changedByProject);
                        force = true;
                        break;
                    }

                    log(LOG_FULL, "%d files in dependency project '%s' changed or removed: %s", changedFiles.size(), depProject.getName(), changedByProject);

                } else {
                    log(LOG_BASIC, "no info available on changes from project '%s'", depProject.getName());
                }
            }
        } else
            log(LOG_FULL, "Ignoring project dependencies because we already decided to build");

        // Process the sub-builders to determine whether a rebuild, force
        // rebuild, or nothing is required.
        if (!model.isNoBundles())
            for (Builder builder : subBuilders) {
                // If the builder's output JAR has been removed, this could be
                // because the user
                // deleted it, so we should force build in order to regenerate
                // it.
                File targetFile = new File(model.getTarget(), builder.getBsn() + ".jar");
                if (!targetFile.isFile()) {
                    log(LOG_FULL, "output file %s of builder %s was removed, will force a rebuild", targetFile, builder.getBsn());
                    force = true;
                    break;
                }

                // Account for this builder's target JAR
                targetJars.remove(targetFile);

                // Finally if any removed or changed files are in scope for the
                // bundle, we simply force rebuild
                if (!changedFiles.isEmpty()) {
                    if (changedFiles.contains(builder.getPropertiesFile())) {
                        log(LOG_FULL, "the properties file for builder %s was changes, will force a rebuild", builder.getBsn());
                        force = true;
                        break;
                    } else if (builder.isInScope(changedFiles)) {
                        log(LOG_FULL, "some removed files were in scope for builder %s, will force a rebuild", builder.getBsn());
                        force = true;
                        break;
                    }
                }
            }

        // Delete any unaccounted-for Jars from target dir
        for (File jar : targetJars) {
            try {
                jar.delete();
            } catch (Exception e) {
                logger.logError("Error deleting target JAR: " + jar, e);
            }
        }

        // Remove files not in scope

        if (!force && !changedFiles.isEmpty() && !model.isNoBundles()) {
            for (Iterator<File> itr = changedFiles.iterator(); itr.hasNext();) {
                File changeFile = itr.next();
                boolean inScope = false;
                for (Builder builder : subBuilders) {
                    if (builder.isInScope(Collections.singleton(changeFile))) {
                        inScope = true;
                        break;
                    }
                }
                if (!inScope) {
                    itr.remove();
                    log(LOG_FULL, "removed file %s, was not in scope for project %s", changeFile, getProject().getName());
                }
            }
        }

        // Do it
        boolean builtAny = false;
        if (force) {
            builtAny = rebuild(true);
        } else if (!changedFiles.isEmpty()) {
            builtAny = rebuild(false);
        }
        return builtAny;
    }

    private Set<File> findJarsInTarget() throws Exception {
        File targetDir = model.getTarget();
        File[] targetJars = targetDir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.getName().toLowerCase().endsWith(".jar");
            }
        });
        Set<File> result = new HashSet<File>();
        if (targetJars != null)
            for (File jar : targetJars) {
                result.add(jar);
            }
        return result;
    }

    private static IPath calculateTargetDirPath(Project model) throws Exception {
        IPath basePath = Path.fromOSString(model.getBase().getAbsolutePath());
        final IPath targetDirPath = Path.fromOSString(model.getTarget().getAbsolutePath()).makeRelativeTo(basePath);
        return targetDirPath;
    }

    private enum Action {
        build, delete
    }

    /**
     * @param force
     *            Whether to force bnd to build
     * @return Whether any files were built
     */
    @SuppressWarnings("unchecked")
    private boolean rebuild(boolean force) throws Exception {
        clearBuildMarkers();

        // Check if compilation errors exist, and if so check the project
        // settings for what to do about that...
        Action buildAction = Action.build;
        if (hasBlockingErrors()) {
            ScopedPreferenceStore store = new ScopedPreferenceStore(new ProjectScope(getProject()), BndtoolsConstants.CORE_PLUGIN_ID);
            switch (CompileErrorAction.parse(store.getString(CompileErrorAction.PREFERENCE_KEY))) {
            case skip :
            default :
                addBuildMarkers(String.format("Will not build OSGi bundle(s) for project %s until compilation problems are fixed.", model.getName()), IMarker.SEVERITY_ERROR);
                log(LOG_BASIC, "SKIPPING due to Java problem markers");
                return false;
            case build :
                buildAction = Action.build;
                break;
            case delete :
                buildAction = Action.delete;
                break;
            }
        } else if (!classpathErrors.isEmpty()) {
            ScopedPreferenceStore store = new ScopedPreferenceStore(new ProjectScope(getProject()), BndtoolsConstants.CORE_PLUGIN_ID);
            switch (CompileErrorAction.parse(store.getString(CompileErrorAction.PREFERENCE_KEY))) {
            case skip :
            default :
                addBuildMarkers(String.format("Will not build OSGi bundle(s) for project %s until classpath resolution problems are fixed.", model.getName()), IMarker.SEVERITY_ERROR);
                log(LOG_BASIC, "SKIPPING due to classpath resolution problem markers");
                return false;
            case build :
                buildAction = Action.build;
                break;
            case delete :
                buildAction = Action.delete;
                break;
            }
        }

        File[] built;

        // Clear errors & warnings before build
        model.clear();

        // Validate
        List<IValidator> validators = loadValidators();
        if (validators != null) {
            for (Builder builder : subBuilders) {
                validate(builder, validators);
            }
        }

        // Load Eclipse classpath containers
        model.clearClasspath();
        EclipseClasspathPreference classpathPref = EclipseClasspathPreference.parse(projectPrefs.getString(EclipseClasspathPreference.PREFERENCE_KEY));
        if (classpathPref == EclipseClasspathPreference.expose) {
            List<File> classpathFiles = new ArrayList<File>(20);
            accumulateClasspath(classpathFiles, JavaCore.create(getProject()), false, new ClasspathContainerFilter());
            for (File file : classpathFiles) {
                log(LOG_FULL, "Adding Eclipse classpath entry %s", file.getAbsolutePath());
                model.addClasspath(file);
            }
        }

        if (buildAction == Action.build) {
            // Build!
            model.setTrace(true);
            boolean stale = model.isStale();

            if (force || stale) {
                log(LOG_BASIC, "REBUILDING: force=%b; stale=%b", force, stale);
                long now = System.currentTimeMillis();
                built = model.buildLocal(false);
                if (built == null)
                    built = new File[0]; // shouldn't happen but just in case

                nrFilesBuilt += built.length;
                log(LOG_BASIC, "Build took %s secs", (System.currentTimeMillis() - now) / 1000);
                decorate();
            } else {
                log(LOG_BASIC, "NOT REBUILDING: force=%b;stale=%b", force, stale);
                built = new File[0];
            }

            // Notify the build listeners
            if (listeners != null && built.length > 0) {
                IPath[] paths = new IPath[built.length];
                for (int i = 0; i < built.length; i++)
                    paths[i] = Central.toPath(built[i]);
                listeners.fireBuiltBundles(getProject(), paths);
            }

            // Log rebuilt files
            log(LOG_BASIC, "%d files were rebuilt", built.length);
            if (logLevel >= LOG_FULL) {
                for (File builtFile : built) {
                    log(LOG_FULL, "target file %s has an age of %d ms", builtFile, System.currentTimeMillis() - builtFile.lastModified());
                }
            }
        } else {
            // Delete target files since the project has compile errors and the
            // delete action was selected.
            for (Builder builder : subBuilders) {
                File targetFile = new File(model.getTarget(), builder.getBsn() + ".jar");
                boolean deleted = targetFile.delete();
                log(LOG_FULL, "deleted target file %s (%b)", targetFile, deleted);
            }
            built = new File[0];
        }

        // Notify central that there are new bundles
        if (built.length > 0)
            Central.invalidateIndex();

        // Make sure Eclipse knows about the changed files (should already have
        // been done?)
        IFolder targetFolder = getProject().getFolder(calculateTargetDirPath(model));
        targetFolder.refreshLocal(IResource.DEPTH_INFINITE, null);

        // Report errors
        createBuildMarkers(model);

        return built.length > 0;
    }

    static List<IValidator> loadValidators() {
        List<IValidator> validators = null;
        IConfigurationElement[] validatorElems = Platform.getExtensionRegistry().getConfigurationElementsFor(BndtoolsConstants.CORE_PLUGIN_ID, "validators");
        if (validatorElems != null && validatorElems.length > 0) {
            validators = new ArrayList<IValidator>(validatorElems.length);
            for (IConfigurationElement elem : validatorElems) {
                try {
                    validators.add((IValidator) elem.createExecutableExtension("class"));
                } catch (Exception e) {
                    logger.logError("Unable to instantiate validator: " + elem.getAttribute("name"), e);
                }
            }
        }
        return validators;
    }

    void validate(Builder builder, List<IValidator> validators) {
        for (IValidator validator : validators) {
            IStatus status = validator.validate(builder);
            if (!status.isOK())
                validationResults.add(status);
        }
    }

    private IProject[] calculateDependsOn(Project model) throws Exception {
        Collection<Project> dependsOn = model.getDependson();
        List<IProject> result = new ArrayList<IProject>(dependsOn.size() + 1);

        IProject cnfProject = WorkspaceUtils.findCnfProject();
        if (cnfProject != null)
            result.add(cnfProject);

        IWorkspaceRoot wsroot = ResourcesPlugin.getWorkspace().getRoot();
        for (Project project : dependsOn) {
            IProject targetProj = WorkspaceUtils.findOpenProject(wsroot, project);
            if (targetProj == null)
                logger.logWarning("No open project in workspace for Bnd '-dependson' dependency: " + project.getName(), null);
            else
                result.add(targetProj);
        }

        log(LOG_FULL, "Calculated depends-on list: %s", result);
        return result.toArray(new IProject[result.size()]);
    }

    private void accumulateClasspath(List<File> files, IJavaProject project, boolean exports, Predicate<IClasspathContainer>... containerFilters) throws JavaModelException {
        if (exports) {
            IPath outputPath = project.getOutputLocation();
            files.add(getFileForPath(outputPath));
        }

        IClasspathEntry[] entries = project.getRawClasspath();
        List<IClasspathEntry> queue = new ArrayList<IClasspathEntry>(entries.length);
        queue.addAll(Arrays.asList(entries));

        while (!queue.isEmpty()) {
            IClasspathEntry entry = queue.remove(0);

            if (exports && !entry.isExported())
                continue;

            IPath path = entry.getPath();

            switch (entry.getEntryKind()) {
            case IClasspathEntry.CPE_LIBRARY :
                files.add(getFileForPath(path));
                break;
            case IClasspathEntry.CPE_VARIABLE :
                IPath resolvedPath = JavaCore.getResolvedVariablePath(path);
                files.add(getFileForPath(resolvedPath));
                break;
            case IClasspathEntry.CPE_SOURCE :
                IPath outputLocation = entry.getOutputLocation();
                if (exports && outputLocation != null)
                    files.add(getFileForPath(outputLocation));
                break;
            case IClasspathEntry.CPE_CONTAINER :
                IClasspathContainer container = JavaCore.getClasspathContainer(path, project);
                boolean allow = true;
                for (Predicate<IClasspathContainer> filter : containerFilters)
                    if (!filter.select(container))
                        allow = false;
                if (allow)
                    queue.addAll(Arrays.asList(container.getClasspathEntries()));
                break;
            case IClasspathEntry.CPE_PROJECT :
                IProject targetProject = ResourcesPlugin.getWorkspace().getRoot().getProject(path.lastSegment());
                IJavaProject targetJavaProject = JavaCore.create(targetProject);
                accumulateClasspath(files, targetJavaProject, true, containerFilters);
                break;
            default :
                logger.logError("Unhandled IPath entryKind of " + entry.getEntryKind(), null);
                break;
            }
        }
    }

    private static File getFileForPath(IPath path) {
        File file;
        IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
        if (resource != null && resource.exists())
            file = resource.getLocation().toFile();
        else
            file = path.toFile();
        return file;
    }

    private boolean hasBlockingErrors() {
        try {
            if (containsError(getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE)))
                return true;
            return false;
        } catch (CoreException e) {
            logger.logError("Error looking for project problem markers", e);
            return false;
        }
    }

    private static boolean containsError(IMarker[] markers) {
        if (markers != null)
            for (IMarker marker : markers) {
                int severity = marker.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
                if (severity == IMarker.SEVERITY_ERROR)
                    return true;
            }
        return false;
    }

    private static boolean isChangeDelta(IResourceDelta delta) {
        if (IResourceDelta.MARKERS == delta.getFlags())
            return false;
        if ((delta.getKind() & (IResourceDelta.ADDED | IResourceDelta.CHANGED | IResourceDelta.REMOVED)) == 0)
            return false;
        return true;
    }

    private void createBuildMarkers(Project model) throws Exception {
        List<String> errors = model.getErrors();
        List<String> warnings = model.getWarnings();

        for (String error : errors) {
            addBuildMarkers(error, IMarker.SEVERITY_ERROR);
        }
        for (String warning : warnings) {
            addBuildMarkers(warning, IMarker.SEVERITY_WARNING);
        }
        for (String error : classpathErrors) {
            addClasspathMarker(error, IMarker.SEVERITY_ERROR);
        }

        if (!validationResults.isOK()) {
            for (IStatus status : validationResults.getChildren()) {
                addBuildMarkers(status);
            }
        }
    }

    private void clearBuildMarkers() throws CoreException {
        getProject().deleteMarkers(BndtoolsConstants.MARKER_BND_PROBLEM, true, IResource.DEPTH_INFINITE);
    }

    private void addBuildMarkers(IStatus status) throws Exception {
        if (status.isMultiStatus()) {
            for (IStatus child : status.getChildren()) {
                addBuildMarkers(child);
            }
            return;
        }

        addBuildMarkers(status.getMessage(), iStatusSeverityToIMarkerSeverity(status));
    }

    private void addBuildMarkers(String message, int severity) throws Exception {
        Location location = model != null ? model.getLocation(message) : null;
        if (location != null) {
            String type = location.details != null ? location.details.getClass().getName() : null;
            BuildErrorDetailsHandler handler = BuildErrorDetailsHandlers.INSTANCE.findHandler(type);

            List<MarkerData> markers = handler.generateMarkerData(getProject(), model, location);
            for (MarkerData markerData : markers) {
                IResource resource = markerData.getResource();
                //
                // TODO pkr: Just fixed an NPE java.lang.NullPointerException
                //                at org.bndtools.builder.NewBuilder.addBuildMarkers(NewBuilder.java:822)
                //                at org.bndtools.builder.NewBuilder.createBuildMarkers(NewBuilder.java:783)
                //                at org.bndtools.builder.NewBuilder.rebuild(NewBuilder.java:637)
                //                at org.bndtools.builder.NewBuilder.rebuildIfLocalChanges(NewBuilder.java:481)
                //                at org.bndtools.builder.NewBuilder.build(NewBuilder.java:184)
                //                at org.eclipse.core.internal.events.BuildManager$2.run(BuildManager.java:734)
                //                at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:42)
                //                at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:206)
                //                at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:246)
                //                at org.eclipse.core.internal.events.BuildManager$1.run(BuildManager.java:299)
                //                at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:42)
                //                at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:302)
                //                at org.eclipse.core.internal.events.BuildManager.basicBuildLoop(BuildManager.java:358)
                //                at org.eclipse.core.internal.events.BuildManager.build(BuildManager.java:381)
                //                at org.eclipse.core.internal.events.AutoBuildJob.doBuild(AutoBuildJob.java:143)
                //                at org.eclipse.core.internal.events.AutoBuildJob.run(AutoBuildJob.java:241)
                //                at org.eclipse.core.internal.jobs.Worker.run(Worker.java:54)
                if (resource != null) {
                    IMarker marker = resource.createMarker(BndtoolsConstants.MARKER_BND_PROBLEM);
                    marker.setAttribute(IMarker.SEVERITY, severity);
                    marker.setAttribute("$bndType", type);
                    marker.setAttribute(BuildErrorDetailsHandler.PROP_HAS_RESOLUTIONS, markerData.hasResolutions());
                    for (Entry<String,Object> attrib : markerData.getAttribs().entrySet())
                        marker.setAttribute(attrib.getKey(), attrib.getValue());
                }
            }
        } else {
            IMarker marker = DefaultBuildErrorDetailsHandler.getDefaultResource(getProject()).createMarker(BndtoolsConstants.MARKER_BND_PROBLEM);
            marker.setAttribute(IMarker.SEVERITY, severity);
            marker.setAttribute(IMarker.MESSAGE, message);
        }
    }

    private void addClasspathMarker(String message, int severity) throws CoreException {
        IResource resource = DefaultBuildErrorDetailsHandler.getDefaultResource(getProject());

        IMarker marker = resource.createMarker(BndtoolsConstants.MARKER_BND_CLASSPATH_PROBLEM);
        marker.setAttribute(IMarker.SEVERITY, severity);
        marker.setAttribute(IMarker.MESSAGE, message);
    }

    private int iStatusSeverityToIMarkerSeverity(IStatus status) {
        int severity;
        switch (status.getSeverity()) {
        case IStatus.CANCEL :
        case IStatus.ERROR :
            severity = IMarker.SEVERITY_ERROR;
            break;
        case IStatus.WARNING :
            severity = IMarker.SEVERITY_WARNING;
            break;
        default :
            severity = IMarker.SEVERITY_INFO;
        }

        return severity;
    }

    //    private void addClasspathMarker(IStatus status) throws CoreException {
    //        if (status.isMultiStatus()) {
    //            for (IStatus child : status.getChildren()) {
    //                addClasspathMarker(child);
    //            }
    //            return;
    //        }
    //
    //        addClasspathMarker(status.getMessage(), iStatusSeverityToIMarkerSeverity(status));
    //    }

    private void log(int level, String message, Object... args) {
        if (logLevel >= level)
            buildLog.add(String.format(message, args));
    }

    private static class ProjectDeltaVisitor implements IResourceDeltaVisitor {

        final Project model;
        final Set<File> changedFiles;
        final IPath targetDirFullPath;
        final IPath bin;

        boolean force = false;

        ProjectDeltaVisitor(final IProject project, final Set<File> changedFiles) throws Exception {
            this.changedFiles = changedFiles;
            this.model = Central.getProject(project.getLocation().toFile());
            if (this.model == null) {
                this.targetDirFullPath = null;
                this.bin = null;
                return;
            }

            this.targetDirFullPath = project.getFullPath().append(calculateTargetDirPath(model));
            this.bin = targetDirFullPath.removeLastSegments(1).append(model.getOutput().getName());
        }

        @Override
        public boolean visit(IResourceDelta delta) throws CoreException {
            if (targetDirFullPath == null) {
                return false;
            }
            if (!isChangeDelta(delta))
                return false;

            IResource resource = delta.getResource();
            if (resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT)
                return true;

            if (resource.getType() == IResource.FOLDER) {
                IPath folderPath = resource.getFullPath();

                // #860

                if (folderPath.equals(bin)) {
                    //
                    // Any changes in bin should result in a rebuild of any dependent
                    // project.
                    // TODO We could of course optimize to see if this represents
                    // an exported package?
                    //
                    force = true;
                    return false;
                }
                // ignore ALL files in target dir
                return !folderPath.equals(targetDirFullPath);
            }

            if (resource.getType() == IResource.FILE) {
                File file = resource.getLocation().toFile();
                changedFiles.add(file);
            }

            return false;
        }
    }

    /*
     * We can now decorate based on the build we just did.
     */
    private void decorate() throws Exception {

        //
        // Calculate the source path resources
        //

        File projectBaseFile = getProject().getLocation().toFile().getAbsoluteFile();
        Collection<File> modelSourcePaths = model.getSourcePath();
        Collection<IResource> modelSourcePathsResources = null;
        if (modelSourcePaths != null && !modelSourcePaths.isEmpty()) {
            modelSourcePathsResources = new HashSet<IResource>();
            for (File modelSourcePath : modelSourcePaths) {
                if (projectBaseFile.equals(modelSourcePath.getAbsoluteFile())) {
                    continue;
                }
                IResource modelSourcePathResource = FileUtils.toProjectResource(getProject(), modelSourcePath);
                if (modelSourcePathResource != null) {
                    modelSourcePathsResources.add(modelSourcePathResource);
                }
            }
        }

        //
        // Gobble up the information for exports and contained
        //

        Map<String,SortedSet<Version>> allExports = new HashMap<String,SortedSet<Version>>();
        Set<String> allContained = new HashSet<String>();

        //
        // First the exports
        //

        for (Map.Entry<PackageRef,Attrs> entry : model.getExports().entrySet()) {
            String v = entry.getValue().getVersion();
            Version version = v == null ? Version.emptyVersion : new Version(v);
            allExports.put(entry.getKey().getFQN(), new SortedList<Version>(version));
        }

        for (Map.Entry<PackageRef,Attrs> entry : model.getContained().entrySet()) {
            allContained.add(entry.getKey().getFQN());
        }

        Central.setProjectPackageModel(getProject(), allExports, allContained, modelSourcePathsResources);

        Display display = PlatformUI.getWorkbench().getDisplay();
        SWTConcurrencyUtil.execForDisplay(display, true, new Runnable() {
            @Override
            public void run() {
                PlatformUI.getWorkbench().getDecoratorManager().update("bndtools.packageDecorator");
            }
        });
    }

}
TOP

Related Classes of org.bndtools.builder.NewBuilder$ProjectDeltaVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.