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 final class JavaNodeFactory {
042
043  @SuppressWarnings("unchecked")
044  static <T> ClassNodeImpl<T> createClassNode(final Node parent, final 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 && ((ClassNode<?>) parent).isUnit();
051
052    if (clazz.isLocalClass() || clazz.isMemberClass()) {
053      if (!isStatic) {
054        if (parent instanceof ClassNode) {
055          injectable = ((ClassNode<?>) parent).isUnit();
056        } else {
057          injectable = false;
058        }
059      } else {
060        injectable = true;
061      }
062    } else {
063      injectable = true;
064    }
065
066    boolean foundNonStaticInnerClass = false;
067    for (final Class<?> c : clazz.getDeclaredClasses()) {
068      if (!Modifier.isStatic(c.getModifiers())) {
069        foundNonStaticInnerClass = true;
070      }
071    }
072
073    if (unit && !foundNonStaticInnerClass) {
074      throw new ClassHierarchyException("Class " + ReflectionUtilities.getFullName(clazz) +
075          " has an @Unit annotation, but no non-static inner classes. " +
076          " Such @Unit annotations would have no effect, and are therefore disallowed.");
077    }
078
079    final Constructor<T>[] constructors = (Constructor<T>[]) clazz
080        .getDeclaredConstructors();
081    final MonotonicSet<ConstructorDef<T>> injectableConstructors = new MonotonicSet<>();
082    final ArrayList<ConstructorDef<T>> allConstructors = new ArrayList<>();
083    for (int k = 0; k < constructors.length; k++) {
084      final boolean constructorAnnotatedInjectable = constructors[k].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 +
093                "  Such constructors are disallowed.");
094      }
095      final boolean constructorInjectable = constructorAnnotatedInjectable || parentIsUnit;
096      // ConstructorDef's constructor checks for duplicate
097      // parameters
098      // The injectableConstructors set checks for ambiguous
099      // boundConstructors.
100      final ConstructorDef<T> def = JavaNodeFactory.createConstructorDef(injectable,
101          constructors[k], constructorAnnotatedInjectable);
102      if (constructorInjectable) {
103        if (injectableConstructors.contains(def)) {
104          throw new ClassHierarchyException(
105              "Ambiguous boundConstructors detected in class " + clazz + ": "
106                  + def + " differs from some other" + " constructor only "
107                  + "by parameter order.");
108        } else {
109          injectableConstructors.add(def);
110        }
111      }
112      allConstructors.add(def);
113    }
114    final String defaultImplementation;
115    if (clazz.isAnnotationPresent(DefaultImplementation.class)) {
116      final DefaultImplementation defaultImpl
117          = clazz.getAnnotation(DefaultImplementation.class);
118      final Class<?> defaultImplementationClazz = defaultImpl.value();
119      if (defaultImplementationClazz.equals(Void.class)) {
120        defaultImplementation = defaultImpl.name();
121        // XXX check isAssignableFrom, other type problems here.
122      } else {
123        if (!clazz.isAssignableFrom(defaultImplementationClazz)) {
124          throw new ClassHierarchyException(clazz
125              + " declares its default implementation to be non-subclass "
126              + defaultImplementationClazz);
127        }
128        defaultImplementation = ReflectionUtilities.getFullName(defaultImplementationClazz);
129      }
130    } else {
131      defaultImplementation = null;
132    }
133
134    return new ClassNodeImpl<T>(parent, simpleName, fullName, unit, injectable,
135        ExternalConstructor.class.isAssignableFrom(clazz),
136        injectableConstructors.toArray(new ConstructorDefImpl[0]),
137        allConstructors.toArray(new ConstructorDefImpl[0]), defaultImplementation);
138  }
139
140  /**
141   * XXX: This method assumes that all generic types have exactly one type parameter.
142   */
143  public static <T> NamedParameterNode<T> createNamedParameterNode(final Node parent,
144                                                                   final Class<? extends Name<T>> clazz,
145                                                                   final Type argClass)
146      throws ClassHierarchyException {
147
148    Class<?> argRawClass = ReflectionUtilities.getRawClass(argClass);
149
150    final boolean isSet = argRawClass.equals(Set.class);
151    final boolean isList = argRawClass.equals(List.class);
152
153    final Type argClazz;
154
155    if (isSet || isList) {
156      argClazz = ReflectionUtilities.getInterfaceTarget(Collection.class, argClass);
157    } else {
158      argClazz = argClass;
159    }
160
161    final String simpleName = ReflectionUtilities.getSimpleName(clazz);
162    final String fullName = ReflectionUtilities.getFullName(clazz);
163    final String fullArgName = ReflectionUtilities.getFullName(argClazz);
164    final String simpleArgName = ReflectionUtilities.getSimpleName(argClazz);
165
166
167    final NamedParameter namedParameter = clazz.getAnnotation(NamedParameter.class);
168
169    if (namedParameter == null) {
170      throw new IllegalStateException("Got name without named parameter post-validation!");
171    }
172    final boolean hasStringDefault, hasClassDefault, hasStringSetDefault, hasClassSetDefault;
173
174    int defaultCount = 0;
175    if (namedParameter.default_value().equals(NamedParameter.REEF_UNINITIALIZED_VALUE)) {
176      hasStringDefault = false;
177    } else {
178      hasStringDefault = true;
179      defaultCount++;
180    }
181    if (namedParameter.default_class() != Void.class) {
182      hasClassDefault = true;
183      defaultCount++;
184    } else {
185      hasClassDefault = false;
186    }
187    if (namedParameter.default_values() != null && namedParameter.default_values().length > 0) {
188      hasStringSetDefault = true;
189      defaultCount++;
190    } else {
191      hasStringSetDefault = false;
192    }
193    if (namedParameter.default_classes() != null && namedParameter.default_classes().length > 0) {
194      hasClassSetDefault = true;
195      defaultCount++;
196    } else {
197      hasClassSetDefault = false;
198    }
199    if (defaultCount > 1) {
200      throw new ClassHierarchyException("Named parameter " + fullName +
201          " defines more than one of default_value, default_class, default_values and default_classes");
202    }
203
204    final String[] defaultInstanceAsStrings;
205
206    if (defaultCount == 0) {
207      defaultInstanceAsStrings = new String[]{};
208    } else if (hasClassDefault) {
209      final Class<?> defaultClass = namedParameter.default_class();
210      assertIsSubclassOf(clazz, defaultClass, argClazz);
211      defaultInstanceAsStrings = new String[]{ReflectionUtilities.getFullName(defaultClass)};
212    } else if (hasStringDefault) {
213      // Don't know if the string is a class or literal here, so don't bother validating.
214      defaultInstanceAsStrings = new String[]{namedParameter.default_value()};
215    } else if (hasClassSetDefault) {
216      final Class<?>[] clzs = namedParameter.default_classes();
217      defaultInstanceAsStrings = new String[clzs.length];
218      for (int i = 0; i < clzs.length; i++) {
219        assertIsSubclassOf(clazz, clzs[i], argClazz);
220        defaultInstanceAsStrings[i] = ReflectionUtilities.getFullName(clzs[i]);
221      }
222    } else if (hasStringSetDefault) {
223      defaultInstanceAsStrings = namedParameter.default_values();
224    } else {
225      throw new IllegalStateException();
226    }
227
228    final String documentation = namedParameter.doc();
229
230    final String shortName = namedParameter.short_name().isEmpty()
231        ? null : namedParameter.short_name();
232
233    return new NamedParameterNodeImpl<>(parent, simpleName, fullName,
234        fullArgName, simpleArgName, isSet, isList, documentation, shortName, defaultInstanceAsStrings);
235  }
236
237  private static void assertIsSubclassOf(final Class<?> namedParameter, final Class<?> defaultClass,
238                                         final Type argClass) {
239    boolean isSubclass = false;
240    boolean isGenericSubclass = false;
241    final Class<?> argRawClass = ReflectionUtilities.getRawClass(argClass);
242
243    // Note: We intentionally strip the raw type information here.  The reason is to handle
244    // EventHandler-style patterns and collections.
245
246    /// If we have a Name that takes EventHandler<A>, we want to be able to pass in an EventHandler<Object>.
247
248    for (final Type c : ReflectionUtilities.classAndAncestors(defaultClass)) {
249      if (ReflectionUtilities.getRawClass(c).equals(argRawClass)) {
250        isSubclass = true;
251        if (argClass instanceof ParameterizedType &&
252            c instanceof ParameterizedType) {
253          final ParameterizedType argPt = (ParameterizedType) argClass;
254          final ParameterizedType defaultPt = (ParameterizedType) c;
255
256          final Class<?> rawDefaultParameter = ReflectionUtilities.getRawClass(defaultPt.getActualTypeArguments()[0]);
257          final Class<?> rawArgParameter = ReflectionUtilities.getRawClass(argPt.getActualTypeArguments()[0]);
258
259          for (final Type d : ReflectionUtilities.classAndAncestors(argPt.getActualTypeArguments()[0])) {
260            if (ReflectionUtilities.getRawClass(d).equals(rawDefaultParameter)) {
261              isGenericSubclass = true;
262            }
263          }
264          for (final Type d : ReflectionUtilities.classAndAncestors(defaultPt.getActualTypeArguments()[0])) {
265            if (ReflectionUtilities.getRawClass(d).equals(rawArgParameter)) {
266              isGenericSubclass = true;
267            }
268          }
269        } else {
270          isGenericSubclass = true;
271        }
272      }
273    }
274
275    if (!isSubclass) {
276      throw new ClassHierarchyException(namedParameter + " defines a default class "
277          + ReflectionUtilities.getFullName(defaultClass)
278          + " with a raw type that does not extend of its target's raw type " + argRawClass);
279    }
280    if (!isGenericSubclass) {
281      throw new ClassHierarchyException(namedParameter + " defines a default class "
282          + ReflectionUtilities.getFullName(defaultClass)
283          + " with a type that does not extend its target's type " + argClass);
284    }
285  }
286
287  public static PackageNode createRootPackageNode() {
288    return new PackageNodeImpl();
289  }
290
291  private static <T> ConstructorDef<T> createConstructorDef(
292      final boolean isClassInjectionCandidate, final Constructor<T> constructor,
293      final boolean injectable) throws ClassHierarchyException {
294    // We don't support injection of non-static member classes with @Inject
295    // annotations.
296    if (injectable && !isClassInjectionCandidate) {
297      throw new ClassHierarchyException("Cannot @Inject non-static member class unless the enclosing class an @Unit. "
298          + " Nested class is:"
299          + ReflectionUtilities.getFullName(constructor.getDeclaringClass()));
300    }
301    // TODO: When we use paramTypes below, we strip generic parameters.  Is that OK?
302    final Class<?>[] paramTypes = constructor.getParameterTypes();
303    final Type[] genericParamTypes = constructor.getGenericParameterTypes();
304    final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
305    if (paramTypes.length != paramAnnotations.length) {
306      throw new IllegalStateException();
307    }
308    final ConstructorArg[] args = new ConstructorArg[genericParamTypes.length];
309    for (int i = 0; i < genericParamTypes.length; i++) {
310      // If this parameter is an injection future, unwrap the target class,
311      // and remember by setting isFuture to true.
312      final Type type;
313      final boolean isFuture;
314      if (InjectionFuture.class.isAssignableFrom(paramTypes[i])) {
315        type = ReflectionUtilities.getInterfaceTarget(InjectionFuture.class, genericParamTypes[i]);
316        isFuture = true;
317      } else {
318        type = paramTypes[i];
319        isFuture = false;
320      }
321      // Make node of the named parameter annotation (if any).
322      Parameter named = null;
323      for (int j = 0; j < paramAnnotations[i].length; j++) {
324        final Annotation annotation = paramAnnotations[i][j];
325        if (annotation instanceof Parameter) {
326          if (!isClassInjectionCandidate || !injectable) {
327            throw new ClassHierarchyException(constructor + " is not injectable, but it has an @Parameter annotation.");
328          }
329          named = (Parameter) annotation;
330        }
331      }
332      args[i] = new ConstructorArgImpl(
333          ReflectionUtilities.getFullName(type), named == null ? null
334          : ReflectionUtilities.getFullName(named.value()),
335          isFuture);
336    }
337    return new ConstructorDefImpl<T>(
338        ReflectionUtilities.getFullName(constructor.getDeclaringClass()),
339        args, injectable);
340  }
341
342  /**
343   * Empty private constructor to prohibit instantiation of utility class.
344   */
345  private JavaNodeFactory() {
346  }
347}