001 /*
002 * Copyright 2004-2012 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.codehaus.griffon.runtime.core;
017
018 import griffon.core.GriffonApplication;
019 import griffon.core.GriffonClass;
020 import griffon.exceptions.NewInstanceCreationException;
021 import griffon.util.GriffonClassUtils;
022 import griffon.util.GriffonNameUtils;
023 import groovy.lang.*;
024 import org.codehaus.griffon.runtime.util.GriffonApplicationHelper;
025 import org.codehaus.groovy.runtime.InvokerHelper;
026 import org.slf4j.Logger;
027 import org.slf4j.LoggerFactory;
028
029 import java.beans.PropertyDescriptor;
030 import java.lang.reflect.InvocationTargetException;
031 import java.lang.reflect.Method;
032 import java.lang.reflect.Modifier;
033 import java.util.ArrayList;
034 import java.util.List;
035 import java.util.Set;
036 import java.util.TreeSet;
037
038 import static griffon.util.GriffonExceptionHandler.sanitize;
039
040 /**
041 * Abstract base class for Griffon types that provides common functionality for
042 * evaluating conventions within classes
043 *
044 * @author Steven Devijver (Grails 0.1)
045 * @author Graeme Rocher (Grails 0.1)
046 * @author Andres Almiray
047 * @since 0.9.1
048 */
049 public abstract class AbstractGriffonClass implements GriffonClass {
050 private final Class<?> clazz;
051 private final String type;
052 private final GriffonApplication app;
053 private final String fullName;
054 private final String name;
055 private final String packageName;
056 private final String naturalName;
057 private final String shortName;
058 private final String propertyName;
059 private final String logicalPropertyName;
060 private final ClassPropertyFetcher classPropertyFetcher;
061
062 protected final Set<String> eventsCache = new TreeSet<String>();
063 protected final Logger log;
064
065 /**
066 * <p>Contructor to be used by all child classes to create a
067 * new instance and get the name right.
068 *
069 * @param app
070 * @param clazz
071 * @param type
072 * @param trailingName
073 */
074 public AbstractGriffonClass(GriffonApplication app, Class<?> clazz, String type, String trailingName) {
075 this.app = app;
076 this.clazz = clazz;
077 this.type = type;
078 fullName = clazz.getName();
079 log = LoggerFactory.getLogger(getClass().getSimpleName() + "[" + fullName + "]");
080 packageName = GriffonClassUtils.getPackageName(clazz);
081 naturalName = GriffonNameUtils.getNaturalName(clazz.getName());
082 shortName = GriffonClassUtils.getShortClassName(clazz);
083 name = GriffonNameUtils.getLogicalName(clazz, trailingName);
084 propertyName = GriffonNameUtils.getPropertyNameRepresentation(shortName);
085 if (GriffonNameUtils.isBlank(name)) {
086 logicalPropertyName = propertyName;
087 } else {
088 logicalPropertyName = GriffonNameUtils.getPropertyNameRepresentation(name);
089 }
090 classPropertyFetcher = ClassPropertyFetcher.forClass(clazz);
091 }
092
093 public String getShortName() {
094 return shortName;
095 }
096
097 public Class<?> getClazz() {
098 return clazz;
099 }
100
101 public String getArtifactType() {
102 return type;
103 }
104
105 public GriffonApplication getApp() {
106 return app;
107 }
108
109 public Object newInstance() {
110 try {
111 Object instance = GriffonApplicationHelper.newInstance(app, clazz, type);
112 InvokerHelper.setProperty(instance, "app", app);
113 return instance;
114 } catch (Exception e) {
115 Throwable targetException = null;
116 if (e instanceof InvocationTargetException) {
117 targetException = ((InvocationTargetException) e).getTargetException();
118 } else {
119 targetException = e;
120 }
121 throw new NewInstanceCreationException("Could not create a new instance of class " + clazz.getName(), sanitize(targetException));
122 }
123 }
124
125 public String getName() {
126 return name;
127 }
128
129 public String getNaturalName() {
130 return naturalName;
131 }
132
133 public String getFullName() {
134 return fullName;
135 }
136
137 public String getPropertyName() {
138 return propertyName;
139 }
140
141 public String getLogicalPropertyName() {
142 return logicalPropertyName;
143 }
144
145 public String getPackageName() {
146 return packageName;
147 }
148
149 public Object getReferenceInstance() {
150 Object obj = classPropertyFetcher.getReference();
151 MetaClass myMetaClass = getMetaClass();
152 if (obj instanceof GroovyObject) {
153 MetaClass otherMetaClass = ((GroovyObject) obj).getMetaClass();
154 if (myMetaClass != otherMetaClass) {
155 if (log.isDebugEnabled()) {
156 log.debug("Setting MetaClass " + myMetaClass + " on GroovyObject " + obj);
157 }
158 ((GroovyObject) obj).setMetaClass(myMetaClass);
159 }
160 } else {
161 MetaClass otherMetaClass = GroovySystem.getMetaClassRegistry().getMetaClass(clazz);
162 if (myMetaClass != otherMetaClass) {
163 if (log.isDebugEnabled()) {
164 log.debug("Setting MetaClass " + myMetaClass + " on non-GroovyObject " + obj);
165 }
166 GroovySystem.getMetaClassRegistry().setMetaClass(clazz, myMetaClass);
167 }
168 }
169 return obj;
170 }
171
172 public PropertyDescriptor[] getPropertyDescriptors() {
173 return classPropertyFetcher.getPropertyDescriptors();
174 }
175
176 /**
177 * Returns an array of property names that are backed by a filed with a matching
178 * name.<p>
179 * Fields must be private and non-static. Names will be returned in the order
180 * they are declared in the class, starting from the deepest class in the
181 * class hierarchy up to the topmost superclass != null
182 */
183 public String[] getPropertiesWithFields() {
184 return classPropertyFetcher.getPropertiesWithFields();
185 }
186
187 public Class<?> getPropertyType(String name) {
188 return classPropertyFetcher.getPropertyType(name);
189 }
190
191 public boolean isReadableProperty(String name) {
192 return classPropertyFetcher.isReadableProperty(name);
193 }
194
195 public boolean hasMetaMethod(String name) {
196 return hasMetaMethod(name, null);
197 }
198
199 public boolean hasMetaMethod(String name, Object[] args) {
200 return (getMetaClass().getMetaMethod(name, args) != null);
201 }
202
203 public boolean hasMetaProperty(String name) {
204 return (getMetaClass().getMetaProperty(name) != null);
205 }
206
207 public MetaProperty[] getMetaProperties() {
208 List<MetaProperty> properties = new ArrayList<MetaProperty>();
209 for (MetaProperty property : getMetaClass().getProperties()) {
210 if (!"class".equals(property.getName()) && !"metaClass".equals(property.getName())) {
211 properties.add(property);
212 }
213 }
214
215 return properties.toArray(new MetaProperty[properties.size()]);
216 }
217
218 /**
219 * <p>Looks for a property of the reference instance with a given name and type.</p>
220 * <p>If found its value is returned. We follow the Java bean conventions with augmentation for groovy support
221 * and static fields/properties. We will therefore match, in this order:
222 * </p>
223 * <ol>
224 * <li>Public static field
225 * <li>Public static property with getter method
226 * <li>Standard public bean property (with getter or just public field, using normal introspection)
227 * </ol>
228 *
229 * @return property value or null if no property or static field was found
230 */
231 protected Object getPropertyOrStaticPropertyOrFieldValue(@SuppressWarnings("hiding") String name, Class<?> type) {
232 Object value = classPropertyFetcher.getPropertyValue(name);
233 return returnOnlyIfInstanceOf(value, type);
234 }
235
236 /**
237 * Get the value of the named static property.
238 *
239 * @param propName
240 * @param type
241 * @return The property value or null
242 */
243 public <T> T getStaticPropertyValue(String propName, Class<T> type) {
244 T value = classPropertyFetcher.getStaticPropertyValue(propName, type);
245 if (value == null) {
246 return getGroovyProperty(propName, type, true);
247 }
248 return value;
249 }
250
251 /**
252 * Get the value of the named property, with support for static properties in both Java and Groovy classes
253 * (which as of Groovy JSR 1.0 RC 01 only have getters in the metaClass)
254 *
255 * @param propName
256 * @param type
257 * @return The property value or null
258 */
259 public <T> T getPropertyValue(String propName, Class<T> type) {
260 T value = classPropertyFetcher.getPropertyValue(propName, type);
261 if (value == null) {
262 // Groovy workaround
263 return getGroovyProperty(propName, type, false);
264 }
265 return returnOnlyIfInstanceOf(value, type);
266 }
267
268 private <T> T getGroovyProperty(String propName, Class<T> type, boolean onlyStatic) {
269 Object value = null;
270 if (GroovyObject.class.isAssignableFrom(getClazz())) {
271 MetaProperty metaProperty = getMetaClass().getMetaProperty(propName);
272 if (metaProperty != null) {
273 int modifiers = metaProperty.getModifiers();
274 if (Modifier.isStatic(modifiers)) {
275 value = metaProperty.getProperty(clazz);
276 } else if (!onlyStatic) {
277 value = metaProperty.getProperty(getReferenceInstance());
278 }
279 }
280 }
281 return returnOnlyIfInstanceOf(value, type);
282 }
283
284 public Object getPropertyValueObject(String propertyNAme) {
285 return getPropertyValue(propertyNAme, Object.class);
286 }
287
288 @SuppressWarnings("unchecked")
289 private <T> T returnOnlyIfInstanceOf(Object value, Class<T> type) {
290 if ((value != null) && (type == Object.class || GriffonClassUtils.isGroovyAssignableFrom(type, value.getClass()))) {
291 return (T) value;
292 }
293
294 return null;
295 }
296
297 public Object getPropertyValue(String name) {
298 return getPropertyOrStaticPropertyOrFieldValue(name, Object.class);
299 }
300
301 /**
302 * Finds out if the property was defined with a Closure as value.<p>
303 */
304 public boolean isClosureMetaProperty(MetaProperty property) {
305 int modifiers = property.getModifiers();
306 if (modifiers != Modifier.PUBLIC) return false;
307
308 Object value = property.getProperty(getReferenceInstance());
309
310 if (value != null) return Closure.class.isAssignableFrom(value.getClass());
311
312 if (property instanceof MetaBeanProperty) {
313 // Instances of MetaBeanProperty store the closure in a descendant
314 // of ClosureInvokingMethod so we only need to check the type of
315 // the getter
316 MetaMethod getter = ((MetaBeanProperty) property).getGetter();
317 return getter instanceof ClosureInvokingMethod;
318 }
319
320 return false;
321 }
322
323 public boolean hasProperty(String propName) {
324 return classPropertyFetcher.isReadableProperty(propName);
325 }
326
327 /**
328 * @return the metaClass
329 */
330 public MetaClass getMetaClass() {
331 return GriffonApplicationHelper.expandoMetaClassFor(clazz);
332 }
333
334 public void setMetaClass(MetaClass metaClass) {
335 GroovySystem.getMetaClassRegistry().setMetaClass(clazz, metaClass);
336 }
337
338 public String toString() {
339 return "Artifact[" + type + "] > " + getName();
340 }
341
342 public void resetCaches() {
343 eventsCache.clear();
344 }
345
346 public boolean equals(Object obj) {
347 if (this == obj) return true;
348 if (obj == null) return false;
349 if (!obj.getClass().getName().equals(getClass().getName())) return false;
350
351 GriffonClass gc = (GriffonClass) obj;
352 return clazz.getName().equals(gc.getClazz().getName());
353 }
354
355 public int hashCode() {
356 return clazz.hashCode() + type.hashCode();
357 }
358
359 public void updateMetaClass(Closure updater) {
360 if (updater == null) return;
361 updater.setDelegate(getMetaClass());
362 updater.setResolveStrategy(Closure.DELEGATE_FIRST);
363 updater.run();
364 resetCaches();
365 }
366
367 // Any artifact can become an Event listener
368 public String[] getEventNames() {
369 if (eventsCache.isEmpty()) {
370 for (String propertyName : getPropertiesWithFields()) {
371 if (!eventsCache.contains(propertyName) &&
372 GriffonClassUtils.isEventHandler(propertyName) &&
373 getPropertyValue(propertyName, Closure.class) != null) {
374 eventsCache.add(propertyName.substring(2));
375 }
376 }
377 for (Method method : getClazz().getMethods()) {
378 String methodName = method.getName();
379 if (!eventsCache.contains(methodName) &&
380 GriffonClassUtils.isPlainMethod(method) &&
381 GriffonClassUtils.isEventHandler(methodName)) {
382 eventsCache.add(methodName.substring(2));
383 }
384 }
385 for (MetaProperty p : getMetaProperties()) {
386 String propertyName = p.getName();
387 if (GriffonClassUtils.isGetter(p, true)) {
388 propertyName = GriffonNameUtils.uncapitalize(propertyName.substring(3));
389 }
390 if (!eventsCache.contains(propertyName) &&
391 GriffonClassUtils.isEventHandler(propertyName) &&
392 isClosureMetaProperty(p)) {
393 eventsCache.add(propertyName.substring(2));
394 }
395 }
396 for (MetaMethod method : getMetaClass().getMethods()) {
397 String methodName = method.getName();
398 if (!eventsCache.contains(methodName) &&
399 GriffonClassUtils.isPlainMethod(method) &&
400 GriffonClassUtils.isEventHandler(methodName)) {
401 eventsCache.add(methodName.substring(2));
402 }
403 }
404 }
405
406 return eventsCache.toArray(new String[eventsCache.size()]);
407 }
408 }
|