Home | History | Annotate | Download | only in manager
      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.resources.manager;
     18 
     19 import com.android.ide.common.resources.FrameworkResources;
     20 import com.android.ide.common.resources.ResourceFile;
     21 import com.android.ide.common.resources.ResourceFolder;
     22 import com.android.ide.common.resources.ResourceRepository;
     23 import com.android.ide.common.resources.ScanningContext;
     24 import com.android.ide.eclipse.adt.AdtConstants;
     25 import com.android.ide.eclipse.adt.AdtPlugin;
     26 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
     27 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
     28 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IRawDeltaListener;
     29 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
     30 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     31 import com.android.ide.eclipse.adt.io.IFileWrapper;
     32 import com.android.ide.eclipse.adt.io.IFolderWrapper;
     33 import com.android.io.FolderWrapper;
     34 import com.android.resources.ResourceFolderType;
     35 import com.android.sdklib.IAndroidTarget;
     36 import com.android.sdklib.SdkConstants;
     37 
     38 import org.eclipse.core.resources.IContainer;
     39 import org.eclipse.core.resources.IFile;
     40 import org.eclipse.core.resources.IFolder;
     41 import org.eclipse.core.resources.IMarkerDelta;
     42 import org.eclipse.core.resources.IProject;
     43 import org.eclipse.core.resources.IResource;
     44 import org.eclipse.core.resources.IResourceDelta;
     45 import org.eclipse.core.resources.IResourceDeltaVisitor;
     46 import org.eclipse.core.resources.ResourcesPlugin;
     47 import org.eclipse.core.runtime.CoreException;
     48 import org.eclipse.core.runtime.IPath;
     49 import org.eclipse.core.runtime.IStatus;
     50 import org.eclipse.core.runtime.QualifiedName;
     51 
     52 import java.io.IOException;
     53 import java.util.ArrayList;
     54 import java.util.Collection;
     55 import java.util.HashMap;
     56 import java.util.Map;
     57 
     58 /**
     59  * The ResourceManager tracks resources for all opened projects.
     60  * <p/>
     61  * It provide direct access to all the resources of a project as a {@link ProjectResources}
     62  * object that allows accessing the resources through their file representation or as Android
     63  * resources (similar to what is seen by an Android application).
     64  * <p/>
     65  * The ResourceManager automatically tracks file changes to update its internal representation
     66  * of the resources so that they are always up to date.
     67  * <p/>
     68  * It also gives access to a monitor that is more resource oriented than the
     69  * {@link GlobalProjectMonitor}.
     70  * This monitor will let you track resource changes by giving you direct access to
     71  * {@link ResourceFile}, or {@link ResourceFolder}.
     72  *
     73  * @see ProjectResources
     74  */
     75 public final class ResourceManager {
     76     public final static boolean DEBUG = false;
     77 
     78     private final static ResourceManager sThis = new ResourceManager();
     79 
     80     /**
     81      * Map associating project resource with project objects.
     82      * <p/><b>All accesses must be inside a synchronized(mMap) block</b>, and do as a little as
     83      * possible and <b>not call out to other classes</b>.
     84      */
     85     private final Map<IProject, ProjectResources> mMap =
     86         new HashMap<IProject, ProjectResources>();
     87 
     88     /**
     89      * Interface to be notified of resource changes.
     90      *
     91      * @see ResourceManager#addListener(IResourceListener)
     92      * @see ResourceManager#removeListener(IResourceListener)
     93      */
     94     public interface IResourceListener {
     95         /**
     96          * Notification for resource file change.
     97          * @param project the project of the file.
     98          * @param file the {@link ResourceFile} representing the file.
     99          * @param eventType the type of event. See {@link IResourceDelta}.
    100          */
    101         void fileChanged(IProject project, ResourceFile file, int eventType);
    102         /**
    103          * Notification for resource folder change.
    104          * @param project the project of the file.
    105          * @param folder the {@link ResourceFolder} representing the folder.
    106          * @param eventType the type of event. See {@link IResourceDelta}.
    107          */
    108         void folderChanged(IProject project, ResourceFolder folder, int eventType);
    109     }
    110 
    111     private final ArrayList<IResourceListener> mListeners = new ArrayList<IResourceListener>();
    112 
    113     /**
    114      * Sets up the resource manager with the global project monitor.
    115      * @param monitor The global project monitor
    116      */
    117     public static void setup(GlobalProjectMonitor monitor) {
    118         monitor.addProjectListener(sThis.mProjectListener);
    119         monitor.addRawDeltaListener(sThis.mRawDeltaListener);
    120 
    121         CompiledResourcesMonitor.setupMonitor(monitor);
    122     }
    123 
    124     /**
    125      * Returns the singleton instance.
    126      */
    127     public static ResourceManager getInstance() {
    128         return sThis;
    129     }
    130 
    131     /**
    132      * Adds a new {@link IResourceListener} to be notified of resource changes.
    133      * @param listener the listener to be added.
    134      */
    135     public void addListener(IResourceListener listener) {
    136         synchronized (mListeners) {
    137             mListeners.add(listener);
    138         }
    139     }
    140 
    141     /**
    142      * Removes an {@link IResourceListener}, so that it's not notified of resource changes anymore.
    143      * @param listener the listener to be removed.
    144      */
    145     public void removeListener(IResourceListener listener) {
    146         synchronized (mListeners) {
    147             mListeners.remove(listener);
    148         }
    149     }
    150 
    151     /**
    152      * Returns the resources of a project.
    153      * @param project The project
    154      * @return a ProjectResources object or null if none was found.
    155      */
    156     public ProjectResources getProjectResources(IProject project) {
    157         synchronized (mMap) {
    158             ProjectResources resources = mMap.get(project);
    159 
    160             if (resources == null) {
    161                 resources = new ProjectResources(project);
    162                 mMap.put(project, resources);
    163             }
    164 
    165             return resources;
    166         }
    167     }
    168 
    169     /**
    170      * Update the resource repository with a delta
    171      *
    172      * @param delta the resource changed delta to process.
    173      * @param context a context object with state for the current update, such
    174      *            as a place to stash errors encountered
    175      */
    176     public void processDelta(IResourceDelta delta, IdeScanningContext context) {
    177         doProcessDelta(delta, context);
    178 
    179         // when a project is added to the workspace it is possible this is called before the
    180         // repo is actually created so this will return null.
    181         ResourceRepository repo = context.getRepository();
    182         if (repo != null) {
    183             repo.postUpdateCleanUp();
    184         }
    185     }
    186 
    187     /**
    188      * Update the resource repository with a delta
    189      *
    190      * @param delta the resource changed delta to process.
    191      * @param context a context object with state for the current update, such
    192      *            as a place to stash errors encountered
    193      */
    194     private void doProcessDelta(IResourceDelta delta, IdeScanningContext context) {
    195         // Skip over deltas that don't fit our mask
    196         int mask = IResourceDelta.ADDED | IResourceDelta.REMOVED | IResourceDelta.CHANGED;
    197         int kind = delta.getKind();
    198         if ( (mask & kind) == 0) {
    199             return;
    200         }
    201 
    202         // Process this delta first as we need to make sure new folders are created before
    203         // we process their content
    204         IResource r = delta.getResource();
    205         int type = r.getType();
    206 
    207         if (type == IResource.FILE) {
    208             context.startScanning(r);
    209             updateFile((IFile)r, delta.getMarkerDeltas(), kind, context);
    210             context.finishScanning(r);
    211         } else if (type == IResource.FOLDER) {
    212             updateFolder((IFolder)r, kind, context);
    213         } // We only care about files and folders.
    214           // Project deltas are handled by our project listener
    215 
    216         // Now, process children recursively
    217         IResourceDelta[] children = delta.getAffectedChildren();
    218         for (IResourceDelta child : children)  {
    219             processDelta(child, context);
    220         }
    221     }
    222 
    223     /**
    224      * Update a resource folder that we know about
    225      * @param folder the folder that was updated
    226      * @param kind the delta type (added/removed/updated)
    227      */
    228     private void updateFolder(IFolder folder, int kind, IdeScanningContext context) {
    229         ProjectResources resources;
    230 
    231         final IProject project = folder.getProject();
    232 
    233         try {
    234             if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
    235                 return;
    236             }
    237         } catch (CoreException e) {
    238             // can't get the project nature? return!
    239             return;
    240         }
    241 
    242         switch (kind) {
    243             case IResourceDelta.ADDED:
    244                 // checks if the folder is under res.
    245                 IPath path = folder.getFullPath();
    246 
    247                 // the path will be project/res/<something>
    248                 if (path.segmentCount() == 3) {
    249                     if (isInResFolder(path)) {
    250                         // get the project and its resource object.
    251                         synchronized (mMap) {
    252                             resources = mMap.get(project);
    253 
    254                             // if it doesn't exist, we create it.
    255                             if (resources == null) {
    256                                 resources = new ProjectResources(project);
    257                                 mMap.put(project, resources);
    258                             }
    259                         }
    260 
    261                         ResourceFolder newFolder = resources.processFolder(
    262                                 new IFolderWrapper(folder));
    263                         if (newFolder != null) {
    264                             notifyListenerOnFolderChange(project, newFolder, kind);
    265                         }
    266                     }
    267                 }
    268                 break;
    269             case IResourceDelta.CHANGED:
    270                 // only call the listeners.
    271                 synchronized (mMap) {
    272                     resources = mMap.get(folder.getProject());
    273                 }
    274                 if (resources != null) {
    275                     ResourceFolder resFolder = resources.getResourceFolder(folder);
    276                     if (resFolder != null) {
    277                         notifyListenerOnFolderChange(project, resFolder, kind);
    278                     }
    279                 }
    280                 break;
    281             case IResourceDelta.REMOVED:
    282                 synchronized (mMap) {
    283                     resources = mMap.get(folder.getProject());
    284                 }
    285                 if (resources != null) {
    286                     // lets get the folder type
    287                     ResourceFolderType type = ResourceFolderType.getFolderType(
    288                             folder.getName());
    289 
    290                     context.startScanning(folder);
    291                     ResourceFolder removedFolder = resources.removeFolder(type,
    292                             new IFolderWrapper(folder), context);
    293                     context.finishScanning(folder);
    294                     if (removedFolder != null) {
    295                         notifyListenerOnFolderChange(project, removedFolder, kind);
    296                     }
    297                 }
    298                 break;
    299         }
    300     }
    301 
    302     /**
    303      * Called when a delta indicates that a file has changed. Depending on the
    304      * file being changed, and the type of change (ADDED, REMOVED, CHANGED), the
    305      * file change is processed to update the resource manager data.
    306      *
    307      * @param file The file that changed.
    308      * @param markerDeltas The marker deltas for the file.
    309      * @param kind The change kind. This is equivalent to
    310      *            {@link IResourceDelta#accept(IResourceDeltaVisitor)}
    311      * @param context a context object with state for the current update, such
    312      *            as a place to stash errors encountered
    313      */
    314     private void updateFile(IFile file, IMarkerDelta[] markerDeltas, int kind,
    315             ScanningContext context) {
    316         final IProject project = file.getProject();
    317 
    318         try {
    319             if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
    320                 return;
    321             }
    322         } catch (CoreException e) {
    323             // can't get the project nature? return!
    324             return;
    325         }
    326 
    327         // get the project resources
    328         ProjectResources resources;
    329         synchronized (mMap) {
    330             resources = mMap.get(project);
    331         }
    332 
    333         if (resources == null) {
    334             return;
    335         }
    336 
    337         // checks if the file is under res/something or bin/res/something
    338         IPath path = file.getFullPath();
    339 
    340         if (path.segmentCount() == 4 || path.segmentCount() == 5) {
    341             if (isInResFolder(path)) {
    342                 IContainer container = file.getParent();
    343                 if (container instanceof IFolder) {
    344 
    345                     ResourceFolder folder = resources.getResourceFolder(
    346                             (IFolder)container);
    347 
    348                     // folder can be null as when the whole folder is deleted, the
    349                     // REMOVED event for the folder comes first. In this case, the
    350                     // folder will have taken care of things.
    351                     if (folder != null) {
    352                         ResourceFile resFile = folder.processFile(
    353                                 new IFileWrapper(file),
    354                                 ResourceHelper.getResourceDeltaKind(kind), context);
    355                         notifyListenerOnFileChange(project, resFile, kind);
    356                     }
    357                 }
    358             }
    359         }
    360     }
    361 
    362     /**
    363      * Implementation of the {@link IProjectListener} as an internal class so that the methods
    364      * do not appear in the public API of {@link ResourceManager}.
    365      */
    366     private final IProjectListener mProjectListener = new IProjectListener() {
    367         @Override
    368         public void projectClosed(IProject project) {
    369             synchronized (mMap) {
    370                 mMap.remove(project);
    371             }
    372         }
    373 
    374         @Override
    375         public void projectDeleted(IProject project) {
    376             synchronized (mMap) {
    377                 mMap.remove(project);
    378             }
    379         }
    380 
    381         @Override
    382         public void projectOpened(IProject project) {
    383             createProject(project);
    384         }
    385 
    386         @Override
    387         public void projectOpenedWithWorkspace(IProject project) {
    388             createProject(project);
    389         }
    390 
    391         @Override
    392         public void allProjectsOpenedWithWorkspace() {
    393             // nothing to do.
    394         }
    395 
    396         @Override
    397         public void projectRenamed(IProject project, IPath from) {
    398             // renamed project get a delete/open event too, so this can be ignored.
    399         }
    400     };
    401 
    402     /**
    403      * Implementation of {@link IRawDeltaListener} as an internal class so that the methods
    404      * do not appear in the public API of {@link ResourceManager}. Delta processing can be
    405      * accessed through the {@link ResourceManager#visitDelta(IResourceDelta delta)} method.
    406      */
    407     private final IRawDeltaListener mRawDeltaListener = new IRawDeltaListener() {
    408         @Override
    409         public void visitDelta(IResourceDelta workspaceDelta) {
    410             // If we're auto-building, then PreCompilerBuilder will pass us deltas and
    411             // they will be processed as part of the build.
    412             if (isAutoBuilding()) {
    413                 return;
    414             }
    415 
    416             // When *not* auto building, we need to process the deltas immediately on save,
    417             // even if the user is not building yet, such that for example resource ids
    418             // are updated in the resource repositories so rendering etc. can work for
    419             // those new ids.
    420 
    421             IResourceDelta[] projectDeltas = workspaceDelta.getAffectedChildren();
    422             for (IResourceDelta delta : projectDeltas) {
    423                 if (delta.getResource() instanceof IProject) {
    424                     IProject project = (IProject) delta.getResource();
    425                     IdeScanningContext context =
    426                             new IdeScanningContext(getProjectResources(project), project);
    427 
    428                     processDelta(delta, context);
    429 
    430                     Collection<IProject> projects = context.getAaptRequestedProjects();
    431                     if (projects != null) {
    432                         for (IProject p : projects) {
    433                             markAaptRequested(p);
    434                         }
    435                     }
    436                 } else {
    437                     AdtPlugin.log(IStatus.WARNING, "Unexpected delta type: %1$s",
    438                             delta.getResource().toString());
    439                 }
    440             }
    441         }
    442     };
    443 
    444     /**
    445      * Returns the {@link ResourceFolder} for the given file or <code>null</code> if none exists.
    446      */
    447     public ResourceFolder getResourceFolder(IFile file) {
    448         IContainer container = file.getParent();
    449         if (container.getType() == IResource.FOLDER) {
    450             IFolder parent = (IFolder)container;
    451             IProject project = file.getProject();
    452 
    453             ProjectResources resources = getProjectResources(project);
    454             if (resources != null) {
    455                 return resources.getResourceFolder(parent);
    456             }
    457         }
    458 
    459         return null;
    460     }
    461 
    462     /**
    463      * Returns the {@link ResourceFolder} for the given folder or <code>null</code> if none exists.
    464      */
    465     public ResourceFolder getResourceFolder(IFolder folder) {
    466         IProject project = folder.getProject();
    467 
    468         ProjectResources resources = getProjectResources(project);
    469         if (resources != null) {
    470             return resources.getResourceFolder(folder);
    471         }
    472 
    473         return null;
    474     }
    475 
    476     /**
    477      * Loads and returns the resources for a given {@link IAndroidTarget}
    478      * @param androidTarget the target from which to load the framework resources
    479      */
    480     public ResourceRepository loadFrameworkResources(IAndroidTarget androidTarget) {
    481         String osResourcesPath = androidTarget.getPath(IAndroidTarget.RESOURCES);
    482 
    483         FolderWrapper frameworkRes = new FolderWrapper(osResourcesPath);
    484         if (frameworkRes.exists()) {
    485             FrameworkResources resources = new FrameworkResources();
    486 
    487             try {
    488                 resources.loadResources(frameworkRes);
    489                 resources.loadPublicResources(frameworkRes, AdtPlugin.getDefault());
    490                 return resources;
    491             } catch (IOException e) {
    492                 // since we test that folders are folders, and files are files, this shouldn't
    493                 // happen. We can ignore it.
    494             }
    495         }
    496 
    497         return null;
    498     }
    499 
    500     /**
    501      * Initial project parsing to gather resource info.
    502      * @param project
    503      */
    504     private void createProject(IProject project) {
    505         if (project.isOpen()) {
    506             try {
    507                 if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
    508                     return;
    509                 }
    510             } catch (CoreException e1) {
    511                 // can't check the nature of the project? ignore it.
    512                 return;
    513             }
    514 
    515             IFolder resourceFolder = project.getFolder(SdkConstants.FD_RESOURCES);
    516 
    517             ProjectResources projectResources;
    518             synchronized (mMap) {
    519                 projectResources = mMap.get(project);
    520                 if (projectResources == null) {
    521                     projectResources = new ProjectResources(project);
    522                     mMap.put(project, projectResources);
    523                 }
    524             }
    525             IdeScanningContext context = new IdeScanningContext(projectResources, project);
    526 
    527             if (resourceFolder != null && resourceFolder.exists()) {
    528                 try {
    529                     IResource[] resources = resourceFolder.members();
    530 
    531                     for (IResource res : resources) {
    532                         if (res.getType() == IResource.FOLDER) {
    533                             IFolder folder = (IFolder)res;
    534                             ResourceFolder resFolder = projectResources.processFolder(
    535                                     new IFolderWrapper(folder));
    536 
    537                             if (resFolder != null) {
    538                                 // now we process the content of the folder
    539                                 IResource[] files = folder.members();
    540 
    541                                 for (IResource fileRes : files) {
    542                                     if (fileRes.getType() == IResource.FILE) {
    543                                         IFile file = (IFile)fileRes;
    544 
    545                                         context.startScanning(file);
    546 
    547                                         resFolder.processFile(new IFileWrapper(file),
    548                                                 ResourceHelper.getResourceDeltaKind(
    549                                                         IResourceDelta.ADDED), context);
    550 
    551                                         context.finishScanning(file);
    552                                     }
    553                                 }
    554                             }
    555                         }
    556                     }
    557                 } catch (CoreException e) {
    558                     // This happens if the project is closed or if the folder doesn't exist.
    559                     // Since we already test for that, we can ignore this exception.
    560                 }
    561             }
    562         }
    563     }
    564 
    565 
    566     /**
    567      * Returns true if the path is under /project/res/
    568      * @param path a workspace relative path
    569      * @return true if the path is under /project res/
    570      */
    571     private boolean isInResFolder(IPath path) {
    572         return SdkConstants.FD_RESOURCES.equalsIgnoreCase(path.segment(1));
    573     }
    574 
    575     private void notifyListenerOnFolderChange(IProject project, ResourceFolder folder,
    576             int eventType) {
    577         synchronized (mListeners) {
    578             for (IResourceListener listener : mListeners) {
    579                 try {
    580                     listener.folderChanged(project, folder, eventType);
    581                 } catch (Throwable t) {
    582                     AdtPlugin.log(t,
    583                             "Failed to execute ResourceManager.IResouceListener.folderChanged()"); //$NON-NLS-1$
    584                 }
    585             }
    586         }
    587     }
    588 
    589     private void notifyListenerOnFileChange(IProject project, ResourceFile file, int eventType) {
    590         synchronized (mListeners) {
    591             for (IResourceListener listener : mListeners) {
    592                 try {
    593                     listener.fileChanged(project, file, eventType);
    594                 } catch (Throwable t) {
    595                     AdtPlugin.log(t,
    596                             "Failed to execute ResourceManager.IResouceListener.fileChanged()"); //$NON-NLS-1$
    597                 }
    598             }
    599         }
    600     }
    601 
    602     /**
    603      * Private constructor to enforce singleton design.
    604      */
    605     private ResourceManager() {
    606     }
    607 
    608     // debug only
    609     @SuppressWarnings("unused")
    610     private String getKindString(int kind) {
    611         if (DEBUG) {
    612             switch (kind) {
    613                 case IResourceDelta.ADDED: return "ADDED";
    614                 case IResourceDelta.REMOVED: return "REMOVED";
    615                 case IResourceDelta.CHANGED: return "CHANGED";
    616             }
    617         }
    618 
    619         return Integer.toString(kind);
    620     }
    621 
    622     /**
    623      * Returns true if the Project > Build Automatically option is turned on
    624      * (default).
    625      *
    626      * @return true if the Project > Build Automatically option is turned on
    627      *         (default).
    628      */
    629     public static boolean isAutoBuilding() {
    630         return ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding();
    631     }
    632 
    633     /** Qualified name for the per-project persistent property "needs aapt" */
    634     private final static QualifiedName NEED_AAPT = new QualifiedName(AdtPlugin.PLUGIN_ID,
    635             "aapt");//$NON-NLS-1$
    636 
    637     /**
    638      * Mark the given project, and any projects which depend on it as a library
    639      * project, as needing a full aapt build the next time the project is built.
    640      *
    641      * @param project the project to mark as needing aapt
    642      */
    643     public static void markAaptRequested(IProject project) {
    644         try {
    645             String needsAapt = Boolean.TRUE.toString();
    646             project.setPersistentProperty(NEED_AAPT, needsAapt);
    647 
    648             ProjectState state = Sdk.getProjectState(project);
    649             if (state.isLibrary()) {
    650                 // For library projects also mark the dependent projects as needing full aapt
    651                 for (ProjectState parent : state.getFullParentProjects()) {
    652                     IProject parentProject = parent.getProject();
    653                     // Mark the project, but only if it's open. Resource#setPersistentProperty
    654                     // only works on open projects.
    655                     if (parentProject.isOpen()) {
    656                         parentProject.setPersistentProperty(NEED_AAPT, needsAapt);
    657                     }
    658                 }
    659             }
    660         } catch (CoreException e) {
    661             AdtPlugin.log(e,  null);
    662         }
    663     }
    664 
    665     /**
    666      * Clear the "needs aapt" flag set by {@link #markAaptRequested(IProject)}.
    667      * This is usually called when a project is built. Note that this will only
    668      * clean the build flag on the given project, not on any downstream projects
    669      * that depend on this project as a library project.
    670      *
    671      * @param project the project to clear from the needs aapt list
    672      */
    673     public static void clearAaptRequest(IProject project) {
    674         try {
    675             project.setPersistentProperty(NEED_AAPT, null);
    676             // Note that even if this project is a library project, we -don't- clear
    677             // the aapt flags on the dependent projects since they may still depend
    678             // on other dirty projects. When they are built, they will issue their
    679             // own clear flag requests.
    680         } catch (CoreException e) {
    681             AdtPlugin.log(e,  null);
    682         }
    683     }
    684 
    685     /**
    686      * Returns whether the given project needs a full aapt build.
    687      *
    688      * @param project the project to check
    689      * @return true if the project needs a full aapt run
    690      */
    691     public static boolean isAaptRequested(IProject project) {
    692         try {
    693             String b = project.getPersistentProperty(NEED_AAPT);
    694             return b != null && Boolean.valueOf(b);
    695         } catch (CoreException e) {
    696             AdtPlugin.log(e,  null);
    697         }
    698 
    699         return false;
    700     }
    701 }
    702