Package org.bndtools.builder.classpath

Source Code of org.bndtools.builder.classpath.BndContainerInitializer

package org.bndtools.builder.classpath;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.jar.Attributes.Name;
import java.util.jar.Manifest;

import org.bndtools.api.BndtoolsConstants;
import org.bndtools.api.ILogger;
import org.bndtools.api.Logger;
import org.bndtools.api.ModelListener;
import org.bndtools.utils.jar.JarUtils;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.ClasspathContainerInitializer;
import org.eclipse.jdt.core.IAccessRule;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.osgi.framework.Constants;

import aQute.bnd.build.CircularDependencyException;
import aQute.bnd.build.Container;
import aQute.bnd.build.Container.TYPE;
import aQute.bnd.build.Project;
import aQute.bnd.build.Workspace;
import aQute.bnd.header.Parameters;
import bndtools.central.Central;
import bndtools.central.RefreshFileJob;

/**
* A bnd container reads the bnd.bnd file in the project directory and use the information in there to establish the
* classpath. The classpath is defined by the -build-env instruction. This instruction contains a list of bsn's that are
* searched in the available repositories and returned as File objects. This initializer establishes the link between
* the container object and the BndModel. The container object is just a delegator because for some unknown reasons, you
* can only update the container (refresh the contents) when you give it a new object ;-( Because this plugin uses the
* Bnd Builder in different places, the Bnd Model is centralized and available from the Activator.
*/
public class BndContainerInitializer extends ClasspathContainerInitializer implements ModelListener {
    private static final ILogger logger = Logger.getLogger(BndContainerInitializer.class);

    static final IClasspathEntry[] EMPTY_ENTRIES = new IClasspathEntry[0];

    public BndContainerInitializer() {
        Central.getInstance().addModelListener(this);
    }

