From 182f6d9897dee60c26709610e10d445f4f101759 Mon Sep 17 00:00:00 2001 From: Stepan Koltsov Date: Wed, 28 Mar 2012 23:38:59 +0400 Subject: [PATCH] di: objects of generic types --- .../jet/di/DependencyInjectorGenerator.java | 90 +++++++++---- .../src/org/jetbrains/jet/di/DiType.java | 124 ++++++++++++++++++ .../src/org/jetbrains/jet/di/Field.java | 12 +- .../src/org/jetbrains/jet/di/Parameter.java | 6 +- 4 files changed, 199 insertions(+), 33 deletions(-) create mode 100644 injector-generator/src/org/jetbrains/jet/di/DiType.java diff --git a/injector-generator/src/org/jetbrains/jet/di/DependencyInjectorGenerator.java b/injector-generator/src/org/jetbrains/jet/di/DependencyInjectorGenerator.java index f5f9f269753..b080123c6f5 100644 --- a/injector-generator/src/org/jetbrains/jet/di/DependencyInjectorGenerator.java +++ b/injector-generator/src/org/jetbrains/jet/di/DependencyInjectorGenerator.java @@ -33,6 +33,7 @@ import java.io.PrintStream; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Type; import java.util.*; /** @@ -44,7 +45,7 @@ public class DependencyInjectorGenerator { private static final String LOCK_NAME = "__lock__"; private final boolean lazy; - private final Multimap, Field> typeToField = HashMultimap.create(); + private final Multimap typeToField = HashMultimap.create(); private final Set satisfied = Sets.newHashSet(); private final Set fields = Sets.newLinkedHashSet(); private final Set parameters = Sets.newLinkedHashSet(); @@ -119,25 +120,37 @@ public class DependencyInjectorGenerator { } public void addPublicParameter(Class type) { + addPublicParameter(new DiType(type)); + } + + public void addPublicParameter(DiType type) { addPublicParameter(type, true); } - public void addPublicParameter(Class type, boolean required) { + public void addPublicParameter(DiType type, boolean required) { addParameter(true, type, var(type), required); } public void addParameter(Class type) { + addParameter(DiType.fromReflectionType(type)); + } + + public void addParameter(DiType type) { addParameter(type, true); } public void addParameter(Class type, boolean required) { + addParameter(new DiType(type), required); + } + + public void addParameter(DiType type, boolean required) { addParameter(false, type, var(type), required); } - public void addParameter(boolean reexport, @NotNull Class type, @Nullable String name, boolean required) { + public void addParameter(boolean reexport, @NotNull DiType type, @Nullable String name, boolean required) { Field field = addField(reexport, type, name, null); Parameter parameter = new Parameter(type, name, field, required); parameters.add(parameter); @@ -147,14 +160,26 @@ public class DependencyInjectorGenerator { } public Field addPublicField(Class type) { + return addPublicField(new DiType(type)); + } + + public Field addPublicField(DiType type) { return addField(true, type, null, null); } public Field addField(Class type) { + return addField(new DiType(type)); + } + + public Field addField(DiType type) { return addField(false, type, null, null); } public Field addField(boolean isPublic, Class type, @Nullable String name, @Nullable Expression init) { + return addField(isPublic, new DiType(type), name, init); + } + + public Field addField(boolean isPublic, DiType type, @Nullable String name, @Nullable Expression init) { Field field = Field.create(isPublic, type, name == null ? var(type) : name, init); fields.add(field); typeToField.put(type, field); @@ -163,10 +188,10 @@ public class DependencyInjectorGenerator { private void generateImports(PrintStream out, String injectorPackageName) { for (Field field : fields) { - generateImportDirective(out, field.getType(), injectorPackageName); + generateImportDirectives(out, field.getType(), injectorPackageName); } for (Parameter parameter : parameters) { - generateImportDirective(out, parameter.getType(), injectorPackageName); + generateImportDirectives(out, parameter.getType(), injectorPackageName); } for (Parameter parameter : parameters) { if (parameter.isRequired()) { @@ -176,6 +201,13 @@ public class DependencyInjectorGenerator { } } + private void generateImportDirectives(PrintStream out, DiType type, String injectorPackageName) { + generateImportDirective(out, type.getClazz(), injectorPackageName); + for (DiType typeParameter : type.getTypeParameters()) { + generateImportDirectives(out, typeParameter, injectorPackageName); + } + } + private void generateImportDirective(PrintStream out, Class type, String injectorPackageName) { if (type.isPrimitive()) return; String importedPackageName = type.getPackage().getName(); @@ -248,7 +280,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()); + List postConstructMethods = getPostConstructMethods(field.getType().getClazz()); for (Method postConstruct : postConstructMethods) { out.println(indent + field.getName() + "." + postConstruct.getName() + "();"); } @@ -333,7 +365,7 @@ public class DependencyInjectorGenerator { } // Sort setters in order to get deterministic behavior - List declaredMethods = Lists.newArrayList(field.getType().getDeclaredMethods()); + List declaredMethods = Lists.newArrayList(field.getType().getClazz().getDeclaredMethods()); Collections.sort(declaredMethods, new Comparator() { @Override public int compare(Method o1, Method o2) { @@ -345,18 +377,18 @@ public class DependencyInjectorGenerator { || !method.getName().startsWith("set") || method.getParameterTypes().length != 1) continue; - Class parameterType = method.getParameterTypes()[0]; + Type parameterType = method.getGenericParameterTypes()[0]; - Field dependency = findDependencyOfType(parameterType, field + ": " + method + ": " + fields, field); + Field dependency = findDependencyOfType(DiType.fromReflectionType(parameterType), field + ": " + method + ": " + fields, field); field.getDependencies().add(new SetterDependency(field, method.getName(), dependency)); } } - private Field findDependencyOfType(Class parameterType, String errorMessage, Field neededFor) { + private Field findDependencyOfType(DiType parameterType, String errorMessage, Field neededFor) { List fields = Lists.newArrayList(); - for (Map.Entry, Field> entry : typeToField.entries()) { + for (Map.Entry entry : typeToField.entries()) { if (parameterType.isAssignableFrom(entry.getKey())) { fields.add(entry.getValue()); } @@ -365,7 +397,7 @@ public class DependencyInjectorGenerator { Field dependency; if (fields.isEmpty()) { - if (parameterType.isPrimitive() || parameterType.getPackage().getName().equals("java.lang")) { + if (parameterType.getClazz().isPrimitive() || parameterType.getClazz().getPackage().getName().equals("java.lang")) { throw new IllegalArgumentException( "cannot declare magic field of type " + parameterType + ": " + errorMessage); } @@ -383,35 +415,45 @@ public class DependencyInjectorGenerator { } private void initializeByConstructorCall(Field field, Field neededFor) { - Class type = field.getType(); + DiType type = field.getType(); - if (type.isInterface()) { - throw new IllegalArgumentException("cannot instantiate interface: " + type.getName() + " needed for " + neededFor); + if (type.getClazz().isInterface()) { + throw new IllegalArgumentException("cannot instantiate interface: " + type.getClazz().getName() + " needed for " + neededFor); } - if (Modifier.isAbstract(type.getModifiers())) { - throw new IllegalArgumentException("cannot instantiate abstract class: " + type.getName() + " needed for " + neededFor); + if (Modifier.isAbstract(type.getClazz().getModifiers())) { + throw new IllegalArgumentException("cannot instantiate abstract class: " + type.getClazz().getName() + " needed for " + neededFor); } + // Note: projections are not computed here + // Look for constructor - Constructor[] constructors = type.getConstructors(); + Constructor[] constructors = type.getClazz().getConstructors(); if (constructors.length == 0 || !Modifier.isPublic(constructors[0].getModifiers())) { - throw new IllegalArgumentException("No constructor: " + type.getName() + " needed for " + neededFor); + throw new IllegalArgumentException("No constructor: " + type.getClazz().getName() + " needed for " + neededFor); } Constructor constructor = constructors[0]; // Find arguments ConstructorCall dependency = new ConstructorCall(constructor); - Class[] parameterTypes = constructor.getParameterTypes(); - for (Class parameterType : parameterTypes) { - Field fieldForParameter = findDependencyOfType(parameterType, "constructor: " + constructor + ", parameter: " + parameterType, field); + Type[] parameterTypes = constructor.getGenericParameterTypes(); + for (Type parameterType : parameterTypes) { + Field fieldForParameter = findDependencyOfType(DiType.fromReflectionType(parameterType), "constructor: " + constructor + ", parameter: " + parameterType, field); dependency.getConstructorArguments().add(fieldForParameter); } field.setInitialization(dependency); } - private String var(Class theClass) { - return StringUtil.decapitalize(theClass.getSimpleName()); + private String var(@NotNull DiType type) { + StringBuilder sb = new StringBuilder(); + sb.append(StringUtil.decapitalize(type.getClazz().getSimpleName())); + if (type.getTypeParameters().size() > 0) { + sb.append("Of"); + } + for (DiType parameter : type.getTypeParameters()) { + sb.append(StringUtil.capitalize(var(parameter))); + } + return sb.toString(); } private void generateMakeFunction(PrintStream out) { diff --git a/injector-generator/src/org/jetbrains/jet/di/DiType.java b/injector-generator/src/org/jetbrains/jet/di/DiType.java new file mode 100644 index 00000000000..ab4e2c37bba --- /dev/null +++ b/injector-generator/src/org/jetbrains/jet/di/DiType.java @@ -0,0 +1,124 @@ +/* + * 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 com.google.common.collect.Lists; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * @author Stepan Koltsov + */ +class DiType { + @NotNull + private final Class clazz; + @NotNull + private final List typeParameters; + + public DiType(@NotNull Class clazz, @NotNull List typeParameters) { + this.clazz = clazz; + this.typeParameters = typeParameters; + + if (clazz.getTypeParameters().length != typeParameters.size()) { + throw new IllegalStateException("type parameter count mismatch: " + clazz + ", " + typeParameters); + } + } + + public DiType(@NotNull Class clazz) { + this(clazz, Collections.emptyList()); + } + + @NotNull + public Class getClazz() { + return clazz; + } + + @NotNull + public List getTypeParameters() { + return typeParameters; + } + + public boolean isAssignableFrom(@NotNull DiType that) { + if (!this.clazz.isAssignableFrom(that.clazz)) { + return false; + } + // TODO: following code incorrectly assumes that each type parameter + // is projected into type parameter in the same position + return this.typeParameters.equals(that.typeParameters); + } + + @NotNull + public String getSimpleName() { + StringBuilder sb = new StringBuilder(); + sb.append(clazz.getSimpleName()); + Iterator typeParameterIterator = typeParameters.iterator(); + if (typeParameterIterator.hasNext()) { + sb.append("<"); + if (typeParameterIterator.hasNext()) { + sb.append(typeParameterIterator.next().getSimpleName()); + } + while (typeParameterIterator.hasNext()) { + sb.append(", "); + sb.append(typeParameterIterator.next().getSimpleName()); + } + sb.append(">"); + } + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DiType type = (DiType) o; + + if (!clazz.equals(type.clazz)) return false; + if (!typeParameters.equals(type.typeParameters)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = clazz.hashCode(); + result = 31 * result + typeParameters.hashCode(); + return result; + } + + @NotNull + public static DiType fromReflectionType(@NotNull Type type) { + if (type instanceof Class) { + return new DiType((Class) type); + } + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Class owner = (Class) parameterizedType.getRawType(); + List diTypeParameters = Lists.newArrayList(); + for (Type typeParameter : parameterizedType.getActualTypeArguments()) { + diTypeParameters.add(fromReflectionType(typeParameter)); + } + return new DiType(owner, diTypeParameters); + } + throw new IllegalArgumentException("unsupported type: " + type); + } +} diff --git a/injector-generator/src/org/jetbrains/jet/di/Field.java b/injector-generator/src/org/jetbrains/jet/di/Field.java index 6dd96064c67..7d38ee13343 100644 --- a/injector-generator/src/org/jetbrains/jet/di/Field.java +++ b/injector-generator/src/org/jetbrains/jet/di/Field.java @@ -28,13 +28,13 @@ import java.util.List; */ class Field { - public static Field create(boolean isPublic, Class type, String name, @Nullable Expression init) { + public static Field create(boolean isPublic, DiType type, String name, @Nullable Expression init) { Field field = new Field(isPublic, type, name); field.initialization = init; return field; } - private final Class type; + private final DiType type; private final String name; private final boolean isPublic; @@ -43,13 +43,13 @@ class Field { private final List dependencies = Lists.newArrayList(); - Field(boolean isPublic, Class type, String name) { + Field(boolean isPublic, DiType type, String name) { this.isPublic = isPublic; this.type = type; this.name = name; } - public Class getType() { + public DiType getType() { return type; } @@ -58,12 +58,12 @@ class Field { } public String getTypeName() { - return type.getSimpleName(); + return type.getClazz().getSimpleName(); } public String getGetterName() { String prefix; - if (getType() == boolean.class || getType() == Boolean.class) { + if (getType().getClazz() == boolean.class || getType().getClazz() == Boolean.class) { prefix = "is"; } else { diff --git a/injector-generator/src/org/jetbrains/jet/di/Parameter.java b/injector-generator/src/org/jetbrains/jet/di/Parameter.java index 25cc125b2fa..7a83042c5c5 100644 --- a/injector-generator/src/org/jetbrains/jet/di/Parameter.java +++ b/injector-generator/src/org/jetbrains/jet/di/Parameter.java @@ -20,19 +20,19 @@ package org.jetbrains.jet.di; * @author abreslav */ class Parameter { - private final Class type; + private final DiType type; private final String name; private final Field field; private final boolean required; - Parameter(Class type, String name, Field field, boolean required) { + Parameter(DiType type, String name, Field field, boolean required) { this.type = type; this.name = name; this.field = field; this.required = required; } - public Class getType() { + public DiType getType() { return type; }