// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT License. See LICENSE file in the project root for license information. package com.microsoft.javapkgsrv; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.jdt.core.CompletionProposal; import org.eclipse.jdt.core.CompletionRequestor; import org.eclipse.jdt.core.IBuffer; import org.eclipse.jdt.core.IClassFile; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaModel; import org.eclipse.jdt.core.IOpenable; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.ISourceReference; import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.SourceRange; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jface.text.Document; import com.microsoft.javapkgsrv.Protocol.TypeRootIdentifier; import com.microsoft.javapkgsrv.Protocol.Response.FileParseMessagesResponse.Problem; import com.microsoft.javapkgsrv.Protocol.Response.QuickInfoResponse.JavaElement; import com.microsoft.javapkgsrv.Protocol.Response.*; public class JavaParser { private HashMap<Integer, CompilationUnit> ActiveUnits = new HashMap<Integer, CompilationUnit>(); private HashMap<String, ITypeRoot> ActiveTypeRoots = new HashMap<String, ITypeRoot>(); public IWorkspaceRoot WorkspaceRoot = null; public IJavaModel JavaModel = null; public void Init() throws JavaModelException { WorkspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); JavaModel = JavaCore.create(WorkspaceRoot); System.out.println("Updating external archives..."); JavaModel.refreshExternalArchives(null, null); } public Integer ProcessParseRequest(String contentFile, String fileName) throws Exception { CompilationUnit cu = Parse(contentFile, fileName); int hashCode = cu.hashCode(); ActiveUnits.put(hashCode, cu); return hashCode; } public void ProcessDisposeFileRequest(int fileIdentifier) { if (ActiveUnits.containsKey(fileIdentifier)) ActiveUnits.remove(fileIdentifier); } private CompilationUnit Parse(String contentFile, String fileName) throws Exception { File file = new File(fileName); IFile[] files = WorkspaceRoot.findFilesForLocationURI(file.toURI(), IResource.FILE); if (files.length > 1) throw new Exception("Ambigous parse request for file: " + fileName); else if (files.length == 0) throw new Exception("File is not part of the enlistment: " + fileName); ASTParser parser = ASTParser.newParser(AST.JLS8); parser.setKind(ASTParser.K_COMPILATION_UNIT); parser.setSource(contentFile.toCharArray()); parser.setUnitName(files[0].getName()); parser.setProject(JavaCore.create(files[0].getProject())); parser.setResolveBindings(true); CompilationUnit cu = (CompilationUnit)parser.createAST(null); return cu; } public List<Protocol.Response.OutlineResultResponse.Outline> ProcessOutlineRequest(Integer fileId) { final List<OutlineResultResponse.Outline> ret = new ArrayList<OutlineResultResponse.Outline>(); if (ActiveUnits.containsKey(fileId)) { CompilationUnit cu = ActiveUnits.get(fileId); cu.accept(new ASTVisitor() { @Override public boolean visit(TypeDeclaration type) { ret.add(OutlineResultResponse.Outline.newBuilder() .setStartPosition(type.getStartPosition()) .setLength(type.getLength()) .setHoverText(type.toString()) .setSummaryText(type.getName().toString()) .build()); return true; } @Override public boolean visit(MethodDeclaration method) { ret.add(OutlineResultResponse.Outline.newBuilder() .setStartPosition(method.getStartPosition()) .setLength(method.getLength()) .setHoverText(method.toString()) .setSummaryText(method.getName().toString()) .build()); return true; } }); } return ret; } public List<AutocompleteResponse.Completion> ProcessAutocompleteRequest(String contentFile, String typeRootId, int cursorPosition) throws Exception { if (ActiveTypeRoots.containsKey(typeRootId)) { ITypeRoot typeRoot = ActiveTypeRoots.get(typeRootId); typeRoot.getBuffer().setContents(contentFile.toCharArray()); return Autocomplete(typeRoot, cursorPosition); } return null; } private List<AutocompleteResponse.Completion> Autocomplete(ITypeRoot cu, int cursorPosition) throws JavaModelException { final List<AutocompleteResponse.Completion> proposals = new ArrayList<AutocompleteResponse.Completion>(); cu.codeComplete(cursorPosition, new CompletionRequestor() { @Override public void accept(CompletionProposal proposal) { try { System.out.println(proposal.toString()); proposals.add(translateToCompletion(proposal)); } catch(Exception e) { e.printStackTrace(); } } }); return proposals; } private AutocompleteResponse.Completion translateToCompletion(CompletionProposal proposal) { AutocompleteResponse.Completion.Builder builder = AutocompleteResponse.Completion.newBuilder() .setKind(AutocompleteResponse.Completion.CompletionKind.valueOf(proposal.getKind())) .setIsConstructor(proposal.isConstructor()) .setCompletionText(String.copyValueOf(proposal.getCompletion())) .setFlags(proposal.getFlags()) .setRelevance(proposal.getRelevance()) .setReplaceStart(proposal.getReplaceStart()) .setReplaceEnd(proposal.getReplaceEnd()); char[] sig = proposal.getSignature(); if (sig != null) { if (proposal.getKind() == CompletionProposal.METHOD_REF || proposal.getKind() == CompletionProposal.JAVADOC_METHOD_REF) builder.setSignature(new String(Signature.toCharArray(sig, proposal.getName(), null, false, true))); else builder.setSignature(new String(Signature.toCharArray(sig))); } char[] name = proposal.getName(); if (name == null) builder.setName(builder.getCompletionText()); else builder.setName(String.copyValueOf(name)); return builder.build(); } public List<ParamHelpResponse.Signature> ProcessParamHelpRequest(String contentFile, String typeRootId, int cursorPosition) throws Exception { if (ActiveTypeRoots.containsKey(typeRootId)) { ITypeRoot typeRoot = ActiveTypeRoots.get(typeRootId); typeRoot.getBuffer().setContents(contentFile.toCharArray()); return ParamHelp(typeRoot, cursorPosition); } return null; } private List<ParamHelpResponse.Signature> ParamHelp(ITypeRoot cu, int cursorPosition) throws JavaModelException { final List<ParamHelpResponse.Signature> proposals = new ArrayList<ParamHelpResponse.Signature>(); cu.codeComplete(cursorPosition, new CompletionRequestor() { @Override public void accept(CompletionProposal proposal) { try { System.out.println(proposal.toString()); if (proposal.getKind() == CompletionProposal.METHOD_REF) { char[] javaSig = proposal.getSignature(); ParamHelpResponse.Signature.Builder sig = ParamHelpResponse.Signature.newBuilder() .setName(new String(proposal.getName())) .setReturnValue(new String(Signature.toCharArray(Signature.getReturnType(javaSig)))); char[][] javaParamTypes = Signature.getParameterTypes(javaSig); for(char[] javaParamType: javaParamTypes) { sig.addParameters(ParamHelpResponse.Parameter.newBuilder() .setName(new String(Signature.toCharArray(javaParamType))) .build()); } proposals.add(sig.build()); } } catch(Exception e) { e.printStackTrace(); } } }); return proposals; } protected final static char[] BRACKETS= { '{', '}', '(', ')', '[', ']', '<', '>' }; protected final static char[] SEPARATORS = { ',' }; public JavaParamHelpMatcher.ParamRegion getScope(String fileParseContent, int cursorPosition) { JavaParamHelpMatcher matcher = new JavaParamHelpMatcher(BRACKETS, SEPARATORS); Document doc = new Document(fileParseContent); return matcher.findEnclosingPeerCharacters(doc, cursorPosition, 0); } public JavaParamHelpMatcher.ParamRegion updateScope(String fileParseContents, int cursorPosition) { JavaParamHelpMatcher matcher = new JavaParamHelpMatcher(BRACKETS, SEPARATORS); Document doc = new Document(fileParseContents); return matcher.findEnclosingPeerCharacters(doc, cursorPosition, 0); } public List<Problem> ProcessFileParseMessagesRequest(Integer fileId) { List<FileParseMessagesResponse.Problem> ret = new ArrayList<FileParseMessagesResponse.Problem>(); if (ActiveUnits.containsKey(fileId)) { CompilationUnit cu = ActiveUnits.get(fileId); IProblem[] problems = cu.getProblems(); for(IProblem problem: problems) { System.out.println(problem.toString()); FileParseMessagesResponse.Problem.Builder retProblem = FileParseMessagesResponse.Problem.newBuilder() .setId(problem.getID()) .setMessage(problem.getMessage()) .setFileName(new String(problem.getOriginatingFileName())) .setScopeStart(problem.getSourceStart()) .setScopeEnd(problem.getSourceEnd() + 1) .setLineNumber(problem.getSourceLineNumber()) .setProblemType(GetProblemType(problem)); for(String arg: problem.getArguments()) retProblem.addArguments(arg); ret.add(retProblem.build()); } } return ret; } private FileParseMessagesResponse.Problem.ProblemType GetProblemType(IProblem problem) { if (problem.isError()) return FileParseMessagesResponse.Problem.ProblemType.Error; if (problem.isWarning()) return FileParseMessagesResponse.Problem.ProblemType.Warning; return FileParseMessagesResponse.Problem.ProblemType.Message; } public List<JavaElement> ProcessQuickInfoRequest(String fileParseContents, String typeRootId, int cursorPosition) throws Exception { if (ActiveTypeRoots.containsKey(typeRootId)) { ITypeRoot cu = ActiveTypeRoots.get(typeRootId); cu.getBuffer().setContents(fileParseContents.toCharArray()); IJavaElement[] elements = cu.codeSelect(cursorPosition, 0); List<JavaElement> ret = new ArrayList<JavaElement>(); long flags = JavaElementLabelComposer.ALL_FULLY_QUALIFIED | JavaElementLabelComposer.ALL_DEFAULT | JavaElementLabelComposer.M_PRE_RETURNTYPE | JavaElementLabelComposer.F_PRE_TYPE_SIGNATURE; for(IJavaElement element: elements) { StringBuffer buffer = new StringBuffer(); JavaElementLabelComposer composer = new JavaElementLabelComposer(buffer); composer.appendElementLabel(element, flags); System.out.println(element.getPath().toString()); JavaElement.Builder b = JavaElement.newBuilder() .setDefinition(buffer.toString()); String javaDoc = null; try { javaDoc = element.getAttachedJavadoc(null); } catch(JavaModelException jme) { jme.printStackTrace(); } if (javaDoc != null) b.setJavaDoc(javaDoc); ret.add(b.build()); } return ret; } return null; } public List<FindDefinitionResponse.JavaElement> ProcessFindDefinintionRequest(String fileParseContents, String typeRootId, int cursorPosition) throws Exception { if (ActiveTypeRoots.containsKey(typeRootId)) { ITypeRoot cu = ActiveTypeRoots.get(typeRootId); cu.getBuffer().setContents(fileParseContents.toCharArray()); IJavaElement[] elements = cu.codeSelect(cursorPosition, 0); List<FindDefinitionResponse.JavaElement> ret = new ArrayList<FindDefinitionResponse.JavaElement>(); for(IJavaElement element: elements) { String definition = element.toString(); String path = element.getResource() != null ? element.getResource().getLocation().toOSString() : element.getPath().toOSString(); //String path = element.getPath().makeAbsolute().toOSString(); // element.getPath().toString(); boolean isAvailable = false; int posStart = -1; int posLength = 0; String contents = null; String classFileName = null; IClassFile classFileObj = null; ISourceReference srcRef = (ISourceReference)element; if (srcRef != null) { ISourceRange range = srcRef.getSourceRange(); if (SourceRange.isAvailable(range)) { isAvailable = true; posStart = range.getOffset(); posLength = range.getLength(); //if (path.endsWith(".jar")) //{ IOpenable op = element.getOpenable(); if (op != null && op instanceof IClassFile) { IBuffer buff = op.getBuffer(); classFileObj = (IClassFile)op; classFileName = classFileObj.getElementName(); contents = buff.getContents(); } //} } } FindDefinitionResponse.JavaElement.Builder retItem = FindDefinitionResponse.JavaElement.newBuilder() .setDefinition(definition) .setFilePath(path) .setHasSource(isAvailable) .setPositionStart(posStart) .setPositionLength(posLength); if (contents != null) { //int hashCode = classFileObj.hashCode(); String handle = classFileObj.getHandleIdentifier(); ActiveTypeRoots.put(handle, classFileObj); retItem.setFileName(classFileName); retItem.setTypeRootIdentifier(TypeRootIdentifier.newBuilder() .setHandle(handle) .build()); } System.out.println(retItem.toString()); if (contents != null) { retItem.setFileContents(contents); } ret.add(retItem.build()); } return ret; } return null; } public String ProcessOpenTypeRequest(String fileName) throws Exception { File file = new File(fileName); IFile[] files = WorkspaceRoot.findFilesForLocationURI(file.toURI(), IResource.FILE); if (files.length > 1) throw new Exception("Ambigous parse request for file " + fileName); else if (files.length == 0) throw new Exception("File not found: " + fileName); IJavaElement javaFile = JavaCore.create(files[0]); if (javaFile instanceof ITypeRoot) { //int hashCode = javaFile.hashCode(); String handle = javaFile.getHandleIdentifier(); ActiveTypeRoots.put(handle, (ITypeRoot)javaFile); return handle; } return null; } public void ProcessDisposeTypeRoot(String handle) { if (ActiveTypeRoots.containsKey(handle)) ActiveTypeRoots.remove(handle); } public String ProcessAddTypeRequest(String handle) { IJavaElement javaFile = JavaCore.create(handle); if (javaFile instanceof ITypeRoot) { String newHandle = javaFile.getHandleIdentifier(); ActiveTypeRoots.put(newHandle, (ITypeRoot)javaFile); return newHandle; } return null; } }