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