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