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