/**
* 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, Madhuranga Lakjeewa - initial API and implementation.
*/
package org.eclipse.recommenders.internal.snipmatch.rcp.completion;
import static org.eclipse.recommenders.internal.snipmatch.rcp.Constants.SNIPMATCH_CONTEXT_ID;
import static org.eclipse.recommenders.utils.Logs.log;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateContext;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.recommenders.coordinates.DependencyInfo;
import org.eclipse.recommenders.coordinates.IDependencyListener;
import org.eclipse.recommenders.coordinates.ProjectCoordinate;
import org.eclipse.recommenders.internal.snipmatch.rcp.Repositories;
import org.eclipse.recommenders.internal.snipmatch.rcp.l10n.LogMessages;
import org.eclipse.recommenders.models.rcp.IProjectCoordinateProvider;
import org.eclipse.recommenders.rcp.SharedImages;
import org.eclipse.recommenders.snipmatch.ISnippet;
import org.eclipse.recommenders.snipmatch.ISnippetRepository;
import org.eclipse.recommenders.snipmatch.Location;
import org.eclipse.recommenders.snipmatch.SearchContext;
import org.eclipse.recommenders.snipmatch.model.SnippetRepositoryConfiguration;
import org.eclipse.recommenders.snipmatch.rcp.model.SnippetRepositoryConfigurations;
import org.eclipse.recommenders.utils.Recommendation;
import org.eclipse.recommenders.utils.Result;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import com.google.common.base.Optional;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
public abstract class AbstractContentAssistProcessor<T extends ContentAssistInvocationContext>
implements IContentAssistProcessor {
private final Repositories repos;
private final SnippetRepositoryConfigurations configs;
private final IProjectCoordinateProvider pcProvider;
protected final IDependencyListener dependencyListener;
private final Image contextLoadingImage;
private final Image snippetImage;
protected final TemplateContextType templateContextType;
protected T context;
protected Set<DependencyInfo> availableDependencies;
private String terms;
private ContextLoadingProposal contextLoadingProposal;
private String filename;
@Inject
public AbstractContentAssistProcessor(TemplateContextType templateContextType,
SnippetRepositoryConfigurations configs, Repositories repos, IProjectCoordinateProvider pcProvider,
IDependencyListener dependencyListener, SharedImages images) {
this.templateContextType = templateContextType;
this.repos = repos;
this.configs = configs;
this.dependencyListener = dependencyListener;
this.pcProvider = pcProvider;
contextLoadingImage = images.getImage(SharedImages.Images.OBJ_HOURGLASS);
snippetImage = images.getImage(SharedImages.Images.OBJ_BULLET_BLUE);
}
public void setContext(T context) {
this.context = context;
this.availableDependencies = calculateAvailableDependencies(context);
if (!allProjectCoordinatesCached(pcProvider, availableDependencies)) {
contextLoadingProposal = new ContextLoadingProposal(pcProvider, availableDependencies, contextLoadingImage);
contextLoadingProposal.schedule();
}
}
public void setFilename(String filename) {
this.filename = filename;
}
protected abstract Set<DependencyInfo> calculateAvailableDependencies(T context);
public void setTerms(String terms) {
this.terms = terms;
}
@Override
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
if (StringUtils.isEmpty(terms)) {
return new ICompletionProposal[0];
}
Set<ProjectCoordinate> projectCoordinates = tryResolve(pcProvider, availableDependencies);
SearchContext searchContext = new SearchContext(terms, getLocation(), filename, projectCoordinates);
LinkedList<ICompletionProposal> proposals = Lists.newLinkedList();
List<SnippetRepositoryConfiguration> sortedConfigs = Lists.newArrayList();
sortedConfigs.addAll(configs.getRepos());
Collections.sort(sortedConfigs, new Comparator<SnippetRepositoryConfiguration>() {
@Override
public int compare(SnippetRepositoryConfiguration o1, SnippetRepositoryConfiguration o2) {
return Integer.compare(o1.getPriority(), o2.getPriority());
}
});
Point selection = viewer.getSelectedRange();
IRegion region = new Region(selection.x, selection.y);
Position position = new Position(selection.x, selection.y);
IDocument document = viewer.getDocument();
String selectedText = null;
if (selection.y != 0) {
try {
selectedText = document.get(selection.x, selection.y);
} catch (BadLocationException e) {
}
}
TemplateContext templateContext = getTemplateContext(document, position);
templateContext.setVariable("selection", selectedText); //$NON-NLS-1$
for (int repositoryPriority = 0; repositoryPriority < sortedConfigs.size(); repositoryPriority++) {
Optional<ISnippetRepository> repo = repos.getRepository(sortedConfigs.get(repositoryPriority).getId());
if (repo.isPresent()) {
List<Recommendation<ISnippet>> recommendations = repo.get().search(searchContext);
if (!recommendations.isEmpty()) {
proposals.add(new RepositoryProposal(sortedConfigs.get(repositoryPriority), repositoryPriority,
recommendations.size()));
for (Recommendation<ISnippet> recommendation : recommendations) {
ISnippet snippet = recommendation.getProposal();
Template template = new Template(snippet.getName(), snippet.getDescription(),
SNIPMATCH_CONTEXT_ID, snippet.getCode(), true);
try {
proposals.add(SnippetProposal.newSnippetProposal(recommendation, repositoryPriority,
template, templateContext, region, snippetImage));
} catch (Exception e) {
log(LogMessages.ERROR_CREATING_SNIPPET_PROPOSAL_FAILED, e);
}
}
}
}
}
if (isResolvingProjectDependencies()) {
proposals.add(contextLoadingProposal);
}
return Iterables.toArray(proposals, ICompletionProposal.class);
}
protected abstract Location getLocation();
protected abstract TemplateContext getTemplateContext(IDocument document, Position position);
private boolean isResolvingProjectDependencies() {
return contextLoadingProposal != null && contextLoadingProposal.isStillLoading();
}
@Override
public String getErrorMessage() {
return null;
}
@Override
public IContextInformationValidator getContextInformationValidator() {
return null;
}
@Override
public char[] getContextInformationAutoActivationCharacters() {
return null;
}
@Override
public char[] getCompletionProposalAutoActivationCharacters() {
return null;
}
@Override
public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
return null;
}
private static boolean allProjectCoordinatesCached(IProjectCoordinateProvider pcProvider,
Set<DependencyInfo> dependencyInfos) {
for (DependencyInfo dependencyInfo : dependencyInfos) {
Result<ProjectCoordinate> pc = pcProvider.tryResolve(dependencyInfo);
if (pc.getReason() == org.eclipse.recommenders.utils.Constants.REASON_NOT_IN_CACHE) {
return false;
}
}
return true;
}
private static Set<ProjectCoordinate> tryResolve(IProjectCoordinateProvider pcProvider,
Set<DependencyInfo> dependencyInfos) {
Set<ProjectCoordinate> result = Sets.newHashSet();
for (DependencyInfo dependencyInfo : dependencyInfos) {
ProjectCoordinate pc = pcProvider.tryResolve(dependencyInfo).or(null);
if (pc != null) {
result.add(pc);
}
}
return result;
}
}