/**
* Copyright 2011-2015 John Ericksen
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.androidtransfuse.analysis.module;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.androidtransfuse.Plugin;
import org.androidtransfuse.Plugins;
import org.androidtransfuse.adapter.*;
import org.androidtransfuse.adapter.classes.ASTClassFactory;
import org.androidtransfuse.analysis.repository.InjectionNodeBuilderRepository;
import org.androidtransfuse.annotations.*;
import org.androidtransfuse.transaction.AbstractCompletionTransactionWorker;
import javax.inject.Inject;
import javax.inject.Provider;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Central module processor class. Scans the input AST elements for the appropriate annotations and registers
* the results with the given processor.
*
* @author John Ericksen
*/
public class ModuleTransactionWorker extends AbstractCompletionTransactionWorker<Provider<ASTType>, Void> {
private final ImmutableMap<ASTType, MethodProcessor> methodProcessors;
private final ImmutableMap<ASTType, TypeProcessor> typeProcessors;
private final ModuleRepository moduleRepository;
private final Provider<InjectionNodeBuilderRepository> injectionNodeBuilderRepositoryProvider;
@Inject
public ModuleTransactionWorker(BindProcessor bindProcessor,
BindProviderProcessor bindProviderProcessor,
BindInterceptorProcessor bindInterceptorProcessor,
BindingConfigurationFactory configurationFactory,
ProvidesProcessor providesProcessor,
ASTClassFactory astClassFactory,
UsesPermissionProcessor usesPermissionProcessor,
UsesSdkProcessor usesSdkProcessor,
DefineScopeProcessor defineScopeProcessor,
PermissionProcessor permissionProcessor,
UsesFeatureProcessor usesFeatureProcessor,
ModuleRepository moduleRepository,
PluginProcessor pluginProcessor,
Provider<InjectionNodeBuilderRepository> injectionNodeBuilderRepositoryProvider) {
this.moduleRepository = moduleRepository;
this.injectionNodeBuilderRepositoryProvider = injectionNodeBuilderRepositoryProvider;
ImmutableMap.Builder<ASTType, MethodProcessor> methodProcessorsBuilder = ImmutableMap.builder();
methodProcessorsBuilder.put(astClassFactory.getType(Provides.class), providesProcessor);
this.methodProcessors = methodProcessorsBuilder.build();
ImmutableMap.Builder<ASTType, TypeProcessor> typeProcessorsBuilder = ImmutableMap.builder();
setupProcessor(typeProcessorsBuilder, astClassFactory, configurationFactory, BindInterceptor.class, BindInterceptors.class, bindInterceptorProcessor);
setupProcessor(typeProcessorsBuilder, astClassFactory, configurationFactory, BindProvider.class, BindProviders.class, bindProviderProcessor);
setupProcessor(typeProcessorsBuilder, astClassFactory, configurationFactory, Bind.class, Bindings.class, bindProcessor);
setupProcessor(typeProcessorsBuilder, astClassFactory, configurationFactory, UsesPermission.class, UsesPermissions.class, usesPermissionProcessor);
setupProcessor(typeProcessorsBuilder, astClassFactory, configurationFactory, Permission.class, Permissions.class, permissionProcessor);
setupProcessor(typeProcessorsBuilder, astClassFactory, configurationFactory, UsesFeature.class, UsesFeatures.class, usesFeatureProcessor);
setupProcessor(typeProcessorsBuilder, astClassFactory, configurationFactory, DefineScope.class, DefineScopes.class, defineScopeProcessor);
setupProcessor(typeProcessorsBuilder, astClassFactory, configurationFactory, Plugin.class, Plugins.class, pluginProcessor);
typeProcessorsBuilder.put(astClassFactory.getType(UsesSdk.class), usesSdkProcessor);
typeProcessors = typeProcessorsBuilder.build();
}
private void setupProcessor(ImmutableMap.Builder<ASTType, TypeProcessor> typeProcessorsBuilder,
ASTClassFactory astClassFactory,
BindingConfigurationFactory configurationFactory,
Class<? extends Annotation> processorAnnotation,
Class<? extends Annotation> pluralAnnotation,
TypeProcessor processor) {
typeProcessorsBuilder.put(astClassFactory.getType(processorAnnotation), processor);
typeProcessorsBuilder.put(astClassFactory.getType(pluralAnnotation),
configurationFactory.buildConfigurationComposite(processor));
}
@Override
public Void innerRun(Provider<ASTType> astTypeProvider) {
ImmutableList<ModuleConfiguration> configurations = createConfigurationsForModuleType(astTypeProvider.get());
InjectionNodeBuilderRepository repository = injectionNodeBuilderRepositoryProvider.get();
for (ModuleConfiguration moduleConfiguration : configurations) {
moduleConfiguration.setConfiguration(repository);
}
moduleRepository.addModuleRepository(repository);
return null;
}
private ImmutableList<ModuleConfiguration> createConfigurationsForModuleType(ASTType moduleType) {
ImmutableList.Builder<ModuleConfiguration> configurations = ImmutableList.builder();
createConfigurationsForModuleType(configurations, moduleType, moduleType, new HashSet<MethodSignature>(), new HashMap<String, Set<MethodSignature>>());
return configurations.build();
}
/**
* Recursive method that finds module methods and annotations on all parents of moduleAncestor and moduleAncestor, but creates configuration based on the module.
* This allows any class in the module hierarchy to contribute annotations or providers that can be overridden by subclasses.
*
* @param configurations the holder for annotation and method configurations
* @param module the module type we are configuring
*/
private void createConfigurationsForModuleType(ImmutableList.Builder<ModuleConfiguration> configurations, ASTType module, ASTType scanTarget, Set<MethodSignature> scanned, Map<String, Set<MethodSignature>> packagePrivateScanned) {
configureModuleAnnotations(configurations, module, scanTarget, scanTarget.getAnnotations());
configureModuleMethods(configurations, module, scanTarget, scanTarget.getMethods(), scanned, packagePrivateScanned);
// Add scanned methods to allow for method overriding.
for (ASTMethod astMethod : scanTarget.getMethods()) {
MethodSignature signature = new MethodSignature(astMethod);
if(astMethod.getAccessModifier() == ASTAccessModifier.PUBLIC || astMethod.getAccessModifier() == ASTAccessModifier.PROTECTED){
scanned.add(signature);
}
else if(astMethod.getAccessModifier() == ASTAccessModifier.PACKAGE_PRIVATE){
if(!packagePrivateScanned.containsKey(scanTarget.getPackageClass().getPackage())){
packagePrivateScanned.put(scanTarget.getPackageClass().getPackage(), new HashSet<MethodSignature>());
}
packagePrivateScanned.get(scanTarget.getPackageClass().getPackage()).add(signature);
}
}
// if super type is null, we are at the top of the inheritance hierarchy and we can unwind.
if(scanTarget.getSuperClass() != null) {
// recurse our way up the tree so we add the top most providers first and clobber them on the way down
createConfigurationsForModuleType(configurations, module, scanTarget.getSuperClass(), scanned, packagePrivateScanned);
}
}
private void configureModuleMethods(ImmutableList.Builder<ModuleConfiguration> configurations, ASTType moduleType, ASTType scanTarget, ImmutableSet<ASTMethod> methods, Set<MethodSignature> scanned, Map<String, Set<MethodSignature>> packagePrivateScanned) {
for (ASTMethod astMethod : methods) {
if(!isOverridden(scanned, packagePrivateScanned, moduleType, astMethod)) {
for (ASTAnnotation astAnnotation : astMethod.getAnnotations()) {
if (methodProcessors.containsKey(astAnnotation.getASTType())) {
MethodProcessor methodProcessor = methodProcessors.get(astAnnotation.getASTType());
configurations.add(methodProcessor.process(moduleType, scanTarget, astMethod, astAnnotation));
}
}
}
}
}
private void configureModuleAnnotations(ImmutableList.Builder<ModuleConfiguration> configurations, ASTType moduleType, ASTType scanTarget, ImmutableSet<ASTAnnotation> annotations) {
for (ASTAnnotation typeAnnotation : annotations) {
if (typeProcessors.containsKey(typeAnnotation.getASTType())) {
TypeProcessor typeProcessor = typeProcessors.get(typeAnnotation.getASTType());
configurations.add(typeProcessor.process(moduleType, scanTarget, typeAnnotation));
}
}
}
private boolean isOverridden(Set<MethodSignature> scanned, Map<String, Set<MethodSignature>> packagePrivateScanned, ASTType type, ASTMethod method) {
MethodSignature signature = new MethodSignature(method);
if(method.getAccessModifier() == ASTAccessModifier.PRIVATE){
return false;
}
if(method.getAccessModifier() == ASTAccessModifier.PACKAGE_PRIVATE){
return packagePrivateScanned.containsKey(type.getPackageClass().getPackage()) &&
packagePrivateScanned.get(type.getPackageClass().getPackage()).contains(signature);
}
// PUBLIC and PROTECTED handling
return scanned.contains(signature);
}
}