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}