ResourceBundle.java revision 28e7064d455e2ef9da31c817dfc05ec7405c60df
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * http://www.apache.org/licenses/LICENSE-2.0 7 * Unless required by applicable law or agreed to in writing, software 8 * distributed under the License is distributed on an "AS IS" BASIS, 9 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 * See the License for the specific language governing permissions and 11 * limitations under the License. 12 */ 13 14package android.databinding.tool.store; 15 16import org.apache.commons.lang3.ArrayUtils; 17 18import android.databinding.tool.processing.ErrorMessages; 19import android.databinding.tool.processing.Scope; 20import android.databinding.tool.processing.ScopedException; 21import android.databinding.tool.processing.scopes.FileScopeProvider; 22import android.databinding.tool.processing.scopes.LocationScopeProvider; 23import android.databinding.tool.util.L; 24import android.databinding.tool.util.ParserHelper; 25import android.databinding.tool.util.Preconditions; 26 27import java.io.File; 28import java.io.InputStream; 29import java.io.Serializable; 30import java.io.StringWriter; 31import java.util.ArrayList; 32import java.util.Arrays; 33import java.util.HashMap; 34import java.util.HashSet; 35import java.util.List; 36import java.util.Map; 37import java.util.Set; 38 39import javax.xml.bind.JAXBContext; 40import javax.xml.bind.JAXBException; 41import javax.xml.bind.Marshaller; 42import javax.xml.bind.Unmarshaller; 43import javax.xml.bind.annotation.XmlAccessType; 44import javax.xml.bind.annotation.XmlAccessorType; 45import javax.xml.bind.annotation.XmlAttribute; 46import javax.xml.bind.annotation.XmlElement; 47import javax.xml.bind.annotation.XmlElementWrapper; 48import javax.xml.bind.annotation.XmlRootElement; 49 50/** 51 * This is a serializable class that can keep the result of parsing layout files. 52 */ 53public class ResourceBundle implements Serializable { 54 private static final String[] ANDROID_VIEW_PACKAGE_VIEWS = new String[] 55 {"View", "ViewGroup", "ViewStub", "TextureView", "SurfaceView"}; 56 private String mAppPackage; 57 58 private HashMap<String, List<LayoutFileBundle>> mLayoutBundles 59 = new HashMap<String, List<LayoutFileBundle>>(); 60 61 private List<File> mRemovedFiles = new ArrayList<File>(); 62 63 public ResourceBundle(String appPackage) { 64 mAppPackage = appPackage; 65 } 66 67 public void addLayoutBundle(LayoutFileBundle bundle) { 68 if (bundle.mFileName == null) { 69 L.e("File bundle must have a name. %s does not have one.", bundle); 70 return; 71 } 72 if (!mLayoutBundles.containsKey(bundle.mFileName)) { 73 mLayoutBundles.put(bundle.mFileName, new ArrayList<LayoutFileBundle>()); 74 } 75 final List<LayoutFileBundle> bundles = mLayoutBundles.get(bundle.mFileName); 76 for (LayoutFileBundle existing : bundles) { 77 if (existing.equals(bundle)) { 78 L.d("skipping layout bundle %s because it already exists.", bundle); 79 return; 80 } 81 } 82 L.d("adding bundle %s", bundle); 83 bundles.add(bundle); 84 } 85 86 public HashMap<String, List<LayoutFileBundle>> getLayoutBundles() { 87 return mLayoutBundles; 88 } 89 90 public String getAppPackage() { 91 return mAppPackage; 92 } 93 94 public void validateMultiResLayouts() { 95 for (List<LayoutFileBundle> layoutFileBundles : mLayoutBundles.values()) { 96 for (LayoutFileBundle layoutFileBundle : layoutFileBundles) { 97 List<BindingTargetBundle> unboundIncludes = new ArrayList<BindingTargetBundle>(); 98 for (BindingTargetBundle target : layoutFileBundle.getBindingTargetBundles()) { 99 if (target.isBinder()) { 100 List<LayoutFileBundle> boundTo = 101 mLayoutBundles.get(target.getIncludedLayout()); 102 if (boundTo == null || boundTo.isEmpty()) { 103 L.d("There is no binding for %s, reverting to plain layout", 104 target.getIncludedLayout()); 105 if (target.getId() == null) { 106 unboundIncludes.add(target); 107 } else { 108 target.setIncludedLayout(null); 109 target.setInterfaceType("android.view.View"); 110 target.mViewName = "android.view.View"; 111 } 112 } else { 113 String binding = boundTo.get(0).getFullBindingClass(); 114 target.setInterfaceType(binding); 115 } 116 } 117 } 118 layoutFileBundle.getBindingTargetBundles().removeAll(unboundIncludes); 119 } 120 } 121 122 for (Map.Entry<String, List<LayoutFileBundle>> bundles : mLayoutBundles.entrySet()) { 123 if (bundles.getValue().size() < 2) { 124 continue; 125 } 126 127 // validate all ids are in correct view types 128 // and all variables have the same name 129 for (LayoutFileBundle bundle : bundles.getValue()) { 130 bundle.mHasVariations = true; 131 } 132 String bindingClass = validateAndGetSharedClassName(bundles.getValue()); 133 Map<String, NameTypeLocation> variableTypes = validateAndMergeNameTypeLocations( 134 bundles.getValue(), ErrorMessages.MULTI_CONFIG_VARIABLE_TYPE_MISMATCH, 135 new ValidateAndFilterCallback() { 136 @Override 137 public List<? extends NameTypeLocation> get(LayoutFileBundle bundle) { 138 return bundle.mVariables; 139 } 140 }); 141 142 Map<String, NameTypeLocation> importTypes = validateAndMergeNameTypeLocations( 143 bundles.getValue(), ErrorMessages.MULTI_CONFIG_IMPORT_TYPE_MISMATCH, 144 new ValidateAndFilterCallback() { 145 @Override 146 public List<NameTypeLocation> get(LayoutFileBundle bundle) { 147 return bundle.mImports; 148 } 149 }); 150 151 for (LayoutFileBundle bundle : bundles.getValue()) { 152 // now add missing ones to each to ensure they can be referenced 153 L.d("checking for missing variables in %s / %s", bundle.mFileName, 154 bundle.mConfigName); 155 for (Map.Entry<String, NameTypeLocation> variable : variableTypes.entrySet()) { 156 if (!NameTypeLocation.contains(bundle.mVariables, variable.getKey())) { 157 NameTypeLocation orig = variable.getValue(); 158 bundle.addVariable(orig.name, orig.type, orig.location, false); 159 L.d("adding missing variable %s to %s / %s", variable.getKey(), 160 bundle.mFileName, bundle.mConfigName); 161 } 162 } 163 for (Map.Entry<String, NameTypeLocation> userImport : importTypes.entrySet()) { 164 if (!NameTypeLocation.contains(bundle.mImports, userImport.getKey())) { 165 bundle.mImports.add(userImport.getValue()); 166 L.d("adding missing import %s to %s / %s", userImport.getKey(), 167 bundle.mFileName, bundle.mConfigName); 168 } 169 } 170 } 171 172 Set<String> includeBindingIds = new HashSet<String>(); 173 Set<String> viewBindingIds = new HashSet<String>(); 174 Map<String, String> viewTypes = new HashMap<String, String>(); 175 Map<String, String> includes = new HashMap<String, String>(); 176 L.d("validating ids for %s", bundles.getKey()); 177 Set<String> conflictingIds = new HashSet<String>(); 178 for (LayoutFileBundle bundle : bundles.getValue()) { 179 try { 180 Scope.enter(bundle); 181 for (BindingTargetBundle target : bundle.mBindingTargetBundles) { 182 try { 183 Scope.enter(target); 184 L.d("checking %s %s %s", target.getId(), target.getFullClassName(), 185 target.isBinder()); 186 if (target.mId != null) { 187 if (target.isBinder()) { 188 if (viewBindingIds.contains(target.mId)) { 189 L.d("%s is conflicting", target.mId); 190 conflictingIds.add(target.mId); 191 continue; 192 } 193 includeBindingIds.add(target.mId); 194 } else { 195 if (includeBindingIds.contains(target.mId)) { 196 L.d("%s is conflicting", target.mId); 197 conflictingIds.add(target.mId); 198 continue; 199 } 200 viewBindingIds.add(target.mId); 201 } 202 String existingType = viewTypes.get(target.mId); 203 if (existingType == null) { 204 L.d("assigning %s as %s", target.getId(), 205 target.getFullClassName()); 206 viewTypes.put(target.mId, target.getFullClassName()); 207 if (target.isBinder()) { 208 includes.put(target.mId, target.getIncludedLayout()); 209 } 210 } else if (!existingType.equals(target.getFullClassName())) { 211 if (target.isBinder()) { 212 L.d("overriding %s as base binder", target.getId()); 213 viewTypes.put(target.mId, 214 "android.databinding.ViewDataBinding"); 215 includes.put(target.mId, target.getIncludedLayout()); 216 } else { 217 L.d("overriding %s as base view", target.getId()); 218 viewTypes.put(target.mId, "android.view.View"); 219 } 220 } 221 } 222 } catch (ScopedException ex) { 223 Scope.defer(ex); 224 } finally { 225 Scope.exit(); 226 } 227 } 228 } finally { 229 Scope.exit(); 230 } 231 } 232 233 if (!conflictingIds.isEmpty()) { 234 for (LayoutFileBundle bundle : bundles.getValue()) { 235 for (BindingTargetBundle target : bundle.mBindingTargetBundles) { 236 if (conflictingIds.contains(target.mId)) { 237 Scope.registerError(String.format( 238 ErrorMessages.MULTI_CONFIG_ID_USED_AS_IMPORT, 239 target.mId), bundle, target); 240 } 241 } 242 } 243 } 244 245 for (LayoutFileBundle bundle : bundles.getValue()) { 246 try { 247 Scope.enter(bundle); 248 for (Map.Entry<String, String> viewType : viewTypes.entrySet()) { 249 BindingTargetBundle target = bundle.getBindingTargetById(viewType.getKey()); 250 if (target == null) { 251 String include = includes.get(viewType.getKey()); 252 if (include == null) { 253 bundle.createBindingTarget(viewType.getKey(), viewType.getValue(), 254 false, null, null, null); 255 } else { 256 BindingTargetBundle bindingTargetBundle = bundle 257 .createBindingTarget( 258 viewType.getKey(), null, false, null, null, null); 259 bindingTargetBundle 260 .setIncludedLayout(includes.get(viewType.getKey())); 261 bindingTargetBundle.setInterfaceType(viewType.getValue()); 262 } 263 } else { 264 L.d("setting interface type on %s (%s) as %s", target.mId, 265 target.getFullClassName(), viewType.getValue()); 266 target.setInterfaceType(viewType.getValue()); 267 } 268 } 269 } catch (ScopedException ex) { 270 Scope.defer(ex); 271 } finally { 272 Scope.exit(); 273 } 274 } 275 } 276 // assign class names to each 277 for (Map.Entry<String, List<LayoutFileBundle>> entry : mLayoutBundles.entrySet()) { 278 for (LayoutFileBundle bundle : entry.getValue()) { 279 final String configName; 280 if (bundle.hasVariations()) { 281 // append configuration specifiers. 282 final String parentFileName = bundle.mDirectory; 283 L.d("parent file for %s is %s", bundle.getFileName(), parentFileName); 284 if ("layout".equals(parentFileName)) { 285 configName = ""; 286 } else { 287 configName = ParserHelper.toClassName(parentFileName.substring("layout-".length())); 288 } 289 } else { 290 configName = ""; 291 } 292 bundle.mConfigName = configName; 293 } 294 } 295 } 296 297 /** 298 * Receives a list of bundles which are representations of the same layout file in different 299 * configurations. 300 * @param bundles 301 * @return The map for variables and their types 302 */ 303 private Map<String, NameTypeLocation> validateAndMergeNameTypeLocations( 304 List<LayoutFileBundle> bundles, String errorMessage, 305 ValidateAndFilterCallback callback) { 306 Map<String, NameTypeLocation> result = new HashMap<String, NameTypeLocation>(); 307 Set<String> mismatched = new HashSet<String>(); 308 for (LayoutFileBundle bundle : bundles) { 309 for (NameTypeLocation item : callback.get(bundle)) { 310 NameTypeLocation existing = result.get(item.name); 311 if (existing != null && !existing.type.equals(item.type)) { 312 mismatched.add(item.name); 313 continue; 314 } 315 result.put(item.name, item); 316 } 317 } 318 if (mismatched.isEmpty()) { 319 return result; 320 } 321 // create exceptions. We could get more clever and find the outlier but for now, listing 322 // each file w/ locations seems enough 323 for (String mismatch : mismatched) { 324 for (LayoutFileBundle bundle : bundles) { 325 NameTypeLocation found = null; 326 for (NameTypeLocation item : callback.get(bundle)) { 327 if (mismatch.equals(item.name)) { 328 found = item; 329 break; 330 } 331 } 332 if (found == null) { 333 // variable is not defined in this layout, continue 334 continue; 335 } 336 Scope.registerError(String.format( 337 errorMessage, found.name, found.type, 338 bundle.mDirectory + "/" + bundle.getFileName()), bundle, 339 found.location.createScope()); 340 } 341 } 342 return result; 343 } 344 345 /** 346 * Receives a list of bundles which are representations of the same layout file in different 347 * configurations. 348 * @param bundles 349 * @return The shared class name for these bundles 350 */ 351 private String validateAndGetSharedClassName(List<LayoutFileBundle> bundles) { 352 String sharedClassName = null; 353 boolean hasMismatch = false; 354 for (LayoutFileBundle bundle : bundles) { 355 bundle.mHasVariations = true; 356 String fullBindingClass = bundle.getFullBindingClass(); 357 if (sharedClassName == null) { 358 sharedClassName = fullBindingClass; 359 } else if (!sharedClassName.equals(fullBindingClass)) { 360 hasMismatch = true; 361 break; 362 } 363 } 364 if (!hasMismatch) { 365 return sharedClassName; 366 } 367 // generate proper exceptions for each 368 for (LayoutFileBundle bundle : bundles) { 369 Scope.registerError(String.format(ErrorMessages.MULTI_CONFIG_LAYOUT_CLASS_NAME_MISMATCH, 370 bundle.getFullBindingClass(), bundle.mDirectory + "/" + bundle.getFileName()), 371 bundle, bundle.getClassNameLocationProvider()); 372 } 373 return sharedClassName; 374 } 375 376 public void addRemovedFile(File file) { 377 mRemovedFiles.add(file); 378 } 379 380 public List<File> getRemovedFiles() { 381 return mRemovedFiles; 382 } 383 384 @XmlAccessorType(XmlAccessType.NONE) 385 @XmlRootElement(name="Layout") 386 public static class LayoutFileBundle implements Serializable, FileScopeProvider { 387 @XmlAttribute(name="layout", required = true) 388 public String mFileName; 389 @XmlAttribute(name="modulePackage", required = true) 390 public String mModulePackage; 391 @XmlAttribute(name="absoluteFilePath", required = true) 392 public String mAbsoluteFilePath; 393 private String mConfigName; 394 395 // The binding class as given by the user 396 @XmlAttribute(name="bindingClass", required = false) 397 public String mBindingClass; 398 399 // The location of the name of the generated class, optional 400 @XmlElement(name = "ClassNameLocation", required = false) 401 private Location mClassNameLocation; 402 // The full package and class name as determined from mBindingClass and mModulePackage 403 private String mFullBindingClass; 404 405 // The simple binding class name as determined from mBindingClass and mModulePackage 406 private String mBindingClassName; 407 408 // The package of the binding class as determined from mBindingClass and mModulePackage 409 private String mBindingPackage; 410 411 @XmlAttribute(name="directory", required = true) 412 public String mDirectory; 413 public boolean mHasVariations; 414 415 @XmlElement(name="Variables") 416 public List<VariableDeclaration> mVariables = new ArrayList<VariableDeclaration>(); 417 418 @XmlElement(name="Imports") 419 public List<NameTypeLocation> mImports = new ArrayList<NameTypeLocation>(); 420 421 @XmlElementWrapper(name="Targets") 422 @XmlElement(name="Target") 423 public List<BindingTargetBundle> mBindingTargetBundles = new ArrayList<BindingTargetBundle>(); 424 425 @XmlAttribute(name="isMerge", required = true) 426 private boolean mIsMerge; 427 428 private LocationScopeProvider mClassNameLocationProvider; 429 430 // for XML binding 431 public LayoutFileBundle() { 432 } 433 434 /** 435 * Updates configuration fields from the given bundle but does not change variables, 436 * binding expressions etc. 437 */ 438 public void inheritConfigurationFrom(LayoutFileBundle other) { 439 mFileName = other.mFileName; 440 mModulePackage = other.mModulePackage; 441 mBindingClass = other.mBindingClass; 442 mFullBindingClass = other.mFullBindingClass; 443 mBindingClassName = other.mBindingClassName; 444 mBindingPackage = other.mBindingPackage; 445 mHasVariations = other.mHasVariations; 446 mIsMerge = other.mIsMerge; 447 } 448 449 public LayoutFileBundle(File file, String fileName, String directory, 450 String modulePackage, boolean isMerge) { 451 mFileName = fileName; 452 mDirectory = directory; 453 mModulePackage = modulePackage; 454 mIsMerge = isMerge; 455 mAbsoluteFilePath = file.getAbsolutePath(); 456 } 457 458 public LocationScopeProvider getClassNameLocationProvider() { 459 if (mClassNameLocationProvider == null && mClassNameLocation != null 460 && mClassNameLocation.isValid()) { 461 mClassNameLocationProvider = mClassNameLocation.createScope(); 462 } 463 return mClassNameLocationProvider; 464 } 465 466 public void addVariable(String name, String type, Location location, boolean declared) { 467 Preconditions.check(!NameTypeLocation.contains(mVariables, name), 468 "Cannot use same variable name twice. %s in %s", name, location); 469 mVariables.add(new VariableDeclaration(name, type, location, declared)); 470 } 471 472 public void addImport(String alias, String type, Location location) { 473 Preconditions.check(!NameTypeLocation.contains(mImports, alias), 474 "Cannot import same alias twice. %s in %s", alias, location); 475 mImports.add(new NameTypeLocation(alias, type, location)); 476 } 477 478 public BindingTargetBundle createBindingTarget(String id, String viewName, 479 boolean used, String tag, String originalTag, Location location) { 480 BindingTargetBundle target = new BindingTargetBundle(id, viewName, used, tag, 481 originalTag, location); 482 mBindingTargetBundles.add(target); 483 return target; 484 } 485 486 public boolean isEmpty() { 487 return mVariables.isEmpty() && mImports.isEmpty() && mBindingTargetBundles.isEmpty(); 488 } 489 490 public BindingTargetBundle getBindingTargetById(String key) { 491 for (BindingTargetBundle target : mBindingTargetBundles) { 492 if (key.equals(target.mId)) { 493 return target; 494 } 495 } 496 return null; 497 } 498 499 public String getFileName() { 500 return mFileName; 501 } 502 503 public String getConfigName() { 504 return mConfigName; 505 } 506 507 public String getDirectory() { 508 return mDirectory; 509 } 510 511 public boolean hasVariations() { 512 return mHasVariations; 513 } 514 515 public List<VariableDeclaration> getVariables() { 516 return mVariables; 517 } 518 519 public List<NameTypeLocation> getImports() { 520 return mImports; 521 } 522 523 public boolean isMerge() { 524 return mIsMerge; 525 } 526 527 public String getBindingClassName() { 528 if (mBindingClassName == null) { 529 String fullClass = getFullBindingClass(); 530 int dotIndex = fullClass.lastIndexOf('.'); 531 mBindingClassName = fullClass.substring(dotIndex + 1); 532 } 533 return mBindingClassName; 534 } 535 536 public void setBindingClass(String bindingClass, Location location) { 537 mBindingClass = bindingClass; 538 mClassNameLocation = location; 539 } 540 541 public String getBindingClassPackage() { 542 if (mBindingPackage == null) { 543 String fullClass = getFullBindingClass(); 544 int dotIndex = fullClass.lastIndexOf('.'); 545 mBindingPackage = fullClass.substring(0, dotIndex); 546 } 547 return mBindingPackage; 548 } 549 550 private String getFullBindingClass() { 551 if (mFullBindingClass == null) { 552 if (mBindingClass == null) { 553 mFullBindingClass = getModulePackage() + ".databinding." + 554 ParserHelper.toClassName(getFileName()) + "Binding"; 555 } else if (mBindingClass.startsWith(".")) { 556 mFullBindingClass = getModulePackage() + mBindingClass; 557 } else if (mBindingClass.indexOf('.') < 0) { 558 mFullBindingClass = getModulePackage() + ".databinding." + mBindingClass; 559 } else { 560 mFullBindingClass = mBindingClass; 561 } 562 } 563 return mFullBindingClass; 564 } 565 566 public List<BindingTargetBundle> getBindingTargetBundles() { 567 return mBindingTargetBundles; 568 } 569 570 @Override 571 public boolean equals(Object o) { 572 if (this == o) { 573 return true; 574 } 575 if (o == null || getClass() != o.getClass()) { 576 return false; 577 } 578 579 LayoutFileBundle bundle = (LayoutFileBundle) o; 580 581 if (mConfigName != null ? !mConfigName.equals(bundle.mConfigName) 582 : bundle.mConfigName != null) { 583 return false; 584 } 585 if (mDirectory != null ? !mDirectory.equals(bundle.mDirectory) 586 : bundle.mDirectory != null) { 587 return false; 588 } 589 if (mFileName != null ? !mFileName.equals(bundle.mFileName) 590 : bundle.mFileName != null) { 591 return false; 592 } 593 594 return true; 595 } 596 597 @Override 598 public int hashCode() { 599 int result = mFileName != null ? mFileName.hashCode() : 0; 600 result = 31 * result + (mConfigName != null ? mConfigName.hashCode() : 0); 601 result = 31 * result + (mDirectory != null ? mDirectory.hashCode() : 0); 602 return result; 603 } 604 605 @Override 606 public String toString() { 607 return "LayoutFileBundle{" + 608 "mHasVariations=" + mHasVariations + 609 ", mDirectory='" + mDirectory + '\'' + 610 ", mConfigName='" + mConfigName + '\'' + 611 ", mModulePackage='" + mModulePackage + '\'' + 612 ", mFileName='" + mFileName + '\'' + 613 '}'; 614 } 615 616 public String getModulePackage() { 617 return mModulePackage; 618 } 619 620 public String getAbsoluteFilePath() { 621 return mAbsoluteFilePath; 622 } 623 624 @Override 625 public String provideScopeFilePath() { 626 return mAbsoluteFilePath; 627 } 628 629 private static Marshaller sMarshaller; 630 private static Unmarshaller sUmarshaller; 631 632 public String toXML() throws JAXBException { 633 StringWriter writer = new StringWriter(); 634 getMarshaller().marshal(this, writer); 635 return writer.getBuffer().toString(); 636 } 637 638 public static LayoutFileBundle fromXML(InputStream inputStream) throws JAXBException { 639 return (LayoutFileBundle) getUnmarshaller().unmarshal(inputStream); 640 } 641 642 private static Marshaller getMarshaller() throws JAXBException { 643 if (sMarshaller == null) { 644 JAXBContext context = JAXBContext 645 .newInstance(ResourceBundle.LayoutFileBundle.class); 646 sMarshaller = context.createMarshaller(); 647 } 648 return sMarshaller; 649 } 650 651 private static Unmarshaller getUnmarshaller() throws JAXBException { 652 if (sUmarshaller == null) { 653 JAXBContext context = JAXBContext 654 .newInstance(ResourceBundle.LayoutFileBundle.class); 655 sUmarshaller = context.createUnmarshaller(); 656 } 657 return sUmarshaller; 658 } 659 } 660 661 @XmlAccessorType(XmlAccessType.NONE) 662 public static class NameTypeLocation { 663 @XmlAttribute(name="type", required = true) 664 public String type; 665 666 @XmlAttribute(name="name", required = true) 667 public String name; 668 669 @XmlElement(name="location", required = false) 670 public Location location; 671 672 public NameTypeLocation() { 673 } 674 675 public NameTypeLocation(String name, String type, Location location) { 676 this.type = type; 677 this.name = name; 678 this.location = location; 679 } 680 681 @Override 682 public String toString() { 683 return "{" + 684 "type='" + type + '\'' + 685 ", name='" + name + '\'' + 686 ", location=" + location + 687 '}'; 688 } 689 690 @Override 691 public boolean equals(Object o) { 692 if (this == o) { 693 return true; 694 } 695 if (o == null || getClass() != o.getClass()) { 696 return false; 697 } 698 699 NameTypeLocation that = (NameTypeLocation) o; 700 701 if (location != null ? !location.equals(that.location) : that.location != null) { 702 return false; 703 } 704 if (!name.equals(that.name)) { 705 return false; 706 } 707 if (!type.equals(that.type)) { 708 return false; 709 } 710 711 return true; 712 } 713 714 @Override 715 public int hashCode() { 716 int result = type.hashCode(); 717 result = 31 * result + name.hashCode(); 718 result = 31 * result + (location != null ? location.hashCode() : 0); 719 return result; 720 } 721 722 public static boolean contains(List<? extends NameTypeLocation> list, String name) { 723 for (NameTypeLocation ntl : list) { 724 if (name.equals(ntl.name)) { 725 return true; 726 } 727 } 728 return false; 729 } 730 } 731 732 @XmlAccessorType(XmlAccessType.NONE) 733 public static class VariableDeclaration extends NameTypeLocation { 734 @XmlAttribute(name="declared", required = false) 735 public boolean declared; 736 737 public VariableDeclaration() { 738 739 } 740 741 public VariableDeclaration(String name, String type, Location location, boolean declared) { 742 super(name, type, location); 743 this.declared = declared; 744 } 745 } 746 747 public static class MarshalledMapType { 748 public List<NameTypeLocation> entries; 749 } 750 751 @XmlAccessorType(XmlAccessType.NONE) 752 public static class BindingTargetBundle implements Serializable, LocationScopeProvider { 753 // public for XML serialization 754 755 @XmlAttribute(name="id") 756 public String mId; 757 @XmlAttribute(name="tag", required = true) 758 public String mTag; 759 @XmlAttribute(name="originalTag") 760 public String mOriginalTag; 761 @XmlAttribute(name="view", required = false) 762 public String mViewName; 763 private String mFullClassName; 764 public boolean mUsed = true; 765 @XmlElementWrapper(name="Expressions") 766 @XmlElement(name="Expression") 767 public List<BindingBundle> mBindingBundleList = new ArrayList<BindingBundle>(); 768 @XmlAttribute(name="include") 769 public String mIncludedLayout; 770 @XmlElement(name="location") 771 public Location mLocation; 772 private String mInterfaceType; 773 774 // For XML serialization 775 public BindingTargetBundle() {} 776 777 public BindingTargetBundle(String id, String viewName, boolean used, 778 String tag, String originalTag, Location location) { 779 mId = id; 780 mViewName = viewName; 781 mUsed = used; 782 mTag = tag; 783 mOriginalTag = originalTag; 784 mLocation = location; 785 } 786 787 public void addBinding(String name, String expr, Location location, Location valueLocation) { 788 mBindingBundleList.add(new BindingBundle(name, expr, location, valueLocation)); 789 } 790 791 public void setIncludedLayout(String includedLayout) { 792 mIncludedLayout = includedLayout; 793 } 794 795 public String getIncludedLayout() { 796 return mIncludedLayout; 797 } 798 799 public boolean isBinder() { 800 return mIncludedLayout != null; 801 } 802 803 public void setInterfaceType(String interfaceType) { 804 mInterfaceType = interfaceType; 805 } 806 807 public void setLocation(Location location) { 808 mLocation = location; 809 } 810 811 public Location getLocation() { 812 return mLocation; 813 } 814 815 public String getId() { 816 return mId; 817 } 818 819 public String getTag() { 820 return mTag; 821 } 822 823 public String getOriginalTag() { 824 return mOriginalTag; 825 } 826 827 public String getFullClassName() { 828 if (mFullClassName == null) { 829 if (isBinder()) { 830 mFullClassName = mInterfaceType; 831 } else if (mViewName.indexOf('.') == -1) { 832 if (ArrayUtils.contains(ANDROID_VIEW_PACKAGE_VIEWS, mViewName)) { 833 mFullClassName = "android.view." + mViewName; 834 } else if("WebView".equals(mViewName)) { 835 mFullClassName = "android.webkit." + mViewName; 836 } else { 837 mFullClassName = "android.widget." + mViewName; 838 } 839 } else { 840 mFullClassName = mViewName; 841 } 842 } 843 if (mFullClassName == null) { 844 L.e("Unexpected full class name = null. view = %s, interface = %s, layout = %s", 845 mViewName, mInterfaceType, mIncludedLayout); 846 } 847 return mFullClassName; 848 } 849 850 public boolean isUsed() { 851 return mUsed; 852 } 853 854 public List<BindingBundle> getBindingBundleList() { 855 return mBindingBundleList; 856 } 857 858 public String getInterfaceType() { 859 return mInterfaceType; 860 } 861 862 @Override 863 public List<Location> provideScopeLocation() { 864 return mLocation == null ? null : Arrays.asList(mLocation); 865 } 866 867 @XmlAccessorType(XmlAccessType.NONE) 868 public static class BindingBundle implements Serializable { 869 870 private String mName; 871 private String mExpr; 872 private Location mLocation; 873 private Location mValueLocation; 874 875 public BindingBundle() {} 876 877 public BindingBundle(String name, String expr, Location location, 878 Location valueLocation) { 879 mName = name; 880 mExpr = expr; 881 mLocation = location; 882 mValueLocation = valueLocation; 883 } 884 885 @XmlAttribute(name="attribute", required=true) 886 public String getName() { 887 return mName; 888 } 889 890 @XmlAttribute(name="text", required=true) 891 public String getExpr() { 892 return mExpr; 893 } 894 895 public void setName(String name) { 896 mName = name; 897 } 898 899 public void setExpr(String expr) { 900 mExpr = expr; 901 } 902 903 @XmlElement(name="Location") 904 public Location getLocation() { 905 return mLocation; 906 } 907 908 public void setLocation(Location location) { 909 mLocation = location; 910 } 911 912 @XmlElement(name="ValueLocation") 913 public Location getValueLocation() { 914 return mValueLocation; 915 } 916 917 public void setValueLocation(Location valueLocation) { 918 mValueLocation = valueLocation; 919 } 920 } 921 } 922 923 /** 924 * Just an inner callback class to process imports and variables w/ the same code. 925 */ 926 private interface ValidateAndFilterCallback { 927 List<? extends NameTypeLocation> get(LayoutFileBundle bundle); 928 } 929} 930