/*******************************************************************************
 * Copyright (c) 2006, 2017 BEA Systems, Inc. and others
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * Contributors:
 *    wharley@bea.com - initial API and implementation
 *    IBM Corporation - Fix for bug 341494
 *    IBM Corporation - Fix for bug 328575
 *    IBM Corporation - Java 8 support
 *******************************************************************************/

package org.aspectj.org.eclipse.jdt.internal.compiler.apt.model;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;

import org.aspectj.org.eclipse.jdt.core.compiler.CharOperation;
import org.aspectj.org.eclipse.jdt.internal.compiler.apt.dispatch.BaseProcessingEnvImpl;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.Javadoc;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.MethodVerifier;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TagBits;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeConstants;

/**
 * Utilities for working with java8 and earlier language elements.
 * There is one of these for every ProcessingEnvironment.
 * 
 * @see ElementsImpl9
 */
public class ElementsImpl implements Elements {

	// Used for parsing Javadoc comments: matches initial delimiter, followed by whitespace
	private static final Pattern INITIAL_DELIMITER = Pattern.compile("^\\s*/\\*+"); //$NON-NLS-1$

	protected final BaseProcessingEnvImpl _env;

	/*
	 * The processing env creates and caches an ElementsImpl.  Other clients should
	 * not create their own; they should ask the env for it.
	 */
	protected ElementsImpl(BaseProcessingEnvImpl env) {
		_env = env;
	}

	public static ElementsImpl create(BaseProcessingEnvImpl env) {
		return (SourceVersion.latest().compareTo(SourceVersion.RELEASE_8) <= 0)? new ElementsImpl(env): new ElementsImpl9(env);
	}

	/**
	 * Return all the annotation mirrors on this element, including inherited annotations.
	 * Annotations are inherited only if the annotation type is meta-annotated with @Inherited,
	 * and the annotation is on a class: e.g., annotations are not inherited for interfaces, methods,
	 * or fields.
	 */
	@Override
	public List<? extends AnnotationMirror> getAllAnnotationMirrors(Element e) {
		// if e is a class, walk up its superclass hierarchy looking for @Inherited annotations not already in the list
		if (e.getKind() == ElementKind.CLASS && e instanceof TypeElementImpl) {
			List<AnnotationBinding> annotations = new ArrayList<>();
			// A class can only have one annotation of a particular annotation type.
			Set<ReferenceBinding> annotationTypes = new HashSet<>();
			ReferenceBinding binding = (ReferenceBinding)((TypeElementImpl)e)._binding;
			boolean checkIfInherited = false;
			while (null != binding) {
				if (binding instanceof ParameterizedTypeBinding) {
					binding = ((ParameterizedTypeBinding) binding).genericType();
				}
				for (AnnotationBinding annotation : Factory.getPackedAnnotationBindings(binding.getAnnotations())) {
					if (annotation == null) continue;
					ReferenceBinding annotationType = annotation.getAnnotationType();
					if (checkIfInherited && (annotationType.getAnnotationTagBits() & TagBits.AnnotationInherited) == 0)
						continue;
					if (!annotationTypes.contains(annotationType)) {
						annotationTypes.add(annotationType);
						annotations.add(annotation);
					}
				}
				binding = binding.superclass();
				checkIfInherited = true;
			}
			List<AnnotationMirror> list = new ArrayList<>(annotations.size());
			for (AnnotationBinding annotation : annotations) {
				list.add(_env.getFactory().newAnnotationMirror(annotation));
			}
			return Collections.unmodifiableList(list);
		}
		else {
			return e.getAnnotationMirrors();
		}
	}

