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