/*******************************************************************************
 * Copyright (c) 2012, 2017 itemis AG (http://www.itemis.eu) and others.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/
package org.eclipse.xtext.xbase.imports;

import static com.google.common.collect.Iterables.*;
import static com.google.common.collect.Sets.*;
import static org.eclipse.xtext.common.types.TypesPackage.Literals.*;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.common.types.JvmAnnotationType;
import org.eclipse.xtext.common.types.JvmArrayType;
import org.eclipse.xtext.common.types.JvmCompoundTypeReference;
import org.eclipse.xtext.common.types.JvmDeclaredType;
import org.eclipse.xtext.common.types.JvmEnumerationLiteral;
import org.eclipse.xtext.common.types.JvmFeature;
import org.eclipse.xtext.common.types.JvmField;
import org.eclipse.xtext.common.types.JvmGenericArrayTypeReference;
import org.eclipse.xtext.common.types.JvmIdentifiableElement;
import org.eclipse.xtext.common.types.JvmMember;
import org.eclipse.xtext.common.types.JvmOperation;
import org.eclipse.xtext.common.types.JvmParameterizedTypeReference;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.common.types.JvmWildcardTypeReference;
import org.eclipse.xtext.common.types.TypesPackage;
import org.eclipse.xtext.common.types.util.RawSuperTypes;
import org.eclipse.xtext.documentation.IEObjectDocumentationProvider;
import org.eclipse.xtext.documentation.IEObjectDocumentationProviderExtension;
import org.eclipse.xtext.documentation.IJavaDocTypeReferenceProvider;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.parser.IParseResult;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.resource.ILocationInFileProvider;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.scoping.IScope;
import org.eclipse.xtext.scoping.IScopeProvider;
import org.eclipse.xtext.util.ITextRegion;
import org.eclipse.xtext.util.ReplaceRegion;
import org.eclipse.xtext.util.TextRegion;
import org.eclipse.xtext.xbase.XAbstractFeatureCall;
import org.eclipse.xtext.xbase.XAssignment;
import org.eclipse.xtext.xbase.XCasePart;
import org.eclipse.xtext.xbase.XConstructorCall;
import org.eclipse.xtext.xbase.XExpression;
import org.eclipse.xtext.xbase.XFeatureCall;
import org.eclipse.xtext.xbase.XMemberFeatureCall;
import org.eclipse.xtext.xbase.XSwitchExpression;
import org.eclipse.xtext.xbase.XTypeLiteral;
import org.eclipse.xtext.xbase.XbasePackage;
import org.eclipse.xtext.xbase.annotations.xAnnotations.XAnnotation;
import org.eclipse.xtext.xbase.annotations.xAnnotations.XAnnotationElementValuePair;
import org.eclipse.xtext.xbase.annotations.xAnnotations.XAnnotationsPackage;
import org.eclipse.xtext.xbase.jvmmodel.IJvmModelAssociations;
import org.eclipse.xtext.xbase.scoping.batch.ImplicitlyImportedFeatures;
import org.eclipse.xtext.xbase.typesystem.IBatchTypeResolver;
import org.eclipse.xtext.xbase.typesystem.IResolvedTypes;
import org.eclipse.xtext.xbase.typesystem.computation.IAmbiguousLinkingCandidate;
import org.eclipse.xtext.xbase.typesystem.computation.ILinkingCandidate;
import org.eclipse.xtext.xbase.typesystem.references.LightweightTypeReference;
import org.eclipse.xtext.xbase.util.FeatureCallAsTypeLiteralHelper;
import org.eclipse.xtext.xtype.XFunctionTypeRef;

import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.inject.Inject;

/**
 * @author Jan Koehnlein - Initial contribution and API
 */
public class TypeUsageCollector {
	
	@Inject
	private IJvmModelAssociations associations;
	
	@Inject
	private ImplicitlyImportedFeatures implicitImports;
	
	@Inject
	private ILocationInFileProvider locationInFileProvider;

	@Inject
	private IJavaDocTypeReferenceProvider javaDocTypeReferenceProvider;

	@Inject
	private TypeUsages typeUsages;
	
	@Inject
	private IBatchTypeResolver batchTypeResolver;
	
