Home | History | Annotate | Download | only in exportgradle
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.wizards.exportgradle;
     18 
     19 import com.android.annotations.NonNull;
     20 import com.android.annotations.Nullable;
     21 import com.android.ide.eclipse.adt.AdtConstants;
     22 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     23 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
     24 import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryState;
     25 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     26 import com.google.common.collect.Lists;
     27 import com.google.common.collect.Maps;
     28 
     29 import org.eclipse.core.resources.IProject;
     30 import org.eclipse.core.resources.IResource;
     31 import org.eclipse.core.resources.ResourcesPlugin;
     32 import org.eclipse.core.runtime.CoreException;
     33 import org.eclipse.core.runtime.IPath;
     34 import org.eclipse.core.runtime.Path;
     35 import org.eclipse.jdt.core.IClasspathEntry;
     36 import org.eclipse.jdt.core.IJavaProject;
     37 import org.eclipse.jdt.core.IPackageFragmentRoot;
     38 import org.eclipse.jdt.core.JavaCore;
     39 import org.eclipse.jdt.core.JavaModelException;
     40 
     41 import java.io.File;
     42 import java.util.Collection;
     43 import java.util.List;
     44 import java.util.Map;
     45 import java.util.regex.Pattern;
     46 
     47 /**
     48  * Class to setup the project and its modules.
     49  */
     50 public class ProjectSetupBuilder {
     51 
     52     private final static class InternalException extends Exception {
     53         private static final long serialVersionUID = 1L;
     54 
     55         InternalException(String message) {
     56             super(message);
     57         }
     58     }
     59 
     60     private boolean mCanFinish = false;
     61     private boolean mCanGenerate = false;
     62     private final List<GradleModule> mOriginalModules = Lists.newArrayList();
     63     private final Map<IJavaProject, GradleModule> mModules = Maps.newHashMap();
     64     private IPath mCommonRoot;
     65     private ExportStatus mStatus;
     66 
     67     public ProjectSetupBuilder() {
     68 
     69     }
     70 
     71     public void setCanGenerate(boolean generate) {
     72         mCanGenerate = generate;
     73     }
     74 
     75     public void setCanFinish(boolean canFinish) {
     76         mCanFinish = canFinish;
     77     }
     78 
     79     public boolean canFinish() {
     80         return mCanFinish;
     81     }
     82 
     83     public boolean canGenerate() {
     84         return mCanGenerate;
     85     }
     86 
     87     public void setStatus(ExportStatus status) {
     88         mStatus = status;
     89     }
     90 
     91     public ExportStatus getStatus() {
     92         return mStatus;
     93     }
     94 
     95     @NonNull
     96     public String setProject(@NonNull List<IJavaProject> selectedProjects)
     97             throws CoreException {
     98         mModules.clear();
     99 
    100         // build a list of all projects that must be included. This is in case
    101         // some dependencies have not been included in the selected projects. We also include
    102         // parent projects so that the full multi-project setup is correct.
    103         // Note that if two projects are selected that are not related, both will be added
    104         // in the same multi-project anyway.
    105         try {
    106             for (IJavaProject javaProject : selectedProjects) {
    107                 GradleModule module;
    108 
    109                 if (javaProject.getProject().hasNature(AdtConstants.NATURE_DEFAULT)) {
    110                     module = processAndroidProject(javaProject);
    111                 } else {
    112                     module = processJavaProject(javaProject);
    113                 }
    114 
    115                 mOriginalModules.add(module);
    116             }
    117 
    118             Collection<GradleModule> modules = mModules.values();
    119             computeRootAndPaths(modules);
    120 
    121             return null;
    122         } catch (InternalException e) {
    123             return e.getMessage();
    124         }
    125     }
    126 
    127     @NonNull
    128     public Collection<GradleModule> getModules() {
    129         return mModules.values();
    130     }
    131 
    132     public int getModuleCount() {
    133         return mModules.size();
    134     }
    135 
    136     @Nullable
    137     public IPath getCommonRoot() {
    138         return mCommonRoot;
    139     }
    140 
    141     @Nullable
    142     public GradleModule getModule(IJavaProject javaProject) {
    143         return mModules.get(javaProject);
    144     }
    145 
    146     public boolean isOriginalProject(@NonNull IJavaProject javaProject) {
    147         GradleModule module = mModules.get(javaProject);
    148         return mOriginalModules.contains(module);
    149     }
    150 
    151     @NonNull
    152     public List<GradleModule> getOriginalModules() {
    153         return mOriginalModules;
    154     }
    155 
    156     @Nullable
    157     public List<GradleModule> getShortestDependencyTo(GradleModule module) {
    158         return findModule(module, mOriginalModules);
    159     }
    160 
    161     @Nullable
    162     public List<GradleModule> findModule(GradleModule toFind, GradleModule rootModule) {
    163         if (toFind == rootModule) {
    164             List<GradleModule> list = Lists.newArrayList();
    165             list.add(toFind);
    166             return list;
    167         }
    168 
    169         List<GradleModule> shortestChain = findModule(toFind, rootModule.getDependencies());
    170 
    171         if (shortestChain != null) {
    172             shortestChain.add(0, rootModule);
    173         }
    174 
    175         return shortestChain;
    176     }
    177 
    178     @Nullable
    179     public List<GradleModule> findModule(GradleModule toFind, List<GradleModule> modules) {
    180         List<GradleModule> currentChain = null;
    181 
    182         for (GradleModule child : modules) {
    183             List<GradleModule> newChain = findModule(toFind, child);
    184             if (currentChain == null) {
    185                 currentChain = newChain;
    186             } else if (newChain != null) {
    187                 if (currentChain.size() > newChain.size()) {
    188                     currentChain = newChain;
    189                 }
    190             }
    191         }
    192 
    193         return currentChain;
    194     }
    195 
    196     @NonNull
    197     private GradleModule processAndroidProject(@NonNull IJavaProject javaProject)
    198             throws InternalException, CoreException {
    199 
    200         // get/create the module
    201         GradleModule module = createModuleOnDemand(javaProject);
    202         if (module.isConfigured()) {
    203             return module;
    204         }
    205 
    206         module.setType(GradleModule.Type.ANDROID);
    207 
    208         ProjectState projectState = Sdk.getProjectState(javaProject.getProject());
    209         assert projectState != null;
    210 
    211         // add library project dependencies
    212         List<LibraryState> libraryProjects = projectState.getLibraries();
    213         for (LibraryState libraryState : libraryProjects) {
    214             ProjectState libProjectState = libraryState.getProjectState();
    215             if (libProjectState != null) {
    216                 IJavaProject javaLib = getJavaProject(libProjectState);
    217                 if (javaLib != null) {
    218                     GradleModule libModule = processAndroidProject(javaLib);
    219                     module.addDependency(libModule);
    220                 } else {
    221                     throw new InternalException(String.format(
    222                             "Project %1$s is missing. Needed by %2$s.\n" +
    223                             "Make sure all dependencies are opened.",
    224                             libraryState.getRelativePath(),
    225                             javaProject.getProject().getName()));
    226                 }
    227             } else {
    228                 throw new InternalException(String.format(
    229                         "Project %1$s is missing. Needed by %2$s.\n" +
    230                         "Make sure all dependencies are opened.",
    231                         libraryState.getRelativePath(),
    232                         javaProject.getProject().getName()));
    233             }
    234         }
    235 
    236         // add java project dependencies
    237         List<IJavaProject> javaDepProjects = getReferencedProjects(javaProject);
    238         for (IJavaProject javaDep : javaDepProjects) {
    239             GradleModule libModule = processJavaProject(javaDep);
    240             module.addDependency(libModule);
    241         }
    242 
    243         return module;
    244     }
    245 
    246     @NonNull
    247     private GradleModule processJavaProject(@NonNull IJavaProject javaProject)
    248             throws InternalException, CoreException {
    249         // get/create the module
    250         GradleModule module = createModuleOnDemand(javaProject);
    251 
    252         if (module.isConfigured()) {
    253             return module;
    254         }
    255 
    256         module.setType(GradleModule.Type.JAVA);
    257 
    258         // add java project dependencies
    259         List<IJavaProject> javaDepProjects = getReferencedProjects(javaProject);
    260         for (IJavaProject javaDep : javaDepProjects) {
    261             // Java project should not reference Android project!
    262             if (javaDep.getProject().hasNature(AdtConstants.NATURE_DEFAULT)) {
    263                 throw new InternalException(String.format(
    264                         "Java project %1$s depends on Android project %2$s!\n" +
    265                         "This is not a valid dependency",
    266                         javaProject.getProject().getName(), javaDep.getProject().getName()));
    267             }
    268             GradleModule libModule = processJavaProject(javaDep);
    269             module.addDependency(libModule);
    270         }
    271 
    272         return module;
    273     }
    274 
    275     private void computeRootAndPaths(Collection<GradleModule> modules) throws InternalException {
    276         // compute the common root.
    277         mCommonRoot = determineCommonRoot(modules);
    278 
    279         // compute all the relative paths.
    280         for (GradleModule module : modules) {
    281             String path = getGradlePath(module.getJavaProject().getProject().getLocation(),
    282                     mCommonRoot);
    283 
    284             module.setPath(path);
    285         }
    286     }
    287 
    288     /**
    289      * Finds the common parent directory shared by this project and all its dependencies.
    290      * If there's only one project, returns the single project's folder.
    291      * @throws InternalException
    292      */
    293     @NonNull
    294     private static IPath determineCommonRoot(Collection<GradleModule> modules)
    295             throws InternalException {
    296         IPath commonRoot = null;
    297         for (GradleModule module : modules) {
    298             if (commonRoot == null) {
    299                 commonRoot = module.getJavaProject().getProject().getLocation();
    300             } else {
    301                 commonRoot = findCommonRoot(commonRoot,
    302                         module.getJavaProject().getProject().getLocation());
    303             }
    304         }
    305 
    306         return commonRoot;
    307     }
    308 
    309     /**
    310      * Converts the given path to be relative to the given root path, and converts it to
    311      * Gradle project notation, such as is used in the settings.gradle file.
    312      */
    313     @NonNull
    314     private static String getGradlePath(IPath path, IPath root) {
    315         IPath relativePath = path.makeRelativeTo(root);
    316         String relativeString = relativePath.toOSString();
    317         return ":" + relativeString.replaceAll(Pattern.quote(File.separator), ":"); //$NON-NLS-1$
    318     }
    319 
    320     /**
    321      * Given two IPaths, finds the parent directory of both of them.
    322      * @throws InternalException
    323      */
    324     @NonNull
    325     private static IPath findCommonRoot(@NonNull IPath path1, @NonNull IPath path2)
    326             throws InternalException {
    327         if (path1.getDevice() != null && !path1.getDevice().equals(path2.getDevice())) {
    328             throw new InternalException(
    329                     "Different modules have been detected on different drives.\n" +
    330                     "This prevents finding a common root to all modules.");
    331         }
    332 
    333         IPath result = path1.uptoSegment(0);
    334 
    335         final int count = Math.min(path1.segmentCount(), path2.segmentCount());
    336         for (int i = 0; i < count; i++) {
    337             if (path1.segment(i).equals(path2.segment(i))) {
    338                 result = result.append(Path.SEPARATOR + path2.segment(i));
    339             }
    340         }
    341         return result;
    342     }
    343 
    344     @Nullable
    345     private IJavaProject getJavaProject(ProjectState projectState) {
    346         try {
    347             return BaseProjectHelper.getJavaProject(projectState.getProject());
    348         } catch (CoreException e) {
    349             return null;
    350         }
    351     }
    352 
    353     @NonNull
    354     private GradleModule createModuleOnDemand(@NonNull IJavaProject javaProject) {
    355         GradleModule module = mModules.get(javaProject);
    356         if (module == null) {
    357             module = new GradleModule(javaProject);
    358             mModules.put(javaProject, module);
    359         }
    360 
    361         return module;
    362     }
    363 
    364     @NonNull
    365     private static List<IJavaProject> getReferencedProjects(IJavaProject javaProject)
    366             throws JavaModelException, InternalException {
    367 
    368         List<IJavaProject> projects = Lists.newArrayList();
    369 
    370         IClasspathEntry entries[] = javaProject.getRawClasspath();
    371         for (IClasspathEntry classpathEntry : entries) {
    372             if (classpathEntry.getContentKind() == IPackageFragmentRoot.K_SOURCE
    373                     && classpathEntry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
    374                 // found required project on build path
    375                 String subProjectRoot = classpathEntry.getPath().toString();
    376                 IJavaProject subProject = getJavaProject(subProjectRoot);
    377                 // is project available in workspace?
    378                 if (subProject != null) {
    379                     projects.add(subProject);
    380                 } else {
    381                     throw new InternalException(String.format(
    382                             "Project '%s' is missing project dependency '%s' in Eclipse workspace.\n" +
    383                             "Make sure all dependencies are opened.",
    384                             javaProject.getProject().getName(),
    385                             classpathEntry.getPath().toString()));
    386                 }
    387             }
    388         }
    389 
    390         return projects;
    391     }
    392 
    393     /**
    394      * Get Java project for given root.
    395      */
    396     @Nullable
    397     private static IJavaProject getJavaProject(String root) {
    398         IPath path = new Path(root);
    399         if (path.segmentCount() == 1) {
    400             return getJavaProjectByName(root);
    401         }
    402         IResource resource = ResourcesPlugin.getWorkspace().getRoot()
    403                 .findMember(path);
    404         if (resource != null && resource.getType() == IResource.PROJECT) {
    405             if (resource.exists()) {
    406                 return (IJavaProject) JavaCore.create(resource);
    407             }
    408         }
    409         return null;
    410     }
    411 
    412     /**
    413      * Get Java project from resource.
    414      */
    415     private static IJavaProject getJavaProjectByName(String name) {
    416         try {
    417             IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
    418             if (project.exists()) {
    419                 return JavaCore.create(project);
    420             }
    421         } catch (IllegalArgumentException iae) {
    422         }
    423         return null;
    424     }
    425 }
    426