Dependency injection frameworks improvements

* Injectors can now implement interfaces (this was the primary goal)
* Fields can have different types than getters
* Imports are more accurate and no duplicate imports are added
This commit is contained in:
Andrey Breslav
2012-07-02 20:31:07 +04:00
parent 3d58d1ec16
commit af882cbd51
7 changed files with 226 additions and 16 deletions
@@ -17,15 +17,18 @@
package org.jetbrains.jet.di;
import com.google.common.collect.Lists;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* @author abreslav
*/
class ConstructorCall implements Expression {
public class ConstructorCall implements Expression {
private final Constructor<?> constructor;
private final List<Field> constructorArguments = Lists.newArrayList();
@@ -39,6 +42,12 @@ class ConstructorCall implements Expression {
@Override
public String toString() {
return constructor.toString();
}
@NotNull
@Override
public String renderAsCode() {
StringBuilder builder = new StringBuilder("new " + constructor.getDeclaringClass().getSimpleName() + "(");
for (Iterator<Field> iterator = constructorArguments.iterator(); iterator.hasNext(); ) {
Field argument = iterator.next();
@@ -55,4 +64,15 @@ class ConstructorCall implements Expression {
builder.append(")");
return builder.toString();
}
@NotNull
@Override
public Collection<DiType> getTypesToImport() {
return Collections.singletonList(new DiType(constructor.getDeclaringClass()));
}
@NotNull
public DiType getType() {
return new DiType(constructor.getDeclaringClass());
}
}
@@ -55,6 +55,9 @@ public class DependencyInjectorGenerator {
private final Set<Field> backsParameter = Sets.newHashSet();
private final List<Class<?>> implementsList = Lists.newArrayList();
private final Collection<Class<?>> imports = Sets.newHashSet();
public DependencyInjectorGenerator(boolean lazy) {
this.lazy = lazy;
}
@@ -99,7 +102,9 @@ public class DependencyInjectorGenerator {
out.println();
out.println("/* This file is generated by " + AllInjectorsGenerator.class.getName() + ". DO NOT EDIT! */");
out.println("public class " + injectorClassName + " {");
out.print("public class " + injectorClassName);
generateImplementsList(out);
out.println(" {");
// Needed for double-checked locking
// out.println();
// out.println(" private static final Object " + LOCK_NAME + " = new Object();");
@@ -131,6 +136,26 @@ public class DependencyInjectorGenerator {
}
}
private void generateImplementsList(PrintStream out) {
if (!implementsList.isEmpty()) {
out.print(" implements ");
for (Iterator<Class<?>> iterator = implementsList.iterator(); iterator.hasNext(); ) {
Class<?> superInterface = iterator.next();
if (!superInterface.isInterface()) {
throw new IllegalArgumentException("Only interfaces are supported as supertypes");
}
out.print(superInterface.getSimpleName());
if (iterator.hasNext()) {
out.print(", ");
}
}
}
}
public void implementInterface(Class<?> superInterface) {
implementsList.add(superInterface);
}
public void addPublicParameter(Class<?> type) {
addPublicParameter(new DiType(type));
}
@@ -206,6 +231,17 @@ public class DependencyInjectorGenerator {
private void generateImports(PrintStream out, String injectorPackageName) {
for (Field field : fields) {
generateImportDirectives(out, field.getType(), injectorPackageName);
DiType implType = field.getInitialization().getType();
if (implType != null) {
generateImportDirectives(out, implType, injectorPackageName);
}
for (DiType typeToImport : field.getInitialization().getTypesToImport()) {
generateImportDirective(out, typeToImport.getClazz(), injectorPackageName);
}
}
for (Class<?> superInterface : implementsList) {
generateImportDirective(out, superInterface, injectorPackageName);
}
for (Parameter parameter : parameters) {
generateImportDirectives(out, parameter.getType(), injectorPackageName);
@@ -230,7 +266,8 @@ public class DependencyInjectorGenerator {
if (type.isPrimitive()) return;
String importedPackageName = type.getPackage().getName();
if ("java.lang".equals(importedPackageName)
|| injectorPackageName.equals(importedPackageName)) {
|| injectorPackageName.equals(importedPackageName)
|| !imports.add(type)) {
return;
}
out.println("import " + type.getCanonicalName() + ";");
@@ -239,10 +276,15 @@ public class DependencyInjectorGenerator {
private void generateFields(PrintStream out) {
for (Field field : fields) {
String _final = backsParameter.contains(field) ? "final " : "";
out.println(" private " + _final + field.getType().getSimpleName() + " " + field.getName() + ";");
out.println(" private " + _final + getEffectiveFieldType(field).getSimpleName() + " " + field.getName() + ";");
}
}
private static DiType getEffectiveFieldType(Field field) {
DiType implType = field.getInitialization().getType();
return implType == null ? field.getType() : implType;
}
private void generateConstructor(String injectorClassName, PrintStream out) {
String indent = " ";
@@ -277,7 +319,7 @@ public class DependencyInjectorGenerator {
for (Field field : fields) {
//if (!backsParameter.contains(field) || field.isPublic()) {
String prefix = "this.";
out.println(indent + prefix + field.getName() + " = " + field.getInitialization() + ";");
out.println(indent + prefix + field.getName() + " = " + field.getInitialization().renderAsCode() + ";");
//}
}
out.println();
@@ -296,7 +338,7 @@ public class DependencyInjectorGenerator {
// call @PostConstruct
for (Field field : fields) {
// TODO: type of field may be different from type of object
List<Method> postConstructMethods = getPostConstructMethods(field.getType().getClazz());
List<Method> postConstructMethods = getPostConstructMethods(getEffectiveFieldType(field).getClazz());
for (Method postConstruct : postConstructMethods) {
out.println(indent + field.getName() + "." + postConstruct.getName() + "();");
}
@@ -335,7 +377,7 @@ public class DependencyInjectorGenerator {
out.println(" public void destroy() {");
for (Field field : fields) {
// TODO: type of field may be different from type of object
List<Method> preDestroyMethods = getPreDestroyMethods(field.getType().getClazz());
List<Method> preDestroyMethods = getPreDestroyMethods(getEffectiveFieldType(field).getClazz());
for (Method preDestroy : preDestroyMethods) {
out.println(" " + field.getName() + "." + preDestroy.getName() + "();");
}
@@ -359,7 +401,7 @@ public class DependencyInjectorGenerator {
if (lazy && !backsParameter.contains(field)) {
Expression initialization = field.getInitialization();
assert initialization != null : field;
assert initialization instanceof InstantiateType : field;
// Double-checked locking
out.println(indent1 + "if (this." + field.getName() + " == null) {");
@@ -400,12 +442,14 @@ public class DependencyInjectorGenerator {
if (!satisfied.add(field)) return;
if (backsParameter.contains(field)) return;
if (field.getInitialization() == null) {
Expression initialization = field.getInitialization();
if (initialization instanceof InstantiateType) {
initializeByConstructorCall(field, neededFor);
}
DiType typeToInitialize = getEffectiveFieldType(field);
// Sort setters in order to get deterministic behavior
List<Method> declaredMethods = Lists.newArrayList(field.getType().getClazz().getDeclaredMethods());
List<Method> declaredMethods = Lists.newArrayList(typeToInitialize.getClazz().getDeclaredMethods());
Collections.sort(declaredMethods, new Comparator<Method>() {
@Override
public int compare(Method o1, Method o2) {
@@ -455,7 +499,8 @@ public class DependencyInjectorGenerator {
}
private void initializeByConstructorCall(Field field, Field neededFor) {
DiType type = field.getType();
Expression initialization = field.getInitialization();
DiType type = ((InstantiateType) initialization).getType();
if (type.getClazz().isInterface()) {
throw new IllegalArgumentException("cannot instantiate interface: " + type.getClazz().getName() + " needed for " + neededFor);
@@ -16,7 +16,21 @@
package org.jetbrains.jet.di;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* @author abreslav
*/
interface Expression {}
interface Expression {
@NotNull
String renderAsCode();
@NotNull
Collection<DiType> getTypesToImport();
@Nullable
DiType getType();
}
@@ -30,7 +30,9 @@ class Field {
public static Field create(boolean isPublic, DiType type, String name, @Nullable Expression init) {
Field field = new Field(isPublic, type, name);
field.initialization = init;
if (init != null) {
field.setInitialization(init);
}
return field;
}
@@ -38,7 +40,7 @@ class Field {
private final String name;
private final boolean isPublic;
@Nullable
@NotNull
private Expression initialization;
private final List<SetterDependency> dependencies = Lists.newArrayList();
@@ -47,6 +49,7 @@ class Field {
this.isPublic = isPublic;
this.type = type;
this.name = name;
this.initialization = new InstantiateType(type);
}
public DiType getType() {
@@ -72,7 +75,7 @@ class Field {
return prefix + StringUtil.capitalize(getName());
}
@Nullable
@NotNull
public Expression getInitialization() {
return initialization;
}
@@ -16,14 +16,44 @@
package org.jetbrains.jet.di;
import com.google.common.collect.Lists;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
/**
* @author abreslav
*/
class GivenExpression implements Expression {
private final String expression;
private final Collection<DiType> typesToImport;
GivenExpression(String expression) {
public GivenExpression(@NotNull String expression) {
this(expression, Collections.<DiType>emptyList());
}
public GivenExpression(@NotNull String expression, @NotNull DiType... typesToImport) {
this(expression, Arrays.asList(typesToImport));
}
public GivenExpression(@NotNull String expression, @NotNull Class<?>... typesToImport) {
this(expression, convertClassesToDiTypes(typesToImport));
}
private static Collection<DiType> convertClassesToDiTypes(Class<?>[] typesToImport) {
Collection<DiType> types = Lists.newArrayList();
for (Class<?> aClass : typesToImport) {
types.add(new DiType(aClass));
}
return types;
}
public GivenExpression(@NotNull String expression, @NotNull Collection<DiType> typesToImport) {
this.expression = expression;
this.typesToImport = typesToImport;
}
public String getExpression() {
@@ -32,6 +62,22 @@ class GivenExpression implements Expression {
@Override
public String toString() {
return "given<" + expression + ">";
}
public DiType getType() {
return null;
}
@NotNull
@Override
public String renderAsCode() {
return expression;
}
@NotNull
@Override
public Collection<DiType> getTypesToImport() {
return typesToImport;
}
}
@@ -0,0 +1,59 @@
/*
* Copyright 2010-2012 JetBrains s.r.o.
*
* 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 org.jetbrains.jet.di;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* @author abreslav
*/
public class InstantiateType implements Expression {
private final DiType type;
public InstantiateType(@NotNull DiType type) {
this.type = type;
}
public InstantiateType(@NotNull Class<?> theClass) {
this(new DiType(theClass));
}
@NotNull
public DiType getType() {
return type;
}
@Override
public String toString() {
return "[Instantiate type: " + getType() + "]";
}
@NotNull
@Override
public String renderAsCode() {
throw new UnsupportedOperationException("This should be replaced by some concrete expression by the time this method is called");
}
@NotNull
@Override
public Collection<DiType> getTypesToImport() {
throw new UnsupportedOperationException("This should be replaced by some concrete expression by the time this method is called");
}
}
@@ -16,6 +16,11 @@
package org.jetbrains.jet.di;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Collections;
/**
* @author abreslav
*/
@@ -32,6 +37,24 @@ public class ParameterExpression implements Expression {
@Override
public String toString() {
return "parameter<" + parameter.getName() + ">";
}
@NotNull
@Override
public String renderAsCode() {
return parameter.getName();
}
@NotNull
@Override
public Collection<DiType> getTypesToImport() {
return Collections.emptyList();
}
@NotNull
@Override
public DiType getType() {
return parameter.getType();
}
}