	@Inject
	private RawSuperTypes rawSuperTypes;
	
	@Inject
	private FeatureCallAsTypeLiteralHelper typeLiteralHelper;
	
	@Inject
	private IScopeProvider scopeProvider;
	
	private JvmDeclaredType currentThisType;
	
	private JvmMember currentContext;

	private XtextResource resource;

	private List<JvmType> implicitStaticImports;

	private List<JvmType> implicitExtensionImports;

	private Set<JvmType> knownTypesForStaticImports = newHashSet();

	private IEObjectDocumentationProviderExtension documentationProvider;
	
	protected static class PreferredType {
		public final JvmType referencedType;
		public final JvmType usedType;
		public final String unresolvedTypeName;
		public PreferredType(JvmType referencedType, JvmType usedType) {
			if (referencedType.eIsProxy()) {
				throw new IllegalArgumentException();
			}
			this.referencedType = referencedType;
			this.usedType = usedType;
			this.unresolvedTypeName = null;
		}
		public PreferredType(String name) {
			this.unresolvedTypeName = name;
			this.referencedType = null;
			this.usedType = null;
		}
	}
	
	public XtextResource getResource() {
		return resource;
	}
	
	protected JvmMember getCurrentContext() {
		return currentContext;
	}
	
	protected TypeUsages getTypeUsages() {
		return typeUsages;
	}

	@Inject
	private void setDocumentationProvider(IEObjectDocumentationProvider documentationProvider) {
		if(documentationProvider instanceof IEObjectDocumentationProviderExtension) 
			this.documentationProvider = (IEObjectDocumentationProviderExtension) documentationProvider;
	}
	
	public TypeUsages collectTypeUsages(XtextResource resource) {
		if(resource != null && !resource.getContents().isEmpty()) {
			this.resource = resource;
			this.implicitStaticImports = implicitImports.getStaticImportClasses(resource);
			this.implicitExtensionImports = implicitImports.getExtensionClasses(resource);
			collectAllReferences(resource.getContents().get(0));
		}
		return typeUsages; 
	}

