/*************************************************************************************
 * Copyright (c) 2008-2014 Red Hat, Inc. 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:
 *     JBoss by Red Hat - Initial implementation.
 ************************************************************************************/

package org.jboss.tools.arquillian.core.internal.util;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.codehaus.plexus.util.IOUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModelMarker;
import org.eclipse.jdt.core.IJavaModelStatusConstants;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IRegion;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.junit.JUnitMessages;
import org.eclipse.jdt.internal.junit.launcher.ITestKind;
import org.eclipse.jdt.internal.junit.util.CoreTestSearchEngine;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.jboss.tools.arquillian.core.ArquillianCoreActivator;
import org.jboss.tools.arquillian.core.internal.ArquillianConstants;
import org.jboss.tools.arquillian.core.internal.archives.Archive;
import org.jboss.tools.arquillian.core.internal.archives.ArchiveContainer;
import org.jboss.tools.arquillian.core.internal.archives.ArchiveLocation;
import org.jboss.tools.arquillian.core.internal.util.xpl.ArquillianSecurityException;
import org.jboss.tools.arquillian.core.internal.util.xpl.ArquillianSecurityManager;

/**
 * 
 * @author snjeza
 *
 */
public class ArquillianSearchEngine {

	private static final String ORG_JBOSS_SHRINKWRAP_API_ARCHIVE = "org.jboss.shrinkwrap.api.Archive"; //$NON-NLS-1$
	private static final String ARCHIVE_WAR = "archive.war"; //$NON-NLS-1$
	private static final String ARCHIVE_JAR = "archive.jar"; //$NON-NLS-1$
	private static final String VALUE = "value"; //$NON-NLS-1$
	public static final String ARQUILLIAN_JUNIT_ARQUILLIAN = "org.jboss.arquillian.junit.Arquillian"; //$NON-NLS-1$
	public static final String CONTAINER_DEPLOYABLE_CONTAINER = "org.jboss.arquillian.container.spi.client.container.DeployableContainer"; //$NON-NLS-1$

	public static final int CONTAINER_DEPLOYABLE_CONTAINER_NOT_EXISTS = 0;
	
	private static class Annotation {
	
		private static final Annotation RUN_WITH = new Annotation("org.junit.runner.RunWith"); //$NON-NLS-1$
		private static final Annotation TEST = new Annotation("org.junit.Test"); //$NON-NLS-1$
		private static final Annotation DEPLOYMENT = new Annotation(ArquillianUtility.ORG_JBOSS_ARQUILLIAN_CONTAINER_TEST_API_DEPLOYMENT);
		private final String fName;
	
		private Annotation(String name) {
			fName= name;
		}
		
		private String getName() {
			return fName;
		}
	
		public boolean annotatesTypeOrSuperTypes(ITypeBinding type, String value) {
			while (type != null) {
				if (annotates(type.getAnnotations(), fName, value)) {
					return true;
				}
				type= type.getSuperclass();
			}
			return false;
		}
	
		public boolean annotatesAtLeastOneMethod(ITypeBinding type) {
			while (type != null) {
				IMethodBinding[] declaredMethods= type.getDeclaredMethods();
				for (int i= 0; i < declaredMethods.length; i++) {
					IMethodBinding curr= declaredMethods[i];
					if (annotates(curr.getAnnotations(), fName, null)) {
						return true;
					}
				}
				type= type.getSuperclass();
			}
			return false;
		}
		
		public boolean annotatesAtLeastOneMethod(ITypeBinding type, String value) {
			while (type != null) {
				IMethodBinding[] declaredMethods= type.getDeclaredMethods();
				for (int i= 0; i < declaredMethods.length; i++) {
					IMethodBinding curr= declaredMethods[i];
					if (annotates(curr.getAnnotations(), fName, value)) {
						return true;
					}
				}
				type= type.getSuperclass();
			}
			return false;
		}
	}
	
	private static class AnnotationSearchRequestor extends SearchRequestor {

		private final Collection fResult;
		private final ITypeHierarchy fHierarchy;

		public AnnotationSearchRequestor(ITypeHierarchy hierarchy, Collection result) {
			fHierarchy= hierarchy;
			fResult= result;
		}

