    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            Set<Dependency> dependents = map.get(dependency);
    591            if (dependents == null)
    592            {
    593                dependents = new HashSet<>();
    594                map.put(dependency, dependents);
    595            }
    596            dependents.add(new Dependency(component, type));
    597        }
    598    }
    599
    600    @Override
    601    public void listen(InvalidationEventHub invalidationEventHub)
    602    {
    603        invalidationEventHub.addInvalidationCallback(this::listen);
    604    }
    605
    606    // Protected just for testing
    607    List<String> listen(List<String> resources)
    608    {
    609        List<String> furtherDependents = EMPTY_LIST;
    610        if (resources.isEmpty())
    611        {
    612            clear();
    613            furtherDependents = EMPTY_LIST;
    614        }
    615        else if (INVALIDATIONS_DISABLED.get() > 0)
    616        {
    617            furtherDependents = Collections.emptyList();
    618        }
    619        // Don't invalidate component dependency information when
    620        // PageClassloaderContextManager is merging contexts
    621        // TODO: is this still needed since the inception of INVALIDATIONS_ENABLED?
    622        else if (!pageClassLoaderContextManager.isMerging())
    623        {
    624            furtherDependents = new ArrayList<>();
    625            for (String resource : resources)
    626            {
    627
    628                final Set<String> dependents = getDependents(resource);
    629                for (String furtherDependent : dependents)
    630                {
    631                    if (!resources.contains(furtherDependent) && !furtherDependents.contains(furtherDependent))
    632                    {
    633                        furtherDependents.add(furtherDependent);
    634                    }
    635                }
    636
    637                clear(resource);
    638
    639            }
    640        }
    641        return furtherDependents;
    642    }
    643
    644    @Override
    645    public void writeFile()
    646    {
    647        synchronized (this)
    648        {
    649            try (FileWriter fileWriter = new FileWriter(storedDependencies);
    650                    BufferedWriter bufferedWriter = new BufferedWriter(fileWriter))
    651            {
    652                Set<String> classNames = new HashSet<>(alreadyProcessed.size());
    653                classNames.addAll(map.keySet());
    654                classNames.addAll(alreadyProcessed);
    655                JSONArray jsonArray = new JSONArray();
    656                for (String className : classNames)
    657                {
    658                    for (DependencyType dependencyType : DependencyType.values())
    659                    {
    660                        final Set<String> dependencies = getDependencies(className, dependencyType);
    661                        for (String dependency : dependencies)
    662                        {
    663                            JSONObject object = new JSONObject();
    664                            object.put("class", className);
    665                            object.put("type", dependencyType.name());
    666                            object.put("dependency", dependency);
    667                            jsonArray.add(object);
    668                        }
    669                    }
    670                }
    671                bufferedWriter.write(jsonArray.toString());
    672            }
    673            catch (IOException e)
    674            {
    675                throw new TapestryException("Exception trying to write " + storedDependencies.getAbsolutePath(), e);
    676            }
    677
    678            Logger logger = LoggerFactory.getLogger(ComponentDependencyRegistry.class);
    679
    680            logger.info("Component dependencies written to {}",
    681                    storedDependencies.getAbsolutePath());
    682        }
    683    }
    684
    685    @Override
    686    public boolean contains(String className)
    687    {
    688        return alreadyProcessed.contains(className);
    689    }
    690
    691    @Override
    692    public Set<String> getClassNames()
    693    {
    694        return Collections.unmodifiableSet(new HashSet<>(alreadyProcessed));
    695    }
    696
    697    @Override
    698    public Set<String> getRootClasses() {
    699        return alreadyProcessed.stream()
    700                .filter(c -> getDependencies(c, DependencyType.USAGE).isEmpty() &&
    701                        getDependencies(c, DependencyType.INJECT_PAGE).isEmpty() &&
    702                        getDependencies(c, DependencyType.SUPERCLASS).isEmpty())
    703                .collect(Collectors.toSet());
    704    }
    705
    706    private boolean isTransformed(Class<?> clasz)
    707    {
    708        return plasticManager.shouldInterceptClassLoading(clasz.getName());
    709    }
    710
    711    @Override
    712    public boolean isStoredDependencyInformationPresent()
    713    {
    714        return storedDependencyInformationPresent;
    715    }
    716
    717    @Override
    718    public void disableInvalidations()
    719    {
    720        INVALIDATIONS_DISABLED.set(INVALIDATIONS_DISABLED.get() + 1);
    721    }
    722
    723    @Override
    724    public void enableInvalidations()
    725    {
    726        INVALIDATIONS_DISABLED.set(INVALIDATIONS_DISABLED.get() - 1);
    727        if (INVALIDATIONS_DISABLED.get() < 0)
    728        {
    729            INVALIDATIONS_DISABLED.set(0);
    730        }
    731    }
    732
    733    /**
    734     * Only really implemented method is {@link ComponentModel#getBaseResource()}
    735     */
    736    private class ComponentModelMock implements ComponentModel
    737    {
    738
    739        final private Resource baseResource;
    740        final private boolean isPage;
    741        final private String componentClassName;
    742
    743        public ComponentModelMock(Class<?> component, boolean isPage)
    744        {
    745            componentClassName = component.getName();
    746            String templateLocation = componentClassName.replace('.', '/');
    747            baseResource = new ClasspathResource(templateLocation);
    748
    749            this.isPage = isPage;
    750        }
    751
    752        @Override
    753        public Resource getBaseResource()
    754        {
    755            return baseResource;
    756        }
    757
    758        @Override
    759        public String getLibraryName()
    760        {
    761            return null;
    762        }
    763
    764        @Override
    765        public boolean isPage()
    766        {
    767            return isPage;
    768        }
    769
    770        @Override
    771        public String getComponentClassName()
    772        {
    773            return componentClassName;
    774        }
    775
    776        @Override
    777        public List<String> getEmbeddedComponentIds()
    778        {
    779            return null;
    780        }
    781
    782        @Override
    783        public EmbeddedComponentModel getEmbeddedComponentModel(String componentId)
    784        {
    785            return null;
    786        }
    787
    788        @Override
    789        public String getFieldPersistenceStrategy(String fieldName)
    790        {
    791            return null;
    792        }
    793
    794        @Override
    795        public Logger getLogger()
    796        {
    797            return null;
    798        }
    799
    800        @Override
    801        public List<String> getMixinClassNames()
    802        {
    803            return null;
    804        }
    805
    806        @Override
    807        public ParameterModel getParameterModel(String parameterName)
    808        {
    809            return null;
    810        }
    811
    812        @Override
    813        public boolean isFormalParameter(String parameterName)
    814        {
    815            return false;
    816        }
    817
    818        @Override
    819        public List<String> getParameterNames()
    820        {
    821            return null;
    822        }
    823
    824        @Override
    825        public List<String> getDeclaredParameterNames()
    826        {
    827            return null;
    828        }
    829
    830        @Override
    831        public List<String> getPersistentFieldNames()
    832        {
    833            return null;
    834        }
    835
    836        @Override
    837        public boolean isRootClass()
    838        {
    839            return false;
    840        }
    841
    842        @Override
    843        public boolean getSupportsInformalParameters()
    844        {
    845            return false;
    846        }
    847
    848        @Override
    849        public ComponentModel getParentModel()
    850        {
    851            return null;
    852        }
    853
    854        @Override
    855        public boolean isMixinAfter()
    856        {
    857            return false;
    858        }
    859
    860        @Override
    861        public String getMeta(String key)
    862        {
    863            return null;
    864        }
    865
    866        @SuppressWarnings("rawtypes")
    867        @Override
    868        public Set<Class> getHandledRenderPhases()
    869        {
    870            return null;
    871        }
    872
    873        @Override
    874        public boolean handlesEvent(String eventType)
    875        {
    876            return false;
    877        }
    878
    879        @Override
    880        public String[] getOrderForMixin(String mixinClassName)
    881        {
    882            return null;
    883        }
    884
    885        @Override
    886        public boolean handleActivationEventContext()
    887        {
    888            return false;
    889        }
    890
    891    }
    892
    893    private static final class Dependency
    894    {
    895        private final String className;
    896        private final DependencyType type;
    897
    898        public Dependency(String className, DependencyType dependencyType)
    899        {
    900            super();
    901            this.className = className;
    902            this.type = dependencyType;
    903        }
    904
    905        @Override
    906        public int hashCode() {
    907            return Objects.hash(className, type);
    908        }
    909
    910        @Override
    911        public boolean equals(Object obj)
    912        {
    913            if (this == obj)
    914            {
    915                return true;
    916            }
    917            if (!(obj instanceof Dependency))
    918            {
    919                return false;
    920            }
    921            Dependency other = (Dependency) obj;
    922            return Objects.equals(className, other.className) && type == other.type;
    923        }
    924
    925        @Override
    926        public String toString()
    927        {
    928            return "Dependency [className=" + className + ", dependencyType=" + type + "]";
    929        }
    930
    931    }
    932
    933}