• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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.project;
18 
19 import static com.android.ide.eclipse.adt.AdtConstants.COMPILER_COMPLIANCE_PREFERRED;
20 
21 import com.android.SdkConstants;
22 import com.android.annotations.NonNull;
23 import com.android.ide.common.xml.ManifestData;
24 import com.android.ide.eclipse.adt.AdtConstants;
25 import com.android.ide.eclipse.adt.AdtPlugin;
26 import com.android.ide.eclipse.adt.internal.build.builders.PostCompilerBuilder;
27 import com.android.ide.eclipse.adt.internal.build.builders.PreCompilerBuilder;
28 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
29 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
30 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
31 import com.android.utils.Pair;
32 
33 import org.eclipse.core.resources.ICommand;
34 import org.eclipse.core.resources.IFile;
35 import org.eclipse.core.resources.IFolder;
36 import org.eclipse.core.resources.IMarker;
37 import org.eclipse.core.resources.IProject;
38 import org.eclipse.core.resources.IProjectDescription;
39 import org.eclipse.core.resources.IResource;
40 import org.eclipse.core.resources.IWorkspace;
41 import org.eclipse.core.resources.IncrementalProjectBuilder;
42 import org.eclipse.core.resources.ResourcesPlugin;
43 import org.eclipse.core.runtime.CoreException;
44 import org.eclipse.core.runtime.IPath;
45 import org.eclipse.core.runtime.IProgressMonitor;
46 import org.eclipse.core.runtime.NullProgressMonitor;
47 import org.eclipse.core.runtime.Path;
48 import org.eclipse.core.runtime.QualifiedName;
49 import org.eclipse.jdt.core.IClasspathEntry;
50 import org.eclipse.jdt.core.IJavaModel;
51 import org.eclipse.jdt.core.IJavaProject;
52 import org.eclipse.jdt.core.JavaCore;
53 import org.eclipse.jdt.core.JavaModelException;
54 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
55 import org.eclipse.jdt.launching.IVMInstall;
56 import org.eclipse.jdt.launching.IVMInstall2;
57 import org.eclipse.jdt.launching.IVMInstallType;
58 import org.eclipse.jdt.launching.JavaRuntime;
59 
60 import java.util.ArrayList;
61 import java.util.HashMap;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.TreeMap;
65 
66 /**
67  * Utility class to manipulate Project parameters/properties.
68  */
69 public final class ProjectHelper {
70     public final static int COMPILER_COMPLIANCE_OK = 0;
71     public final static int COMPILER_COMPLIANCE_LEVEL = 1;
72     public final static int COMPILER_COMPLIANCE_SOURCE = 2;
73     public final static int COMPILER_COMPLIANCE_CODEGEN_TARGET = 3;
74 
75     /**
76      * Adds the corresponding source folder to the class path entries.
77      * This method does not check whether the entry is already defined in the project.
78      *
79      * @param entries The class path entries to read. A copy will be returned.
80      * @param newEntry The new class path entry to add.
81      * @return A new class path entries array.
82      */
addEntryToClasspath( IClasspathEntry[] entries, IClasspathEntry newEntry)83     public static IClasspathEntry[] addEntryToClasspath(
84             IClasspathEntry[] entries, IClasspathEntry newEntry) {
85         int n = entries.length;
86         IClasspathEntry[] newEntries = new IClasspathEntry[n + 1];
87         System.arraycopy(entries, 0, newEntries, 0, n);
88         newEntries[n] = newEntry;
89         return newEntries;
90     }
91 
92     /**
93      * Adds the corresponding source folder to the project's class path entries.
94      * This method does not check whether the entry is already defined in the project.
95      *
96      * @param javaProject The java project of which path entries to update.
97      * @param newEntry The new class path entry to add.
98      * @throws JavaModelException
99      */
addEntryToClasspath(IJavaProject javaProject, IClasspathEntry newEntry)100     public static void addEntryToClasspath(IJavaProject javaProject, IClasspathEntry newEntry)
101             throws JavaModelException {
102 
103         IClasspathEntry[] entries = javaProject.getRawClasspath();
104         entries = addEntryToClasspath(entries, newEntry);
105         javaProject.setRawClasspath(entries, new NullProgressMonitor());
106     }
107 
108     /**
109      * Checks whether the given class path entry is already defined in the project.
110      *
111      * @param javaProject The java project of which path entries to check.
112      * @param newEntry The parent source folder to remove.
113      * @return True if the class path entry is already defined.
114      * @throws JavaModelException
115      */
isEntryInClasspath(IJavaProject javaProject, IClasspathEntry newEntry)116     public static boolean isEntryInClasspath(IJavaProject javaProject, IClasspathEntry newEntry)
117             throws JavaModelException {
118 
119         IClasspathEntry[] entries = javaProject.getRawClasspath();
120         for (IClasspathEntry entry : entries) {
121             if (entry.equals(newEntry)) {
122                 return true;
123             }
124         }
125         return false;
126     }
127 
128     /**
129      * Remove a classpath entry from the array.
130      * @param entries The class path entries to read. A copy will be returned
131      * @param index The index to remove.
132      * @return A new class path entries array.
133      */
removeEntryFromClasspath( IClasspathEntry[] entries, int index)134     public static IClasspathEntry[] removeEntryFromClasspath(
135             IClasspathEntry[] entries, int index) {
136         int n = entries.length;
137         IClasspathEntry[] newEntries = new IClasspathEntry[n-1];
138 
139         // copy the entries before index
140         System.arraycopy(entries, 0, newEntries, 0, index);
141 
142         // copy the entries after index
143         System.arraycopy(entries, index + 1, newEntries, index,
144                 entries.length - index - 1);
145 
146         return newEntries;
147     }
148 
149     /**
150      * Converts a OS specific path into a path valid for the java doc location
151      * attributes of a project.
152      * @param javaDocOSLocation The OS specific path.
153      * @return a valid path for the java doc location.
154      */
getJavaDocPath(String javaDocOSLocation)155     public static String getJavaDocPath(String javaDocOSLocation) {
156         // first thing we do is convert the \ into /
157         String javaDoc = javaDocOSLocation.replaceAll("\\\\", //$NON-NLS-1$
158                 AdtConstants.WS_SEP);
159 
160         // then we add file: at the beginning for unix path, and file:/ for non
161         // unix path
162         if (javaDoc.startsWith(AdtConstants.WS_SEP)) {
163             return "file:" + javaDoc; //$NON-NLS-1$
164         }
165 
166         return "file:/" + javaDoc; //$NON-NLS-1$
167     }
168 
169     /**
170      * Look for a specific classpath entry by full path and return its index.
171      * @param entries The entry array to search in.
172      * @param entryPath The OS specific path of the entry.
173      * @param entryKind The kind of the entry. Accepted values are 0
174      * (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT,
175      * IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE,
176      * and IClasspathEntry.CPE_CONTAINER
177      * @return the index of the found classpath entry or -1.
178      */
findClasspathEntryByPath(IClasspathEntry[] entries, String entryPath, int entryKind)179     public static int findClasspathEntryByPath(IClasspathEntry[] entries,
180             String entryPath, int entryKind) {
181         for (int i = 0 ; i < entries.length ; i++) {
182             IClasspathEntry entry = entries[i];
183 
184             int kind = entry.getEntryKind();
185 
186             if (kind == entryKind || entryKind == 0) {
187                 // get the path
188                 IPath path = entry.getPath();
189 
190                 String osPathString = path.toOSString();
191                 if (osPathString.equals(entryPath)) {
192                     return i;
193                 }
194             }
195         }
196 
197         // not found, return bad index.
198         return -1;
199     }
200 
201     /**
202      * Look for a specific classpath entry for file name only and return its
203      *  index.
204      * @param entries The entry array to search in.
205      * @param entryName The filename of the entry.
206      * @param entryKind The kind of the entry. Accepted values are 0
207      * (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT,
208      * IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE,
209      * and IClasspathEntry.CPE_CONTAINER
210      * @param startIndex Index where to start the search
211      * @return the index of the found classpath entry or -1.
212      */
findClasspathEntryByName(IClasspathEntry[] entries, String entryName, int entryKind, int startIndex)213     public static int findClasspathEntryByName(IClasspathEntry[] entries,
214             String entryName, int entryKind, int startIndex) {
215         if (startIndex < 0) {
216             startIndex = 0;
217         }
218         for (int i = startIndex ; i < entries.length ; i++) {
219             IClasspathEntry entry = entries[i];
220 
221             int kind = entry.getEntryKind();
222 
223             if (kind == entryKind || entryKind == 0) {
224                 // get the path
225                 IPath path = entry.getPath();
226                 String name = path.segment(path.segmentCount()-1);
227 
228                 if (name.equals(entryName)) {
229                     return i;
230                 }
231             }
232         }
233 
234         // not found, return bad index.
235         return -1;
236     }
237 
updateProject(IJavaProject project)238     public static boolean updateProject(IJavaProject project) {
239         return updateProjects(new IJavaProject[] { project});
240     }
241 
242     /**
243      * Update the android-specific projects's classpath containers.
244      * @param projects the projects to update
245      * @return
246      */
updateProjects(IJavaProject[] projects)247     public static boolean updateProjects(IJavaProject[] projects) {
248         boolean r = AndroidClasspathContainerInitializer.updateProjects(projects);
249         if (r) {
250             return LibraryClasspathContainerInitializer.updateProjects(projects);
251         }
252         return false;
253     }
254 
255     /**
256      * Fix the project. This checks the SDK location.
257      * @param project The project to fix.
258      * @throws JavaModelException
259      */
fixProject(IProject project)260     public static void fixProject(IProject project) throws JavaModelException {
261         if (AdtPlugin.getOsSdkFolder().length() == 0) {
262             AdtPlugin.printToConsole(project, "Unknown SDK Location, project not fixed.");
263             return;
264         }
265 
266         // get a java project
267         IJavaProject javaProject = JavaCore.create(project);
268         fixProjectClasspathEntries(javaProject);
269     }
270 
271     /**
272      * Fix the project classpath entries. The method ensures that:
273      * <ul>
274      * <li>The project does not reference any old android.zip/android.jar archive.</li>
275      * <li>The project does not use its output folder as a sourc folder.</li>
276      * <li>The project does not reference a desktop JRE</li>
277      * <li>The project references the AndroidClasspathContainer.
278      * </ul>
279      * @param javaProject The project to fix.
280      * @throws JavaModelException
281      */
fixProjectClasspathEntries(IJavaProject javaProject)282     public static void fixProjectClasspathEntries(IJavaProject javaProject)
283             throws JavaModelException {
284 
285         // get the project classpath
286         IClasspathEntry[] entries = javaProject.getRawClasspath();
287         IClasspathEntry[] oldEntries = entries;
288 
289         // check if the JRE is set as library
290         int jreIndex = ProjectHelper.findClasspathEntryByPath(entries, JavaRuntime.JRE_CONTAINER,
291                 IClasspathEntry.CPE_CONTAINER);
292         if (jreIndex != -1) {
293             // the project has a JRE included, we remove it
294             entries = ProjectHelper.removeEntryFromClasspath(entries, jreIndex);
295         }
296 
297         // get the output folder
298         IPath outputFolder = javaProject.getOutputLocation();
299 
300         boolean foundFrameworkContainer = false;
301         boolean foundLibrariesContainer = false;
302 
303         for (int i = 0 ; i < entries.length ;) {
304             // get the entry and kind
305             IClasspathEntry entry = entries[i];
306             int kind = entry.getEntryKind();
307 
308             if (kind == IClasspathEntry.CPE_SOURCE) {
309                 IPath path = entry.getPath();
310 
311                 if (path.equals(outputFolder)) {
312                     entries = ProjectHelper.removeEntryFromClasspath(entries, i);
313 
314                     // continue, to skip the i++;
315                     continue;
316                 }
317             } else if (kind == IClasspathEntry.CPE_CONTAINER) {
318                 String path = entry.getPath().toString();
319                 if (AdtConstants.CONTAINER_FRAMEWORK.equals(path)) {
320                     foundFrameworkContainer = true;
321                 }
322                 if (AdtConstants.CONTAINER_LIBRARIES.equals(path)) {
323                     foundLibrariesContainer = true;
324                 }
325             }
326 
327             i++;
328         }
329 
330         // if the framework container is not there, we add it
331         if (foundFrameworkContainer == false) {
332             // add the android container to the array
333             entries = ProjectHelper.addEntryToClasspath(entries,
334                     JavaCore.newContainerEntry(new Path(AdtConstants.CONTAINER_FRAMEWORK)));
335         }
336 
337         // same thing for the library container
338         if (foundLibrariesContainer == false) {
339             // add the android container to the array
340             entries = ProjectHelper.addEntryToClasspath(entries,
341                     JavaCore.newContainerEntry(new Path(AdtConstants.CONTAINER_LIBRARIES)));
342         }
343 
344         // set the new list of entries to the project
345         if (entries != oldEntries) {
346             javaProject.setRawClasspath(entries, new NullProgressMonitor());
347         }
348 
349         // If needed, check and fix compiler compliance and source compatibility
350         ProjectHelper.checkAndFixCompilerCompliance(javaProject);
351     }
352 
353 
354     /**
355      * Checks the project compiler compliance level is supported.
356      * @param javaProject The project to check
357      * @return A pair with the first integer being an error code, and the second value
358      *   being the invalid value found or null. The error code can be: <ul>
359      * <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li>
360      * <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li>
361      * <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li>
362      * <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li>
363      * </ul>
364      */
checkCompilerCompliance(IJavaProject javaProject)365     public static final Pair<Integer, String> checkCompilerCompliance(IJavaProject javaProject) {
366         // get the project compliance level option
367         String compliance = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
368 
369         // check it against a list of valid compliance level strings.
370         if (checkCompliance(compliance) == false) {
371             // if we didn't find the proper compliance level, we return an error
372             return Pair.of(COMPILER_COMPLIANCE_LEVEL, compliance);
373         }
374 
375         // otherwise we check source compatibility
376         String source = javaProject.getOption(JavaCore.COMPILER_SOURCE, true);
377 
378         // check it against a list of valid compliance level strings.
379         if (checkCompliance(source) == false) {
380             // if we didn't find the proper compliance level, we return an error
381             return Pair.of(COMPILER_COMPLIANCE_SOURCE, source);
382         }
383 
384         // otherwise check codegen level
385         String codeGen = javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true);
386 
387         // check it against a list of valid compliance level strings.
388         if (checkCompliance(codeGen) == false) {
389             // if we didn't find the proper compliance level, we return an error
390             return Pair.of(COMPILER_COMPLIANCE_CODEGEN_TARGET, codeGen);
391         }
392 
393         return Pair.of(COMPILER_COMPLIANCE_OK, null);
394     }
395 
396     /**
397      * Checks the project compiler compliance level is supported.
398      * @param project The project to check
399      * @return A pair with the first integer being an error code, and the second value
400      *   being the invalid value found or null. The error code can be: <ul>
401      * <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li>
402      * <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li>
403      * <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li>
404      * <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li>
405      * </ul>
406      */
checkCompilerCompliance(IProject project)407     public static final Pair<Integer, String> checkCompilerCompliance(IProject project) {
408         // get the java project from the IProject resource object
409         IJavaProject javaProject = JavaCore.create(project);
410 
411         // check and return the result.
412         return checkCompilerCompliance(javaProject);
413     }
414 
415 
416     /**
417      * Checks, and fixes if needed, the compiler compliance level, and the source compatibility
418      * level
419      * @param project The project to check and fix.
420      */
checkAndFixCompilerCompliance(IProject project)421     public static final void checkAndFixCompilerCompliance(IProject project) {
422         // FIXME This method is never used. Shall we just removed it?
423         // {@link #checkAndFixCompilerCompliance(IJavaProject)} is used instead.
424 
425         // get the java project from the IProject resource object
426         IJavaProject javaProject = JavaCore.create(project);
427 
428         // Now we check the compiler compliance level and make sure it is valid
429         checkAndFixCompilerCompliance(javaProject);
430     }
431 
432     /**
433      * Checks, and fixes if needed, the compiler compliance level, and the source compatibility
434      * level
435      * @param javaProject The Java project to check and fix.
436      */
checkAndFixCompilerCompliance(IJavaProject javaProject)437     public static final void checkAndFixCompilerCompliance(IJavaProject javaProject) {
438         Pair<Integer, String> result = checkCompilerCompliance(javaProject);
439         if (result.getFirst().intValue() != COMPILER_COMPLIANCE_OK) {
440             // setup the preferred compiler compliance level.
441             javaProject.setOption(JavaCore.COMPILER_COMPLIANCE,
442                     AdtConstants.COMPILER_COMPLIANCE_PREFERRED);
443             javaProject.setOption(JavaCore.COMPILER_SOURCE,
444                     AdtConstants.COMPILER_COMPLIANCE_PREFERRED);
445             javaProject.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM,
446                     AdtConstants.COMPILER_COMPLIANCE_PREFERRED);
447 
448             // clean the project to make sure we recompile
449             try {
450                 javaProject.getProject().build(IncrementalProjectBuilder.CLEAN_BUILD,
451                         new NullProgressMonitor());
452             } catch (CoreException e) {
453                 AdtPlugin.printErrorToConsole(javaProject.getProject(),
454                         "Project compiler settings changed. Clean your project.");
455             }
456         }
457     }
458 
459     /**
460      * Makes the given project use JDK 6 (or more specifically,
461      * {@link AdtConstants#COMPILER_COMPLIANCE_PREFERRED} as the compilation
462      * target, regardless of what the default IDE JDK level is, provided a JRE
463      * of the given level is installed.
464      *
465      * @param javaProject the Java project
466      * @throws CoreException if the IDE throws an exception setting the compiler
467      *             level
468      */
469     @SuppressWarnings("restriction") // JDT API for setting compliance options
enforcePreferredCompilerCompliance(@onNull IJavaProject javaProject)470     public static void enforcePreferredCompilerCompliance(@NonNull IJavaProject javaProject)
471             throws CoreException {
472         String compliance = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
473         if (compliance == null ||
474                 JavaModelUtil.isVersionLessThan(compliance, COMPILER_COMPLIANCE_PREFERRED)) {
475             IVMInstallType[] types = JavaRuntime.getVMInstallTypes();
476             for (int i = 0; i < types.length; i++) {
477                 IVMInstallType type = types[i];
478                 IVMInstall[] installs = type.getVMInstalls();
479                 for (int j = 0; j < installs.length; j++) {
480                     IVMInstall install = installs[j];
481                     if (install instanceof IVMInstall2) {
482                         IVMInstall2 install2 = (IVMInstall2) install;
483                         // Java version can be 1.6.0, and preferred is 1.6
484                         if (install2.getJavaVersion().startsWith(COMPILER_COMPLIANCE_PREFERRED)) {
485                             Map<String, String> options = javaProject.getOptions(false);
486                             JavaCore.setComplianceOptions(COMPILER_COMPLIANCE_PREFERRED, options);
487                             JavaModelUtil.setDefaultClassfileOptions(options,
488                                     COMPILER_COMPLIANCE_PREFERRED);
489                             javaProject.setOptions(options);
490                             return;
491                         }
492                     }
493                 }
494             }
495         }
496     }
497 
498     /**
499      * Returns a {@link IProject} by its running application name, as it returned by the AVD.
500      * <p/>
501      * <var>applicationName</var> will in most case be the package declared in the manifest, but
502      * can, in some cases, be a custom process name declared in the manifest, in the
503      * <code>application</code>, <code>activity</code>, <code>receiver</code>, or
504      * <code>service</code> nodes.
505      * @param applicationName The application name.
506      * @return a project or <code>null</code> if no matching project were found.
507      */
findAndroidProjectByAppName(String applicationName)508     public static IProject findAndroidProjectByAppName(String applicationName) {
509         // Get the list of project for the current workspace
510         IWorkspace workspace = ResourcesPlugin.getWorkspace();
511         IProject[] projects = workspace.getRoot().getProjects();
512 
513         // look for a project that matches the packageName of the app
514         // we're trying to debug
515         for (IProject p : projects) {
516             if (p.isOpen()) {
517                 try {
518                     if (p.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
519                         // ignore non android projects
520                         continue;
521                     }
522                 } catch (CoreException e) {
523                     // failed to get the nature? skip project.
524                     continue;
525                 }
526 
527                 // check that there is indeed a manifest file.
528                 IFile manifestFile = getManifest(p);
529                 if (manifestFile == null) {
530                     // no file? skip this project.
531                     continue;
532                 }
533 
534                 ManifestData data = AndroidManifestHelper.parseForData(manifestFile);
535                 if (data == null) {
536                     // skip this project.
537                     continue;
538                 }
539 
540                 String manifestPackage = data.getPackage();
541 
542                 if (manifestPackage != null && manifestPackage.equals(applicationName)) {
543                     // this is the project we were looking for!
544                     return p;
545                 } else {
546                     // if the package and application name don't match,
547                     // we look for other possible process names declared in the manifest.
548                     String[] processes = data.getProcesses();
549                     for (String process : processes) {
550                         if (process.equals(applicationName)) {
551                             return p;
552                         }
553                     }
554                 }
555             }
556         }
557 
558         return null;
559 
560     }
561 
fixProjectNatureOrder(IProject project)562     public static void fixProjectNatureOrder(IProject project) throws CoreException {
563         IProjectDescription description = project.getDescription();
564         String[] natures = description.getNatureIds();
565 
566         // if the android nature is not the first one, we reorder them
567         if (AdtConstants.NATURE_DEFAULT.equals(natures[0]) == false) {
568             // look for the index
569             for (int i = 0 ; i < natures.length ; i++) {
570                 if (AdtConstants.NATURE_DEFAULT.equals(natures[i])) {
571                     // if we try to just reorder the array in one pass, this doesn't do
572                     // anything. I guess JDT check that we are actually adding/removing nature.
573                     // So, first we'll remove the android nature, and then add it back.
574 
575                     // remove the android nature
576                     removeNature(project, AdtConstants.NATURE_DEFAULT);
577 
578                     // now add it back at the first index.
579                     description = project.getDescription();
580                     natures = description.getNatureIds();
581 
582                     String[] newNatures = new String[natures.length + 1];
583 
584                     // first one is android
585                     newNatures[0] = AdtConstants.NATURE_DEFAULT;
586 
587                     // next the rest that was before the android nature
588                     System.arraycopy(natures, 0, newNatures, 1, natures.length);
589 
590                     // set the new natures
591                     description.setNatureIds(newNatures);
592                     project.setDescription(description, null);
593 
594                     // and stop
595                     break;
596                 }
597             }
598         }
599     }
600 
601 
602     /**
603      * Removes a specific nature from a project.
604      * @param project The project to remove the nature from.
605      * @param nature The nature id to remove.
606      * @throws CoreException
607      */
removeNature(IProject project, String nature)608     public static void removeNature(IProject project, String nature) throws CoreException {
609         IProjectDescription description = project.getDescription();
610         String[] natures = description.getNatureIds();
611 
612         // check if the project already has the android nature.
613         for (int i = 0; i < natures.length; ++i) {
614             if (nature.equals(natures[i])) {
615                 String[] newNatures = new String[natures.length - 1];
616                 if (i > 0) {
617                     System.arraycopy(natures, 0, newNatures, 0, i);
618                 }
619                 System.arraycopy(natures, i + 1, newNatures, i, natures.length - i - 1);
620                 description.setNatureIds(newNatures);
621                 project.setDescription(description, null);
622 
623                 return;
624             }
625         }
626 
627     }
628 
629     /**
630      * Returns if the project has error level markers.
631      * @param includeReferencedProjects flag to also test the referenced projects.
632      * @throws CoreException
633      */
hasError(IProject project, boolean includeReferencedProjects)634     public static boolean hasError(IProject project, boolean includeReferencedProjects)
635     throws CoreException {
636         IMarker[] markers = project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
637         if (markers != null && markers.length > 0) {
638             // the project has marker(s). even though they are "problem" we
639             // don't know their severity. so we loop on them and figure if they
640             // are warnings or errors
641             for (IMarker m : markers) {
642                 int s = m.getAttribute(IMarker.SEVERITY, -1);
643                 if (s == IMarker.SEVERITY_ERROR) {
644                     return true;
645                 }
646             }
647         }
648 
649         // test the referenced projects if needed.
650         if (includeReferencedProjects) {
651             List<IProject> projects = getReferencedProjects(project);
652 
653             for (IProject p : projects) {
654                 if (hasError(p, false)) {
655                     return true;
656                 }
657             }
658         }
659 
660         return false;
661     }
662 
663     /**
664      * Saves a String property into the persistent storage of a resource.
665      * @param resource The resource into which the string value is saved.
666      * @param propertyName the name of the property. The id of the plug-in is added to this string.
667      * @param value the value to save
668      * @return true if the save succeeded.
669      */
saveStringProperty(IResource resource, String propertyName, String value)670     public static boolean saveStringProperty(IResource resource, String propertyName,
671             String value) {
672         QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName);
673 
674         try {
675             resource.setPersistentProperty(qname, value);
676         } catch (CoreException e) {
677             return false;
678         }
679 
680         return true;
681     }
682 
683     /**
684      * Loads a String property from the persistent storage of a resource.
685      * @param resource The resource from which the string value is loaded.
686      * @param propertyName the name of the property. The id of the plug-in is added to this string.
687      * @return the property value or null if it was not found.
688      */
loadStringProperty(IResource resource, String propertyName)689     public static String loadStringProperty(IResource resource, String propertyName) {
690         QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName);
691 
692         try {
693             String value = resource.getPersistentProperty(qname);
694             return value;
695         } catch (CoreException e) {
696             return null;
697         }
698     }
699 
700     /**
701      * Saves a property into the persistent storage of a resource.
702      * @param resource The resource into which the boolean value is saved.
703      * @param propertyName the name of the property. The id of the plug-in is added to this string.
704      * @param value the value to save
705      * @return true if the save succeeded.
706      */
saveBooleanProperty(IResource resource, String propertyName, boolean value)707     public static boolean saveBooleanProperty(IResource resource, String propertyName,
708             boolean value) {
709         return saveStringProperty(resource, propertyName, Boolean.toString(value));
710     }
711 
712     /**
713      * Loads a boolean property from the persistent storage of a resource.
714      * @param resource The resource from which the boolean value is loaded.
715      * @param propertyName the name of the property. The id of the plug-in is added to this string.
716      * @param defaultValue The default value to return if the property was not found.
717      * @return the property value or the default value if the property was not found.
718      */
loadBooleanProperty(IResource resource, String propertyName, boolean defaultValue)719     public static boolean loadBooleanProperty(IResource resource, String propertyName,
720             boolean defaultValue) {
721         String value = loadStringProperty(resource, propertyName);
722         if (value != null) {
723             return Boolean.parseBoolean(value);
724         }
725 
726         return defaultValue;
727     }
728 
loadBooleanProperty(IResource resource, String propertyName)729     public static Boolean loadBooleanProperty(IResource resource, String propertyName) {
730         String value = loadStringProperty(resource, propertyName);
731         if (value != null) {
732             return Boolean.valueOf(value);
733         }
734 
735         return null;
736     }
737 
738     /**
739      * Saves the path of a resource into the persistent storage of a resource.
740      * @param resource The resource into which the resource path is saved.
741      * @param propertyName the name of the property. The id of the plug-in is added to this string.
742      * @param value The resource to save. It's its path that is actually stored. If null, an
743      *      empty string is stored.
744      * @return true if the save succeeded
745      */
saveResourceProperty(IResource resource, String propertyName, IResource value)746     public static boolean saveResourceProperty(IResource resource, String propertyName,
747             IResource value) {
748         if (value != null) {
749             IPath iPath = value.getFullPath();
750             return saveStringProperty(resource, propertyName, iPath.toString());
751         }
752 
753         return saveStringProperty(resource, propertyName, ""); //$NON-NLS-1$
754     }
755 
756     /**
757      * Loads the path of a resource from the persistent storage of a resource, and returns the
758      * corresponding IResource object.
759      * @param resource The resource from which the resource path is loaded.
760      * @param propertyName the name of the property. The id of the plug-in is added to this string.
761      * @return The corresponding IResource object (or children interface) or null
762      */
loadResourceProperty(IResource resource, String propertyName)763     public static IResource loadResourceProperty(IResource resource, String propertyName) {
764         String value = loadStringProperty(resource, propertyName);
765 
766         if (value != null && value.length() > 0) {
767             return ResourcesPlugin.getWorkspace().getRoot().findMember(new Path(value));
768         }
769 
770         return null;
771     }
772 
773     /**
774      * Returns the list of referenced project that are opened and Java projects.
775      * @param project
776      * @return a new list object containing the opened referenced java project.
777      * @throws CoreException
778      */
getReferencedProjects(IProject project)779     public static List<IProject> getReferencedProjects(IProject project) throws CoreException {
780         IProject[] projects = project.getReferencedProjects();
781 
782         ArrayList<IProject> list = new ArrayList<IProject>();
783 
784         for (IProject p : projects) {
785             if (p.isOpen() && p.hasNature(JavaCore.NATURE_ID)) {
786                 list.add(p);
787             }
788         }
789 
790         return list;
791     }
792 
793 
794     /**
795      * Checks a Java project compiler level option against a list of supported versions.
796      * @param optionValue the Compiler level option.
797      * @return true if the option value is supproted.
798      */
checkCompliance(String optionValue)799     private static boolean checkCompliance(String optionValue) {
800         for (String s : AdtConstants.COMPILER_COMPLIANCE) {
801             if (s != null && s.equals(optionValue)) {
802                 return true;
803             }
804         }
805 
806         return false;
807     }
808 
809     /**
810      * Returns the apk filename for the given project
811      * @param project The project.
812      * @param config An optional config name. Can be null.
813      */
getApkFilename(IProject project, String config)814     public static String getApkFilename(IProject project, String config) {
815         if (config != null) {
816             return project.getName() + "-" + config + SdkConstants.DOT_ANDROID_PACKAGE; //$NON-NLS-1$
817         }
818 
819         return project.getName() + SdkConstants.DOT_ANDROID_PACKAGE;
820     }
821 
822     /**
823      * Find the list of projects on which this JavaProject is dependent on at the compilation level.
824      *
825      * @param javaProject Java project that we are looking for the dependencies.
826      * @return A list of Java projects for which javaProject depend on.
827      * @throws JavaModelException
828      */
getAndroidProjectDependencies(IJavaProject javaProject)829     public static List<IJavaProject> getAndroidProjectDependencies(IJavaProject javaProject)
830         throws JavaModelException {
831         String[] requiredProjectNames = javaProject.getRequiredProjectNames();
832 
833         // Go from java project name to JavaProject name
834         IJavaModel javaModel = javaProject.getJavaModel();
835 
836         // loop through all dependent projects and keep only those that are Android projects
837         List<IJavaProject> projectList = new ArrayList<IJavaProject>(requiredProjectNames.length);
838         for (String javaProjectName : requiredProjectNames) {
839             IJavaProject androidJavaProject = javaModel.getJavaProject(javaProjectName);
840 
841             //Verify that the project has also the Android Nature
842             try {
843                 if (!androidJavaProject.getProject().hasNature(AdtConstants.NATURE_DEFAULT)) {
844                     continue;
845                 }
846             } catch (CoreException e) {
847                 continue;
848             }
849 
850             projectList.add(androidJavaProject);
851         }
852 
853         return projectList;
854     }
855 
856     /**
857      * Returns the android package file as an IFile object for the specified
858      * project.
859      * @param project The project
860      * @return The android package as an IFile object or null if not found.
861      */
getApplicationPackage(IProject project)862     public static IFile getApplicationPackage(IProject project) {
863         // get the output folder
864         IFolder outputLocation = BaseProjectHelper.getAndroidOutputFolder(project);
865 
866         if (outputLocation == null) {
867             AdtPlugin.printErrorToConsole(project,
868                     "Failed to get the output location of the project. Check build path properties"
869                     );
870             return null;
871         }
872 
873 
874         // get the package path
875         String packageName = project.getName() + SdkConstants.DOT_ANDROID_PACKAGE;
876         IResource r = outputLocation.findMember(packageName);
877 
878         // check the package is present
879         if (r instanceof IFile && r.exists()) {
880             return (IFile)r;
881         }
882 
883         String msg = String.format("Could not find %1$s!", packageName);
884         AdtPlugin.printErrorToConsole(project, msg);
885 
886         return null;
887     }
888 
889     /**
890      * Returns an {@link IFile} object representing the manifest for the given project.
891      *
892      * @param project The project containing the manifest file.
893      * @return An IFile object pointing to the manifest or null if the manifest
894      *         is missing.
895      */
getManifest(IProject project)896     public static IFile getManifest(IProject project) {
897         IResource r = project.findMember(AdtConstants.WS_SEP
898                 + SdkConstants.FN_ANDROID_MANIFEST_XML);
899 
900         if (r == null || r.exists() == false || (r instanceof IFile) == false) {
901             return null;
902         }
903         return (IFile) r;
904     }
905 
906     /**
907      * Does a full release build of the application, including the libraries. Do not build the
908      * package.
909      *
910      * @param project The project to be built.
911      * @param monitor A eclipse runtime progress monitor to be updated by the builders.
912      * @throws CoreException
913      */
914     @SuppressWarnings("unchecked")
compileInReleaseMode(IProject project, IProgressMonitor monitor)915     public static void compileInReleaseMode(IProject project, IProgressMonitor monitor)
916             throws CoreException {
917         compileInReleaseMode(project, true /*includeDependencies*/, monitor);
918     }
919 
920     /**
921      * Does a full release build of the application, including the libraries. Do not build the
922      * package.
923      *
924      * @param project The project to be built.
925      * @param monitor A eclipse runtime progress monitor to be updated by the builders.
926      * @throws CoreException
927      */
928     @SuppressWarnings("unchecked")
compileInReleaseMode(IProject project, boolean includeDependencies, IProgressMonitor monitor)929     private static void compileInReleaseMode(IProject project, boolean includeDependencies,
930             IProgressMonitor monitor)
931             throws CoreException {
932 
933         if (includeDependencies) {
934             ProjectState projectState = Sdk.getProjectState(project);
935 
936             // this gives us all the library projects, direct and indirect dependencies,
937             // so no need to run this method recursively.
938             List<IProject> libraries = projectState.getFullLibraryProjects();
939 
940             // build dependencies in reverse order to prevent libraries being rebuilt
941             // due to refresh of other libraries (they would be compiled in the wrong mode).
942             for (int i = libraries.size() - 1 ; i >= 0 ; i--) {
943                 IProject lib = libraries.get(i);
944                 compileInReleaseMode(lib, false /*includeDependencies*/, monitor);
945 
946                 // force refresh of the dependency.
947                 lib.refreshLocal(IResource.DEPTH_INFINITE, monitor);
948             }
949         }
950 
951         // do a full build on all the builders to guarantee that the builders are called.
952         // (Eclipse does an optimization where builders are not called if there aren't any
953         // deltas).
954 
955         ICommand[] commands = project.getDescription().getBuildSpec();
956         for (ICommand command : commands) {
957             String name = command.getBuilderName();
958             if (PreCompilerBuilder.ID.equals(name)) {
959                 Map newArgs = new HashMap();
960                 newArgs.put(PreCompilerBuilder.RELEASE_REQUESTED, "");
961                 if (command.getArguments() != null) {
962                     newArgs.putAll(command.getArguments());
963                 }
964 
965                 project.build(IncrementalProjectBuilder.FULL_BUILD,
966                         PreCompilerBuilder.ID, newArgs, monitor);
967             } else if (PostCompilerBuilder.ID.equals(name)) {
968                 if (includeDependencies == false) {
969                     // this is a library, we need to build it!
970                     project.build(IncrementalProjectBuilder.FULL_BUILD, name,
971                             command.getArguments(), monitor);
972                 }
973             } else {
974 
975                 project.build(IncrementalProjectBuilder.FULL_BUILD, name,
976                         command.getArguments(), monitor);
977             }
978         }
979     }
980 
981     /**
982      * Force building the project and all its dependencies.
983      *
984      * @param project the project to build
985      * @param kind the build kind
986      * @param monitor
987      * @throws CoreException
988      */
buildWithDeps(IProject project, int kind, IProgressMonitor monitor)989     public static void buildWithDeps(IProject project, int kind, IProgressMonitor monitor)
990             throws CoreException {
991         // Get list of projects that we depend on
992         ProjectState projectState = Sdk.getProjectState(project);
993 
994         // this gives us all the library projects, direct and indirect dependencies,
995         // so no need to run this method recursively.
996         List<IProject> libraries = projectState.getFullLibraryProjects();
997 
998         // build dependencies in reverse order to prevent libraries being rebuilt
999         // due to refresh of other libraries (they would be compiled in the wrong mode).
1000         for (int i = libraries.size() - 1 ; i >= 0 ; i--) {
1001             IProject lib = libraries.get(i);
1002             lib.build(kind, monitor);
1003             lib.refreshLocal(IResource.DEPTH_INFINITE, monitor);
1004         }
1005 
1006         project.build(kind, monitor);
1007     }
1008 
1009 
1010     /**
1011      * Build project incrementally, including making the final packaging even if it is disabled
1012      * by default.
1013      *
1014      * @param project The project to be built.
1015      * @param monitor A eclipse runtime progress monitor to be updated by the builders.
1016      * @throws CoreException
1017      */
doFullIncrementalDebugBuild(IProject project, IProgressMonitor monitor)1018     public static void doFullIncrementalDebugBuild(IProject project, IProgressMonitor monitor)
1019             throws CoreException {
1020         // Get list of projects that we depend on
1021         List<IJavaProject> androidProjectList = new ArrayList<IJavaProject>();
1022         try {
1023             androidProjectList = getAndroidProjectDependencies(
1024                                     BaseProjectHelper.getJavaProject(project));
1025         } catch (JavaModelException e) {
1026             AdtPlugin.printErrorToConsole(project, e);
1027         }
1028         // Recursively build dependencies
1029         for (IJavaProject dependency : androidProjectList) {
1030             doFullIncrementalDebugBuild(dependency.getProject(), monitor);
1031         }
1032 
1033         // Do an incremental build to pick up all the deltas
1034         project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
1035 
1036         // If the preferences indicate not to use post compiler optimization
1037         // then the incremental build will have done everything necessary, otherwise,
1038         // we have to run the final builder manually (if requested).
1039         if (AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) {
1040             // Create the map to pass to the PostC builder
1041             Map<String, String> args = new TreeMap<String, String>();
1042             args.put(PostCompilerBuilder.POST_C_REQUESTED, ""); //$NON-NLS-1$
1043 
1044             // call the post compiler manually, forcing FULL_BUILD otherwise Eclipse won't
1045             // call the builder since the delta is empty.
1046             project.build(IncrementalProjectBuilder.FULL_BUILD,
1047                           PostCompilerBuilder.ID, args, monitor);
1048         }
1049 
1050         // because the post compiler builder does a delayed refresh due to
1051         // library not picking the refresh up if it's done during the build,
1052         // we want to force a refresh here as this call is generally asking for
1053         // a build to use the apk right after the call.
1054         project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
1055     }
1056 }
1057