Skip to content

Commit

Permalink
JAVA-2109: Bootstrap entity annotations and processing
Browse files Browse the repository at this point in the history
  • Loading branch information
olim7t committed Jun 21, 2019
1 parent 704ec9d commit d0ea482
Show file tree
Hide file tree
Showing 18 changed files with 778 additions and 6 deletions.
Expand Up @@ -16,13 +16,17 @@
package com.datastax.oss.driver.internal.mapper.processor;

import com.datastax.oss.driver.api.mapper.annotations.Dao;
import com.datastax.oss.driver.api.mapper.annotations.Entity;
import com.datastax.oss.driver.api.mapper.annotations.Mapper;
import com.datastax.oss.driver.internal.mapper.processor.util.NameIndex;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;

public interface CodeGeneratorFactory {

/** The "helper" class associated to an {@link Entity}-annotated class. */
CodeGenerator newEntity(TypeElement classElement);

/**
* All the types derived from a {@link Mapper}-annotated interface.
*
Expand Down
Expand Up @@ -28,6 +28,10 @@ public DecoratedMessager(Messager messager) {
this.messager = messager;
}

public void warn(Element element, String template, Object... arguments) {
messager.printMessage(Diagnostic.Kind.WARNING, String.format(template, arguments), element);
}

public void warn(String template, Object... arguments) {
messager.printMessage(Diagnostic.Kind.WARNING, String.format(template, arguments));
}
Expand Down
Expand Up @@ -17,6 +17,7 @@

import com.datastax.oss.driver.api.mapper.annotations.Dao;
import com.datastax.oss.driver.internal.mapper.processor.dao.DaoImplementationGenerator;
import com.datastax.oss.driver.internal.mapper.processor.entity.EntityHelperGenerator;
import com.datastax.oss.driver.internal.mapper.processor.mapper.DaoFactoryMethodGenerator;
import com.datastax.oss.driver.internal.mapper.processor.mapper.MapperBuilderGenerator;
import com.datastax.oss.driver.internal.mapper.processor.mapper.MapperGenerator;
Expand All @@ -40,6 +41,11 @@ public DefaultCodeGeneratorFactory(ProcessorContext context) {
this.context = context;
}

@Override
public CodeGenerator newEntity(TypeElement classElement) {
return new EntityHelperGenerator(classElement, context);
}

@Override
public CodeGenerator newMapper(TypeElement interfaceElement) {
return new MapperGenerator(interfaceElement, context);
Expand Down
Expand Up @@ -18,6 +18,8 @@
import com.datastax.oss.driver.internal.core.context.DefaultDriverContext;
import com.datastax.oss.driver.internal.core.util.concurrent.CycleDetector;
import com.datastax.oss.driver.internal.core.util.concurrent.LazyReference;
import com.datastax.oss.driver.internal.mapper.processor.entity.DefaultEntityFactory;
import com.datastax.oss.driver.internal.mapper.processor.entity.EntityFactory;
import com.datastax.oss.driver.internal.mapper.processor.util.Classes;
import javax.annotation.processing.Filer;
import javax.lang.model.util.Elements;
Expand All @@ -32,6 +34,9 @@ public class DefaultProcessorContext implements ProcessorContext {
private final LazyReference<CodeGeneratorFactory> codeGeneratorFactoryRef =
new LazyReference<>("codeGeneratorFactory", this::buildCodeGeneratorFactory, cycleDetector);

private final LazyReference<EntityFactory> entityFactoryRef =
new LazyReference<>("codeGeneratorFactory", this::buildEntityFactory, cycleDetector);

private final DecoratedMessager messager;
private final Types typeUtils;
private final Elements elementUtils;
Expand All @@ -55,6 +60,10 @@ protected CodeGeneratorFactory buildCodeGeneratorFactory() {
return new DefaultCodeGeneratorFactory(this);
}

protected EntityFactory buildEntityFactory() {
return new DefaultEntityFactory(this);
}

@Override
public DecoratedMessager getMessager() {
return messager;
Expand Down Expand Up @@ -84,4 +93,9 @@ public JavaPoetFiler getFiler() {
public CodeGeneratorFactory getCodeGeneratorFactory() {
return codeGeneratorFactoryRef.get();
}

@Override
public EntityFactory getEntityFactory() {
return entityFactoryRef.get();
}
}
Expand Up @@ -16,6 +16,7 @@
package com.datastax.oss.driver.internal.mapper.processor;

import com.datastax.oss.driver.api.mapper.annotations.Dao;
import com.datastax.oss.driver.api.mapper.annotations.Entity;
import com.datastax.oss.driver.api.mapper.annotations.Mapper;
import com.squareup.javapoet.ClassName;
import javax.lang.model.element.ExecutableElement;
Expand All @@ -24,6 +25,14 @@
/** Centralizes the naming conventions for types or members generated by the processor. */
public class GeneratedNames {

/** The entity helpers' private constants holding generic type definitions. */
public static final String GENERIC_TYPE_CONSTANT = "GENERIC_TYPE";

/** The helper class generated for an {@link Entity}-annotated class. */
public static ClassName entityHelper(TypeElement entityClass) {
return ClassName.get(entityClass).peerClass(entityClass.getSimpleName() + "_Helper");
}

/** The builder for a {@link Mapper}-annotated interface. */
public static ClassName mapperBuilder(TypeElement mapperInterface) {
String custom = mapperInterface.getAnnotation(Mapper.class).builderName();
Expand Down
Expand Up @@ -16,6 +16,7 @@
package com.datastax.oss.driver.internal.mapper.processor;

import com.datastax.oss.driver.api.mapper.annotations.Dao;
import com.datastax.oss.driver.api.mapper.annotations.Entity;
import com.datastax.oss.driver.api.mapper.annotations.Mapper;
import com.datastax.oss.driver.shaded.guava.common.base.Strings;
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet;
Expand Down Expand Up @@ -68,8 +69,12 @@ public boolean process(
ProcessorContext context = buildContext(messager, typeUtils, elementUtils, filer, indent);

CodeGeneratorFactory generatorFactory = context.getCodeGeneratorFactory();
processAnnotatedInterfaces(roundEnvironment, Dao.class, generatorFactory::newDao);
processAnnotatedInterfaces(roundEnvironment, Mapper.class, generatorFactory::newMapper);
processAnnotatedTypes(
roundEnvironment, Entity.class, ElementKind.CLASS, generatorFactory::newEntity);
processAnnotatedTypes(
roundEnvironment, Dao.class, ElementKind.INTERFACE, generatorFactory::newDao);
processAnnotatedTypes(
roundEnvironment, Mapper.class, ElementKind.INTERFACE, generatorFactory::newMapper);
return true;
}

Expand All @@ -82,14 +87,18 @@ protected ProcessorContext buildContext(
return new DefaultProcessorContext(messager, typeUtils, elementUtils, filer, indent);
}

protected void processAnnotatedInterfaces(
protected void processAnnotatedTypes(
RoundEnvironment roundEnvironment,
Class<? extends Annotation> annotationClass,
ElementKind expectedKind,
Function<TypeElement, CodeGenerator> generatorFactory) {
for (Element element : roundEnvironment.getElementsAnnotatedWith(annotationClass)) {
if (element.getKind() != ElementKind.INTERFACE) {
if (element.getKind() != expectedKind) {
messager.error(
element, "Only interfaces can be annotated with %s", annotationClass.getSimpleName());
element,
"Only %s elements can be annotated with %s",
expectedKind,
annotationClass.getSimpleName());
} else {
// Safe cast given that we checked the kind above
TypeElement typeElement = (TypeElement) element;
Expand All @@ -107,7 +116,7 @@ protected void processAnnotatedInterfaces(

@Override
public Set<String> getSupportedAnnotationTypes() {
return ImmutableSet.of(Mapper.class.getName(), Dao.class.getName());
return ImmutableSet.of(Entity.class.getName(), Mapper.class.getName(), Dao.class.getName());
}

@Override
Expand Down
Expand Up @@ -24,6 +24,8 @@
* <p>For example, a final field with the corresponding initialization statements in the constructor
* and getter.
*/
// TODO get rid of this? we could generalize the pattern where partial generators invoke callbacks
// on the parent generator
public interface PartialClassGenerator {

void addMembers(TypeSpec.Builder classBuilder);
Expand Down
Expand Up @@ -15,6 +15,7 @@
*/
package com.datastax.oss.driver.internal.mapper.processor;

import com.datastax.oss.driver.internal.mapper.processor.entity.EntityFactory;
import com.datastax.oss.driver.internal.mapper.processor.util.Classes;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
Expand All @@ -33,4 +34,6 @@ public interface ProcessorContext {
JavaPoetFiler getFiler();

CodeGeneratorFactory getCodeGeneratorFactory();

EntityFactory getEntityFactory();
}
@@ -0,0 +1,49 @@
/*
* Copyright DataStax, Inc.
*
* 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 com.datastax.oss.driver.internal.mapper.processor.entity;

import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList;
import com.squareup.javapoet.ClassName;
import java.util.List;

public class DefaultEntityDefinition implements EntityDefinition {

private final ClassName className;
private final String cqlName;
private final ImmutableList<PropertyDefinition> propertyDefinitions;

public DefaultEntityDefinition(
ClassName className, String cqlName, List<PropertyDefinition> propertyDefinitions) {
this.className = className;
this.cqlName = cqlName;
this.propertyDefinitions = ImmutableList.copyOf(propertyDefinitions);
}

@Override
public ClassName getClassName() {
return className;
}

@Override
public String getCqlName() {
return cqlName;
}

@Override
public Iterable<PropertyDefinition> getProperties() {
return propertyDefinitions;
}
}
@@ -0,0 +1,136 @@
/*
* Copyright DataStax, Inc.
*
* 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 com.datastax.oss.driver.internal.mapper.processor.entity;

import com.datastax.oss.driver.api.mapper.annotations.Entity;
import com.datastax.oss.driver.internal.mapper.processor.ProcessorContext;
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
import java.beans.Introspector;
import java.util.HashMap;
import java.util.Map;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

public class DefaultEntityFactory implements EntityFactory {

private final ProcessorContext context;

public DefaultEntityFactory(ProcessorContext context) {
this.context = context;
}

@Override
public EntityDefinition getDefinition(TypeElement classElement) {

// Basic implementation to get things started: look for pairs of getter/setter methods that
// share the same name and operate on the same type.
// This will get revisited in future tickets:
// TODO support custom naming conventions
// TODO property annotations: PK, custom name, computed, ignored...
// TODO inherit annotations and properties from superclass / parent interface
// TODO handle annotations on fields...

Map<String, DefaultPropertyDefinition.Builder> propertyBuilders = new HashMap<>();
for (Element child : classElement.getEnclosedElements()) {
if (child.getKind() == ElementKind.METHOD) {
ExecutableElement method = (ExecutableElement) child;
String methodName = method.getSimpleName().toString();
if (methodName.startsWith("get") && method.getParameters().isEmpty()) {
TypeMirror returnTypeMirror = method.getReturnType();
TypeName propertyType = TypeName.get(returnTypeMirror);
if (TypeName.VOID.equals(propertyType)) {
continue;
}
String propertyName = Introspector.decapitalize(methodName.substring(3));
DefaultPropertyDefinition.Builder builder = propertyBuilders.get(propertyName);
if (builder == null) {
builder =
new DefaultPropertyDefinition.Builder(
propertyName, propertyType, getEntityElement(returnTypeMirror));
propertyBuilders.put(propertyName, builder);
} else if (!builder.getType().equals(propertyType)) {
context
.getMessager()
.warn(
method,
"Ignoring method %s %s() because there is a setter "
+ "with the same name but a different type: %s(%s)",
propertyType,
methodName,
builder.getSetterName(),
builder.getType());
continue;
}
builder.withGetterName(methodName);
} else if (methodName.startsWith("set") && method.getParameters().size() == 1) {
String propertyName = Introspector.decapitalize(methodName.substring(3));
VariableElement parameter = method.getParameters().get(0);
TypeMirror typeMirror = parameter.asType();
TypeName propertyType = TypeName.get(typeMirror);
DefaultPropertyDefinition.Builder builder = propertyBuilders.get(propertyName);
if (builder == null) {
builder =
new DefaultPropertyDefinition.Builder(
propertyName, propertyType, getEntityElement(typeMirror));
propertyBuilders.put(propertyName, builder);
} else if (!builder.getType().equals(propertyType)) {
context
.getMessager()
.warn(
method,
"Ignoring method %s(%s) because there is a getter "
+ "with the same name but a different type: %s %s()",
methodName,
propertyType,
builder.getType(),
builder.getGetterName());
continue;
}
builder.withSetterName(methodName);
}
}
}

ImmutableList.Builder<PropertyDefinition> definitions = ImmutableList.builder();
for (DefaultPropertyDefinition.Builder builder : propertyBuilders.values()) {
if (builder.getGetterName() != null && builder.getSetterName() != null) {
definitions.add(builder.build());
}
}

String entityName = Introspector.decapitalize(classElement.getSimpleName().toString());
return new DefaultEntityDefinition(
ClassName.get(classElement), entityName, definitions.build());
}

private TypeElement getEntityElement(TypeMirror typeMirror) {
if (typeMirror.getKind() == TypeKind.DECLARED) {
Element element = ((DeclaredType) typeMirror).asElement();
if (element.getKind() == ElementKind.CLASS && element.getAnnotation(Entity.class) != null) {
return (TypeElement) element;
}
}
return null;
}
}

0 comments on commit d0ea482

Please sign in to comment.