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}