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