	/**
	 * Compute a list of all the visible entities in this type.  Specifically:
	 * <ul>
	 * <li>All nested types declared in this type, including interfaces and enums</li>
	 * <li>All protected or public nested types declared in this type's superclasses
	 * and superinterfaces, that are not hidden by a name collision</li>
	 * <li>All methods declared in this type, including constructors but not
	 * including static or instance initializers, and including abstract
	 * methods and unimplemented methods declared in interfaces</li>
	 * <li>All protected or public methods declared in this type's superclasses,
	 * that are not overridden by another method, but not including constructors
	 * or initializers. Includes abstract methods and methods declared in
	 * superinterfaces but not implemented</li>
	 * <li>All fields declared in this type, including constants</li>
	 * <li>All non-private fields declared in this type's superclasses and
	 * superinterfaces, that are not hidden by a name collision.</li>
	 * </ul>
	 */
	@Override
	public List<? extends Element> getAllMembers(TypeElement type) {
		if (null == type || !(type instanceof TypeElementImpl)) {
			return Collections.emptyList();
		}
		ReferenceBinding binding = (ReferenceBinding)((TypeElementImpl)type)._binding;
		// Map of element simple name to binding
		Map<String, ReferenceBinding> types = new HashMap<>();
		// Javac implementation does not take field name collisions into account
		List<FieldBinding> fields = new ArrayList<>();
		// For methods, need to compare parameters, not just names
		Map<String, Set<MethodBinding>> methods = new HashMap<>();
		Set<ReferenceBinding> superinterfaces = new LinkedHashSet<>();
		boolean ignoreVisibility = true;
		while (null != binding) {
			addMembers(binding, ignoreVisibility, types, fields, methods);
			Set<ReferenceBinding> newfound = new LinkedHashSet<>();
			collectSuperInterfaces(binding, superinterfaces, newfound);
			for (ReferenceBinding superinterface : newfound) {
				addMembers(superinterface, false, types, fields, methods);
			}
			superinterfaces.addAll(newfound);
			binding = binding.superclass();
			ignoreVisibility = false;
		}
		List<Element> allMembers = new ArrayList<>();
		for (ReferenceBinding nestedType : types.values()) {
			allMembers.add(_env.getFactory().newElement(nestedType));
		}
		for (FieldBinding field : fields) {
			allMembers.add(_env.getFactory().newElement(field));
		}
		for (Set<MethodBinding> sameNamedMethods : methods.values()) {
			for (MethodBinding method : sameNamedMethods) {
				allMembers.add(_env.getFactory().newElement(method));
			}
		}
		return allMembers;
	}

	/**
	 * Recursively depth-first walk the tree of superinterfaces of a type, collecting
	 * all the unique superinterface bindings.  (Note that because of generics, a type may
	 * have multiple unique superinterface bindings corresponding to the same interface
	 * declaration.)
	 * @param existing bindings already in this set will not be re-added or recursed into
	 * @param newfound newly found bindings will be added to this set
	 */
	private void collectSuperInterfaces(ReferenceBinding type,
			Set<ReferenceBinding> existing, Set<ReferenceBinding> newfound) {
		for (ReferenceBinding superinterface : type.superInterfaces()) {
			if (!existing.contains(superinterface) && !newfound.contains(superinterface)) {
				newfound.add(superinterface);
				collectSuperInterfaces(superinterface, existing, newfound);
			}
		}
	}

	/**
	 * Add the members of a type to the maps of subtypes, fields, and methods.  Add only those
	 * which are non-private and which are not overridden by an already-discovered member.
	 * For fields, add them all; javac implementation does not take field hiding into account.
	 * @param binding the type whose members will be added to the lists
	 * @param ignoreVisibility if true, all members will be added regardless of whether they
	 * are private, overridden, etc.
	 * @param types a map of type simple name to type binding
	 * @param fields a list of field bindings
	 * @param methods a map of method simple name to set of method bindings with that name
	 */
	private void addMembers(ReferenceBinding binding, boolean ignoreVisibility, Map<String, ReferenceBinding> types,
			List<FieldBinding> fields, Map<String, Set<MethodBinding>> methods)
	{
		for (ReferenceBinding subtype : binding.memberTypes()) {
			if (ignoreVisibility || !subtype.isPrivate()) {
				String name = new String(subtype.sourceName());
				if (null == types.get(name)) {
					types.put(name, subtype);
				}
			}
		}
		for (FieldBinding field : binding.fields()) {
			if (ignoreVisibility || !field.isPrivate()) {
				fields.add(field);
			}
		}
		for (MethodBinding method : binding.methods()) {
			if (!method.isSynthetic() && (ignoreVisibility || (!method.isPrivate() && !method.isConstructor()))) {
				String methodName = new String(method.selector);
				Set<MethodBinding> sameNamedMethods = methods.get(methodName);
				if (null == sameNamedMethods) {
					// New method name.  Create a set for it and add it to the list.
					// We don't expect many methods with same name, so only 4 slots:
					sameNamedMethods = new HashSet<>(4);
					methods.put(methodName, sameNamedMethods);
					sameNamedMethods.add(method);
				}
				else {
					// We already have a method with this name.  Is this method overridden?
					boolean unique = true;
					if (!ignoreVisibility) {
						for (MethodBinding existing : sameNamedMethods) {
							MethodVerifier verifier = _env.getLookupEnvironment().methodVerifier();
							if (verifier.doesMethodOverride(existing, method)) {
								unique = false;
								break;
							}
						}
					}
					if (unique) {
						sameNamedMethods.add(method);
					}
				}
			}
		}
	}

