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