/*******************************************************************************
 * Copyright (c) 2000, 2017 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *     Rabea Gransberger <rgransberger@gmx.de> - [quick fix] Fix several visibility issues - https://bugs.eclipse.org/394692
 *******************************************************************************/
package org.eclipse.jdt.internal.corext.util;

import org.eclipse.core.runtime.Assert;

import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.TypeDeclaration;

/**
 * Java element flags from a Java language model point of view.
 * <p>
 * {@link IMember#getFlags()} (and hence {@link Flags}) only take explicitly declared flags
 * from source into account.
 * This class also considers the implicit properties as defined in the Java Language Specification.
 * </p>
 * <p>
 * <code>JdtFlags</code> is the right choice for most analysis tasks and for presentation in the UI.
 * </p>
 */
// @see JDTUIHelperClasses
public class JdtFlags {
	private JdtFlags(){
	}

	public static final String VISIBILITY_STRING_PRIVATE= 	"private";		//$NON-NLS-1$
	public static final String VISIBILITY_STRING_PACKAGE= 	"";				//$NON-NLS-1$
	public static final String VISIBILITY_STRING_PROTECTED= 	"protected";	//$NON-NLS-1$
	public static final String VISIBILITY_STRING_PUBLIC= 	"public";		//$NON-NLS-1$


	public static final int VISIBILITY_CODE_INVALID= 	-1;

	public static boolean isDefaultMethod(IMethodBinding method) {
		return Modifier.isDefault(method.getModifiers());
	}

	public static boolean isDefaultMethod(IMethod method) throws JavaModelException {
		return Flags.isDefaultMethod(method.getFlags());
	}

	public static boolean isAbstract(IMember member) throws JavaModelException{
		int flags= member.getFlags();
		if (!member.isBinary() && isInterfaceOrAnnotationMethod(member)) {
			return !Flags.isPrivate(flags) && !Flags.isStatic(flags) && !Flags.isDefaultMethod(flags);
		}
		return Flags.isAbstract(flags);
	}

	public static boolean isAbstract(IMethodBinding member) {
		return Modifier.isAbstract(member.getModifiers());
	}

	public static boolean isStatic(BodyDeclaration bodyDeclaration) {
		if (isNestedInterfaceOrAnnotation(bodyDeclaration))
			return true;
		int nodeType= bodyDeclaration.getNodeType();
		if (!(nodeType == ASTNode.METHOD_DECLARATION || nodeType == ASTNode.ANNOTATION_TYPE_MEMBER_DECLARATION) &&
				isInterfaceOrAnnotationMember(bodyDeclaration))
			return true;
		if (bodyDeclaration instanceof EnumConstantDeclaration)
			return true;
		if (bodyDeclaration instanceof EnumDeclaration && bodyDeclaration.getParent() instanceof AbstractTypeDeclaration)
			return true;
		return Modifier.isStatic(bodyDeclaration.getModifiers());
	}
	
	public static boolean isStatic(IMember member) throws JavaModelException {
		if (isNestedInterfaceOrAnnotation(member))
			return true;
		if (member.getElementType() != IJavaElement.METHOD
				&& isInterfaceOrAnnotationMember(member))
			return true;
		if (isEnum(member) && (member.getElementType() == IJavaElement.FIELD || member.getDeclaringType() != null))
			return true;
		return Flags.isStatic(member.getFlags());
	}

	public static boolean isStatic(IMethodBinding methodBinding){
		return Modifier.isStatic(methodBinding.getModifiers());
	}

	public static boolean isDeprecated(IMember member) throws JavaModelException{
		return Flags.isDeprecated(member.getFlags());
	}

	public static boolean isFinal(IMember member) throws JavaModelException{
		if (isInterfaceOrAnnotationField(member))
			return true;
		if (isAnonymousType(member))
			return true;
		if (isEnumConstant(member) || isEnumTypeFinal(member))
			return true;
		return Flags.isFinal(member.getFlags());
	}

	private static boolean isEnumTypeFinal(IMember member) throws JavaModelException {
		if (!(isEnum(member) && member.getElementType() == IJavaElement.TYPE))
			return false;
		// An enum type is implicitly final unless it contains at least one enum constant that has a class body.
		IJavaElement[] children= member.getChildren();
		for (IJavaElement child : children) {
			if (isEnumConstant((IMember) child) && ((IField) child).getChildren().length != 0) {
				return false;
			}
		}
		return true;
	}

	public static boolean isNative(IMember member) throws JavaModelException{
		return Flags.isNative(member.getFlags());
	}

	public static boolean isPackageVisible(IMember member) throws JavaModelException{
		return (! isPrivate(member) && ! isProtected(member) && ! isPublic(member));
	}

	public static boolean isPackageVisible(BodyDeclaration bodyDeclaration) {
		return (! isPrivate(bodyDeclaration) && ! isProtected(bodyDeclaration) && ! isPublic(bodyDeclaration));
	}

	public static boolean isPackageVisible(IBinding binding) {
		return (! isPrivate(binding) && ! isProtected(binding) && ! isPublic(binding));
	}