	/* (non-Javadoc)
	 * @see javax.lang.model.util.Elements#getBinaryName(javax.lang.model.element.TypeElement)
	 */
	@Override
	public Name getBinaryName(TypeElement type) {
		TypeElementImpl typeElementImpl = (TypeElementImpl) type;
		ReferenceBinding referenceBinding = (ReferenceBinding) typeElementImpl._binding;
		return new NameImpl(
			CharOperation.replaceOnCopy(referenceBinding.constantPoolName(), '/', '.'));
	}

	/* (non-Javadoc)
	 * @see javax.lang.model.util.Elements#getConstantExpression(java.lang.Object)
	 */
	@Override
	public String getConstantExpression(Object value) {
		if (!(value instanceof Integer)
				&& !(value instanceof Byte)
				&& !(value instanceof Float)
				&& !(value instanceof Double)
				&& !(value instanceof Long)
				&& !(value instanceof Short)
				&& !(value instanceof Character)
				&& !(value instanceof String)
				&& !(value instanceof Boolean)) {
			throw new IllegalArgumentException("Not a valid wrapper type : " + value.getClass()); //$NON-NLS-1$
		}
		if (value instanceof Character) {
			StringBuilder builder = new StringBuilder();
			builder.append('\'').append(value).append('\'');
			return String.valueOf(builder);
		} else if (value instanceof String) {
			StringBuilder builder = new StringBuilder();
			builder.append('\"').append(value).append('\"');
			return String.valueOf(builder);
		} else if (value instanceof Float) {
			StringBuilder builder = new StringBuilder();
			builder.append(value).append('f');
			return String.valueOf(builder);
		} else if (value instanceof Long) {
			StringBuilder builder = new StringBuilder();
			builder.append(value).append('L');
			return String.valueOf(builder);
		} else if (value instanceof Short) {
			StringBuilder builder = new StringBuilder();
			builder.append("(short)").append(value); //$NON-NLS-1$
			return String.valueOf(builder);
		} else if (value instanceof Byte) {
			StringBuilder builder = new StringBuilder();
			builder.append("(byte)0x"); //$NON-NLS-1$
			int intValue = ((Byte) value).byteValue();
			String hexString = Integer.toHexString(intValue & 0xFF);
			if (hexString.length() < 2) {
				builder.append('0');
			}
			builder.append(hexString);
			return String.valueOf(builder);
		}
		return String.valueOf(value);
	}

	/* (non-Javadoc)
	 * @see javax.lang.model.util.Elements#getDocComment(javax.lang.model.element.Element)
	 */
	@Override
	public String getDocComment(Element e) {
		char[] unparsed = getUnparsedDocComment(e);
		return formatJavadoc(unparsed);
	}

