Skip to main content
summaryrefslogtreecommitdiffstats
blob: 07d34fb46ebb3579727945bf6b856bace0fe6bdb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
package org.eclipse.jst.jsf.common.internal.provisional.util;

import java.beans.Introspector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

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
 * 
 * @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;

	/**
	 * @param type
	 */
	public JDTBeanIntrospector(IType type)
	{
		_type = type;
	}
	
	/**
	 * @return an map of all properties with the property names
     * as keys and the values being JDTBeanProperty objects representing
     * the properties.
	 */
	public Map getProperties()
	{
		final Map	    propertiesWorkingCopy = new HashMap();
		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 (final Iterator it = propertiesWorkingCopy.keySet().iterator(); it.hasNext();)
        {
            final String key = (String) it.next();
            JDTBeanPropertyWorkingCopy  wcopy = 
                (JDTBeanPropertyWorkingCopy) propertiesWorkingCopy.get(key);
            properties.put(key, wcopy.toValueObject());
        }
        
		return properties;
	}
	
	private void processPropertyMethod(IMethod method, Map properties) throws JavaModelException
	{
		// to be a bean method, it must not a constructor, must be public
		// and must not be static
		if (!method.isConstructor()
				&& (method.getFlags() & Flags.AccPublic) != 0
				&& (method.getFlags() & Flags.AccStatic) == 0)
		{
			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);
					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   methods = new ArrayList();
        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 < superTypes.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 (IMethod[]) methods.toArray(new IMethod[0]);
    }

	
}

Back to the top