		public void acceptSearchMatch(SearchMatch match) throws CoreException {
			if (match.getAccuracy() == SearchMatch.A_ACCURATE && !match.isInsideDocComment()) {
				Object element= match.getElement();
				if (element instanceof IType || element instanceof IMethod) {
					IMember member= (IMember) element;
					IType type= member.getElementType() == IJavaElement.TYPE ? (IType) member : member.getDeclaringType();
					addTypeAndSubtypes(type);
				}
			}
		}

		private void addTypeAndSubtypes(IType type) {
			if (fResult.add(type)) {
				IType[] subclasses= fHierarchy.getSubclasses(type);
				for (int i= 0; i < subclasses.length; i++) {
					addTypeAndSubtypes(subclasses[i]);
				}
			}
		}
	}

	/**
	 * Returns whether a Java element is an Arquillian JUnit test
	 * 
	 * @param element the Java element
	 * @param checkDeployment check if there is a deployment method
	 * @param checkTest check if there is a test method
	 * @return true if a Java element is an Arquillian JUnit test
	 */
	public static boolean isArquillianJUnitTest(IJavaElement element, boolean checkDeployment, boolean checkTest) {
		return isArquillianJUnitTest(element, checkDeployment, checkTest, true);
	}
	
	/**
	 * Returns whether a Java element is an Arquillian JUnit test
	 * 
	 * @param element the Java element
	 * @param checkDeployment check if there is a deployment method
	 * @param checkTest check if there is a test method
	 * @param checkAbstract check an abstract class
	 * @return true if a Java element is an Arquillian JUnit test
	 */
	public static boolean isArquillianJUnitTest(IJavaElement element, boolean checkDeployment, boolean checkTest, boolean checkAbstract) {
		if (element == null || element.getJavaProject() == null || !ArquillianUtility.isArquillianProject(element.getJavaProject().getProject())) {
			return false;
		}
		try {
			IType testType= null;
			if (element instanceof ICompilationUnit) {
				testType= (((ICompilationUnit) element)).findPrimaryType();
			} else if (element instanceof IClassFile) {
				testType= (((IClassFile) element)).getType();
			} else if (element instanceof IType) {
				testType= (IType) element;
			} else if (element instanceof IMember) {
				testType= ((IMember) element).getDeclaringType();
			}
			if (testType != null && testType.exists()) {
				return isArquillianJUnitTest(testType, checkDeployment, checkTest, checkAbstract);
			}
		} catch (CoreException e) {
			// ignore, return false
		}
		return false;
	}

	public static boolean isAccessibleClass(IType type) throws JavaModelException {
		if (type == null) {
			return false;
		}
		int flags= type.getFlags();
		if (Flags.isInterface(flags)) {
			return false;
		}
		IJavaElement parent= type.getParent();
		while (true) {
			if (parent instanceof ICompilationUnit || parent instanceof IClassFile) {
				return true;
			}
			if (!(parent instanceof IType) || !Flags.isStatic(flags) || !Flags.isPublic(flags)) {
				return false;
			}
			flags= ((IType) parent).getFlags();
			parent= parent.getParent();
		}
	}

	public static boolean hasSuiteMethod(IType type) throws JavaModelException {
		IMethod method= type.getMethod("suite", new String[0]); //$NON-NLS-1$
		if (!method.exists())
			return false;
	
		if (!Flags.isStatic(method.getFlags()) || !Flags.isPublic(method.getFlags())) {
			return false;
		}
		if (!Signature.getSimpleName(Signature.toString(method.getReturnType())).equals(ArquillianUtility.SIMPLE_TEST_INTERFACE_NAME)) {
			return false;
		}
		return true;
	}

	private static boolean isArquillianJUnitTest(IType type, boolean checkDeployment, boolean checkTest, boolean checkAbstract) throws JavaModelException {
		if (type == null || type.getJavaProject() == null || !ArquillianUtility.isArquillianProject(type.getJavaProject().getProject())) {
			return false;
		}
		if (isAccessibleClass(type)) {
			ITypeBinding binding = getTypeBinding(type);
			if (binding != null) {
				return isTest(binding, checkDeployment, checkTest, checkAbstract);
			}
		}
		return false;
	
	}