	/**
	 * Return the entire javadoc comment on e, including the comment characters and whitespace
	 * @param e an Element of any sort, possibly with a javadoc comment.
	 * @return a String, or null if the comment is not available
	 */
	private char[] getUnparsedDocComment(Element e)
	{
		Javadoc javadoc = null;
		ReferenceContext referenceContext = null;
		switch(e.getKind()) {
			case ANNOTATION_TYPE :
			case CLASS :
			case ENUM :
			case INTERFACE :
				TypeElementImpl typeElementImpl = (TypeElementImpl) e;
				ReferenceBinding referenceBinding = (ReferenceBinding)typeElementImpl._binding;
				if (referenceBinding instanceof SourceTypeBinding) {
					SourceTypeBinding sourceTypeBinding = (SourceTypeBinding) referenceBinding;
					referenceContext = sourceTypeBinding.scope.referenceContext;
					javadoc = ((TypeDeclaration) referenceContext).javadoc;
				}
				break;
			case PACKAGE :
				// might need to handle javadoc of package-info.java file
				PackageElementImpl packageElementImpl = (PackageElementImpl) e;
				PackageBinding packageBinding = (PackageBinding) packageElementImpl._binding;
				char[][] compoundName = CharOperation.arrayConcat(packageBinding.compoundName, TypeConstants.PACKAGE_INFO_NAME);
				ReferenceBinding type = this._env.getLookupEnvironment().getType(compoundName);
				if (type != null && type.isValidBinding() && (type instanceof SourceTypeBinding)) {
					SourceTypeBinding sourceTypeBinding = (SourceTypeBinding) type;
					referenceContext = sourceTypeBinding.scope.referenceContext;
					javadoc = ((TypeDeclaration) referenceContext).javadoc;
				}
				break;
			case CONSTRUCTOR :
			case METHOD :
				ExecutableElementImpl executableElementImpl = (ExecutableElementImpl) e;
				MethodBinding methodBinding = (MethodBinding) executableElementImpl._binding;
				AbstractMethodDeclaration sourceMethod = methodBinding.sourceMethod();
				if (sourceMethod != null) {
					javadoc = sourceMethod.javadoc;
					referenceContext = sourceMethod;
				}
				break;
			case ENUM_CONSTANT :
			case FIELD :
				VariableElementImpl variableElementImpl = (VariableElementImpl) e;
				FieldBinding fieldBinding = (FieldBinding) variableElementImpl._binding;
				FieldDeclaration sourceField = fieldBinding.sourceField();
				if (sourceField != null) {
					javadoc = sourceField.javadoc;
					if (fieldBinding.declaringClass instanceof SourceTypeBinding) {
						SourceTypeBinding sourceTypeBinding = (SourceTypeBinding) fieldBinding.declaringClass;
						referenceContext = sourceTypeBinding.scope.referenceContext;
					}
				}
				break;
			default:
				return null;
		}
		if (javadoc != null && referenceContext != null) {
			char[] contents = referenceContext.compilationResult().getCompilationUnit().getContents();
			if (contents != null) {
				return CharOperation.subarray(contents, javadoc.sourceStart, javadoc.sourceEnd - 1);
			}
		}
		return null;
	}

	/**
	 * Strip the comment characters from a javadoc comment. Assume the comment is already
	 * missing its closing delimiter.
	 *
	 * Javac's behavior with regard to tab expansion and trimming of whitespace and
	 * asterisks is bizarre and undocumented.  We do our best here to emulate it.
	 */
	private static String formatJavadoc(char[] unparsed)
	{
		if (unparsed == null || unparsed.length < 5) { // delimiters take 5 chars
			return null;
		}

		String[] lines = new String(unparsed).split("\n"); //$NON-NLS-1$
		Matcher delimiterMatcher = INITIAL_DELIMITER.matcher(lines[0]);
		if (!delimiterMatcher.find()) {
			return null;
		}
		int iOpener = delimiterMatcher.end();
		lines[0] = lines[0].substring(iOpener);
		if (lines.length == 1) {
			// single-line comment.  Should trim(), but javac doesn't.
			// we should however remove the starting whitespaces
			StringBuilder sb = new StringBuilder();
			char[] chars = lines[0].toCharArray();
			boolean startingWhitespaces = true;
			for (char c : chars) {
				if (Character.isWhitespace(c))
					if (startingWhitespaces) {
						continue;
					} else {
						sb.append(c);
				} else {
					startingWhitespaces = false;
					sb.append(c);
				}
			}
			return sb.toString();
		}

		// if the first line ends with spaces after the /** then we want to insert a line separator
		int firstLine = lines[0].trim().length() > 0 ? 0 : 1;

		// If the last line is now empty, skip it
		int lastLine = lines[lines.length - 1].trim().length() > 0 ? lines.length - 1 : lines.length - 2;

		StringBuilder sb = new StringBuilder();
		if (lines[0].length() != 0 && firstLine == 1) {
			// insert a line separator only if the remaining chars on the line are whitespaces
			sb.append('\n');
		}
		boolean preserveLineSeparator = lines[0].length() == 0;
		for (int line = firstLine; line <= lastLine; ++line) {
			char[] chars = lines[line].toCharArray();
			int starsIndex = getStars(chars);
			int leadingWhitespaces = 0;
			boolean recordLeadingWhitespaces = true;
			for (int i = 0, max = chars.length; i < max; i++) {
				char c = chars[i];
				switch(c) {
					case ' ' :
						if (starsIndex == -1) {
							if (recordLeadingWhitespaces) {
								leadingWhitespaces++;
							} else {
								sb.append(c);
							}
						} else if (i >= starsIndex) {
							sb.append(c);
						}
						break;
					default :
						// convert leadingwhitespaces to spaces
						recordLeadingWhitespaces = false;
						if (leadingWhitespaces != 0) {
							int numberOfTabs = leadingWhitespaces / 8;
							if (numberOfTabs != 0) {
								for (int j = 0, max2 = numberOfTabs; j < max2; j++) {
									sb.append("        "); //$NON-NLS-1$
								}
								if ((leadingWhitespaces % 8) >= 1) {
									sb.append(' ');
								}
							} else if (line != 0) {
								// we don't want to preserve the leading spaces for the first line
								for (int j = 0, max2 = leadingWhitespaces; j < max2; j++) {
									sb.append(' ');
								}
							}
							leadingWhitespaces = 0;
							sb.append(c);
						} else if (c == '\t') {
							if (i >= starsIndex) {
								sb.append(c);
							}
						} else if (c != '*' || i > starsIndex) {
							sb.append(c);
						}
				}
			}
			
			// append a newline at the end of each line except the last, even if we skipped the last entirely
			int end = lines.length - 1;
			if (line < end) {
				sb.append('\n');
			} else if (preserveLineSeparator && line == end) {
				sb.append('\n');
			}
		}
		return sb.toString();
	}

