/*******************************************************************************
 * Copyright (c) 2000, 2019 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
 *     Microsoft Corporation - copied to jdt.core.manipulation
 *******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.CoreException;

import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchRequestor;

import org.eclipse.jdt.internal.corext.refactoring.base.ReferencesInBinaryContext;

/**
 * Collects the results returned by a <code>SearchEngine</code>.
 * If a {@link ReferencesInBinaryContext} is passed, matches that are
 * inside a binary element are not collected (but added to the context if they are accurate).
 */
public class CollectingSearchRequestor extends SearchRequestor {
	private final ArrayList<SearchMatch> fFound;
	private final ReferencesInBinaryContext fBinaryRefs;

	public CollectingSearchRequestor() {
		this(null);
	}

	public CollectingSearchRequestor(ReferencesInBinaryContext binaryRefs) {
		fFound= new ArrayList<>();
		fBinaryRefs= binaryRefs;
	}

	/**
	 * The default implementation calls {@link #collectMatch(SearchMatch)} for
	 * all matches that make it through {@link #filterMatch(SearchMatch)}.
	 *
	 * @param match the found match
	 * @throws CoreException
	 *
	 * @see org.eclipse.jdt.core.search.SearchRequestor#acceptSearchMatch(org.eclipse.jdt.core.search.SearchMatch)
	 */
	@Override
	public void acceptSearchMatch(SearchMatch match) throws CoreException {
		if (! filterMatch(match))
			collectMatch(match);
	}

	public void collectMatch(SearchMatch match) {
		fFound.add(match);
	}

	/**
	 * Returns whether the given match should be filtered out.
	 * The default implementation filters out matches in binaries iff
	 * {@link #CollectingSearchRequestor(ReferencesInBinaryContext)} has been called with a
	 * non-<code>null</code> argument. Accurate binary matches are added to the {@link ReferencesInBinaryContext}.
	 *
	 * @param match the match to test
	 * @return <code>true</code> iff the given match should <em>not</em> be collected
	 * @throws CoreException
	 */
	public boolean filterMatch(SearchMatch match) throws CoreException {
		if (fBinaryRefs == null)
			return false;

		if (match.getAccuracy() == SearchMatch.A_ACCURATE && isBinaryElement(match.getElement())) {
			// binary classpaths are often incomplete -> avoiding false positives from inaccurate matches
			fBinaryRefs.add(match);
			return true;
		}

		return false;
	}

	private static boolean isBinaryElement(Object element) throws JavaModelException {
		if (element instanceof IMember) {
			return ((IMember)element).isBinary();

		} else if (element instanceof ICompilationUnit) {
			return true;

		} else if (element instanceof IClassFile) {
			return false;

		} else if (element instanceof IPackageFragment) {
			return isBinaryElement(((IPackageFragment) element).getParent());

		} else if (element instanceof IPackageFragmentRoot) {
			return ((IPackageFragmentRoot) element).getKind() == IPackageFragmentRoot.K_BINARY;

		}
		return false;

	}

	/**
	 * @return a List of {@link SearchMatch}es (not sorted)
	 */
	public List<SearchMatch> getResults() {
		return fFound;
	}
}