	protected void collectAllReferences(EObject rootElement) {
		IResolvedTypes resolvedTypes = batchTypeResolver.resolveTypes(rootElement);
		Collection<IAmbiguousLinkingCandidate> ambiguousCandidates = resolvedTypes.getAmbiguousLinkingCandidates();
		Map<XExpression, IAmbiguousLinkingCandidate> indexedAmbiguousCandidates = Maps.newHashMap();
		for(IAmbiguousLinkingCandidate ambiguous: ambiguousCandidates) {
			indexedAmbiguousCandidates.put(ambiguous.getAlternatives().get(0).getExpression(), ambiguous);
		}
		TreeIterator<EObject> contents = EcoreUtil.getAllContents(rootElement, true);
		while (contents.hasNext()) {
			EObject next = contents.next();
			if (next instanceof JvmTypeReference) {
				acceptType((JvmTypeReference) next);
			} else if (next instanceof XAnnotation) {
				acceptPreferredType(next, XAnnotationsPackage.Literals.XANNOTATION__ANNOTATION_TYPE);
			} else if (next instanceof XConstructorCall) {
				acceptPreferredType(next, XbasePackage.Literals.XCONSTRUCTOR_CALL__CONSTRUCTOR);
			} else if (next instanceof XTypeLiteral) {
				acceptPreferredType(next, XbasePackage.Literals.XTYPE_LITERAL__TYPE);
			} else if (next instanceof XFeatureCall) {
				XFeatureCall featureCall = (XFeatureCall) next;
				if (featureCall.getFeature() instanceof JvmType && featureCall.isTypeLiteral()) {
					if (isOuterTypeLiteral(featureCall)) {
						contents.prune();
						continue;
					}
					acceptPreferredType(featureCall, XbasePackage.Literals.XABSTRACT_FEATURE_CALL__FEATURE);
				} else if (featureCall.getFeature().eIsProxy()) {
					acceptPreferredType(featureCall, XbasePackage.Literals.XABSTRACT_FEATURE_CALL__FEATURE);
				} else {
					collectStaticImportsFrom(featureCall, indexedAmbiguousCandidates.get(featureCall));
				}
			} else if (next instanceof XMemberFeatureCall) {
				XMemberFeatureCall featureCall = (XMemberFeatureCall) next;
				if (featureCall.getFeature() instanceof JvmType && featureCall.isTypeLiteral()) {
					if (isOuterTypeLiteral(featureCall)) {
						contents.prune();
						continue;
					}
					acceptPreferredType(featureCall, XbasePackage.Literals.XABSTRACT_FEATURE_CALL__FEATURE);
				}
				if (!featureCall.isExplicitStatic()) {
					XExpression target = featureCall.getMemberCallTarget();
					if (!isTypeLiteral(target) || featureCall.isExtension()) {
						collectStaticImportsFrom(featureCall, indexedAmbiguousCandidates.get(featureCall));
					}
				} 
			} else if ((next instanceof XAbstractFeatureCall && ((XAbstractFeatureCall) next).isOperation())
					|| (next instanceof XAssignment && currentThisType != null && !contains(currentThisType.getAllFeatures(), ((XAssignment) next).getFeature()))) {
				collectStaticImportsFrom((XAbstractFeatureCall) next, indexedAmbiguousCandidates.get(next));
			} else {
				Set<EObject> elements = associations.getJvmElements(next);
				if (!elements.isEmpty()) {
					EObject firstJvmElement = elements.iterator().next();
					if (firstJvmElement instanceof JvmMember) {
						JvmDeclaredType declaringType = (firstJvmElement instanceof JvmDeclaredType) 
								? (JvmDeclaredType) firstJvmElement
								: ((JvmMember) firstJvmElement).getDeclaringType();
						if(currentThisType != declaringType) {
							JvmDeclaredType containerType = EcoreUtil2.getContainerOfType(declaringType.eContainer(), JvmDeclaredType.class);
							if(containerType == null) {
								knownTypesForStaticImports.clear();
							} else {
								addToKnownStaticImports(containerType);
							}
							currentThisType = declaringType;
							addToKnownStaticImports(currentThisType);
						}
						currentContext = (JvmMember) firstJvmElement;
					}
				} 
				addJavaDocReferences(next);
			}
		}
	}

	private void addToKnownStaticImports(JvmDeclaredType type) {
		knownTypesForStaticImports.add(type);
		knownTypesForStaticImports.addAll(rawSuperTypes.collect(type));
	}
	
	private boolean isOuterTypeLiteral(XAbstractFeatureCall featureCall) {
		if (featureCall.eContainingFeature() == XbasePackage.Literals.XMEMBER_FEATURE_CALL__MEMBER_CALL_TARGET) {
			XMemberFeatureCall container = (XMemberFeatureCall) featureCall.eContainer();
			if (container.isTypeLiteral()) {
				return true;
			}
		}
		return false;
	}

	private boolean isTypeLiteral(XExpression memberCallTarget) {
		if (memberCallTarget instanceof XAbstractFeatureCall) {
			return ((XAbstractFeatureCall) memberCallTarget).isTypeLiteral();
		}
		return false;
	}

	private void collectStaticImportsFrom(final XAbstractFeatureCall featureCall, IAmbiguousLinkingCandidate optionalAmbiguousCandidate) {
		if (optionalAmbiguousCandidate != null) {
			for (ILinkingCandidate linkingCandidate : optionalAmbiguousCandidate.getAlternatives()) {
				collectStaticImportsFrom(linkingCandidate);
			}
		} else {
			collectStaticImportsFrom(featureCall, featureCall.getFeature());
		}
	}
	
	protected void collectStaticImportsFrom(ILinkingCandidate linkingCandidate) {
		if (linkingCandidate == null) {
			return;
		}
		XExpression expression = linkingCandidate.getExpression();
		collectStaticImportsFrom(expression, linkingCandidate.getFeature());
	}