	public static boolean isPrivate(IMember member) throws JavaModelException{
		return Flags.isPrivate(member.getFlags());
	}

	public static boolean isPrivate(BodyDeclaration bodyDeclaration) {
		return Modifier.isPrivate(bodyDeclaration.getModifiers());
	}

	public static boolean isPrivate(IBinding binding) {
		return Modifier.isPrivate(binding.getModifiers());
	}

	public static boolean isProtected(IMember member) throws JavaModelException{
		return Flags.isProtected(member.getFlags());
	}

	public static boolean isProtected(BodyDeclaration bodyDeclaration) {
		return Modifier.isProtected(bodyDeclaration.getModifiers());
	}

	public static boolean isProtected(IBinding binding) {
		return Modifier.isProtected(binding.getModifiers());
	}

	public static boolean isPublic(IMember member) throws JavaModelException{
		if (isInterfaceOrAnnotationMember(member))
			return true;
		if (isEnumConstant(member))
			return true;
		return Flags.isPublic(member.getFlags());
	}

	public static boolean isPublic(IBinding binding) {
		if (isInterfaceOrAnnotationMember(binding))
			return true;
		return Modifier.isPublic(binding.getModifiers());
	}


	public static boolean isPublic(BodyDeclaration bodyDeclaration) {
		if (isInterfaceOrAnnotationMember(bodyDeclaration))
			return true;
		return Modifier.isPublic(bodyDeclaration.getModifiers());
	}

	public static boolean isStatic(IVariableBinding variableBinding){
		if (isInterfaceOrAnnotationMember(variableBinding))
			return true;
		return Modifier.isStatic(variableBinding.getModifiers());
	}

	public static boolean isSynchronized(IMember member) throws JavaModelException{
		return Flags.isSynchronized(member.getFlags());
	}

	public static boolean isSynthetic(IMember member) throws JavaModelException{
		return Flags.isSynthetic(member.getFlags());
	}

	public static boolean isAnnotation(IMember member) throws JavaModelException{
		return Flags.isAnnotation(member.getFlags());
	}

	public static boolean isEnum(IMember member) throws JavaModelException{
		return Flags.isEnum(member.getFlags());
	}

	public static boolean isVarargs(IMethod method) throws JavaModelException{
		return Flags.isVarargs(method.getFlags());
	}

	public static boolean isTransient(IMember member) throws JavaModelException{
		return Flags.isTransient(member.getFlags());
	}

	public static boolean isVolatile(IMember member) throws JavaModelException{
		return Flags.isVolatile(member.getFlags());
	}

	private static boolean isInterfaceOrAnnotationMethod(IMember member) throws JavaModelException {
		return member.getElementType() == IJavaElement.METHOD && isInterfaceOrAnnotationMember(member);
	}

	private static boolean isInterfaceOrAnnotationField(IMember member) throws JavaModelException {
		return member.getElementType() == IJavaElement.FIELD && isInterfaceOrAnnotationMember(member);
	}

	private static boolean isInterfaceOrAnnotationMember(IMember member) throws JavaModelException {
		return member.getDeclaringType() != null && JavaModelUtil.isInterfaceOrAnnotation(member.getDeclaringType());
	}

	private static boolean isInterfaceOrAnnotationMember(IBinding binding) {
		ITypeBinding declaringType= null;
		if (binding instanceof IVariableBinding) {
			declaringType= ((IVariableBinding) binding).getDeclaringClass();
		} else if (binding instanceof IMethodBinding) {
			declaringType= ((IMethodBinding) binding).getDeclaringClass();
		} else if (binding instanceof ITypeBinding) {
			declaringType= ((ITypeBinding) binding).getDeclaringClass();
		}
		return declaringType != null && (declaringType.isInterface() || declaringType.isAnnotation());
	}

	private static boolean isInterfaceOrAnnotationMember(BodyDeclaration bodyDeclaration) {
		return isInterfaceOrAnnotation(bodyDeclaration.getParent());
	}

	private static boolean isInterfaceOrAnnotation(ASTNode node) {
		boolean isInterface= (node instanceof TypeDeclaration) && ((TypeDeclaration) node).isInterface();
		boolean isAnnotation= node instanceof AnnotationTypeDeclaration;
		return isInterface || isAnnotation;
	}

	private static boolean isNestedInterfaceOrAnnotation(BodyDeclaration bodyDeclaration) {
		return bodyDeclaration.getParent() instanceof AbstractTypeDeclaration && isInterfaceOrAnnotation(bodyDeclaration);
	}
	
	private static boolean isNestedInterfaceOrAnnotation(IMember member) throws JavaModelException{
		return member.getElementType() == IJavaElement.TYPE &&
				member.getDeclaringType() != null &&
				JavaModelUtil.isInterfaceOrAnnotation((IType)member);
	}

	private static boolean isEnumConstant(IMember member) throws JavaModelException {
		return member.getElementType() == IJavaElement.FIELD && isEnum(member);
	}

