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