/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.ext.java.jdt.codeassistant; import org.eclipse.che.ide.ext.java.jdt.Images; import org.eclipse.che.ide.ext.java.jdt.JavaPreferencesSettings; import org.eclipse.che.ide.ext.java.jdt.codeassistant.ui.StyledString; import org.eclipse.che.ide.ext.java.jdt.core.CompletionProposal; import org.eclipse.che.ide.ext.java.jdt.core.ISourceRange; import org.eclipse.che.ide.ext.java.jdt.core.IType; import org.eclipse.che.ide.ext.java.jdt.core.JavaCore; import org.eclipse.che.ide.ext.java.jdt.core.Signature; import org.eclipse.che.ide.ext.java.jdt.core.compiler.CharOperation; import org.eclipse.che.ide.ext.java.jdt.core.dom.AST; import org.eclipse.che.ide.ext.java.jdt.core.dom.ASTNode; import org.eclipse.che.ide.ext.java.jdt.core.dom.ASTParser; import org.eclipse.che.ide.ext.java.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.dom.CompilationUnit; import org.eclipse.che.ide.ext.java.jdt.core.dom.IMethodBinding; import org.eclipse.che.ide.ext.java.jdt.core.dom.ITypeBinding; import org.eclipse.che.ide.ext.java.jdt.core.dom.MethodDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.dom.Modifier; import org.eclipse.che.ide.ext.java.jdt.core.dom.NodeFinder; import org.eclipse.che.ide.ext.java.jdt.core.dom.TypeDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ASTRewrite; import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ITrackedNodePosition; import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ImportRewrite; import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ListRewrite; import org.eclipse.che.ide.ext.java.jdt.core.formatter.CodeFormatter; import org.eclipse.che.ide.ext.java.jdt.core.formatter.DefaultCodeFormatterConstants; import org.eclipse.che.ide.ext.java.jdt.core.formatter.IndentManipulation; import org.eclipse.che.ide.ext.java.jdt.internal.corext.codemanipulation.CodeGenerationSettings; import org.eclipse.che.ide.ext.java.jdt.internal.corext.codemanipulation.StubUtility2; import org.eclipse.che.ide.ext.java.jdt.internal.corext.util.CodeFormatterUtil; import org.eclipse.che.ide.ext.java.worker.WorkerDocument; import org.eclipse.che.ide.ext.java.worker.WorkerMessageHandler; import org.eclipse.che.ide.ext.java.jdt.text.Document; import org.eclipse.che.ide.ext.java.jdt.text.TextUtilities; import org.eclipse.che.ide.ext.java.jdt.text.edits.MalformedTreeException; import org.eclipse.che.ide.runtime.Assert; import org.eclipse.che.ide.runtime.CoreException; import org.eclipse.che.ide.api.text.BadLocationException; import org.eclipse.che.ide.api.text.Region; import java.util.ArrayList; import java.util.List; import java.util.Map; public class AnonymousTypeCompletionProposal extends JavaTypeCompletionProposal { private final String fDeclarationSignature; // private boolean fIsContextInformationComputed; private int fContextInformationPosition; private ImportRewrite fImportRewrite; private ITypeBinding fSuperType; private CompilationUnit fCompilationUnit; public AnonymousTypeCompletionProposal(JavaContentAssistInvocationContext invocationContext, int start, int length, String constructorCompletion, StyledString displayName, String declarationSignature, ITypeBinding superType, int relevance) { super(constructorCompletion, start, length, null, displayName, relevance, null, invocationContext); Assert.isNotNull(declarationSignature); Assert.isNotNull(superType); fDeclarationSignature = declarationSignature; fSuperType = superType; fCompilationUnit = invocationContext.getCompilationUnit(); setImage(getImageForType(fSuperType)); setCursorPosition(constructorCompletion.indexOf('(') + 1); } private String createDummyType(String name) { StringBuffer buffer = new StringBuffer(); buffer.append("abstract class "); //$NON-NLS-1$ buffer.append(name); if (fSuperType.isInterface()) buffer.append(" implements "); //$NON-NLS-1$ else buffer.append(" extends "); //$NON-NLS-1$ if (fDeclarationSignature != null) buffer.append(Signature.toString(fDeclarationSignature)); else buffer.append(fSuperType.getQualifiedName()); buffer.append(" {"); //$NON-NLS-1$ buffer.append("\n"); // Using newline is ok since source is used in dummy compilation unit //$NON-NLS-1$ buffer.append("}"); //$NON-NLS-1$ return buffer.toString(); } private String createNewBody(ImportRewrite importRewrite) throws CoreException { if (importRewrite == null) return null; try { String name = "Type" + System.currentTimeMillis(); //$NON-NLS-1$ ISourceRange range = null; //fSuperType.getSourceRange(); boolean sameUnit = range != null && ((TypeDeclaration)fCompilationUnit.types().get(0)).getName().getFullyQualifiedName() .equals(fSuperType.getQualifiedName()); // creates a type that extends the super type String dummyClassContent = createDummyType(name); StringBuffer workingCopyContents = new StringBuffer(fInvocationContext.getDocument().get()); int insertPosition; if (sameUnit) { insertPosition = range.getOffset() + range.getLength(); } else { // ISourceRange firstTypeRange = ((TypeDeclaration)fInvocationContext.getCompilationUnit().types().get(0)) // .getSourceRange(); // insertPosition = firstTypeRange.getOffset(); insertPosition = ((TypeDeclaration)fInvocationContext.getCompilationUnit().types().get(0)).getStartPosition(); } if (fSuperType.isLocal()) { // add an extra block: helps the AST to recover workingCopyContents.insert(insertPosition, '{' + dummyClassContent + '}'); insertPosition++; } else { /* * The two empty lines are added because the trackedDeclaration uses the covered range * and hence would also included comments that directly follow the dummy class. */ workingCopyContents.insert(insertPosition, dummyClassContent + "\n\n"); //$NON-NLS-1$ } ASTParser parser = ASTParser.newParser(AST.JLS3); parser.setResolveBindings(true); parser.setStatementsRecovery(true); parser.setSource(workingCopyContents.toString().toCharArray()); parser.setNameEnvironment(WorkerMessageHandler.get().getNameEnvironment()); CompilationUnit astRoot = (CompilationUnit)parser.createAST(); ASTNode newType = NodeFinder.perform(astRoot, insertPosition, dummyClassContent.length()); if (!(newType instanceof AbstractTypeDeclaration)) return null; AbstractTypeDeclaration declaration = (AbstractTypeDeclaration)newType; ITypeBinding dummyTypeBinding = declaration.resolveBinding(); if (dummyTypeBinding == null) return null; IMethodBinding[] bindings = StubUtility2.getOverridableMethods(astRoot.getAST(), dummyTypeBinding, true); if (fSuperType.isInterface()) { ITypeBinding[] dummySuperInterfaces = dummyTypeBinding.getInterfaces(); if (dummySuperInterfaces.length == 0 || dummySuperInterfaces.length == 1 && dummySuperInterfaces[0].isRawType()) bindings = new IMethodBinding[0]; } else { ITypeBinding dummySuperclass = dummyTypeBinding.getSuperclass(); if (dummySuperclass == null || dummySuperclass.isRawType()) bindings = new IMethodBinding[0]; } CodeGenerationSettings settings = JavaPreferencesSettings.getCodeGenerationSettings(); IMethodBinding[] methodsToOverride = null; IType type = null; //TODO // if (!fSuperType.isInterface() && !fSuperType.isAnnotation()) // { // IJavaElement typeElement = dummyTypeBinding.getJavaElement(); // // add extra checks here as the recovered code is fragile // if (typeElement instanceof IType && name.equals(typeElement.getElementName()) && typeElement.exists()) // { // type = (IType)typeElement; // } // } if (type != null) { //TODO // OverrideMethodDialog dialog = // new OverrideMethodDialog(JavaPlugin.getActiveWorkbenchShell(), null, type, true); // dialog.setGenerateComment(false); // dialog.setElementPositionEnabled(false); // if (dialog.open() == Window.OK) // { // Object[] selection = dialog.getResult(); // ArrayList<Object> result = new ArrayList<Object>(selection.length); // for (int i = 0; i < selection.length; i++) // { // if (selection[i] instanceof IMethodBinding) // result.add(selection[i]); // } // methodsToOverride = result.toArray(new IMethodBinding[result.size()]); // settings.createComments = dialog.getGenerateComment(); // } // else // { // // cancelled // setReplacementString(""); //$NON-NLS-1$ // setReplacementLength(0); return null; // } } else { settings.createComments = false; List<IMethodBinding> result = new ArrayList<IMethodBinding>(); for (int i = 0; i < bindings.length; i++) { IMethodBinding curr = bindings[i]; if (Modifier.isAbstract(curr.getModifiers())) result.add(curr); } methodsToOverride = result.toArray(new IMethodBinding[result.size()]); } ASTRewrite rewrite = ASTRewrite.create(astRoot.getAST()); ITrackedNodePosition trackedDeclaration = rewrite.track(declaration); ListRewrite rewriter = rewrite.getListRewrite(declaration, declaration.getBodyDeclarationsProperty()); for (int i = 0; i < methodsToOverride.length; i++) { IMethodBinding curr = methodsToOverride[i]; MethodDeclaration stub = StubUtility2.createImplementationStub(rewrite, importRewrite, null, curr, dummyTypeBinding.getName(), settings, dummyTypeBinding.isInterface()); rewriter.insertFirst(stub, null); } Document document = new WorkerDocument(workingCopyContents.toString()); try { rewrite.rewriteAST(document, WorkerMessageHandler.get().getOptions()).apply(document); int bodyStart = trackedDeclaration.getStartPosition() + dummyClassContent.indexOf('{'); int bodyEnd = trackedDeclaration.getStartPosition() + trackedDeclaration.getLength(); return document.get(bodyStart, bodyEnd - bodyStart); } catch (MalformedTreeException exception) { exception.printStackTrace(); } catch (BadLocationException exception) { exception.printStackTrace(); } return null; } finally { } } private Images getImageForType(ITypeBinding type) { Images image = Images.classDefaultItem; // default if (type.isAnnotation()) { image = Images.annotationItem; } else if (type.isInterface()) { image = Images.interfaceItem; } return image; } /* * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension4#isAutoInsertable() */ public boolean isAutoInsertable() { return false; } /* * @see org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal#isOffsetValid(int) * @since 3.5 */ @Override protected boolean isOffsetValid(int offset) { CompletionProposal coreProposal = ((MemberProposalInfo)getProposalInfo()).fProposal; if (coreProposal.getKind() != CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION) return super.isOffsetValid(offset); return coreProposal.getRequiredProposals()[0].getReplaceStart() <= offset; } /* * @see * org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal#getPrefixCompletionStart(org.eclipse.jface.text.IDocument * , int) * @since 3.5 */ @Override public int getPrefixCompletionStart(Document document, int completionOffset) { CompletionProposal coreProposal = ((MemberProposalInfo)getProposalInfo()).fProposal; if (coreProposal.getKind() != CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION) return super.getPrefixCompletionStart(document, completionOffset); return coreProposal.getRequiredProposals()[0].getReplaceStart(); } /* * @see * org.eclipse.jdt.internal.ui.text.java.JavaTypeCompletionProposal#getPrefixCompletionText(org.eclipse.jface.text.IDocument, * int) * @since 3.5 */ @Override public CharSequence getPrefixCompletionText(Document document, int completionOffset) { CompletionProposal coreProposal = ((MemberProposalInfo)getProposalInfo()).fProposal; if (coreProposal.getKind() != CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION) return super.getPrefixCompletionText(document, completionOffset); return String.valueOf(coreProposal.getName()); } /* * @see org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal#getPrefix(org.eclipse.jface.text.IDocument, int) */ @Override protected String getPrefix(Document document, int offset) { CompletionProposal coreProposal = ((MemberProposalInfo)getProposalInfo()).fProposal; if (coreProposal.getKind() != CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION) return super.getPrefix(document, offset); int replacementOffset = coreProposal.getRequiredProposals()[0].getReplaceStart(); try { int length = offset - replacementOffset; if (length > 0) return document.get(replacementOffset, length); } catch (BadLocationException x) { } return ""; //$NON-NLS-1$ } // /* // * @see org.eclipse.jdt.internal.ui.text.java.JavaTypeCompletionProposal#isValidPrefix(java.lang.String) // * @since 3.5 // */ // @Override // protected boolean isValidPrefix(String prefix) // { // CompletionProposal coreProposal = ((MemberProposalInfo)getProposalInfo()).fProposal; // if (coreProposal.getKind() != CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION) // return super.isValidPrefix(prefix); // // return super.isValidPrefix(prefix) || isPrefix(prefix, String.valueOf(coreProposal.getName())); // } /* * @see org.eclipse.jdt.internal.ui.text.java.JavaTypeCompletionProposal#apply(org.eclipse.jface.text.IDocument, char, int) * @since 3.5 */ @Override public void apply(Document document, char trigger, int offset) { super.apply(document, trigger, offset); // LinkedModeModel.closeAllModels(document); } /* * @see * org.eclipse.jdt.internal.ui.text.java.JavaTypeCompletionProposal#updateReplacementString(org.eclipse.jface.text.IDocument, * char, int, org.eclipse.jdt.core.dom.rewrite.ImportRewrite) */ @Override protected boolean updateReplacementString(Document document, char trigger, int offset, ImportRewrite impRewrite) throws CoreException, BadLocationException { fImportRewrite = impRewrite; String newBody = createNewBody(impRewrite); if (newBody == null) return false; CompletionProposal coreProposal = ((MemberProposalInfo)getProposalInfo()).fProposal; boolean isAnonymousConstructorInvoc = coreProposal.getKind() == CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION; boolean replacementStringEndsWithParentheses = isAnonymousConstructorInvoc || getReplacementString().endsWith(")"); //$NON-NLS-1$ // construct replacement text: an expression to be formatted StringBuffer buf = new StringBuffer("new A("); //$NON-NLS-1$ if (!replacementStringEndsWithParentheses || isAnonymousConstructorInvoc) buf.append(')'); buf.append(newBody); // use the code formatter String lineDelim = TextUtilities.getDefaultLineDelimiter(document); Region lineInfo = document.getLineInformationOfOffset(getReplacementOffset()); int indent = IndentManipulation.measureIndentUnits(document.get(lineInfo.getOffset(), lineInfo.getLength()), CodeFormatterUtil.getTabWidth(), CodeFormatterUtil.getIndentWidth()); @SuppressWarnings("unchecked") Map<String, String> options = JavaCore.getOptions(); options.put(DefaultCodeFormatterConstants.FORMATTER_INDENT_EMPTY_LINES, DefaultCodeFormatterConstants.TRUE); String replacementString = CodeFormatterUtil.format(CodeFormatter.K_EXPRESSION, buf.toString(), 0, lineDelim, options); int lineEndOffset = lineInfo.getOffset() + lineInfo.getLength(); int p = offset; char ch = document.getChar(p); while (p < lineEndOffset) { if (ch == '(' || ch == ')' || ch == ';' || ch == ',') break; ch = document.getChar(++p); } if (ch != ';' && ch != ',' && ch != ')') replacementString = replacementString + ';'; replacementString = IndentManipulation.changeIndent(replacementString, 0, CodeFormatterUtil.getTabWidth(), CodeFormatterUtil.getIndentWidth(), CodeFormatterUtil.createIndentString(indent), lineDelim); int beginIndex = replacementString.indexOf('('); if (!isAnonymousConstructorInvoc) beginIndex++; replacementString = replacementString.substring(beginIndex); int pos = offset; if (isAnonymousConstructorInvoc && (insertCompletion() ^ isInsertModeToggled())) { // Keep existing code int endPos = pos; ch = document.getChar(endPos); while (endPos < lineEndOffset && ch != '(' && ch != ')' && ch != ';' && ch != ',' && !CharOperation.isWhitespace(ch)) ch = document.getChar(++endPos); int keepLength = endPos - pos; if (keepLength > 0) { String keepStr = document.get(pos, keepLength); replacementString = replacementString + keepStr; setCursorPosition(replacementString.length() - keepLength); } } else setCursorPosition(replacementString.length()); setReplacementString(replacementString); if (pos < document.getLength() && document.getChar(pos) == ')') { int currentLength = getReplacementLength(); if (replacementStringEndsWithParentheses) setReplacementLength(currentLength + pos - offset); else setReplacementLength(currentLength + pos - offset + 1); } return false; } /* * @see ICompletionProposalExtension#getContextInformationPosition() * @since 3.4 */ @Override public int getContextInformationPosition() { // if (!fIsContextInformationComputed) // setContextInformation(computeContextInformation()); return fContextInformationPosition; } // /* // * @see ICompletionProposal#getContextInformation() // * @since 3.4 // */ // @Override // public final ContextInformation getContextInformation() // { // if (!fIsContextInformationComputed) // setContextInformation(computeContextInformation()); // return super.getContextInformation(); // } // protected ContextInformation computeContextInformation() // { // try // { // fContextInformationPosition = getReplacementOffset() - 1; // // CompletionProposal proposal = ((MemberProposalInfo)getProposalInfo()).fProposal; // // no context information for METHOD_NAME_REF proposals (e.g. for static imports) // // https://bugs.eclipse.org/bugs/show_bug.cgi?id=94654 // if (hasParameters() && (getReplacementString().endsWith(")") || getReplacementString().length() == 0)) { //$NON-NLS-1$ // ProposalContextInformation contextInformation = new ProposalContextInformation(proposal); // fContextInformationPosition = getReplacementOffset() + getCursorPosition(); // if (fContextInformationPosition != 0 && proposal.getCompletion().length == 0) // contextInformation.setContextInformationPosition(fContextInformationPosition); // return contextInformation; // } // return null; // } // finally // { // fIsContextInformationComputed = true; // } // } // // /** // * Returns <code>true</code> if the method being inserted has at least one parameter. Note that this does not say anything // * about whether the argument list should be inserted. // * // * @return <code>true</code> if the method has any parameters, <code>false</code> if it has no parameters // * @since 3.4 // */ // private boolean hasParameters() // { // CompletionProposal proposal = ((MemberProposalInfo)getProposalInfo()).fProposal; // return Signature.getParameterCount(proposal.getSignature()) > 0; // } /* * @see * org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal#createLazyJavaTypeCompletionProposal(org.eclipse.jdt * .core.CompletionProposal, org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext) * @since 3.5 */ @Override protected LazyJavaCompletionProposal createRequiredTypeCompletionProposal(CompletionProposal completionProposal, JavaContentAssistInvocationContext invocationContext) { LazyJavaCompletionProposal proposal = super.createRequiredTypeCompletionProposal(completionProposal, invocationContext); if (proposal instanceof LazyJavaTypeCompletionProposal) ((LazyJavaTypeCompletionProposal)proposal).setImportRewrite(fImportRewrite); return proposal; } }