	private static ITypeBinding getTypeBinding(IType type)
			throws JavaModelException {
		ASTParser parser= ASTParser.newParser(AST.JLS8);
		
		if (type.getCompilationUnit() != null) {
			parser.setSource(type.getCompilationUnit());
		} else if (!isAvailable(type.getSourceRange())) { // class file with no source
			parser.setProject(type.getJavaProject());
			IBinding[] bindings= parser.createBindings(new IJavaElement[] { type }, null);
			if (bindings.length == 1 && bindings[0] instanceof ITypeBinding) {
				return (ITypeBinding) bindings[0];
			}
			return null;
		} else {
			parser.setSource(type.getClassFile());
		}
		parser.setFocalPosition(0);
		parser.setResolveBindings(true);
		CompilationUnit root= (CompilationUnit) parser.createAST(null);
		ASTNode node= root.findDeclaringNode(type.getKey());
		if (node instanceof TypeDeclaration) {
			return ((TypeDeclaration) node).resolveBinding();
		}
		return null;
	}

	static boolean isAvailable(ISourceRange range) {
		return range != null && range.getOffset() != -1;
	}

	static boolean isTest(ITypeBinding binding, boolean checkDeployment, boolean checkTest, boolean checkAbstract) {
		if (checkAbstract && Modifier.isAbstract(binding.getModifiers()))
			return false;
	
		if (Annotation.RUN_WITH.annotatesTypeOrSuperTypes(binding, ARQUILLIAN_JUNIT_ARQUILLIAN)) {
			if (checkDeployment && !Annotation.DEPLOYMENT.annotatesAtLeastOneMethod(binding)) {
				return false;
			}
			if (checkTest && !Annotation.TEST.annotatesAtLeastOneMethod(binding)) {
				return false;
			}
			return true;
		}
		return isTestImplementor(binding);
	}

	private static boolean isImplementor(ITypeBinding type, String interfaceName) {
		ITypeBinding superType= type.getSuperclass();
		if (superType != null && isTestImplementor(superType)) {
			return true;
		}
		ITypeBinding[] interfaces= type.getInterfaces();
		for (int i= 0; i < interfaces.length; i++) {
			ITypeBinding curr= interfaces[i];
			if (interfaceName.equals(curr.getQualifiedName()) || isTestImplementor(curr)) {
				return true;
			}
		}
		return false;
	}
	
	public static boolean isTestImplementor(ITypeBinding type) {
		return isImplementor(type, ArquillianUtility.TEST_INTERFACE_NAME);
	}

