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}