From af882cbd51f010d2f46845d2f136f28ab282ce41 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Mon, 2 Jul 2012 20:31:07 +0400 Subject: [PATCH] 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 --- .../org/jetbrains/jet/di/ConstructorCall.java | 22 ++++++- .../jet/di/DependencyInjectorGenerator.java | 65 ++++++++++++++++--- .../src/org/jetbrains/jet/di/Expression.java | 16 ++++- .../src/org/jetbrains/jet/di/Field.java | 9 ++- .../org/jetbrains/jet/di/GivenExpression.java | 48 +++++++++++++- .../org/jetbrains/jet/di/InstantiateType.java | 59 +++++++++++++++++ .../jetbrains/jet/di/ParameterExpression.java | 23 +++++++ 7 files changed, 226 insertions(+), 16 deletions(-) create mode 100644 injector-generator/src/org/jetbrains/jet/di/InstantiateType.java diff --git a/injector-generator/src/org/jetbrains/jet/di/ConstructorCall.java b/injector-generator/src/org/jetbrains/jet/di/ConstructorCall.java index a424938170b..1681afdf6cc 100644 --- a/injector-generator/src/org/jetbrains/jet/di/ConstructorCall.java +++ b/injector-generator/src/org/jetbrains/jet/di/ConstructorCall.java @@ -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 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 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 getTypesToImport() { + return Collections.singletonList(new DiType(constructor.getDeclaringClass())); + } + + @NotNull + public DiType getType() { + return new DiType(constructor.getDeclaringClass()); + } } diff --git a/injector-generator/src/org/jetbrains/jet/di/DependencyInjectorGenerator.java b/injector-generator/src/org/jetbrains/jet/di/DependencyInjectorGenerator.java index 28d3fb92dc8..e2f64a11d72 100644 --- a/injector-generator/src/org/jetbrains/jet/di/DependencyInjectorGenerator.java +++ b/injector-generator/src/org/jetbrains/jet/di/DependencyInjectorGenerator.java @@ -55,6 +55,9 @@ public class DependencyInjectorGenerator { private final Set backsParameter = Sets.newHashSet(); + private final List> implementsList = Lists.newArrayList(); + private final Collection> 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> 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 postConstructMethods = getPostConstructMethods(field.getType().getClazz()); + List 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 preDestroyMethods = getPreDestroyMethods(field.getType().getClazz()); + List 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 declaredMethods = Lists.newArrayList(field.getType().getClazz().getDeclaredMethods()); + List declaredMethods = Lists.newArrayList(typeToInitialize.getClazz().getDeclaredMethods()); Collections.sort(declaredMethods, new Comparator() { @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); diff --git a/injector-generator/src/org/jetbrains/jet/di/Expression.java b/injector-generator/src/org/jetbrains/jet/di/Expression.java index 87fbb484e96..bb66cb18c68 100644 --- a/injector-generator/src/org/jetbrains/jet/di/Expression.java +++ b/injector-generator/src/org/jetbrains/jet/di/Expression.java @@ -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 getTypesToImport(); + + @Nullable + DiType getType(); +} diff --git a/injector-generator/src/org/jetbrains/jet/di/Field.java b/injector-generator/src/org/jetbrains/jet/di/Field.java index 7d38ee13343..8a625ba1081 100644 --- a/injector-generator/src/org/jetbrains/jet/di/Field.java +++ b/injector-generator/src/org/jetbrains/jet/di/Field.java @@ -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 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; } diff --git a/injector-generator/src/org/jetbrains/jet/di/GivenExpression.java b/injector-generator/src/org/jetbrains/jet/di/GivenExpression.java index 884cbca03cf..5ff4617ca8a 100644 --- a/injector-generator/src/org/jetbrains/jet/di/GivenExpression.java +++ b/injector-generator/src/org/jetbrains/jet/di/GivenExpression.java @@ -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 typesToImport; - GivenExpression(String expression) { + + public GivenExpression(@NotNull String expression) { + this(expression, Collections.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 convertClassesToDiTypes(Class[] typesToImport) { + Collection types = Lists.newArrayList(); + for (Class aClass : typesToImport) { + types.add(new DiType(aClass)); + } + return types; + } + + public GivenExpression(@NotNull String expression, @NotNull Collection 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 getTypesToImport() { + return typesToImport; + } } diff --git a/injector-generator/src/org/jetbrains/jet/di/InstantiateType.java b/injector-generator/src/org/jetbrains/jet/di/InstantiateType.java new file mode 100644 index 00000000000..2cc6532d1a1 --- /dev/null +++ b/injector-generator/src/org/jetbrains/jet/di/InstantiateType.java @@ -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 getTypesToImport() { + throw new UnsupportedOperationException("This should be replaced by some concrete expression by the time this method is called"); + } +} diff --git a/injector-generator/src/org/jetbrains/jet/di/ParameterExpression.java b/injector-generator/src/org/jetbrains/jet/di/ParameterExpression.java index aefd1f39b5a..2bfa282afca 100644 --- a/injector-generator/src/org/jetbrains/jet/di/ParameterExpression.java +++ b/injector-generator/src/org/jetbrains/jet/di/ParameterExpression.java @@ -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 getTypesToImport() { + return Collections.emptyList(); + } + + @NotNull + @Override + public DiType getType() { + return parameter.getType(); + } }