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}