/** * A helper method that returns the parsed default value of a given NamedParameter. * * @return null or an empty set if there is no default value, the default value (or set of values) * otherwise. * @throws ClassHierarchyException if a default value was specified, but could not be parsed, or * if a set of values were specified for a non-set parameter. */ @SuppressWarnings("unchecked") @Override public <T> T parseDefaultValue(final NamedParameterNode<T> name) { final String[] vals = name.getDefaultInstanceAsStrings(); final T[] ret = (T[]) new Object[vals.length]; for (int i = 0; i < vals.length; i++) { final String val = vals[i]; try { ret[i] = parse(name, val); } catch (final ParseException e) { throw new ClassHierarchyException("Could not parse default value", e); } } if (name.isSet()) { return (T) new HashSet<T>(Arrays.asList(ret)); } else if (name.isList()) { return (T) new ArrayList<T>(Arrays.asList(ret)); } else { if (ret.length == 0) { return null; } else if (ret.length == 1) { return ret[0]; } else { throw new IllegalStateException( "Multiple defaults for non-set named parameter! " + name.getFullName()); } } }
/** * Parse a string, assuming that it is of the type expected by a given NamedParameter. * * <p>This method does not deal with sets; if the NamedParameter is set valued, then the provided * string should correspond to a single member of the set. It is up to the caller to call parse * once for each value that should be parsed as a member of the set. * * @return a non-null reference to the parsed value. */ @Override @SuppressWarnings("unchecked") public <T> T parse(final NamedParameterNode<T> np, final String value) throws ParseException { final ClassNode<T> iface; try { iface = (ClassNode<T>) getNode(np.getFullArgName()); } catch (final NameResolutionException e) { throw new IllegalStateException( "Could not parse validated named parameter argument type. NamedParameter is " + np.getFullName() + " argument type is " + np.getFullArgName()); } Class<?> clazz; String fullName; try { clazz = classForName(iface.getFullName()); fullName = null; } catch (final ClassNotFoundException e) { clazz = null; fullName = iface.getFullName(); } try { if (clazz != null) { return (T) parameterParser.parse(clazz, value); } else { return parameterParser.parse(fullName, value); } } catch (final UnsupportedOperationException e) { try { final Node impl = getNode(value); if (impl instanceof ClassNode && isImplementation(iface, (ClassNode<?>) impl)) { return (T) impl; } throw new ParseException( "Name<" + iface.getFullName() + "> " + np.getFullName() + " cannot take non-subclass " + impl.getFullName(), e); } catch (final NameResolutionException e2) { throw new ParseException( "Name<" + iface.getFullName() + "> " + np.getFullName() + " cannot take non-class " + value, e); } } }
private <T, U> Node buildPathToNode(final Class<U> clazz) throws ClassHierarchyException { final String[] path = clazz.getName().split("\\$"); Node root = namespace; for (int i = 0; i < path.length - 1; i++) { root = root.get(path[i]); } if (root == null) { throw new NullPointerException(); } final Node parent = root; final Type argType = ReflectionUtilities.getNamedParameterTargetOrNull(clazz); if (argType == null) { return JavaNodeFactory.createClassNode(parent, clazz); } else { // checked inside of NamedParameterNode, using reflection. @SuppressWarnings("unchecked") final NamedParameterNode<T> np = JavaNodeFactory.createNamedParameterNode( parent, (Class<? extends Name<T>>) clazz, argType); if (parameterParser.canParse(ReflectionUtilities.getFullName(argType)) && clazz.getAnnotation(NamedParameter.class).default_class() != Void.class) { throw new ClassHierarchyException( "Named parameter " + ReflectionUtilities.getFullName(clazz) + " defines default implementation for parsable type " + ReflectionUtilities.getFullName(argType)); } final String shortName = np.getShortName(); if (shortName != null) { final NamedParameterNode<?> oldNode = shortNames.get(shortName); if (oldNode != null) { if (oldNode.getFullName().equals(np.getFullName())) { throw new IllegalStateException( "Tried to double bind " + oldNode.getFullName() + " to short name " + shortName); } throw new ClassHierarchyException( "Named parameters " + oldNode.getFullName() + " and " + np.getFullName() + " have the same short name: " + shortName); } shortNames.put(shortName, np); } return np; } }
private Node register(final String s) { final Class<?> c; try { c = classForName(s); } catch (final ClassNotFoundException e1) { return null; } try { final Node n = getAlreadyBoundNode(c); return n; } catch (final NameResolutionException e) { // node not bound yet } // First, walk up the class hierarchy, registering all out parents. This // can't be loopy. if (c.getSuperclass() != null) { register(ReflectionUtilities.getFullName(c.getSuperclass())); } for (final Class<?> i : c.getInterfaces()) { register(ReflectionUtilities.getFullName(i)); } // Now, we'd like to register our enclosing classes. This turns out to be // safe. // Thankfully, Java doesn't allow: // class A implements A.B { class B { } } // It also doesn't allow cycles such as: // class A implements B.BB { interface AA { } } // class B implements A.AA { interface BB { } } // So, even though grafting arbitrary DAGs together can give us cycles, Java // seems // to have our back on this one. final Class<?> enclosing = c.getEnclosingClass(); if (enclosing != null) { register(ReflectionUtilities.getFullName(enclosing)); } // Now register the class. This has to be after the above so we know our // parents (superclasses and enclosing packages) are already registered. final Node n = registerClass(c); // Finally, do things that might introduce cycles that invlove c. // This has to be below registerClass, which ensures that any cycles // this stuff introduces are broken. for (final Class<?> innerClass : c.getDeclaredClasses()) { register(ReflectionUtilities.getFullName(innerClass)); } if (n instanceof ClassNode) { final ClassNode<?> cls = (ClassNode<?>) n; for (final ConstructorDef<?> def : cls.getInjectableConstructors()) { for (final ConstructorArg arg : def.getArgs()) { register(arg.getType()); if (arg.getNamedParameterName() != null) { final NamedParameterNode<?> np = (NamedParameterNode<?>) register(arg.getNamedParameterName()); try { // TODO: When handling sets, need to track target of generic parameter, and check the // type here! if (!np.isSet() && !np.isList() && !ReflectionUtilities.isCoercable( classForName(arg.getType()), classForName(np.getFullArgName()))) { throw new ClassHierarchyException( "Named parameter type mismatch in " + cls.getFullName() + ". Constructor expects a " + arg.getType() + " but " + np.getName() + " is a " + np.getFullArgName()); } } catch (final ClassNotFoundException e) { throw new ClassHierarchyException( "Constructor refers to unknown class " + arg.getType(), e); } } } } } else if (n instanceof NamedParameterNode) { final NamedParameterNode<?> np = (NamedParameterNode<?>) n; register(np.getFullArgName()); } return n; }