    @Override
    public void initialize(IPath containerPath, final IJavaProject project) throws CoreException {
        final ArrayList<String> errors = new ArrayList<String>();
        calculateAndUpdateClasspathEntries(project, errors);

        WorkspaceJob replaceMarkersJob = new WorkspaceJob("Update bnd classpath markers") {
            @Override
            public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
                replaceClasspathProblemMarkers(project.getProject(), errors);
                return Status.OK_STATUS;
            }
        };
        replaceMarkersJob.setRule(project.getProject());
        replaceMarkersJob.schedule();
    }

    @Override
    public boolean canUpdateClasspathContainer(IPath containerPath, IJavaProject project) {
        return true;
    }

    @Override
    // The suggested classpath container is ignored here; always recalculated
    // from the project.
    public void requestClasspathContainerUpdate(IPath containerPath, IJavaProject project, IClasspathContainer containerSuggestion) throws CoreException {
        BndContainerSourceManager.saveAttachedSources(project, (containerSuggestion == null) ? null : Arrays.asList(containerSuggestion.getClasspathEntries()));

        ArrayList<String> errors = new ArrayList<String>();
        calculateAndUpdateClasspathEntries(project, errors);
        replaceClasspathProblemMarkers(project.getProject(), errors);
    }

    public static boolean resetClasspaths(Project model, IProject project, Collection<String> errors) throws CoreException {
        IJavaProject javaProject = JavaCore.create(project);
        return resetClasspaths(model, javaProject, errors);
    }

    public static boolean resetClasspaths(Project model, IJavaProject javaProject, Collection<String> errors) throws CoreException {
        IClasspathContainer container = JavaCore.getClasspathContainer(BndtoolsConstants.BND_CLASSPATH_ID, javaProject);
        List<IClasspathEntry> currentClasspath = Arrays.asList(container.getClasspathEntries());

        List<IClasspathEntry> newClasspath = BndContainerInitializer.calculateProjectClasspath(model, javaProject, errors);
        newClasspath = BndContainerSourceManager.loadAttachedSources(javaProject, newClasspath);

        replaceClasspathProblemMarkers(javaProject.getProject(), errors);

        if (!newClasspath.equals(currentClasspath)) {
            BndContainerInitializer.setClasspathEntries(javaProject, newClasspath.toArray(new IClasspathEntry[newClasspath.size()]));
            return true;
        }
        return false;
    }

    private static void calculateAndUpdateClasspathEntries(IJavaProject project, Collection< ? super String> errors) throws CoreException {
        IClasspathEntry[] entries = new IClasspathEntry[0];
        Project model;
        try {
            model = Central.getProject(project.getProject().getLocation().toFile());
        } catch (Exception e) {
            // Abort quickly if there is no Bnd workspace
            setClasspathEntries(project, entries);
            return;
        }

        try {
            if (model != null) {
                List<IClasspathEntry> classpath = calculateProjectClasspath(model, project, errors);
                if (classpath != null) {
                    classpath = BndContainerSourceManager.loadAttachedSources(project, classpath);
                    entries = classpath.toArray(new IClasspathEntry[classpath.size()]);
                }
            }
            setClasspathEntries(project, entries);
        } catch (Exception e) {
            logger.logError("Error requesting bnd classpath update.", e);
        }
    }

    @Override
    public void modelChanged(Project model) throws Exception {
        IJavaProject project = Central.getJavaProject(model);
        if (model == null || project == null) {
            System.out.println("Help! No IJavaProject for " + model);
        } else {
            requestClasspathContainerUpdate(BndtoolsConstants.BND_CLASSPATH_ID, project, null);
        }
    }

    @SuppressWarnings("unused")
    public static void workspaceChanged(Workspace ws) throws Exception {
        System.out.println("Workspace changed");
    }

    public static void setClasspathEntries(IJavaProject javaProject, IClasspathEntry[] entries) throws JavaModelException {
        JavaCore.setClasspathContainer(BndtoolsConstants.BND_CLASSPATH_ID, new IJavaProject[] {
            javaProject
        }, new IClasspathContainer[] {
            new BndContainer(javaProject, entries, null)
        }, null);
    }

    public static List<IClasspathEntry> calculateProjectClasspath(Project model, IJavaProject javaProject, Collection< ? super String> errors) throws CoreException {
        IProject project = javaProject.getProject();
        if (!project.exists() || !project.isOpen())
            return null;

        if (model == null) {
            setClasspathEntries(javaProject, EMPTY_ENTRIES);
            errors.add("bnd workspace is not configured.");
            return null;
        }

        model.clear();

        Collection<Container> buildPath;
        Collection<Container> testPath;
        Collection<Container> bootClasspath;
        List<Container> containers;

        try {
            buildPath = model.getBuildpath();
            testPath = model.getTestpath();
            bootClasspath = model.getBootclasspath();

            containers = new ArrayList<Container>(buildPath.size() + testPath.size() + bootClasspath.size());
            containers.addAll(buildPath);

            // The first file is always the project directory,
            // Eclipse already includes that for us.
            if (containers.size() > 0) {
                containers.remove(0);
            }
            containers.addAll(testPath);
            containers.addAll(bootClasspath);
        } catch (CircularDependencyException e) {
            errors.add("Circular dependency: " + e.getMessage());
            containers = Collections.emptyList();
        } catch (Exception e) {
            errors.add("Unexpected error during classpath calculation: " + e);
            containers = Collections.emptyList();
        }

        ArrayList<IClasspathEntry> result = new ArrayList<IClasspathEntry>(containers.size());
        LinkedHashMap<Project,List<IAccessRule>> projectAccessRules = new LinkedHashMap<Project,List<IAccessRule>>();
        for (Container c : containers) {
            if (c.getType() == TYPE.PROJECT && c.getError() == null) {
                calculateWorkspaceBundleAccessRules(projectAccessRules, c);
            }
        }

        for (Container c : containers) {
            IClasspathEntry cpe;

            if (c.getError() == null) {
                File file = c.getFile();
                assert file.isAbsolute();

                if (!file.exists()) {
                    switch (c.getType()) {
                    case REPO :
                        errors.add("Repository file " + c.getFile() + " does not exist");
                        break;
                    case LIBRARY :
                        errors.add("Library file " + c.getFile() + " does not exist");
                        break;
                    case PROJECT :
                        errors.add("Project bundle " + c.getFile() + " does not exist");
                        break;
                    case EXTERNAL :
                        errors.add("External file " + c.getFile() + " does not exist");
                        break;
                    default :
                        break;
                    }
                }

                IPath p = null;
                try {
                    p = fileToPath(file);
                } catch (Exception e) {
                    errors.add(String.format("Failed to convert file %s to Eclipse path: %s: %s", file, e.getClass().getName(), e.getMessage()));
                }
                if (p != null) {
                    IClasspathAttribute[] extraAttrs = calculateExtraClasspathAttrs(c);

                    if (c.getType() == Container.TYPE.PROJECT) {
                        IResource resource = ResourcesPlugin.getWorkspace().getRoot().getFile(p);
                        List<IAccessRule> rules = projectAccessRules.get(c.getProject());
                        IAccessRule[] accessRules = null;
                        if (rules != null) {
                            rules.add(JavaCore.newAccessRule(new Path("**"), IAccessRule.K_NON_ACCESSIBLE));
                            accessRules = rules.toArray(new IAccessRule[rules.size()]);
                        }
                        cpe = JavaCore.newProjectEntry(resource.getProject().getFullPath(), accessRules, false, extraAttrs, true);
                    } else {
                        IAccessRule[] accessRules = calculateRepoBundleAccessRules(c);
                        cpe = JavaCore.newLibraryEntry(p, null, null, accessRules, extraAttrs, false);
                    }
                    result.add(cpe);
                }
            } else {
                errors.add(c.getError());
            }
        }

        errors.addAll(model.getErrors());

        return result;
    }

    // TODO: this is a workaround for bug #89 in bnd
    static boolean isProjectContainer(Container container) {
        return container.getType() == TYPE.PROJECT && !container.getFile().isFile();
    }

    static IClasspathAttribute[] calculateExtraClasspathAttrs(Container c) {
        List<IClasspathAttribute> attrs = new ArrayList<IClasspathAttribute>();
        attrs.add(JavaCore.newClasspathAttribute("bsn", c.getBundleSymbolicName()));

        String version = c.getAttributes().get("version");

        if (version != null) {
            attrs.add(JavaCore.newClasspathAttribute("version", version));
        }

        return attrs.toArray(new IClasspathAttribute[0]);
    }

    static IAccessRule[] calculateRepoBundleAccessRules(Container c) {
        String packageList = c.getAttributes().get("packages");
        if (packageList != null) {
            List<IAccessRule> tmp = new LinkedList<IAccessRule>();
            StringTokenizer tokenizer = new StringTokenizer(packageList, ",");
            while (tokenizer.hasMoreTokens()) {
                String token = tokenizer.nextToken();
                String pathStr = token.replace('.', '/') + "/*";
                tmp.add(JavaCore.newAccessRule(new Path(pathStr), IAccessRule.K_ACCESSIBLE));
            }
            tmp.add(JavaCore.newAccessRule(new Path("**"), IAccessRule.K_NON_ACCESSIBLE));
            return tmp.toArray(new IAccessRule[tmp.size()]);
        }
        return null;
    }

    static void calculateWorkspaceBundleAccessRules(Map<Project,List<IAccessRule>> projectAccessRules, Container c) {
        String packageList = c.getAttributes().get("packages");
        if (packageList != null) {
            List<IAccessRule> tmp = new LinkedList<IAccessRule>();
            StringTokenizer tokenizer = new StringTokenizer(packageList, ",");
            while (tokenizer.hasMoreTokens()) {
                String token = tokenizer.nextToken();
                String pathStr = token.replace('.', '/') + "/*";
                tmp.add(JavaCore.newAccessRule(new Path(pathStr), IAccessRule.K_ACCESSIBLE));
            }
            addAccessRules(projectAccessRules, c.getProject(), tmp);
        } else if (isProjectContainer(c)) {
            // No access rules please.
            addAccessRules(projectAccessRules, c.getProject(), null);
        } else if (c.getType() == TYPE.PROJECT) {
            Manifest mf = null;
            try {
                mf = JarUtils.loadJarManifest(new FileInputStream(c.getFile()));
            } catch (IOException e) {
                logger.logError("Unable to generate access rules from bundle " + c.getFile(), e);
                return;
            }
            Parameters exportPkgs = new Parameters(mf.getMainAttributes().getValue(new Name(Constants.EXPORT_PACKAGE)));
            List<IAccessRule> tmp = new LinkedList<IAccessRule>();
            for (String exportPkg : exportPkgs.keySet()) {
                String pathStr = exportPkg.replace('.', '/') + "/*";
                tmp.add(JavaCore.newAccessRule(new Path(pathStr), IAccessRule.K_ACCESSIBLE));
            }
            addAccessRules(projectAccessRules, c.getProject(), tmp);
        }
    }

    static void addAccessRules(Map<Project,List<IAccessRule>> projectAccessRules, Project project, List<IAccessRule> accessRules) {
        if (projectAccessRules.containsKey(project)) {
            List<IAccessRule> currentAccessRules = projectAccessRules.get(project);

            if (currentAccessRules != null) {
                if (accessRules == null)
                    projectAccessRules.put(project, null);
                else
                    currentAccessRules.addAll(accessRules);
            }
        } else {
            projectAccessRules.put(project, accessRules);
        }
    }

    /*
     * static void replaceClasspathProblemMarkers(final IProject project, final Collection<String> errors, boolean
     * submitMarkerJob) throws CoreException{ final IFile bndFile = project.getFile(Project.BNDFILE); if (bndFile ==
     * null) { return; } if (submitMarkerJob) { Job markerJob = new Job("Bndtools: Resolving markers") {
     * @Override protected IStatus run(IProgressMonitor monitor) { try { replaceMarkersJob(bndFile, project, errors); }
     * catch (CoreException e) { return e.getStatus(); } return Status.OK_STATUS; } }; markerJob.setRule(project);
     * markerJob.setRule(bndFile); markerJob.setPriority(Job.BUILD); markerJob.schedule(); } else {
     * replaceMarkersJob(bndFile, project, errors); } }
     */

    public static void replaceClasspathProblemMarkers(IProject project, Collection<String> errors) throws CoreException {
        assert project != null;

        if (!project.exists() || !project.isOpen()) {
            logger.logError(String.format("Cannot replace bnd classpath problem markers: project %s is not in the Eclipse workspace or is not open.", project.getName()), null);
            return;
        }

        IResource resource = project.getFile(Project.BNDFILE);
        if (resource == null || !resource.exists())
            resource = project;

        project.deleteMarkers(BndtoolsConstants.MARKER_BND_CLASSPATH_PROBLEM, true, IResource.DEPTH_INFINITE);
        for (String error : errors) {
            IMarker marker = resource.createMarker(BndtoolsConstants.MARKER_BND_CLASSPATH_PROBLEM);
            marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
            marker.setAttribute(IMarker.MESSAGE, error);
        }
    }

    protected static IPath fileToPath(File file) throws Exception {
        IPath path = Central.toPath(file);
        if (path == null)
            path = Path.fromOSString(file.getAbsolutePath());

        RefreshFileJob refreshJob = new RefreshFileJob(file, false);
        if (refreshJob.needsToSchedule())
            refreshJob.schedule(100);

        return path;
    }
}
TOP

Related Classes of org.bndtools.builder.classpath.BndContainerInitializer

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.