AbstractGriffonClass.java
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 (instanceof InvocationTargetException) {
117                 targetException = ((InvocationTargetExceptione).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 = ((GroovyObjectobj).getMetaClass();
154             if (myMetaClass != otherMetaClass) {
155                 if (log.isDebugEnabled()) {
156                     log.debug("Setting MetaClass " + myMetaClass + " on GroovyObject " + obj);
157                 }
158                 ((GroovyObjectobj).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 (Tvalue;
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.PUBLICreturn false;
307 
308         Object value = property.getProperty(getReferenceInstance());
309 
310         if (value != nullreturn 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 = ((MetaBeanPropertyproperty).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 == objreturn true;
348         if (obj == nullreturn false;
349         if (!obj.getClass().getName().equals(getClass().getName())) return false;
350 
351         GriffonClass gc = (GriffonClassobj;
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 == nullreturn;
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 }