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}