| 
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 == 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 }
 |