blob: 63ded692f6a0cb07520acd96ecb2935d15f9de05 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004 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;
}
}