	protected void collectStaticImportsFrom(XExpression expression, JvmIdentifiableElement feature) {
		if (expression instanceof XAbstractFeatureCall) {
			if (feature instanceof JvmEnumerationLiteral && expression instanceof XFeatureCall) {
				if (isEnumLiteralImplicitelyImported(expression, (JvmEnumerationLiteral) feature)) {
					return;
				}
			}
			XAbstractFeatureCall featureCall = (XAbstractFeatureCall) expression;
			if ((feature instanceof JvmOperation || feature instanceof JvmField) && featureCall.isStatic()) {
				if (featureCall.isExtension()) {
					acceptStaticExtensionImport((JvmMember) feature);
				} else {
					acceptStaticImport((JvmMember) feature);
				}
			}
		}
	}
	
	private boolean isEnumLiteralImplicitelyImported(XExpression expression, JvmEnumerationLiteral literal) {
		XCasePart container = EcoreUtil2.getContainerOfType(expression, XCasePart.class);
		if (container != null && EcoreUtil.isAncestor(container.getCase(), expression)) {
			XSwitchExpression switchExpression = (XSwitchExpression) container.eContainer();
			if (switchExpression.getSwitch() != null) {
				LightweightTypeReference switchVarType = batchTypeResolver.resolveTypes(expression).getActualType(switchExpression.getSwitch());
				if (switchVarType != null && literal.getEnumType() == switchVarType.getType()) {
					return true;
				}
			}
		} else {
			XAnnotation annotation = EcoreUtil2.getContainerOfType(expression, XAnnotation.class); 
			if (annotation != null) {
				if (annotation.getValue() != null && EcoreUtil.isAncestor(annotation.getValue(), expression)) {
					JvmType type = annotation.getAnnotationType();
					if (type instanceof JvmAnnotationType) {
						Iterable<JvmFeature> valueCandidates = ((JvmAnnotationType) type).findAllFeaturesByName("value");
						JvmOperation value = (JvmOperation) Iterables.getFirst(valueCandidates, null);
						JvmType expectedType = getTypeForAnnotationValue(value);
						if (literal.getEnumType() == expectedType) {
							return true;
						}
					}
				} else {
					XAnnotationElementValuePair valuePair = EcoreUtil2.getContainerOfType(expression, XAnnotationElementValuePair.class);
					JvmOperation operation = valuePair.getElement();
					JvmType expectedType = getTypeForAnnotationValue(operation);
					if (literal.getEnumType() == expectedType) {
						return true;
					}
				}
			}
		}
		return false;
	}

	private JvmType getTypeForAnnotationValue(JvmOperation value) {
		JvmType expectedType = value.getReturnType().getType();
		if (expectedType instanceof JvmArrayType) {
			expectedType = ((JvmArrayType) expectedType).getComponentType();
		}
		return expectedType;
	}

	protected void addJavaDocReferences(EObject element) {
		if(element != null && documentationProvider != null && currentThisType != null) {
			for(INode documentationNode: documentationProvider.getDocumentationNodes(element)) {
				for(ReplaceRegion docTypeReference: javaDocTypeReferenceProvider.computeTypeRefRegions(documentationNode)) {
					String docTypeText = docTypeReference.getText();
					IScope scope = scopeProvider.getScope(element, TypesPackage.Literals.JVM_PARAMETERIZED_TYPE_REFERENCE__TYPE);
					IEObjectDescription singleElement = scope.getSingleElement(QualifiedName.create(docTypeText));
					ITextRegion textRegion = new TextRegion(docTypeReference.getOffset(), docTypeReference.getLength());
					JvmType jvmType = null;
					if (singleElement != null) {
						jvmType = (JvmType) singleElement.getEObjectOrProxy();
					}
					if(jvmType instanceof JvmDeclaredType && !jvmType.eIsProxy()) {
						PreferredType preferredType = findPreferredType((JvmDeclaredType) jvmType, docTypeText);
						if (preferredType.referencedType != null) {
							acceptType(preferredType.referencedType, preferredType.usedType, textRegion);
						} else {
							typeUsages.addUnresolved(docTypeText, "", textRegion, currentThisType);
						}
					} else {
						typeUsages.addUnresolved(docTypeText, "", textRegion, currentThisType);
					}
				}
			}
		}
	}
	
