/******************************************************************************* * Copyright (c) 2004, 2006 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.jst.jsp.ui.internal.contentassist; import java.beans.Introspector; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import com.ibm.icu.util.StringTokenizer; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeHierarchy; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jst.jsp.ui.internal.Logger; /** * Navigates the IJavaProject classpath (incl. source) on a given resource and infers bean properties * given a fully qualified beanname. Bean properties can be retrieved using: * <code>getRuntimeProperties(IResource baseResource, String typeName)</code> * * @plannedfor 1.0 */ public class BeanInfoProvider implements IBeanInfoProvider { public class JavaPropertyDescriptor implements IJavaPropertyDescriptor { String fType = null; String fName = null; boolean fReadable = true; boolean fWritable = true; public JavaPropertyDescriptor(String name, String type, boolean readable, boolean writable) { fName = name; fType = type; fReadable = readable; fWritable = writable; } public String getDeclaredType() { return fType; } public String getDisplayName() { return fName; } public String getName() { return fName; } public boolean getReadable() { return fReadable; } public boolean getWriteable() { return fWritable; } } // looks up encoded type (see Class.getName), and gives you a displayable string private HashMap fEncodedTypeMap = null; // to avoid repeat properties from showing up private HashSet fRepeatMethods = null; public BeanInfoProvider() { fRepeatMethods = new HashSet(); } /** * Returns the inferred properties of a bean based on the project from the baseResource, * and the fully qualified name of the bean. * * @param baseResource the base resource where the bean is being used * @param typeName the <i>fully qualified</i> type name (eg. javax.swing.JButton) of the bean */ public IJavaPropertyDescriptor[] getRuntimeProperties(IResource baseResource, String typeName) { IJavaProject javaProject = JavaCore.create(baseResource.getProject()); QualifiedName typeQualifiedName = getTypeQualifiedName(typeName); List getMethodResults = new ArrayList(); List isMethodResults = new ArrayList(); List setMethodResults = new ArrayList(); List descriptorResults = new ArrayList(); try { IType type = javaProject.findType(typeQualifiedName.getQualifier() + "." + typeQualifiedName.getLocalName()); //$NON-NLS-1$ // type must exist if(type != null) { ITypeHierarchy hierarchy = type.newTypeHierarchy(null); IType[] supers = hierarchy.getAllSuperclasses(type); IMethod[] methods = type.getMethods(); // iterate the bean's methods for (int i = 0; i < methods.length; i++) acceptMethod(getMethodResults, isMethodResults, setMethodResults, methods[i]); // the bean hierarchy's methods for (int i = 0; i < supers.length; i++) { methods = supers[i].getMethods(); for (int j = 0; j < methods.length; j++) acceptMethod(getMethodResults, isMethodResults, setMethodResults, methods[j]); } adaptMethodsToPropertyDescriptors(getMethodResults, isMethodResults, setMethodResults, descriptorResults); } } catch (JavaModelException jmex) { Logger.logException("Problem navigating JavaProject in BeanInfoProvider", jmex); //$NON-NLS-1$ } IJavaPropertyDescriptor[] finalResults = new IJavaPropertyDescriptor[descriptorResults.size()]; System.arraycopy(descriptorResults.toArray(), 0, finalResults, 0, descriptorResults.size()); return finalResults; } /** * Retrieves the necessary information from method declaration lists, creates and fills a list of JavaPropertyDescriptors. * @param getMethods * @param isMethods * @param setMethods * @param descriptorResults */ private void adaptMethodsToPropertyDescriptors(List getMethods, List isMethods, List setMethods, List descriptors) throws JavaModelException { List readable = new ArrayList(); HashMap types = new HashMap(); // iterate through get* and is* methods, updating 'readable' list and 'types' map filterGetMethods(getMethods, readable, types); filterIsMethods(isMethods, readable, types); // iterate set* methods, checking overlap w/ readable Iterator it = setMethods.iterator(); IMethod temp = null; String name = ""; //$NON-NLS-1$ String type = ""; //$NON-NLS-1$ String[] encodedParams = null; String returnType = ""; //$NON-NLS-1$ String param0 = ""; //$NON-NLS-1$ while (it.hasNext()) { temp = (IMethod) it.next(); name = createPropertyNameFromMethod(temp); // invalid naming convention if (name == null) continue; returnType = getDecodedTypeName(temp.getReturnType()); // setter should have no return type if (!returnType.equals("void")) //$NON-NLS-1$ continue; // need to get type from parameter encodedParams = temp.getParameterTypes(); if (encodedParams != null && encodedParams.length > 0) { if (encodedParams.length > 1) { // multiple params param0 = getDecodedTypeName(encodedParams[0]); if (!param0.equals("int")) //$NON-NLS-1$ // not a valid indexed property continue; type = getDecodedTypeName(encodedParams[1]); } else { // one param, regular setter if (isArray(encodedParams[0])) type = getDecodedTypeName(encodedParams[0]); } } if (readable.contains(name)) { // writable and readable if (!fRepeatMethods.contains(name)) { descriptors.add(new JavaPropertyDescriptor(name, (String) types.get(name), true, true)); readable.remove(name); fRepeatMethods.add(name); } } else { // wasn't readable, just writable String[] params = temp.getParameterTypes(); // can't be setProperty if no parameters if (!(params.length > 0)) continue; if (!fRepeatMethods.contains(name)) { type = getDecodedTypeName(params[0]); descriptors.add(new JavaPropertyDescriptor(name, type, false, true)); fRepeatMethods.add(name); } } } // add leftover from readable, get* and is* methods (readable = true, writable = false) it = readable.iterator(); while (it.hasNext()) { name = (String) it.next(); if (!fRepeatMethods.contains(name)) { descriptors.add(new JavaPropertyDescriptor(name, (String) types.get(name), true, false)); fRepeatMethods.add(name); } } } private void filterGetMethods(List getMethods, List readable, HashMap types) throws JavaModelException { IMethod temp; String name; String encodedReturnType; String returnType; Iterator it = getMethods.iterator(); String[] encodedParams; String paramType; // iterate get* methods while (it.hasNext()) { temp = (IMethod) it.next(); name = createPropertyNameFromMethod(temp); // invalid bean naming convention if (name == null) continue; encodedReturnType = temp.getReturnType(); returnType = getDecodedTypeName(encodedReturnType); // can't get be a getProperty if returns void if (returnType.equals("void")) //$NON-NLS-1$ continue; // check params in case it's indexed propety encodedParams = temp.getParameterTypes(); if (encodedParams != null && encodedParams.length == 1) { paramType = getDecodedTypeName(encodedParams[0]); // syntax is > Type getter(int); if (!paramType.equals("int")) { //$NON-NLS-1$ //it's not an indexed property continue; } // it is indexed, prop type is an ARRAY returnType += "[]"; //$NON-NLS-1$ } readable.add(name); types.put(name, returnType); } } private void filterIsMethods(List isMethodResults, List readable, HashMap types) throws JavaModelException { IMethod temp; String name; String encodedReturnType; String returnType; String[] encodedParams; String paramType; // iterate is* methods Iterator it = isMethodResults.iterator(); while (it.hasNext()) { temp = (IMethod) it.next(); name = createPropertyNameFromMethod(temp); // invalid bean naming convention if (name == null) continue; encodedReturnType = temp.getReturnType(); returnType = getDecodedTypeName(encodedReturnType); // isProperty only valid for boolean if (!returnType.equals("boolean")) //$NON-NLS-1$ continue; // check params in case it's indexed propety encodedParams = temp.getParameterTypes(); if (encodedParams != null && encodedParams.length == 1) { paramType = getDecodedTypeName(encodedParams[0]); // syntax is > Type getter(int); if (!paramType.equals("int")) { //$NON-NLS-1$ //it's not a valid indexed property continue; } } readable.add(name); types.put(name, returnType); } } /** * Pass in a get*|set*|is* method and it will return an inferred property name using <code>Introspector.decapitalize(String)</code> * @param temp * @return an inferred property name based on the IMethod name, null if the name is not valid according to bean spec */ private String createPropertyNameFromMethod(IMethod temp) { String name = temp.getElementName(); if (name.startsWith("is")) //$NON-NLS-1$ name = Introspector.decapitalize(name.substring(2)); else // must be get or set name = Introspector.decapitalize(name.substring(3)); return name; } /** * Initial filtering of methods. Checks prefix if it's valid length. If the prefix is "get" the method name * is placed in the getMethodResults List. If the prefix is "is", the name is added to the isMethodResults list. If the * prefix is "set", it's added to the setMethodResultsList. * * @param getMethodResults * @param isMethodResults * @param setMethodResults * @param method */ private void acceptMethod(List getMethodResults, List isMethodResults, List setMethodResults, IMethod method) throws JavaModelException { if (!fRepeatMethods.contains(method.getElementName())) { fRepeatMethods.add(method.getElementName()); int flags = method.getFlags(); String methodName = method.getElementName(); if (Flags.isPublic(flags)) { if (methodName.length() > 3 && methodName.startsWith("get")) //$NON-NLS-1$ getMethodResults.add(method); else if (methodName.length() > 2 && methodName.startsWith("is")) //$NON-NLS-1$ isMethodResults.add(method); else if (methodName.length() > 3 && methodName.startsWith("set")) //$NON-NLS-1$ setMethodResults.add(method); } } } /** * @param typeName * @return a Qualified name with the package as the qualifier, and class name as LocalName */ private QualifiedName getTypeQualifiedName(String typeName) { StringTokenizer st = new StringTokenizer(typeName, ".", false); //$NON-NLS-1$ int length = st.countTokens(); int count = 0; StringBuffer root = new StringBuffer(); while (count++ < length - 1) { root.append(st.nextToken()); if (count < length - 1) root.append('.'); } return new QualifiedName(root.toString(), st.nextToken()); } /** * Checks if encodedTypeName is an array * @param encodedTypeName * @return true if encodedTypeName is an array, false otherwise. */ private boolean isArray(String encodedTypeName) { if (encodedTypeName != null && encodedTypeName.length() > 0) { if (encodedTypeName.charAt(0) == '[') return true; } return false; } /** * Returns the decoded (displayable) name fo the type. * Either a primitive type (int, long, float...) Object (String) * @param type * @return decoded name for the encoded string */ private String getDecodedTypeName(String encoded) { HashMap map = getEncodedTypeMap(); StringBuffer decoded = new StringBuffer(); char BRACKET = '['; String BRACKETS = "[]"; //$NON-NLS-1$ char identifier = ' '; int last = 0; // count brackets while (encoded.indexOf(BRACKET, last) != -1) { last++; } identifier = encoded.charAt(last); Object primitiveType = map.get(String.valueOf(identifier)); // L > binary type name, Q > source type name if (identifier == 'L' || identifier == 'Q') { // handle object String classname = encoded.substring(last + 1, encoded.length() - 1); decoded.append(classname); } else if (primitiveType != null) { // handle primitive type (from IField.getSignature()) decoded.append((String) primitiveType); } else { // handle primitive type (from Class.getName()) decoded.append(encoded); } // handle arrays if (last > 0) { for (int i = 0; i < last; i++) { decoded.append(BRACKETS); } } return decoded.toString(); } /** * from Class.getName() javadoc * also see Signature in jdt.core api *<pre> * B byte * C char * D double * F float * I int * J long * Lclassname; class or interface * Qsourcename; source * S short * Z boolean * V void *</pre> * * @return the "encoding letter" to "type" map. */ private HashMap getEncodedTypeMap() { if (fEncodedTypeMap == null) { fEncodedTypeMap = new HashMap(); fEncodedTypeMap.put("B", "byte"); //$NON-NLS-1$ //$NON-NLS-2$ fEncodedTypeMap.put("C", "char"); //$NON-NLS-1$ //$NON-NLS-2$ fEncodedTypeMap.put("D", "double"); //$NON-NLS-1$ //$NON-NLS-2$ fEncodedTypeMap.put("F", "float"); //$NON-NLS-1$ //$NON-NLS-2$ fEncodedTypeMap.put("I", "int"); //$NON-NLS-1$ //$NON-NLS-2$ fEncodedTypeMap.put("J", "long"); //$NON-NLS-1$ //$NON-NLS-2$ fEncodedTypeMap.put("S", "short"); //$NON-NLS-1$ //$NON-NLS-2$ fEncodedTypeMap.put("Z", "boolean"); //$NON-NLS-1$ //$NON-NLS-2$ fEncodedTypeMap.put("V", "void"); //$NON-NLS-1$ //$NON-NLS-2$ } return fEncodedTypeMap; } }