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