001// Copyright 2023, 2024 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.services.pageload; 015 016import java.util.ArrayList; 017import java.util.Collections; 018import java.util.Comparator; 019import java.util.HashSet; 020import java.util.List; 021import java.util.Objects; 022import java.util.Set; 023import java.util.concurrent.atomic.AtomicInteger; 024import java.util.function.Function; 025import java.util.function.Supplier; 026import java.util.stream.Collectors; 027 028import org.apache.tapestry5.SymbolConstants; 029import org.apache.tapestry5.commons.internal.util.TapestryException; 030import org.apache.tapestry5.commons.services.InvalidationEventHub; 031import org.apache.tapestry5.commons.services.PlasticProxyFactory; 032import org.apache.tapestry5.internal.ThrowawayClassLoader; 033import org.apache.tapestry5.internal.services.ComponentDependencyRegistry; 034import org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType; 035import org.apache.tapestry5.internal.services.InternalComponentInvalidationEventHub; 036import org.apache.tapestry5.ioc.annotations.ComponentClasses; 037import org.apache.tapestry5.ioc.annotations.Symbol; 038import org.apache.tapestry5.plastic.PlasticUtils; 039import org.apache.tapestry5.services.ComponentClassResolver; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043/** 044 * Default {@linkplain PageClassLoaderContextManager} implementation. 045 * 046 * @since 5.8.3 047 */ 048public class PageClassLoaderContextManagerImpl implements PageClassLoaderContextManager 049{ 050 051 private static final Logger LOGGER = LoggerFactory.getLogger(PageClassLoaderContextManager.class); 052 053 private final ComponentDependencyRegistry componentDependencyRegistry; 054 055 private final ComponentClassResolver componentClassResolver; 056 057 private final InternalComponentInvalidationEventHub invalidationHub; 058 059 private final InvalidationEventHub componentClassesInvalidationEventHub; 060 061 private final boolean multipleClassLoaders; 062 063 private final boolean productionMode; 064 065 private final static ThreadLocal<Integer> NESTED_MERGE_COUNT = ThreadLocal.withInitial(() -> 0); 066 067 private final static ThreadLocal<Boolean> INVALIDATING_CONTEXT = ThreadLocal.withInitial(() -> false); 068 069 private static final AtomicInteger MERGED_COUNTER = new AtomicInteger(1); 070 071 private final static ThreadLocal<AtomicInteger> CONTEXTS_CREATED = ThreadLocal.withInitial(AtomicInteger::new); 072 073 private Function<ClassLoader, PlasticProxyFactory> plasticProxyFactoryProvider; 074 075 private PageClassLoaderContext root; 076 077 private boolean preloadingContexts; 078 079 public PageClassLoaderContextManagerImpl( 080 final ComponentDependencyRegistry componentDependencyRegistry, 081 final ComponentClassResolver componentClassResolver, 082 final InternalComponentInvalidationEventHub invalidationHub, 083 final @ComponentClasses InvalidationEventHub componentClassesInvalidationEventHub, 084 final @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode, 085 final @Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS) boolean multipleClassLoaders) 086 { 087 super(); 088 this.componentDependencyRegistry = componentDependencyRegistry; 089 this.componentClassResolver = componentClassResolver; 090 this.invalidationHub = invalidationHub; 091 this.componentClassesInvalidationEventHub = componentClassesInvalidationEventHub; 092 this.multipleClassLoaders = multipleClassLoaders && !productionMode; 093 this.productionMode = productionMode; 094 invalidationHub.addInvalidationCallback(this::listen); 095 NESTED_MERGE_COUNT.set(0); 096 } 097 098 @Override 099 public void invalidateUnknownContext() 100 { 101 synchronized (this) { 102 markAsNotInvalidatingContext(); 103 for (PageClassLoaderContext context : root.getChildren()) 104 { 105 if (context.isUnknown()) 106 { 107 invalidateAndFireInvalidationEvents(context); 108 break; 109 } 110 } 111 } 112 } 113 114 @Override 115 public void initialize( 116 final PageClassLoaderContext root, 117 final Function<ClassLoader, PlasticProxyFactory> plasticProxyFactoryProvider) 118 { 119 if (this.root != null) 120 { 121 throw new IllegalStateException("PageClassloaderContextManager.initialize() can only be called once"); 122 } 123 Objects.requireNonNull(root); 124 Objects.requireNonNull(plasticProxyFactoryProvider); 125 this.root = root; 126 this.plasticProxyFactoryProvider = plasticProxyFactoryProvider; 127 LOGGER.info("Root context: {}", root); 128 } 129 130 @Override 131 public synchronized PageClassLoaderContext get(final String className) 132 { 133 PageClassLoaderContext context; 134 135 // Class isn't in a controlled package, so it doesn't get transformed 136 // and should go for the root context, which is never thrown out. 137 if (!root.getPlasticManager().shouldInterceptClassLoading(className)) 138 { 139 context = root; 140 } 141 else if (productionMode || !multipleClassLoaders) 142 { 143 context = getUnknownContext(root, plasticProxyFactoryProvider); 144 } 145 else 146 { 147 148 // Multiple classloader mode. 149 final String enclosingClassName = getAdjustedClassName(className); 150 context = root.findByClassName(enclosingClassName); 151 152 if (context == null) 153 { 154 Set<String> classesToInvalidate = new HashSet<>(); 155 156 context = processUsingDependencies( 157 enclosingClassName, 158 root, 159 () -> getUnknownContext(root, plasticProxyFactoryProvider), 160 plasticProxyFactoryProvider, 161 classesToInvalidate); 162 163 if (!classesToInvalidate.isEmpty()) 164 { 165 invalidate(classesToInvalidate); 166 } 167 168 if (!className.equals(enclosingClassName)) 169 { 170 loadClass(enclosingClassName, context); 171 } 172 173 } 174 175 } 176 177 return context; 178 179 } 180 181 private String getAdjustedClassName(final String className) 182 { 183 return PlasticUtils.getEnclosingClassName(className) 184 .replaceAll("\\[\\]", ""); 185 } 186 187 private PageClassLoaderContext getUnknownContext(final PageClassLoaderContext root, 188 final Function<ClassLoader, PlasticProxyFactory> plasticProxyFactoryProvider) 189 { 190 191 PageClassLoaderContext unknownContext = null; 192 193 for (PageClassLoaderContext child : root.getChildren()) 194 { 195 if (child.getName().equals(PageClassLoaderContext.UNKOWN_CONTEXT_NAME)) 196 { 197 unknownContext = child; 198 break; 199 } 200 } 201 202 if (unknownContext == null) 203 { 204 unknownContext = new PageClassLoaderContext(PageClassLoaderContext.UNKOWN_CONTEXT_NAME, root, 205 Collections.emptySet(), 206 plasticProxyFactoryProvider.apply(root.getClassLoader()), 207 this::get); 208 root.addChild(unknownContext); 209 if (multipleClassLoaders) 210 { 211 LOGGER.debug("Unknown context: {}", unknownContext); 212 } 213 } 214 return unknownContext; 215 } 216 217 private PageClassLoaderContext processUsingDependencies( 218 String className, 219 PageClassLoaderContext root, 220 Supplier<PageClassLoaderContext> unknownContextProvider, 221 Function<ClassLoader, PlasticProxyFactory> plasticProxyFactoryProvider, Set<String> classesToInvalidate) 222 { 223 return processUsingDependencies(className, root, unknownContextProvider, plasticProxyFactoryProvider, classesToInvalidate, new HashSet<>()); 224 } 225 226 private PageClassLoaderContext processUsingDependencies( 227 String className, 228 PageClassLoaderContext root, 229 Supplier<PageClassLoaderContext> unknownContextProvider, 230 Function<ClassLoader, PlasticProxyFactory> plasticProxyFactoryProvider, 231 Set<String> classesToInvalidate, 232 Set<String> alreadyProcessed) 233 { 234 return processUsingDependencies(className, root, unknownContextProvider, 235 plasticProxyFactoryProvider, classesToInvalidate, alreadyProcessed, true); 236 } 237 238 239 private PageClassLoaderContext processUsingDependencies( 240 String className, 241 PageClassLoaderContext root, 242 Supplier<PageClassLoaderContext> unknownContextProvider, 243 Function<ClassLoader, PlasticProxyFactory> plasticProxyFactoryProvider, 244 Set<String> classesToInvalidate, 245 Set<String> alreadyProcessed, 246 boolean processCircularDependencies) 247 { 248 PageClassLoaderContext context = root.findByClassName(className); 249 if (context == null) 250 { 251 252 LOGGER.debug("Processing class {}", className); 253 alreadyProcessed.add(className); 254 255 // Sorting dependencies by type/alphabetically so we have consistent 256 // context trees between runs of the same webapp 257 List<String> allNonPageDependencies = new ArrayList<>( 258 componentDependencyRegistry.getAllNonPageDependencies(className)); 259 Collections.sort(allNonPageDependencies, ClassNameComparator.INSTANCE); 260 261 List<String> dependencies = new ArrayList<>(getDependenciesWithoutPages(className)); 262 Collections.sort(dependencies, ClassNameComparator.INSTANCE); 263 264 // Process dependencies depth-first 265 do 266 { 267 268 // Very unlikely to have infinite loops, but lets 269 // avoid them anyway. 270 int passes = 0; 271 272 int contextsCreatedInThisPass = -1; 273 274 while (contextsCreatedInThisPass < CONTEXTS_CREATED.get().get() && passes < 1000) 275 { 276 277 contextsCreatedInThisPass = CONTEXTS_CREATED.get().get(); 278 279 for (String dependency : allNonPageDependencies) 280 { 281 // Avoid infinite recursion loops 282 if (!alreadyProcessed.contains(dependency) 283 || root.findByClassName(dependency) == null) 284 { 285 processUsingDependencies(dependency, root, unknownContextProvider, 286 plasticProxyFactoryProvider, classesToInvalidate, alreadyProcessed, false); 287 } 288 } 289 290 } 291 292 } 293 while (!allNeededContextsAvailable(allNonPageDependencies)); 294 295 // Collect context dependencies 296 Set<PageClassLoaderContext> contextDependencies = new HashSet<>(); 297 for (String dependency : allNonPageDependencies) 298 { 299 PageClassLoaderContext dependencyContext = root.findByClassName(dependency); 300 // Avoid infinite recursion loops 301 if (multipleClassLoaders || !alreadyProcessed.contains(dependency)) 302 { 303 if (dependencyContext == null) 304 { 305 dependencyContext = processUsingDependencies(dependency, root, unknownContextProvider, 306 plasticProxyFactoryProvider, classesToInvalidate, alreadyProcessed); 307 308 } 309 if (!dependencyContext.isRoot()) 310 { 311 contextDependencies.add(dependencyContext); 312 } 313 } 314 } 315 316 if (contextDependencies.size() == 0) 317 { 318 context = new PageClassLoaderContext( 319 getContextName(className), 320 root, 321 Collections.singleton(className), 322 plasticProxyFactoryProvider.apply(root.getClassLoader()), 323 this::get); 324 CONTEXTS_CREATED.get().incrementAndGet(); 325 } 326 else 327 { 328 PageClassLoaderContext parentContext; 329 if (contextDependencies.size() == 1) 330 { 331 parentContext = contextDependencies.iterator().next(); 332 } 333 else 334 { 335 parentContext = merge(contextDependencies, plasticProxyFactoryProvider, root, classesToInvalidate); 336 } 337 context = new PageClassLoaderContext( 338 getContextName(className), 339 parentContext, 340 Collections.singleton(className), 341 plasticProxyFactoryProvider.apply(parentContext.getClassLoader()), 342 this::get); 343 CONTEXTS_CREATED.get().incrementAndGet(); 344 } 345 346 LOGGER.debug("New context: {}", context); 347 348 } 349 context.addClass(className); 350 context.getParent().addChild(context); 351 352 return context; 353 } 354 355 private boolean allNeededContextsAvailable(List<String> dependencies) 356 { 357 boolean available = true; 358 for (String dependency : dependencies) 359 { 360 if (root.findByClassName(dependency) == null) 361 { 362 available = false; 363 break; 364 } 365 } 366 return available; 367 } 368 369 private Set<String> getDependenciesWithoutPages(String className) 370 { 371 Set<String> dependencies = new HashSet<>(); 372 dependencies.addAll(componentDependencyRegistry.getDependencies(className, DependencyType.USAGE)); 373 dependencies.addAll(componentDependencyRegistry.getDependencies(className, DependencyType.SUPERCLASS)); 374 dependencies.remove(className); // Just in case 375 return Collections.unmodifiableSet(dependencies); 376 } 377 378 private Class<?> loadClass(String className, PageClassLoaderContext context) 379 { 380 try 381 { 382 final ClassLoader classLoader = context.getPlasticManager().getClassLoader(); 383 return classLoader.loadClass(className); 384 } catch (Exception e) { 385 throw new RuntimeException(e); 386 } 387 } 388 389 private PageClassLoaderContext merge( 390 Set<PageClassLoaderContext> contextDependencies, 391 Function<ClassLoader, PlasticProxyFactory> plasticProxyFactoryProvider, 392 PageClassLoaderContext root, Set<String> classesToInvalidate) 393 { 394 395 NESTED_MERGE_COUNT.set(NESTED_MERGE_COUNT.get() + 1); 396 397 if (LOGGER.isDebugEnabled()) 398 { 399 400 LOGGER.debug("Nested merge count going up to {}", NESTED_MERGE_COUNT.get()); 401 402 String classes; 403 StringBuilder builder = new StringBuilder(); 404 builder.append("Merging the following page classloader contexts into one:\n"); 405 for (PageClassLoaderContext context : contextDependencies) 406 { 407 classes = context.getClassNames().stream() 408 .map(this::getContextName) 409 .sorted() 410 .collect(Collectors.joining(", ")); 411 builder.append(String.format("\t%s (parent %s) (%s)\n", context.getName(), context.getParent().getName(), classes)); 412 } 413 LOGGER.debug(builder.toString().trim()); 414 } 415 416 final Set<String> classesToReprocess = multipleClassLoaders ? new HashSet<>() : Collections.emptySet(); 417 Set<PageClassLoaderContext> allContextsIncludingDescendents = new HashSet<>(); 418 419 for (PageClassLoaderContext context : contextDependencies) 420 { 421 final Set<PageClassLoaderContext> descendents = context.getDescendents(); 422 allContextsIncludingDescendents.add(context); 423 allContextsIncludingDescendents.addAll(descendents); 424 for (PageClassLoaderContext descendent : descendents) 425 { 426 addClassNames(descendent, classesToReprocess); 427 } 428 } 429 430 PageClassLoaderContext merged; 431 432 // Collect the classes in these dependencies, then invalidate the contexts 433 434 Set<PageClassLoaderContext> furtherDependencies = new HashSet<>(); 435 436 Set<String> classNames = new HashSet<>(); 437 438 for (PageClassLoaderContext context : contextDependencies) 439 { 440 if (!context.isRoot()) 441 { 442 classNames.addAll(context.getClassNames()); 443 } 444 final PageClassLoaderContext parent = context.getParent(); 445 // We don't want the merged context to have a further dependency on 446 // the root context (it's not mergeable) nor on itself. 447 if (!parent.isRoot() && 448 !allContextsIncludingDescendents.contains(parent)) 449 { 450 furtherDependencies.add(parent); 451 } 452 } 453 454 final List<PageClassLoaderContext> contextsToInvalidate = contextDependencies.stream() 455 .filter(c -> !c.isRoot()) 456 .collect(Collectors.toList()); 457 458 if (!contextsToInvalidate.isEmpty()) 459 { 460 classesToInvalidate.addAll(invalidate(contextsToInvalidate.toArray(new PageClassLoaderContext[contextsToInvalidate.size()]))); 461 } 462 463 PageClassLoaderContext parent; 464 465 // No context dependencies, so parent is going to be the root one 466 if (furtherDependencies.size() == 0) 467 { 468 parent = root; 469 } 470 else 471 { 472 // Single shared context dependency, so it's our parent 473 if (furtherDependencies.size() == 1) 474 { 475 parent = furtherDependencies.iterator().next(); 476 } 477 // No single context dependency, so we'll need to recursively merge it 478 // so we can have a single parent. 479 else 480 { 481 parent = merge(furtherDependencies, plasticProxyFactoryProvider, root, classesToInvalidate); 482 LOGGER.debug("New context: {}", parent); 483 } 484 } 485 486 merged = new PageClassLoaderContext( 487 "merged " + MERGED_COUNTER.getAndIncrement(), 488 parent, 489 classNames, 490 plasticProxyFactoryProvider.apply(parent.getClassLoader()), 491 this::get); 492 493 CONTEXTS_CREATED.get().incrementAndGet(); 494 495 parent.addChild(merged); 496 497 // Recreating contexts for classes that got invalidated but 498 // aren't part of the new merged context (i.e. the classes 499 // in contexts are are descendent of the merged contexts). 500// if (!classesToReprocess.isEmpty()) 501// { 502// final List<String> sorted = new ArrayList<>(classesToReprocess); 503// for (String className : sorted) 504// { 505// get(className); 506// } 507// } 508 509// for (String className : classNames) 510// { 511// loadClass(className, merged); 512// } 513 514 NESTED_MERGE_COUNT.set(NESTED_MERGE_COUNT.get() - 1); 515 if (LOGGER.isDebugEnabled()) 516 { 517 LOGGER.debug("Nested merge count going down to {}", NESTED_MERGE_COUNT.get()); 518 } 519 520 return merged; 521 } 522 523 @Override 524 public void clear(String className) 525 { 526 final PageClassLoaderContext context = root.findByClassName(className); 527 if (context != null) 528 { 529// invalidationHub.fireInvalidationEvent(new ArrayList<>(invalidate(context))); 530 invalidate(context); 531 } 532 } 533 534 private String getContextName(String className) 535 { 536 String contextName = componentClassResolver.getLogicalName(className); 537 if (contextName == null) 538 { 539 contextName = className; 540 } 541 return contextName; 542 } 543 544 @Override 545 public Set<String> invalidate(PageClassLoaderContext ... contexts) 546 { 547 Set<String> classNames = new HashSet<>(); 548 for (PageClassLoaderContext context : contexts) { 549 addClassNames(context, classNames); 550 context.invalidate(); 551 if (context.getParent() != null) 552 { 553 context.getParent().removeChild(context); 554 } 555 } 556 return classNames; 557 } 558 559 private List<String> listen(List<String> resources) 560 { 561 562 List<String> returnValue; 563 564 if (!multipleClassLoaders) 565 { 566 for (PageClassLoaderContext context : root.getChildren()) 567 { 568 context.invalidate(); 569 } 570 returnValue = Collections.emptyList(); 571 } 572 else if (INVALIDATING_CONTEXT.get()) 573 { 574 returnValue = Collections.emptyList(); 575 } 576 else 577 { 578 579 Set<PageClassLoaderContext> contextsToInvalidate = new HashSet<>(); 580 for (String resource : resources) 581 { 582 PageClassLoaderContext context = root.findByClassName(resource); 583 if (context != null && !context.isRoot()) 584 { 585 contextsToInvalidate.add(context); 586 } 587 } 588 589 Set<String> furtherResources = invalidate(contextsToInvalidate.toArray( 590 new PageClassLoaderContext[contextsToInvalidate.size()])); 591 592 // We don't want to invalidate resources more than once 593 furtherResources.removeAll(resources); 594 595 returnValue = new ArrayList<>(furtherResources); 596 } 597 598 return returnValue; 599 600 } 601 602 @SuppressWarnings("unchecked") 603 @Override 604 public void invalidateAndFireInvalidationEvents(PageClassLoaderContext... contexts) { 605 markAsInvalidatingContext(); 606 if (multipleClassLoaders) 607 { 608 final Set<String> classNames = invalidate(contexts); 609 invalidate(classNames); 610 } 611 else 612 { 613 invalidate(Collections.EMPTY_SET); 614 } 615 markAsNotInvalidatingContext(); 616 } 617 618 private void markAsNotInvalidatingContext() { 619 INVALIDATING_CONTEXT.set(false); 620 } 621 622 private void markAsInvalidatingContext() { 623 INVALIDATING_CONTEXT.set(true); 624 } 625 626 private void invalidate(Set<String> classesToInvalidate) { 627 if (!classesToInvalidate.isEmpty() && !preloadingContexts) 628 { 629 LOGGER.debug("Invalidating classes {}", classesToInvalidate); 630 markAsInvalidatingContext(); 631 final List<String> classesToInvalidateAsList = new ArrayList<>(classesToInvalidate); 632 633 componentDependencyRegistry.disableInvalidations(); 634 635 try 636 { 637 // TODO: do we really need both invalidation hubs to be invoked here? 638 invalidationHub.fireInvalidationEvent(classesToInvalidateAsList); 639 componentClassesInvalidationEventHub.fireInvalidationEvent(classesToInvalidateAsList); 640 markAsNotInvalidatingContext(); 641 } 642 finally 643 { 644 componentDependencyRegistry.enableInvalidations(); 645 } 646 647 } 648 } 649 650 private void addClassNames(PageClassLoaderContext context, Set<String> classNames) { 651 classNames.addAll(context.getClassNames()); 652 for (PageClassLoaderContext child : context.getChildren()) { 653 addClassNames(child, classNames); 654 } 655 } 656 657 @Override 658 public PageClassLoaderContext getRoot() { 659 return root; 660 } 661 662 @Override 663 public boolean isMerging() 664 { 665 return NESTED_MERGE_COUNT.get() > 0; 666 } 667 668 @Override 669 public void clear() 670 { 671 } 672 673 @Override 674 public Class<?> getClassInstance(Class<?> clasz, String pageName) 675 { 676 final String className = clasz.getName(); 677 PageClassLoaderContext context = root.findByClassName(className); 678 if (context == null) 679 { 680 context = get(className); 681 } 682 try 683 { 684 clasz = context.getProxyFactory().getClassLoader().loadClass(className); 685 } catch (ClassNotFoundException e) 686 { 687 throw new TapestryException(e.getMessage(), e); 688 } 689 return clasz; 690 } 691 692 @Override 693 public void preload() 694 { 695 696 final ClassLoader classLoader = new ThrowawayClassLoader(PageClassLoaderContext.class.getClassLoader()); 697 698 final List<String> pageNames = componentClassResolver.getPageNames(); 699 final List<String> classNames = new ArrayList<>(pageNames.size()); 700 701 long start = System.currentTimeMillis(); 702 703 LOGGER.info("Preloading dependency information for {} pages", pageNames.size()); 704 705 for (String page : pageNames) 706 { 707 try 708 { 709 final String className = componentClassResolver.resolvePageNameToClassName(page); 710 componentDependencyRegistry.register(classLoader.loadClass(className)); 711 classNames.add(className); 712 } catch (ClassNotFoundException e) 713 { 714 throw new RuntimeException(e); 715 } 716 catch (Exception e) 717 { 718 LOGGER.warn("Exception while preloading page " + page, e); 719 } 720 } 721 722 long finish = System.currentTimeMillis(); 723 724 if (LOGGER.isInfoEnabled()) 725 { 726 LOGGER.info(String.format("Dependency information for %d pages gathered in %.3f s", 727 pageNames.size(), (finish - start) / 1000.0)); 728 } 729 730 preloadContexts(); 731 732 } 733 734 @Override 735 public void preloadContexts() 736 { 737 long start; 738 long finish; 739 LOGGER.info("Starting preloading page classloader contexts"); 740 741 start = System.currentTimeMillis(); 742 743 final List<String> classNames = new ArrayList<>(componentDependencyRegistry.getClassNames()); 744 classNames.sort(ClassNameComparator.INSTANCE); 745 746 int runs = 0; 747 preloadingContexts = true; 748 749 try 750 { 751 // The run counter check is to just avoid possible infinite loops, 752 // although that's very unlikely. 753 int contexts = -1; 754 while (runs < 5000 && contexts < CONTEXTS_CREATED.get().get()) 755 { 756 runs++; 757 contexts = CONTEXTS_CREATED.get().get(); 758 for (String className : classNames) 759 { 760 get(className); 761 } 762 } 763 } 764 finally 765 { 766 preloadingContexts = false; 767 } 768 769 finish = System.currentTimeMillis(); 770 771 if (LOGGER.isInfoEnabled()) 772 { 773 LOGGER.info(String.format("Preloading of page classloader contexts finished in %.3f s (%d passes)", (finish - start) / 1000.0, runs)); 774 } 775 } 776 777 /** 778 * Sorts base classes before mixins, mixins before components and components 779 * before pages. If both classes belong to the same type, order alphabetically. 780 */ 781 private static final class ClassNameComparator implements Comparator<String> 782 { 783 784 private static final Comparator<String> INSTANCE = new ClassNameComparator(); 785 786 @Override 787 public int compare(String o1, String o2) 788 { 789 int value1 = getValue(o1); 790 int value2 = getValue(o2); 791 int comparison = value1 - value2; 792 if (comparison == 0) 793 { 794 comparison = o1.compareTo(o2); 795 } 796 return comparison; 797 } 798 799 private int getValue(String className) 800 { 801 int value; 802 if (className.contains(".base.")) 803 { 804 value = 0; 805 } 806 else if (className.contains(".mixins.")) 807 { 808 value = 1; 809 } 810 else if (className.contains(".components.")) 811 { 812 value = 2; 813 } 814 else 815 { 816 value = 3; 817 } 818 return value; 819 } 820 821 } 822 823}