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("The named parameter " + namedParameter.short_name() + " has a default value,"
226              + " but the value cannot be retrieved. The defaultCount is " + defaultCount
227              + ", but hasClassDefault, hasStringDefault, hasClassSetDefault, hasStringSetDefault"
228              + " conditions are all false");
229    }
230
231    final String documentation = namedParameter.doc();
232
233    final String shortName = namedParameter.short_name().isEmpty()
234        ? null : namedParameter.short_name();
235
236    return new NamedParameterNodeImpl<>(parent, simpleName, fullName,
237        fullArgName, simpleArgName, isSet, isList, documentation, shortName, defaultInstanceAsStrings);
238  }
239
240  private static void assertIsSubclassOf(final Class<?> namedParameter, final Class<?> defaultClass,
241                                         final Type argClass) {
242    boolean isSubclass = false;
243    boolean isGenericSubclass = false;
244    final Class<?> argRawClass = ReflectionUtilities.getRawClass(argClass);
245
246    // Note: We intentionally strip the raw type information here.  The reason is to handle
247    // EventHandler-style patterns and collections.
248
249    /// If we have a Name that takes EventHandler<A>, we want to be able to pass in an EventHandler<Object>.
250
251    for (final Type c : ReflectionUtilities.classAndAncestors(defaultClass)) {
252      if (ReflectionUtilities.getRawClass(c).equals(argRawClass)) {
253        isSubclass = true;
254        if (argClass instanceof ParameterizedType &&
255            c instanceof ParameterizedType) {
256          final ParameterizedType argPt = (ParameterizedType) argClass;
257          final ParameterizedType defaultPt = (ParameterizedType) c;
258
259          final Class<?> rawDefaultParameter = ReflectionUtilities.getRawClass(defaultPt.getActualTypeArguments()[0]);
260          final Class<?> rawArgParameter = ReflectionUtilities.getRawClass(argPt.getActualTypeArguments()[0]);
261
262          for (final Type d : ReflectionUtilities.classAndAncestors(argPt.getActualTypeArguments()[0])) {
263            if (ReflectionUtilities.getRawClass(d).equals(rawDefaultParameter)) {
264              isGenericSubclass = true;
265            }
266          }
267          for (final Type d : ReflectionUtilities.classAndAncestors(defaultPt.getActualTypeArguments()[0])) {
268            if (ReflectionUtilities.getRawClass(d).equals(rawArgParameter)) {
269              isGenericSubclass = true;
270            }
271          }
272        } else {
273          isGenericSubclass = true;
274        }
275      }
276    }
277
278    if (!isSubclass) {
279      throw new ClassHierarchyException(namedParameter + " defines a default class "
280          + ReflectionUtilities.getFullName(defaultClass)
281          + " with a raw type that does not extend of its target's raw type " + argRawClass);
282    }
283    if (!isGenericSubclass) {
284      throw new ClassHierarchyException(namedParameter + " defines a default class "
285          + ReflectionUtilities.getFullName(defaultClass)
286          + " with a type that does not extend its target's type " + argClass);
287    }
288  }
289
290  public static PackageNode createRootPackageNode() {
291    return new PackageNodeImpl();
292  }
293
294  private static <T> ConstructorDef<T> createConstructorDef(
295      final boolean isClassInjectionCandidate, final Constructor<T> constructor,
296      final boolean injectable) throws ClassHierarchyException {
297    // We don't support injection of non-static member classes with @Inject
298    // annotations.
299    if (injectable && !isClassInjectionCandidate) {
300      throw new ClassHierarchyException("Cannot @Inject non-static member class unless the enclosing class an @Unit. "
301          + " Nested class is:"
302          + ReflectionUtilities.getFullName(constructor.getDeclaringClass()));
303    }
304    // TODO: When we use paramTypes below, we strip generic parameters.  Is that OK?
305    final Class<?>[] paramTypes = constructor.getParameterTypes();
306    final Type[] genericParamTypes = constructor.getGenericParameterTypes();
307    final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
308    if (paramTypes.length != paramAnnotations.length) {
309      throw new IllegalStateException("The paramTypes.length is " + paramTypes.length
310              + ", and paramAnnotations.length is " + paramAnnotations.length
311              + ". These values should be equal."
312      );
313    }
314    final ConstructorArg[] args = new ConstructorArg[genericParamTypes.length];
315    for (int i = 0; i < genericParamTypes.length; i++) {
316      // If this parameter is an injection future, unwrap the target class,
317      // and remember by setting isFuture to true.
318      final Type type;
319      final boolean isFuture;
320      if (InjectionFuture.class.isAssignableFrom(paramTypes[i])) {
321        type = ReflectionUtilities.getInterfaceTarget(InjectionFuture.class, genericParamTypes[i]);
322        isFuture = true;
323      } else {
324        type = paramTypes[i];
325        isFuture = false;
326      }
327      // Make node of the named parameter annotation (if any).
328      Parameter named = null;
329      for (int j = 0; j < paramAnnotations[i].length; j++) {
330        final Annotation annotation = paramAnnotations[i][j];
331        if (annotation instanceof Parameter) {
332          if (!isClassInjectionCandidate || !injectable) {
333            throw new ClassHierarchyException(constructor + " is not injectable, but it has an @Parameter annotation.");
334          }
335          named = (Parameter) annotation;
336        }
337      }
338      args[i] = new ConstructorArgImpl(
339          ReflectionUtilities.getFullName(type), named == null ? null
340          : ReflectionUtilities.getFullName(named.value()),
341          isFuture);
342    }
343    return new ConstructorDefImpl<T>(
344        ReflectionUtilities.getFullName(constructor.getDeclaringClass()),
345        args, injectable);
346  }
347
348  /**
349   * Empty private constructor to prohibit instantiation of utility class.
350   */
351  private JavaNodeFactory() {
352  }
353}