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