/*******************************************************************************
 * Copyright (c) 2005, 2011 IBM Corporation and others.
 * 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 org.eclipse.jdt.internal.core.util;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.Wildcard;

public class BindingKeyParser {

	int keyStart;

	static final char C_THROWN = '|';

	static class Scanner {
		static final int PACKAGE = 0;
		static final int TYPE = 1;
		static final int FIELD = 2;
		static final int METHOD = 3;
		static final int ARRAY = 4;
		static final int LOCAL_VAR = 5;
		static final int FLAGS = 6;
		static final int WILDCARD = 7;
		static final int CAPTURE = 8;
		static final int BASE_TYPE = 9;
		static final int END = 10;

		static final int START = -1;

		int index = 0, start;
		char[] source;
		int token = START;

		Scanner(char[] source) {
			this.source = source;
		}

		char[] getTokenSource() {
			int length = this.index-this.start;
			char[] result = new char[length];
			System.arraycopy(this.source, this.start, result, 0, length);
			return result;
		}

		boolean isAtAnnotationStart() {
			return
				this.index < this.source.length
				&& this.source[this.index] == '@';
		}

		boolean isAtCaptureStart() {
			return
				this.index < this.source.length
				&& this.source[this.index] == '!';
		}

		boolean isAtFieldOrMethodStart() {
			return
				this.index < this.source.length
				&& this.source[this.index] == '.';
		}

		boolean isAtLocalVariableStart() {
			return
				this.index < this.source.length
				&& this.source[this.index] == '#';
		}

		boolean isAtMemberTypeStart() {
			return
				this.index < this.source.length
				&& (this.source[this.index] == '$'
					|| (this.source[this.index] == '.' && this.source[this.index-1] == '>'));
		}

		boolean isAtParametersEnd() {
			return
				this.index < this.source.length
					&& this.source[this.index] == '>';
		}

		boolean isAtParametersStart() {
			char currentChar;
			return
				this.index > 0
				&& this.index < this.source.length
				&& ((currentChar = this.source[this.index]) == '<'
					|| currentChar == '%');
		}

		boolean isAtRawTypeEnd() {
			return
				this.index > 0
				&& this.index < this.source.length
				&& this.source[this.index] == '>';
		}

		boolean isAtSecondaryTypeStart() {
			return
				this.index < this.source.length
				&& this.source[this.index] == '~';
		}

		boolean isAtWildcardStart() {
			return
				this.index < this.source.length
				&& this.source[this.index] == '{';   // e.g {1}+Ljava/lang/String;
		}

		boolean isAtTypeParameterStart() {
			return
				this.index < this.source.length
				&& this.source[this.index] == 'T';
		}

		boolean isAtTypeArgumentStart() {
			return this.index < this.source.length && "LIZVCDBFJS[!".indexOf(this.source[this.index]) != -1; //$NON-NLS-1$
		}

		boolean isAtThrownStart() {
			return
				this.index < this.source.length
				&& this.source[this.index] == C_THROWN;
		}

		boolean isAtTypeVariableStart() {
			return
				this.index < this.source.length
				&& this.source[this.index] == ':';
		}

		boolean isAtTypeWithCaptureStart() {
			return
				this.index < this.source.length
				&& this.source[this.index] == '&';
		}

		int nextToken() {
			int previousTokenEnd = this.index;
			this.start = this.index;
			int dollarIndex = -1;
			int length = this.source.length;
			while (this.index <= length) {
				char currentChar = this.index == length ? Character.MIN_VALUE : this.source[this.index];
				switch (currentChar) {
					case 'B':
					case 'C':
					case 'D':
					case 'F':
					case 'I':
					case 'J':
					case 'N':
					case 'S':
					case 'V':
					case 'Z':
						// base type
						if (this.index == previousTokenEnd
								&& (this.index == 0 || this.source[this.index-1] != '.')) { // case of field or method starting with one of the character above
							this.index++;
							this.token = BASE_TYPE;
							return this.token;
						}
						break;
					case 'L':
					case 'T':
						if (this.index == previousTokenEnd
								&& (this.index == 0 || this.source[this.index-1] != '.')) { // case of field or method starting with one of the character above
							this.start = this.index+1;
							dollarIndex = -1;
						}
						break;
					case ';':
						if (this.index == previousTokenEnd) {
							this.start = this.index+1;
							dollarIndex = -1;
							previousTokenEnd = this.start;
						} else {
							if (dollarIndex != -1) this.index = dollarIndex;
							this.token = TYPE;
							return this.token;
						}
						break;
					case '$':
						if (this.index == previousTokenEnd) {
							this.start = this.index+1;
							dollarIndex = -1;
						} else {
							if (dollarIndex == -1) {
								dollarIndex = this.index;
								break;
							}
							this.index = dollarIndex;
							this.token = TYPE;
							return this.token;
						}
						break;
					case '~':
						if (this.index == previousTokenEnd) {
							this.start = this.index+1;
							dollarIndex = -1;
						} else {
							this.token = TYPE;
							return this.token;
						}
						break;
					case '.':
					case '%':
					case ':':
					case '>':
					case '@':
						this.start = this.index+1;
						dollarIndex = -1;
						previousTokenEnd = this.start;
						break;
					case '[':
						while (this.index < length && this.source[this.index] == '[')
							this.index++;
						this.token = ARRAY;
						return this.token;
					case '<':
						if (this.start > 0) {
							switch (this.source[this.start-1]) {
								case '.':
									if (this.source[this.start-2] == '>') {
										// case of member type where enclosing type is parameterized
										if (dollarIndex != -1) this.index = dollarIndex;
										this.token = TYPE;
									} else {
										this.token = METHOD;
									}
									return this.token;
								default:
									if (this.index == previousTokenEnd) {
										this.start = this.index+1;
										dollarIndex = -1;
										previousTokenEnd = this.start;
									} else {
										if (dollarIndex != -1) this.index = dollarIndex;
										this.token = TYPE;
										return this.token;
									}
							}
						}
						break;
					case '(':
						this.token = METHOD;
						return this.token;
					case ')':
						if (this.token == TYPE) {
							this.token = FIELD;
							return this.token;
						}
						this.start = this.index+1;
						dollarIndex = -1;
						previousTokenEnd = this.start;
						break;
					case '#':
						if (this.index == previousTokenEnd) {
							this.start = this.index+1;
							dollarIndex = -1;
							previousTokenEnd = this.start;
						} else {
							this.token = LOCAL_VAR;
							return this.token;
						}
						break;
					case Character.MIN_VALUE:
						switch (this.token) {
							case START:
								this.token = PACKAGE;
								break;
							case METHOD:
							case LOCAL_VAR:
								this.token = LOCAL_VAR;
								break;
							case TYPE:
								if (this.index > this.start && this.source[this.start-1] == '.')
									this.token = FIELD;
								else
									this.token = END;
								break;
							case WILDCARD:
								this.token = TYPE;
								break;
							default:
								this.token = END;
								break;
						}
						return this.token;
					case '*':
					case '+':
					case '-':
						this.index++;
						this.token = WILDCARD;
						return this.token;
					case '!':
					case '&':
						this.index++;
						this.token = CAPTURE;
						return this.token;
				}
				this.index++;
			}
			this.token = END;
			return this.token;
		}

		void skipMethodSignature() {
			this.start = this.index;
			int braket = 0;
			while (this.index < this.source.length) {
				switch (this.source[this.index]) {
					case '#':
					case '%':
					case '@':
					case C_THROWN:
						return;
					case ':':
						if (braket == 0)
							return;
						break;
					case '<':
					case '(':
						braket++;
						break;
					case '>':
					case ')':
						braket--;
						break;
				}
				this.index++;
			}
		}

		void skipRank() {
			this.start = this.index;
			while (this.index < this.source.length && "0123456789".indexOf(this.source[this.index]) != -1) //$NON-NLS-1$
				this.index++;
		}
		
		void skipThrownStart() {
			while (this.index < this.source.length && this.source[this.index] == C_THROWN)
				this.index++;
		}

		void skipParametersStart() {
			while (this.index < this.source.length && (this.source[this.index] == '<' || this.source[this.index] == '%'))
				this.index++;
		}

		void skipParametersEnd() {
			while (this.index < this.source.length && this.source[this.index] != '>')
				this.index++;
			this.index++;
		}

		void skipTypeEnd() {
			if (this.index < this.source.length && this.source[this.index] == ';')
				this.index++;
		}
		
		void skipRankStart() {
			if (this.index < this.source.length && this.source[this.index] == '{')
				this.index++;
		}
		
		void skipRankEnd() {
			if (this.index < this.source.length && this.source[this.index] == '}')
				this.index++;
			this.start = this.index;
		}

		public String toString() {
			StringBuffer buffer = new StringBuffer();
			switch (this.token) {
				case START:
					buffer.append("START: "); //$NON-NLS-1$
					break;
				case PACKAGE:
					buffer.append("PACKAGE: "); //$NON-NLS-1$
					break;
				case TYPE:
					buffer.append("TYPE: "); //$NON-NLS-1$
					break;
				case FIELD:
					buffer.append("FIELD: "); //$NON-NLS-1$
					break;
				case METHOD:
					buffer.append("METHOD: "); //$NON-NLS-1$
					break;
				case ARRAY:
					buffer.append("ARRAY: "); //$NON-NLS-1$
					break;
				case LOCAL_VAR:
					buffer.append("LOCAL VAR: "); //$NON-NLS-1$
					break;
				case FLAGS:
					buffer.append("MODIFIERS: "); //$NON-NLS-1$
					break;
				case WILDCARD:
					buffer.append("WILDCARD: "); //$NON-NLS-1$
					break;
				case CAPTURE:
					buffer.append("CAPTURE: "); //$NON-NLS-1$
					break;
				case BASE_TYPE:
					buffer.append("BASE TYPE: "); //$NON-NLS-1$
					break;
				case END:
					buffer.append("END: "); //$NON-NLS-1$
					break;
			}
			if (this.index < 0) {
				buffer.append("**"); //$NON-NLS-1$
				buffer.append(this.source);
			} else if (this.index <= this.source.length) {
				buffer.append(this.source, 0, this.start);
				buffer.append('*');
				if (this.start <= this.index) {
					buffer.append(this.source, this.start, this.index - this.start);
					buffer.append('*');
					buffer.append(this.source, this.index, this.source.length - this.index);
				} else {
					buffer.append('*');
					buffer.append(this.source, this.start, this.source.length - this.start);
				}
			} else {
				buffer.append(this.source);
				buffer.append("**"); //$NON-NLS-1$
			}
			return buffer.toString();
		}
	}
	private boolean parsingPaused;

	private Scanner scanner;

	private boolean hasTypeName = true;

	private boolean isMalformed;
	
	private boolean isParsingThrownExceptions = false;	// https://bugs.eclipse.org/bugs/show_bug.cgi?id=336451

	public BindingKeyParser(BindingKeyParser parser) {
		this(""); //$NON-NLS-1$
		this.scanner = parser.scanner;
	}

	public BindingKeyParser(String key) {
		this.scanner = new Scanner(key.toCharArray());
	}

	public void consumeAnnotation() {
		// default is to do nothing
	}

	public void consumeArrayDimension(char[] brakets) {
		// default is to do nothing
	}

	public void consumeBaseType(char[] baseTypeSig) {
		// default is to do nothing
	}

	public void consumeCapture(int position) {
		// default is to do nothing
	}

	public void consumeException() {
		// default is to do nothing
	}

	public void consumeField(char[] fieldName) {
		// default is to do nothing
	}

	public void consumeParameterizedGenericMethod() {
		// default is to do nothing
	}

	public void consumeLocalType(char[] uniqueKey) {
		// default is to do nothing
	}

	public void consumeLocalVar(char[] varName, int occurrenceCount) {
		// default is to do nothing
	}

	public void consumeMethod(char[] selector, char[] signature) {
		// default is to do nothing
	}

	public void consumeModifiers(char[] modifiers) {
		// default is to do nothing
	}

	public void consumeNonGenericType() {
		// default is to do nothing
	}

	public void consumeMemberType(char[] simpleTypeName) {
		// default is to do nothing
	}

	public void consumePackage(char[] pkgName) {
		// default is to do nothing
	}

	public void consumeParameterizedType(char[] simpleTypeName, boolean isRaw) {
		// default is to do nothing
	}

	public void consumeParser(BindingKeyParser parser) {
		// default is to do nothing
	}

	public void consumeRawType() {
		// default is to do nothing
	}

	public void consumeScope(int scopeNumber) {
		// default is to do nothing
	}

	public void consumeSecondaryType(char[] simpleTypeName) {
		// default is to do nothing
	}

	public void consumeFullyQualifiedName(char[] fullyQualifiedName) {
		// default is to do nothing
	}

	public void consumeKey() {
		// default is to do nothing
	}

	public void consumeTopLevelType() {
		// default is to do nothing
	}

	public void consumeType() {
		// default is to do nothing
	}

	public void consumeTypeParameter(char[] typeParameterName) {
		// default is to do nothing
	}

	public void consumeTypeVariable(char[] position, char[] typeVariableName) {
		// default is to do nothing
	}

	public void consumeTypeWithCapture() {
		// default is to do nothing
	}

	public void consumeWildCard(int kind) {
		// default is to do nothing
	}
	
	public void consumeWildcardRank(int rank) {
		// default is to do nothing
	}

	/*
	 * Returns the string that this binding key wraps.
	 */
	public String getKey() {
		return new String(this.scanner.source);
	}

	public boolean hasTypeName() {
		return this.hasTypeName;
	}

	public void malformedKey() {
		this.isMalformed = true;
	}

	public BindingKeyParser newParser() {
		return new BindingKeyParser(this);
	}

	public void parse() {
		parse(false/*don't pause after fully qualified name*/);
	}

	public void parse(boolean pauseAfterFullyQualifiedName) {
		if (!this.parsingPaused) {
			// fully qualified name
			parseFullyQualifiedName();
			parseSecondaryType();
			if (pauseAfterFullyQualifiedName) {
				this.parsingPaused = true;
				return;
			}
		}
		if (!hasTypeName()) {
			consumeKey();
			return;
		}
		consumeTopLevelType();
		parseInnerType();

		if (this.scanner.isAtParametersStart()) {
			this.scanner.skipParametersStart();
			if (this.scanner.isAtTypeParameterStart())	{
				// generic type
				parseGenericType();
			 	// skip ";>"
			 	this.scanner.skipParametersEnd();
				// local type in generic type
				parseInnerType();
			} else if (this.scanner.isAtTypeArgumentStart())
				// parameterized type
				parseParameterizedType(null/*top level type or member type with raw enclosing type*/, false/*no raw*/);
			else if (this.scanner.isAtRawTypeEnd())
				// raw type
				parseRawType();
		} else {
			// non-generic type
			consumeNonGenericType();
		}

		consumeType();
		this.scanner.skipTypeEnd();

		if (this.scanner.isAtFieldOrMethodStart()) {
			switch (this.scanner.nextToken()) {
				case Scanner.FIELD:
					parseField();
					if (this.scanner.isAtAnnotationStart()) {
						parseAnnotation();
					}
					return;
				case Scanner.METHOD:
					parseMethod();
					if (this.scanner.isAtLocalVariableStart()) {
						parseLocalVariable();
					} else if (this.scanner.isAtTypeVariableStart()) {
						parseTypeVariable();
					} else if (this.scanner.isAtAnnotationStart()) {
						parseAnnotation();
					}
			 		break;
				default:
					malformedKey();
					return;
			}
		} else if (!this.isParsingThrownExceptions && this.scanner.isAtTypeVariableStart()) {
			parseTypeVariable();
		} else if (this.scanner.isAtWildcardStart()) {
			parseWildcard();
		} else if (this.scanner.isAtTypeWithCaptureStart()) {
			parseTypeWithCapture();
		} else if (this.scanner.isAtAnnotationStart()) {
			parseAnnotation();
		}

		consumeKey();
	}

	private void parseFullyQualifiedName() {
		if (this.scanner.isAtCaptureStart()) {
			parseCapture();
			this.hasTypeName = false;
			return;
		}
		switch(this.scanner.nextToken()) {
			case Scanner.PACKAGE:
				this.keyStart = 0;
				consumePackage(this.scanner.getTokenSource());
				this.hasTypeName = false;
				return;
			case Scanner.TYPE:
				this.keyStart = this.scanner.start-1;
				consumeFullyQualifiedName(this.scanner.getTokenSource());
				break;
			case Scanner.BASE_TYPE:
				this.keyStart = this.scanner.start-1;
				consumeBaseType(this.scanner.getTokenSource());
				this.hasTypeName = false;
				break;
			case Scanner.ARRAY:
				this.keyStart = this.scanner.start;
				consumeArrayDimension(this.scanner.getTokenSource());
				switch (this.scanner.nextToken()) {
					case Scanner.TYPE:
						consumeFullyQualifiedName(this.scanner.getTokenSource());
						break;
					case Scanner.BASE_TYPE:
						consumeBaseType(this.scanner.getTokenSource());
						this.hasTypeName = false;
						break;
					default:
						malformedKey();
						return;
				}
				break;
			default:
				malformedKey();
				return;
		}
	}

	private void parseParameterizedMethod() {
		this.scanner.skipParametersStart();
		while (!this.scanner.isAtParametersEnd() && !this.isMalformed) {
			parseTypeArgument();
		}
		consumeParameterizedGenericMethod();
	}

	private void parseGenericType() {
		while (!this.scanner.isAtParametersEnd() && !this.isMalformed) {
			if (this.scanner.nextToken() != Scanner.TYPE) {
				malformedKey();
				return;
			}
			consumeTypeParameter(this.scanner.getTokenSource());
			this.scanner.skipTypeEnd();
		}
	}

	private void parseInnerType() {
		if (!this.scanner.isAtMemberTypeStart() || this.scanner.nextToken() != Scanner.TYPE)
			return;
		char[] typeName = this.scanner.getTokenSource();
		// Might not actually be an inner type but came here as a consequence of '$' being present in type name
		if (typeName.length == 0)
			return;
		if (Character.isDigit(typeName[0])) {
			// anonymous or local type
			int nextToken = Scanner.TYPE;
			while (this.scanner.isAtMemberTypeStart() && !this.isMalformed)
				nextToken = this.scanner.nextToken();
			typeName = nextToken == Scanner.END ? this.scanner.source : CharOperation.subarray(this.scanner.source, this.keyStart, this.scanner.index+1);
			consumeLocalType(typeName);
		} else {
			consumeMemberType(typeName);
			parseInnerType();
		}
	}

	private void parseLocalVariable() {
		if (this.scanner.nextToken() != Scanner.LOCAL_VAR) {
			malformedKey();
			return;
		}
		char[] varName = this.scanner.getTokenSource();
		if (Character.isDigit(varName[0])) {
			int index = Integer.parseInt(new String(varName));
			consumeScope(index);
			if (!this.scanner.isAtLocalVariableStart()) {
				malformedKey();
				return;
			}
			parseLocalVariable();
		} else {
			int occurrenceCount = 0;
			if (this.scanner.isAtLocalVariableStart()) {
				if (this.scanner.nextToken() != Scanner.LOCAL_VAR) {
					malformedKey();
					return;
				}
				char[] occurrence = this.scanner.getTokenSource();
				occurrenceCount = Integer.parseInt(new String(occurrence));
			}
			consumeLocalVar(varName, occurrenceCount);
		}
	}

	private void parseMethod() {
		char[] selector = this.scanner.getTokenSource();
		this.scanner.skipMethodSignature();
		char[] signature = this.scanner.getTokenSource();
		consumeMethod(selector, signature);
		if (this.scanner.isAtThrownStart()) {
			parseThrownExceptions();
		}
		if (this.scanner.isAtParametersStart())
			parseParameterizedMethod();
	}

	private void parseAnnotation() {
		/*
		 * The call parser.parse() might have a side-effect on the current token type
		 * See bug 264443
		 */
		int token = this.scanner.token;
		BindingKeyParser parser = newParser();
		parser.parse();
		consumeParser(parser);
		consumeAnnotation();
		this.isMalformed = parser.isMalformed;
		this.scanner.token = token;
	}

	private void parseCapture() {
		if (this.scanner.nextToken() != Scanner.CAPTURE) return;
		parseCaptureWildcard();
		if (this.scanner.nextToken() != Scanner.TYPE) {
			malformedKey();
			return;
		}
		char[] positionChars = this.scanner.getTokenSource();
		int position = Integer.parseInt(new String(positionChars));
		consumeCapture(position);
		this.scanner.skipTypeEnd();
	}

	private void parseCaptureWildcard() {
		/*
		 * The call parser.parse() might have a side-effect on the current token type
		 * See bug 264443
		 */
		int token = this.scanner.token;
		BindingKeyParser parser = newParser();
		parser.parse();
		consumeParser(parser);
		this.isMalformed = parser.isMalformed;
		this.scanner.token = token;
	}

	private void parseField() {
		char[] fieldName = this.scanner.getTokenSource();
		parseReturnType();
 		consumeField(fieldName);
	}

	private void parseThrownExceptions() {
		/*
		 * The call parser.parse() might have a side-effect on the current token type
		 * See bug 264443
		 */
		int token = this.scanner.token;
		while (this.scanner.isAtThrownStart() && !this.isMalformed) {
			this.scanner.skipThrownStart();
			BindingKeyParser parser = newParser();
			parser.isParsingThrownExceptions = true;
			parser.parse();
			consumeParser(parser);
			consumeException();
			this.isMalformed = parser.isMalformed;
		}
		this.scanner.token = token;
	}

	private void parseParameterizedType(char[] typeName, boolean isRaw) {
		if (!isRaw) {
			while (!this.scanner.isAtParametersEnd() && !this.isMalformed) {
				parseTypeArgument();
			}
		}
	 	// skip ";>"
	 	this.scanner.skipParametersEnd();
		consumeParameterizedType(typeName, isRaw);
		this.scanner.skipTypeEnd();
	 	if (this.scanner.isAtMemberTypeStart() && this.scanner.nextToken() == Scanner.TYPE) {
	 		typeName = this.scanner.getTokenSource();
			if (this.scanner.isAtParametersStart()) {
				this.scanner.skipParametersStart();
		 		parseParameterizedType(typeName, this.scanner.isAtRawTypeEnd());
			} else
				consumeParameterizedType(typeName, true/*raw*/);
	 	}
	}

	private void parseRawType() {
		this.scanner.skipParametersEnd();
		consumeRawType();
		this.scanner.skipTypeEnd();
	 	if (this.scanner.isAtMemberTypeStart() && this.scanner.nextToken() == Scanner.TYPE) {
	 		char[] typeName = this.scanner.getTokenSource();
			if (this.scanner.isAtParametersStart()) {
				this.scanner.skipParametersStart();
		 		parseParameterizedType(typeName, this.scanner.isAtRawTypeEnd());
			} else
				consumeParameterizedType(typeName, true/*raw*/);
	 	}
	}

	private void parseReturnType() {
		this.scanner.index++; // skip ')'
		/*
		 * The call parser.parse() might have a side-effect on the current token type
		 * See bug 264443
		 */
		int token = this.scanner.token;
		BindingKeyParser parser = newParser();
		parser.parse();
		consumeParser(parser);
		this.isMalformed = parser.isMalformed;
		this.scanner.token = token;
	}

	private void parseSecondaryType() {
		if (!this.scanner.isAtSecondaryTypeStart() || this.scanner.nextToken() != Scanner.TYPE) return;
		consumeSecondaryType(this.scanner.getTokenSource());
	}

	private void parseTypeArgument() {
		/*
		 * The call parser.parse() might have a side-effect on the current token type
		 * See bug 264443
		 */
		int token = this.scanner.token;
		BindingKeyParser parser = newParser();
		parser.parse();
		consumeParser(parser);
		this.isMalformed = parser.isMalformed;
		this.scanner.token = token;
	}

	private void parseTypeWithCapture() {
		if (this.scanner.nextToken() != Scanner.CAPTURE) return;
		/*
		 * The call parser.parse() might have a side-effect on the current token type
		 * See bug 264443
		 */
		int token = this.scanner.token;
		BindingKeyParser parser = newParser();
		parser.parse();
		consumeParser(parser);
		consumeTypeWithCapture();
		this.isMalformed = parser.isMalformed;
		this.scanner.token = token;
	}

	private void parseTypeVariable() {
		if (this.scanner.nextToken() != Scanner.TYPE) {
			malformedKey();
			return;
		}
		char[] typeVariableName = this.scanner.getTokenSource();
		char[] position;
		int length = typeVariableName.length;
		if (length > 0 && Character.isDigit(typeVariableName[0])) {
			int firstT = CharOperation.indexOf('T', typeVariableName);
			position = CharOperation.subarray(typeVariableName, 0, firstT);
			typeVariableName = CharOperation.subarray(typeVariableName, firstT+1, typeVariableName.length);
		} else {
			position = CharOperation.NO_CHAR;
		}
		consumeTypeVariable(position, typeVariableName);
		this.scanner.skipTypeEnd();
	}

	private void parseWildcard() {
		parseWildcardRank();
		if (this.scanner.nextToken() != Scanner.WILDCARD) return;
		char[] source = this.scanner.getTokenSource();
		if (source.length == 0) {
			malformedKey();
			return;
		}
		int kind = -1;
		switch (source[0]) {
			case '*':
				kind = Wildcard.UNBOUND;
				break;
			case '+':
				kind = Wildcard.EXTENDS;
				break;
			case '-':
				kind = Wildcard.SUPER;
				break;
		}
		if (kind == -1) {
			malformedKey();
			return;
		}
		if (kind != Wildcard.UNBOUND)
			parseWildcardBound();
		consumeWildCard(kind);
	}

	private void parseWildcardRank() {
		this.scanner.skipRankStart();
		this.scanner.skipRank();
		char[] source = this.scanner.getTokenSource();
		consumeWildcardRank(Integer.parseInt(new String(source)));
		this.scanner.skipRankEnd();
	}
	
	private void parseWildcardBound() {
		/*
		 * The call parser.parse() might have a side-effect on the current token type
		 * See bug 264443
		 */
		int token = this.scanner.token;
		BindingKeyParser parser = newParser();
		parser.parse();
		consumeParser(parser);
		this.isMalformed = parser.isMalformed;
		this.scanner.token = token;
	}

}
