Home | History | Annotate | Download | only in project
      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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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")
    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")
    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      */
    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      */
   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