	protected void acceptType(JvmTypeReference ref) {
		if (ref instanceof XFunctionTypeRef 
		 || ref instanceof JvmWildcardTypeReference
		 || ref instanceof JvmGenericArrayTypeReference
		 || ref instanceof JvmCompoundTypeReference
		 || (ref.eContainer() instanceof XFunctionTypeRef 
				 && ref.eContainmentFeature() == JVM_SPECIALIZED_TYPE_REFERENCE__EQUIVALENT)
		 || NodeModelUtils.findActualNodeFor(ref) == null) 
			return;
		else 
			acceptPreferredType(ref);
	}
	
	protected void acceptPreferredType(JvmTypeReference ref) {
		if (ref instanceof JvmParameterizedTypeReference) {
			acceptPreferredType(ref, JVM_PARAMETERIZED_TYPE_REFERENCE__TYPE);
		} else {
			acceptType(ref.getType(), locationInFileProvider.getFullTextRegion(ref));
		}
	}
	
	/**
	 * Tries to locate the syntax for the type reference that the user used in the original code.
	 * Especially interesting for nested types, where one could prefer the (arguably) more explicit (or verbose)
	 * {@code Resource$Factory} with an import of {@code org.eclipse.emf.core.Resource} over the probably shorter
	 * {@code Factory} with an import of {@code org.eclipse.emf.core.Resource$Factory}.
	 * 
	 * The function relies on a node model to be available. Otherwise the actually referenced type is 
	 * used as the preferred type.
	 * 
	 * @param owner the referrer to the JVM concept.
	 * @param reference a reference to a {@link JvmType} or {@link JvmMember} that is declared in a type.
	 * @return the referenced type or one of its containers.
	 */
	protected PreferredType findPreferredType(EObject owner, EReference reference, String text) {
		JvmIdentifiableElement referencedThing = getReferencedElement(owner, reference);
		JvmDeclaredType referencedType = null;
		if (referencedThing instanceof JvmDeclaredType) {
			referencedType = (JvmDeclaredType) referencedThing;
		} else if (referencedThing instanceof JvmMember) {
			referencedType = ((JvmMember) referencedThing).getDeclaringType();
		} else if(referencedThing instanceof JvmType) {
			if (referencedThing.eIsProxy()) {
				String importedName = getFirstNameSegment(owner, reference);
				return new PreferredType(importedName);
			}
			return null;
		}
		return findPreferredType(referencedType, text);
	}

	protected JvmIdentifiableElement getReferencedElement(EObject owner, EReference reference) {
		JvmIdentifiableElement referencedThing = (JvmIdentifiableElement) owner.eGet(reference);
		if (referencedThing != null && owner instanceof XConstructorCall && referencedThing.eIsProxy()) {
			JvmIdentifiableElement potentiallyLinkedType = batchTypeResolver.resolveTypes(owner).getLinkedFeature((XConstructorCall)owner);
			if (potentiallyLinkedType != null && !potentiallyLinkedType.eIsProxy()) {
				referencedThing = potentiallyLinkedType;
			}
		}
		return referencedThing;
	}

	private String getFirstNameSegment(EObject owner, EReference reference) {
		List<INode> nodes = NodeModelUtils.findNodesForFeature(owner, reference);
		if (nodes.size() == 1) {
			String text = NodeModelUtils.getTokenText(nodes.get(0));
			return getFirstNameSegment(text);
		}
		throw new IllegalStateException("Cannot find node for feature");
	}

	protected String getFirstNameSegment(String text) {
		int firstDelimiter = text.indexOf('.');
		if (firstDelimiter == -1) {
			firstDelimiter = text.indexOf('$');
		} else {
			int dollar = text.indexOf('$');
			if (dollar != -1) {
				firstDelimiter = Math.min(firstDelimiter, dollar);
			}
		}
		if (firstDelimiter == -1) {
			firstDelimiter = text.indexOf("::");
			if (firstDelimiter == text.length() - 2 && firstDelimiter >= 0) {
				text = text.substring(0, firstDelimiter);
				firstDelimiter = -1;
			}
		} else {
			int colon = text.indexOf("::");
			if (colon != text.length() - 2 && colon != -1) {
				firstDelimiter = Math.min(firstDelimiter, colon);
			}
		}
		if (firstDelimiter != -1) {
			return text.substring(0, firstDelimiter);
		}
		return text;
	}

