package ru.naumen.gintonic.guice;
import java.io.Serializable;
import java.util.*;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.dom.IPackageBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import ru.naumen.gintonic.GinTonicIDs;
import ru.naumen.gintonic.GinTonicPlugin;
import ru.naumen.gintonic.guice.annotations.IGuiceAnnotation;
import ru.naumen.gintonic.guice.injection.IInjectionPoint;
import ru.naumen.gintonic.guice.statements.AssistedBindingStatement;
import ru.naumen.gintonic.guice.statements.BindingDefinition;
import ru.naumen.gintonic.guice.statements.InstallModuleStatement;
import ru.naumen.gintonic.guice.statements.JustInTimeBindingStatement;
import ru.naumen.gintonic.project.builder.GinTonicProjectBuilder;
import ru.naumen.gintonic.project.source.references.SourceCodeReference;
import ru.naumen.gintonic.utils.*;
/**
* The guice index holds the informations about the guice modules as collected
* during the build process ({@link GinTonicProjectBuilder}), so they can be
* accessed faster (e.g by the quickfixes).
*
* @author tmajunke
*/
public class GuiceIndex implements Serializable {
private static final long serialVersionUID = 2155643314847687772L;
private static volatile GuiceIndex instance;
private GuiceIndexState buildState = GuiceIndexState.INITIAL;
private boolean fromDisc;
/**
* The guice modules.
*/
private ArrayList<GuiceModule> guiceModules = new ArrayList<GuiceModule>(100);
public GuiceIndex() {
super();
}
public static GuiceIndex get() {
if (instance == null) {
synchronized (GuiceIndex.class) {
if (instance == null) {
instance = new GuiceIndex();
build();
}
}
}
return instance;
}
public static void set(GuiceIndex instance) {
GuiceIndex.instance = instance;
}
public boolean isFromDisc() {
return fromDisc;
}
public void setFromDisc(boolean fromDisc) {
this.fromDisc = fromDisc;
}
public GuiceIndexState getBuildState() {
return buildState;
}
public void setBuildState(GuiceIndexState buildState) {
this.buildState = buildState;
}
private static Job build() {
/*
* The Job has versus the IWorkspaceRunnable approach the nose ahead as
* it delivers us with a IProgressMonitor so we can see the progress in
* the UI and maybe cancel it.
*/
Job job = new Job("GinTonic - Building Guice/Gin module index") {
@Override
protected IStatus run(IProgressMonitor progressMonitor) {
final List<IProject> projectsWithGinTonicNature = IProjectUtils.getAccessibleProjectsWithGinTonicNature();
for (IProject project : projectsWithGinTonicNature) {
try {
IProject iProject = project.getProject();
iProject.build(IncrementalProjectBuilder.FULL_BUILD, GinTonicIDs.BUILDER, null, progressMonitor);
} catch (CoreException e) {
GinTonicPlugin.logException(e);
}
}
return Status.OK_STATUS;
}
};
job.schedule();
return job;
}
/**
* Returns the number of guice modules contained in this index.
*/
public int getNrOfGuiceModules() {
return guiceModules.size();
}
/**
* Rebuilds the index by triggering an asynchronous project builder job.
*/
public static void rebuild() {
set(null);
get();
}
public void addGuiceModuleDontLog(GuiceModule guiceModule) {
addGuiceModule(guiceModule, false);
}
public void addGuiceModule(GuiceModule guiceModule, boolean log) {
guiceModules.add(guiceModule);
if (log) {
GinTonicPlugin.logInfo("Added " + guiceModule.getPrimaryTypeNameFullyQualified() + " to Guice index "
+ getIndexInfoShort() + ".");
}
}
/**
* Removes all modules of the given project.
*/
public void removeGuiceModulesByProjectName(String projectName) {
Preconditions.checkNotNull(projectName);
List<GuiceModule> guiceModuleInfosToRemove = ListUtils.newArrayListWithCapacity(guiceModules.size());
for (GuiceModule guiceModule : guiceModules) {
String guiceModuleProjectName = guiceModule.getProjectName();
if (guiceModuleProjectName.equals(projectName)) {
guiceModuleInfosToRemove.add(guiceModule);
}
}
for (GuiceModule guiceModule : guiceModuleInfosToRemove) {
removeGuiceModule(guiceModule.getPrimaryTypeNameFullyQualified(), false);
}
}
public void removeGuiceModule(String nameFullyQualified, boolean log) {
for (int i = 0; i < guiceModules.size(); i++) {
GuiceModule guiceModule = guiceModules.get(i);
String fullyQualifiedName = guiceModule.getPrimaryTypeNameFullyQualified();
if (fullyQualifiedName.equals(nameFullyQualified)) {
GuiceModule removedModule = guiceModules.remove(i);
if (log) {
String message = nameFullyQualified + " from Guice index " + getIndexInfoShort() + ".";
if (removedModule == null) {
message = "Tried but not succeeded in removing " + message;
} else {
message = "Removed " + message;
}
GinTonicPlugin.logInfo(message);
}
}
}
}
public void updateGuiceModule(GuiceModule guiceModule) {
removeGuiceModule(guiceModule.getPrimaryTypeNameFullyQualified(), false);
addGuiceModule(guiceModule, false);
GinTonicPlugin.logInfo("Updated " + guiceModule.getPrimaryTypeNameFullyQualified() + " from Guice index "
+ getIndexInfoShort() + ".");
}
/**
* <pre>
* getGuiceModulesInAndBelowPackage(currentPackageBinding, null, 2);
* </pre>
*/
public List<GuiceModule> getGuiceModulesInAndBelowPackage(IPackageBinding currentPackageBinding) {
return getGuiceModulesInAndBelowPackage(currentPackageBinding, null, 2);
}
/**
* Returns the guice modules of the given package and the parent packages as
* indicated by the parameter depth. Returns an empty list if no modules
* were found.
*
* @param packageBinding
* the package in which we look for the guice modules
* @param ignoreModule
* a fully qualified type name of a guice module, which is
* ignored. Can be null.
* @param depth
* the number of parent packages to include (0 = the empty list
* is returned, 1 means the modules in the given package, 2 means
* the modules in the given package and the parent package, 3...
* think you got it).
*/
public List<GuiceModule> getGuiceModulesInAndBelowPackage(IPackageBinding packageBinding, String ignoreModule,
int depth) {
IPackageFragment currentPackage = (IPackageFragment) packageBinding.getJavaElement();
List<IPackageFragment> parentPackages = IPackageFragmentUtils.getParentPackages(currentPackage, depth);
parentPackages.add(currentPackage);
List<GuiceModule> modules = getGuiceModulesInPackage(parentPackages, ignoreModule);
return modules;
}
/**
* Returns the guice modules in the given packages. Returns an empty List if
* no modules were found.
*/
public List<GuiceModule> getGuiceModulesInPackage(List<IPackageFragment> packages, String moduleToIgnore) {
List<GuiceModule> guiceModulesInGivenPackages = ListUtils.newArrayList();
for (IPackageFragment packageFragment : packages) {
if (packageFragment == null) {
continue;
}
String packagePath = packageFragment.getElementName();
for (GuiceModule guiceModule : guiceModules) {
String packageFullyQualified = guiceModule.getPackageNameComponentsFullyQualified();
boolean ignoreModule = guiceModule.getPrimaryTypeNameFullyQualified().equals(moduleToIgnore);
if (ignoreModule) {
continue;
}
if (packageFullyQualified.equals(packagePath)) {
guiceModulesInGivenPackages.add(guiceModule);
}
}
}
return guiceModulesInGivenPackages;
}
public List<BindingDefinition> getBindingDefinitions(IInjectionPoint injectionPoint) {
ITypeBinding typeBinding = injectionPoint.getTargetTypeBinding();
ITypeBinding typeBindingWithoutProvider = ITypeBindingUtils.removeSurroundingProvider(typeBinding);
IGuiceAnnotation guiceAnnotation = injectionPoint.getGuiceAnnotation();
List<BindingDefinition> bindingDefinitions = getBindingDefinitions(typeBindingWithoutProvider, guiceAnnotation);
return bindingDefinitions;
}
/**
* Returns the {@link BindingDefinition}s for the given bound type and
* annotation type.
*
* <h5>Just in time binding</h5>
*
* If we could not find an explicit binding then we check if it could be a
* just-in-time-binding. We assume a just-in-time-binding if the typeToFind
* is a concrete class and there is no guice annotation applied and it is
* not a binary type.
*
* <h5>MapBinder Statements</h5>
*
* If the type to find is a {@link Map}, then we also check if we can find a
* suitable MapBinder statement.
*
* @param typeToFind
* the bound type. Cannot be null. Primitives (like "int") values
* are allowed. Provider types are not allowed( e.g instead of
* asking for "Provider<Date>" you must ask for "Date").
*
* @param annotationTypeToFind
* the annotationType. Can be null.
* @param namedAnnotationLiteralValueToFind
* the literal value of the named annotation. Can be null.
* @param packageToLimit
* if given then only the Guice modules in the same package are
* processed. Can be null.
* @return the discovered {@link BindingDefinition}s. Is empty if we did not
* find a binding.
*/
public List<BindingDefinition> getBindingDefinitions(ITypeBinding typeToFind,
IGuiceAnnotation guiceAnnotationToFind, Set<String> packageToLimit) {
String typeToFindQualifiedName = typeToFind.getQualifiedName();
/*
* We only store the wrappers instead of primitives, so maybe we must
* replace the primitive type by the wrapper type.
*/
typeToFindQualifiedName = StringUtils.translatePrimitiveToWrapper(typeToFindQualifiedName);
List<BindingDefinition> bindings = ListUtils.newArrayList();
for (GuiceModule guiceModule : guiceModules) {
if (packageToLimit != null) {
String packageFullyQualified = guiceModule.getPackageNameComponentsFullyQualified();
if (!packageToLimit.contains(packageFullyQualified)) {
continue;
}
}
List<BindingDefinition> bindingStatements = guiceModule.getBindingDefinitions();
if (bindingStatements == null) {
continue;
}
for (BindingDefinition bindingStatement : bindingStatements) {
String bindingsBoundType = bindingStatement.getBoundType();
if (!bindingsBoundType.equals(typeToFindQualifiedName)) {
continue;
}
IGuiceAnnotation guiceAnnotation = bindingStatement.getGuiceAnnotation();
if (guiceAnnotationToFind != null) {
if (guiceAnnotation != null && guiceAnnotation.equals(guiceAnnotationToFind)) {
bindings.add(bindingStatement);
}
} else { /* No annotation in field declaration */
if (guiceAnnotation == null) {
bindings.add(bindingStatement);
}
}
}
}
checkForJustInTimeBindings(guiceAnnotationToFind, typeToFind, bindings);
return bindings;
}
private void checkForJustInTimeBindings(IGuiceAnnotation guiceAnnotationToFind,
ITypeBinding typeBindingOfInterfaceType, List<BindingDefinition> bindings) {
if (bindings.isEmpty() && guiceAnnotationToFind == null) {
/*
* No implicit binding if the injected type is parameterized!
*
* Example:
*
* @Inject private ModulSpez<T> modulSpez;
*
* @Inject private DatendateiParser<M15N1OV14>
* m15n1oDatendateiParser;
*/
boolean isConcreteType = ITypeBindingUtils.isConcreteType(typeBindingOfInterfaceType);
if (isConcreteType) {
IJavaElement javaElement = typeBindingOfInterfaceType.getJavaElement();
if (!(javaElement instanceof IMember)) {
return;
}
IMember member = (IMember) javaElement;
if (member.isBinary()) {
return;
}
IJavaProject javaProject = javaElement.getJavaProject();
IProject project = javaProject.getProject();
String projectName = project.getName();
boolean isParameterizedType = typeBindingOfInterfaceType.isParameterizedType();
String typeName = null;
if (isParameterizedType) {
ITypeBinding typeDeclaration = typeBindingOfInterfaceType.getTypeDeclaration();
typeName = typeDeclaration.getName();
} else {
typeName = typeBindingOfInterfaceType.getName();
}
IPackageBinding packageBinding = typeBindingOfInterfaceType.getPackage();
String[] packageName = packageBinding.getNameComponents();
ICompilationUnit compilationUnit = member.getCompilationUnit();
List<String> srcFolderPathComponents = ICompilationUnitUtils
.getSrcFolderPathComponents(compilationUnit);
Integer startPositionOfTopLevelType = ICompilationUnitUtils
.getStartPositionOfTopLevelType(compilationUnit);
JustInTimeBindingStatement justInTimeBinding = new JustInTimeBindingStatement();
SourceCodeReference sourceCodeReference = new SourceCodeReference();
sourceCodeReference.setProjectName(projectName);
sourceCodeReference.setPrimaryTypeName(typeName);
sourceCodeReference.setPackageNameComponents(Arrays.asList(packageName));
sourceCodeReference.setSrcFolderPathComponents(srcFolderPathComponents);
sourceCodeReference.setOffset(startPositionOfTopLevelType);
justInTimeBinding.setSourceCodeReference(sourceCodeReference);
bindings.add(justInTimeBinding);
}
}
}
/**
* Synonym for getBindingsByTypeAndAnnotationLimitToPackage(typeToFind,
* guiceAnnotationToFind, null);
*
* @see #getBindingDefinitions(ITypeBinding, IGuiceAnnotation, Set)
*/
public List<BindingDefinition> getBindingDefinitions(ITypeBinding typeToFind, IGuiceAnnotation guiceAnnotationToFind) {
return getBindingDefinitions(typeToFind, guiceAnnotationToFind, null);
}
public AssistedBindingStatement getAssistedBindingDefinitionsByModelType(String boundModelType) {
for (GuiceModule guiceModule : guiceModules) {
List<BindingDefinition> bindingStatements = guiceModule.getBindingDefinitions();
if (bindingStatements == null) {
continue;
}
for (BindingDefinition bindingStatement : bindingStatements) {
if (bindingStatement instanceof AssistedBindingStatement) {
AssistedBindingStatement assistedBindingStatement = (AssistedBindingStatement) bindingStatement;
String modelType = assistedBindingStatement.getModelTypeName();
if (modelType.equals(boundModelType)) {
return assistedBindingStatement;
}
}
}
}
return null;
}
public String getIndexInfoShort() {
return "(" + getNrOfGuiceModules() + " Guice modules indexed)";
}
public String getIndexInfoDetailed() {
return getGuiceIndexStatistic().getDetailedInfo();
}
private GuiceIndexStatistic getGuiceIndexStatistic() {
GuiceIndexStatistic statistic = new GuiceIndexStatistic();
statistic.nrOfGuiceModules = guiceModules.size();
Set<String> projectNames = SetUtils.newHashSet();
for (GuiceModule guiceModule : guiceModules) {
List<BindingDefinition> bindingStatements = guiceModule.getBindingDefinitions();
if (bindingStatements == null) {
continue;
}
statistic.nrOfBindingStatements += bindingStatements.size();
List<InstallModuleStatement> installedModules = guiceModule.getInstalledModules();
if (installedModules != null) {
statistic.nrOfInstallStatements += installedModules.size();
}
projectNames.add(guiceModule.getProjectName());
}
statistic.nrOfProjects = projectNames.size();
return statistic;
}
public class GuiceIndexStatistic {
public int nrOfGuiceModules = 0;
public int nrOfBindingStatements = 0;
public int nrOfInstallStatements = 0;
public int nrOfProjects = 0;
public String getDetailedInfo() {
GuiceIndexStatistic guiceIndexStatistic = getGuiceIndexStatistic();
StringBuilder sb = new StringBuilder("Statistic:");
sb.append(guiceIndexStatistic.nrOfGuiceModules + " Guice modules in ");
sb.append(guiceIndexStatistic.nrOfProjects + " projects, ");
sb.append(guiceIndexStatistic.nrOfBindingStatements + " binding statements, ");
sb.append(guiceIndexStatistic.nrOfInstallStatements + " install statements.");
return sb.toString();
}
}
}