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