001// Copyright 2022, 2023 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014package org.apache.tapestry5.internal.services; 015 016import java.io.BufferedReader; 017import java.io.BufferedWriter; 018import java.io.File; 019import java.io.FileReader; 020import java.io.FileWriter; 021import java.io.IOException; 022import java.lang.reflect.Field; 023import java.util.ArrayList; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.Iterator; 029import java.util.LinkedList; 030import java.util.List; 031import java.util.Locale; 032import java.util.Map; 033import java.util.Objects; 034import java.util.Set; 035import java.util.WeakHashMap; 036import java.util.function.Consumer; 037import java.util.stream.Collectors; 038 039import org.apache.tapestry5.ComponentResources; 040import org.apache.tapestry5.SymbolConstants; 041import org.apache.tapestry5.annotations.InjectComponent; 042import org.apache.tapestry5.annotations.InjectPage; 043import org.apache.tapestry5.annotations.Mixin; 044import org.apache.tapestry5.annotations.MixinClasses; 045import org.apache.tapestry5.annotations.Mixins; 046import org.apache.tapestry5.commons.Resource; 047import org.apache.tapestry5.commons.internal.util.TapestryException; 048import org.apache.tapestry5.commons.services.InvalidationEventHub; 049import org.apache.tapestry5.internal.TapestryInternalUtils; 050import org.apache.tapestry5.internal.parser.ComponentTemplate; 051import org.apache.tapestry5.internal.parser.StartComponentToken; 052import org.apache.tapestry5.internal.parser.TemplateToken; 053import org.apache.tapestry5.internal.structure.ComponentPageElement; 054import org.apache.tapestry5.ioc.Orderable; 055import org.apache.tapestry5.ioc.annotations.Symbol; 056import org.apache.tapestry5.ioc.internal.util.ClasspathResource; 057import org.apache.tapestry5.ioc.internal.util.InternalUtils; 058import org.apache.tapestry5.ioc.services.PerthreadManager; 059import org.apache.tapestry5.json.JSONArray; 060import org.apache.tapestry5.json.JSONObject; 061import org.apache.tapestry5.model.ComponentModel; 062import org.apache.tapestry5.model.EmbeddedComponentModel; 063import org.apache.tapestry5.model.MutableComponentModel; 064import org.apache.tapestry5.model.ParameterModel; 065import org.apache.tapestry5.plastic.PlasticField; 066import org.apache.tapestry5.plastic.PlasticManager; 067import org.apache.tapestry5.runtime.Component; 068import org.apache.tapestry5.services.ComponentClassResolver; 069import org.apache.tapestry5.services.pageload.PageClassLoaderContextManager; 070import org.apache.tapestry5.services.templates.ComponentTemplateLocator; 071import org.slf4j.Logger; 072import org.slf4j.LoggerFactory; 073 074@SuppressWarnings("deprecation") 075public class ComponentDependencyRegistryImpl implements ComponentDependencyRegistry 076{ 077 078 private static final List<String> EMPTY_LIST = Collections.emptyList(); 079 080 final private PageClassLoaderContextManager pageClassLoaderContextManager; 081 082 private static final String META_ATTRIBUTE = "injectedComponentDependencies"; 083 084 private static final String META_ATTRIBUTE_SEPARATOR = ","; 085 086 // Key is a component, values are the components that depend on it. 087 final private Map<String, Set<Dependency>> map; 088 089 // Cache to check which classes were already processed or not. 090 final private Set<String> alreadyProcessed; 091 092 final private File storedDependencies; 093 094 final private static ThreadLocal<Integer> INVALIDATIONS_DISABLED = ThreadLocal.withInitial(() -> 0); 095 096 final private PlasticManager plasticManager; 097 098 final private ComponentClassResolver resolver; 099 100 final private TemplateParser templateParser; 101 102 final private Map<String, Boolean> isPageCache = new WeakHashMap<>(); 103 104 final private ComponentTemplateLocator componentTemplateLocator; 105 106 final private boolean storedDependencyInformationPresent; 107 108 public ComponentDependencyRegistryImpl( 109 final PageClassLoaderContextManager pageClassLoaderContextManager, 110 final PlasticManager plasticManager, 111 final ComponentClassResolver componentClassResolver, 112 final TemplateParser templateParser, 113 final ComponentTemplateLocator componentTemplateLocator, 114 final @Symbol(SymbolConstants.COMPONENT_DEPENDENCY_FILE) String componentDependencyFile, 115 final @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode) 116 { 117 this.pageClassLoaderContextManager = pageClassLoaderContextManager; 118 map = new HashMap<>(); 119 alreadyProcessed = new HashSet<>(); 120 this.plasticManager = plasticManager; 121 this.resolver = componentClassResolver; 122 this.templateParser = templateParser; 123 this.componentTemplateLocator = componentTemplateLocator; 124 125 if (!productionMode) 126 { 127 128 Logger logger = LoggerFactory.getLogger(ComponentDependencyRegistry.class); 129 130 storedDependencies = new File(componentDependencyFile); 131 final boolean fileExists = storedDependencies.exists(); 132 133 logger.info("Component dependencies file: {} Found? {}", 134 storedDependencies.getAbsolutePath(), fileExists); 135 136 if (fileExists) 137 { 138 try (FileReader fileReader = new FileReader(storedDependencies); 139 BufferedReader reader = new BufferedReader(fileReader)) 140 { 141 StringBuilder builder = new StringBuilder(); 142 String line = reader.readLine(); 143 while (line != null) 144 { 145 builder.append(line); 146 line = reader.readLine(); 147 } 148 JSONArray jsonArray = new JSONArray(builder.toString()); 149 for (int i = 0; i < jsonArray.size(); i++) 150 { 151 final JSONObject jsonObject = jsonArray.getJSONObject(i); 152 final String className = jsonObject.getString("class"); 153 final DependencyType dependencyType = DependencyType.valueOf(jsonObject.getString("type")); 154 final String dependency = jsonObject.getString("dependency"); 155 add(className, dependency, dependencyType); 156 alreadyProcessed.add(dependency); 157 alreadyProcessed.add(className); 158 } 159 } catch (IOException e) 160 { 161 throw new TapestryException("Exception trying to read " + storedDependencies.getAbsolutePath(), e); 162 } 163 164 } 165 166 } 167 else 168 { 169 storedDependencies = null; 170 } 171 172 storedDependencyInformationPresent = !map.isEmpty(); 173 174 } 175 176 public void setupThreadCleanup(final PerthreadManager perthreadManager) 177 { 178 perthreadManager.addThreadCleanupCallback(() -> { 179 INVALIDATIONS_DISABLED.set(0); 180 }); 181 } 182 183 @Override 184 public void register(Class<?> component) 185 { 186 187 final String className = component.getName(); 188 final Set<Class<?>> furtherDependencies = new HashSet<>(); 189 Consumer<Class<?>> processClass = furtherDependencies::add; 190 Consumer<String> processClassName = s -> { 191 try { 192 furtherDependencies.add(component.getClassLoader().loadClass(s)); 193 } catch (ClassNotFoundException e) { 194 throw new RuntimeException(e); 195 } 196 }; 197 198 // Components declared in the template 199 registerTemplate(component, processClassName); 200 201 // Dependencies from injecting or component-declaring annotations: 202 // @InjectPage, @InjectComponent 203 for (Field field : component.getDeclaredFields()) 204 { 205 206 // Component injection annotation 207 if (field.isAnnotationPresent(InjectComponent.class)) 208 { 209 final Class<?> dependency = field.getType(); 210 add(component, dependency, DependencyType.USAGE); 211 processClass.accept(dependency); 212 } 213 214 // Page injection annotation 215 if (field.isAnnotationPresent(InjectPage.class)) 216 { 217 final Class<?> dependency = field.getType(); 218 add(component, dependency, DependencyType.INJECT_PAGE); 219 } 220 221 // @Component 222 registerComponentInstance(field, processClassName); 223 224 // Mixins, class level: @Mixin 225 registerMixin(field, processClassName); 226 227 // Mixins applied to embedded component instances through @MixinClasses or @Mixins 228 registerComponentInstanceMixins(field, processClass, processClassName); 229 } 230 231 // Superclass 232 Class<?> superclass = component.getSuperclass(); 233 if (isTransformed(superclass)) 234 { 235 processClass.accept(superclass); 236 add(component, superclass, DependencyType.SUPERCLASS); 237 } 238 239 alreadyProcessed.add(className); 240 241 for (Class<?> dependency : furtherDependencies) 242 { 243 // Avoid infinite recursion 244 final String dependencyClassName = dependency.getName(); 245 if (!alreadyProcessed.contains(dependencyClassName) 246 && plasticManager.shouldInterceptClassLoading(dependency.getName())) 247 { 248 register(dependency); 249 } 250 } 251 252 } 253 254 /** 255 * Notice only the main template (i.e. not the locale- or axis-specific ones) 256 * are checked here. They hopefully will be covered when the ComponentModel-based 257 * component dependency processing is done. 258 * @param component 259 * @param processClassName 260 */ 261 private void registerTemplate(Class<?> component, Consumer<String> processClassName) 262 { 263 // TODO: implement caching of template dependency information, probably 264 // by listening separaterly to ComponentTemplateSource to invalidate caches 265 // just when template changes. 266 267 final String className = component.getName(); 268 ComponentModel mock = new ComponentModelMock(component, isPage(className)); 269 final Resource templateResource = componentTemplateLocator.locateTemplate(mock, Locale.getDefault()); 270 String dependency; 271 if (templateResource != null) 272 { 273 final ComponentTemplate template = templateParser.parseTemplate(templateResource); 274 final List<TemplateToken> tokens = new LinkedList<>(); 275 276 tokens.addAll(template.getTokens()); 277 for (String id : template.getExtensionPointIds()) 278 { 279 tokens.addAll(template.getExtensionPointTokens(id)); 280 } 281 282 for (TemplateToken token : tokens) 283 { 284 if (token instanceof StartComponentToken) 285 { 286 StartComponentToken componentToken = (StartComponentToken) token; 287 String logicalName = componentToken.getComponentType(); 288 if (logicalName != null) 289 { 290 dependency = resolver.resolveComponentTypeToClassName(logicalName); 291 add(className, dependency, DependencyType.USAGE); 292 processClassName.accept(dependency); 293 } 294 for (String mixin : TapestryInternalUtils.splitAtCommas(componentToken.getMixins())) 295 { 296 dependency = resolver.resolveMixinTypeToClassName(mixin); 297 add(className, dependency, DependencyType.USAGE); 298 processClassName.accept(dependency); 299 } 300 } 301 } 302 } 303 } 304 305 private boolean isPage(final String className) 306 { 307 Boolean result = isPageCache.get(className); 308 if (result == null) 309 { 310 result = resolver.isPage(className); 311 isPageCache.put(className, result); 312 } 313 return result; 314 } 315 316 private void registerComponentInstance(Field field, Consumer<String> processClassName) 317 { 318 if (field.isAnnotationPresent(org.apache.tapestry5.annotations.Component.class)) 319 { 320 org.apache.tapestry5.annotations.Component component = 321 field.getAnnotation(org.apache.tapestry5.annotations.Component.class); 322 323 final String typeFromAnnotation = component.type().trim(); 324 String dependency; 325 if (typeFromAnnotation.isEmpty()) 326 { 327 dependency = field.getType().getName(); 328 } 329 else 330 { 331 dependency = resolver.resolveComponentTypeToClassName(typeFromAnnotation); 332 } 333 add(field.getDeclaringClass().getName(), dependency, DependencyType.USAGE); 334 processClassName.accept(dependency); 335 } 336 } 337 338 private void registerMixin(Field field, Consumer<String> processClassName) { 339 if (field.isAnnotationPresent(Mixin.class)) 340 { 341 // Logic adapted from MixinWorker 342 String mixinType = field.getAnnotation(Mixin.class).value(); 343 String mixinClassName = InternalUtils.isBlank(mixinType) ? 344 getFieldTypeClassName(field) : 345 resolver.resolveMixinTypeToClassName(mixinType); 346 347 add(getDeclaringClassName(field), mixinClassName, DependencyType.USAGE); 348 processClassName.accept(mixinClassName); 349 } 350 } 351 352 private String getDeclaringClassName(Field field) { 353 return field.getDeclaringClass().getName(); 354 } 355 356 private String getFieldTypeClassName(Field field) { 357 return field.getType().getName(); 358 } 359 360 private void registerComponentInstanceMixins(Field field, Consumer<Class<?>> processClass, Consumer<String> processClassName) 361 { 362 363 if (field.isAnnotationPresent(org.apache.tapestry5.annotations.Component.class)) 364 { 365 366 MixinClasses mixinClasses = field.getAnnotation(MixinClasses.class); 367 if (mixinClasses != null) 368 { 369 for (Class<?> dependency : mixinClasses.value()) 370 { 371 add(field.getDeclaringClass(), dependency, DependencyType.USAGE); 372 processClass.accept(dependency); 373 } 374 } 375 376 Mixins mixins = field.getAnnotation(Mixins.class); 377 if (mixins != null) 378 { 379 for (String mixin : mixins.value()) 380 { 381 // Logic adapted from MixinsWorker 382 Orderable<String> typeAndOrder = TapestryInternalUtils.mixinTypeAndOrder(mixin); 383 final String dependency = resolver.resolveMixinTypeToClassName(typeAndOrder.getTarget()); 384 add(getDeclaringClassName(field), dependency, DependencyType.USAGE); 385 processClassName.accept(dependency); 386 } 387 } 388 389 } 390 391 } 392 393 @Override 394 public void register(ComponentPageElement componentPageElement) 395 { 396 final String componentClassName = getClassName(componentPageElement); 397 398 if (!alreadyProcessed.contains(componentClassName)) 399 { 400 synchronized (map) 401 { 402 403 // Components in the tree (i.e. declared in the template 404 for (String id : componentPageElement.getEmbeddedElementIds()) 405 { 406 final ComponentPageElement child = componentPageElement.getEmbeddedElement(id); 407 add(componentPageElement, child, DependencyType.USAGE); 408 register(child); 409 } 410 411 // Mixins, class level 412 final ComponentResources componentResources = componentPageElement.getComponentResources(); 413 final ComponentModel componentModel = componentResources.getComponentModel(); 414 for (String mixinClassName : componentModel.getMixinClassNames()) 415 { 416 add(componentClassName, mixinClassName, DependencyType.USAGE); 417 } 418 419 // Mixins applied to embedded component instances 420 final List<String> embeddedComponentIds = componentModel.getEmbeddedComponentIds(); 421 for (String id : embeddedComponentIds) 422 { 423 final EmbeddedComponentModel embeddedComponentModel = componentResources 424 .getComponentModel() 425 .getEmbeddedComponentModel(id); 426 final List<String> mixinClassNames = embeddedComponentModel 427 .getMixinClassNames(); 428 for (String mixinClassName : mixinClassNames) { 429 add(componentClassName, mixinClassName, DependencyType.USAGE); 430 } 431 } 432 433 // Superclass 434 final Component component = componentPageElement.getComponent(); 435 Class<?> parent = component.getClass().getSuperclass(); 436 if (parent != null && !Object.class.equals(parent)) 437 { 438 add(componentClassName, parent.getName(), DependencyType.SUPERCLASS); 439 } 440 441 // Dependencies from injecting annotations: 442 // @InjectPage, @InjectComponent, @InjectComponent 443 final String metaDependencies = component.getComponentResources().getComponentModel().getMeta(META_ATTRIBUTE); 444 if (metaDependencies != null) 445 { 446 for (String dependency : metaDependencies.split(META_ATTRIBUTE_SEPARATOR)) 447 { 448 add(componentClassName, dependency, 449 isPage(dependency) ? DependencyType.INJECT_PAGE : DependencyType.USAGE); 450 } 451 } 452 453 alreadyProcessed.add(componentClassName); 454 455 } 456 457 } 458 459 } 460 461 @Override 462 public void register(PlasticField plasticField, MutableComponentModel componentModel) 463 { 464 if (plasticField.hasAnnotation(InjectPage.class) || 465 plasticField.hasAnnotation(InjectComponent.class) || 466 plasticField.hasAnnotation(org.apache.tapestry5.annotations.Component.class)) 467 { 468 String dependencies = componentModel.getMeta(META_ATTRIBUTE); 469 final String dependency = plasticField.getTypeName(); 470 if (dependencies == null) 471 { 472 dependencies = dependency; 473 } 474 else 475 { 476 if (!dependencies.contains(dependency)) 477 { 478 dependencies = dependencies + META_ATTRIBUTE_SEPARATOR + dependency; 479 } 480 } 481 componentModel.setMeta(META_ATTRIBUTE, dependencies); 482 } 483 } 484 485 private String getClassName(ComponentPageElement component) 486 { 487 return component.getComponentResources().getComponentModel().getComponentClassName(); 488 } 489 490 @Override 491 public void clear(String className) 492 { 493 synchronized (map) 494 { 495 alreadyProcessed.remove(className); 496 map.remove(className); 497 final Collection<Set<Dependency>> allDependentSets = map.values(); 498 for (Set<Dependency> dependents : allDependentSets) 499 { 500 if (dependents != null) 501 { 502 final Iterator<Dependency> iterator = dependents.iterator(); 503 while (iterator.hasNext()) 504 { 505 if (className.equals(iterator.next().className)) 506 { 507 iterator.remove(); 508 } 509 } 510 } 511 } 512 } 513 } 514 515 @Override 516 public void clear(ComponentPageElement component) 517 { 518 clear(getClassName(component)); 519 } 520 521 @Override 522 public void clear() { 523 map.clear(); 524 alreadyProcessed.clear(); 525 } 526 527 @Override 528 public Set<String> getDependents(String className) 529 { 530 final Set<Dependency> dependents = map.get(className); 531 return dependents != null 532 ? dependents.stream().map(d -> d.className).collect(Collectors.toSet()) 533 : Collections.emptySet(); 534 } 535 536 @Override 537 public Set<String> getDependencies(String className, DependencyType type) 538 { 539 Set<String> dependencies = Collections.emptySet(); 540 if (alreadyProcessed.contains(className)) 541 { 542 dependencies = map.entrySet().stream() 543 .filter(e -> contains(e.getValue(), className, type)) 544 .map(e -> e.getKey()) 545 .collect(Collectors.toSet()); 546 } 547 548 return dependencies; 549 } 550 551 552 private boolean contains(Set<Dependency> dependencies, String className, DependencyType type) 553 { 554 boolean contains = false; 555 for (Dependency dependency : dependencies) 556 { 557 if (dependency.type.equals(type) && dependency.className.equals(className)) 558 { 559 contains = true; 560 break; 561 } 562 } 563 return contains; 564 } 565 566 private void add(ComponentPageElement component, ComponentPageElement dependency, DependencyType type) 567 { 568 add(getClassName(component), getClassName(dependency), type); 569 } 570 571 // Just for unit tests 572 void add(String component, String dependency, DependencyType type, boolean markAsAlreadyProcessed) 573 { 574 if (markAsAlreadyProcessed) 575 { 576 alreadyProcessed.add(component); 577 } 578 if (dependency != null) 579 { 580 add(component, dependency, type); 581 } 582 } 583 584 private void add(Class<?> component, Class<?> dependency, DependencyType type) 585 { 586 if (plasticManager.shouldInterceptClassLoading(dependency.getName())) 587 { 588 add(component.getName(), dependency.getName(), type); 589 } 590 } 591 592 private void add(String component, String dependency, DependencyType type) 593 { 594 Objects.requireNonNull(component, "Parameter component cannot be null"); 595 Objects.requireNonNull(dependency, "Parameter dependency cannot be null"); 596 Objects.requireNonNull(dependency, "Parameter type cannot be null"); 597 synchronized (map) 598 { 599 if (!component.equals(dependency)) 600 { 601 Set<Dependency> dependents = map.get(dependency); 602 if (dependents == null) 603 { 604 dependents = new HashSet<>(); 605 map.put(dependency, dependents); 606 } 607 dependents.add(new Dependency(component, type)); 608 } 609 } 610 } 611 612 @Override 613 public void listen(InvalidationEventHub invalidationEventHub) 614 { 615 invalidationEventHub.addInvalidationCallback(this::listen); 616 } 617 618 // Protected just for testing 619 List<String> listen(List<String> resources) 620 { 621 List<String> furtherDependents = EMPTY_LIST; 622 if (resources.isEmpty()) 623 { 624 clear(); 625 furtherDependents = EMPTY_LIST; 626 } 627 else if (INVALIDATIONS_DISABLED.get() > 0) 628 { 629 furtherDependents = Collections.emptyList(); 630 } 631 // Don't invalidate component dependency information when 632 // PageClassloaderContextManager is merging contexts 633 // TODO: is this still needed since the inception of INVALIDATIONS_ENABLED? 634 else if (!pageClassLoaderContextManager.isMerging()) 635 { 636 furtherDependents = new ArrayList<>(); 637 for (String resource : resources) 638 { 639 640 final Set<String> dependents = getDependents(resource); 641 for (String furtherDependent : dependents) 642 { 643 if (!resources.contains(furtherDependent) && !furtherDependents.contains(furtherDependent)) 644 { 645 furtherDependents.add(furtherDependent); 646 } 647 } 648 649 clear(resource); 650 651 } 652 } 653 return furtherDependents; 654 } 655 656 @Override 657 public void writeFile() 658 { 659 synchronized (this) 660 { 661 try (FileWriter fileWriter = new FileWriter(storedDependencies); 662 BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) 663 { 664 Set<String> classNames = new HashSet<>(alreadyProcessed.size()); 665 classNames.addAll(map.keySet()); 666 classNames.addAll(alreadyProcessed); 667 JSONArray jsonArray = new JSONArray(); 668 for (String className : classNames) 669 { 670 for (DependencyType dependencyType : DependencyType.values()) 671 { 672 final Set<String> dependencies = getDependencies(className, dependencyType); 673 for (String dependency : dependencies) 674 { 675 JSONObject object = new JSONObject(); 676 object.put("class", className); 677 object.put("type", dependencyType.name()); 678 object.put("dependency", dependency); 679 jsonArray.add(object); 680 } 681 } 682 } 683 bufferedWriter.write(jsonArray.toString()); 684 } 685 catch (IOException e) 686 { 687 throw new TapestryException("Exception trying to write " + storedDependencies.getAbsolutePath(), e); 688 } 689 690 Logger logger = LoggerFactory.getLogger(ComponentDependencyRegistry.class); 691 692 logger.info("Component dependencies written to {}", 693 storedDependencies.getAbsolutePath()); 694 } 695 } 696 697 @Override 698 public boolean contains(String className) 699 { 700 return alreadyProcessed.contains(className); 701 } 702 703 @Override 704 public Set<String> getClassNames() 705 { 706 return Collections.unmodifiableSet(new HashSet<>(alreadyProcessed)); 707 } 708 709 @Override 710 public Set<String> getRootClasses() { 711 return alreadyProcessed.stream() 712 .filter(c -> getDependencies(c, DependencyType.USAGE).isEmpty() && 713 getDependencies(c, DependencyType.INJECT_PAGE).isEmpty() && 714 getDependencies(c, DependencyType.SUPERCLASS).isEmpty()) 715 .collect(Collectors.toSet()); 716 } 717 718 private boolean isTransformed(Class<?> clasz) 719 { 720 return plasticManager.shouldInterceptClassLoading(clasz.getName()); 721 } 722 723 @Override 724 public boolean isStoredDependencyInformationPresent() 725 { 726 return storedDependencyInformationPresent; 727 } 728 729 @Override 730 public void disableInvalidations() 731 { 732 INVALIDATIONS_DISABLED.set(INVALIDATIONS_DISABLED.get() + 1); 733 } 734 735 @Override 736 public void enableInvalidations() 737 { 738 INVALIDATIONS_DISABLED.set(INVALIDATIONS_DISABLED.get() - 1); 739 if (INVALIDATIONS_DISABLED.get() < 0) 740 { 741 INVALIDATIONS_DISABLED.set(0); 742 } 743 } 744 745 /** 746 * Only really implemented method is {@link ComponentModel#getBaseResource()} 747 */ 748 private class ComponentModelMock implements ComponentModel 749 { 750 751 final private Resource baseResource; 752 final private boolean isPage; 753 final private String componentClassName; 754 755 public ComponentModelMock(Class<?> component, boolean isPage) 756 { 757 componentClassName = component.getName(); 758 String templateLocation = componentClassName.replace('.', '/'); 759 baseResource = new ClasspathResource(templateLocation); 760 761 this.isPage = isPage; 762 } 763 764 @Override 765 public Resource getBaseResource() 766 { 767 return baseResource; 768 } 769 770 @Override 771 public String getLibraryName() 772 { 773 return null; 774 } 775 776 @Override 777 public boolean isPage() 778 { 779 return isPage; 780 } 781 782 @Override 783 public String getComponentClassName() 784 { 785 return componentClassName; 786 } 787 788 @Override 789 public List<String> getEmbeddedComponentIds() 790 { 791 return null; 792 } 793 794 @Override 795 public EmbeddedComponentModel getEmbeddedComponentModel(String componentId) 796 { 797 return null; 798 } 799 800 @Override 801 public String getFieldPersistenceStrategy(String fieldName) 802 { 803 return null; 804 } 805 806 @Override 807 public Logger getLogger() 808 { 809 return null; 810 } 811 812 @Override 813 public List<String> getMixinClassNames() 814 { 815 return null; 816 } 817 818 @Override 819 public ParameterModel getParameterModel(String parameterName) 820 { 821 return null; 822 } 823 824 @Override 825 public boolean isFormalParameter(String parameterName) 826 { 827 return false; 828 } 829 830 @Override 831 public List<String> getParameterNames() 832 { 833 return null; 834 } 835 836 @Override 837 public List<String> getDeclaredParameterNames() 838 { 839 return null; 840 } 841 842 @Override 843 public List<String> getPersistentFieldNames() 844 { 845 return null; 846 } 847 848 @Override 849 public boolean isRootClass() 850 { 851 return false; 852 } 853 854 @Override 855 public boolean getSupportsInformalParameters() 856 { 857 return false; 858 } 859 860 @Override 861 public ComponentModel getParentModel() 862 { 863 return null; 864 } 865 866 @Override 867 public boolean isMixinAfter() 868 { 869 return false; 870 } 871 872 @Override 873 public String getMeta(String key) 874 { 875 return null; 876 } 877 878 @SuppressWarnings("rawtypes") 879 @Override 880 public Set<Class> getHandledRenderPhases() 881 { 882 return null; 883 } 884 885 @Override 886 public boolean handlesEvent(String eventType) 887 { 888 return false; 889 } 890 891 @Override 892 public String[] getOrderForMixin(String mixinClassName) 893 { 894 return null; 895 } 896 897 @Override 898 public boolean handleActivationEventContext() 899 { 900 return false; 901 } 902 903 } 904 905 private static final class Dependency 906 { 907 private final String className; 908 private final DependencyType type; 909 910 public Dependency(String className, DependencyType dependencyType) 911 { 912 super(); 913 this.className = className; 914 this.type = dependencyType; 915 } 916 917 @Override 918 public int hashCode() { 919 return Objects.hash(className, type); 920 } 921 922 @Override 923 public boolean equals(Object obj) 924 { 925 if (this == obj) 926 { 927 return true; 928 } 929 if (!(obj instanceof Dependency)) 930 { 931 return false; 932 } 933 Dependency other = (Dependency) obj; 934 return Objects.equals(className, other.className) && type == other.type; 935 } 936 937 @Override 938 public String toString() 939 { 940 return "Dependency [className=" + className + ", dependencyType=" + type + "]"; 941 } 942 943 } 944 945}