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 InternalException(String message)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 ProjectSetupBuilder()67 public ProjectSetupBuilder() { 68 69 } 70 setCanGenerate(boolean generate)71 public void setCanGenerate(boolean generate) { 72 mCanGenerate = generate; 73 } 74 setCanFinish(boolean canFinish)75 public void setCanFinish(boolean canFinish) { 76 mCanFinish = canFinish; 77 } 78 canFinish()79 public boolean canFinish() { 80 return mCanFinish; 81 } 82 canGenerate()83 public boolean canGenerate() { 84 return mCanGenerate; 85 } 86 setStatus(ExportStatus status)87 public void setStatus(ExportStatus status) { 88 mStatus = status; 89 } 90 getStatus()91 public ExportStatus getStatus() { 92 return mStatus; 93 } 94 95 @NonNull setProject(@onNull List<IJavaProject> selectedProjects)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 getModules()128 public Collection<GradleModule> getModules() { 129 return mModules.values(); 130 } 131 getModuleCount()132 public int getModuleCount() { 133 return mModules.size(); 134 } 135 136 @Nullable getCommonRoot()137 public IPath getCommonRoot() { 138 return mCommonRoot; 139 } 140 141 @Nullable getModule(IJavaProject javaProject)142 public GradleModule getModule(IJavaProject javaProject) { 143 return mModules.get(javaProject); 144 } 145 isOriginalProject(@onNull IJavaProject javaProject)146 public boolean isOriginalProject(@NonNull IJavaProject javaProject) { 147 GradleModule module = mModules.get(javaProject); 148 return mOriginalModules.contains(module); 149 } 150 151 @NonNull getOriginalModules()152 public List<GradleModule> getOriginalModules() { 153 return mOriginalModules; 154 } 155 156 @Nullable getShortestDependencyTo(GradleModule module)157 public List<GradleModule> getShortestDependencyTo(GradleModule module) { 158 return findModule(module, mOriginalModules); 159 } 160 161 @Nullable findModule(GradleModule toFind, GradleModule rootModule)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 findModule(GradleModule toFind, List<GradleModule> modules)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 processAndroidProject(@onNull IJavaProject javaProject)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 processJavaProject(@onNull IJavaProject javaProject)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 computeRootAndPaths(Collection<GradleModule> modules)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 determineCommonRoot(Collection<GradleModule> modules)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 getGradlePath(IPath path, IPath root)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 findCommonRoot(@onNull IPath path1, @NonNull IPath path2)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 getJavaProject(ProjectState projectState)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 createModuleOnDemand(@onNull IJavaProject javaProject)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 getReferencedProjects(IJavaProject javaProject)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 getJavaProject(String root)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 */ getJavaProjectByName(String name)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