/**
* Copyright (c) 2010, 2013 Darmstadt University of Technology.
* 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:
* Marcel Bruch - initial API and implementation.
* Olav Lenz - Added caching functionality.
*/
package org.eclipse.recommenders.internal.models.rcp;
import static com.google.common.base.Optional.absent;
import static com.google.common.base.Optional.of;
import static org.eclipse.jdt.core.IJavaElement.PACKAGE_FRAGMENT_ROOT;
import static org.eclipse.recommenders.coordinates.DependencyInfo.PROJECT_NAME;
import static org.eclipse.recommenders.coordinates.DependencyType.*;
import static org.eclipse.recommenders.utils.Checks.cast;
import static org.eclipse.recommenders.utils.Constants.REASON_NOT_IN_CACHE;
import static org.eclipse.recommenders.utils.Result.*;
import java.io.File;
import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.recommenders.coordinates.DependencyInfo;
import org.eclipse.recommenders.coordinates.IProjectCoordinateAdvisorService;
import org.eclipse.recommenders.coordinates.ProjectCoordinate;
import org.eclipse.recommenders.coordinates.rcp.DependencyInfos;
import org.eclipse.recommenders.internal.models.rcp.l10n.LogMessages;
import org.eclipse.recommenders.models.UniqueMethodName;
import org.eclipse.recommenders.models.UniqueTypeName;
import org.eclipse.recommenders.models.rcp.IProjectCoordinateProvider;
import org.eclipse.recommenders.rcp.IRcpService;
import org.eclipse.recommenders.rcp.JavaElementResolver;
import org.eclipse.recommenders.rcp.utils.JdtUtils;
import org.eclipse.recommenders.utils.Logs;
import org.eclipse.recommenders.utils.Result;
import org.eclipse.recommenders.utils.names.IMethodName;
import org.eclipse.recommenders.utils.names.ITypeName;
import com.google.common.base.Optional;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
public class ProjectCoordinateProvider implements IProjectCoordinateProvider, IRcpService {
private final IPath jreContainerPath = new Path("org.eclipse.jdt.launching.JRE_CONTAINER");
private final JavaElementResolver javaElementResolver;
private final IProjectCoordinateAdvisorService pcAdvisorService;
private final LoadingCache<IPackageFragmentRoot, Optional<DependencyInfo>> dependencyInfoCache;
@Inject
public ProjectCoordinateProvider(IProjectCoordinateAdvisorService pcAdvisorService,
JavaElementResolver javaElementResolver) {
this.pcAdvisorService = pcAdvisorService;
this.javaElementResolver = javaElementResolver;
dependencyInfoCache = createCache();
}
private LoadingCache<IPackageFragmentRoot, Optional<DependencyInfo>> createCache() {
return CacheBuilder.newBuilder().maximumSize(200)
.build(new CacheLoader<IPackageFragmentRoot, Optional<DependencyInfo>>() {
@Override
public Optional<DependencyInfo> load(IPackageFragmentRoot pfr) {
return extractDependencyInfo(pfr);
}
});
}
@Override
public Optional<ProjectCoordinate> resolve(ITypeBinding binding) {
if (binding == null) {
return absent();
}
IType type = cast(binding.getJavaElement());
return resolve(type);
}
@Override
public Optional<ProjectCoordinate> resolve(IType type) {
if (type == null) {
return absent();
}
IPackageFragmentRoot root = cast(type.getAncestor(PACKAGE_FRAGMENT_ROOT));
return resolve(root);
}
@Override
public Optional<ProjectCoordinate> resolve(IMethodBinding binding) {
if (binding == null) {
return absent();
}
IMethod method = cast(binding.getJavaElement());
return resolve(method);
}
@Override
public Optional<ProjectCoordinate> resolve(IMethod method) {
if (method == null) {
return absent();
}
IPackageFragmentRoot root = cast(method.getAncestor(PACKAGE_FRAGMENT_ROOT));
return resolve(root);
}
@Override
public Optional<ProjectCoordinate> resolve(IPackageFragmentRoot root) {
try {
Optional<DependencyInfo> dependencyInfo = dependencyInfoCache.get(root);
if (dependencyInfo.isPresent()) {
return resolve(dependencyInfo.get());
}
return absent();
} catch (ExecutionException e) {
return absent();
}
}
private Optional<DependencyInfo> extractDependencyInfo(IPackageFragmentRoot root) {
if (root == null) {
return absent();
}
IJavaProject javaProject = root.getJavaProject();
if (javaProject == null) {
return absent();
}
if (!root.isArchive()) {
return extractDependencyInfo(javaProject);
}
File location = JdtUtils.getLocation(root).orNull();
if (location == null) {
return absent();
}
if (isPartOfJre(root)) {
return DependencyInfos.createJreDependencyInfo(javaProject);
} else {
return Optional.of(new DependencyInfo(location, JAR));
}
}
private boolean isPartOfJre(IPackageFragmentRoot root) {
try {
IClasspathEntry entry = root.getRawClasspathEntry();
File file = root.getPath().toFile();
return jreContainerPath.isPrefixOf(entry.getPath()) && !"ext".equals(file.getParentFile().getName());
} catch (JavaModelException e) {
Logs.log(LogMessages.ERROR_FAILED_TO_GET_CLASSPATH_ENTRY, e, root);
return false;
}
}
@Override
public Optional<ProjectCoordinate> resolve(IJavaProject javaProject) {
return resolve(extractDependencyInfo(javaProject).get());
}
private Optional<DependencyInfo> extractDependencyInfo(IJavaProject javaProject) {
File location = JdtUtils.getLocation(javaProject).orNull();
DependencyInfo request = new DependencyInfo(location, PROJECT,
ImmutableMap.of(PROJECT_NAME, javaProject.getElementName()));
return of(request);
}
@Override
public Optional<ProjectCoordinate> resolve(DependencyInfo info) {
return pcAdvisorService.suggest(info);
}
@Override
public Result<ProjectCoordinate> tryResolve(DependencyInfo info) {
return pcAdvisorService.trySuggest(info);
}
@Override
public Optional<UniqueTypeName> toUniqueName(IType type) {
ProjectCoordinate base = resolve(type).orNull();
if (null == base) {
return absent();
}
return of(new UniqueTypeName(base, toName(type)));
}
@Override
public Optional<UniqueMethodName> toUniqueName(IMethod method) {
ProjectCoordinate base = resolve(method).orNull();
if (null == base) {
return absent();
}
IMethodName name = toName(method).orNull();
if (null == name) {
return absent();
}
return of(new UniqueMethodName(base, name));
}
@Override
public ITypeName toName(IType type) {
return javaElementResolver.toRecType(type);
}
@Override
public Optional<IMethodName> toName(IMethod method) {
return javaElementResolver.toRecMethod(method);
}
@Override
public Result<UniqueTypeName> tryToUniqueName(IType type) {
Result<ProjectCoordinate> pc = tryToProjectCoordinate(type);
switch (pc.getReason()) {
case REASON_NOT_IN_CACHE:
return Result.absent(REASON_NOT_IN_CACHE);
case OK:
return Result.of(new UniqueTypeName(pc.get(), toName(type)));
case ABSENT:
default:
return Result.absent();
}
}
@Override
public Result<UniqueMethodName> tryToUniqueName(IMethod method) {
Result<ProjectCoordinate> pc = tryToProjectCoordinate(method);
switch (pc.getReason()) {
case REASON_NOT_IN_CACHE:
return Result.absent(REASON_NOT_IN_CACHE);
case OK:
Optional<IMethodName> name = toName(method);
if (name.isPresent()) {
return Result.of(new UniqueMethodName(pc.get(), name.get()));
}
case ABSENT:
default:
return Result.absent();
}
}
private Result<ProjectCoordinate> tryToProjectCoordinate(IJavaElement element) {
IPackageFragmentRoot root = cast(element.getAncestor(PACKAGE_FRAGMENT_ROOT));
if (root == null) {
return Result.absent();
}
try {
DependencyInfo info = dependencyInfoCache.get(root).orNull();
if (info == null) {
return Result.absent();
}
return pcAdvisorService.trySuggest(info); // Pass-through REASON_NOT_IN_CACHE results
} catch (Exception e) {
return Result.absent(e);
}
}
}