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.ClassHierarchy; 022import org.apache.reef.tang.ExternalConstructor; 023import org.apache.reef.tang.JavaClassHierarchy; 024import org.apache.reef.tang.annotations.Name; 025import org.apache.reef.tang.annotations.NamedParameter; 026import org.apache.reef.tang.exceptions.BindException; 027import org.apache.reef.tang.exceptions.ClassHierarchyException; 028import org.apache.reef.tang.exceptions.NameResolutionException; 029import org.apache.reef.tang.exceptions.ParseException; 030import org.apache.reef.tang.formats.ParameterParser; 031import org.apache.reef.tang.types.*; 032import org.apache.reef.tang.util.MonotonicTreeMap; 033import org.apache.reef.tang.util.ReflectionUtilities; 034 035import java.lang.reflect.Type; 036import java.net.URL; 037import java.net.URLClassLoader; 038import java.util.*; 039 040public class ClassHierarchyImpl implements JavaClassHierarchy { 041 // TODO Want to add a "register namespace" method, but Java is not designed 042 // to support such things. 043 // There are third party libraries that would help, but they can fail if the 044 // relevant jar has not yet been loaded. Tint works around this using such 045 // a library. 046 047 /** 048 * The ParameterParser that this ClassHierarchy uses to parse default values. 049 * Custom parameter parsers allow applications to extend the set of classes 050 * that Tang can parse. 051 */ 052 private final ParameterParser parameterParser = new ParameterParser(); 053 /** 054 * The classloader that was used to populate this class hierarchy. 055 */ 056 private final URLClassLoader loader; 057 /** 058 * The jars that are reflected by that loader. These are in addition to 059 * whatever jars are available by the default classloader (i.e., the one 060 * that loaded Tang. We need this list so that we can merge class hierarchies 061 * that are backed by different classpaths. 062 */ 063 private final List<URL> jars; 064 /** 065 * A reference to the root package which is a root of a tree of nodes. 066 * The children of each node in the tree are accessible by name, and the 067 * structure of the tree mirrors Java's package namespace. 068 */ 069 private final PackageNode namespace; 070 /** 071 * A map from short name to named parameter node. This is only used to 072 * sanity check short names so that name clashes get resolved. 073 */ 074 private final Map<String, NamedParameterNode<?>> shortNames = new MonotonicTreeMap<>(); 075 076 @SuppressWarnings("unchecked") 077 public ClassHierarchyImpl() { 078 this(new URL[0], new Class[0]); 079 } 080 081 @SuppressWarnings("unchecked") 082 public ClassHierarchyImpl(final URL... jars) { 083 this(jars, new Class[0]); 084 } 085 086 public ClassHierarchyImpl(final URL[] jars, final Class<? extends ExternalConstructor<?>>[] parameterParsers) { 087 this.namespace = JavaNodeFactory.createRootPackageNode(); 088 this.jars = new ArrayList<>(Arrays.asList(jars)); 089 this.loader = new URLClassLoader(jars, this.getClass().getClassLoader()); 090 for (final Class<? extends ExternalConstructor<?>> p : parameterParsers) { 091 try { 092 parameterParser.addParser(p); 093 } catch (final BindException e) { 094 throw new IllegalArgumentException("Could not register parameter parsers", e); 095 } 096 } 097 } 098 099 /** 100 * A helper method that returns the parsed default value of a given 101 * NamedParameter. 102 * 103 * @return null or an empty set if there is no default value, the default value (or set of values) otherwise. 104 * @throws ClassHierarchyException if a default value was specified, but could not be parsed, or if a set of 105 * values were specified for a non-set parameter. 106 */ 107 @SuppressWarnings("unchecked") 108 @Override 109 public <T> T parseDefaultValue(final NamedParameterNode<T> name) { 110 final String[] vals = name.getDefaultInstanceAsStrings(); 111 final T[] ret = (T[]) new Object[vals.length]; 112 for (int i = 0; i < vals.length; i++) { 113 final String val = vals[i]; 114 try { 115 ret[i] = parse(name, val); 116 } catch (final ParseException e) { 117 throw new ClassHierarchyException("Could not parse default value", e); 118 } 119 } 120 if (name.isSet()) { 121 return (T) new HashSet<T>(Arrays.asList(ret)); 122 } else if (name.isList()) { 123 return (T) new ArrayList<T>(Arrays.asList(ret)); 124 } else { 125 if (ret.length == 0) { 126 return null; 127 } else if (ret.length == 1) { 128 return ret[0]; 129 } else { 130 throw new IllegalStateException("Multiple defaults for non-set named parameter! " + name.getFullName()); 131 } 132 } 133 } 134 135 /** 136 * Parse a string, assuming that it is of the type expected by a given NamedParameter. 137 * <p> 138 * This method does not deal with sets; if the NamedParameter is set valued, then the provided 139 * string should correspond to a single member of the set. It is up to the caller to call parse 140 * once for each value that should be parsed as a member of the set. 141 * 142 * @return a non-null reference to the parsed value. 143 */ 144 @Override 145 @SuppressWarnings("unchecked") 146 public <T> T parse(final NamedParameterNode<T> np, final String value) throws ParseException { 147 final ClassNode<T> iface; 148 try { 149 iface = (ClassNode<T>) getNode(np.getFullArgName()); 150 } catch (final NameResolutionException e) { 151 throw new IllegalStateException("Could not parse validated named parameter argument type. NamedParameter is " + 152 np.getFullName() + " argument type is " + np.getFullArgName(), e); 153 } 154 Class<?> clazz; 155 String fullName; 156 try { 157 clazz = classForName(iface.getFullName()); 158 fullName = null; 159 } catch (final ClassNotFoundException e) { 160 clazz = null; 161 fullName = iface.getFullName(); 162 } 163 try { 164 if (clazz != null) { 165 return (T) parameterParser.parse(clazz, value); 166 } else { 167 return parameterParser.parse(fullName, value); 168 } 169 } catch (final UnsupportedOperationException e) { 170 try { 171 final Node impl = getNode(value); 172 if (impl instanceof ClassNode && isImplementation(iface, (ClassNode<?>) impl)) { 173 return (T) impl; 174 } 175 throw new ParseException("Name<" + iface.getFullName() + "> " + np.getFullName() + 176 " cannot take non-subclass " + impl.getFullName(), e); 177 } catch (final NameResolutionException e2) { 178 throw new ParseException("Name<" + iface.getFullName() + "> " + np.getFullName() + 179 " cannot take non-class " + value, e2); 180 } 181 } 182 } 183 184 /** 185 * Helper method that converts a String to a Class using this 186 * ClassHierarchy's classloader. 187 */ 188 @Override 189 public Class<?> classForName(final String name) throws ClassNotFoundException { 190 return ReflectionUtilities.classForName(name, loader); 191 } 192 193 private <T, U> Node buildPathToNode(final Class<U> clazz) 194 throws ClassHierarchyException { 195 final String[] path = clazz.getName().split("\\$"); 196 197 Node root = namespace; 198 for (int i = 0; i < path.length - 1; i++) { 199 root = root.get(path[i]); 200 } 201 202 if (root == null) { 203 throw new NullPointerException("The root of the path to node is null. " 204 + "The class name is likely to be malformed. The clazz.getName() returns " + clazz.getName()); 205 } 206 final Node parent = root; 207 208 final Type argType = ReflectionUtilities.getNamedParameterTargetOrNull(clazz); 209 210 if (argType == null) { 211 return JavaNodeFactory.createClassNode(parent, clazz); 212 } else { 213 214 // checked inside of NamedParameterNode, using reflection. 215 @SuppressWarnings("unchecked") final NamedParameterNode<T> np = JavaNodeFactory.createNamedParameterNode( 216 parent, (Class<? extends Name<T>>) clazz, argType); 217 218 if (parameterParser.canParse(ReflectionUtilities.getFullName(argType)) && 219 clazz.getAnnotation(NamedParameter.class).default_class() != Void.class) { 220 throw new ClassHierarchyException("Named parameter " + ReflectionUtilities.getFullName(clazz) + 221 " defines default implementation for parsable type " + ReflectionUtilities.getFullName(argType)); 222 } 223 224 final String shortName = np.getShortName(); 225 if (shortName != null) { 226 final NamedParameterNode<?> oldNode = shortNames.get(shortName); 227 if (oldNode != null) { 228 if (oldNode.getFullName().equals(np.getFullName())) { 229 throw new IllegalStateException("Tried to double bind " 230 + oldNode.getFullName() + " to short name " + shortName); 231 } 232 throw new ClassHierarchyException("Named parameters " + oldNode.getFullName() 233 + " and " + np.getFullName() + " have the same short name: " 234 + shortName); 235 } 236 shortNames.put(shortName, np); 237 } 238 return np; 239 } 240 } 241 242 private Node getAlreadyBoundNode(final Class<?> clazz) throws NameResolutionException { 243 return getAlreadyBoundNode(ReflectionUtilities.getFullName(clazz)); 244 } 245 246 @Override 247 public Node getNode(final Class<?> clazz) { 248 try { 249 return getNode(ReflectionUtilities.getFullName(clazz)); 250 } catch (final NameResolutionException e) { 251 throw new ClassHierarchyException("JavaClassHierarchy could not resolve " + clazz 252 + " which is definitely avalable at runtime", e); 253 } 254 } 255 256 @Override 257 public synchronized Node getNode(final String name) throws NameResolutionException { 258 final Node n = register(name); 259 if (n == null) { 260 // This will never succeed; it just generates a nice exception. 261 getAlreadyBoundNode(name); 262 throw new IllegalStateException("IMPLEMENTATION BUG: Register failed, " 263 + "but getAlreadyBoundNode succeeded!"); 264 } 265 return n; 266 } 267 268 private Node getAlreadyBoundNode(final String name) throws NameResolutionException { 269 Node root = namespace; 270 final String[] toks = name.split("\\$"); 271 final String outerClassName = toks[0]; 272 root = root.get(outerClassName); 273 if (root == null) { 274 throw new NameResolutionException(name, outerClassName); 275 } 276 for (int i = 1; i < toks.length; i++) { 277 root = root.get(toks[i]); 278 if (root == null) { 279 final StringBuilder sb = new StringBuilder(outerClassName); 280 for (int j = 0; j < i; j++) { 281 sb.append(toks[j]); 282 if (j != i - 1) { 283 sb.append("."); 284 } 285 } 286 throw new NameResolutionException(name, sb.toString()); 287 } 288 } 289 return root; 290 } 291 292 private Node register(final String s) { 293 final Class<?> c; 294 try { 295 c = classForName(s); 296 } catch (final ClassNotFoundException e1) { 297 return null; 298 } 299 try { 300 final Node n = getAlreadyBoundNode(c); 301 return n; 302 } catch (final NameResolutionException ignored) { 303 // node not bound yet 304 } 305 // First, walk up the class hierarchy, registering all out parents. This 306 // can't be loopy. 307 if (c.getSuperclass() != null) { 308 register(ReflectionUtilities.getFullName(c.getSuperclass())); 309 } 310 for (final Class<?> i : c.getInterfaces()) { 311 register(ReflectionUtilities.getFullName(i)); 312 } 313 // Now, we'd like to register our enclosing classes. This turns out to be 314 // safe. 315 // Thankfully, Java doesn't allow: 316 // class A implements A.B { class B { } } 317 318 // It also doesn't allow cycles such as: 319 // class A implements B.BB { interface AA { } } 320 // class B implements A.AA { interface BB { } } 321 322 // So, even though grafting arbitrary DAGs together can give us cycles, Java 323 // seems 324 // to have our back on this one. 325 final Class<?> enclosing = c.getEnclosingClass(); 326 if (enclosing != null) { 327 register(ReflectionUtilities.getFullName(enclosing)); 328 } 329 330 // Now register the class. This has to be after the above so we know our 331 // parents (superclasses and enclosing packages) are already registered. 332 final Node n = registerClass(c); 333 334 // Finally, do things that might introduce cycles that invlove c. 335 // This has to be below registerClass, which ensures that any cycles 336 // this stuff introduces are broken. 337 for (final Class<?> innerClass : c.getDeclaredClasses()) { 338 register(ReflectionUtilities.getFullName(innerClass)); 339 } 340 if (n instanceof ClassNode) { 341 final ClassNode<?> cls = (ClassNode<?>) n; 342 for (final ConstructorDef<?> def : cls.getInjectableConstructors()) { 343 for (final ConstructorArg arg : def.getArgs()) { 344 register(arg.getType()); 345 if (arg.getNamedParameterName() != null) { 346 final NamedParameterNode<?> np = (NamedParameterNode<?>) register(arg 347 .getNamedParameterName()); 348 try { 349 // TODO: When handling sets, need to track target of generic parameter, and check the type here! 350 if (!np.isSet() && !np.isList() && 351 !ReflectionUtilities.isCoercable(classForName(arg.getType()), classForName(np.getFullArgName()))) { 352 throw new ClassHierarchyException( 353 "Named parameter type mismatch in " + cls.getFullName() + ". Constructor expects a " 354 + arg.getType() + " but " + np.getName() + " is a " 355 + np.getFullArgName()); 356 } 357 } catch (final ClassNotFoundException e) { 358 throw new ClassHierarchyException("Constructor refers to unknown class " 359 + arg.getType(), e); 360 } 361 } 362 } 363 } 364 } else if (n instanceof NamedParameterNode) { 365 final NamedParameterNode<?> np = (NamedParameterNode<?>) n; 366 register(np.getFullArgName()); 367 } 368 return n; 369 } 370 371 /** 372 * Assumes that all of the parents of c have been registered already. 373 * 374 * @param c 375 */ 376 @SuppressWarnings("unchecked") 377 private <T> Node registerClass(final Class<T> c) 378 throws ClassHierarchyException { 379 if (c.isArray()) { 380 throw new UnsupportedOperationException("Can't register array types"); 381 } 382 try { 383 return getAlreadyBoundNode(c); 384 } catch (final NameResolutionException ignored) { 385 // node not bound yet 386 } 387 388 final Node n = buildPathToNode(c); 389 390 if (n instanceof ClassNode) { 391 final ClassNode<T> cn = (ClassNode<T>) n; 392 final Class<T> superclass = (Class<T>) c.getSuperclass(); 393 if (superclass != null) { 394 try { 395 ((ClassNode<T>) getAlreadyBoundNode(superclass)).putImpl(cn); 396 } catch (final NameResolutionException e) { 397 throw new IllegalStateException(e); 398 } 399 } 400 for (final Class<?> interf : c.getInterfaces()) { 401 try { 402 ((ClassNode<T>) getAlreadyBoundNode(interf)).putImpl(cn); 403 } catch (final NameResolutionException e) { 404 throw new IllegalStateException(e); 405 } 406 } 407 } 408 return n; 409 } 410 411 @Override 412 public PackageNode getNamespace() { 413 return namespace; 414 } 415 416 public ParameterParser getParameterParser() { 417 return parameterParser; 418 } 419 420 @Override 421 public synchronized boolean isImplementation(final ClassNode<?> inter, final ClassNode<?> impl) { 422 return impl.isImplementationOf(inter); 423 } 424 425 @Override 426 public synchronized ClassHierarchy merge(final ClassHierarchy ch) { 427 if (this == ch) { 428 return this; 429 } 430 if (!(ch instanceof ClassHierarchyImpl)) { 431 throw new UnsupportedOperationException("Can't merge java and non-java class hierarchies yet!"); 432 } 433 if (this.jars.size() == 0) { 434 return ch; 435 } 436 final ClassHierarchyImpl chi = (ClassHierarchyImpl) ch; 437 final HashSet<URL> otherJars = new HashSet<>(); 438 otherJars.addAll(chi.jars); 439 final HashSet<URL> myJars = new HashSet<>(); 440 myJars.addAll(this.jars); 441 if (myJars.containsAll(otherJars)) { 442 return this; 443 } else if (otherJars.containsAll(myJars)) { 444 return ch; 445 } else { 446 myJars.addAll(otherJars); 447 return new ClassHierarchyImpl(myJars.toArray(new URL[0])); 448 } 449 } 450}