blob: 5175158cff6b8a96ec81a3cdf6a45916e56aa001 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007 Oracle Corporation.
* 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:
* Cameron Bateman/Oracle - initial API and implementation
*
********************************************************************************/
package org.eclipse.jst.jsf.common.util;
import java.beans.Introspector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jst.jsf.common.JSFCommonPlugin;
/**
* A class that does bean introspection on a JDT IType
*
* This functionality is not meant to replace runtime bean
* introspection. Rather, it is meant to provide a
* more "lightweight" (in terms of class loading as well as
* error handling of bean instantiation out of context) way
* to determine a bean's properties at design time.
*
* This class may not be sub-classed by clients.
*
* @author cbateman
*
*/
public class JDTBeanIntrospector
{
private final static String GET_PREFIX = "get"; //$NON-NLS-1$
private final static String SET_PREFIX = "set"; //$NON-NLS-1$
private final static String IS_PREFIX = "is"; //$NON-NLS-1$
private final IType _type;
private final HashMap<String, String> _resolvedSignatures;
/**
* @param type
*/
public JDTBeanIntrospector(IType type)
{
_type = type;
_resolvedSignatures = new HashMap<String, String>();
}
/**
* @return an map of all properties with the property names
* as keys and the values being JDTBeanProperty objects representing
* the properties.
*/
public Map<String, JDTBeanProperty> getProperties()
{
_resolvedSignatures.clear();
final Map<String, JDTBeanProperty> propertiesWorkingCopy =
new HashMap<String, JDTBeanProperty>();
final IMethod[] methods = getAllMethods();
for (int i = 0; i < methods.length; i++)
{
final IMethod method = methods[i];
try
{
processPropertyMethod(method, propertiesWorkingCopy);
}
catch (JavaModelException jme)
{
// log and then proceed to next method
JSFCommonPlugin.log(jme, "Error processing IMethod for bean property info"); //$NON-NLS-1$
}
}
final Map properties = new HashMap();
for (Entry<String, JDTBeanProperty> entry : propertiesWorkingCopy.entrySet())
{
final String key = entry.getKey();
JDTBeanPropertyWorkingCopy wcopy = (JDTBeanPropertyWorkingCopy) entry.getValue();
properties.put(key, wcopy.toValueObject());
}
return properties;
}
private void processPropertyMethod(IMethod method, Map<String, JDTBeanProperty> properties) throws JavaModelException
{
// to be a bean method, it must not a constructor, must be public
// and must not be static
if (!method.isConstructor()
&& ( Flags.isPublic(method.getFlags())
|| _type.isInterface())
&& !Flags.isStatic(method.getFlags()))
{
final String methodName = method.getElementName();
final String returnType = method.getReturnType();
// either starts with get or is boolean and starts with is
// is access must start with 'is', have a boolean return type and no parameters
final boolean startsWithIs = methodName.startsWith(IS_PREFIX)
&& Signature.SIG_BOOLEAN.equals(returnType)
&& method.getNumberOfParameters() == 0
&& methodName.length() > IS_PREFIX.length();
// get accessor must start with 'get', have no parameters and return non-void
final boolean startsWithGet = (methodName.startsWith(GET_PREFIX)
&& method.getNumberOfParameters() == 0)
&& !Signature.SIG_VOID.equals(returnType)
&& methodName.length() > GET_PREFIX.length();
// mutator must start with 'set' and have one parameter and a void return type
final boolean startsWithSet = methodName.startsWith(SET_PREFIX)
&& method.getNumberOfParameters() == 1
&& Signature.SIG_VOID.equals(returnType)
&& methodName.length() > SET_PREFIX.length();
if (startsWithGet || startsWithSet || startsWithIs)
{
final String propertyName =
Introspector.decapitalize(methodName.substring(startsWithIs ? 2 : 3));
JDTBeanPropertyWorkingCopy workingCopy =
(JDTBeanPropertyWorkingCopy) properties.get(propertyName);
if (workingCopy == null)
{
workingCopy = new JDTBeanPropertyWorkingCopy(_type, _resolvedSignatures);
properties.put(propertyName, workingCopy);
}
if (startsWithIs)
{
workingCopy.setIsGetter(method);
}
else if (startsWithGet)
{
workingCopy.setGetter(method);
}
else if (startsWithSet)
{
workingCopy.addSetter(method);
}
}
}
}
/**
* @return all methods for the type including inherited ones
*/
public IMethod[] getAllMethods()
{
IMethod[] methods = new IMethod[0];
try
{
// type not resolved so don't proceed
if (_type != null)
{
// TODO: type hierarchy is potentially expensive, should
// cache once and listen for changes
ITypeHierarchy hierarchy = _type.newSupertypeHierarchy(new NullProgressMonitor());
methods = getAllMethods(hierarchy, _type);
}
}
catch(JavaModelException jme)
{
JSFCommonPlugin.log(jme, "Error getting type information for bean"); //$NON-NLS-1$
}
return methods;
}
/**
* @param typeHierarchy
* @param type
* @return all methods of the type and it's super types
*/
private static IMethod[] getAllMethods(final ITypeHierarchy typeHierarchy, final IType type)
{
final List<IMethod> methods = new ArrayList<IMethod>();
final IType[] superTypes = typeHierarchy.getAllSuperclasses(type);
final IType[] closure = new IType[superTypes.length+1];
closure[0] = type;
System.arraycopy(superTypes, 0, closure, 1, superTypes.length);
for (int i = 0; i < closure.length; i++)
{
try {
final IType superType = closure[i];
methods.addAll(Arrays.asList(superType.getMethods()));
} catch (JavaModelException e) {
JSFCommonPlugin.log(e, "Error getting super type information for bean"); //$NON-NLS-1$
}
}
return methods.toArray(new IMethod[methods.size()]);
}
}