Java tutorial
/** * Copyright 2012 Alex Jones * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. * * @author unclealex72 * */ package uk.co.unclealex.executable.generator.scan; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.List; import java.util.Map; import uk.co.unclealex.executable.Executable; import uk.co.unclealex.executable.generator.model.ExecutableAnnotationInformation; import uk.co.unclealex.executable.generator.scan.exception.ApplicationNameNotDeclaredExecutableScanException; import uk.co.unclealex.executable.generator.scan.exception.CommandLineClassNotAnInterfaceExecutableScanException; import uk.co.unclealex.executable.generator.scan.exception.CommandLineClassNotAnnotatedExecutableScanException; import uk.co.unclealex.executable.generator.scan.exception.CommandNotInstantiableExecutableScanException; import uk.co.unclealex.executable.generator.scan.exception.ExecutableScanException; import uk.co.unclealex.executable.generator.scan.exception.InvalidParameterCountExecutableScanException; import uk.co.unclealex.executable.generator.scan.exception.NotPublicMethodExecutableScanException; import uk.co.unclealex.executable.generator.scan.exception.NotTopLevelClassExecutableScanException; import uk.co.unclealex.executable.generator.scan.exception.StaticMethodExecutableScanException; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.inject.Module; import com.lexicalscope.jewel.cli.CommandLineInterface; /** * The default implementation of {@link ExecutableAnnotationInformationFinder}. * * @author alex * */ public class ExecutableAnnotationInformationFinderImpl implements ExecutableAnnotationInformationFinder { /** * {@inheritDoc} */ @Override public List<ExecutableAnnotationInformation> findExecutableAnnotationInformation( List<? extends Class<?>> classes) throws ExecutableScanException { Map<String, ExecutableAnnotationInformation> allExecutableAnnotationInformations = Maps.newHashMap(); for (Class<?> clazz : classes) { List<ExecutableAnnotationInformation> executableAnnotationInformations = findExecutableAnnotationInformation( clazz); for (ExecutableAnnotationInformation executableAnnotationInformation : executableAnnotationInformations) { String scriptName = executableAnnotationInformation.getScriptName(); ExecutableAnnotationInformation existingExecutableAnnotationInformation = allExecutableAnnotationInformations .get(scriptName); if (existingExecutableAnnotationInformation == null) { allExecutableAnnotationInformations.put(scriptName, executableAnnotationInformation); } else { throw new NonUniqueScriptNameExecutableScanException(scriptName, existingExecutableAnnotationInformation, executableAnnotationInformation); } } } return Lists.newArrayList(allExecutableAnnotationInformations.values()); } /** * Find all {@link Executable} annotations in a list of classes. * * @param clazz * The class to search. * @return A list of all found methods that have an {@link Executable} * annotation. * @throws ExecutableScanException * Thrown if there is an error with {@link Executable} annotations * for the given class. */ protected List<ExecutableAnnotationInformation> findExecutableAnnotationInformation(Class<?> clazz) throws ExecutableScanException { Predicate<Method> isAnnotatedPredicate = Predicates.compose(Predicates.not(Predicates.isNull()), new ExecutableAnnotationFunction()); List<Method> annotatedMethods = Lists .newArrayList(Iterables.filter(Arrays.asList(clazz.getDeclaredMethods()), isAnnotatedPredicate)); List<ExecutableAnnotationInformation> executableAnnotationInformations = Lists.newArrayList(); if (!annotatedMethods.isEmpty()) { if (clazz.getEnclosingClass() != null || clazz.isInterface()) { throw new NotTopLevelClassExecutableScanException(clazz); } for (Method annotatedMethod : annotatedMethods) { ExecutableAnnotationInformation executableAnnotationInformation = generateExecutableAnnotationInformation( clazz, annotatedMethod); executableAnnotationInformations.add(executableAnnotationInformation); } } return executableAnnotationInformations; } /** * Generate the {@link ExecutableAnnotationInformation} for an annotated * method. * * @param clazz * The class being searched. * @param annotatedMethod * The annotated method. * @return An {@link ExecutableAnnotationInformation} bean containing all * required information about the annotated method. * @throws ExecutableScanException * Thrown if there are any errors in gathering the required * information. */ protected ExecutableAnnotationInformation generateExecutableAnnotationInformation(Class<?> clazz, Method annotatedMethod) throws ExecutableScanException { int modifiers = annotatedMethod.getModifiers(); if (!Modifier.isPublic(modifiers)) { throw new NotPublicMethodExecutableScanException(clazz, annotatedMethod); } if (Modifier.isStatic(modifiers)) { throw new StaticMethodExecutableScanException(clazz, annotatedMethod); } Class<?>[] parameterTypes = annotatedMethod.getParameterTypes(); if (parameterTypes.length != 1) { throw new InvalidParameterCountExecutableScanException(clazz, annotatedMethod, parameterTypes.length); } Class<?> commandLineClass = parameterTypes[0]; if (!commandLineClass.isInterface()) { throw new CommandLineClassNotAnInterfaceExecutableScanException(clazz, annotatedMethod, commandLineClass); } CommandLineInterface commandLineInterface = commandLineClass.getAnnotation(CommandLineInterface.class); if (commandLineInterface == null) { throw new CommandLineClassNotAnnotatedExecutableScanException(clazz, annotatedMethod, commandLineClass); } String application = commandLineInterface.application(); if (Strings.isNullOrEmpty(application)) { throw new ApplicationNameNotDeclaredExecutableScanException(clazz, annotatedMethod, commandLineClass); } Executable executable = new ExecutableAnnotationFunction().apply(annotatedMethod); Class<? extends Module>[] guiceModules = extractGuiceModules(clazz, annotatedMethod, executable); List<String> guiceModuleClassNames = Lists .newArrayList(Iterables.transform(Arrays.asList(guiceModules), new ClassNameFunction())); ExecutableAnnotationInformation executableAnnotationInformation = new ExecutableAnnotationInformation( clazz.getName(), annotatedMethod.getName(), commandLineClass.getName(), application, guiceModuleClassNames); return executableAnnotationInformation; } /** * Check to see if the command class is instantiable and return any declared * Guice Modules. * * @param clazz * The command class. * @param executable * The {@link Executable} annotation that has been found. * @return The array of Guice modules declared by the annotation. * @throws CommandNotInstantiableExecutableScanException * Thrown if the command class cannot be instantiatied. * @throws NonGuiceModulesReferencedException Thrown if non-Guice {@link Module}s are found. */ @SuppressWarnings("unchecked") protected Class<? extends Module>[] extractGuiceModules(Class<?> clazz, Method annotatedMethod, Executable executable) throws CommandNotInstantiableExecutableScanException, NonGuiceModulesReferencedException { Class<?>[] guiceModules = executable.value(); List<Class<?>> nonGuiceModules = Lists.newArrayList( Iterables.filter(Arrays.asList(guiceModules), Predicates.not(new IsGuiceModulePredicate()))); if (!nonGuiceModules.isEmpty()) { throw new NonGuiceModulesReferencedException(clazz, annotatedMethod, nonGuiceModules); } Predicate<Constructor<?>> isInstantiablePredicate = new IsDefaultConstructor(); if (guiceModules.length != 0) { isInstantiablePredicate = Predicates.or(isInstantiablePredicate, IsConstructorAnnotated.with(javax.inject.Inject.class)); isInstantiablePredicate = Predicates.or(isInstantiablePredicate, IsConstructorAnnotated.with(com.google.inject.Inject.class)); } if (!Iterables.any(Arrays.asList(clazz.getConstructors()), isInstantiablePredicate)) { throw new CommandNotInstantiableExecutableScanException(clazz); } return (Class<? extends Module>[]) guiceModules; } /** * A {@link Predicate} to check to see if a class is a subclass of * {@link Module}. * * @author alex * */ static class IsGuiceModulePredicate implements Predicate<Class<?>> { @Override public boolean apply(Class<?> clazz) { return Module.class.isAssignableFrom(clazz); } } /** * A {@link Function} that returns a method's {@link Executable} annotation if * it has one or null otherwise. * * @author alex * */ static class ExecutableAnnotationFunction implements Function<Method, Executable> { @Override public Executable apply(Method method) { return method.getAnnotation(Executable.class); } } /** * A {@ink Function} to get a class's name. * * @author alex * */ static class ClassNameFunction implements Function<Class<?>, String> { @Override public String apply(Class<?> clazz) { return clazz.getName(); } } /** * A {@link Predicate} that returns true if it is a class's default * constructor. * * @author alex * */ static class IsDefaultConstructor implements Predicate<Constructor<?>> { @Override public boolean apply(Constructor<?> constructor) { return constructor.getParameterTypes().length == 0; } } /** * A {@link Predicate} that returns true if a constructor is annotated with a * given predicate. * * @author alex * */ static class IsConstructorAnnotated implements Predicate<Constructor<?>> { /** * Create a new instance of {@link IsConstructorAnnotated}. * * @param annotationClass * The class of the annotation to look for. * @return A new instance of {@link IsConstructorAnnotated}. */ public static Predicate<Constructor<?>> with(Class<? extends Annotation> annotationClass) { return new IsConstructorAnnotated(annotationClass); } /** * The class of the annotation to look for. */ private final Class<? extends Annotation> annotationClass; public IsConstructorAnnotated(Class<? extends Annotation> annotationClass) { super(); this.annotationClass = annotationClass; } @Override public boolean apply(Constructor<?> constructor) { return constructor.isAnnotationPresent(getAnnotationClass()); } public Class<? extends Annotation> getAnnotationClass() { return annotationClass; } } }