Java tutorial
/*- * Copyright (C) 2011-2014 by Iwao AVE! * This program is made available under the terms of the MIT License. */ package org.eclipselabs.stlipse.javaeditor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.CompletionContext; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IAnnotatable; import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMemberValuePair; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext; import org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer; import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipselabs.stlipse.Activator; import org.eclipselabs.stlipse.cache.BeanPropertyCache; import org.eclipselabs.stlipse.cache.BeanPropertyVisitor; /** * @author Iwao AVE! */ public class JavaCompletionProposalComputer implements IJavaCompletionProposalComputer { private static String VALIDATE_NESTED = "ValidateNestedProperties"; private static String VALIDATE = "Validate"; private static String STRICT_BINDING = "StrictBinding"; private static String AFTER = "After"; private static String BEFORE = "Before"; private static String VALIDATION_METHOD = "ValidationMethod"; private static String WIZARD = "Wizard"; public void sessionStarted() { // Nothing todo for now. } public List<ICompletionProposal> computeCompletionProposals(ContentAssistInvocationContext context, IProgressMonitor monitor) { List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(); if (context instanceof JavaContentAssistInvocationContext) { JavaContentAssistInvocationContext javaContext = (JavaContentAssistInvocationContext) context; CompletionContext coreContext = javaContext.getCoreContext(); ICompilationUnit unit = javaContext.getCompilationUnit(); try { if (unit != null && unit.isStructureKnown()) { int offset = javaContext.getInvocationOffset(); IJavaElement element = unit.getElementAt(offset); if (element != null && element instanceof IAnnotatable) { ValueInfo valueInfo = scanAnnotation(offset, (IAnnotatable) element); if (valueInfo != null) { int replacementLength = valueInfo.getValueLength(); IJavaProject project = javaContext.getProject(); String beanFqn = unit.getType(element.getParent().getElementName()) .getFullyQualifiedName(); if (valueInfo.isField()) { if (valueInfo.isPropertyOmmitted()) { StringBuilder matchStr = resolveBeanPropertyName(element); if (matchStr.length() > 0) { char[] token = coreContext.getToken(); matchStr.append('.').append(token); Map<String, String> fields = BeanPropertyCache.searchFields(project, beanFqn, matchStr.toString(), false, -1, false); proposals.addAll(BeanPropertyCache.buildFieldNameProposal(fields, String.valueOf(token), coreContext.getTokenStart() + 1, replacementLength)); } } else { String input = String.valueOf(coreContext.getToken()); Map<String, String> fields = BeanPropertyCache.searchFields(project, beanFqn, input, false, -1, false); proposals.addAll(BeanPropertyCache.buildFieldNameProposal(fields, input, coreContext.getTokenStart() + 1, replacementLength)); } } else if (valueInfo.isEventHandler()) { String input = String.valueOf(coreContext.getToken()); boolean isNot = false; if (input.length() > 0 && input.startsWith("!")) { isNot = true; input = input.length() > 1 ? input.substring(1) : ""; } List<String> events = BeanPropertyCache.searchEventHandler(project, beanFqn, input, false, false); int relevance = events.size(); for (String event : events) { String replaceStr = isNot ? "!" + event : event; ICompletionProposal proposal = new JavaCompletionProposal(replaceStr, coreContext.getTokenStart() + 1, replacementLength, replaceStr.length(), Activator.getIcon(), event, null, null, relevance--); proposals.add(proposal); } } } } } } catch (JavaModelException e) { Activator.log(Status.ERROR, "Something went wrong.", e); } } return proposals; } private StringBuilder resolveBeanPropertyName(IJavaElement element) throws JavaModelException { StringBuilder result = new StringBuilder(); int elementType = element.getElementType(); String elementName = element.getElementName(); if (elementType == IJavaElement.FIELD) { result.append(elementName); } else if (elementType == IJavaElement.METHOD) { IMethod method = (IMethod) element; if (Flags.isPublic(method.getFlags()) && (isSetter(method) || isGetter(method))) { result.append(BeanPropertyVisitor.getFieldNameFromAccessor(elementName)); } } return result; } private boolean isSetter(IMethod method) throws JavaModelException { String name = method.getElementName(); return isVoid(method) && name.startsWith("set") && name.length() > 3; } private boolean isGetter(IMethod method) throws JavaModelException { String name = method.getElementName(); return !isVoid(method) && ((name.startsWith("get") && name.length() > 3) || name.startsWith("is") && name.length() > 2); } private boolean isVoid(IMethod method) throws JavaModelException { return String.valueOf(Signature.C_VOID).equals(method.getReturnType()); } private ValueInfo scanAnnotation(int offset, IAnnotatable annotatable) throws JavaModelException { IAnnotation[] annotations = annotatable.getAnnotations(); for (IAnnotation annotation : annotations) { ISourceRange sourceRange = annotation.getSourceRange(); if (isInRange(sourceRange, offset)) { String annotationName = annotation.getElementName(); if (VALIDATE_NESTED.equals(annotationName)) { // parse nested @Validate IMemberValuePair[] valuePairs = annotation.getMemberValuePairs(); for (IMemberValuePair valuePair : valuePairs) { if ("value".equals(valuePair.getMemberName()) && valuePair.getValueKind() == IMemberValuePair.K_ANNOTATION) { // the value is an array of IAnnotation Object[] validates = (Object[]) valuePair.getValue(); for (Object validate : validates) { IAnnotation validateAnnotation = (IAnnotation) validate; ISourceRange validateSourceRange = validateAnnotation.getSourceRange(); if (isInRange(validateSourceRange, offset)) return parseAnnotation(offset, validateAnnotation, validateSourceRange); } } } } else if (isNonNestedSupportedAnnotation(annotationName)) { return parseAnnotation(offset, annotation, sourceRange); } } } return null; } private static boolean isNonNestedSupportedAnnotation(String annotationName) { final List<String> annotations = Arrays.asList(VALIDATE, STRICT_BINDING, BEFORE, AFTER, VALIDATION_METHOD, WIZARD); return annotations.contains(annotationName); } private ValueInfo parseAnnotation(int offset, IAnnotation annotation, ISourceRange sourceRange) throws JavaModelException { int annotationOffset = sourceRange.getOffset(); String source = annotation.getSource(); int index = source.indexOf('(', annotation.getElementName().length() + 1); if (index > -1) { int stringValueLength = 0; boolean scanningName = true; boolean scanningValue = false; boolean inStringValue = false; boolean inArrayValue = false; boolean escaped = false; StringBuilder optionName = new StringBuilder(); for (index++; index < source.length(); index++) { char c = source.charAt(index); if (scanningName) { if (c == '=') scanningName = false; else if (c != ' ') optionName.append(c); } else if (!scanningValue) { if (c != ' ') { scanningValue = true; if (c == '"') { inStringValue = true; stringValueLength = 0; } else if (c == '{') inArrayValue = true; } } else { // scanning value part if (inStringValue) { if (escaped) ; // ignore the escaped character else if (c == '\\') escaped = true; else if (c == '"') { if (annotationOffset + index >= offset) break; inStringValue = false; stringValueLength = 0; } if (inStringValue) stringValueLength++; } else if (inArrayValue) { if (c == '"') inStringValue = true; else if (c == '}') inArrayValue = false; else ; // ignore } else if (c == ',') { scanningValue = false; scanningName = true; optionName.setLength(0); } } } if (optionName.length() > 0) { return new ValueInfo(annotation.getElementName(), optionName.toString(), stringValueLength); } } return null; } private boolean isInRange(ISourceRange sourceRange, int offset) { int start = sourceRange.getOffset(); int end = start + sourceRange.getLength(); return start <= offset && offset <= end; } public List<IContextInformation> computeContextInformation(ContentAssistInvocationContext context, IProgressMonitor monitor) { return Collections.emptyList(); } public String getErrorMessage() { return null; } public void sessionEnded() { // Nothing todo for now. } static class ValueInfo { private String annotationName; private String attributeName; private int valueLength; public ValueInfo(String annotationName, String attributeName, int valueLength) { super(); this.annotationName = annotationName; this.attributeName = attributeName; this.valueLength = valueLength; } public boolean isField() { return (STRICT_BINDING.equals(annotationName) && ("allow".equals(attributeName) || "deny".equals(attributeName))) || (VALIDATE.equals(annotationName) && "field".equals(attributeName)); } public boolean isPropertyOmmitted() { return !STRICT_BINDING.equals(annotationName); } public boolean isEventHandler() { return (("on".equals(attributeName) && (VALIDATE.equals(annotationName) || AFTER.equals(annotationName) || BEFORE.equals(annotationName) || VALIDATION_METHOD.equals(annotationName)))) || ("startEvents".equals(attributeName) && WIZARD.equals(annotationName)); } public String getAnnotationName() { return annotationName; } public String getAttributeName() { return attributeName; } public int getValueLength() { return valueLength; } } }