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