/* * Copyright (c) 2016 the original author or authors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.buildship.core.workspace.internal; import java.io.File; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import com.google.common.base.Optional; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.gradleware.tooling.toolingmodel.OmniEclipseClasspathContainer; import com.gradleware.tooling.toolingmodel.OmniJavaSourceSettings; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.IAccessRule; import org.eclipse.jdt.core.IClasspathAttribute; 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.eclipse.jdt.launching.IVMInstall; import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jdt.launching.environments.IExecutionEnvironment; import org.eclipse.buildship.core.util.classpath.ClasspathUtils; import org.eclipse.buildship.core.workspace.GradleClasspathContainer; /** * Updates the classpath containers of the target project. * <p/> * The update is triggered via {@link #update(IJavaProject, Optional, IProgressMonitor)}. The method * executes synchronously and unprotected, without thread synchronization or job scheduling. * <p/> * If the connected Gradle version supports containers, all user-defined ones will be overwritten. If Gradle * does not support containers, only the JRE will be updated. The Gradle classpath container will be added to the end * of the container list in all cases. * * Containers are added after the last source folder entry. * <p/> * The Gradle classpath container is always configured on the project. * * @author Donat Csikos */ final class ClasspathContainerUpdater { private static final IPath DEFAULT_JRE_PATH = JavaRuntime.newDefaultJREContainerPath(); private final IJavaProject project; private final boolean gradleSupportsContainers; private final List<OmniEclipseClasspathContainer> containers; private final OmniJavaSourceSettings sourceSettings; private ClasspathContainerUpdater(IJavaProject project, Optional<List<OmniEclipseClasspathContainer>> containers, OmniJavaSourceSettings sourceSettings) { this.project = project; this.gradleSupportsContainers = containers.isPresent(); this.containers = containers.or(Collections.<OmniEclipseClasspathContainer> emptyList()); this.sourceSettings = sourceSettings; } private void updateContainers(IProgressMonitor monitor) throws CoreException { List<IClasspathEntry> classpath = Lists.newArrayList(this.project.getRawClasspath()); updateContainers(classpath); this.project.setRawClasspath(classpath.toArray(new IClasspathEntry[classpath.size()]), monitor); } private void updateContainers(List<IClasspathEntry> classpath) throws JavaModelException { if (this.gradleSupportsContainers) { overWriteContainers(classpath); } else { updateJre(classpath); } } private void overWriteContainers(List<IClasspathEntry> classpath) { removeOldContainers(classpath); LinkedHashMap<IPath, IClasspathEntry> containersToAdd = Maps.newLinkedHashMap(); for (OmniEclipseClasspathContainer container : this.containers) { IClasspathEntry entry = createContainerEntry(container); containersToAdd.put(entry.getPath(), entry); } ensureGradleContainerIsPresent(containersToAdd); classpath.addAll(indexOfNewContainers(classpath), containersToAdd.values()); } private void updateJre(List<IClasspathEntry> classpath) { Map<IPath, IClasspathEntry> oldContainers = removeOldContainers(classpath); LinkedHashMap<IPath, IClasspathEntry> containersToAdd = Maps.newLinkedHashMap(); IClasspathEntry jreEntry = createContainerEntry(getJrePathFromSourceSettings()); containersToAdd.put(jreEntry.getPath(), jreEntry); containersToAdd.putAll(oldContainers); ensureGradleContainerIsPresent(containersToAdd); classpath.addAll(indexOfNewContainers(classpath), containersToAdd.values()); } private Map<IPath, IClasspathEntry> removeOldContainers(List<IClasspathEntry> classpath) { Map<IPath, IClasspathEntry> retainedEntries = Maps.newLinkedHashMap(); ListIterator<IClasspathEntry> iterator = classpath.listIterator(); while (iterator.hasNext()) { IClasspathEntry entry = iterator.next(); if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { if (shouldRetainContainer(entry)) { retainedEntries.put(entry.getPath(), entry); } iterator.remove(); } } return retainedEntries; } private boolean shouldRetainContainer(IClasspathEntry entry) { return !DEFAULT_JRE_PATH.isPrefixOf(entry.getPath()); } private void ensureGradleContainerIsPresent(LinkedHashMap<IPath, IClasspathEntry> containersToAdd) { if (!containersToAdd.containsKey(GradleClasspathContainer.CONTAINER_PATH)) { containersToAdd.put(GradleClasspathContainer.CONTAINER_PATH, createContainerEntry(GradleClasspathContainer.CONTAINER_PATH)); } } private IPath getJrePathFromSourceSettings() { String targetVersion = this.sourceSettings.getTargetBytecodeLevel().getName(); File vmLocation = this.sourceSettings.getTargetRuntime().getHomeDirectory(); IVMInstall vm = EclipseVmUtil.findOrRegisterStandardVM(targetVersion, vmLocation); Optional<IExecutionEnvironment> executionEnvironment = EclipseVmUtil.findExecutionEnvironment(targetVersion); return executionEnvironment.isPresent() ? JavaRuntime.newJREContainerPath(executionEnvironment.get()) : JavaRuntime.newJREContainerPath(vm); } private int indexOfNewContainers(List<IClasspathEntry> classpath) { int index = 0; for (int i = 0; i < classpath.size(); i++) { if (classpath.get(i).getEntryKind() == IClasspathEntry.CPE_SOURCE) { index = i + 1; } } return index; } private static IClasspathEntry createContainerEntry(OmniEclipseClasspathContainer container) { IPath containerPath = new Path(container.getPath()); boolean isExported = container.isExported(); IAccessRule[] accessRules = ClasspathUtils.createAccessRules(container); IClasspathAttribute[] attributes = ClasspathUtils.createClasspathAttributes(container); return JavaCore.newContainerEntry(containerPath, accessRules, attributes, isExported); } private static IClasspathEntry createContainerEntry(IPath path) { return JavaCore.newContainerEntry(path); } public static void update(IJavaProject project, Optional<List<OmniEclipseClasspathContainer>> containers, OmniJavaSourceSettings omniJavaSourceSettings, IProgressMonitor monitor) throws CoreException { new ClasspathContainerUpdater(project, containers, omniJavaSourceSettings).updateContainers(monitor); } }