private static <T> Maybe<T> tryValidateBean(
      T object, RegisteredType type, final RegisteredTypeLoadingContext context) {
    if (object == null) return Maybe.absentNull("object is null");

    if (type != null) {
      if (type.getKind() != RegisteredTypeKind.BEAN)
        return Maybe.absent("Validating a bean when type is " + type.getKind() + " " + type);
      if (!isSubtypeOf(object.getClass(), type))
        return Maybe.absent(object + " does not have all the java supertypes of " + type);
    }

    if (context != null) {
      if (context.getExpectedKind() != null && context.getExpectedKind() != RegisteredTypeKind.BEAN)
        return Maybe.absent(
            "Validating a bean when constraint expected " + context.getExpectedKind());
      if (context.getExpectedJavaSuperType() != null
          && !context.getExpectedJavaSuperType().isInstance(object))
        return Maybe.absent(
            object
                + " is not of the expected java supertype "
                + context.getExpectedJavaSuperType());
    }

    return Maybe.of(object);
  }
  private static <T> Maybe<T> tryValidateSpec(
      T object, RegisteredType rType, final RegisteredTypeLoadingContext constraint) {
    if (object == null) return Maybe.absentNull("object is null");

    if (!(object instanceof AbstractBrooklynObjectSpec)) {
      Maybe.absent("Found " + object + " when expecting a spec");
    }
    Class<?> targetType = ((AbstractBrooklynObjectSpec<?, ?>) object).getType();

    if (targetType == null) {
      Maybe.absent("Spec " + object + " does not have a target type");
    }

    if (rType != null) {
      if (rType.getKind() != RegisteredTypeKind.SPEC)
        Maybe.absent("Validating a spec when type is " + rType.getKind() + " " + rType);
      if (!isSubtypeOf(targetType, rType))
        Maybe.absent(object + " does not have all the java supertypes of " + rType);
    }

    if (constraint != null) {
      if (constraint.getExpectedJavaSuperType() != null) {
        if (!constraint.getExpectedJavaSuperType().isAssignableFrom(targetType)) {
          Maybe.absent(
              object
                  + " does not target the expected java supertype "
                  + constraint.getExpectedJavaSuperType());
        }
        if (constraint.getExpectedJavaSuperType().isAssignableFrom(BrooklynObjectInternal.class)) {
          // don't check spec type; any spec is acceptable
        } else {
          @SuppressWarnings("unchecked")
          Class<? extends AbstractBrooklynObjectSpec<?, ?>> specType =
              RegisteredTypeLoadingContexts.lookupSpecTypeForTarget(
                  (Class<? extends BrooklynObject>) constraint.getExpectedJavaSuperType());
          if (specType == null) {
            // means a problem in our classification of spec types!
            Maybe.absent(
                object
                    + " is returned as spec for unexpected java supertype "
                    + constraint.getExpectedJavaSuperType());
          }
          if (!specType.isAssignableFrom(object.getClass())) {
            Maybe.absent(
                object
                    + " is not a spec of the expected java supertype "
                    + constraint.getExpectedJavaSuperType());
          }
        }
      }
    }
    return Maybe.of(object);
  }
  /**
   * returns the {@link Class} object corresponding to the given java type name and registered type,
   * using the cache on the type in the first instance, falling back to the loader defined the type
   * and context.
   */
  @Beta
  // TODO should this be on the AbstractTypePlanTransformer ?
  public static Class<?> loadActualJavaType(
      String javaTypeName,
      ManagementContext mgmt,
      RegisteredType type,
      RegisteredTypeLoadingContext context) {
    Class<?> result = peekActualJavaType(type);
    if (result != null) return result;

    result =
        CatalogUtils.newClassLoadingContext(
                mgmt, type, context == null ? null : context.getLoader())
            .loadClass(javaTypeName);

    cacheActualJavaType(type, result);
    return result;
  }
  /**
   * validates that the given object (required) satisfies the constraints implied by the given type
   * and context object, using {@link Maybe} as the result set absent containing the error(s) if not
   * satisfied. returns an {@link Absent} if failed with details of the error, with {@link
   * Absent#isNull()} true if the object is null.
   */
  public static <T> Maybe<T> tryValidate(
      final T object,
      @Nullable final RegisteredType type,
      @Nullable final RegisteredTypeLoadingContext context) {
    if (object == null) return Maybe.absentNull("object is null");

    RegisteredTypeKind kind =
        type != null ? type.getKind() : context != null ? context.getExpectedKind() : null;
    if (kind == null) {
      if (object instanceof AbstractBrooklynObjectSpec) kind = RegisteredTypeKind.SPEC;
      else kind = RegisteredTypeKind.BEAN;
    }
    return new RegisteredTypeKindVisitor<Maybe<T>>() {
      @Override
      protected Maybe<T> visitSpec() {
        return tryValidateSpec(object, type, context);
      }

      @Override
      protected Maybe<T> visitBean() {
        return tryValidateBean(object, type, context);
      }
    }.visit(kind);
  }
  /**
   * Validates that the given type matches the context (if supplied); if not satisfied. returns an
   * {@link Absent} if failed with details of the error, with {@link Absent#isNull()} true if the
   * object is null.
   */
  public static Maybe<RegisteredType> tryValidate(
      RegisteredType item, final RegisteredTypeLoadingContext constraint) {
    // kept as a Maybe in case someone wants a wrapper around item validity;
    // unclear what the contract should be, as this can return Maybe.Present(null)
    // which is suprising, but it is more natural to callers otherwise they'll likely do a separate
    // null check on the item
    // (often handling null different to errors) so the Maybe.get() is redundant as they have an
    // object for the input anyway.

    if (item == null || constraint == null) return Maybe.ofDisallowingNull(item);
    if (constraint.getExpectedKind() != null
        && !constraint.getExpectedKind().equals(item.getKind()))
      return Maybe.absent(item + " is not the expected kind " + constraint.getExpectedKind());
    if (constraint.getExpectedJavaSuperType() != null) {
      if (!isSubtypeOf(item, constraint.getExpectedJavaSuperType())) {
        return Maybe.absent(
            item + " is not for the expected type " + constraint.getExpectedJavaSuperType());
      }
    }
    return Maybe.of(item);
  }