blob: d9dc3f9b2e1941f6f9e77a3f0aab6b13679cf17d [file] [log] [blame]
/*
* Copyright (C) 2013 Square, Inc.
* Copyright (C) 2013 Google, 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.google.autofactory;
import static com.google.autofactory.CodeGen.strippedTypeName;
import static java.lang.reflect.Modifier.FINAL;
import static java.lang.reflect.Modifier.PUBLIC;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.inject.Inject;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import com.google.autofactory.ProcessorUtils.InjectedClass;
import com.squareup.java.JavaWriter;
/**
* A class that generates a Factory implementation for a given class.
*/
public final class FactoryAdapterGenerator extends AbstractGenerator {
private static final int PACKAGE = 0;
static final String FACTORY_IMPLEMENTATION = "$AutoFactory";
FactoryAdapterGenerator(ProcessingEnvironment env) {
super(env);
}
/**
* Write a companion class for a factory that prodouces {@code type} and
* extends {@link com.google.autofactory.internal.Binding}.
*
* @param constructor the injectable constructor, or null if this binding
* supports members injection only.
*/
void generate(InjectedClass clazz, TypeElement factory, ExecutableElement method)
throws IOException {
TypeElement type = clazz.type;
String packageName = CodeGen.getPackage(type).getQualifiedName().toString();
String strippedTypeName = strippedTypeName(type.getQualifiedName().toString(), packageName);
String adapterName = CodeGen.adapterName(factory, FACTORY_IMPLEMENTATION);
JavaWriter writer = newWriter(adapterName, type);
List<? extends VariableElement> parameters = (clazz.constructor != null)
? clazz.constructor.getParameters()
: new ArrayList<VariableElement>();
writer.emitEndOfLineComment("Generated by com.google.autofactory's generator. Do not edit.");
writer.emitPackage(packageName);
writer.emitEmptyLine();
writer.emitImports(getImports(factory, clazz.fields, parameters));
writer.emitEmptyLine();
writer.emitJavadoc("An implementation of {@code %s} to act as a factory for {@code %s}",
factory.getSimpleName(), strippedTypeName);
writer.beginType(adapterName, "class", PUBLIC | FINAL, null,
factory.getSimpleName().toString());
for (VariableElement parameter : parameters) {
if (parameter.getAnnotation(Param.class) == null) {
writer.emitAnnotation(Inject.class);
writer.emitField(CodeGen.typeToString(parameter.asType()),
parameterName(false, parameter), PACKAGE);
}
}
for (Element field : clazz.fields) {
if (field.getAnnotation(Inject.class) != null) {
writer.emitAnnotation(Inject.class);
writer.emitField(CodeGen.typeToString(field.asType()),
fieldName(false, field), PACKAGE);
}
}
if (!hasDependencies(clazz)) { // If no deps, generate an @Inject constructor.
writer.emitEmptyLine();
writer.emitAnnotation(Inject.class);
writer.beginMethod(null, adapterName, PUBLIC);
writer.endMethod();
}
writer.emitEmptyLine();
writer.emitAnnotation(Override.class);
writer.beginMethod(strippedTypeName, method.getSimpleName().toString(), PUBLIC,
getParameterPairs(method));
StringBuilder newInstance = new StringBuilder();
newInstance.append(strippedTypeName).append(" instance = new ");
newInstance.append(strippedTypeName).append('(');
boolean first = true;
for (VariableElement parameter : parameters) {
if (!first) newInstance.append(", ");
else first = false;
newInstance.append(parameterName(false, parameter));
}
newInstance.append(')');
writer.emitStatement(newInstance.toString());
for (Element field : clazz.fields) {
writer.emitStatement("instance.%s = %s", field.getSimpleName(),
fieldName(false, field));
}
writer.emitStatement("return instance");
writer.endMethod();
writer.endType();
writer.close();
}
private boolean hasDependencies(InjectedClass clazz) {
for (Element e : clazz.fields) {
if (e.getAnnotation(Inject.class) != null) return true;
}
if (clazz.constructor != null && clazz.constructor.getAnnotation(Inject.class) != null) {
for (Element e : clazz.constructor.getParameters()) {
if (e.getAnnotation(Param.class) == null) return true;
}
}
return false;
}
private String[] getParameterPairs(ExecutableElement method) {
List<String> paramPairs = new ArrayList<String>();
for (VariableElement element : method.getParameters()) {
paramPairs.add(element.asType().toString());
paramPairs.add(element.getSimpleName().toString());
}
return paramPairs.toArray(new String[paramPairs.size()]);
}
private Set<String> getImports(TypeElement factory, List<Element> fields,
List<? extends VariableElement> parameters) {
Set<String> imports = new LinkedHashSet<String>();
imports.add(factory.getQualifiedName().toString());
imports.add(Inject.class.getName());
collectImportsFromElements(fields, imports);
collectImportsFromElements(parameters, imports);
return imports;
}
private void collectImportsFromElements(List<? extends Element> elements, Set<String> imports) {
for (Element e : elements) {
TypeMirror tm = e.asType();
if (!tm.getKind().isPrimitive() && !tm.toString().startsWith("java.lang")) {
imports.add(tm.toString());
}
}
}
}