OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / sdk / eclipse / plugins / com.android.ide.eclipse.adt / src / com / android / ide / eclipse / adt / internal / resources / manager / ResourceManager.java
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.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.AndroidConstants;
21 import com.android.ide.eclipse.adt.internal.resources.ResourceType;
22 import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
23 import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier;
24 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
25 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFolderListener;
26 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
27 import com.android.ide.eclipse.adt.io.IFileWrapper;
28 import com.android.ide.eclipse.adt.io.IFolderWrapper;
29 import com.android.sdklib.IAndroidTarget;
30 import com.android.sdklib.SdkConstants;
31 import com.android.sdklib.io.FolderWrapper;
32 import com.android.sdklib.io.IAbstractFile;
33 import com.android.sdklib.io.IAbstractFolder;
34 import com.android.sdklib.io.IAbstractResource;
35
36 import org.eclipse.core.resources.IContainer;
37 import org.eclipse.core.resources.IFile;
38 import org.eclipse.core.resources.IFolder;
39 import org.eclipse.core.resources.IMarkerDelta;
40 import org.eclipse.core.resources.IProject;
41 import org.eclipse.core.resources.IResource;
42 import org.eclipse.core.resources.IResourceDelta;
43 import org.eclipse.core.runtime.CoreException;
44 import org.eclipse.core.runtime.IPath;
45
46 import java.io.File;
47 import java.io.IOException;
48 import java.util.ArrayList;
49 import java.util.HashMap;
50
51 /**
52  * The ResourceManager tracks resources for all opened projects.
53  * <p/>
54  * It provide direct access to all the resources of a project as a {@link ProjectResources}
55  * object that allows accessing the resources through their file representation or as Android
56  * resources (similar to what is seen by an Android application).
57  * <p/>
58  * The ResourceManager automatically tracks file changes to update its internal representation
59  * of the resources so that they are always up to date.
60  * <p/>
61  * It also gives access to a monitor that is more resource oriented than the
62  * {@link GlobalProjectMonitor}.
63  * This monitor will let you track resource changes by giving you direct access to
64  * {@link ResourceFile}, or {@link ResourceFolder}.
65  *
66  * @see ProjectResources
67  */
68 public final class ResourceManager {
69
70     private final static ResourceManager sThis = new ResourceManager();
71
72     /** List of the qualifier object helping for the parsing of folder names */
73     private final ResourceQualifier[] mQualifiers;
74
75     /**
76      * Map associating project resource with project objects.
77      * <p/><b>All accesses must be inside a synchronized(mMap) block</b>, and do as a little as
78      * possible and <b>not call out to other classes</b>.
79      */
80     private final HashMap<IProject, ProjectResources> mMap =
81         new HashMap<IProject, ProjectResources>();
82
83     /**
84      * Interface to be notified of resource changes.
85      *
86      * @see ResourceManager#addListener(IResourceListener)
87      * @see ResourceManager#removeListener(IResource)
88      */
89     public interface IResourceListener {
90         /**
91          * Notification for resource file change.
92          * @param project the project of the file.
93          * @param file the {@link ResourceFile} representing the file.
94          * @param eventType the type of event. See {@link IResourceDelta}.
95          */
96         void fileChanged(IProject project, ResourceFile file, int eventType);
97         /**
98          * Notification for resource folder change.
99          * @param project the project of the file.
100          * @param folder the {@link ResourceFolder} representing the folder.
101          * @param eventType the type of event. See {@link IResourceDelta}.
102          */
103         void folderChanged(IProject project, ResourceFolder folder, int eventType);
104     }
105
106     private final ArrayList<IResourceListener> mListeners = new ArrayList<IResourceListener>();
107
108     /**
109      * Sets up the resource manager with the global project monitor.
110      * @param monitor The global project monitor
111      */
112     public static void setup(GlobalProjectMonitor monitor) {
113         monitor.addProjectListener(sThis.mProjectListener);
114         int mask = IResourceDelta.ADDED | IResourceDelta.REMOVED | IResourceDelta.CHANGED;
115         monitor.addFolderListener(sThis.mFolderListener, mask);
116         monitor.addFileListener(sThis.mFileListener, mask);
117
118         CompiledResourcesMonitor.setupMonitor(monitor);
119     }
120
121     /**
122      * Returns the singleton instance.
123      */
124     public static ResourceManager getInstance() {
125         return sThis;
126     }
127
128     /**
129      * Adds a new {@link IResourceListener} to be notified of resource changes.
130      * @param listener the listener to be added.
131      */
132     public void addListener(IResourceListener listener) {
133         synchronized (mListeners) {
134             mListeners.add(listener);
135         }
136     }
137
138     /**
139      * Removes an {@link IResourceListener}, so that it's not notified of resource changes anymore.
140      * @param listener the listener to be removed.
141      */
142     public void removeListener(IResource listener) {
143         synchronized (mListeners) {
144             mListeners.remove(listener);
145         }
146     }
147
148     /**
149      * Returns the resources of a project.
150      * @param project The project
151      * @return a ProjectResources object or null if none was found.
152      */
153     public ProjectResources getProjectResources(IProject project) {
154         synchronized (mMap) {
155             return mMap.get(project);
156         }
157     }
158
159     /**
160      * Implementation of the {@link IFolderListener} as an internal class so that the methods
161      * do not appear in the public API of {@link ResourceManager}.
162      */
163     private IFolderListener mFolderListener = new IFolderListener() {
164         public void folderChanged(IFolder folder, int kind) {
165             ProjectResources resources;
166
167             final IProject project = folder.getProject();
168
169             try {
170                 if (project.hasNature(AndroidConstants.NATURE_DEFAULT) == false) {
171                     return;
172                 }
173             } catch (CoreException e) {
174                 // can't get the project nature? return!
175                 return;
176             }
177
178             switch (kind) {
179                 case IResourceDelta.ADDED:
180                     // checks if the folder is under res.
181                     IPath path = folder.getFullPath();
182
183                     // the path will be project/res/<something>
184                     if (path.segmentCount() == 3) {
185                         if (isInResFolder(path)) {
186                             // get the project and its resource object.
187                             synchronized (mMap) {
188                                 resources = mMap.get(project);
189
190                                 // if it doesn't exist, we create it.
191                                 if (resources == null) {
192                                     resources = new ProjectResources(project);
193                                     mMap.put(project, resources);
194                                 }
195                             }
196
197                             ResourceFolder newFolder = processFolder(new IFolderWrapper(folder),
198                                     resources);
199                             if (newFolder != null) {
200                                 notifyListenerOnFolderChange(project, newFolder, kind);
201                             }
202                         }
203                     }
204                     break;
205                 case IResourceDelta.CHANGED:
206                     synchronized (mMap) {
207                         resources = mMap.get(folder.getProject());
208                     }
209                     if (resources != null) {
210                         ResourceFolder resFolder = resources.getResourceFolder(folder);
211                         if (resFolder != null) {
212                             resFolder.touch();
213                             notifyListenerOnFolderChange(project, resFolder, kind);
214                         }
215                     }
216                     break;
217                 case IResourceDelta.REMOVED:
218                     synchronized (mMap) {
219                         resources = mMap.get(folder.getProject());
220                     }
221                     if (resources != null) {
222                         // lets get the folder type
223                         ResourceFolderType type = ResourceFolderType.getFolderType(
224                                 folder.getName());
225
226                         ResourceFolder removedFolder = resources.removeFolder(type, folder);
227                         if (removedFolder != null) {
228                             notifyListenerOnFolderChange(project, removedFolder, kind);
229                         }
230                     }
231                     break;
232             }
233         }
234     };
235
236     /**
237      * Implementation of the {@link IFileListener} as an internal class so that the methods
238      * do not appear in the public API of {@link ResourceManager}.
239      */
240     private IFileListener mFileListener = new IFileListener() {
241         /* (non-Javadoc)
242          * Sent when a file changed. Depending on the file being changed, and the type of change
243          * (ADDED, REMOVED, CHANGED), the file change is processed to update the resource
244          * manager data.
245          *
246          * @param file The file that changed.
247          * @param markerDeltas The marker deltas for the file.
248          * @param kind The change kind. This is equivalent to
249          * {@link IResourceDelta#accept(IResourceDeltaVisitor)}
250          *
251          * @see IFileListener#fileChanged
252          */
253         public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
254             ProjectResources resources;
255
256             final IProject project = file.getProject();
257
258             try {
259                 if (project.hasNature(AndroidConstants.NATURE_DEFAULT) == false) {
260                     return;
261                 }
262             } catch (CoreException e) {
263                 // can't get the project nature? return!
264                 return;
265             }
266
267             switch (kind) {
268                 case IResourceDelta.ADDED:
269                     // checks if the file is under res/something.
270                     IPath path = file.getFullPath();
271
272                     if (path.segmentCount() == 4) {
273                         if (isInResFolder(path)) {
274                             // get the project and its resources
275                             synchronized (mMap) {
276                                 resources = mMap.get(project);
277                             }
278
279                             IContainer container = file.getParent();
280                             if (container instanceof IFolder && resources != null) {
281
282                                 ResourceFolder folder = resources.getResourceFolder(
283                                         (IFolder)container);
284
285                                 if (folder != null) {
286                                     ResourceFile resFile = processFile(
287                                             new IFileWrapper(file), folder);
288                                     notifyListenerOnFileChange(project, resFile, kind);
289                                 }
290                             }
291                         }
292                     }
293                     break;
294                 case IResourceDelta.CHANGED:
295                     // try to find a matching ResourceFile
296                     synchronized (mMap) {
297                         resources = mMap.get(project);
298                     }
299                     if (resources != null) {
300                         IContainer container = file.getParent();
301                         if (container instanceof IFolder) {
302                             ResourceFolder resFolder = resources.getResourceFolder(
303                                     (IFolder)container);
304
305                             // we get the delete on the folder before the file, so it is possible
306                             // the associated ResourceFolder doesn't exist anymore.
307                             if (resFolder != null) {
308                                 // get the resourceFile, and touch it.
309                                 ResourceFile resFile = resFolder.getFile(file);
310                                 if (resFile != null) {
311                                     resFile.touch();
312                                     notifyListenerOnFileChange(project, resFile, kind);
313                                 }
314                             }
315                         }
316                     }
317                     break;
318                 case IResourceDelta.REMOVED:
319                     // try to find a matching ResourceFile
320                     synchronized (mMap) {
321                         resources = mMap.get(project);
322                     }
323                     if (resources != null) {
324                         IContainer container = file.getParent();
325                         if (container instanceof IFolder) {
326                             ResourceFolder resFolder = resources.getResourceFolder(
327                                     (IFolder)container);
328
329                             // we get the delete on the folder before the file, so it is possible
330                             // the associated ResourceFolder doesn't exist anymore.
331                             if (resFolder != null) {
332                                 // remove the file
333                                 ResourceFile resFile = resFolder.removeFile(file);
334                                 if (resFile != null) {
335                                     notifyListenerOnFileChange(project, resFile, kind);
336                                 }
337                             }
338                         }
339                     }
340                     break;
341             }
342         }
343     };
344
345
346     /**
347      * Implementation of the {@link IProjectListener} as an internal class so that the methods
348      * do not appear in the public API of {@link ResourceManager}.
349      */
350     private IProjectListener mProjectListener = new IProjectListener() {
351         public void projectClosed(IProject project) {
352             synchronized (mMap) {
353                 mMap.remove(project);
354             }
355         }
356
357         public void projectDeleted(IProject project) {
358             synchronized (mMap) {
359                 mMap.remove(project);
360             }
361         }
362
363         public void projectOpened(IProject project) {
364             createProject(project);
365         }
366
367         public void projectOpenedWithWorkspace(IProject project) {
368             createProject(project);
369         }
370
371         public void projectRenamed(IProject project, IPath from) {
372             // renamed project get a delete/open event too, so this can be ignored.
373         }
374     };
375
376     /**
377      * Returns the {@link ResourceFolder} for the given file or <code>null</code> if none exists.
378      */
379     public ResourceFolder getResourceFolder(IFile file) {
380         IContainer container = file.getParent();
381         if (container.getType() == IResource.FOLDER) {
382             IFolder parent = (IFolder)container;
383             IProject project = file.getProject();
384
385             ProjectResources resources = getProjectResources(project);
386             if (resources != null) {
387                 return resources.getResourceFolder(parent);
388             }
389         }
390
391         return null;
392     }
393
394     /**
395      * Returns the {@link ResourceFolder} for the given folder or <code>null</code> if none exists.
396      */
397     public ResourceFolder getResourceFolder(IFolder folder) {
398         IProject project = folder.getProject();
399
400         ProjectResources resources = getProjectResources(project);
401         if (resources != null) {
402             return resources.getResourceFolder(folder);
403         }
404
405         return null;
406     }
407
408     /**
409      * Loads and returns the resources for a given {@link IAndroidTarget}
410      * @param androidTarget the target from which to load the framework resources
411      */
412     public ProjectResources loadFrameworkResources(IAndroidTarget androidTarget) {
413         String osResourcesPath = androidTarget.getPath(IAndroidTarget.RESOURCES);
414
415         FolderWrapper frameworkRes = new FolderWrapper(osResourcesPath);
416         if (frameworkRes.exists()) {
417             ProjectResources resources = new ProjectResources();
418
419             try {
420                 loadResources(resources, frameworkRes);
421                 return resources;
422             } catch (IOException e) {
423                 // since we test that folders are folders, and files are files, this shouldn't
424                 // happen. We can ignore it.
425             }
426         }
427
428         return null;
429     }
430
431     /**
432      * Loads the resources from a folder, and fills the given {@link ProjectResources}.
433      * <p/>
434      * This is mostly a utility method that should not be used to process actual Eclipse projects
435      * (Those are loaded with {@link #createProject(IProject)} for new project or
436      * {@link #processFolder(IAbstractFolder, ProjectResources)} and
437      * {@link #processFile(IAbstractFile, ResourceFolder)} for folder/file modifications)<br>
438      * This method will process files/folders with implementations of {@link IAbstractFile} and
439      * {@link IAbstractFolder} based on {@link File} instead of {@link IFile} and {@link IFolder}
440      * respectively. This is not proper for handling {@link IProject}s.
441      * </p>
442      * This is used to load the framework resources, or to do load project resources when
443      * setting rendering tests.
444      *
445      *
446      * @param resources The {@link ProjectResources} files to load. It is expected that the
447      * framework flag has been properly setup. This is filled up with the content of the folder.
448      * @param rootFolder The folder to read the resources from. This is the top level
449      * resource folder (res/)
450      * @throws IOException
451      */
452     public void loadResources(ProjectResources resources, IAbstractFolder rootFolder)
453             throws IOException {
454         IAbstractResource[] files = rootFolder.listMembers();
455         for (IAbstractResource file : files) {
456             if (file instanceof IAbstractFolder) {
457                 IAbstractFolder folder = (IAbstractFolder) file;
458                 ResourceFolder resFolder = processFolder(folder, resources);
459
460                 if (resFolder != null) {
461                     // now we process the content of the folder
462                     IAbstractResource[] children = folder.listMembers();
463
464                     for (IAbstractResource childRes : children) {
465                         if (childRes instanceof IAbstractFile) {
466                             processFile((IAbstractFile) childRes, resFolder);
467                         }
468                     }
469                 }
470             }
471         }
472
473         // now that we have loaded the files, we need to force load the resources from them
474         resources.loadAll();
475     }
476
477     /**
478      * Initial project parsing to gather resource info.
479      * @param project
480      */
481     private void createProject(IProject project) {
482         if (project.isOpen()) {
483             try {
484                 if (project.hasNature(AndroidConstants.NATURE_DEFAULT) == false) {
485                     return;
486                 }
487             } catch (CoreException e1) {
488                 // can't check the nature of the project? ignore it.
489                 return;
490             }
491
492             IFolder resourceFolder = project.getFolder(SdkConstants.FD_RESOURCES);
493
494             ProjectResources projectResources;
495             synchronized (mMap) {
496                 projectResources = mMap.get(project);
497                 if (projectResources == null) {
498                     projectResources = new ProjectResources(project);
499                     mMap.put(project, projectResources);
500                 }
501             }
502
503             if (resourceFolder != null && resourceFolder.exists()) {
504                 try {
505                     IResource[] resources = resourceFolder.members();
506
507                     for (IResource res : resources) {
508                         if (res.getType() == IResource.FOLDER) {
509                             IFolder folder = (IFolder)res;
510                             ResourceFolder resFolder = processFolder(new IFolderWrapper(folder),
511                                     projectResources);
512
513                             if (resFolder != null) {
514                                 // now we process the content of the folder
515                                 IResource[] files = folder.members();
516
517                                 for (IResource fileRes : files) {
518                                     if (fileRes.getType() == IResource.FILE) {
519                                         IFile file = (IFile)fileRes;
520
521                                         processFile(new IFileWrapper(file), resFolder);
522                                     }
523                                 }
524                             }
525                         }
526                     }
527                 } catch (CoreException e) {
528                     // This happens if the project is closed or if the folder doesn't exist.
529                     // Since we already test for that, we can ignore this exception.
530                 }
531             }
532         }
533     }
534
535     /**
536      * Creates a {@link FolderConfiguration} matching the folder segments.
537      * @param folderSegments The segments of the folder name. The first segments should contain
538      * the name of the folder
539      * @return a FolderConfiguration object, or null if the folder name isn't valid..
540      */
541     public FolderConfiguration getConfig(String[] folderSegments) {
542         FolderConfiguration config = new FolderConfiguration();
543
544         // we are going to loop through the segments, and match them with the first
545         // available qualifier. If the segment doesn't match we try with the next qualifier.
546         // Because the order of the qualifier is fixed, we do not reset the first qualifier
547         // after each sucessful segment.
548         // If we run out of qualifier before processing all the segments, we fail.
549
550         int qualifierIndex = 0;
551         int qualifierCount = mQualifiers.length;
552
553         for (int i = 1 ; i < folderSegments.length; i++) {
554             String seg = folderSegments[i];
555             if (seg.length() > 0) {
556                 while (qualifierIndex < qualifierCount &&
557                         mQualifiers[qualifierIndex].checkAndSet(seg, config) == false) {
558                     qualifierIndex++;
559                 }
560
561                 // if we reached the end of the qualifier we didn't find a matching qualifier.
562                 if (qualifierIndex == qualifierCount) {
563                     return null;
564                 }
565
566             } else {
567                 return null;
568             }
569         }
570
571         return config;
572     }
573
574     /**
575      * Processes a folder and adds it to the list of the project resources.
576      * @param folder the folder to process
577      * @param project the folder's project.
578      * @return the ConfiguredFolder created from this folder, or null if the process failed.
579      */
580     private ResourceFolder processFolder(IAbstractFolder folder, ProjectResources project) {
581         // split the name of the folder in segments.
582         String[] folderSegments = folder.getName().split(FolderConfiguration.QUALIFIER_SEP);
583
584         // get the enum for the resource type.
585         ResourceFolderType type = ResourceFolderType.getTypeByName(folderSegments[0]);
586
587         if (type != null) {
588             // get the folder configuration.
589             FolderConfiguration config = getConfig(folderSegments);
590
591             if (config != null) {
592                 ResourceFolder configuredFolder = project.add(type, config, folder);
593
594                 return configuredFolder;
595             }
596         }
597
598         return null;
599     }
600
601     /**
602      * Processes a file and adds it to its parent folder resource.
603      * @param file the underlying resource file.
604      * @param folder the parent of the resource file.
605      * @return the {@link ResourceFile} that was created.
606      */
607     private ResourceFile processFile(IAbstractFile file, ResourceFolder folder) {
608         // get the type of the folder
609         ResourceFolderType type = folder.getType();
610
611         // look for this file if it's already been created
612         ResourceFile resFile = folder.getFile(file);
613
614         if (resFile != null) {
615             // invalidate the file
616             resFile.touch();
617         } else {
618             // create a ResourceFile for it.
619
620             // check if that's a single or multi resource type folder. For now we define this by
621             // the number of possible resource type output by files in the folder. This does
622             // not make the difference between several resource types from a single file or
623             // the ability to have 2 files in the same folder generating 2 different types of
624             // resource. The former is handled by MultiResourceFile properly while we don't
625             // handle the latter. If we were to add this behavior we'd have to change this call.
626             ResourceType[] types = FolderTypeRelationship.getRelatedResourceTypes(type);
627
628             if (types.length == 1) {
629                 resFile = new SingleResourceFile(file, folder);
630             } else {
631                 resFile = new MultiResourceFile(file, folder);
632             }
633
634             // add it to the folder
635             folder.addFile(resFile);
636         }
637
638         return resFile;
639     }
640
641     /**
642      * Returns true if the path is under /project/res/
643      * @param path a workspace relative path
644      * @return true if the path is under /project res/
645      */
646     private boolean isInResFolder(IPath path) {
647         return SdkConstants.FD_RESOURCES.equalsIgnoreCase(path.segment(1));
648     }
649
650     private void notifyListenerOnFolderChange(IProject project, ResourceFolder folder,
651             int eventType) {
652         synchronized (mListeners) {
653             for (IResourceListener listener : mListeners) {
654                 try {
655                     listener.folderChanged(project, folder, eventType);
656                 } catch (Throwable t) {
657                     AdtPlugin.log(t,
658                             "Failed to execute ResourceManager.IResouceListener.folderChanged()"); //$NON-NLS-1$
659                 }
660             }
661         }
662     }
663
664     private void notifyListenerOnFileChange(IProject project, ResourceFile file, int eventType) {
665         synchronized (mListeners) {
666             for (IResourceListener listener : mListeners) {
667                 try {
668                     listener.fileChanged(project, file, eventType);
669                 } catch (Throwable t) {
670                     AdtPlugin.log(t,
671                             "Failed to execute ResourceManager.IResouceListener.fileChanged()"); //$NON-NLS-1$
672                 }
673             }
674         }
675     }
676
677     /**
678      * Private constructor to enforce singleton design.
679      */
680     private ResourceManager() {
681         // get the default qualifiers.
682         FolderConfiguration defaultConfig = new FolderConfiguration();
683         defaultConfig.createDefault();
684         mQualifiers = defaultConfig.getQualifiers();
685     }
686 }