• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
fileChanged(IProject project, ResourceFile file, int eventType)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          */
folderChanged(IProject project, ResourceFolder folder, int eventType)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      */
setup(GlobalProjectMonitor monitor)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      */
getInstance()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      */
addListener(IResourceListener listener)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      */
removeListener(IResourceListener listener)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      */
getProjectResources(IProject project)156     public ProjectResources getProjectResources(IProject project) {
157         synchronized (mMap) {
158             return mMap.get(project);
159         }
160     }
161 
162     /**
163      * Update the resource repository with a delta
164      *
165      * @param delta the resource changed delta to process.
166      * @param context a context object with state for the current update, such
167      *            as a place to stash errors encountered
168      */
processDelta(IResourceDelta delta, IdeScanningContext context)169     public void processDelta(IResourceDelta delta, IdeScanningContext context) {
170         // Skip over deltas that don't fit our mask
171         int mask = IResourceDelta.ADDED | IResourceDelta.REMOVED | IResourceDelta.CHANGED;
172         int kind = delta.getKind();
173         if ( (mask & kind) == 0) {
174             return;
175         }
176 
177         // Process children recursively
178         IResourceDelta[] children = delta.getAffectedChildren();
179         for (IResourceDelta child : children)  {
180             processDelta(child, context);
181         }
182 
183         // Process this delta
184         IResource r = delta.getResource();
185         int type = r.getType();
186 
187         if (type == IResource.FILE) {
188             context.startScanning(r);
189             updateFile((IFile)r, delta.getMarkerDeltas(), kind, context);
190             context.finishScanning(r);
191         } else if (type == IResource.FOLDER) {
192             updateFolder((IFolder)r, kind, context);
193         } // We only care about files and folders.
194           // Project deltas are handled by our project listener
195     }
196 
197     /**
198      * Update a resource folder that we know about
199      * @param folder the folder that was updated
200      * @param kind the delta type (added/removed/updated)
201      */
updateFolder(IFolder folder, int kind, IdeScanningContext context)202     private void updateFolder(IFolder folder, int kind, IdeScanningContext context) {
203         ProjectResources resources;
204 
205         final IProject project = folder.getProject();
206 
207         try {
208             if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
209                 return;
210             }
211         } catch (CoreException e) {
212             // can't get the project nature? return!
213             return;
214         }
215 
216         switch (kind) {
217             case IResourceDelta.ADDED:
218                 // checks if the folder is under res.
219                 IPath path = folder.getFullPath();
220 
221                 // the path will be project/res/<something>
222                 if (path.segmentCount() == 3) {
223                     if (isInResFolder(path)) {
224                         // get the project and its resource object.
225                         synchronized (mMap) {
226                             resources = mMap.get(project);
227 
228                             // if it doesn't exist, we create it.
229                             if (resources == null) {
230                                 resources = new ProjectResources(project);
231                                 mMap.put(project, resources);
232                             }
233                         }
234 
235                         ResourceFolder newFolder = resources.processFolder(
236                                 new IFolderWrapper(folder));
237                         if (newFolder != null) {
238                             notifyListenerOnFolderChange(project, newFolder, kind);
239                         }
240                     }
241                 }
242                 break;
243             case IResourceDelta.CHANGED:
244                 // only call the listeners.
245                 synchronized (mMap) {
246                     resources = mMap.get(folder.getProject());
247                 }
248                 if (resources != null) {
249                     ResourceFolder resFolder = resources.getResourceFolder(folder);
250                     if (resFolder != null) {
251                         notifyListenerOnFolderChange(project, resFolder, kind);
252                     }
253                 }
254                 break;
255             case IResourceDelta.REMOVED:
256                 synchronized (mMap) {
257                     resources = mMap.get(folder.getProject());
258                 }
259                 if (resources != null) {
260                     // lets get the folder type
261                     ResourceFolderType type = ResourceFolderType.getFolderType(
262                             folder.getName());
263 
264                     context.startScanning(folder);
265                     ResourceFolder removedFolder = resources.removeFolder(type,
266                             new IFolderWrapper(folder), context);
267                     context.finishScanning(folder);
268                     if (removedFolder != null) {
269                         notifyListenerOnFolderChange(project, removedFolder, kind);
270                     }
271                 }
272                 break;
273         }
274     }
275 
276     /**
277      * Called when a delta indicates that a file has changed. Depending on the
278      * file being changed, and the type of change (ADDED, REMOVED, CHANGED), the
279      * file change is processed to update the resource manager data.
280      *
281      * @param file The file that changed.
282      * @param markerDeltas The marker deltas for the file.
283      * @param kind The change kind. This is equivalent to
284      *            {@link IResourceDelta#accept(IResourceDeltaVisitor)}
285      * @param context a context object with state for the current update, such
286      *            as a place to stash errors encountered
287      */
updateFile(IFile file, IMarkerDelta[] markerDeltas, int kind, ScanningContext context)288     private void updateFile(IFile file, IMarkerDelta[] markerDeltas, int kind,
289             ScanningContext context) {
290         final IProject project = file.getProject();
291 
292         try {
293             if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
294                 return;
295             }
296         } catch (CoreException e) {
297             // can't get the project nature? return!
298             return;
299         }
300 
301         // get the project resources
302         ProjectResources resources;
303         synchronized (mMap) {
304             resources = mMap.get(project);
305         }
306 
307         if (resources == null) {
308             return;
309         }
310 
311         // checks if the file is under res/something or bin/res/something
312         IPath path = file.getFullPath();
313 
314         if (path.segmentCount() == 4 || path.segmentCount() == 5) {
315             if (isInResFolder(path)) {
316                 IContainer container = file.getParent();
317                 if (container instanceof IFolder) {
318 
319                     ResourceFolder folder = resources.getResourceFolder(
320                             (IFolder)container);
321 
322                     // folder can be null as when the whole folder is deleted, the
323                     // REMOVED event for the folder comes first. In this case, the
324                     // folder will have taken care of things.
325                     if (folder != null) {
326                         ResourceFile resFile = folder.processFile(
327                                 new IFileWrapper(file),
328                                 ResourceHelper.getResourceDeltaKind(kind), context);
329                         notifyListenerOnFileChange(project, resFile, kind);
330                     }
331                 }
332             }
333         }
334     }
335 
336     /**
337      * Implementation of the {@link IProjectListener} as an internal class so that the methods
338      * do not appear in the public API of {@link ResourceManager}.
339      */
340     private final IProjectListener mProjectListener = new IProjectListener() {
341         public void projectClosed(IProject project) {
342             synchronized (mMap) {
343                 mMap.remove(project);
344             }
345         }
346 
347         public void projectDeleted(IProject project) {
348             synchronized (mMap) {
349                 mMap.remove(project);
350             }
351         }
352 
353         public void projectOpened(IProject project) {
354             createProject(project);
355         }
356 
357         public void projectOpenedWithWorkspace(IProject project) {
358             createProject(project);
359         }
360 
361         public void projectRenamed(IProject project, IPath from) {
362             // renamed project get a delete/open event too, so this can be ignored.
363         }
364     };
365 
366     /**
367      * Implementation of {@link IRawDeltaListener} as an internal class so that the methods
368      * do not appear in the public API of {@link ResourceManager}. Delta processing can be
369      * accessed through the {@link ResourceManager#visitDelta(IResourceDelta delta)} method.
370      */
371     private final IRawDeltaListener mRawDeltaListener = new IRawDeltaListener() {
372         public void visitDelta(IResourceDelta workspaceDelta) {
373             // If we're auto-building, then PreCompilerBuilder will pass us deltas and
374             // they will be processed as part of the build.
375             if (isAutoBuilding()) {
376                 return;
377             }
378 
379             // When *not* auto building, we need to process the deltas immediately on save,
380             // even if the user is not building yet, such that for example resource ids
381             // are updated in the resource repositories so rendering etc. can work for
382             // those new ids.
383 
384             IResourceDelta[] projectDeltas = workspaceDelta.getAffectedChildren();
385             for (IResourceDelta delta : projectDeltas) {
386                 if (delta.getResource() instanceof IProject) {
387                     IProject project = (IProject) delta.getResource();
388                     IdeScanningContext context =
389                             new IdeScanningContext(getProjectResources(project), project);
390 
391                     processDelta(delta, context);
392 
393                     Collection<IProject> projects = context.getAaptRequestedProjects();
394                     if (projects != null) {
395                         for (IProject p : projects) {
396                             markAaptRequested(p);
397                         }
398                     }
399                 } else {
400                     AdtPlugin.log(IStatus.WARNING, "Unexpected delta type: %1$s",
401                             delta.getResource().toString());
402                 }
403             }
404         }
405     };
406 
407     /**
408      * Returns the {@link ResourceFolder} for the given file or <code>null</code> if none exists.
409      */
getResourceFolder(IFile file)410     public ResourceFolder getResourceFolder(IFile file) {
411         IContainer container = file.getParent();
412         if (container.getType() == IResource.FOLDER) {
413             IFolder parent = (IFolder)container;
414             IProject project = file.getProject();
415 
416             ProjectResources resources = getProjectResources(project);
417             if (resources != null) {
418                 return resources.getResourceFolder(parent);
419             }
420         }
421 
422         return null;
423     }
424 
425     /**
426      * Returns the {@link ResourceFolder} for the given folder or <code>null</code> if none exists.
427      */
getResourceFolder(IFolder folder)428     public ResourceFolder getResourceFolder(IFolder folder) {
429         IProject project = folder.getProject();
430 
431         ProjectResources resources = getProjectResources(project);
432         if (resources != null) {
433             return resources.getResourceFolder(folder);
434         }
435 
436         return null;
437     }
438 
439     /**
440      * Loads and returns the resources for a given {@link IAndroidTarget}
441      * @param androidTarget the target from which to load the framework resources
442      */
loadFrameworkResources(IAndroidTarget androidTarget)443     public ResourceRepository loadFrameworkResources(IAndroidTarget androidTarget) {
444         String osResourcesPath = androidTarget.getPath(IAndroidTarget.RESOURCES);
445 
446         FolderWrapper frameworkRes = new FolderWrapper(osResourcesPath);
447         if (frameworkRes.exists()) {
448             FrameworkResources resources = new FrameworkResources();
449 
450             try {
451                 resources.loadResources(frameworkRes);
452                 resources.loadPublicResources(frameworkRes, AdtPlugin.getDefault());
453                 return resources;
454             } catch (IOException e) {
455                 // since we test that folders are folders, and files are files, this shouldn't
456                 // happen. We can ignore it.
457             }
458         }
459 
460         return null;
461     }
462 
463     /**
464      * Initial project parsing to gather resource info.
465      * @param project
466      */
createProject(IProject project)467     private void createProject(IProject project) {
468         if (project.isOpen()) {
469             try {
470                 if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
471                     return;
472                 }
473             } catch (CoreException e1) {
474                 // can't check the nature of the project? ignore it.
475                 return;
476             }
477 
478             IFolder resourceFolder = project.getFolder(SdkConstants.FD_RESOURCES);
479 
480             ProjectResources projectResources;
481             synchronized (mMap) {
482                 projectResources = mMap.get(project);
483                 if (projectResources == null) {
484                     projectResources = new ProjectResources(project);
485                     mMap.put(project, projectResources);
486                 }
487             }
488             IdeScanningContext context = new IdeScanningContext(projectResources, project);
489 
490             if (resourceFolder != null && resourceFolder.exists()) {
491                 try {
492                     IResource[] resources = resourceFolder.members();
493 
494                     for (IResource res : resources) {
495                         if (res.getType() == IResource.FOLDER) {
496                             IFolder folder = (IFolder)res;
497                             ResourceFolder resFolder = projectResources.processFolder(
498                                     new IFolderWrapper(folder));
499 
500                             if (resFolder != null) {
501                                 // now we process the content of the folder
502                                 IResource[] files = folder.members();
503 
504                                 for (IResource fileRes : files) {
505                                     if (fileRes.getType() == IResource.FILE) {
506                                         IFile file = (IFile)fileRes;
507 
508                                         context.startScanning(file);
509 
510                                         resFolder.processFile(new IFileWrapper(file),
511                                                 ResourceHelper.getResourceDeltaKind(
512                                                         IResourceDelta.ADDED), context);
513 
514                                         context.finishScanning(file);
515                                     }
516                                 }
517                             }
518                         }
519                     }
520                 } catch (CoreException e) {
521                     // This happens if the project is closed or if the folder doesn't exist.
522                     // Since we already test for that, we can ignore this exception.
523                 }
524             }
525         }
526     }
527 
528 
529     /**
530      * Returns true if the path is under /project/res/
531      * @param path a workspace relative path
532      * @return true if the path is under /project res/
533      */
isInResFolder(IPath path)534     private boolean isInResFolder(IPath path) {
535         return SdkConstants.FD_RESOURCES.equalsIgnoreCase(path.segment(1));
536     }
537 
notifyListenerOnFolderChange(IProject project, ResourceFolder folder, int eventType)538     private void notifyListenerOnFolderChange(IProject project, ResourceFolder folder,
539             int eventType) {
540         synchronized (mListeners) {
541             for (IResourceListener listener : mListeners) {
542                 try {
543                     listener.folderChanged(project, folder, eventType);
544                 } catch (Throwable t) {
545                     AdtPlugin.log(t,
546                             "Failed to execute ResourceManager.IResouceListener.folderChanged()"); //$NON-NLS-1$
547                 }
548             }
549         }
550     }
551 
notifyListenerOnFileChange(IProject project, ResourceFile file, int eventType)552     private void notifyListenerOnFileChange(IProject project, ResourceFile file, int eventType) {
553         synchronized (mListeners) {
554             for (IResourceListener listener : mListeners) {
555                 try {
556                     listener.fileChanged(project, file, eventType);
557                 } catch (Throwable t) {
558                     AdtPlugin.log(t,
559                             "Failed to execute ResourceManager.IResouceListener.fileChanged()"); //$NON-NLS-1$
560                 }
561             }
562         }
563     }
564 
565     /**
566      * Private constructor to enforce singleton design.
567      */
ResourceManager()568     private ResourceManager() {
569     }
570 
571     // debug only
572     @SuppressWarnings("unused")
getKindString(int kind)573     private String getKindString(int kind) {
574         if (DEBUG) {
575             switch (kind) {
576                 case IResourceDelta.ADDED: return "ADDED";
577                 case IResourceDelta.REMOVED: return "REMOVED";
578                 case IResourceDelta.CHANGED: return "CHANGED";
579             }
580         }
581 
582         return Integer.toString(kind);
583     }
584 
585     /**
586      * Returns true if the Project > Build Automatically option is turned on
587      * (default).
588      *
589      * @return true if the Project > Build Automatically option is turned on
590      *         (default).
591      */
isAutoBuilding()592     public static boolean isAutoBuilding() {
593         return ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding();
594     }
595 
596     /** Qualified name for the per-project persistent property "needs aapt" */
597     private final static QualifiedName NEED_AAPT = new QualifiedName(AdtPlugin.PLUGIN_ID,
598             "aapt");//$NON-NLS-1$
599 
600     /**
601      * Mark the given project, and any projects which depend on it as a library
602      * project, as needing a full aapt build the next time the project is built.
603      *
604      * @param project the project to mark as needing aapt
605      */
markAaptRequested(IProject project)606     public static void markAaptRequested(IProject project) {
607         try {
608             String needsAapt = Boolean.TRUE.toString();
609             project.setPersistentProperty(NEED_AAPT, needsAapt);
610 
611             ProjectState state = Sdk.getProjectState(project);
612             if (state.isLibrary()) {
613                 // For library projects also mark the dependent projects as needing full aapt
614                 for (ProjectState parent : state.getFullParentProjects()) {
615                     IProject parentProject = parent.getProject();
616                     // Mark the project, but only if it's open. Resource#setPersistentProperty
617                     // only works on open projects.
618                     if (parentProject.isOpen()) {
619                         parentProject.setPersistentProperty(NEED_AAPT, needsAapt);
620                     }
621                 }
622             }
623         } catch (CoreException e) {
624             AdtPlugin.log(e,  null);
625         }
626     }
627 
628     /**
629      * Clear the "needs aapt" flag set by {@link #markAaptRequested(IProject)}.
630      * This is usually called when a project is built. Note that this will only
631      * clean the build flag on the given project, not on any downstream projects
632      * that depend on this project as a library project.
633      *
634      * @param project the project to clear from the needs aapt list
635      */
clearAaptRequest(IProject project)636     public static void clearAaptRequest(IProject project) {
637         try {
638             project.setPersistentProperty(NEED_AAPT, null);
639             // Note that even if this project is a library project, we -don't- clear
640             // the aapt flags on the dependent projects since they may still depend
641             // on other dirty projects. When they are built, they will issue their
642             // own clear flag requests.
643         } catch (CoreException e) {
644             AdtPlugin.log(e,  null);
645         }
646     }
647 
648     /**
649      * Returns whether the given project needs a full aapt build.
650      *
651      * @param project the project to check
652      * @return true if the project needs a full aapt run
653      */
isAaptRequested(IProject project)654     public static boolean isAaptRequested(IProject project) {
655         try {
656             String b = project.getPersistentProperty(NEED_AAPT);
657             return b != null && Boolean.valueOf(b);
658         } catch (CoreException e) {
659             AdtPlugin.log(e,  null);
660         }
661 
662         return false;
663     }
664 }
665