	/**
	 * Returns the index of the last leading stars on this line, -1 if none.
	 * 
	 * @param line the given line
	 * @return the computed index
	 */
	private static int getStars(char[] line) {
		loop: for (int i = 0, max = line.length; i < max; i++) {
			char c = line[i];
			if (!Character.isWhitespace(c)) {
				if (c == '*') {
					// only whitespaces before the first star
					// consume all stars and return the last index
					for (int j = i + 1; j < max; j++) {
						if (line[j] != '*') {
							return j;
						}
					}
					return max - 1;
				}
				// no need to continue
				break loop;
			}
		}
		return -1;
	}
	/**
	 * @return all the annotation instance's explicitly set values, plus default values
	 *         for all the annotation members that are not explicitly set but that have
	 *         defaults. By comparison, {@link AnnotationMirror#getElementValues()} only
	 *         returns the explicitly set values.
	 * @see javax.lang.model.util.Elements#getElementValuesWithDefaults(javax.lang.model.element.AnnotationMirror)
	 */
	@Override
	public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValuesWithDefaults(
			AnnotationMirror a) {
		return ((AnnotationMirrorImpl)a).getElementValuesWithDefaults();
	}

	/* (non-Javadoc)
	 * @see javax.lang.model.util.Elements#getName(java.lang.CharSequence)
	 */
	@Override
	public Name getName(CharSequence cs) {
		return new NameImpl(cs);
	}

	@Override
	public PackageElement getPackageElement(CharSequence name) {
		LookupEnvironment le = _env.getLookupEnvironment(); // FIXME(SHMOD): does this lookup need to be module-aware?
		if (name.length() == 0) {
			return (PackageElement) _env.getFactory().newElement(le.defaultPackage);
		}
		char[] packageName = name.toString().toCharArray();
		PackageBinding packageBinding = le.createPackage(CharOperation.splitOn('.', packageName));
		if (packageBinding == null) {
			return null;
		}
		return (PackageElement) _env.getFactory().newElement(packageBinding);
	}

	@Override
	public PackageElement getPackageOf(Element type) {
		switch(type.getKind()) {
			case ANNOTATION_TYPE :
			case CLASS :
			case ENUM :
			case INTERFACE :
				TypeElementImpl typeElementImpl = (TypeElementImpl) type;
				ReferenceBinding referenceBinding = (ReferenceBinding)typeElementImpl._binding;
				return (PackageElement) _env.getFactory().newElement(referenceBinding.fPackage);
			case PACKAGE :
				return (PackageElement) type;
			case CONSTRUCTOR :
			case METHOD :
				ExecutableElementImpl executableElementImpl = (ExecutableElementImpl) type;
				MethodBinding methodBinding = (MethodBinding) executableElementImpl._binding;
				return (PackageElement) _env.getFactory().newElement(methodBinding.declaringClass.fPackage);
			case ENUM_CONSTANT :
			case FIELD :
				VariableElementImpl variableElementImpl = (VariableElementImpl) type;
				FieldBinding fieldBinding = (FieldBinding) variableElementImpl._binding;
				return (PackageElement) _env.getFactory().newElement(fieldBinding.declaringClass.fPackage);
			case PARAMETER :
				variableElementImpl = (VariableElementImpl) type;
				LocalVariableBinding localVariableBinding = (LocalVariableBinding) variableElementImpl._binding;
				return (PackageElement) _env.getFactory().newElement(localVariableBinding.declaringScope.classScope().referenceContext.binding.fPackage);
			case EXCEPTION_PARAMETER :
			case INSTANCE_INIT :
			case OTHER :
			case STATIC_INIT :
			case TYPE_PARAMETER :
			case LOCAL_VARIABLE :
				return null;
		default:
			break;
		}
		// unreachable
		return null;
	}

