/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.jsdt.internal.compiler.lookup;

import org.eclipse.wst.jsdt.core.compiler.CharOperation;

/**
 * Denote a raw type, i.e. a generic type referenced without any type arguments.
 * e.g. X<T extends Exception> can be used a raw type 'X', in which case it
 * 	will behave as X<Exception>
 */
public class RawTypeBinding extends ParameterizedTypeBinding {
    
    /**
     * Raw type arguments are erasure of respective parameter bounds. But we may not have resolved
     * these bounds yet if creating raw types while supertype hierarchies are being connected.
     * Therefore, use 'null' instead, and access these in a lazy way later on (when substituting).
     */
	public RawTypeBinding(ReferenceBinding type, ReferenceBinding enclosingType, LookupEnvironment environment){
		super(type, null, enclosingType, environment);
		if (enclosingType == null || (enclosingType.modifiers & AccGenericSignature) == 0)
			this.modifiers ^= AccGenericSignature; // only need signature if enclosing needs one
	}    
	/**
	 * @see org.eclipse.wst.jsdt.internal.compiler.lookup.ParameterizedTypeBinding#createParameterizedMethod(org.eclipse.wst.jsdt.internal.compiler.lookup.MethodBinding)
	 */
	public ParameterizedMethodBinding createParameterizedMethod(MethodBinding originalMethod) {
		if (originalMethod.typeVariables == NoTypeVariables) {
			return super.createParameterizedMethod(originalMethod);
		}
		return new ParameterizedGenericMethodBinding(originalMethod, this, this.environment);
	}
	/**
	 * @see org.eclipse.wst.jsdt.internal.compiler.lookup.TypeBinding#debugName()
	 */
	public String debugName() {
	    StringBuffer nameBuffer = new StringBuffer(10);
		nameBuffer.append(this.type.sourceName()).append("#RAW"); //$NON-NLS-1$
	    return nameBuffer.toString();		
	}	

	/**
	 * Ltype<param1 ... paramN>;
	 * LY<TT;>;
	 */
	public char[] genericTypeSignature() {

	    if (this.genericTypeSignature == null) {
		    StringBuffer sig = new StringBuffer(10);
			if (this.isMemberType() && this.enclosingType().isParameterizedType()) {
			    char[] typeSig = this.enclosingType().genericTypeSignature();
			    for (int i = 0; i < typeSig.length-1; i++) sig.append(typeSig[i]); // copy all but trailing semicolon
			    sig.append('.').append(this.sourceName()).append(';');
				int sigLength = sig.length();
				this.genericTypeSignature = new char[sigLength];
				sig.getChars(0, sigLength, this.genericTypeSignature, 0);						    
			} else {
			     this.genericTypeSignature = this.type.signature(); // erasure
			}
	    }
	   return this.genericTypeSignature;
	}		
	
    public boolean isEquivalentTo(TypeBinding otherType) {
	    if (this == otherType) return true;
        if (otherType == null) return false;
		if (otherType.isWildcard()) // wildcard
			return ((WildcardBinding) otherType).boundCheck(this);
        return otherType.erasure() == this.erasure();
    }
	/**
	 * Raw type is not treated as a standard parameterized type
	 * @see org.eclipse.wst.jsdt.internal.compiler.lookup.TypeBinding#isParameterizedType()
	 */
	public boolean isParameterizedType() {
	    return false;
	}	
	public boolean isRawType() {
	    return true;
	}	
	
	protected void initializeArguments() {
		TypeVariableBinding[] typeVariables = this.type.typeVariables();
		int length = typeVariables.length;
		TypeBinding[] typeArguments = new TypeBinding[length];
		for (int i = 0; i < length; i++) {
		    typeArguments[i] = typeVariables[i].erasure();
		}
		this.arguments = typeArguments;
	}
	/**
	 * @see org.eclipse.wst.jsdt.internal.compiler.lookup.Binding#readableName()
	 */
	public char[] readableName() /*java.lang.Object,  p.X<T> */ {
	    char[] readableName;
		if (isMemberType()) {
			readableName = CharOperation.concat(enclosingType().readableName(), sourceName, '.');
		} else {
			readableName = CharOperation.concatWith(this.type.compoundName, '.');
		}
		return readableName;
	}

	/**
	 * Returns a type, where original type was substituted using the receiver
	 * raw type.
	 * On raw types, all parameterized type denoting same original type are converted
	 * to raw types. e.g. 
	 * class X <T> {
	 *   X<T> foo;
	 *   X<String> bar;
	 * } when used in raw fashion, then type of both foo and bar is raw type X.
	 */
	public TypeBinding substitute(TypeBinding originalType) {
	    
	    if (originalType.isTypeVariable()) {
	        TypeVariableBinding originalVariable = (TypeVariableBinding) originalType;
		    ParameterizedTypeBinding currentType = this;
	        while (true) {
		        TypeVariableBinding[] typeVariables = currentType.type.typeVariables();
		        int length = typeVariables.length;
		        // check this variable can be substituted given parameterized type
		        if (originalVariable.rank < length && typeVariables[originalVariable.rank] == originalVariable) {
				    // lazy init, since cannot do so during binding creation if during supertype connection
				    if (currentType.arguments == null)  currentType.initializeArguments();
				    if (currentType.arguments != null)
			           return currentType.arguments[originalVariable.rank];
		        }
			    // recurse on enclosing type, as it may hold more substitutions to perform
			    ReferenceBinding enclosing = currentType.enclosingType();
			    if (!(enclosing instanceof ParameterizedTypeBinding))
			        break;
			    currentType = (ParameterizedTypeBinding) enclosing;
	        }
	    } else if (originalType.isParameterizedType()) {
	        ParameterizedTypeBinding originalParameterizedType = (ParameterizedTypeBinding) originalType;
			return this.environment.createRawType(originalParameterizedType.type, originalParameterizedType.enclosingType());
	    } else  if (originalType.isGenericType()) {
            return this.environment.createRawType((ReferenceBinding)originalType, null);
	    } else if (originalType.isArrayType()) {
			TypeBinding originalLeafComponentType = originalType.leafComponentType();
			TypeBinding substitute = substitute(originalLeafComponentType); // substitute could itself be array type
			if (substitute != originalLeafComponentType) {
				return this.environment.createArrayType(substitute.leafComponentType(), substitute.dimensions() + originalType.dimensions());
			}
	    }
	    return originalType;
	}	

	/**
	 * @see org.eclipse.wst.jsdt.internal.compiler.lookup.Binding#shortReadableName()
	 */
	public char[] shortReadableName() /*Object*/ {
	    char[] shortReadableName;
		if (isMemberType()) {
			shortReadableName = CharOperation.concat(enclosingType().shortReadableName(), sourceName, '.');
		} else {
			shortReadableName = this.type.sourceName;
		}
		return shortReadableName;
	}
}