	protected PreferredType findPreferredType(JvmDeclaredType referencedType, String text) {
		if (referencedType != null && !referencedType.eIsProxy()) {
			if (referencedType.getDeclaringType() == null) {
				return new PreferredType(referencedType, referencedType);
			}
			String outerSegment = getFirstNameSegment(text);
			JvmType outerType = findDeclaringTypeBySimpleName(referencedType, outerSegment);
			if (outerType == null) {
				throw new IllegalStateException();
			}
			return new PreferredType(outerType, referencedType);
		}
		String name = getFirstNameSegment(text);
		return new PreferredType(name);
	}

	private JvmDeclaredType findDeclaringTypeBySimpleName(JvmDeclaredType referencedType, String outerSegment) {
		if (referencedType.getDeclaringType() == null || outerSegment.equals(referencedType.getSimpleName())) {
			return referencedType;
		}
		return findDeclaringTypeBySimpleName(referencedType.getDeclaringType(), outerSegment);
	}

	protected void acceptPreferredType(EObject owner, EReference referenceToTypeOrMember) {
		ITextRegion refRegion = locationInFileProvider.getFullTextRegion(owner, referenceToTypeOrMember, 0);
		if (refRegion.getLength() > 0) {
			IParseResult parseResult = resource.getParseResult();
			if(parseResult != null) {
				String completeText = parseResult.getRootNode().getText();
				String refText = completeText.substring(refRegion.getOffset(), refRegion.getOffset() + refRegion.getLength());
				PreferredType preferredType = findPreferredType(owner, referenceToTypeOrMember, refText);
				if (preferredType != null) {
					if (preferredType.referencedType != null) {
						acceptType(preferredType.referencedType, preferredType.usedType, refRegion);
					} else {
						String suffix = refText.substring(preferredType.unresolvedTypeName.length());
						if (owner instanceof XFeatureCall) {
							XFeatureCall featureCall = (XFeatureCall) owner; 
							if (typeLiteralHelper.isPotentialTypeLiteral(featureCall, null)) {
								XAbstractFeatureCall root = typeLiteralHelper.getRootTypeLiteral(featureCall);
								if (root != null) {
									ITextRegion region = locationInFileProvider.getSignificantTextRegion(root);
									if (region.getOffset() == refRegion.getOffset()) {
										suffix = completeText.substring(region.getOffset(), region.getOffset() + region.getLength());
										suffix = suffix.substring(preferredType.unresolvedTypeName.length());
										refRegion = region;
									}
								}
							}
						}
						acceptUnresolvedType(preferredType.unresolvedTypeName, suffix, refRegion);
					}
				}
			}
		}
	}
	
	protected void acceptType(JvmType type, ITextRegion refRegion) {
		acceptType(type, type, refRegion);
	}
	
	protected void acceptType(JvmType type, JvmType usedType, ITextRegion refRegion) {
		if(currentContext != null) {
			if (type == null || type.eIsProxy()) {
				throw new IllegalArgumentException();
			} else if (type instanceof JvmDeclaredType) {
				typeUsages.addTypeUsage((JvmDeclaredType) type, (JvmDeclaredType) usedType, refRegion, currentContext);
			}
		}
	}
	
	protected void acceptUnresolvedType(String usedTypeName, String suffix, ITextRegion refRegion) {
		if(currentContext != null) {
			typeUsages.addUnresolved(usedTypeName, suffix, refRegion, currentContext);
		}
	}
	
	protected void acceptStaticImport(JvmMember member) {
		JvmDeclaredType declarator = member.getDeclaringType();
		if (!needsStaticImport(declarator) || implicitStaticImports.contains(declarator))
			return;
		typeUsages.addStaticImport(member);
	}

	protected void acceptStaticExtensionImport(JvmMember member) {
		JvmDeclaredType declarator = member.getDeclaringType();
		if (!needsStaticImport(declarator) || implicitExtensionImports.contains(declarator))
			return;
		typeUsages.addExtensionImport(member);
	}

	protected boolean needsStaticImport(JvmDeclaredType declarator) {
		return !knownTypesForStaticImports.contains(declarator);
	}
	
}