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