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