/*******************************************************************************
 * Copyright (c) 2009, 2012 Alena Laskavaia 
 * 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:
 *     Alena Laskavaia  - initial API and implementation
 *     Patrick Hofer [bug 315528]
 *     Sergey Prigogin (Google)
 *     Marc-Andre Laperle
 *******************************************************************************/
package org.eclipse.cdt.codan.internal.checkers;

import java.util.HashSet;

import org.eclipse.cdt.codan.core.cxx.model.AbstractIndexAstChecker;
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTVisibilityLabel;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBase;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ClassTypeHelper;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPInternalBinding;

/**
 * Checker to find that class has virtual method and non virtual destructor
 * 
 * @author Alena Laskavaia
 */
public class NonVirtualDestructor extends AbstractIndexAstChecker {
	public static final String PROBLEM_ID = "org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem"; //$NON-NLS-1$
	
	// Prevent stack overflow in case: class A: public A {};
	private static HashSet<ICPPClassType> checkedClassTypes = new HashSet<ICPPClassType>();

	@Override
	public void processAst(IASTTranslationUnit ast) {
		// Traverse the AST using the visitor pattern.
		ast.accept(new OnEachClass());
	}

	private static ICPPMethod getDestructor(ICPPClassType classType, IASTNode point) {
		for (ICPPMethod method : ClassTypeHelper.getDeclaredMethods(classType, point)) {
			if (method.isDestructor()) {
				return method;
			}
		}
		return null;
	}

	private static boolean hasVirtualDestructor(ICPPClassType classType, IASTNode point) {
		checkedClassTypes.add(classType);
		ICPPMethod destructor = getDestructor(classType, point);
		if (destructor != null && destructor.isVirtual()) {
			return true;
		}
		ICPPBase[] bases = ClassTypeHelper.getBases(classType, point);   
		for (ICPPBase base : bases) {
			IBinding baseClass = base.getBaseClass();
			if (baseClass instanceof ICPPClassType) {
				ICPPClassType cppClassType = (ICPPClassType) baseClass;
				if (!checkedClassTypes.contains(cppClassType) && hasVirtualDestructor(cppClassType, point)) {
					return true;
				}
			}
		}
		return false;
	}

	private class OnEachClass extends ASTVisitor {
		OnEachClass() {
			shouldVisitDeclSpecifiers = true;
		}

		@Override
		public int visit(IASTDeclSpecifier decl) {
			if (decl instanceof ICPPASTCompositeTypeSpecifier) {
				ICPPASTCompositeTypeSpecifier spec = (ICPPASTCompositeTypeSpecifier) decl;
				IASTName className = spec.getName();
				IBinding binding = className.resolveBinding();
				if (!(binding instanceof ICPPClassType)) {
					return PROCESS_SKIP;
				}
				ICPPClassType classType = (ICPPClassType) binding;
				boolean hasVirtualDestructor = hasVirtualDestructor(classType, className);
				checkedClassTypes.clear();
				if (hasVirtualDestructor) {
					return PROCESS_SKIP;
				}
				ICPPMethod virtualMethod = null;
				for (ICPPMethod method : ClassTypeHelper.getAllDeclaredMethods(classType, className)) {
					if (!method.isDestructor() && method.isVirtual()) {
						virtualMethod = method;
					}
				}
				if (virtualMethod == null) {
					return PROCESS_SKIP;
				}
				ICPPMethod destructor = getDestructor(classType, className);
				if (destructor != null &&
						destructor.getVisibility() != ICPPASTVisibilityLabel.v_public &&
						classType.getFriends().length == 0) {
					// No error if the destructor is protected or private and there are no friends.
					return PROCESS_SKIP;
				}

				IASTNode node = decl;
				if (destructor instanceof ICPPInternalBinding) {
					IASTNode[] decls = ((ICPPInternalBinding) destructor).getDeclarations();
					if (decls != null && decls.length > 0) {
						node = decls[0];
					}
				}
				reportProblem(PROBLEM_ID, node, new String(className.getSimpleID()),
						virtualMethod.getName());
				return PROCESS_SKIP;
			}
			return PROCESS_CONTINUE;
		}
	}
}