	private static boolean isAnonymousType(IMember member) throws JavaModelException {
		return member.getElementType() == IJavaElement.TYPE &&
				((IType)member).isAnonymous();
	}

	public static int getVisibilityCode(IMember member) throws JavaModelException {
		if (isPublic(member))
			return Modifier.PUBLIC;
		else if (isProtected(member))
			return Modifier.PROTECTED;
		else if (isPackageVisible(member))
			return Modifier.NONE;
		else if (isPrivate(member))
			return Modifier.PRIVATE;
		Assert.isTrue(false);
		return VISIBILITY_CODE_INVALID;
	}

	public static int getVisibilityCode(BodyDeclaration bodyDeclaration) {
		if (isPublic(bodyDeclaration))
			return Modifier.PUBLIC;
		else if (isProtected(bodyDeclaration))
			return Modifier.PROTECTED;
		else if (isPackageVisible(bodyDeclaration))
			return Modifier.NONE;
		else if (isPrivate(bodyDeclaration))
			return Modifier.PRIVATE;
		Assert.isTrue(false);
		return VISIBILITY_CODE_INVALID;
	}

	public static int getVisibilityCode(IBinding binding) {
		if (isPublic(binding))
			return Modifier.PUBLIC;
		else if (isProtected(binding))
			return Modifier.PROTECTED;
		else if (isPackageVisible(binding))
			return Modifier.NONE;
		else if (isPrivate(binding))
			return Modifier.PRIVATE;
		Assert.isTrue(false);
		return VISIBILITY_CODE_INVALID;
	}


	public static String getVisibilityString(int visibilityCode){
		if (Modifier.isPublic(visibilityCode))
			return VISIBILITY_STRING_PUBLIC;
		if (Modifier.isProtected(visibilityCode))
			return VISIBILITY_STRING_PROTECTED;
		if (Modifier.isPrivate(visibilityCode))
			return VISIBILITY_STRING_PRIVATE;
		return VISIBILITY_STRING_PACKAGE;
	}

	public static int getVisibilityCode(String visibilityString) {
		Assert.isNotNull(visibilityString);
		if (VISIBILITY_STRING_PACKAGE.equals(visibilityString))
			return 0;
		else if (VISIBILITY_STRING_PRIVATE.equals(visibilityString))
			return Modifier.PRIVATE;
		else if (VISIBILITY_STRING_PROTECTED.equals(visibilityString))
			return Modifier.PROTECTED;
		else if (VISIBILITY_STRING_PUBLIC.equals(visibilityString))
			return Modifier.PUBLIC;
		return VISIBILITY_CODE_INVALID;
	}

	public static void assertVisibility(int visibility){
		Assert.isTrue(	visibility == Modifier.PUBLIC ||
		            	visibility == Modifier.PROTECTED ||
		            	visibility == Modifier.NONE ||
		            	visibility == Modifier.PRIVATE);
	}

	/**
	 * Compares two visibilities.
	 *
	 * @param newVisibility the 'new' visibility
	 * @param oldVisibility the 'old' visibility
	 * @return <code>true</code> iff the 'new' visibility is strictly higher than the old visibility
	 *
	 * @see Modifier#PUBLIC
	 * @see Modifier#PROTECTED
	 * @see Modifier#NONE
	 * @see Modifier#PRIVATE
	 */
	public static boolean isHigherVisibility(int newVisibility, int oldVisibility){
		assertVisibility(oldVisibility);
		assertVisibility(newVisibility);
		switch (oldVisibility) {
			case Modifier.PRIVATE :
				return 	newVisibility == Modifier.NONE
						||	newVisibility == Modifier.PUBLIC
						||  newVisibility == Modifier.PROTECTED;
			case Modifier.NONE :
				return 	newVisibility == Modifier.PUBLIC
						||  newVisibility == Modifier.PROTECTED;

			case Modifier.PROTECTED :
				return newVisibility == Modifier.PUBLIC;

			case Modifier.PUBLIC :
				return false;
			default:
				Assert.isTrue(false);
				return false;
		}
	}

	public static int getLowerVisibility(int visibility1, int visibility2) {
		if (isHigherVisibility(visibility1, visibility2))
			return visibility2;
		else
			return visibility1;
	}

	/**
	 * Gets the higher visibility of the given parameters.
	 * 
	 * @param visibility1 First visibility value to compare. Valid inputs are the {@link Modifier} visibility constants.
	 * @param visibility2 Second visibility value to compare. Valid inputs are the {@link Modifier} visibility constants.
	 * @return The parameter value with the higher visibility.
	 * @since 3.9
	 */
	public static int getHigherVisibility(int visibility1, int visibility2) {
		if (isHigherVisibility(visibility1, visibility2))
			return visibility1;
		else
			return visibility2;
	}

	public static int clearAccessModifiers(int flags) {
		return clearFlag(Modifier.PROTECTED | Modifier.PUBLIC | Modifier.PRIVATE, flags);
	}

	public static int clearFlag(int flag, int flags){
		return flags & ~ flag;
	}
}
