001/**
002 * Copyright 2013 John Ericksen
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *    http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.androidtransfuse.analysis.astAnalyzer;
017
018import com.google.common.collect.ImmutableSet;
019import org.androidtransfuse.adapter.*;
020import org.androidtransfuse.analysis.AnalysisContext;
021import org.androidtransfuse.analysis.astAnalyzer.validation.AnnotationValidator;
022import org.androidtransfuse.analysis.astAnalyzer.validation.AnnotationValidatorBuilder;
023import org.androidtransfuse.annotations.*;
024import org.androidtransfuse.model.InjectionNode;
025
026import javax.inject.Inject;
027import java.util.Arrays;
028
029/**
030 * @author John Ericksen
031 */
032public class AnnotationValidationAnalysis implements ASTAnalysis {
033
034    private final AnnotationValidator annotationValidator;
035
036    @Inject
037    public AnnotationValidationAnalysis(AnnotationValidatorBuilder builder) {
038
039        //built in specialty qualifiers
040        builder.given(Extra.class).requires(Inject.class, "@Extra annotation must be accompanied by @Inject");
041        builder.given(Extra.class).parameterMatches("value", "^[a-zA-Z][a-zA-Z0-9_]*$", "@Extra value parameter must follow Java Bean syntax");
042        builder.given(View.class).requires(Inject.class, "@View annotation must be accompanied by @Inject");
043        builder.given(Preference.class).requires(Inject.class, "@Preference annotation must be accompanied by @Inject");
044        builder.given(Resource.class).requires(Inject.class, "@Resource annotation must be accompanied by @Inject");
045        builder.given(SystemService.class).requires(Inject.class, "@SystemService annotation must be accompanied by @Inject");
046
047        //activity metadata
048        builder.given(Layout.class).requires(Arrays.asList(Activity.class, Fragment.class), "@Layout annotation must be accompanied by @Activity");
049        builder.given(LayoutHandler.class).requires(Arrays.asList(Activity.class, Fragment.class), "@LayoutHandler annotation must be accompanied by @Activity");
050        builder.given(MetaData.class).requires(Arrays.asList(Activity.class, Application.class, Service.class, BroadcastReceiver.class), "@MetaData annotation must be accompanied by a component annotation");
051        builder.given(MetaDataSet.class).requires(Arrays.asList(Activity.class, Application.class, Service.class, BroadcastReceiver.class), "@MetaDataSet annotation must be accompanied by a component annotation");
052        builder.given(Activity.class).parameterMatches("name", "^[a-zA-Z][a-zA-Z0-9_]*$", "@Activity name parameter must follow Java Bean syntax");
053        builder.given(Service.class).parameterMatches("name", "^[a-zA-Z][a-zA-Z0-9_]*$", "@Service name parameter must follow Java Bean syntax");
054        builder.given(Fragment.class).parameterMatches("name", "^[a-zA-Z][a-zA-Z0-9_]*$", "@Fragment name parameter must follow Java Bean syntax");
055        builder.given(BroadcastReceiver.class).parameterMatches("name", "^[a-zA-Z][a-zA-Z0-9_]*$", "@BroadcastReceiver name parameter must follow Java Bean syntax");
056        builder.given(Application.class).parameterMatches("name", "^[a-zA-Z][a-zA-Z0-9_]*$", "@Application name parameter must follow Java Bean syntax");
057
058        //module metadata
059        builder.given(Bind.class).requires(TransfuseModule.class, "@Bind annotation must be accompanied by @TransfuseModule");
060        builder.given(Bindings.class).requires(TransfuseModule.class, "@Bindings annotation must be accompanied by @TransfuseModule");
061        builder.given(BindInterceptor.class).requires(TransfuseModule.class, "@BindInterceptor annotation must be accompanied by @TransfuseModule");
062        builder.given(BindInterceptors.class).requires(TransfuseModule.class, "@BindInterceptors annotation must be accompanied by @TransfuseModule");
063        builder.given(BindProvider.class).requires(TransfuseModule.class, "@BindProvider annotation must be accompanied by @TransfuseModule");
064        builder.given(BindProviders.class).requires(TransfuseModule.class, "@BindProviders annotation must be accompanied by @TransfuseModule");
065        builder.given(DefineScope.class).requires(TransfuseModule.class, "@DefineScope annotation must be accompanied by @TransfuseModule");
066        builder.given(DefineScopes.class).requires(TransfuseModule.class, "@DefineScopes annotation must be accompanied by @TransfuseModule");
067        builder.given(UsesPermission.class).requires(TransfuseModule.class, "@UsesPermission annotation must be accompanied by @TransfuseModule");
068        builder.given(UsesPermissions.class).requires(TransfuseModule.class, "@UsesPermissions annotation must be accompanied by @TransfuseModule");
069        builder.given(UsesSdk.class).requires(TransfuseModule.class, "@UsesSdk annotation must be accompanied by @TransfuseModule");
070
071        builder.given(Provides.class).requires(TransfuseModule.class, "@Provides annotation must be accompanied by @TransfuseModule");
072
073        annotationValidator = builder.build();
074    }
075
076    @Override
077    public void analyzeType(InjectionNode injectionNode, ASTType astType, AnalysisContext context) {
078
079        for (ASTAnnotation annotation : astType.getAnnotations()) {
080            validateAnnotation(annotation, astType, astType.getAnnotations());
081        }
082
083        for (ASTConstructor astConstructor : astType.getConstructors()) {
084            for (ASTAnnotation annotation : astConstructor.getAnnotations()) {
085                validateAnnotation(annotation, astConstructor, astConstructor.getAnnotations(), astType.getAnnotations());
086
087                for (ASTParameter astParameter : astConstructor.getParameters()) {
088                    validateParameter(astParameter, astConstructor, astType);
089                }
090            }
091        }
092    }
093
094    @Override
095    public void analyzeMethod(InjectionNode injectionNode, ASTType concreteType, ASTMethod astMethod, AnalysisContext context) {
096        for (ASTAnnotation annotation : astMethod.getAnnotations()) {
097            validateAnnotation(annotation, astMethod, astMethod.getAnnotations(), concreteType.getAnnotations());
098
099            for (ASTParameter astParameter : astMethod.getParameters()) {
100                validateParameter(astParameter, astMethod, concreteType);
101            }
102        }
103    }
104
105    @Override
106    public void analyzeField(InjectionNode injectionNode, ASTType concreteType, ASTField astField, AnalysisContext context) {
107        for (ASTAnnotation annotation : astField.getAnnotations()) {
108            validateAnnotation(annotation, astField, astField.getAnnotations(), concreteType.getAnnotations());
109        }
110    }
111
112    private void validateParameter(ASTParameter parameter, ASTBase containingAST, ASTType containingType){
113        for (ASTAnnotation paramAnnotation : parameter.getAnnotations()) {
114            validateAnnotation(paramAnnotation, parameter, parameter.getAnnotations(), containingAST.getAnnotations(), containingType.getAnnotations());
115        }
116    }
117
118    private void validateAnnotation(ASTAnnotation annotation, ASTBase astBase, ImmutableSet<ASTAnnotation>... applicableAnnotations){
119        annotationValidator.validate(annotation, astBase, flatten(applicableAnnotations));
120    }
121
122    private ImmutableSet<ASTAnnotation> flatten(ImmutableSet<ASTAnnotation>... sets){
123        ImmutableSet.Builder<ASTAnnotation> combineBuilder = ImmutableSet.builder();
124        for (ImmutableSet<ASTAnnotation> set : sets) {
125            combineBuilder.addAll(set);
126        }
127        return combineBuilder.build();
128    }
129}