/******************************************************************************* * Copyright (c) 2016 Pivotal, Inc. * 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: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.quickfix.jdt.processors.imports; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.internal.corext.util.JavaModelUtil; import org.eclipse.jdt.internal.ui.text.correction.ASTResolving; import org.eclipse.jdt.internal.ui.text.correction.JavaCorrectionProcessor; import org.eclipse.jdt.internal.ui.text.correction.QuickFixProcessor; import org.eclipse.jdt.ui.text.java.IInvocationContext; import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; import org.eclipse.jdt.ui.text.java.IProblemLocation; import org.springframework.ide.eclipse.quickfix.Activator; /** * Specialised QuickFixProcessor that replaces the JDT QuickFixProcessor * specifically to recompute relevance of "add import" proposals. The reason * this needs to REPLACE the full complete JDT QuickFixProcessor is that the JDT * {@link QuickFixProcessor} does not have well defined hooks or extension * points to reorder proposals (note that extension point <br/> * <br/> * "org.eclipse.jdt.ui.javaCompletionProposalSorters" <br/> * <br/> * allows for sorters to be contributed but <br/> * 1. they don't seem to be invoked on quickfix proposals <br/> * 2. the API used by this extension point does not seem have enough information * to recompute relevance of proposals. In particular, the selected AST Node in * the context is not present in the API, although it's possible it is available * in the proposals themselves. <br/> * * <p/> * This processor takes over the JDT QuickFixProcessor, computes ALL the * proposals that the JDT processor provides, but it ALSO contains additional * support to recompute relevance of proposals based on additional criteria * defined in {@link AddImportRelevanceResolverFactory} * <p/> * Note that since this STS quickfix processor REPLACES the JDT * QuickFixProcessor, both cannot coexist in the {@link JavaCorrectionProcessor} * (the JDT "registry" for quickfix processors) , as it will result in duplicate * proposals. Therefore the STS version has support to disable the default JDT * QuickFixProcessor from the registry. * */ public class AddImportsQuickFixProcessor extends QuickFixProcessor { private AddImportRelevanceResolverFactory factory; public void setProposalRelevanceFactory(AddImportRelevanceResolverFactory factory) { if (factory != null) { this.factory = factory; } } protected AddImportRelevanceResolverFactory getFactory() { if (this.factory == null) { setProposalRelevanceFactory(new AddImportRelevanceResolverFactory()); } return this.factory; } @Override public IJavaCompletionProposal[] getCorrections(IInvocationContext context, IProblemLocation[] locations) throws CoreException { ICompilationUnit cu = context != null ? context.getCompilationUnit() : null; try { // BUG: when first invoking quickfix, JDT and STS quickfix processor are // already loaded since the quickfix processors are loaded by the JDT // registry (JavaCorrectionsProcessor) // lazily. This will result in duplicate proposals. To avoid this, so // far // the only solution is to only allow one or the other to contribute // proposals, BUT NOT BOTH. // Therefore if the JDT processor is being removed below, it means it // has already been loaded, so don't return any // proposals. ONLY return // proposals if the JDT process was removed in a PREVIOUS run if (JDTQuickFixProcessorHelper.getInstance().removeJDTQuickFixProcessor(cu)) { return null; } } catch (Throwable e) { // Any errors while handling the default JDT processor should not // propagate to avoid interfering with quickfix operation. Activator.log(e); } // Get the proposals only if the default JDT processor has been removed // to // avoid duplicate entries if (JDTQuickFixProcessorHelper.getInstance().isJDTProcessorRemoved()) { IJavaCompletionProposal[] proposals = super.getCorrections(context, locations); if (proposals != null && proposals.length > 0) { List<IJavaCompletionProposal> proposalsFromJDT = Arrays.asList(proposals); try { recomputeProposalRelevance(context, locations, proposalsFromJDT); } catch (Throwable e) { Activator.log(e); } } return proposals; } return null; } protected void recomputeProposalRelevance(IInvocationContext context, IProblemLocation[] locations, List<IJavaCompletionProposal> proposalsFromJDT) throws Exception { if (context == null || locations == null || proposalsFromJDT == null) { return; } Set<Integer> handledProblems = new HashSet<Integer>(); for (int i = 0; i < locations.length; i++) { IProblemLocation problem = locations[i]; Integer id = new Integer(problem.getProblemId()); if (id != null && handledProblems.add(id)) { switch (id) { case IProblem.UndefinedType: case IProblem.JavadocUndefinedType: ICompilationUnit cu = context.getCompilationUnit(); ASTNode selectedNode = problem.getCoveringNode(context.getASTRoot()); if (selectedNode != null && cu != null) { int kind = evaluateTypeKind(selectedNode, cu.getJavaProject()); List<AddImportRelevanceResolver> relevanceResolvers = getFactory().getResolvers(cu, proposalsFromJDT); // only use the first resolver that successfully // recomputes the relevance if (relevanceResolvers != null) { for (AddImportRelevanceResolver resolver : relevanceResolvers) { if (resolver.recomputeRelevance(kind, selectedNode)) { break; } } } } break; } } } } private int evaluateTypeKind(ASTNode node, IJavaProject project) { int kind = ASTResolving.getPossibleTypeKinds(node, JavaModelUtil.is50OrHigher(project)); return kind; } }