	/**
	 * Validates a deployable container.</br>
	 * The validation works in the following way:
	 * <ul>
	 * <li>scan a classpath</li>
     * <li>if there is no implementation of the DeployableContainer interface, it reports an error</li>
     * <li>if there is exactly one implementation of this interface, nothing reported (the Arquillian environment is valid)</li>
     * <li>if there is more than one implementation of this interface, we would check if there is the META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension resource on the classpath as well as if there is a class defined in this resource.</li>
     * </ul>
     * If both exist, nothing is reported.
     * Otherwise, Arquillian Eclipse reports an error.
     * 
	 * @param javaProject the Java Project
	 * @return true if a project contains a valid deployable container 
	 */
	public static IStatus validateDeployableContainer(IJavaProject javaProject) {
		try {
			IType type = javaProject.findType(CONTAINER_DEPLOYABLE_CONTAINER);
			if (type == null) {
				return new Status(
						IStatus.ERROR,
						ArquillianCoreActivator.PLUGIN_ID,
						CONTAINER_DEPLOYABLE_CONTAINER_NOT_EXISTS,
						"Cannot find 'org.jboss.arquillian.container.spi.client.container.DeployableContainer' on the project build path. Do you want to add it?",
						null);
			}
			ITypeHierarchy hierarchy = type.newTypeHierarchy(javaProject, new NullProgressMonitor());
            IType[] subTypes = hierarchy.getAllSubtypes(type);
            int count = 0;
            for (IType subType:subTypes) {
            	if (isNonAbstractClass(subType)) {
            		count++;
            	}
            }
            if (count == 0) {
            	return new Status(IStatus.ERROR, ArquillianCoreActivator.PLUGIN_ID, 1 ,  
            			"Arquillian tests are missing an implementation of DeploymentContainer on the project build path. Do you want to configure it?", null);
            }
            if (count == 1) {
            	return Status.OK_STATUS;
            }
            
            IFile file = getFile(javaProject, "META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension");
			if (file != null) {
				InputStream is = null;
				try {
					is = file.getContents();
					if (is != null) {
						String content = IOUtil.toString(is);
						if (content != null && !content.isEmpty()) {
							content = content.trim();
							IType loadableExtension = javaProject
									.findType(content);
							if (loadableExtension != null
									&& loadableExtension.exists()) {
								ITypeBinding binding = getTypeBinding(loadableExtension);
								if (isImplementor(binding, "org.jboss.arquillian.core.spi.LoadableExtension")) {
									return Status.OK_STATUS;
								}
							}
						}
					}
				} catch (Exception e) {
					ArquillianCoreActivator.log(e);
				} finally {
					if (is != null) {
						try {
							is.close();
						} catch (IOException e) {
							// ignore
						}
					}
				}
			}
		} catch (JavaModelException e) {
			return new Status(IStatus.ERROR, ArquillianCoreActivator.PLUGIN_ID, e.getLocalizedMessage(), e);
		}
		return new Status(IStatus.ERROR, ArquillianCoreActivator.PLUGIN_ID, 1 ,  
    			"Arquillian tests require exactly one implementation of DeploymentContainer on the project build path but several were found. Do you want to configure it?", null);
	}
	
