001 /*
002 * Copyright 2008-2014 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 == obj) return true;
175 if (obj == null) return false;
176 if (!obj.getClass().getName().equals(getClass().getName()))
177 return false;
178
179 GriffonClass gc = (GriffonClass) obj;
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 }
|