/** * Copyright (c) 2015 Codetrails GmbH. * 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: * Andreas Sewe - initial API and implementation */ package org.eclipse.recommenders.internal.constructors.rcp; import static java.math.RoundingMode.HALF_EVEN; import static java.text.MessageFormat.format; import static java.util.Objects.requireNonNull; import static org.eclipse.recommenders.completion.rcp.processable.ProposalTag.RECOMMENDERS_SCORE; import static org.eclipse.recommenders.rcp.SharedImages.Images.OVR_STAR; import static org.eclipse.recommenders.utils.Constants.REASON_NOT_IN_CACHE; import static org.eclipse.recommenders.utils.Result.*; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.inject.Inject; import javax.inject.Provider; import org.eclipse.jdt.core.CompletionProposal; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.internal.codeassist.complete.CompletionOnQualifiedTypeReference; import org.eclipse.jdt.internal.codeassist.complete.CompletionOnSingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; import org.eclipse.jface.viewers.IDecoration; import org.eclipse.recommenders.completion.rcp.IProposalNameProvider; import org.eclipse.recommenders.completion.rcp.IRecommendersCompletionContext; import org.eclipse.recommenders.completion.rcp.processable.IProcessableProposal; import org.eclipse.recommenders.completion.rcp.processable.OverlayImageProposalProcessor; import org.eclipse.recommenders.completion.rcp.processable.ProposalProcessorManager; import org.eclipse.recommenders.completion.rcp.processable.SessionProcessor; import org.eclipse.recommenders.completion.rcp.processable.SimpleProposalProcessor; import org.eclipse.recommenders.constructors.ConstructorModel; import org.eclipse.recommenders.constructors.IConstructorModelProvider; import org.eclipse.recommenders.internal.constructors.rcp.l10n.Messages; import org.eclipse.recommenders.internal.models.rcp.PrefetchModelArchiveJob; import org.eclipse.recommenders.models.UniqueTypeName; import org.eclipse.recommenders.models.rcp.IProjectCoordinateProvider; import org.eclipse.recommenders.rcp.SharedImages; import org.eclipse.recommenders.utils.Recommendation; import org.eclipse.recommenders.utils.Recommendations; import org.eclipse.recommenders.utils.Result; import org.eclipse.recommenders.utils.names.IMethodName; import com.google.common.base.Function; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.math.DoubleMath; public class ConstructorCompletionSessionProcessor extends SessionProcessor { private final ImmutableSet<Class<? extends ASTNode>> supportedCompletionRequests = ImmutableSet .<Class<? extends ASTNode>>of(CompletionOnSingleTypeReference.class, CompletionOnQualifiedTypeReference.class); private final Provider<IProjectCoordinateProvider> pcProvider; private final Provider<IConstructorModelProvider> modelProvider; private final IProposalNameProvider methodNameProvider; private final ConstructorsRcpPreferences prefs; private final OverlayImageProposalProcessor overlayProcessor; private Map<CompletionProposal, Double> recommationationsMap; @Inject public ConstructorCompletionSessionProcessor(Provider<IProjectCoordinateProvider> pcProvider, Provider<IConstructorModelProvider> modelProvider, IProposalNameProvider methodNameProvider, ConstructorsRcpPreferences prefs, SharedImages images) { this.pcProvider = requireNonNull(pcProvider); this.modelProvider = requireNonNull(modelProvider); this.methodNameProvider = requireNonNull(methodNameProvider); this.prefs = requireNonNull(prefs); this.overlayProcessor = new OverlayImageProposalProcessor(images.getDescriptor(OVR_STAR), IDecoration.TOP_LEFT); } @Override public boolean startSession(final IRecommendersCompletionContext context) { if (!isCompletionRequestSupported(context)) { return false; } IType expectedType = context.getExpectedType().orNull(); if (expectedType == null) { return false; } final ConstructorModel model; Result<UniqueTypeName> res = pcProvider.get().tryToUniqueName(expectedType); switch (res.getReason()) { case OK: model = modelProvider.get().acquireModel(res.get()).orNull(); break; case REASON_NOT_IN_CACHE: new PrefetchModelArchiveJob<ConstructorModel>(expectedType, pcProvider.get(), modelProvider.get()) .schedule(200); // fall-through case ABSENT: default: return false; } try { if (model == null) { return false; } Map<IJavaCompletionProposal, CompletionProposal> proposals = context.getProposals(); final Map<CompletionProposal, IMethodName> foundConstructors = new HashMap<>(); int runningTotal = 0; for (Entry<IJavaCompletionProposal, CompletionProposal> entry : proposals.entrySet()) { CompletionProposal coreProposal = entry.getValue(); if (coreProposal.getKind() != CompletionProposal.CONSTRUCTOR_INVOCATION) { continue; } IMethodName methodName = methodNameProvider.toMethodName(coreProposal).orNull(); if (methodName == null) { continue; } if (!methodName.isInit()) { continue; } int constructorCallCount = model.getConstructorCallCount(methodName); if (constructorCallCount == 0) { continue; } foundConstructors.put(coreProposal, methodName); runningTotal += constructorCallCount; } final int foundConstructorsTotal = runningTotal; if (foundConstructorsTotal == 0) { return false; } Iterable<Recommendation<CompletionProposal>> recommendations = Iterables.transform( foundConstructors.entrySet(), new Function<Entry<CompletionProposal, IMethodName>, Recommendation<CompletionProposal>>() { @Override public Recommendation<CompletionProposal> apply(Entry<CompletionProposal, IMethodName> entry) { IMethodName methodName = entry.getValue(); double relevance = model.getConstructorCallCount(methodName) / (double) foundConstructorsTotal; return Recommendation.newRecommendation(entry.getKey(), relevance); } }); List<Recommendation<CompletionProposal>> topRecommendations = Recommendations.top(recommendations, prefs.maxNumberOfProposals, prefs.minProposalPercentage / 100.0); if (topRecommendations.isEmpty()) { return false; } recommationationsMap = Recommendations.asMap(topRecommendations); return true; } finally { modelProvider.get().releaseModel(model); } } @Override public void process(IProcessableProposal proposal) throws Exception { CompletionProposal coreProposal = proposal.getCoreProposal().orNull(); if (coreProposal == null) { return; } Double relevance = recommationationsMap.get(coreProposal); if (relevance == null) { return; } final int score; if (relevance < 0.01) { score = DoubleMath.roundToInt(relevance * 10000, HALF_EVEN); } else { score = 100 + DoubleMath.roundToInt(relevance * 100, HALF_EVEN); } if (score == 0) { return; } final int boost = prefs.changeProposalRelevance ? score : 0; if (boost > 0) { proposal.setTag(RECOMMENDERS_SCORE, relevance * 100); } String label = null; if (prefs.decorateProposalText) { String format = relevance < 0.01d ? Messages.PROPOSAL_LABEL_PROMILLE : Messages.PROPOSAL_LABEL_PERCENTAGE; label = format(format, relevance); } ProposalProcessorManager manager = proposal.getProposalProcessorManager(); if (boost != 0 || label != null) { manager.addProcessor(new SimpleProposalProcessor(boost, label)); } if (prefs.decorateProposalIcon) { manager.addProcessor(overlayProcessor); } } private boolean isCompletionRequestSupported(IRecommendersCompletionContext context) { final ASTNode node = context.getCompletionNode().orNull(); if (node == null) { return false; } else { for (Class<? extends ASTNode> supportedCompletionRequest : supportedCompletionRequests) { if (supportedCompletionRequest.isInstance(node)) { return true; } } return false; } } }