	/* (non-Javadoc)
	 * @see javax.lang.model.util.Elements#getTypeElement(java.lang.CharSequence)
	 */
	@Override
	public TypeElement getTypeElement(CharSequence name) {
		LookupEnvironment le = _env.getLookupEnvironment();
		final char[][] compoundName = CharOperation.splitOn('.', name.toString().toCharArray());
		ReferenceBinding binding = le.getType(compoundName);
		// If we didn't find the binding, maybe it's a nested type;
		// try finding the top-level type and then working downwards.
		if (null == binding) {
			ReferenceBinding topLevelBinding = null;
			int topLevelSegments = compoundName.length;
			while (--topLevelSegments > 0) {
				char[][] topLevelName = new char[topLevelSegments][];
				for (int i = 0; i < topLevelSegments; ++i) {
					topLevelName[i] = compoundName[i];
				}
				topLevelBinding = le.getType(topLevelName);
				if (null != topLevelBinding) {
					break;
				}
			}
			if (null == topLevelBinding) {
				return null;
			}
			binding = topLevelBinding;
			for (int i = topLevelSegments; null != binding && i < compoundName.length; ++i) {
				binding = binding.getMemberType(compoundName[i]);
			}
		}
		if (null == binding) {
			return null;
		}
		return new TypeElementImpl(_env, binding, null);
	}

	/* (non-Javadoc)
	 * Element A hides element B if: A and B are both fields, both nested types, or both methods; and
	 * the enclosing element of B is a superclass or superinterface of the enclosing element of A.
	 * See JLS 8.3 (for hiding of fields), 8.4.8.2 (hiding of class methods), and 8.5 (for hiding of member types).
	 * @see javax.lang.model.util.Elements#hides(javax.lang.model.element.Element, javax.lang.model.element.Element)
	 */
	@Override
	public boolean hides(Element hider, Element hidden) {
		if (hidden == null) {
			// required by API spec
			throw new NullPointerException();
		}
		return ((ElementImpl)hider).hides(hidden);
	}

	/* (non-Javadoc)
	 * @see javax.lang.model.util.Elements#isDeprecated(javax.lang.model.element.Element)
	 */
	@Override
	public boolean isDeprecated(Element e) {
		if (!(e instanceof ElementImpl)) {
			return false;
		}
		return (((ElementImpl)e)._binding.getAnnotationTagBits() & TagBits.AnnotationDeprecated) != 0;
	}

	/* (non-Javadoc)
	 * See JLS 8.4.8.1 for discussion of hiding of methods
	 * @see javax.lang.model.util.Elements#overrides(javax.lang.model.element.ExecutableElement, javax.lang.model.element.ExecutableElement, javax.lang.model.element.TypeElement)
	 */
	@Override
	public boolean overrides(ExecutableElement overrider, ExecutableElement overridden,
			TypeElement type) {
		if (overridden == null || type == null) {
			throw new NullPointerException();
		}
		return ((ExecutableElementImpl)overrider).overrides(overridden, type);
	}

	/* (non-Javadoc)
	 * @see javax.lang.model.util.Elements#printElements(java.io.Writer, javax.lang.model.element.Element[])
	 */
	@Override
	public void printElements(Writer w, Element... elements) {
		String lineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$
		for (Element element : elements) {
			try {
				w.write(element.toString());
				w.write(lineSeparator);
			} catch (IOException e) {
				// ignore
			}
		}
		try {
			w.flush();
		} catch (IOException e) {
			// ignore
		}
	}

	@Override
	public boolean isFunctionalInterface(TypeElement type) {
		if (type != null && type.getKind() == ElementKind.INTERFACE) {
			ReferenceBinding binding = (ReferenceBinding)((TypeElementImpl) type)._binding;
			if (binding instanceof SourceTypeBinding) {
				return binding.isFunctionalInterface(((SourceTypeBinding) binding).scope);
			}
		}
		return false;
	}

}
