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 == obj) return true;
177 if (obj == null) return false;
178 if (!obj.getClass().getName().equals(getClass().getName()))
179 return false;
180
181 GriffonClass gc = (GriffonClass) obj;
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 }
|