• 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.eclipse.adt.AndroidConstants;
20 import com.android.ide.eclipse.adt.internal.resources.ResourceType;
21 import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
22 import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier;
23 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor.IFileListener;
24 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor.IFolderListener;
25 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor.IProjectListener;
26 import com.android.ide.eclipse.adt.internal.resources.manager.files.FileWrapper;
27 import com.android.ide.eclipse.adt.internal.resources.manager.files.FolderWrapper;
28 import com.android.ide.eclipse.adt.internal.resources.manager.files.IAbstractFile;
29 import com.android.ide.eclipse.adt.internal.resources.manager.files.IAbstractFolder;
30 import com.android.ide.eclipse.adt.internal.resources.manager.files.IFileWrapper;
31 import com.android.ide.eclipse.adt.internal.resources.manager.files.IFolderWrapper;
32 import com.android.sdklib.IAndroidTarget;
33 import com.android.sdklib.SdkConstants;
34 
35 import org.eclipse.core.resources.IContainer;
36 import org.eclipse.core.resources.IFile;
37 import org.eclipse.core.resources.IFolder;
38 import org.eclipse.core.resources.IMarkerDelta;
39 import org.eclipse.core.resources.IProject;
40 import org.eclipse.core.resources.IResource;
41 import org.eclipse.core.resources.IResourceDelta;
42 import org.eclipse.core.runtime.CoreException;
43 import org.eclipse.core.runtime.IPath;
44 
45 import java.io.File;
46 import java.io.IOException;
47 import java.util.HashMap;
48 
49 public final class ResourceManager implements IProjectListener, IFolderListener, IFileListener {
50 
51     private final static ResourceManager sThis = new ResourceManager();
52 
53     /** List of the qualifier object helping for the parsing of folder names */
54     private final ResourceQualifier[] mQualifiers;
55 
56     /**
57      * Map associating project resource with project objects.
58      */
59     private final HashMap<IProject, ProjectResources> mMap =
60         new HashMap<IProject, ProjectResources>();
61 
62     /**
63      * Sets up the resource manager with the global resource monitor.
64      * @param monitor The global resource monitor
65      */
setup(ResourceMonitor monitor)66     public static void setup(ResourceMonitor monitor) {
67         monitor.addProjectListener(sThis);
68         int mask = IResourceDelta.ADDED | IResourceDelta.REMOVED | IResourceDelta.CHANGED;
69         monitor.addFolderListener(sThis, mask);
70         monitor.addFileListener(sThis, mask);
71 
72         CompiledResourcesMonitor.setupMonitor(monitor);
73     }
74 
75     /**
76      * Returns the singleton instance.
77      */
getInstance()78     public static ResourceManager getInstance() {
79         return sThis;
80     }
81 
82     /**
83      * Returns the resources of a project.
84      * @param project The project
85      * @return a ProjectResources object or null if none was found.
86      */
getProjectResources(IProject project)87     public ProjectResources getProjectResources(IProject project) {
88         return mMap.get(project);
89     }
90 
91     /**
92      * Processes folder event.
93      */
folderChanged(IFolder folder, int kind)94     public void folderChanged(IFolder folder, int kind) {
95         ProjectResources resources;
96 
97         final IProject project = folder.getProject();
98 
99         try {
100             if (project.hasNature(AndroidConstants.NATURE) == false) {
101                 return;
102             }
103         } catch (CoreException e) {
104             // can't get the project nature? return!
105             return;
106         }
107 
108         switch (kind) {
109             case IResourceDelta.ADDED:
110                 // checks if the folder is under res.
111                 IPath path = folder.getFullPath();
112 
113                 // the path will be project/res/<something>
114                 if (path.segmentCount() == 3) {
115                     if (isInResFolder(path)) {
116                         // get the project and its resource object.
117                         resources = mMap.get(project);
118 
119                         // if it doesn't exist, we create it.
120                         if (resources == null) {
121                             resources = new ProjectResources(false /* isFrameworkRepository */);
122                             mMap.put(project, resources);
123                         }
124 
125                         processFolder(new IFolderWrapper(folder), resources);
126                     }
127                 }
128                 break;
129             case IResourceDelta.CHANGED:
130                 resources = mMap.get(folder.getProject());
131                 if (resources != null) {
132                     ResourceFolder resFolder = resources.getResourceFolder(folder);
133                     if (resFolder != null) {
134                         resFolder.touch();
135                     }
136                 }
137                 break;
138             case IResourceDelta.REMOVED:
139                 resources = mMap.get(folder.getProject());
140                 if (resources != null) {
141                     // lets get the folder type
142                     ResourceFolderType type = ResourceFolderType.getFolderType(folder.getName());
143 
144                     resources.removeFolder(type, folder);
145                 }
146                 break;
147         }
148     }
149 
150     /* (non-Javadoc)
151      * Sent when a file changed. Depending on the file being changed, and the type of change (ADDED,
152      * REMOVED, CHANGED), the file change is processed to update the resource manager data.
153      *
154      * @param file The file that changed.
155      * @param markerDeltas The marker deltas for the file.
156      * @param kind The change kind. This is equivalent to
157      * {@link IResourceDelta#accept(IResourceDeltaVisitor)}
158      *
159      * @see IFileListener#fileChanged
160      */
fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind)161     public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
162         ProjectResources resources;
163 
164         final IProject project = file.getProject();
165 
166         try {
167             if (project.hasNature(AndroidConstants.NATURE) == false) {
168                 return;
169             }
170         } catch (CoreException e) {
171             // can't get the project nature? return!
172             return;
173         }
174 
175         switch (kind) {
176             case IResourceDelta.ADDED:
177                 // checks if the file is under res/something.
178                 IPath path = file.getFullPath();
179 
180                 if (path.segmentCount() == 4) {
181                     if (isInResFolder(path)) {
182                         // get the project and its resources
183                         resources = mMap.get(project);
184 
185                         IContainer container = file.getParent();
186                         if (container instanceof IFolder && resources != null) {
187 
188                             ResourceFolder folder = resources.getResourceFolder((IFolder)container);
189 
190                             if (folder != null) {
191                                 processFile(new IFileWrapper(file), folder);
192                             }
193                         }
194                     }
195                 }
196                 break;
197             case IResourceDelta.CHANGED:
198                 // try to find a matching ResourceFile
199                 resources = mMap.get(project);
200                 if (resources != null) {
201                     IContainer container = file.getParent();
202                     if (container instanceof IFolder) {
203                         ResourceFolder resFolder = resources.getResourceFolder((IFolder)container);
204 
205                         // we get the delete on the folder before the file, so it is possible
206                         // the associated ResourceFolder doesn't exist anymore.
207                         if (resFolder != null) {
208                             // get the resourceFile, and touch it.
209                             ResourceFile resFile = resFolder.getFile(file);
210                             if (resFile != null) {
211                                 resFile.touch();
212                             }
213                         }
214                     }
215                 }
216                 break;
217             case IResourceDelta.REMOVED:
218                 // try to find a matching ResourceFile
219                 resources = mMap.get(project);
220                 if (resources != null) {
221                     IContainer container = file.getParent();
222                     if (container instanceof IFolder) {
223                         ResourceFolder resFolder = resources.getResourceFolder((IFolder)container);
224 
225                         // we get the delete on the folder before the file, so it is possible
226                         // the associated ResourceFolder doesn't exist anymore.
227                         if (resFolder != null) {
228                             // remove the file
229                             resFolder.removeFile(file);
230                         }
231                     }
232                 }
233                 break;
234         }
235     }
236 
projectClosed(IProject project)237     public void projectClosed(IProject project) {
238         mMap.remove(project);
239     }
240 
projectDeleted(IProject project)241     public void projectDeleted(IProject project) {
242         mMap.remove(project);
243     }
244 
projectOpened(IProject project)245     public void projectOpened(IProject project) {
246         createProject(project);
247     }
248 
projectOpenedWithWorkspace(IProject project)249     public void projectOpenedWithWorkspace(IProject project) {
250         createProject(project);
251     }
252 
253     /**
254      * Returns the {@link ResourceFolder} for the given file or <code>null</code> if none exists.
255      */
getResourceFolder(IFile file)256     public ResourceFolder getResourceFolder(IFile file) {
257         IContainer container = file.getParent();
258         if (container.getType() == IResource.FOLDER) {
259             IFolder parent = (IFolder)container;
260             IProject project = file.getProject();
261 
262             ProjectResources resources = getProjectResources(project);
263             if (resources != null) {
264                 return resources.getResourceFolder(parent);
265             }
266         }
267 
268         return null;
269     }
270 
271     /**
272      * Loads and returns the resources for a given {@link IAndroidTarget}
273      * @param androidTarget the target from which to load the framework resources
274      */
loadFrameworkResources(IAndroidTarget androidTarget)275     public ProjectResources loadFrameworkResources(IAndroidTarget androidTarget) {
276         String osResourcesPath = androidTarget.getPath(IAndroidTarget.RESOURCES);
277 
278         File frameworkRes = new File(osResourcesPath);
279         if (frameworkRes.isDirectory()) {
280             ProjectResources resources = new ProjectResources(true /* isFrameworkRepository */);
281 
282             try {
283                 loadResources(resources, frameworkRes);
284                 return resources;
285             } catch (IOException e) {
286                 // since we test that folders are folders, and files are files, this shouldn't
287                 // happen. We can ignore it.
288             }
289         }
290 
291         return null;
292     }
293 
294     /**
295      * Loads the resources from a folder, and fills the given {@link ProjectResources}.
296      * <p/>
297      * This is mostly a utility method that should not be used to process actual Eclipse projects
298      * (Those are loaded with {@link #createProject(IProject)} for new project or
299      * {@link #processFolder(IAbstractFolder, ProjectResources)} and
300      * {@link #processFile(IAbstractFile, ResourceFolder)} for folder/file modifications)<br>
301      * This method will process files/folders with implementations of {@link IAbstractFile} and
302      * {@link IAbstractFolder} based on {@link File} instead of {@link IFile} and {@link IFolder}
303      * respectively. This is not proper for handling {@link IProject}s.
304      * </p>
305      * This is used to load the framework resources, or to do load project resources when
306      * setting rendering tests.
307      *
308      *
309      * @param resources The {@link ProjectResources} files to load. It is expected that the
310      * framework flag has been properly setup. This is filled up with the content of the folder.
311      * @param folder The folder to read the resources from. This is the top level resource folder
312      * (res/)
313      * @throws IOException
314      */
loadResources(ProjectResources resources, File folder)315     public void loadResources(ProjectResources resources, File folder) throws IOException {
316         File[] files = folder.listFiles();
317         for (File file : files) {
318             if (file.isDirectory()) {
319                 ResourceFolder resFolder = processFolder(new FolderWrapper(file),
320                         resources);
321 
322                 if (resFolder != null) {
323                     // now we process the content of the folder
324                     File[] children = file.listFiles();
325 
326                     for (File childRes : children) {
327                         if (childRes.isFile()) {
328                             processFile(new FileWrapper(childRes), resFolder);
329                         }
330                     }
331                 }
332 
333             }
334         }
335 
336         // now that we have loaded the files, we need to force load the resources from them
337         resources.loadAll();
338     }
339 
340     /**
341      * Initial project parsing to gather resource info.
342      * @param project
343      */
createProject(IProject project)344     private void createProject(IProject project) {
345         if (project.isOpen()) {
346             try {
347                 if (project.hasNature(AndroidConstants.NATURE) == false) {
348                     return;
349                 }
350             } catch (CoreException e1) {
351                 // can't check the nature of the project? ignore it.
352                 return;
353             }
354 
355             IFolder resourceFolder = project.getFolder(SdkConstants.FD_RESOURCES);
356 
357             ProjectResources projectResources = mMap.get(project);
358             if (projectResources == null) {
359                 projectResources = new ProjectResources(false /* isFrameworkRepository */);
360                 mMap.put(project, projectResources);
361             }
362 
363             if (resourceFolder != null && resourceFolder.exists()) {
364                 try {
365                     IResource[] resources = resourceFolder.members();
366 
367                     for (IResource res : resources) {
368                         if (res.getType() == IResource.FOLDER) {
369                             IFolder folder = (IFolder)res;
370                             ResourceFolder resFolder = processFolder(new IFolderWrapper(folder),
371                                     projectResources);
372 
373                             if (resFolder != null) {
374                                 // now we process the content of the folder
375                                 IResource[] files = folder.members();
376 
377                                 for (IResource fileRes : files) {
378                                     if (fileRes.getType() == IResource.FILE) {
379                                         IFile file = (IFile)fileRes;
380 
381                                         processFile(new IFileWrapper(file), resFolder);
382                                     }
383                                 }
384                             }
385                         }
386                     }
387                 } catch (CoreException e) {
388                     // This happens if the project is closed or if the folder doesn't exist.
389                     // Since we already test for that, we can ignore this exception.
390                 }
391             }
392         }
393     }
394 
395     /**
396      * Creates a {@link FolderConfiguration} matching the folder segments.
397      * @param folderSegments The segments of the folder name. The first segments should contain
398      * the name of the folder
399      * @return a FolderConfiguration object, or null if the folder name isn't valid..
400      */
getConfig(String[] folderSegments)401     public FolderConfiguration getConfig(String[] folderSegments) {
402         FolderConfiguration config = new FolderConfiguration();
403 
404         // we are going to loop through the segments, and match them with the first
405         // available qualifier. If the segment doesn't match we try with the next qualifier.
406         // Because the order of the qualifier is fixed, we do not reset the first qualifier
407         // after each sucessful segment.
408         // If we run out of qualifier before processing all the segments, we fail.
409 
410         int qualifierIndex = 0;
411         int qualifierCount = mQualifiers.length;
412 
413         for (int i = 1 ; i < folderSegments.length; i++) {
414             String seg = folderSegments[i];
415             if (seg.length() > 0) {
416                 while (qualifierIndex < qualifierCount &&
417                         mQualifiers[qualifierIndex].checkAndSet(seg, config) == false) {
418                     qualifierIndex++;
419                 }
420 
421                 // if we reached the end of the qualifier we didn't find a matching qualifier.
422                 if (qualifierIndex == qualifierCount) {
423                     return null;
424                 }
425 
426             } else {
427                 return null;
428             }
429         }
430 
431         return config;
432     }
433 
434     /**
435      * Processes a folder and adds it to the list of the project resources.
436      * @param folder the folder to process
437      * @param project the folder's project.
438      * @return the ConfiguredFolder created from this folder, or null if the process failed.
439      */
processFolder(IAbstractFolder folder, ProjectResources project)440     private ResourceFolder processFolder(IAbstractFolder folder, ProjectResources project) {
441         // split the name of the folder in segments.
442         String[] folderSegments = folder.getName().split(FolderConfiguration.QUALIFIER_SEP);
443 
444         // get the enum for the resource type.
445         ResourceFolderType type = ResourceFolderType.getTypeByName(folderSegments[0]);
446 
447         if (type != null) {
448             // get the folder configuration.
449             FolderConfiguration config = getConfig(folderSegments);
450 
451             if (config != null) {
452                 ResourceFolder configuredFolder = project.add(type, config, folder);
453 
454                 return configuredFolder;
455             }
456         }
457 
458         return null;
459     }
460 
461     /**
462      * Processes a file and adds it to its parent folder resource.
463      * @param file
464      * @param folder
465      */
processFile(IAbstractFile file, ResourceFolder folder)466     private void processFile(IAbstractFile file, ResourceFolder folder) {
467         // get the type of the folder
468         ResourceFolderType type = folder.getType();
469 
470         // look for this file if it's already been created
471         ResourceFile resFile = folder.getFile(file);
472 
473         if (resFile != null) {
474             // invalidate the file
475             resFile.touch();
476         } else {
477             // create a ResourceFile for it.
478 
479             // check if that's a single or multi resource type folder. For now we define this by
480             // the number of possible resource type output by files in the folder. This does
481             // not make the difference between several resource types from a single file or
482             // the ability to have 2 files in the same folder generating 2 different types of
483             // resource. The former is handled by MultiResourceFile properly while we don't
484             // handle the latter. If we were to add this behavior we'd have to change this call.
485             ResourceType[] types = FolderTypeRelationship.getRelatedResourceTypes(type);
486 
487             if (types.length == 1) {
488                 resFile = new SingleResourceFile(file, folder);
489             } else {
490                 resFile = new MultiResourceFile(file, folder);
491             }
492 
493             // add it to the folder
494             folder.addFile(resFile);
495         }
496     }
497 
498     /**
499      * Returns true if the path is under /project/res/
500      * @param path a workspace relative path
501      * @return true if the path is under /project res/
502      */
isInResFolder(IPath path)503     private boolean isInResFolder(IPath path) {
504         return SdkConstants.FD_RESOURCES.equalsIgnoreCase(path.segment(1));
505     }
506 
507     /**
508      * Private constructor to enforce singleton design.
509      */
ResourceManager()510     ResourceManager() {
511         // get the default qualifiers.
512         FolderConfiguration defaultConfig = new FolderConfiguration();
513         defaultConfig.createDefault();
514         mQualifiers = defaultConfig.getQualifiers();
515     }
516 }
517