This project has retired. For details please refer to its Attic page.
Source code
001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.reef.tang.implementation.java;
020
021import org.apache.reef.tang.ExternalConstructor;
022import org.apache.reef.tang.InjectionFuture;
023import org.apache.reef.tang.annotations.*;
024import org.apache.reef.tang.exceptions.ClassHierarchyException;
025import org.apache.reef.tang.implementation.types.*;
026import org.apache.reef.tang.types.*;
027import org.apache.reef.tang.util.MonotonicSet;
028import org.apache.reef.tang.util.ReflectionUtilities;
029
030import javax.inject.Inject;
031import java.lang.annotation.Annotation;
032import java.lang.reflect.Constructor;
033import java.lang.reflect.Modifier;
034import java.lang.reflect.ParameterizedType;
035import java.lang.reflect.Type;
036import java.util.ArrayList;
037import java.util.Collection;
038import java.util.List;
039import java.util.Set;
040
041public class JavaNodeFactory {
042
043  @SuppressWarnings("unchecked")
044  static <T> ClassNodeImpl<T> createClassNode(Node parent, Class<T> clazz) throws ClassHierarchyException {
045    final boolean injectable;
046    final boolean unit = clazz.isAnnotationPresent(Unit.class);
047    final String simpleName = ReflectionUtilities.getSimpleName(clazz);
048    final String fullName = ReflectionUtilities.getFullName(clazz);
049    final boolean isStatic = Modifier.isStatic(clazz.getModifiers());
050    final boolean parentIsUnit = ((parent instanceof ClassNode) && !isStatic) ?
051        ((ClassNode<?>) parent).isUnit() : false;
052
053    if (clazz.isLocalClass() || clazz.isMemberClass()) {
054      if (!isStatic) {
055        if (parent instanceof ClassNode) {
056          injectable = ((ClassNode<?>) parent).isUnit();
057        } else {
058          injectable = false;
059        }
060      } else {
061        injectable = true;
062      }
063    } else {
064      injectable = true;
065    }
066
067    boolean foundNonStaticInnerClass = false;
068    for (Class<?> c : clazz.getDeclaredClasses()) {
069      if (!Modifier.isStatic(c.getModifiers())) {
070        foundNonStaticInnerClass = true;
071      }
072    }
073
074    if (unit && !foundNonStaticInnerClass) {
075      throw new ClassHierarchyException("Class " + ReflectionUtilities.getFullName(clazz) + " has an @Unit annotation, but no non-static inner classes.  Such @Unit annotations would have no effect, and are therefore disallowed.");
076    }
077
078    Constructor<T>[] constructors = (Constructor<T>[]) clazz
079        .getDeclaredConstructors();
080    MonotonicSet<ConstructorDef<T>> injectableConstructors = new MonotonicSet<>();
081    ArrayList<ConstructorDef<T>> allConstructors = new ArrayList<>();
082    for (int k = 0; k < constructors.length; k++) {
083      boolean constructorAnnotatedInjectable = (constructors[k]
084          .getAnnotation(Inject.class) != null);
085      if (constructorAnnotatedInjectable && constructors[k].isSynthetic()) {
086        // Not sure if we *can* unit test this one.
087        throw new ClassHierarchyException(
088            "Synthetic constructor was annotated with @Inject!");
089      }
090      if (parentIsUnit && (constructorAnnotatedInjectable || constructors[k].getParameterTypes().length != 1)) {
091        throw new ClassHierarchyException(
092            "Detected explicit constructor in class enclosed in @Unit " + fullName + "  Such constructors are disallowed.");
093      }
094      boolean constructorInjectable = constructorAnnotatedInjectable || parentIsUnit;
095      // ConstructorDef's constructor checks for duplicate
096      // parameters
097      // The injectableConstructors set checks for ambiguous
098      // boundConstructors.
099      ConstructorDef<T> def = JavaNodeFactory.createConstructorDef(injectable,
100          constructors[k], constructorAnnotatedInjectable);
101      if (constructorInjectable) {
102        if (injectableConstructors.contains(def)) {
103          throw new ClassHierarchyException(
104              "Ambiguous boundConstructors detected in class " + clazz + ": "
105                  + def + " differs from some other" + " constructor only "
106                  + "by parameter order.");
107        } else {
108          injectableConstructors.add(def);
109        }
110      }
111      allConstructors.add(def);
112    }
113    final String defaultImplementation;
114    if (clazz.isAnnotationPresent(DefaultImplementation.class)) {
115      DefaultImplementation defaultImpl
116          = clazz.getAnnotation(DefaultImplementation.class);
117      Class<?> defaultImplementationClazz = defaultImpl.value();
118      if (defaultImplementationClazz.equals(Void.class)) {
119        defaultImplementation = defaultImpl.name();
120        // XXX check isAssignableFrom, other type problems here.
121      } else {
122        if (!clazz.isAssignableFrom(defaultImplementationClazz)) {
123          throw new ClassHierarchyException(clazz
124              + " declares its default implementation to be non-subclass "
125              + defaultImplementationClazz);
126        }
127        defaultImplementation = ReflectionUtilities.getFullName(defaultImplementationClazz);
128      }
129    } else {
130      defaultImplementation = null;
131    }
132
133    return new ClassNodeImpl<T>(parent, simpleName, fullName, unit, injectable,
134        ExternalConstructor.class.isAssignableFrom(clazz),
135        injectableConstructors.toArray(new ConstructorDefImpl[0]),
136        allConstructors.toArray(new ConstructorDefImpl[0]), defaultImplementation);
137  }
138
139  /**
140   * XXX: This method assumes that all generic types have exactly one type parameter.
141   */
142  public static <T> NamedParameterNode<T> createNamedParameterNode(Node parent,
143                                                                   Class<? extends Name<T>> clazz, Type argClass) throws ClassHierarchyException {
144
145    Class<?> argRawClass = ReflectionUtilities.getRawClass(argClass);
146
147    final boolean isSet = argRawClass.equals(Set.class);
148    final boolean isList = argRawClass.equals(List.class);
149
150
151    if (isSet || isList) {
152      argClass = ReflectionUtilities.getInterfaceTarget(Collection.class, argClass);
153      argRawClass = ReflectionUtilities.getRawClass(argClass);
154    }
155
156    final String simpleName = ReflectionUtilities.getSimpleName(clazz);
157    final String fullName = ReflectionUtilities.getFullName(clazz);
158    final String fullArgName = ReflectionUtilities.getFullName(argClass);
159    final String simpleArgName = ReflectionUtilities.getSimpleName(argClass);
160
161
162    final NamedParameter namedParameter = clazz.getAnnotation(NamedParameter.class);
163
164    if (namedParameter == null) {
165      throw new IllegalStateException("Got name without named parameter post-validation!");
166    }
167    final boolean hasStringDefault, hasClassDefault, hasStringSetDefault, hasClassSetDefault;
168
169    int default_count = 0;
170    if (!namedParameter.default_value().isEmpty()) {
171      hasStringDefault = true;
172      default_count++;
173    } else {
174      hasStringDefault = false;
175    }
176    if (namedParameter.default_class() != Void.class) {
177      hasClassDefault = true;
178      default_count++;
179    } else {
180      hasClassDefault = false;
181    }
182    if (namedParameter.default_values() != null && namedParameter.default_values().length > 0) {
183      hasStringSetDefault = true;
184      default_count++;
185    } else {
186      hasStringSetDefault = false;
187    }
188    if (namedParameter.default_classes() != null && namedParameter.default_classes().length > 0) {
189      hasClassSetDefault = true;
190      default_count++;
191    } else {
192      hasClassSetDefault = false;
193    }
194    if (default_count > 1) {
195      throw new ClassHierarchyException("Named parameter " + fullName + " defines more than one of default_value, default_class, default_values and default_classes");
196    }
197
198    final String[] defaultInstanceAsStrings;
199
200    if (default_count == 0) {
201      defaultInstanceAsStrings = new String[]{};
202    } else if (hasClassDefault) {
203      final Class<?> default_class = namedParameter.default_class();
204      assertIsSubclassOf(clazz, default_class, argClass);
205      defaultInstanceAsStrings = new String[]{ReflectionUtilities.getFullName(default_class)};
206    } else if (hasStringDefault) {
207      // Don't know if the string is a class or literal here, so don't bother validating.
208      defaultInstanceAsStrings = new String[]{namedParameter.default_value()};
209    } else if (hasClassSetDefault) {
210      Class<?>[] clzs = namedParameter.default_classes();
211      defaultInstanceAsStrings = new String[clzs.length];
212      for (int i = 0; i < clzs.length; i++) {
213        assertIsSubclassOf(clazz, clzs[i], argClass);
214        defaultInstanceAsStrings[i] = ReflectionUtilities.getFullName(clzs[i]);
215      }
216    } else if (hasStringSetDefault) {
217      defaultInstanceAsStrings = namedParameter.default_values();
218    } else {
219      throw new IllegalStateException();
220    }
221
222    final String documentation = namedParameter.doc();
223
224    final String shortName = namedParameter.short_name().isEmpty()
225        ? null : namedParameter.short_name();
226
227    return new NamedParameterNodeImpl<>(parent, simpleName, fullName,
228        fullArgName, simpleArgName, isSet, isList, documentation, shortName, defaultInstanceAsStrings);
229  }
230
231  private static void assertIsSubclassOf(Class<?> named_parameter, Class<?> default_class,
232                                         Type argClass) {
233    boolean isSubclass = false;
234    boolean isGenericSubclass = false;
235    Class<?> argRawClass = ReflectionUtilities.getRawClass(argClass);
236
237    // Note: We intentionally strip the raw type information here.  The reason is to handle
238    // EventHandler-style patterns and collections.
239
240    /// If we have a Name that takes EventHandler<A>, we want to be able to pass in an EventHandler<Object>.
241
242    for (final Type c : ReflectionUtilities.classAndAncestors(default_class)) {
243      if (ReflectionUtilities.getRawClass(c).equals(argRawClass)) {
244        isSubclass = true;
245        if (argClass instanceof ParameterizedType &&
246            c instanceof ParameterizedType) {
247          ParameterizedType argPt = (ParameterizedType) argClass;
248          ParameterizedType defaultPt = (ParameterizedType) c;
249
250          Class<?> rawDefaultParameter = ReflectionUtilities.getRawClass(defaultPt.getActualTypeArguments()[0]);
251          Class<?> rawArgParameter = ReflectionUtilities.getRawClass(argPt.getActualTypeArguments()[0]);
252
253          for (final Type d : ReflectionUtilities.classAndAncestors(argPt.getActualTypeArguments()[0])) {
254            if (ReflectionUtilities.getRawClass(d).equals(rawDefaultParameter)) {
255              isGenericSubclass = true;
256            }
257          }
258          for (final Type d : ReflectionUtilities.classAndAncestors(defaultPt.getActualTypeArguments()[0])) {
259            if (ReflectionUtilities.getRawClass(d).equals(rawArgParameter)) {
260              isGenericSubclass = true;
261            }
262          }
263        } else {
264          isGenericSubclass = true;
265        }
266      }
267    }
268
269    if (!(isSubclass)) {
270      throw new ClassHierarchyException(named_parameter + " defines a default class "
271          + ReflectionUtilities.getFullName(default_class) + " with a raw type that does not extend of its target's raw type " + argRawClass);
272    }
273    if (!(isGenericSubclass)) {
274      throw new ClassHierarchyException(named_parameter + " defines a default class "
275          + ReflectionUtilities.getFullName(default_class) + " with a type that does not extend its target's type " + argClass);
276    }
277  }
278
279  public static PackageNode createRootPackageNode() {
280    return new PackageNodeImpl();
281  }
282
283  private static <T> ConstructorDef<T> createConstructorDef(
284      boolean isClassInjectionCandidate, Constructor<T> constructor,
285      boolean injectable) throws ClassHierarchyException {
286    // We don't support injection of non-static member classes with @Inject
287    // annotations.
288    if (injectable && !isClassInjectionCandidate) {
289      throw new ClassHierarchyException("Cannot @Inject non-static member class unless the enclosing class an @Unit.  Nested class is:"
290          + ReflectionUtilities.getFullName(constructor.getDeclaringClass()));
291    }
292    // TODO: When we use paramTypes below, we strip generic parameters.  Is that OK?
293    Class<?>[] paramTypes = constructor.getParameterTypes();
294    Type[] genericParamTypes = constructor.getGenericParameterTypes();
295    Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
296    if (paramTypes.length != paramAnnotations.length) {
297      throw new IllegalStateException();
298    }
299    ConstructorArg[] args = new ConstructorArg[genericParamTypes.length];
300    for (int i = 0; i < genericParamTypes.length; i++) {
301      // If this parameter is an injection future, unwrap the target class,
302      // and remember by setting isFuture to true.
303      final Type type;
304      final boolean isFuture;
305      if (InjectionFuture.class.isAssignableFrom(paramTypes[i])) {
306        type = ReflectionUtilities.getInterfaceTarget(InjectionFuture.class, genericParamTypes[i]);
307        isFuture = true;
308      } else {
309        type = paramTypes[i];
310        isFuture = false;
311      }
312      // Make node of the named parameter annotation (if any).
313      Parameter named = null;
314      for (int j = 0; j < paramAnnotations[i].length; j++) {
315        Annotation annotation = paramAnnotations[i][j];
316        if (annotation instanceof Parameter) {
317          if ((!isClassInjectionCandidate) || !injectable) {
318            throw new ClassHierarchyException(constructor + " is not injectable, but it has an @Parameter annotation.");
319          }
320          named = (Parameter) annotation;
321        }
322      }
323      args[i] = new ConstructorArgImpl(
324          ReflectionUtilities.getFullName(type), named == null ? null
325          : ReflectionUtilities.getFullName(named.value()),
326          isFuture);
327    }
328    return new ConstructorDefImpl<T>(
329        ReflectionUtilities.getFullName(constructor.getDeclaringClass()),
330        args, injectable);
331  }
332
333}