/*******************************************************************************
 * Copyright (c) 2002 - 2006 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package com.ibm.wala.types;

import java.util.HashMap;

import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.debug.UnimplementedError;
import com.ibm.wala.util.shrike.ShrikeUtil;
import com.ibm.wala.util.strings.Atom;

/**
 * A class to represent the reference in a class file to a field.
 */
public final class FieldReference extends MemberReference {
  private final static boolean DEBUG = false;

  /**
   * Used to canonicalize MemberReferences a mapping from Key -> MemberReference
   */
  final private static HashMap<Key, FieldReference> dictionary = HashMapFactory.make();

  private final TypeReference fieldType;

  @Override
  public String getSignature() {
    return getDeclaringClass().getName() + "." + getName() + " " + getFieldType().getName();
  }

  /**
   * Find or create the canonical MemberReference instance for the given tuple.
   * 
   * @param mn the name of the member
   */
  public static synchronized FieldReference findOrCreate(TypeReference tref, Atom mn, TypeReference fieldType) {
    if (tref == null) {
      throw new IllegalArgumentException("null tref");
    }
    Key key = new Key(tref, mn, fieldType);

    FieldReference val = dictionary.get(key);
    if (val != null) {
      return val;
    }

    val = new FieldReference(key, fieldType);

    dictionary.put(key, val);
    return val;
  }

  /**
   * Find or create the canonical MemberReference instance for the given tuple.
   */
  public static FieldReference findOrCreate(ClassLoaderReference loader, String classType, String fieldName, String fieldType)
      throws IllegalArgumentException {
    TypeReference c = ShrikeUtil.makeTypeReference(loader, classType);
    TypeReference ft = ShrikeUtil.makeTypeReference(loader, fieldType);
    Atom name = Atom.findOrCreateUnicodeAtom(fieldName);
    return findOrCreate(c, name, ft);
  }

  private FieldReference(Key key, TypeReference fieldType) {
    super(key.type, key.name, key.hashCode());
    this.fieldType = fieldType;
    if (DEBUG) {
      if (getName().toString().indexOf('.') > -1)
        throw new UnimplementedError();
      if (fieldType.toString().indexOf('.') > -1)
        Assertions.UNREACHABLE("Field name: " + fieldType.toString());
      if (getName().toString().length() == 0)
        throw new UnimplementedError();
      if (fieldType.toString().length() == 0)
        throw new UnimplementedError();
    }
  }

  /**
   * @return the descriptor component of this member reference
   */
  public final TypeReference getFieldType() {
    return fieldType;
  }

  @Override
  public final String toString() {
    return "< " + getDeclaringClass().getClassLoader().getName() + ", " + getDeclaringClass().getName() + ", " + getName() + ", "
        + fieldType + " >";
  }

  /**
   * An identifier/selector for fields.
   */
  protected static class Key {
    final TypeReference type;

    final Atom name;

    private final TypeReference fieldType;

    Key(TypeReference type, Atom name, TypeReference fieldType) {
      this.type = type;
      this.name = name;
      this.fieldType = fieldType;
    }

    @Override
    public final int hashCode() {
      return 7487 * type.hashCode() + name.hashCode();
    }

    @Override
    public final boolean equals(Object other) {
      assert other != null && this.getClass().equals(other.getClass());

      Key that = (Key) other;
      return type.equals(that.type) && name.equals(that.name) && fieldType.equals(that.fieldType);
    }
  }

}
