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