@Override
        public Object handlePropertyAccess(Object self, ValueImpl impl, Object... args) {
          ValueDescriptorImpl<?> descriptor = impl.descriptor;

          boolean first = true;
          StringBuilder result = new StringBuilder(descriptor.getValueInterface().getName());
          result.append('{');
          for (PropertyImpl property : descriptor.internalGetProperties()) {
            Object selfValue = property.getGetHandler().handlePropertyAccess(self, impl);

            if (first) {
              first = false;
            } else {
              result.append("; ");
            }
            result.append(property.getName());
            result.append(": ");
            if (property.getKind() == Kind.PRIMITIVE) {
              result.append(property.getParser().unparse(selfValue));
            } else {
              result.append(selfValue);
            }
          }
          result.append('}');

          return result.toString();
        }
  /** Completes the analysis of the {@link #getValueInterface()}. */
  public void init() {
    Map<String, PropertyImpl> propertyByRawName = new HashMap<String, PropertyImpl>();
    Map<Method, PropertyImpl> propertyByMethod = new HashMap<Method, PropertyImpl>();
    for (Method method : valueInterface.getMethods()) {
      if (method.getDeclaringClass().isAssignableFrom(Value.class)) {
        // Ignore Value and Object type methods.
        continue;
      }

      int modifiers = method.getModifiers();
      if (!Modifier.isPublic(modifiers)) {
        throw new AssertionError("Expected public modifier on '" + method + "'");
      }

      if (Modifier.isStatic(modifiers)) {
        throw new AssertionError("No static methods allowed on '" + method + "'");
      }

      if (method.getExceptionTypes().length > 0) {
        throw new AssertionError("Method must not declare exceptions: " + method);
      }

      boolean isGetter;
      String prefix;
      String methodName = method.getName();
      if (methodName.startsWith("get")) {
        prefix = "get";
        isGetter = true;
      } else if (methodName.startsWith("set")) {
        prefix = "set";
        isGetter = false;
      } else if (methodName.startsWith("is")) {
        prefix = "is";
        isGetter = true;
      } else if (methodName.startsWith("has")) {
        prefix = "has";
        isGetter = true;
      } else if (methodName.startsWith("can")) {
        prefix = "can";
        isGetter = true;
      } else if (methodName.startsWith("must")) {
        prefix = "must";
        isGetter = true;
      } else {
        throw new AssertionError("Invalid method prefix: " + method);
      }

      Class<?> type;
      if (isGetter) {
        Class<?>[] types = method.getParameterTypes();
        if (types.length != 0) {
          throw new AssertionError("Getter must not have parameters: " + method);
        }

        type = method.getReturnType();
        if (type == Void.class || type == void.class) {
          throw new AssertionError("Getter must not have void return type: " + method);
        }

        boolean isBoolean = type == boolean.class || type == Boolean.class;
        if (!prefix.equals("get") && !isBoolean) {
          throw new AssertionError(
              "Non boolean getters must have '" + "get" + "' prefix: " + method);
        }
      } else {
        Class<?>[] types = method.getParameterTypes();
        if (types.length != 1) {
          throw new AssertionError("Setter must have exactly one argument: " + method);
        }

        Class<?> returnType = method.getReturnType();
        if (returnType != void.class) {
          throw new AssertionError("Setter must have void return type: " + method);
        }

        type = types[0];
      }

      String postfix = methodName.substring(prefix.length());

      if (Character.isLowerCase(postfix.charAt(0))) {
        throw new AssertionError("Expected upper case letter after method prefix: " + method);
      }

      String rawName = Character.toLowerCase(postfix.charAt(0)) + postfix.substring(1);

      PropertyImpl property = propertyByRawName.get(rawName);
      if (property == null) {
        int index = propertyByRawName.size();
        property = new PropertyImpl(this, rawName, index);
        propertyByRawName.put(rawName, property);
      }

      if (isGetter) {
        property.initGetter(method);
      }

      propertyByMethod.put(method, property);
    }

    for (PropertyImpl property : propertyByRawName.values()) {
      PropertyImpl clash = properties.put(property.getName(), property);
      if (clash != null) {
        throw new IllegalArgumentException(
            "Properties must have unique names in interface '"
                + valueInterface.getName()
                + "': "
                + clash.getName());
      }
    }

    for (Entry<Method, PropertyImpl> entry : propertyByMethod.entrySet()) {
      Method method = entry.getKey();
      boolean isGetter = !method.getName().startsWith("set");
      PropertyImpl property = entry.getValue();
      handlerByMethod.put(method, isGetter ? property.getGetHandler() : property.getSetHandler());
    }

    // Add object methods.
    handlerByMethod.put(EQUALS_METHOD, EQUALS_IMPL);
    handlerByMethod.put(HASH_CODE_METHOD, HASH_CODE_IMPL);
    handlerByMethod.put(TO_STRING_METHOD, TO_STRING_IMPL);

    // Add value interface methods.
    handlerByMethod.put(DESCRIPTOR_METHOD, DESCRIPTOR_IMPL);
    handlerByMethod.put(VALUE_METHOD, VALUE_IMPL);
    handlerByMethod.put(PUT_VALUE_METHOD, PUT_VALUE_IMPL);
  }