blob: 3c4231c8154320c2b55629da03749886fe0939f2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 BSI Business Systems Integration AG.
* 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:
* BSI Business Systems Integration AG - initial API and implementation
******************************************************************************/
package org.eclipse.scout.sdk.s2e.ui.internal.template;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
import org.apache.commons.lang3.Validate;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ITrackedNodePosition;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel;
import org.eclipse.jdt.internal.corext.fix.LinkedProposalPositionGroup;
import org.eclipse.jdt.internal.corext.fix.LinkedProposalPositionGroup.PositionInformation;
import org.eclipse.jdt.internal.corext.fix.LinkedProposalPositionGroup.Proposal;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.JavaUIStatus;
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
import org.eclipse.jdt.internal.ui.viewsupport.BindingLabelProvider;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.ui.SharedASTProvider;
import org.eclipse.jdt.ui.text.java.correction.CUCorrectionProposal;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.jface.text.link.LinkedPosition;
import org.eclipse.scout.sdk.core.model.api.IJavaEnvironment;
import org.eclipse.scout.sdk.s2e.job.RunnableJob;
import org.eclipse.scout.sdk.s2e.ui.internal.S2ESdkUiActivator;
import org.eclipse.scout.sdk.s2e.ui.internal.util.ast.AstNodeFactory;
import org.eclipse.scout.sdk.s2e.ui.internal.util.ast.ILinkedPositionHolder;
import org.eclipse.scout.sdk.s2e.util.S2eUtils;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditProcessor;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.texteditor.ITextEditor;
/**
* <h3>{@link AbstractTypeProposal}</h3>
*
* @author Matthias Villiger
* @since 5.2.0
*/
public abstract class AbstractTypeProposal extends CUCorrectionProposal implements ILinkedPositionHolder {
/**
* Workaround to have a complete, valid AST even if there has been a prefix typed by the user. <br>
* Detail: If the user writes "abc" on an empty line before an annotation, this annotation is no longer part of the
* AST. This is because "abc@annot" is no valid pattern and gets excluded by the parser. As a workaround a semicolon
* is added: "abc;@annot". This way the parser recognizes the annotation because the text written by the user looks
* like another, wrong statement. The semicolon is removed again if the proposal is applied.
*/
static final char SEARCH_STRING_END_FIX = ';';
private final TypeProposalContext m_context;
private final LinkedProposalModel m_linkedProposalModel;
private final List<ICompletionProposalProvider> m_asyncProposalProviders;
private ASTRewrite m_rewrite; // to prevent duplicate calculations
private AstNodeFactory m_nodeFactory;
public interface IAstNodeFactoryProvider {
AstNodeFactory createFactoryFor(AbstractTypeProposal proposal);
}
private static volatile IAstNodeFactoryProvider astNodeFactoryProvider = new IAstNodeFactoryProvider() {
@Override
public AstNodeFactory createFactoryFor(AbstractTypeProposal proposal) {
return new AstNodeFactory(proposal.getProposalContext().getDeclaringType(), proposal.getProposalContext().getIcu(), proposal.getProposalContext().getProvider(),
proposal.getProposalContext().getDeclaringTypeBinding(), proposal);
}
};
public static IAstNodeFactoryProvider getAstNodeFactoryProvider() {
return astNodeFactoryProvider;
}
public static void setAstNodeFactoryProvider(IAstNodeFactoryProvider provider) {
astNodeFactoryProvider = Validate.notNull(provider);
}
public AbstractTypeProposal(String displayName, int relevance, String imageId, ICompilationUnit cu, TypeProposalContext context) {
super(displayName, cu, null, relevance, S2ESdkUiActivator.getImage(imageId));
m_context = context;
m_linkedProposalModel = new LinkedProposalModel();
m_asyncProposalProviders = new LinkedList<>();
}
protected abstract void fillRewrite(AstNodeFactory factory, Type superType) throws CoreException;
@Override
public String getAdditionalProposalInfo() {
return null; // disable preview
}
@Override
public Object getAdditionalProposalInfo(IProgressMonitor monitor) {
return null; // disable async preview
}
protected ASTRewrite getRewrite() throws CoreException {
if (m_rewrite == null) {
AstNodeFactory factory = getFactory();
Type superType = getBestMatchingSuperType(m_context.getDefaultSuperClasses());
m_rewrite = factory.getRewrite();
fillRewrite(factory, superType);
}
return m_rewrite;
}
protected Type getBestMatchingSuperType(Iterable<String> candidates) {
IJavaEnvironment env = getFactory().getJavaEnvironment();
for (String superTypeCandidate : candidates) {
if (env.findType(superTypeCandidate) != null) {
// found! return as best candidate
return getFactory().newTypeReference(superTypeCandidate);
}
}
throw new IllegalArgumentException("No default super type available in context.");
}
protected synchronized AstNodeFactory getFactory() {
if (m_nodeFactory == null) {
m_nodeFactory = astNodeFactoryProvider.createFactoryFor(this);
}
return m_nodeFactory;
}
public TypeProposalContext getProposalContext() {
return m_context;
}
@Override
protected void addEdits(IDocument document, TextEdit editRoot) throws CoreException {
try {
ASTRewrite rewrite = getRewrite();
MultiTextEdit edit = (MultiTextEdit) rewrite.rewriteAST();
String searchString = m_context.getSearchString();
if (searchString != null) {
// remove the search prefix
int len = searchString.length();
edit.addChild(new DeleteEdit(m_context.getInsertPosition() - len, len + String.valueOf(SEARCH_STRING_END_FIX).length()));
}
editRoot.addChild(edit);
editRoot.addChild(getFactory().getImportRewrite().rewriteImports(null));
}
catch (IllegalArgumentException e) {
throw new CoreException(JavaUIStatus.createError(IStatus.ERROR, e));
}
}
@Override
protected void performChange(IEditorPart part, IDocument document) throws CoreException {
if (m_context.getSearchString() != null) {
try {
InsertEdit insertFixEdit = new InsertEdit(m_context.getInsertPosition(), String.valueOf(SEARCH_STRING_END_FIX));
TextEditProcessor proc = new TextEditProcessor(document, insertFixEdit, TextEdit.UPDATE_REGIONS);
proc.performEdits();
}
catch (MalformedTreeException | BadLocationException e) {
throw new CoreException(JavaUIStatus.createError(IStatus.ERROR, e));
}
}
// start AST retrieval
RunnableFuture<CompilationUnit> astInitializer = new FutureTask<>(new P_AstInitCallable(m_context.getIcu()));
RunnableJob astInitializerJob = new RunnableJob("Get AST", astInitializer);
astInitializerJob.setUser(false);
astInitializerJob.setSystem(true);
astInitializerJob.setPriority(Job.INTERACTIVE);
astInitializerJob.schedule();
m_context.setCompilationUnit(astInitializer);
getRewrite(); // trigger rewrite creation
// start proposal calculation
for (ICompletionProposalProvider a : m_asyncProposalProviders) {
a.load();
}
try {
super.performChange(part, document);
if (m_linkedProposalModel.hasLinkedPositions() && part instanceof JavaEditor) {
// enter linked mode
ITextViewer viewer = ((JavaEditor) part).getViewer();
new LinkedAsyncProposalModelPresenter().enterLinkedMode(viewer, part, didOpenEditor(), m_linkedProposalModel);
}
else if (part instanceof ITextEditor) {
LinkedProposalPositionGroup.PositionInformation endPosition = m_linkedProposalModel.getEndPosition();
if (endPosition != null) {
// select a result
int pos = endPosition.getOffset() + endPosition.getLength();
((ITextEditor) part).selectAndReveal(pos, 0);
}
}
}
catch (BadLocationException e) {
throw new CoreException(JavaUIStatus.createError(IStatus.ERROR, e));
}
}
@Override
public void addLinkedPositionProposalsHierarchy(String groupId, String hierarchyBaseTypeFqn) {
addLinkedPositionProposalProvider(groupId, new P_HierarchyCallable(hierarchyBaseTypeFqn));
}
@Override
public void addLinkedPositionProposalsBoolean(String groupId) {
addLinkedPositionProposal(groupId, Boolean.FALSE.toString());
addLinkedPositionProposal(groupId, Boolean.TRUE.toString());
}
private void addLinkedPositionProposalProvider(String groupId, Callable<Proposal[]> callable) {
FutureTask<Proposal[]> future = new FutureTask<>(callable);
LinkedProposalPositionGroup group = m_linkedProposalModel.getPositionGroup(groupId, false);
if (group == null || !(group instanceof ICompletionProposalProvider)) {
LinkedAsyncProposalPositionGroup newGroup = new LinkedAsyncProposalPositionGroup(groupId, future);
m_asyncProposalProviders.add(newGroup);
if (group != null) {
// already added positions. copy over
for (PositionInformation info : group.getPositions()) {
newGroup.addPosition(info);
}
}
group = newGroup;
m_linkedProposalModel.addPositionGroup(group);
}
}
@Override
public void addLinkedPosition(ITrackedNodePosition position, boolean isFirst, String groupID) {
m_linkedProposalModel.getPositionGroup(groupID, true).addPosition(position, isFirst);
}
@Override
public void addLinkedPositionProposal(String groupId, String proposal) {
m_linkedProposalModel.getPositionGroup(groupId, true).addProposal(proposal, null, 10);
}
@Override
public void addLinkedPositionProposal(String groupID, ITypeBinding type) {
m_linkedProposalModel.getPositionGroup(groupID, true).addProposal(type, getCompilationUnit(), 10);
}
/**
* Sets the end position of the linked mode to the end of the passed range.
*
* @param position
* The position that describes the end position of the linked mode.
*/
public void setEndPosition(ITrackedNodePosition position) {
m_linkedProposalModel.setEndPosition(position);
}
private static final class P_AstInitCallable implements Callable<CompilationUnit> {
private final ICompilationUnit m_icu;
private P_AstInitCallable(ICompilationUnit icu) {
m_icu = icu;
}
@Override
public CompilationUnit call() throws Exception {
return SharedASTProvider.getAST(m_icu, SharedASTProvider.WAIT_ACTIVE_ONLY, null);
}
}
private final class P_HierarchyCallable implements Callable<Proposal[]> {
private final String m_hierarchyBaseTypeFqn;
private P_HierarchyCallable(String hierarchyBaseTypeFqn) {
m_hierarchyBaseTypeFqn = Validate.notNull(hierarchyBaseTypeFqn);
}
@Override
public Proposal[] call() throws Exception {
Set<IType> abstractClassesInHierarchy = S2eUtils.findAbstractClassesInHierarchy(getFactory().getJavaProject(), m_hierarchyBaseTypeFqn, null);
List<Proposal> result = new ArrayList<>(abstractClassesInHierarchy.size());
for (IType type : abstractClassesInHierarchy) {
ITypeBinding binding = getFactory().resolveTypeBinding(type.getFullyQualifiedName());
if (binding != null) {
result.add(new P_JavaLinkedModeProposal(getFactory().getIcu(), binding, 10));
}
}
return result.toArray(new Proposal[result.size()]);
}
}
private static final class P_JavaLinkedModeProposal extends Proposal {
private final ITypeBinding m_typeProposal;
private final ICompilationUnit m_compilationUnit;
private P_JavaLinkedModeProposal(ICompilationUnit unit, ITypeBinding typeProposal, int relevance) {
super(BindingLabelProvider.getBindingLabel(typeProposal, JavaElementLabels.ALL_DEFAULT | JavaElementLabels.ALL_POST_QUALIFIED), null, relevance);
m_typeProposal = typeProposal;
m_compilationUnit = unit;
ImageDescriptor desc = BindingLabelProvider.getBindingImageDescriptor(m_typeProposal, BindingLabelProvider.DEFAULT_IMAGEFLAGS);
if (desc != null) {
setImage(JavaPlugin.getImageDescriptorRegistry().get(desc));
}
}
@Override
public TextEdit computeEdits(int offset, LinkedPosition position, char trigger, int stateMask, LinkedModeModel model) throws CoreException {
ImportRewrite impRewrite = StubUtility.createImportRewrite(m_compilationUnit, true);
String replaceString = impRewrite.addImport(m_typeProposal);
MultiTextEdit composedEdit = new MultiTextEdit();
composedEdit.addChild(new ReplaceEdit(position.getOffset(), position.getLength(), replaceString));
composedEdit.addChild(impRewrite.rewriteImports(null));
return composedEdit;
}
}
}