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}