/******************************************************************************* * 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()]); } }