`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 (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 }`