/*- * Copyright (C) 2011-2014 by Iwao AVE! * This program is made available under the terms of the MIT License. */ package org.eclipselabs.stlipse.cache; import static org.eclipselabs.stlipse.util.StripesClasses.*; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipselabs.stlipse.Activator; import org.eclipselabs.stlipse.javaeditor.JavaCompletionProposal; /** * @author Iwao AVE! */ public class BeanPropertyCache { private static final Map<IProject, Map<String, BeanPropertyInfo>> projectCache = new ConcurrentHashMap<IProject, Map<String, BeanPropertyInfo>>(); public static void clearBeanPropertyCache() { projectCache.clear(); } public static void clearBeanPropertyCache(IProject project) { projectCache.remove(project); } public static void clearBeanPropertyCache(IProject project, String qualifiedName) { Map<String, BeanPropertyInfo> beans = projectCache.get(project); if (beans != null) { String topLevelClass = removeExtension(qualifiedName); beans.remove(topLevelClass); // Clear cache for inner classes. String innerClassPrefix = topLevelClass + "."; for (Iterator<Entry<String, BeanPropertyInfo>> it = beans.entrySet().iterator(); it.hasNext();) { Entry<String, BeanPropertyInfo> entry = it.next(); if (entry.getKey().startsWith(innerClassPrefix)) { it.remove(); } } } } public static BeanPropertyInfo getBeanPropertyInfo(IJavaProject project, String fqn) { String qualifiedName = removeExtension(fqn); Map<String, BeanPropertyInfo> beans = projectCache.get(project.getProject()); BeanPropertyInfo beanProps = null; if (beans == null) { beans = new ConcurrentHashMap<String, BeanPropertyInfo>(); projectCache.put(project.getProject(), beans); } beanProps = beans.get(qualifiedName); if (beanProps == null) { final Map<String, String> readableFields = new LinkedHashMap<String, String>(); final Map<String, String> writableFields = new LinkedHashMap<String, String>(); final Map<String, EventProperty> eventHandlers = new LinkedHashMap<String, EventProperty>(); parseBean(project, qualifiedName, readableFields, writableFields, eventHandlers); beanProps = new BeanPropertyInfo(readableFields, writableFields, eventHandlers); } beans.put(qualifiedName, beanProps); return beanProps; } protected static void parseBean(IJavaProject project, String qualifiedName, final Map<String, String> readableFields, final Map<String, String> writableFields, final Map<String, EventProperty> eventHandlers) { try { final IType type = project.findType(qualifiedName); if (type != null) { if (type.isBinary()) { parseBinary(project, type, readableFields, writableFields, eventHandlers); } else { parseSource(project, type, qualifiedName, readableFields, writableFields, eventHandlers); } } } catch (JavaModelException e) { Activator.log(Status.ERROR, "Failed to find type " + qualifiedName, e); } } protected static void parseBinary(IJavaProject project, final IType type, final Map<String, String> readableFields, final Map<String, String> writableFields, final Map<String, EventProperty> eventHandlers) throws JavaModelException { parseBinaryFields(type, readableFields, writableFields); parseBinaryMethods(type, readableFields, writableFields, eventHandlers); String superclass = Signature.toString(type.getSuperclassTypeSignature()); if (!Object.class.getName().equals(superclass)) { parseBean(project, superclass, readableFields, writableFields, eventHandlers); } } protected static void parseBinaryMethods(final IType type, final Map<String, String> readableFields, final Map<String, String> writableFields, final Map<String, EventProperty> eventHandlers) throws JavaModelException { for (IMethod method : type.getMethods()) { int flags = method.getFlags(); if (Flags.isPublic(flags)) { final String methodName = method.getElementName(); final int parameterCount = method.getParameters().length; final String returnType = method.getReturnType(); if (Signature.C_VOID == returnType.charAt(0)) { if (BeanPropertyVisitor.isSetter(methodName, parameterCount)) { String fieldName = BeanPropertyVisitor.getFieldNameFromAccessor(methodName); String paramType = method.getParameterTypes()[0]; writableFields.put(fieldName, Signature.toString(paramType)); } } else { if (BeanPropertyVisitor.isGetter(methodName, parameterCount)) { String fieldName = BeanPropertyVisitor.getFieldNameFromAccessor(methodName); readableFields.put(fieldName, Signature.toString(returnType)); } else if (RESOLUTION.equals(Signature.toString(returnType)) && parameterCount == 0) { parseBinaryEventHandler(method, eventHandlers); } } } } } protected static void parseBinaryEventHandler(IMethod method, final Map<String, EventProperty> eventHandlers) throws JavaModelException { boolean isInterceptor = false; boolean isDefaultHandler = false; String annotationValue = null; for (IAnnotation annotation : method.getAnnotations()) { String annotationType = annotation.getElementName(); if (BEFORE.equals(annotationType) || AFTER.equals(annotationType)) { isInterceptor = true; break; } isDefaultHandler |= DEFAULT_HANDLER.equals(annotationType); if (HANDLES_EVENT.equals(annotationType)) { annotationValue = (String)annotation.getMemberValuePairs()[0].getValue(); } } if (!isInterceptor) { String methodName = method.getElementName(); EventProperty eventProperty = new EventProperty(); eventProperty.setDefaultHandler(isDefaultHandler); eventProperty.setMethodName(methodName); eventHandlers.put(annotationValue == null ? methodName : annotationValue, eventProperty); } } protected static void parseBinaryFields(final IType type, final Map<String, String> readableFields, final Map<String, String> writableFields) throws JavaModelException { for (IField field : type.getFields()) { int flags = field.getFlags(); if (Flags.isPublic(flags)) { String fieldName = field.getElementName(); String qualifiedType = Signature.toString(field.getTypeSignature()); readableFields.put(fieldName, qualifiedType); if (!Flags.isFinal(flags)) { writableFields.put(fieldName, qualifiedType); } } } } protected static void parseSource(IJavaProject project, final IType type, final String qualifiedName, final Map<String, String> readableFields, final Map<String, String> writableFields, final Map<String, EventProperty> eventHandlers) throws JavaModelException { ICompilationUnit compilationUnit = (ICompilationUnit)type.getAncestor(IJavaElement.COMPILATION_UNIT); ASTParser parser = ASTParser.newParser(AST.JLS4); parser.setKind(ASTParser.K_COMPILATION_UNIT); parser.setSource(compilationUnit); parser.setResolveBindings(true); // parser.setIgnoreMethodBodies(true); CompilationUnit astUnit = (CompilationUnit)parser.createAST(null); astUnit.accept(new BeanPropertyVisitor(project, qualifiedName, readableFields, writableFields, eventHandlers)); } public static List<String> searchEventHandler(IJavaProject project, String qualifiedName, String matchStr, boolean isValidation, boolean alwaysIncludeDefault) { final List<String> results = new ArrayList<String>(); final BeanPropertyInfo beanProperty = getBeanPropertyInfo(project, qualifiedName); if (beanProperty != null) { for (Entry<String, EventProperty> eventHandler : beanProperty.getEventHandlers() .entrySet()) { String handlerName = eventHandler.getKey(); EventProperty eventProperty = eventHandler.getValue(); if ((alwaysIncludeDefault && eventProperty.isDefaultHandler()) || (isValidation && handlerName.equals(matchStr)) || handlerName.startsWith(matchStr)) { results.add(handlerName); } } } return results; } public static Map<String, EventProperty> getEventHandlers(IJavaProject project, String qualifiedName) { final BeanPropertyInfo beanProperty = getBeanPropertyInfo(project, qualifiedName); return beanProperty == null ? Collections.<String, EventProperty> emptyMap() : beanProperty.getEventHandlers(); } public static Map<String, String> searchFields(IJavaProject project, String qualifiedName, String matchStr, boolean includeReadOnly, int currentIdx, boolean isValidation) { final Map<String, String> results = new LinkedHashMap<String, String>(); String searchStr; final int startIdx = currentIdx + 1; final int dotIdx = getDotIndex(matchStr, startIdx); if (dotIdx == -1) searchStr = matchStr.substring(startIdx); else searchStr = matchStr.substring(startIdx, dotIdx); final int bracePos = searchStr.indexOf("["); searchStr = bracePos > -1 ? searchStr.substring(0, bracePos) : searchStr; final boolean isPrefixMatch = !isValidation && dotIdx == -1; final BeanPropertyInfo beanProperty = getBeanPropertyInfo(project, qualifiedName); if (beanProperty != null) { final Map<String, String> fields = includeReadOnly ? beanProperty.getReadableFields() : beanProperty.getWritableFields(); for (Entry<String, String> entry : fields.entrySet()) { final String fieldName = entry.getKey(); final String fieldQualifiedName = entry.getValue(); if (matched(fieldName, searchStr, isPrefixMatch)) { if (dotIdx > -1) { return searchFields(project, fieldQualifiedName, matchStr, includeReadOnly, dotIdx, isValidation); } else { results.put(fieldName, fieldQualifiedName); } } } } return results; } public static List<ICompletionProposal> buildFieldNameProposal(Map<String, String> fields, final String input, final int offset, final int replacementLength) { List<ICompletionProposal> proposalList = new ArrayList<ICompletionProposal>(); int lastDot = input.lastIndexOf("."); String prefix = lastDot > -1 ? input.substring(0, lastDot) : ""; int relevance = fields.size(); for (Entry<String, String> fieldEntry : fields.entrySet()) { String fieldName = fieldEntry.getKey(); String qualifiedName = fieldEntry.getValue(); StringBuilder replaceStr = new StringBuilder(); if (lastDot > -1) replaceStr.append(prefix).append('.'); replaceStr.append(fieldName); StringBuilder displayStr = new StringBuilder(); displayStr.append(fieldName).append(" - ").append(qualifiedName); ICompletionProposal proposal = new JavaCompletionProposal(replaceStr.toString(), offset, replacementLength, replaceStr.length(), Activator.getIcon(), displayStr.toString(), null, null, relevance--); proposalList.add(proposal); } return proposalList; } private static String removeExtension(String src) { if (src != null && src.endsWith(".java")) return src.substring(0, src.length() - 5); else return src; } private static int getDotIndex(String str, int startIdx) { boolean isIndexedProperty = false; for (int i = startIdx; i < str.length(); i++) { char c = str.charAt(i); if (!isIndexedProperty && c == '.') return i; else if (!isIndexedProperty && c == '[') isIndexedProperty = true; else if (c == ']') isIndexedProperty = false; } return -1; } private static boolean matched(String fieldName, String searchStr, boolean prefixMatch) { return (searchStr == null || searchStr.length() == 0) || (prefixMatch ? fieldName.toLowerCase().startsWith(searchStr.toLowerCase()) : fieldName.equals(searchStr)); } }