/*
 * Copyright 2006 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.dev.util.xml;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * Represents metadata about a parameter in a handler method in a class derived
 * from {@link Schema}.
 */
public final class HandlerParam {

  /**
   * Looks for a field of the form [methodName]_[paramIndex]_[attrName].
   */
  public static HandlerParam create(Method method, String normalizedTagName,
      int paramIndex) {
    Class<?> paramType = method.getParameterTypes()[paramIndex];
    Field[] fields = method.getDeclaringClass().getDeclaredFields();
    String fieldNamePrefix = normalizedTagName + "_" + (paramIndex + 1);
    Field matchingField = null;
    String fieldName = null;
    for (int i = 0, n = fields.length; i < n; ++i) {
      Field testField = fields[i];
      fieldName = testField.getName();
      if (fieldName.startsWith(fieldNamePrefix)) {
        matchingField = testField;
        break;
      }
    }

    if (matchingField == null) {
      throw new IllegalArgumentException("Expecting a meta field with prefix '"
          + fieldNamePrefix + "'");
    }

    int under = fieldName.indexOf("_", fieldNamePrefix.length());
    if (under == -1) {
      // Not a valid signature.
      //
      throw new IllegalArgumentException(
          "Expecting a normalized attribute name suffix (e.g. \"_attr_name\") on field '"
              + fieldName + "'");
    }

    // Infer the associated attribute name.
    //
    String normalizedAttrName = fieldName.substring(under + 1);

    // GWT fields values must be of type String.
    //
    if (!String.class.equals(matchingField.getType())) {
      // Type mismatch.
      //
      throw new IllegalArgumentException("GWT field '" + fieldName
          + "' must be of type String");
    }

    // Instantiate one.
    //
    matchingField.setAccessible(true);
    HandlerParam handlerParam = new HandlerParam(paramType, matchingField,
        normalizedAttrName);
    return handlerParam;
  }

  private final Class<?> paramType;

  private final Field metaField;

  private final boolean isOptional;

  private final String normalizedAttrName;

  private HandlerParam(Class<?> paramType, Field metaField,
      String normalizedAttrName) {
    this.isOptional = normalizedAttrName.endsWith("$");
    if (isOptional) {
      normalizedAttrName = normalizedAttrName.substring(0, normalizedAttrName.length() - 1);
    }
    this.paramType = paramType;
    this.metaField = metaField;
    this.normalizedAttrName = normalizedAttrName;
  }

  /**
   * Called while parsing to get the default value for an attribute.
   */
  public String getDefaultValue(Schema schema) {
    Throwable caught = null;
    try {
      return (String) metaField.get(schema);
    } catch (IllegalArgumentException e) {
      caught = e;
    } catch (IllegalAccessException e) {
      caught = e;
    }

    throw new IllegalStateException(
        "Unable to get attribute default value from meta field '"
            + metaField.getName() + "'", caught);
  }

  public String getNormalizedName() {
    return normalizedAttrName;
  }

  public Class<?> getParamType() {
    return paramType;
  }

  public boolean isOptional() {
    return isOptional;
  }
}
