blob: 1fd6b74f6d9d3d202a25464a2b1667db8577d70e [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.wizards.exportgradle;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Class to setup the project and its modules.
*/
public class ProjectSetupBuilder {
private final static class InternalException extends Exception {
private static final long serialVersionUID = 1L;
InternalException(String message) {
super(message);
}
}
private boolean mCanFinish = false;
private boolean mCanGenerate = false;
private final List<GradleModule> mOriginalModules = Lists.newArrayList();
private final Map<IJavaProject, GradleModule> mModules = Maps.newHashMap();
private IPath mCommonRoot;
private ExportStatus mStatus;
public ProjectSetupBuilder() {
}
public void setCanGenerate(boolean generate) {
mCanGenerate = generate;
}
public void setCanFinish(boolean canFinish) {
mCanFinish = canFinish;
}
public boolean canFinish() {
return mCanFinish;
}
public boolean canGenerate() {
return mCanGenerate;
}
public void setStatus(ExportStatus status) {
mStatus = status;
}
public ExportStatus getStatus() {
return mStatus;
}
@NonNull
public String setProject(@NonNull List<IJavaProject> selectedProjects)
throws CoreException {
mModules.clear();
// build a list of all projects that must be included. This is in case
// some dependencies have not been included in the selected projects. We also include
// parent projects so that the full multi-project setup is correct.
// Note that if two projects are selected that are not related, both will be added
// in the same multi-project anyway.
try {
for (IJavaProject javaProject : selectedProjects) {
GradleModule module;
if (javaProject.getProject().hasNature(AdtConstants.NATURE_DEFAULT)) {
module = processAndroidProject(javaProject);
} else {
module = processJavaProject(javaProject);
}
mOriginalModules.add(module);
}
Collection<GradleModule> modules = mModules.values();
computeRootAndPaths(modules);
return null;
} catch (InternalException e) {
return e.getMessage();
}
}
@NonNull
public Collection<GradleModule> getModules() {
return mModules.values();
}
public int getModuleCount() {
return mModules.size();
}
@Nullable
public IPath getCommonRoot() {
return mCommonRoot;
}
@Nullable
public GradleModule getModule(IJavaProject javaProject) {
return mModules.get(javaProject);
}
public boolean isOriginalProject(@NonNull IJavaProject javaProject) {
GradleModule module = mModules.get(javaProject);
return mOriginalModules.contains(module);
}
@NonNull
public List<GradleModule> getOriginalModules() {
return mOriginalModules;
}
@Nullable
public List<GradleModule> getShortestDependencyTo(GradleModule module) {
return findModule(module, mOriginalModules);
}
@Nullable
public List<GradleModule> findModule(GradleModule toFind, GradleModule rootModule) {
if (toFind == rootModule) {
List<GradleModule> list = Lists.newArrayList();
list.add(toFind);
return list;
}
List<GradleModule> shortestChain = findModule(toFind, rootModule.getDependencies());
if (shortestChain != null) {
shortestChain.add(0, rootModule);
}
return shortestChain;
}
@Nullable
public List<GradleModule> findModule(GradleModule toFind, List<GradleModule> modules) {
List<GradleModule> currentChain = null;
for (GradleModule child : modules) {
List<GradleModule> newChain = findModule(toFind, child);
if (currentChain == null) {
currentChain = newChain;
} else if (newChain != null) {
if (currentChain.size() > newChain.size()) {
currentChain = newChain;
}
}
}
return currentChain;
}
@NonNull
private GradleModule processAndroidProject(@NonNull IJavaProject javaProject)
throws InternalException, CoreException {
// get/create the module
GradleModule module = createModuleOnDemand(javaProject);
if (module.isConfigured()) {
return module;
}
module.setType(GradleModule.Type.ANDROID);
ProjectState projectState = Sdk.getProjectState(javaProject.getProject());
assert projectState != null;
// add library project dependencies
List<LibraryState> libraryProjects = projectState.getLibraries();
for (LibraryState libraryState : libraryProjects) {
ProjectState libProjectState = libraryState.getProjectState();
if (libProjectState != null) {
IJavaProject javaLib = getJavaProject(libProjectState);
if (javaLib != null) {
GradleModule libModule = processAndroidProject(javaLib);
module.addDependency(libModule);
} else {
throw new InternalException(String.format(
"Project %1$s is missing. Needed by %2$s.\n" +
"Make sure all dependencies are opened.",
libraryState.getRelativePath(),
javaProject.getProject().getName()));
}
} else {
throw new InternalException(String.format(
"Project %1$s is missing. Needed by %2$s.\n" +
"Make sure all dependencies are opened.",
libraryState.getRelativePath(),
javaProject.getProject().getName()));
}
}
// add java project dependencies
List<IJavaProject> javaDepProjects = getReferencedProjects(javaProject);
for (IJavaProject javaDep : javaDepProjects) {
GradleModule libModule = processJavaProject(javaDep);
module.addDependency(libModule);
}
return module;
}
@NonNull
private GradleModule processJavaProject(@NonNull IJavaProject javaProject)
throws InternalException, CoreException {
// get/create the module
GradleModule module = createModuleOnDemand(javaProject);
if (module.isConfigured()) {
return module;
}
module.setType(GradleModule.Type.JAVA);
// add java project dependencies
List<IJavaProject> javaDepProjects = getReferencedProjects(javaProject);
for (IJavaProject javaDep : javaDepProjects) {
// Java project should not reference Android project!
if (javaDep.getProject().hasNature(AdtConstants.NATURE_DEFAULT)) {
throw new InternalException(String.format(
"Java project %1$s depends on Android project %2$s!\n" +
"This is not a valid dependency",
javaProject.getProject().getName(), javaDep.getProject().getName()));
}
GradleModule libModule = processJavaProject(javaDep);
module.addDependency(libModule);
}
return module;
}
private void computeRootAndPaths(Collection<GradleModule> modules) throws InternalException {
// compute the common root.
mCommonRoot = determineCommonRoot(modules);
// compute all the relative paths.
for (GradleModule module : modules) {
String path = getGradlePath(module.getJavaProject().getProject().getLocation(),
mCommonRoot);
module.setPath(path);
}
}
/**
* Finds the common parent directory shared by this project and all its dependencies.
* If there's only one project, returns the single project's folder.
* @throws InternalException
*/
@NonNull
private static IPath determineCommonRoot(Collection<GradleModule> modules)
throws InternalException {
IPath commonRoot = null;
for (GradleModule module : modules) {
if (commonRoot == null) {
commonRoot = module.getJavaProject().getProject().getLocation();
} else {
commonRoot = findCommonRoot(commonRoot,
module.getJavaProject().getProject().getLocation());
}
}
return commonRoot;
}
/**
* Converts the given path to be relative to the given root path, and converts it to
* Gradle project notation, such as is used in the settings.gradle file.
*/
@NonNull
private static String getGradlePath(IPath path, IPath root) {
IPath relativePath = path.makeRelativeTo(root);
String relativeString = relativePath.toOSString();
return ":" + relativeString.replaceAll(Pattern.quote(File.separator), ":"); //$NON-NLS-1$
}
/**
* Given two IPaths, finds the parent directory of both of them.
* @throws InternalException
*/
@NonNull
private static IPath findCommonRoot(@NonNull IPath path1, @NonNull IPath path2)
throws InternalException {
if (path1.getDevice() != null && !path1.getDevice().equals(path2.getDevice())) {
throw new InternalException(
"Different modules have been detected on different drives.\n" +
"This prevents finding a common root to all modules.");
}
IPath result = path1.uptoSegment(0);
final int count = Math.min(path1.segmentCount(), path2.segmentCount());
for (int i = 0; i < count; i++) {
if (path1.segment(i).equals(path2.segment(i))) {
result = result.append(Path.SEPARATOR + path2.segment(i));
}
}
return result;
}
@Nullable
private IJavaProject getJavaProject(ProjectState projectState) {
try {
return BaseProjectHelper.getJavaProject(projectState.getProject());
} catch (CoreException e) {
return null;
}
}
@NonNull
private GradleModule createModuleOnDemand(@NonNull IJavaProject javaProject) {
GradleModule module = mModules.get(javaProject);
if (module == null) {
module = new GradleModule(javaProject);
mModules.put(javaProject, module);
}
return module;
}
@NonNull
private static List<IJavaProject> getReferencedProjects(IJavaProject javaProject)
throws JavaModelException, InternalException {
List<IJavaProject> projects = Lists.newArrayList();
IClasspathEntry entries[] = javaProject.getRawClasspath();
for (IClasspathEntry classpathEntry : entries) {
if (classpathEntry.getContentKind() == IPackageFragmentRoot.K_SOURCE
&& classpathEntry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
// found required project on build path
String subProjectRoot = classpathEntry.getPath().toString();
IJavaProject subProject = getJavaProject(subProjectRoot);
// is project available in workspace?
if (subProject != null) {
projects.add(subProject);
} else {
throw new InternalException(String.format(
"Project '%s' is missing project dependency '%s' in Eclipse workspace.\n" +
"Make sure all dependencies are opened.",
javaProject.getProject().getName(),
classpathEntry.getPath().toString()));
}
}
}
return projects;
}
/**
* Get Java project for given root.
*/
@Nullable
private static IJavaProject getJavaProject(String root) {
IPath path = new Path(root);
if (path.segmentCount() == 1) {
return getJavaProjectByName(root);
}
IResource resource = ResourcesPlugin.getWorkspace().getRoot()
.findMember(path);
if (resource != null && resource.getType() == IResource.PROJECT) {
if (resource.exists()) {
return (IJavaProject) JavaCore.create(resource);
}
}
return null;
}
/**
* Get Java project from resource.
*/
private static IJavaProject getJavaProjectByName(String name) {
try {
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
if (project.exists()) {
return JavaCore.create(project);
}
} catch (IllegalArgumentException iae) {
}
return null;
}
}