package xapi.mojo.model;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import org.apache.maven.model.Build;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import xapi.annotation.model.IsModel;
import xapi.bytecode.ClassFile;
import xapi.bytecode.impl.BytecodeAdapterService;
import xapi.dev.model.HasModelFields;
import xapi.dev.model.ModelField;
import xapi.dev.scanner.X_Scanner;
import xapi.dev.scanner.impl.ClasspathResourceMap;
import xapi.inject.impl.SingletonProvider;
import xapi.log.X_Log;
import xapi.model.api.Model;
import xapi.mojo.api.AbstractXapiMojo;
import xapi.mvn.X_Maven;
import xapi.source.X_Source;
import xapi.source.api.IsAnnotation;
import xapi.source.api.IsClass;
import xapi.source.api.IsMethod;
import xapi.util.X_Debug;
import xapi.util.api.ConvertsValue;
/**
* Examines the classpath for interfaces extending {@link Model},
* or any type annotated with {@link IsModel}, and then builds
* implementation classes for the given type.
*
* @author <a href="mailto:james@wetheinter.net">James X. Nelson</a>
* @version $Id$
*/
@Mojo
(
name="modelgen"
,defaultPhase = LifecyclePhase.PROCESS_CLASSES
,requiresDependencyResolution = ResolutionScope.COMPILE
,threadSafe=true
)
public class ModelGeneratorMojo extends AbstractXapiMojo {
@SuppressWarnings("unchecked")
@Override
protected void doExecute() throws MojoExecutionException, MojoFailureException {
// Start by scanning the class folders
MavenProject project = getSession().getCurrentProject();
Build build = project.getBuild();
ClasspathResourceMap classFolder // The classes we just compiled
= X_Scanner.scanFolder(build.getOutputDirectory(), true, false, false, "");
ClasspathResourceMap environment // The complete compile-scope classpath
= X_Maven.compileScopeScanner(project, getSession());
final BytecodeAdapterService adapter = X_Maven.compileScopeAdapter(project, getSession());
// IdentityHashMap is faster, as it uses == for comparison,
// and we know our ClassFiles come from the same map
IdentityHashMap<ClassFile, Integer> models =
new IdentityHashMap<ClassFile, Integer>();
// While we iterate the result of the classes we just compiled,
// The full compile classpath scan is running in separate threads
for (ClassFile model : classFolder.findImplementationOf(Model.class)) {
models.put(model, 1);
}
for (ClassFile model : classFolder.findClassAnnotatedWith(IsModel.class)) {
if (!models.containsKey(model))
models.put(model, 0);
}
// Build implementations for our models
for (Iterator<Entry<ClassFile, Integer>> i = models.entrySet().iterator(); i.hasNext();) {
Entry<ClassFile, Integer> entry = i.next();
if (entry.getValue().equals(0)) {
buildPojo(entry.getKey(), adapter, environment);
} else {
buildModel(entry.getKey(), adapter, environment);
}
i.remove();
}
// Run a javac for annotation processing.
}
private final SingletonProvider<ConvertsValue<IsAnnotation, IsModel>> builder = new SingletonProvider<ConvertsValue<IsAnnotation,IsModel>>() {
protected xapi.util.api.ConvertsValue<IsAnnotation,IsModel> initialValue() {
return new ConvertsValue<IsAnnotation, IsModel>() {
Method cls;
@Override
public IsModel convert(IsAnnotation from) {
try {
if (cls == null) {
cls = Class.forName(IsModel.class.getName()+"Proxy", true, Thread.currentThread().getContextClassLoader())
.getDeclaredMethod("build", IsAnnotation.class);
}
return (IsModel) cls.invoke(null, from);
} catch (Throwable e) {
throw X_Debug.rethrow(e);
}
}
};
};
};
void buildModel(ClassFile clsFile, BytecodeAdapterService adapter, ClasspathResourceMap classpath) {
X_Log.info("Building model ",clsFile);
IsClass cls = adapter.toClass(clsFile.getName());
IsAnnotation modelAnno = cls.getAnnotation(IsModel.class.getName());
HasModelFields model = new HasModelFields();
if (modelAnno != null) {
IsModel isModel = builder.get().convert(modelAnno);
model.setDefaultSerializable(isModel.serializable());
model.setDefaultPersistence(isModel.persistence());
model.setKey(isModel.key());
}
// Collect all model methods
collectModelFields(model, cls, adapter);
}
private void collectModelFields(HasModelFields model, IsClass cls,
BytecodeAdapterService adapter) {
if (cls.isInterface()) {
// Interface-only models don't have to look at any fields or existing methods
// They will also be able to select superclass based on platform type
for (IsMethod method : cls.getMethods()) {
if (X_Source.isJavaLangObject(method.getEnclosingType()))
continue;
if (HasModelFields.isModel(method.getEnclosingType()))
continue;
ModelField field = model.getOrMakeField(normalizeName(method.getName()));
X_Log.info("Field",field);
boolean unknownType = true;
for (IsAnnotation anno : method.getAnnotations()) {
X_Log.info("Anno",anno);
}
if (unknownType) {
//Unknown type; we have to guess if it's a getter, setter, adder, remover.
Annotation guessed = guessType(method);
}
}
for (IsClass iface : cls.getInterfaces()) {
}
} else {
// Classes will only have abstract methods filled in, and serializers built
for (IsMethod method : cls.getMethods()) {
if (method.isAbstract()) {
}
}
}
}
private Annotation guessType(IsMethod method) {
if (method.getParameters().length==0) {
// might be a getter or remover
if (method.getName().startsWith("rem")||method.getName().startsWith("clear")) {
}
} else {
// might be a setter, adder or remover
}
return null;
}
private String normalizeName(String name) {
X_Log.info("Method w/ name",name);
return name;
}
private void buildPojo(ClassFile key, BytecodeAdapterService adapter, ClasspathResourceMap classpath) {
X_Log.info("Building pojo ",key);
}
}