com.greenpepper.maven.plugin.spy.impl.JavaFixtureGenerator Maven / Gradle / Ivy
The newest version!
package com.greenpepper.maven.plugin.spy.impl;
import com.greenpepper.annotation.Fixture;
import com.greenpepper.annotation.FixtureCollection;
import com.greenpepper.annotation.FixtureMethod;
import com.greenpepper.spy.FixtureGenerator;
import com.greenpepper.reflect.CollectionProvider;
import com.greenpepper.reflect.EnterRow;
import com.greenpepper.spy.*;
import com.greenpepper.util.NameUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.filefilter.NameFileFilter;
import org.apache.commons.lang3.StringUtils;
import org.jboss.forge.roaster.Roaster;
import org.jboss.forge.roaster.model.source.FieldSource;
import org.jboss.forge.roaster.model.source.JavaClassSource;
import org.jboss.forge.roaster.model.source.JavaSource;
import org.jboss.forge.roaster.model.source.MethodSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.io.File.separatorChar;
import static java.lang.String.format;
import static org.apache.commons.io.FileUtils.*;
import static org.apache.commons.lang3.StringUtils.*;
public class JavaFixtureGenerator implements FixtureGenerator {
private static final Logger LOGGER = LoggerFactory.getLogger(JavaFixtureGenerator.class);
private static final Pattern FULL_CLASS_NAME_PATTERN = Pattern.compile("([\\.\\p{Alnum}]+)\\.[\\p{Alnum}]+");
private static final char DOT = '.';
private static final String JAVA_EXTENSION = ".java";
private String defaultPackage;
public void setDefaultPackage(String defaultPackage) {
this.defaultPackage = defaultPackage;
}
@Override
public Result generateFixture(FixtureDescription fixture, MetaInformation metaInformation, File fixtureSourceDirectory) throws Exception {
String packageName = getPackageName(fixture, metaInformation);
File javaFile = getJavaSouceFile(fixture, metaInformation, fixtureSourceDirectory, packageName);
ActionDone action = ActionDone.NONE;
boolean fileHasBeenUpdated = false;
final JavaClassSource javaClass;
if (javaFile.exists()) {
javaClass = Roaster.parse(JavaClassSource.class, javaFile);
} else {
javaClass = Roaster.create(JavaClassSource.class);
if (isNotBlank(packageName)) {
javaClass.setPackage(packageName);
}
javaClass.setName(fixture.getName()).addAnnotation(Fixture.class).setStringValue("usage", fixture.getRawName());
fileHasBeenUpdated = true;
action = ActionDone.CREATED;
}
fileHasBeenUpdated |= updateConstructors(fixture, javaClass);
fileHasBeenUpdated |= updateFields(fixture, javaClass);
fileHasBeenUpdated |= updateMethods(fixture, fixtureSourceDirectory, javaClass);
if (fileHasBeenUpdated) {
switch (action) {
case NONE:
action = ActionDone.UPDATED;
writeStringToFile(javaFile, javaClass.toUnformattedString());
break;
case CREATED:
writeStringToFile(javaFile, javaClass.toString());
break;
default:
throw new IllegalArgumentException("Action " + action + " is not supported.");
}
}
return new Result(action, javaFile);
}
private File getJavaSouceFile(FixtureDescription fixture, MetaInformation metaInformation, File fixtureSourceDirectory, String packageName) throws IOException {
String fixtureFilename = fixture.getName() + JAVA_EXTENSION;
File javaSouceFile = null;
// we search in every imports
List imports = metaInformation.getImports();
for (String anImport : imports) {
File packageDir = new File(fixtureSourceDirectory, replaceChars(anImport, DOT, separatorChar));
if (packageDir.isDirectory()) {
Collection files = listFiles(packageDir, new NameFileFilter(fixtureFilename, IOCase.INSENSITIVE), null);
if (files.size() == 1) {
javaSouceFile = files.iterator().next();
break;
} else if (files.size() > 1) {
LOGGER.error("You have multiple java sources with the same case insensitive names.");
LOGGER.error("We can't choose the file to merge the fixture in.");
LOGGER.error("Moreover, your build is platform dependant because of this issue.");
LOGGER.error("Incriminating files:");
for (File file : files) {
LOGGER.error("\t - {}", file.getAbsolutePath());
}
break;
}
}
}
// if we didn't find the file
if (javaSouceFile == null) {
if (isNotBlank(packageName)) {
File directoryForFixure = new File(fixtureSourceDirectory, replaceChars(packageName, DOT, separatorChar));
forceMkdir(directoryForFixure);
javaSouceFile = new File(directoryForFixure, fixtureFilename);
} else {
forceMkdir(fixtureSourceDirectory);
javaSouceFile = new File(fixtureSourceDirectory, fixtureFilename);
LOGGER.warn("You have no default package defined. I can't choose any import packages. Generating the Fixture in the source root. This is generally not a good idea.");
}
}
return javaSouceFile ;
}
private String getPackageName(FixtureDescription fixture, MetaInformation metaInformation) {
String packageName = null;
Matcher matcher = FULL_CLASS_NAME_PATTERN.matcher(fixture.getRawName());
if (matcher.matches()) {
packageName = matcher.group(1);
}
if (isBlank(packageName)) {
List imports = metaInformation.getImports();
if (imports != null && imports.size() == 1 && isNotBlank(imports.get(0))) {
packageName = imports.get(0);
} else if (isNotBlank(defaultPackage)) {
packageName = defaultPackage;
}
}
return packageName;
}
/**
*
* @return true if an update has been made.
*/
private boolean updateMethods(FixtureDescription fixture, File fixtureSourceDirectory, JavaClassSource javaClass) throws FileNotFoundException {
boolean fileHasBeenUpdated = false;
switch (fixture.getType()) {
case WORKFLOW:
for (MethodDescription method : fixture.getMethods()) {
boolean existingMethodFound = isExistingMethodFound(fixtureSourceDirectory, javaClass, method);
if (!existingMethodFound) {
LOGGER.debug("Creating Method '{}' to deal with '{}'", method.getName(), method.getRawName() );
MethodSource methodSource = javaClass.addMethod()
.setName(method.getName())
.setPublic();
methodSource.addAnnotation(FixtureMethod.class).setStringValue("usage", method.getRawName());
FixtureDescription subFixtureSpy = method.getSubFixtureDescription();
if (subFixtureSpy == null) {
methodSource.setReturnType(String.class);
} else {
switch (subFixtureSpy.getType()) {
case COLLECTION_PROVIDER:
generateMethodForCollectionProvider(javaClass, methodSource, subFixtureSpy);
break;
case SETUP:
generateMethodForSetup(javaClass, methodSource, subFixtureSpy);
break;
default:
throw new IllegalStateException("The value '" + subFixtureSpy.getType() + "' of the subFixture is not supported. " +
"This means that you tried to put a '" + subFixtureSpy.getType() + "' fixture inside another one in your specification.");
}
}
for (int i = 0; i < method.getArity(); i++) {
methodSource.addParameter(String.class, "param" + (i + 1));
}
methodSource.setBody("throw new UnsupportedOperationException(\"Not yet implemented!\");");
fileHasBeenUpdated = true;
}
}
break;
case COLLECTION_PROVIDER:
MethodDescription queryMethod = new ProbingMethod("query",0);
boolean existingMethodFound = isExistingMethodFound(fixtureSourceDirectory, javaClass, queryMethod);
if (!existingMethodFound) {
LOGGER.debug("Creating Method '{}' to provide the @CollectionProvider", queryMethod.getName() );
MethodSource methodSource = javaClass.addMethod()
.setName(queryMethod.getName())
.setPublic();
generateMethodForCollectionProvider(javaClass, methodSource, fixture);
methodSource.setBody("throw new UnsupportedOperationException(\"Not yet implemented!\");");
fileHasBeenUpdated = true;
}
break;
case SETUP:
MethodDescription method = new ProbingMethod("enter row", 0);
boolean existingEnterRowMethodFound = isExistingMethodFound(fixtureSourceDirectory, javaClass, method);
if (!existingEnterRowMethodFound) {
LOGGER.debug("Creating Method '{}' to provide the @EnterRow", method.getName() );
MethodSource methodSource = javaClass.addMethod()
.setName(method.getName())
.setPublic();
generateMethodForSetup(javaClass, methodSource, fixture);
methodSource.setBody("throw new UnsupportedOperationException(\"Not yet implemented!\");");
fileHasBeenUpdated = true;
}
break;
default:
throw new IllegalStateException("The value '" + fixture.getType() + "' of the FixtureDefinition is not supported");
}
return fileHasBeenUpdated;
}
private class ProbingMethod implements MethodDescription {
private String rawName;
private int arity;
private ProbingMethod(String rawName, int arity) {
this.rawName = rawName;
this.arity = arity;
}
@Override
public int getArity() {
return arity;
}
@Override
public FixtureDescription getSubFixtureDescription() {
return null;
}
@Override
public String getName() {
return NameUtils.toJavaIdentifierForm(this.rawName);
}
@Override
public String getRawName() {
return rawName;
}
}
private void generateMethodForSetup(JavaClassSource javaClass, MethodSource methodSource, FixtureDescription subFixtureSpy) {
LOGGER.debug("Processing @EnterRow method");
methodSource.addAnnotation(EnterRow.class);
appendFieldsToClass(javaClass, subFixtureSpy.getProperties());
}
private void generateMethodForCollectionProvider(JavaClassSource javaClass, MethodSource methodSource, FixtureDescription subFixtureSpy) {
PojoDescription pojo = subFixtureSpy.getPojo();
if (!javaClass.hasNestedType(pojo.getName())) {
JavaSource> nestedType = javaClass.addNestedType(format("public static class %s {}", pojo.getName()));
if (nestedType.isClass()) {
JavaClassSource nestedType1 = (JavaClassSource) nestedType;
for (PropertyDescription property : pojo.getProperties()) {
nestedType1.addField().setName(property.getName()).setType(String.class).setPublic();
}
}
}
JavaSource> nestedType = javaClass.getNestedType(pojo.getName());
methodSource.setReturnType(format("java.util.Collection<%s>",
replaceChars(nestedType.getQualifiedName(), '$', DOT)))
.addAnnotation(CollectionProvider.class);
methodSource.addAnnotation(FixtureCollection.class);
}
private boolean isExistingMethodFound(File fixtureSourceDirectory, JavaClassSource javaClass, MethodDescription method) throws FileNotFoundException {
LOGGER.debug("Searching method '{}' in class '{}'", method.getRawName(), javaClass.getName());
boolean existingMethodFound = false;
for (MethodSource methodSource : javaClass.getMethods()) {
// Normal case
if (equalsIgnoreCase(method.getName(), methodSource.getName()) &&
methodSource.getParameters().size() == method.getArity()) {
existingMethodFound = true;
LOGGER.debug("Found Method '{}' to deal with '{}'", methodSource.getName(), method.getRawName() );
break;
}
if (equalsIgnoreCase(method.getName(),"query") && methodSource.hasAnnotation(CollectionProvider.class)) {
existingMethodFound = true;
LOGGER.debug("Found Method '{}' to deal with '@CollectionProvider' in collection fixture", methodSource.getName() );
break;
}
if (equalsIgnoreCase(method.getName(),"enterRow") && methodSource.hasAnnotation(EnterRow.class)) {
existingMethodFound = true;
LOGGER.debug("Found Method '{}' to deal with '@EnterRow' in setup fixture", methodSource.getName() );
break;
}
FixtureDescription subFixtureSpy = method.getSubFixtureDescription();
if (subFixtureSpy != null) {
switch (subFixtureSpy.getType()) {
case COLLECTION_PROVIDER:
existingMethodFound = checkForStandardMethod(method, methodSource, CollectionProvider.class, "query");
break;
case SETUP:
existingMethodFound = checkForStandardMethod(method, methodSource, EnterRow.class, "enterRow");
break;
default:
throw new IllegalStateException("The value '" + subFixtureSpy.getType() + "' of the subFixture is not supported");
}
}
}
if (!existingMethodFound) {
// Find on the method on the superclass
String superType = javaClass.getSuperType();
if (isNotBlank(superType) && !StringUtils.equals("java.lang.Object", superType)) {
JavaClassSource superTypeSource = findTheClass(fixtureSourceDirectory, superType);
if (superTypeSource != null) {
existingMethodFound = isExistingMethodFound(fixtureSourceDirectory, superTypeSource, method);
}
}
}
return existingMethodFound;
}
private boolean checkForStandardMethod(MethodDescription method, MethodSource methodSource, Class extends Annotation> annotation, String defaultMethodName) {
boolean existingMethodFound = false;
// method technical name
if (equalsIgnoreCase(methodSource.getName(),defaultMethodName) && methodSource.getParameters().isEmpty()) {
existingMethodFound = true;
LOGGER.debug("Found Method '{}' to deal with '{}'", methodSource.getName(), method.getRawName() );
} else if (methodSource.hasAnnotation(annotation)) {
// annotation
existingMethodFound = true;
LOGGER.debug("Found Method '{}' to deal with '{}'", methodSource.getName(), method.getRawName() );
}
return existingMethodFound;
}
private JavaClassSource findTheClass(File fixtureSourceDirectory, String superType) throws FileNotFoundException {
File javaSourceFile = getFile(fixtureSourceDirectory, replaceChars(superType, DOT, separatorChar) + JAVA_EXTENSION);
if (javaSourceFile.exists()) {
return Roaster.parse(JavaClassSource.class, javaSourceFile);
} else {
LOGGER.info("I can't find the source of '{}' in the source directory '{}'", superType, fixtureSourceDirectory);
}
return null;
}
/**
*
* @return true if an update has been made.
*/
private boolean updateFields(FixtureDescription fixture, JavaClassSource javaClass) {
Collection properties = fixture.getProperties();
return appendFieldsToClass(javaClass, properties);
}
private boolean appendFieldsToClass(JavaClassSource javaClass, Collection properties) {
boolean fileHasBeenUpdated = false;
for (PropertyDescription property : properties) {
if (!hasFieldIgnoreCase(javaClass, property)){
javaClass.addField()
.setName(property.getName())
.setType(String.class)
.setPublic();
fileHasBeenUpdated = true;
}
}
return fileHasBeenUpdated;
}
private boolean hasFieldIgnoreCase(JavaClassSource javaClass, PropertyDescription property) {
for (FieldSource fieldSource : javaClass.getFields()) {
if (equalsIgnoreCase(property.getName(), fieldSource.getName())) {
return true;
}
}
return false;
}
/**
*
* @return true if an update has been made.
*/
private boolean updateConstructors(FixtureDescription fixture, JavaClassSource javaClass) {
boolean fileHasBeenUpdated = false;
for (ConstructorDescription constructor : fixture.getConstructors()) {
if (constructor.getArity() > 0) {
boolean existingConstructorFound = false;
for (MethodSource methodSource : javaClass.getMethods()) {
if (StringUtils.equals(constructor.getName(), methodSource.getName()) &&
methodSource.getParameters().size() == constructor.getArity()) {
existingConstructorFound = true;
break;
}
}
if (!existingConstructorFound) {
MethodSource methodSource = javaClass.addMethod()
.setPublic()
.setConstructor(true)
.setBody("throw new UnsupportedOperationException(\"Not yet implemented!\");");
for (int i = 0; i < constructor.getArity(); i++) {
methodSource.addParameter(String.class, "param" + (i + 1));
}
fileHasBeenUpdated = true;
}
}
}
return fileHasBeenUpdated;
}
}