AbstractGriffonClass.java
001 /*
002  * Copyright 2008-2015 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.artifact;
017 
018 import griffon.core.GriffonApplication;
019 import griffon.core.artifact.GriffonClass;
020 import griffon.util.GriffonClassUtils;
021 import griffon.util.GriffonNameUtils;
022 import org.slf4j.Logger;
023 import org.slf4j.LoggerFactory;
024 
025 import javax.annotation.Nonnull;
026 import javax.annotation.Nullable;
027 import java.lang.reflect.Method;
028 import java.util.Set;
029 import java.util.TreeSet;
030 
031 import static griffon.util.GriffonClassUtils.isEventHandler;
032 import static griffon.util.GriffonClassUtils.isPlainMethod;
033 import static griffon.util.GriffonNameUtils.getPropertyNameRepresentation;
034 import static griffon.util.GriffonNameUtils.isBlank;
035 import static griffon.util.GriffonNameUtils.requireNonBlank;
036 import static java.util.Objects.requireNonNull;
037 import static org.codehaus.griffon.runtime.core.artifact.ClassPropertyFetcher.forClass;
038 
039 /**
040  * Abstract base class for Griffon types that provides common functionality for
041  * evaluating conventions within classes
042  *
043  @author Steven Devijver (Grails 0.1)
044  @author Graeme Rocher (Grails 0.1)
045  @author Andres Almiray
046  @since 2.0.0
047  */
048 public abstract class AbstractGriffonClass implements GriffonClass {
049     private static final String ERROR_NAME_BLANK = "Argument 'name' must not be blank";
050     private static final String ERROR_ARTIFACT_TYPE_BLANK = "Argument 'artifactType' must not be blank";
051     private static final String ERROR_TYPE_NULL = "Argument 'type' must not be null";
052     private static final String ERROR_APPLICATION_NULL = "Argument 'application' must not be null";
053 
054     private final GriffonApplication application;
055     private final Class<?> clazz;
056     private final String artifactType;
057     private final String fullName;
058     private final String name;
059     private final String packageName;
060     private final String naturalName;
061     private final String shortName;
062     private final String propertyName;
063     private final String logicalPropertyName;
064     private final ClassPropertyFetcher classPropertyFetcher;
065 
066     protected final Set<String> eventsCache = new TreeSet<>();
067     protected final Logger log;
068 
069     public AbstractGriffonClass(@Nonnull GriffonApplication application, @Nonnull Class<?> type, @Nonnull String artifactType, @Nonnull String trailingName) {
070         this.application = requireNonNull(application, ERROR_APPLICATION_NULL);
071         this.clazz = requireNonNull(type, ERROR_TYPE_NULL);
072         this.artifactType = requireNonBlank(artifactType, ERROR_ARTIFACT_TYPE_BLANK).trim();
073         trailingName = isBlank(trailingName"" : trailingName.trim();
074         fullName = type.getName();
075         log = LoggerFactory.getLogger(getClass().getSimpleName() "[" + fullName + "]");
076         packageName = GriffonClassUtils.getPackageName(type);
077         naturalName = GriffonNameUtils.getNaturalName(type.getName());
078         shortName = GriffonClassUtils.getShortClassName(type);
079         name = GriffonNameUtils.getLogicalName(type, trailingName);
080         propertyName = getPropertyNameRepresentation(shortName);
081         if (isBlank(name)) {
082             logicalPropertyName = propertyName;
083         else {
084             logicalPropertyName = getPropertyNameRepresentation(name);
085         }
086         classPropertyFetcher = forClass(type);
087     }
088 
089     @Nonnull
090     public GriffonApplication getApplication() {
091         return application;
092     }
093 
094     @Nullable
095     @Override
096     public Object getPropertyValue(@Nonnull String name) {
097         requireNonBlank(name, ERROR_NAME_BLANK);
098         return getPropertyOrStaticPropertyOrFieldValue(name, Object.class);
099     }
100 
101     @Override
102     public boolean hasProperty(@Nonnull String name) {
103         requireNonBlank(name, ERROR_NAME_BLANK);
104         return classPropertyFetcher.isReadableProperty(name);
105     }
106 
107     @Nonnull
108     @Override
109     public String getName() {
110         return name;
111     }
112 
113     @Nonnull
114     @Override
115     public String getShortName() {
116         return shortName;
117     }
118 
119     @Nonnull
120     @Override
121     public String getFullName() {
122         return fullName;
123     }
124 
125     @Nonnull
126     @Override
127     public String getPropertyName() {
128         return propertyName;
129     }
130 
131     @Nonnull
132     @Override
133     public String getLogicalPropertyName() {
134         return logicalPropertyName;
135     }
136 
137     @Nonnull
138     @Override
139     public String getNaturalName() {
140         return naturalName;
141     }
142 
143     @Nonnull
144     @Override
145     public String getPackageName() {
146         return packageName;
147     }
148 
149     @Nonnull
150     @Override
151     public Class<?> getClazz() {
152         return clazz;
153     }
154 
155     @Nonnull
156     @Override
157     public String getArtifactType() {
158         return artifactType;
159     }
160 
161     @Nullable
162     @Override
163     public <T> T getPropertyValue(@Nonnull String name, @Nonnull Class<T> type) {
164         requireNonBlank(name, ERROR_NAME_BLANK);
165         requireNonNull(type, ERROR_TYPE_NULL);
166         return null;
167     }
168 
169     public String toString() {
170         return "Artifact[" + artifactType + "] > " + getName();
171     }
172 
173     public boolean equals(@Nullable Object obj) {
174         if (this == objreturn true;
175         if (obj == nullreturn false;
176         if (!obj.getClass().getName().equals(getClass().getName()))
177             return false;
178 
179         GriffonClass gc = (GriffonClassobj;
180         return clazz.getName().equals(gc.getClazz().getName());
181     }
182 
183     public int hashCode() {
184         return clazz.hashCode() + artifactType.hashCode();
185     }
186 
187     public void resetCaches() {
188         eventsCache.clear();
189     }
190 
191     @Nonnull
192     public String[] getEventNames() {
193         if (eventsCache.isEmpty()) {
194             for (Method method : getClazz().getMethods()) {
195                 String methodName = method.getName();
196                 if (!eventsCache.contains(methodName&&
197                     isPlainMethod(method&&
198                     isEventHandler(methodName)) {
199                     eventsCache.add(methodName.substring(2));
200                 }
201             }
202         }
203 
204         return eventsCache.toArray(new String[eventsCache.size()]);
205     }
206 
207     /**
208      * Returns an array of property names that are backed by a filed with a matching
209      * name.<p>
210      * Fields must be private and non-static. Names will be returned in the order
211      * they are declared in the class, starting from the deepest class in the
212      * class hierarchy up to the topmost superclass != null
213      */
214     public String[] getPropertiesWithFields() {
215         return classPropertyFetcher.getPropertiesWithFields();
216     }
217 
218     public Class<?> getPropertyType(String name) {
219         return classPropertyFetcher.getPropertyType(name);
220     }
221 
222     public boolean isReadableProperty(String name) {
223         return classPropertyFetcher.isReadableProperty(name);
224     }
225 
226     /**
227      <p>Looks for a property of the reference instance with a given name and type.</p>
228      <p>If found its value is returned. We follow the Java bean conventions with augmentation for groovy support
229      * and static fields/properties. We will therefore match, in this order:
230      </p>
231      <ol>
232      <li>Public static field
233      <li>Public static property with getter method
234      <li>Standard public bean property (with getter or just public field, using normal introspection)
235      </ol>
236      *
237      @return property value or null if no property or static field was found
238      */
239     protected Object getPropertyOrStaticPropertyOrFieldValue(@SuppressWarnings("hiding"@Nonnull String name, @Nonnull Class<?> type) {
240         requireNonBlank(name, ERROR_NAME_BLANK);
241         requireNonNull(type, ERROR_TYPE_NULL);
242         Object value = classPropertyFetcher.getPropertyValue(name);
243         return classPropertyFetcher.returnOnlyIfInstanceOf(value, type);
244     }
245 }