	private static IFile getFile(IJavaProject javaProject, String fileName) throws JavaModelException {
		IClasspathEntry[] rawClasspath = javaProject.getRawClasspath();
		for (IClasspathEntry entry : rawClasspath) {
			if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
				IPackageFragmentRoot[] roots = javaProject
						.findPackageFragmentRoots(entry);
				if (roots == null) {
					continue;
				}
				for (IPackageFragmentRoot root : roots) {
					IPath path = root.getPath();
					path = path.append(fileName);
					IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
					IFile file = wsRoot.getFile(path);
					if (file != null && file.exists()) {
						return file;
					}			
				}
			}
		}
		return null;
	}

	public static boolean isNonAbstractClass(IType type) throws JavaModelException {
		int flags= type.getFlags();
		if (Flags.isInterface(flags)) {
			return false;
		}
		if (Flags.isAbstract(flags)) {
			return false;
		}
		return true;
	}
	
	public static IType[] findTests(IRunnableContext context, final IJavaElement element, final ITestKind testKind) throws InvocationTargetException, InterruptedException {
		final Set<IType> result= new HashSet<IType>();

		IRunnableWithProgress runnable= new IRunnableWithProgress() {
			public void run(IProgressMonitor pm) throws InterruptedException, InvocationTargetException {
				try {
					findTestsInContainer(element, result, pm);
				} catch (CoreException e) {
					throw new InvocationTargetException(e);
				}
			}
		};
		context.run(true, true, runnable);
		return result.toArray(new IType[result.size()]);
	}
	
	public static void findTestsInContainer(IJavaElement element, Set result, IProgressMonitor pm) throws CoreException {
		findTestsInContainer(element, result, pm, true, true, true);
	}
	
	public static void findTestsInContainer(IJavaElement element, Set result, IProgressMonitor pm,
			boolean checkDeployment, boolean checkTest, boolean checkSuite) throws CoreException {
		if (element == null || result == null) {
			throw new IllegalArgumentException();
		}

		if (element instanceof IType) {
			if (isArquillianJUnitTest((IType) element, checkDeployment, checkTest, true)) {
				result.add(element);
				return;
			}
		}

		if (pm == null)
			pm= new NullProgressMonitor();

		try {
			pm.beginTask(JUnitMessages.JUnit4TestFinder_searching_description, 4);

			IRegion region= CoreTestSearchEngine.getRegion(element);
			ITypeHierarchy hierarchy= JavaCore.newTypeHierarchy(region, null, new SubProgressMonitor(pm, 1));
			IType[] allClasses= hierarchy.getAllClasses();

			// search for all types with references to RunWith and Test and all subclasses
			HashSet candidates= new HashSet(allClasses.length);
			SearchRequestor requestor= new AnnotationSearchRequestor(hierarchy, candidates);

			IJavaSearchScope scope= SearchEngine.createJavaSearchScope(allClasses, IJavaSearchScope.SOURCES);
			int matchRule= SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE;
			SearchPattern runWithPattern= SearchPattern.createPattern(Annotation.RUN_WITH.getName(), IJavaSearchConstants.ANNOTATION_TYPE, IJavaSearchConstants.ANNOTATION_TYPE_REFERENCE, matchRule);
			//SearchPattern testPattern= SearchPattern.createPattern(Annotation.TEST.getName(), IJavaSearchConstants.ANNOTATION_TYPE, IJavaSearchConstants.ANNOTATION_TYPE_REFERENCE, matchRule);

			//SearchPattern annotationsPattern= SearchPattern.crateOrPattern(runWithPattern, testPattern);
			SearchPattern annotationsPattern = runWithPattern;
			SearchParticipant[] searchParticipants= new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() };
			new SearchEngine().search(annotationsPattern, searchParticipants, scope, requestor, new SubProgressMonitor(pm, 2));

			// find all classes in the region
			for (Iterator iterator= candidates.iterator(); iterator.hasNext();) {
				IType curr= (IType) iterator.next();
				if (isAccessibleClass(curr) && !Flags.isAbstract(curr.getFlags()) && region.contains(curr)) {
					ITypeBinding binding = getTypeBinding(curr);
					if (binding != null && isTest(binding, true, true, true)) {
						result.add(curr);
					}
				}
			}

			if (checkSuite) {
				CoreTestSearchEngine.findSuiteMethods(element, result, new SubProgressMonitor(pm, 1));
			}
		} finally {
			pm.done();
		}
	}
	
	public static boolean hasArquillianType(IJavaProject javaProject) {
		if (javaProject == null) {
			return false;
		}
		try {
			IType type = javaProject.findType(ARQUILLIAN_JUNIT_ARQUILLIAN);
			return type != null;
		} catch (JavaModelException e) {
			// ignore
		}
		return false;
	}
	
	public static boolean isArquillianJUnitTest(IResourceProxy proxy,
			IJavaProject project) {
		if (proxy == null || project == null || ! (proxy.requestResource() instanceof IFile)) {
			return false;
		}
		IFile file = (IFile) proxy.requestResource();
		IJavaElement element = JavaCore.create(file);
		if (!(element instanceof ICompilationUnit)) {
			return false;
		}
		ICompilationUnit cu = (ICompilationUnit) element;
		IType type = cu.findPrimaryType();
		ITypeBinding binding;
		try {
			if (!isAccessibleClass(type)) {
				return false;
			}
			binding = getTypeBinding(type);
			if (binding == null) {
				return false;
			}
		} catch (JavaModelException e) {
			ArquillianCoreActivator.log(e);
			return false;
		}
		if (Modifier.isAbstract(binding.getModifiers())) {
			return false;
		}
		
		if (!Annotation.RUN_WITH.annotatesTypeOrSuperTypes(binding, ARQUILLIAN_JUNIT_ARQUILLIAN)) {
			return false;
		}
		
		return true;
	}

	public static boolean hasDeploymentMethod(IType type) {
		if (type == null) {
			return false;
		}
		try {
			return getDeploymentMethods(type).size() > 0;
		} catch (JavaModelException e) {
			ArquillianCoreActivator.log(e);
			return false;
		}
	}
	
	public static boolean hasInvalidDeploymentMethod(IType type) {
		if (type == null) {
			return false;
		}
		try {
			return getDeploymentMethods(type).size() > 0;
		} catch (JavaModelException e) {
			ArquillianCoreActivator.log(e);
			return false;
		}
	}

	public static boolean hasTestMethod(IType type) {
		try {
			ITypeBinding binding = getTypeBinding(type);
			return Annotation.TEST.annotatesAtLeastOneMethod(binding);
		} catch (JavaModelException e) {
			ArquillianCoreActivator.log(e);
			return false;
		}
	}
	
	public static boolean annotates(IAnnotationBinding[] annotations, String fName, String val) {
		for (int i= 0; i < annotations.length; i++) {
			ITypeBinding annotationType= annotations[i].getAnnotationType();
			if (annotationType != null && (annotationType.getQualifiedName().equals(fName))) {
				if (val == null) {
					return true;
				}
				IMemberValuePairBinding[] pairs = annotations[i].getAllMemberValuePairs();
				if (pairs != null) {
					for (IMemberValuePairBinding pair : pairs) {
						if (VALUE.equals(pair.getName())) {
							Object object = pair.getValue();
							if (object instanceof ITypeBinding) {
								ITypeBinding value = (ITypeBinding) object;
								if (val.equals(value.getQualifiedName())) {
									return true;
								}
							}
						}
					}
				}
			}
		}
		return  false;
	}
	
	public static List<Archive> getDeploymentArchives(IType type, boolean create) {
		List<Archive> archives = new ArrayList<Archive>();
		if (type == null || !hasDeploymentMethod(type)) {
			return archives;
		}
		String projectName = null;
		ICompilationUnit cu = type.getCompilationUnit();
		IJavaProject javaProject = null;
		if (cu != null) {
			IResource resource = cu.getResource();
			if (resource != null) {
				IProject project = resource.getProject();
				if (project != null) {
					projectName = project.getName();
					javaProject = JavaCore.create(project);
				}
			}
		}

		if (projectName == null) {
			ArquillianCoreActivator.logWarning("Cannot find any project for the " + type.getElementName() + "type.");
			return archives;
		}
		String fqn = type.getFullyQualifiedName();
		try {
			List<IMethodBinding> deploymentMethods = getDeploymentMethods(type);
			for (IMethodBinding deploymentMethod : deploymentMethods) {
				String name = deploymentMethod.getName();
				ArchiveLocation location = new ArchiveLocation(projectName, fqn, name);
				Archive archive = ArchiveContainer.getArchive(location);
				if (archive == null || (create && archive != null && archive.getDescription() == null)) {
					SecurityManager orig = System.getSecurityManager();
					try {
						System.setSecurityManager(new ArquillianSecurityManager(orig, Thread.currentThread()));
						archive = createArchive(javaProject, type, deploymentMethod, location);
						if (archive != null) {
							ArchiveContainer.putArchive(location, archive);
						} else {
							archive = new Archive(null, location, null);
							ArchiveContainer.putArchive(location, archive);
						}
					} catch (ArquillianSecurityException e) {
						ArquillianCoreActivator.log(e);
					} finally {
						System.setSecurityManager(orig);
					}
				} 
				if (archive != null) {
					archives.add(archive);
				}
			}
		} catch (JavaModelException e) {
			ArquillianCoreActivator.log(e);
		}

		return archives;
	}

	
	private static Archive createArchive(IJavaProject javaProject, IType type, IMethodBinding deploymentMethod,
			ArchiveLocation location) {
		String className = type.getFullyQualifiedName();
		String methodName = deploymentMethod.getName();
		ClassLoader loader = ArquillianCoreActivator.getDefault().getClassLoader(javaProject);
		ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
		try {
			Thread.currentThread().setContextClassLoader(loader);
			Class<?> clazz = Class.forName(className, true, loader);
			Object object = clazz.newInstance();
			Method method = clazz.getMethod(methodName, new Class[0]);
			
			Object archiveObject = method.invoke(object, new Object[0]);
			Class<?> archiveClass = archiveObject.getClass();
			
			Class<?> formatterClass = Class.forName("org.jboss.arquillian.container.test.impl.client.deployment.tool.ToolingDeploymentFormatter", true, loader); //$NON-NLS-1$
			
			Constructor<?> constructor = formatterClass.getDeclaredConstructor(new Class[] {Class.class} );
			
			Object formatterObject = constructor.newInstance(new Object[] { clazz } );
			
			Class<?> formatter = Class.forName("org.jboss.shrinkwrap.api.formatter.Formatter", true, loader); //$NON-NLS-1$
			
			Method toStringMethod = archiveClass.getMethod("toString", new Class[] { formatter }); //$NON-NLS-1$
			Object toStringObject = toStringMethod.invoke(archiveObject, new Object[] {formatterObject});
			if (toStringObject instanceof String) {
				String description = (String) toStringObject;
				Archive archive = new Archive(description, location, javaProject);
				return archive;
			}
		} catch (OutOfMemoryError e) {
			throw new OutOfMemoryError(e.getLocalizedMessage());
		} catch (InternalError e) {
			throw new InternalError(e.getLocalizedMessage());
		} catch (StackOverflowError e) {
			throw new StackOverflowError(e.getLocalizedMessage());
		} catch (UnknownError e) {
			throw new UnknownError(e.getLocalizedMessage());
		} catch (Throwable e) {
			String message = getText(e) + "(project=" + javaProject.getProject().getName() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
			Throwable cause = e.getCause();
			int i = 0;
			while (cause != null && i++ < 5) {
				message = getText(cause) + "(project=" + javaProject.getProject().getName() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
				cause = cause.getCause();
			}
			ArquillianCoreActivator.logWarning(message);
			if (Platform.inDebugMode()) {
				ArquillianCoreActivator.log(e);
			}
			try {
				Integer severity = ArquillianUtility.getSeverity(ArquillianUtility.getPreference(ArquillianConstants.DEPLOYMENT_ARCHIVE_CANNOT_BE_CREATED));
				createProblem(message, type, deploymentMethod, severity);
			} catch (CoreException e1) {
				ArquillianCoreActivator.log(e1);
			}
		} finally {
			Thread.currentThread().setContextClassLoader(oldLoader);
		}
		return null;
	}

	private static String getText(Throwable e) {
		String text;
		if (e.getLocalizedMessage() == null || e.getLocalizedMessage().isEmpty()) {
			text = e.getClass().getName() + ": "; //$NON-NLS-1$
		} else {
			text = e.getLocalizedMessage();
		}
		return text;
	}

	private static void createProblem(String message, IType type,
			IMethodBinding deploymentMethod, Integer severity) throws CoreException {
		if (severity == null || type == null || type.getJavaProject() == null) {
			return;
		}
		boolean enable = ArquillianUtility.isValidatorEnabled(type.getJavaProject().getProject());
		if (!enable) {
			return;
		}
		ICompilationUnit cu = type.getCompilationUnit();
		if (cu == null) {
			return;
		}
		IResource resource = cu.getResource();
		if (resource == null) {
			return;
		}
		IMarker marker = resource
				.createMarker(ArquillianConstants.MARKER_RESOURCE_ID);
    	
		String[] allNames =  {
		    	IMarker.MESSAGE,
		    	IMarker.SEVERITY,
		    	IJavaModelMarker.ID,
		    	IMarker.CHAR_START,
		    	IMarker.CHAR_END,
		    	IMarker.SOURCE_ID,
		    };
		
		Object[] allValues = new Object[allNames.length];
		int index = 0;
		allValues[index++] = message;
		
		allValues[index++] = severity;
        
		allValues[index++] = ArquillianConstants.ARQUILLIAN_PROBLEM_ID;
		
		IJavaElement javaElement = deploymentMethod.getJavaElement();
		ISourceRange range = null;
		if (javaElement instanceof IMember) {
			IMember member = (IMember) javaElement;
			if (javaElement != null) {
				try {
					range = member.getNameRange();
				} catch (JavaModelException e) {
					if (e.getJavaModelStatus().getCode() != IJavaModelStatusConstants.ELEMENT_DOES_NOT_EXIST) {
						throw e;
					}
					if (!CharOperation.equals(javaElement.getElementName()
							.toCharArray(), TypeConstants.PACKAGE_INFO_NAME)) {
						throw e;
					}

				}
			}
		}
		int start = range == null ? 0 : range.getOffset();
		int end = range == null ? 1 : start + range.getLength();
		
		allValues[index++] = new Integer(start); // start
		allValues[index++] = new Integer(end > 0 ? end + 1 : end); // end

		allValues[index++] = ArquillianConstants.SOURCE_ID;
		
		marker.setAttributes(allNames, allValues);
	}

	public static List<IMethodBinding> getDeploymentMethods(IType type) throws JavaModelException {
		List<IMethodBinding> methodBindings = new ArrayList<IMethodBinding>();
		if (type == null) {
			return methodBindings;
		}
		ITypeBinding binding = getTypeBinding(type);
		while (binding != null) {
			IMethodBinding[] declaredMethods= binding.getDeclaredMethods();
			for (IMethodBinding curr:declaredMethods) {
				if (isDeploymentMethod(curr)) {
					methodBindings.add(curr);
				}
			}
			binding = binding.getSuperclass();
		}
		return methodBindings;
	}
	
	public static List<IMethodBinding> getInvalidDeploymentMethods(IType type) throws JavaModelException {
		List<IMethodBinding> methodBindings = new ArrayList<IMethodBinding>();
		if (type == null) {
			return methodBindings;
		}
		ITypeBinding binding = getTypeBinding(type);
		while (binding != null) {
			IMethodBinding[] declaredMethods= binding.getDeclaredMethods();
			for (IMethodBinding curr:declaredMethods) {
				if (isDeploymentMethod(curr, false)) {
					int modifiers = curr.getModifiers();
					if ( !((modifiers & Modifier.PUBLIC) != 0 &&
							(modifiers & Modifier.STATIC) != 0) ) {
						methodBindings.add(curr);
					}
				}
			}
			binding = binding.getSuperclass();
		}
		return methodBindings;
	}
	
	public static boolean isDeploymentMethod(IMethodBinding methodBinding, boolean testModifiers) {
		if (methodBinding == null) {
			return false;
		}
		if (annotates(methodBinding.getAnnotations(), 
				ArquillianUtility.ORG_JBOSS_ARQUILLIAN_CONTAINER_TEST_API_DEPLOYMENT, null)) {
			boolean condition;
			int modifiers = methodBinding.getModifiers();
			if (testModifiers) {
				condition = (modifiers & Modifier.PUBLIC) != 0 &&
						(modifiers & Modifier.STATIC) != 0 &&
						methodBinding.getParameterTypes().length == 0;
			} else {
				condition = methodBinding.getParameterTypes().length == 0;
			}
			if (condition) {
				ITypeBinding returnType = methodBinding.getReturnType();
				if (isArchiveType(returnType)) {
					return true;
				}
			}
		}
		return false;
	}
	
	public static boolean isDeploymentMethod(IMethodBinding methodBinding) {
		return isDeploymentMethod(methodBinding, true);
	}

	public static boolean isArchiveType(ITypeBinding returnType) {
		if (returnType == null) {
			return false;
		}
		if (ORG_JBOSS_SHRINKWRAP_API_ARCHIVE.equals(returnType
				.getBinaryName())) {
			return true;
		}
		ITypeBinding[] interfaces = returnType.getInterfaces();
		for (ITypeBinding t:interfaces) {
			if (isArchiveType(t)) {
				return true;
			}
		}
		return false;
	}

	public static boolean isDeploymentMethod(IMethod method) {
		if (method == null || !method.exists())
			return false;
	
		try {
			if (!Flags.isStatic(method.getFlags()) || !Flags.isPublic(method.getFlags())) {
				return false;
			}
			if (method.getParameters().length > 0) {
				return false;
			}
			String type = method.getReturnType();
			if (type == null) {
				return false;
			}
			String typeSig = Signature.toString(type);
			if (!"Archive<?>".equals(typeSig)) {
				return false;
			}
			IAnnotation deployment = method.getAnnotation("Deployment");
			if (deployment != null && deployment.exists()) {
				return true;
			}
		} catch (JavaModelException e) {
			ArquillianCoreActivator.log(e);
			return false;
		}
		
		return false;
	}

}
