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.internal.resources.IResourceRepository; 20 import com.android.ide.eclipse.adt.internal.resources.ResourceItem; 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.LanguageQualifier; 24 import com.android.ide.eclipse.adt.internal.resources.configurations.RegionQualifier; 25 import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier; 26 import com.android.ide.eclipse.adt.internal.sdk.ProjectState; 27 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 28 import com.android.ide.eclipse.adt.io.IFolderWrapper; 29 import com.android.layoutlib.api.IResourceValue; 30 import com.android.layoutlib.utils.ResourceValue; 31 import com.android.sdklib.io.IAbstractFolder; 32 33 import org.eclipse.core.resources.IFolder; 34 import org.eclipse.core.resources.IProject; 35 36 import java.util.ArrayList; 37 import java.util.Collection; 38 import java.util.Collections; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 import java.util.SortedSet; 44 import java.util.TreeSet; 45 import java.util.Map.Entry; 46 47 /** 48 * Represents the resources of a project. This is a file view of the resources, with handling 49 * for the alternate resource types. For a compiled view use CompiledResources. 50 */ 51 public class ProjectResources implements IResourceRepository { 52 private final static int DYNAMIC_ID_SEED_START = 0; // this should not conflict with any 53 // project IDs that start at a much higher 54 // value 55 56 private final HashMap<ResourceFolderType, List<ResourceFolder>> mFolderMap = 57 new HashMap<ResourceFolderType, List<ResourceFolder>>(); 58 59 private final HashMap<ResourceType, List<ProjectResourceItem>> mResourceMap = 60 new HashMap<ResourceType, List<ProjectResourceItem>>(); 61 62 /** Map of (name, id) for resources of type {@link ResourceType#ID} coming from R.java */ 63 private Map<String, Map<String, Integer>> mResourceValueMap; 64 /** Map of (id, [name, resType]) for all resources coming from R.java */ 65 private Map<Integer, String[]> mResIdValueToNameMap; 66 /** Map of (int[], name) for styleable resources coming from R.java */ 67 private Map<IntArrayWrapper, String> mStyleableValueToNameMap; 68 69 private final Map<String, Integer> mDynamicIds = new HashMap<String, Integer>(); 70 private int mDynamicSeed = DYNAMIC_ID_SEED_START; 71 72 /** Cached list of {@link IdResourceItem}. This is mix of IdResourceItem created by 73 * {@link MultiResourceFile} for ids coming from XML files under res/values and 74 * {@link IdResourceItem} created manually, from the list coming from R.java */ 75 private final ArrayList<IdResourceItem> mIdResourceList = new ArrayList<IdResourceItem>(); 76 77 private final boolean mIsFrameworkRepository; 78 private final IProject mProject; 79 80 private final IntArrayWrapper mWrapper = new IntArrayWrapper(null); 81 82 83 /** 84 * Makes a ProjectResources for a given <var>project</var>. 85 * @param project the project. 86 */ 87 public ProjectResources(IProject project) { 88 mIsFrameworkRepository = false; 89 mProject = project; 90 } 91 92 /** 93 * Makes a ProjectResource for a framework repository. 94 * 95 * @see #isSystemRepository() 96 */ 97 public ProjectResources() { 98 mIsFrameworkRepository = true; 99 mProject = null; 100 } 101 102 /** 103 * Returns whether this ProjectResources is for a project or for a framework. 104 */ 105 public boolean isSystemRepository() { 106 return mIsFrameworkRepository; 107 } 108 109 /** 110 * Adds a Folder Configuration to the project. 111 * @param type The resource type. 112 * @param config The resource configuration. 113 * @param folder The workspace folder object. 114 * @return the {@link ResourceFolder} object associated to this folder. 115 */ 116 protected ResourceFolder add(ResourceFolderType type, FolderConfiguration config, 117 IAbstractFolder folder) { 118 // get the list for the resource type 119 List<ResourceFolder> list = mFolderMap.get(type); 120 121 if (list == null) { 122 list = new ArrayList<ResourceFolder>(); 123 124 ResourceFolder cf = new ResourceFolder(type, config, folder, mIsFrameworkRepository); 125 list.add(cf); 126 127 mFolderMap.put(type, list); 128 129 return cf; 130 } 131 132 // look for an already existing folder configuration. 133 for (ResourceFolder cFolder : list) { 134 if (cFolder.mConfiguration.equals(config)) { 135 // config already exist. Nothing to be done really, besides making sure 136 // the IFolder object is up to date. 137 cFolder.mFolder = folder; 138 return cFolder; 139 } 140 } 141 142 // If we arrive here, this means we didn't find a matching configuration. 143 // So we add one. 144 ResourceFolder cf = new ResourceFolder(type, config, folder, mIsFrameworkRepository); 145 list.add(cf); 146 147 return cf; 148 } 149 150 /** 151 * Removes a {@link ResourceFolder} associated with the specified {@link IAbstractFolder}. 152 * @param type The type of the folder 153 * @param folder the IFolder object. 154 * @return the {@link ResourceFolder} that was removed, or null if no matches were found. 155 */ 156 protected ResourceFolder removeFolder(ResourceFolderType type, IFolder folder) { 157 // get the list of folders for the resource type. 158 List<ResourceFolder> list = mFolderMap.get(type); 159 160 if (list != null) { 161 int count = list.size(); 162 for (int i = 0 ; i < count ; i++) { 163 ResourceFolder resFolder = list.get(i); 164 // this is only used for Eclipse stuff so we know it's an IFolderWrapper 165 IFolderWrapper wrapper = (IFolderWrapper) resFolder.getFolder(); 166 if (wrapper.getIFolder().equals(folder)) { 167 // we found the matching ResourceFolder. we need to remove it. 168 list.remove(i); 169 170 // we now need to invalidate this resource type. 171 // The easiest way is to touch one of the other folders of the same type. 172 if (list.size() > 0) { 173 list.get(0).touch(); 174 } else { 175 // if the list is now empty, and we have a single ResouceType out of this 176 // ResourceFolderType, then we are done. 177 // However, if another ResourceFolderType can generate similar ResourceType 178 // than this, we need to update those ResourceTypes as well. 179 // For instance, if the last "drawable-*" folder is deleted, we need to 180 // refresh the ResourceItem associated with ResourceType.DRAWABLE. 181 // Those can be found in ResourceFolderType.DRAWABLE but also in 182 // ResourceFolderType.VALUES. 183 // If we don't find a single folder to touch, then it's fine, as the top 184 // level items (the list of generated resource types) is not cached 185 // (for now) 186 187 // get the lists of ResourceTypes generated by this ResourceFolderType 188 ResourceType[] resTypes = FolderTypeRelationship.getRelatedResourceTypes( 189 type); 190 191 // for each of those, make sure to find one folder to touch so that the 192 // list of ResourceItem associated with the type is rebuilt. 193 for (ResourceType resType : resTypes) { 194 // get the list of folder that can generate this type 195 ResourceFolderType[] folderTypes = 196 FolderTypeRelationship.getRelatedFolders(resType); 197 198 // we only need to touch one folder in any of those (since it's one 199 // folder per type, not per folder type). 200 for (ResourceFolderType folderType : folderTypes) { 201 List<ResourceFolder> resFolders = mFolderMap.get(folderType); 202 203 if (resFolders != null && resFolders.size() > 0) { 204 resFolders.get(0).touch(); 205 break; 206 } 207 } 208 } 209 } 210 211 // we're done updating/touching, we can stop 212 return resFolder; 213 } 214 } 215 } 216 217 return null; 218 } 219 220 221 /** 222 * Returns a list of {@link ResourceFolder} for a specific {@link ResourceFolderType}. 223 * @param type The {@link ResourceFolderType} 224 */ 225 public List<ResourceFolder> getFolders(ResourceFolderType type) { 226 return mFolderMap.get(type); 227 } 228 229 /* (non-Javadoc) 230 * @see com.android.ide.eclipse.editors.resources.IResourceRepository#getAvailableResourceTypes() 231 */ 232 public ResourceType[] getAvailableResourceTypes() { 233 ArrayList<ResourceType> list = new ArrayList<ResourceType>(); 234 235 // For each key, we check if there's a single ResourceType match. 236 // If not, we look for the actual content to give us the resource type. 237 238 for (ResourceFolderType folderType : mFolderMap.keySet()) { 239 ResourceType types[] = FolderTypeRelationship.getRelatedResourceTypes(folderType); 240 if (types.length == 1) { 241 // before we add it we check if it's not already present, since a ResourceType 242 // could be created from multiple folders, even for the folders that only create 243 // one type of resource (drawable for instance, can be created from drawable/ and 244 // values/) 245 if (list.indexOf(types[0]) == -1) { 246 list.add(types[0]); 247 } 248 } else { 249 // there isn't a single resource type out of this folder, so we look for all 250 // content. 251 List<ResourceFolder> folders = mFolderMap.get(folderType); 252 if (folders != null) { 253 for (ResourceFolder folder : folders) { 254 Collection<ResourceType> folderContent = folder.getResourceTypes(); 255 256 // then we add them, but only if they aren't already in the list. 257 for (ResourceType folderResType : folderContent) { 258 if (list.indexOf(folderResType) == -1) { 259 list.add(folderResType); 260 } 261 } 262 } 263 } 264 } 265 } 266 267 // in case ResourceType.ID haven't been added yet because there's no id defined 268 // in XML, we check on the list of compiled id resources. 269 if (list.indexOf(ResourceType.ID) == -1 && mResourceValueMap != null) { 270 Map<String, Integer> map = mResourceValueMap.get(ResourceType.ID.getName()); 271 if (map != null && map.size() > 0) { 272 list.add(ResourceType.ID); 273 } 274 } 275 276 // at this point the list is full of ResourceType defined in the files. 277 // We need to sort it. 278 Collections.sort(list); 279 280 return list.toArray(new ResourceType[list.size()]); 281 } 282 283 /* (non-Javadoc) 284 * @see com.android.ide.eclipse.editors.resources.IResourceRepository#getResources(com.android.ide.eclipse.common.resources.ResourceType) 285 */ 286 public ProjectResourceItem[] getResources(ResourceType type) { 287 checkAndUpdate(type); 288 289 if (type == ResourceType.ID) { 290 synchronized (mIdResourceList) { 291 return mIdResourceList.toArray(new ProjectResourceItem[mIdResourceList.size()]); 292 } 293 } 294 295 List<ProjectResourceItem> items = mResourceMap.get(type); 296 297 return items.toArray(new ProjectResourceItem[items.size()]); 298 } 299 300 /* (non-Javadoc) 301 * @see com.android.ide.eclipse.editors.resources.IResourceRepository#hasResources(com.android.ide.eclipse.common.resources.ResourceType) 302 */ 303 public boolean hasResources(ResourceType type) { 304 checkAndUpdate(type); 305 306 if (type == ResourceType.ID) { 307 synchronized (mIdResourceList) { 308 return mIdResourceList.size() > 0; 309 } 310 } 311 312 List<ProjectResourceItem> items = mResourceMap.get(type); 313 return (items != null && items.size() > 0); 314 } 315 316 /** 317 * Returns the {@link ResourceFolder} associated with a {@link IFolder}. 318 * @param folder The {@link IFolder} object. 319 * @return the {@link ResourceFolder} or null if it was not found. 320 */ 321 public ResourceFolder getResourceFolder(IFolder folder) { 322 for (List<ResourceFolder> list : mFolderMap.values()) { 323 for (ResourceFolder resFolder : list) { 324 // this is only used for Eclipse stuff so we know it's an IFolderWrapper 325 IFolderWrapper wrapper = (IFolderWrapper) resFolder.getFolder(); 326 if (wrapper.getIFolder().equals(folder)) { 327 return resFolder; 328 } 329 } 330 } 331 332 return null; 333 } 334 335 /** 336 * Returns the {@link ResourceFile} matching the given name, {@link ResourceFolderType} and 337 * configuration. 338 * <p/>This only works with files generating one resource named after the file (for instance, 339 * layouts, bitmap based drawable, xml, anims). 340 * @return the matching file or <code>null</code> if no match was found. 341 */ 342 public ResourceFile getMatchingFile(String name, ResourceFolderType type, 343 FolderConfiguration config) { 344 // get the folders for the given type 345 List<ResourceFolder> folders = mFolderMap.get(type); 346 347 // look for folders containing a file with the given name. 348 ArrayList<ResourceFolder> matchingFolders = new ArrayList<ResourceFolder>(); 349 350 // remove the folders that do not have a file with the given name, or if their config 351 // is incompatible. 352 for (int i = 0 ; i < folders.size(); i++) { 353 ResourceFolder folder = folders.get(i); 354 355 if (folder.hasFile(name) == true) { 356 matchingFolders.add(folder); 357 } 358 } 359 360 // from those, get the folder with a config matching the given reference configuration. 361 Resource match = findMatchingConfiguredResource(matchingFolders, config); 362 363 // do we have a matching folder? 364 if (match instanceof ResourceFolder) { 365 // get the ResourceFile from the filename 366 return ((ResourceFolder)match).getFile(name); 367 } 368 369 return null; 370 } 371 372 /** 373 * Returns the resources values matching a given {@link FolderConfiguration}. 374 * @param referenceConfig the configuration that each value must match. 375 */ 376 public Map<String, Map<String, IResourceValue>> getConfiguredResources( 377 FolderConfiguration referenceConfig) { 378 379 Map<String, Map<String, IResourceValue>> map = 380 new HashMap<String, Map<String, IResourceValue>>(); 381 382 // if the project contains libraries, we need to add the libraries resources here 383 // so that they are accessible to the layout rendering. 384 if (mProject != null) { 385 ProjectState state = Sdk.getProjectState(mProject); 386 if (state != null) { 387 IProject[] libraries = state.getFullLibraryProjects(); 388 389 ResourceManager resMgr = ResourceManager.getInstance(); 390 391 // because aapt put all the library in their order in this array, the first 392 // one will have priority over the 2nd one. So it's better to loop in the inverse 393 // order and fill the map with resources that will be overwritten by higher 394 // priority resources 395 for (int i = libraries.length - 1 ; i >= 0 ; i--) { 396 IProject library = libraries[i]; 397 398 ProjectResources libRes = resMgr.getProjectResources(library); 399 if (libRes != null) { 400 // make sure they are loaded 401 libRes.loadAll(); 402 403 // we don't want to simply replace the whole map, but instead merge the 404 // content of any sub-map 405 Map<String, Map<String, IResourceValue>> libMap = 406 libRes.getConfiguredResources(referenceConfig); 407 408 for (Entry<String, Map<String, IResourceValue>> entry : libMap.entrySet()) { 409 // get the map currently in the result map for this resource type 410 Map<String, IResourceValue> tempMap = map.get(entry.getKey()); 411 if (tempMap == null) { 412 // since there's no current map for this type, just add the map 413 // directly coming from the library resources 414 map.put(entry.getKey(), entry.getValue()); 415 } else { 416 // already a map for this type. add the resources from the 417 // library. 418 tempMap.putAll(entry.getValue()); 419 } 420 } 421 } 422 } 423 } 424 } 425 426 // now the project resources themselves. 427 // Don't blindly fill the map, instead check if there are sub-map already present 428 // due to library resources. 429 430 // special case for Id since there's a mix of compiled id (declared inline) and id declared 431 // in the XML files. 432 if (mIdResourceList.size() > 0) { 433 String idType = ResourceType.ID.getName(); 434 Map<String, IResourceValue> idMap = map.get(idType); 435 436 if (idMap == null) { 437 idMap = new HashMap<String, IResourceValue>(); 438 map.put(idType, idMap); 439 } 440 for (IdResourceItem id : mIdResourceList) { 441 // FIXME: cache the ResourceValue! 442 idMap.put(id.getName(), new ResourceValue(idType, id.getName(), 443 mIsFrameworkRepository)); 444 } 445 446 } 447 448 Set<ResourceType> keys = mResourceMap.keySet(); 449 for (ResourceType key : keys) { 450 // we don't process ID resources since we already did it above. 451 if (key != ResourceType.ID) { 452 // get the local results 453 Map<String, IResourceValue> localResMap = getConfiguredResource(key, 454 referenceConfig); 455 456 // check if a map for this type already exists 457 String resName = key.getName(); 458 Map<String, IResourceValue> resMap = map.get(resName); 459 if (resMap == null) { 460 // just use the local results. 461 map.put(resName, localResMap); 462 } else { 463 // add to the library results. 464 resMap.putAll(localResMap); 465 } 466 } 467 } 468 469 return map; 470 } 471 472 /** 473 * Loads all the resources. Essentially this forces to load the values from the 474 * {@link ResourceFile} objects to make sure they are up to date and loaded 475 * in {@link #mResourceMap}. 476 */ 477 public void loadAll() { 478 // gets all the resource types available. 479 ResourceType[] types = getAvailableResourceTypes(); 480 481 // loop on them and load them 482 for (ResourceType type: types) { 483 checkAndUpdate(type); 484 } 485 } 486 487 /** 488 * Resolves a compiled resource id into the resource name and type 489 * @param id 490 * @return an array of 2 strings { name, type } or null if the id could not be resolved 491 */ 492 public String[] resolveResourceValue(int id) { 493 if (mResIdValueToNameMap != null) { 494 return mResIdValueToNameMap.get(id); 495 } 496 497 return null; 498 } 499 500 /** 501 * Resolves a compiled resource id of type int[] into the resource name. 502 */ 503 public String resolveResourceValue(int[] id) { 504 if (mStyleableValueToNameMap != null) { 505 mWrapper.set(id); 506 return mStyleableValueToNameMap.get(mWrapper); 507 } 508 509 return null; 510 } 511 512 /** 513 * Returns the value of a resource by its type and name. 514 * <p/>If the resource is of type {@link ResourceType#ID} and does not exist in the 515 * internal map, then new id values are dynamically generated (and stored so that queries 516 * with the same names will return the same value). 517 */ 518 public Integer getResourceValue(String type, String name) { 519 if (mResourceValueMap != null) { 520 Map<String, Integer> map = mResourceValueMap.get(type); 521 if (map != null) { 522 Integer value = map.get(name); 523 524 // if no value 525 if (value == null && ResourceType.ID.getName().equals(type)) { 526 return getDynamicId(name); 527 } 528 529 return value; 530 } else if (ResourceType.ID.getName().equals(type)) { 531 return getDynamicId(name); 532 } 533 } 534 535 return null; 536 } 537 538 /** 539 * Returns the sorted list of languages used in the resources. 540 */ 541 public SortedSet<String> getLanguages() { 542 SortedSet<String> set = new TreeSet<String>(); 543 544 Collection<List<ResourceFolder>> folderList = mFolderMap.values(); 545 for (List<ResourceFolder> folderSubList : folderList) { 546 for (ResourceFolder folder : folderSubList) { 547 FolderConfiguration config = folder.getConfiguration(); 548 LanguageQualifier lang = config.getLanguageQualifier(); 549 if (lang != null) { 550 set.add(lang.getShortDisplayValue()); 551 } 552 } 553 } 554 555 return set; 556 } 557 558 /** 559 * Returns the sorted list of regions used in the resources with the given language. 560 * @param currentLanguage the current language the region must be associated with. 561 */ 562 public SortedSet<String> getRegions(String currentLanguage) { 563 SortedSet<String> set = new TreeSet<String>(); 564 565 Collection<List<ResourceFolder>> folderList = mFolderMap.values(); 566 for (List<ResourceFolder> folderSubList : folderList) { 567 for (ResourceFolder folder : folderSubList) { 568 FolderConfiguration config = folder.getConfiguration(); 569 570 // get the language 571 LanguageQualifier lang = config.getLanguageQualifier(); 572 if (lang != null && lang.getShortDisplayValue().equals(currentLanguage)) { 573 RegionQualifier region = config.getRegionQualifier(); 574 if (region != null) { 575 set.add(region.getShortDisplayValue()); 576 } 577 } 578 } 579 } 580 581 return set; 582 } 583 584 /** 585 * Resets the list of dynamic Ids. This list is used by 586 * {@link #getResourceValue(String, String)} when the resource query is an ID that doesn't 587 * exist (for example for ID automatically generated in layout files that are not saved. 588 * <p/>This method resets those dynamic ID and must be called whenever the actual list of IDs 589 * change. 590 */ 591 public void resetDynamicIds() { 592 synchronized (mDynamicIds) { 593 mDynamicIds.clear(); 594 mDynamicSeed = DYNAMIC_ID_SEED_START; 595 } 596 } 597 598 /** 599 * Returns a map of (resource name, resource value) for the given {@link ResourceType}. 600 * <p/>The values returned are taken from the resource files best matching a given 601 * {@link FolderConfiguration}. 602 * @param type the type of the resources. 603 * @param referenceConfig the configuration to best match. 604 */ 605 private Map<String, IResourceValue> getConfiguredResource(ResourceType type, 606 FolderConfiguration referenceConfig) { 607 // get the resource item for the given type 608 List<ProjectResourceItem> items = mResourceMap.get(type); 609 610 // create the map 611 HashMap<String, IResourceValue> map = new HashMap<String, IResourceValue>(); 612 613 for (ProjectResourceItem item : items) { 614 // get the source files generating this resource 615 List<ResourceFile> list = item.getSourceFileList(); 616 617 // look for the best match for the given configuration 618 Resource match = findMatchingConfiguredResource(list, referenceConfig); 619 620 if (match instanceof ResourceFile) { 621 ResourceFile matchResFile = (ResourceFile)match; 622 623 // get the value of this configured resource. 624 IResourceValue value = matchResFile.getValue(type, item.getName()); 625 626 if (value != null) { 627 map.put(item.getName(), value); 628 } 629 } 630 } 631 632 return map; 633 } 634 635 /** 636 * Returns the best matching {@link Resource}. 637 * @param resources the list of {@link Resource} to choose from. 638 * @param referenceConfig the {@link FolderConfiguration} to match. 639 * @see http://d.android.com/guide/topics/resources/resources-i18n.html#best-match 640 */ 641 private Resource findMatchingConfiguredResource(List<? extends Resource> resources, 642 FolderConfiguration referenceConfig) { 643 // 644 // 1: eliminate resources that contradict the reference configuration 645 // 2: pick next qualifier type 646 // 3: check if any resources use this qualifier, if no, back to 2, else move on to 4. 647 // 4: eliminate resources that don't use this qualifier. 648 // 5: if more than one resource left, go back to 2. 649 // 650 // The precedence of the qualifiers is more important than the number of qualifiers that 651 // exactly match the device. 652 653 // 1: eliminate resources that contradict 654 ArrayList<Resource> matchingResources = new ArrayList<Resource>(); 655 for (int i = 0 ; i < resources.size(); i++) { 656 Resource res = resources.get(i); 657 658 if (res.getConfiguration().isMatchFor(referenceConfig)) { 659 matchingResources.add(res); 660 } 661 } 662 663 // if there is only one match, just take it 664 if (matchingResources.size() == 1) { 665 return matchingResources.get(0); 666 } else if (matchingResources.size() == 0) { 667 return null; 668 } 669 670 // 2. Loop on the qualifiers, and eliminate matches 671 final int count = FolderConfiguration.getQualifierCount(); 672 for (int q = 0 ; q < count ; q++) { 673 // look to see if one resource has this qualifier. 674 // At the same time also record the best match value for the qualifier (if applicable). 675 676 // The reference value, to find the best match. 677 // Note that this qualifier could be null. In which case any qualifier found in the 678 // possible match, will all be considered best match. 679 ResourceQualifier referenceQualifier = referenceConfig.getQualifier(q); 680 681 boolean found = false; 682 ResourceQualifier bestMatch = null; // this is to store the best match. 683 for (Resource res : matchingResources) { 684 ResourceQualifier qualifier = res.getConfiguration().getQualifier(q); 685 if (qualifier != null) { 686 // set the flag. 687 found = true; 688 689 // Now check for a best match. If the reference qualifier is null , 690 // any qualifier is a "best" match (we don't need to record all of them. 691 // Instead the non compatible ones are removed below) 692 if (referenceQualifier != null) { 693 if (qualifier.isBetterMatchThan(bestMatch, referenceQualifier)) { 694 bestMatch = qualifier; 695 } 696 } 697 } 698 } 699 700 // 4. If a resources has a qualifier at the current index, remove all the resources that 701 // do not have one, or whose qualifier value does not equal the best match found above 702 // unless there's no reference qualifier, in which case they are all considered 703 // "best" match. 704 if (found) { 705 for (int i = 0 ; i < matchingResources.size(); ) { 706 Resource res = matchingResources.get(i); 707 ResourceQualifier qualifier = res.getConfiguration().getQualifier(q); 708 709 if (qualifier == null) { 710 // this resources has no qualifier of this type: rejected. 711 matchingResources.remove(res); 712 } else if (referenceQualifier != null && bestMatch != null && 713 bestMatch.equals(qualifier) == false) { 714 // there's a reference qualifier and there is a better match for it than 715 // this resource, so we reject it. 716 matchingResources.remove(res); 717 } else { 718 // looks like we keep this resource, move on to the next one. 719 i++; 720 } 721 } 722 723 // at this point we may have run out of matching resources before going 724 // through all the qualifiers. 725 if (matchingResources.size() < 2) { 726 break; 727 } 728 } 729 } 730 731 // Because we accept resources whose configuration have qualifiers where the reference 732 // configuration doesn't, we can end up with more than one match. In this case, we just 733 // take the first one. 734 if (matchingResources.size() == 0) { 735 return null; 736 } 737 return matchingResources.get(0); 738 } 739 740 /** 741 * Checks if the list of {@link ResourceItem}s for the specified {@link ResourceType} needs 742 * to be updated. 743 * @param type the Resource Type. 744 */ 745 private void checkAndUpdate(ResourceType type) { 746 // get the list of folder that can output this type 747 ResourceFolderType[] folderTypes = FolderTypeRelationship.getRelatedFolders(type); 748 749 for (ResourceFolderType folderType : folderTypes) { 750 List<ResourceFolder> folders = mFolderMap.get(folderType); 751 752 if (folders != null) { 753 for (ResourceFolder folder : folders) { 754 if (folder.isTouched()) { 755 // if this folder is touched we need to update all the types that can 756 // be generated from a file in this folder. 757 // This will include 'type' obviously. 758 ResourceType[] resTypes = FolderTypeRelationship.getRelatedResourceTypes( 759 folderType); 760 for (ResourceType resType : resTypes) { 761 update(resType); 762 } 763 return; 764 } 765 } 766 } 767 } 768 } 769 770 /** 771 * Updates the list of {@link ResourceItem} objects associated with a {@link ResourceType}. 772 * This will reset the touch status of all the folders that can generate this resource type. 773 * @param type the Resource Type. 774 */ 775 private void update(ResourceType type) { 776 // get the cache list, and lets make a backup 777 List<ProjectResourceItem> items = mResourceMap.get(type); 778 List<ProjectResourceItem> backup = new ArrayList<ProjectResourceItem>(); 779 780 if (items == null) { 781 items = new ArrayList<ProjectResourceItem>(); 782 mResourceMap.put(type, items); 783 } else { 784 // backup the list 785 backup.addAll(items); 786 787 // we reset the list itself. 788 items.clear(); 789 } 790 791 // get the list of folder that can output this type 792 ResourceFolderType[] folderTypes = FolderTypeRelationship.getRelatedFolders(type); 793 794 for (ResourceFolderType folderType : folderTypes) { 795 List<ResourceFolder> folders = mFolderMap.get(folderType); 796 797 if (folders != null) { 798 for (ResourceFolder folder : folders) { 799 items.addAll(folder.getResources(type, this)); 800 folder.resetTouch(); 801 } 802 } 803 } 804 805 // now items contains the new list. We "merge" it with the backup list. 806 // Basically, we need to keep the old instances of ResourceItem (where applicable), 807 // but replace them by the content of the new items. 808 // This will let the resource explorer keep the expanded state of the nodes whose data 809 // is a ResourceItem object. 810 if (backup.size() > 0) { 811 // this is not going to change as we're only replacing instances. 812 int count = items.size(); 813 814 for (int i = 0 ; i < count;) { 815 // get the "new" item 816 ProjectResourceItem item = items.get(i); 817 818 // look for a similar item in the old list. 819 ProjectResourceItem foundOldItem = null; 820 for (ProjectResourceItem oldItem : backup) { 821 if (oldItem.getName().equals(item.getName())) { 822 foundOldItem = oldItem; 823 break; 824 } 825 } 826 827 if (foundOldItem != null) { 828 // erase the data of the old item with the data from the new one. 829 foundOldItem.replaceWith(item); 830 831 // remove the old and new item from their respective lists 832 items.remove(i); 833 backup.remove(foundOldItem); 834 835 // add the old item to the new list 836 items.add(foundOldItem); 837 } else { 838 // this is a new item, we skip to the next object 839 i++; 840 } 841 } 842 } 843 844 // if this is the ResourceType.ID, we create the actual list, from this list and 845 // the compiled resource list. 846 if (type == ResourceType.ID) { 847 mergeIdResources(); 848 } else { 849 // else this is the list that will actually be displayed, so we sort it. 850 Collections.sort(items); 851 } 852 } 853 854 private Integer getDynamicId(String name) { 855 synchronized (mDynamicIds) { 856 Integer value = mDynamicIds.get(name); 857 if (value == null) { 858 value = new Integer(++mDynamicSeed); 859 mDynamicIds.put(name, value); 860 } 861 862 return value; 863 } 864 } 865 866 /** 867 * Looks up an existing {@link ProjectResourceItem} by {@link ResourceType} and name. 868 * @param type the Resource Type. 869 * @param name the Resource name. 870 * @return the existing ResourceItem or null if no match was found. 871 */ 872 protected ProjectResourceItem findResourceItem(ResourceType type, String name) { 873 List<ProjectResourceItem> list = mResourceMap.get(type); 874 875 for (ProjectResourceItem item : list) { 876 if (name.equals(item.getName())) { 877 return item; 878 } 879 } 880 881 return null; 882 } 883 884 /** 885 * Sets compiled resource information. 886 * @param resIdValueToNameMap a map of compiled resource id to resource name. 887 * The map is acquired by the {@link ProjectResources} object. 888 * @param styleableValueMap 889 * @param resourceValueMap a map of (name, id) for resources of type {@link ResourceType#ID}. 890 * The list is acquired by the {@link ProjectResources} object. 891 */ 892 void setCompiledResources(Map<Integer, String[]> resIdValueToNameMap, 893 Map<IntArrayWrapper, String> styleableValueMap, 894 Map<String, Map<String, Integer>> resourceValueMap) { 895 mResourceValueMap = resourceValueMap; 896 mResIdValueToNameMap = resIdValueToNameMap; 897 mStyleableValueToNameMap = styleableValueMap; 898 mergeIdResources(); 899 } 900 901 /** 902 * Merges the list of ID resource coming from R.java and the list of ID resources 903 * coming from XML declaration into the cached list {@link #mIdResourceList}. 904 */ 905 void mergeIdResources() { 906 // get the list of IDs coming from XML declaration. Those ids are present in 907 // mCompiledIdResources already, so we'll need to use those instead of creating 908 // new IdResourceItem 909 List<ProjectResourceItem> xmlIdResources = mResourceMap.get(ResourceType.ID); 910 911 synchronized (mIdResourceList) { 912 // copy the currently cached items. 913 ArrayList<IdResourceItem> oldItems = new ArrayList<IdResourceItem>(); 914 oldItems.addAll(mIdResourceList); 915 916 // empty the current list 917 mIdResourceList.clear(); 918 919 // get the list of compile id resources. 920 Map<String, Integer> idMap = null; 921 if (mResourceValueMap != null) { 922 idMap = mResourceValueMap.get(ResourceType.ID.getName()); 923 } 924 925 if (idMap == null) { 926 if (xmlIdResources != null) { 927 for (ProjectResourceItem resourceItem : xmlIdResources) { 928 // check the actual class just for safety. 929 if (resourceItem instanceof IdResourceItem) { 930 mIdResourceList.add((IdResourceItem)resourceItem); 931 } 932 } 933 } 934 } else { 935 // loop on the full list of id, and look for a match in the old list, 936 // in the list coming from XML (in case a new XML item was created.) 937 938 Set<String> idSet = idMap.keySet(); 939 940 idLoop: for (String idResource : idSet) { 941 // first look in the XML list in case an id went from inline to XML declared. 942 if (xmlIdResources != null) { 943 for (ProjectResourceItem resourceItem : xmlIdResources) { 944 if (resourceItem instanceof IdResourceItem && 945 resourceItem.getName().equals(idResource)) { 946 mIdResourceList.add((IdResourceItem)resourceItem); 947 continue idLoop; 948 } 949 } 950 } 951 952 // if we haven't found it, look in the old items. 953 int count = oldItems.size(); 954 for (int i = 0 ; i < count ; i++) { 955 IdResourceItem resourceItem = oldItems.get(i); 956 if (resourceItem.getName().equals(idResource)) { 957 oldItems.remove(i); 958 mIdResourceList.add(resourceItem); 959 continue idLoop; 960 } 961 } 962 963 // if we haven't found it, it looks like it's a new id that was 964 // declared inline. 965 mIdResourceList.add(new IdResourceItem(idResource, 966 true /* isDeclaredInline */)); 967 } 968 } 969 970 // now we sort the list 971 Collections.sort(mIdResourceList); 972 } 973 } 974 } 975