| /* |
| * 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; |
| } |
| } |