/*
* Copyright 2008-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.griffon.runtime.core.artifact;
import griffon.core.GriffonApplication;
import griffon.core.artifact.GriffonClass;
import griffon.util.GriffonClassUtils;
import griffon.util.GriffonNameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.TreeSet;
import static griffon.util.GriffonClassUtils.isEventHandler;
import static griffon.util.GriffonClassUtils.isPlainMethod;
import static griffon.util.GriffonNameUtils.getPropertyNameRepresentation;
import static griffon.util.GriffonNameUtils.isBlank;
import static griffon.util.GriffonNameUtils.requireNonBlank;
import static java.util.Objects.requireNonNull;
import static org.codehaus.griffon.runtime.core.artifact.ClassPropertyFetcher.forClass;
/**
* Abstract base class for Griffon types that provides common functionality for
* evaluating conventions within classes
*
* @author Steven Devijver (Grails 0.1)
* @author Graeme Rocher (Grails 0.1)
* @author Andres Almiray
* @since 2.0.0
*/
public abstract class AbstractGriffonClass implements GriffonClass {
private static final String ERROR_NAME_BLANK = "Argument 'name' must not be blank";
private static final String ERROR_ARTIFACT_TYPE_BLANK = "Argument 'artifactType' must not be blank";
private static final String ERROR_TYPE_NULL = "Argument 'type' must not be null";
private static final String ERROR_APPLICATION_NULL = "Argument 'application' must not be null";
private final GriffonApplication application;
private final Class<?> clazz;
private final String artifactType;
private final String fullName;
private final String name;
private final String packageName;
private final String naturalName;
private final String shortName;
private final String propertyName;
private final String logicalPropertyName;
private final ClassPropertyFetcher classPropertyFetcher;
protected final Set<String> eventsCache = new TreeSet<>();
protected final Logger log;
public AbstractGriffonClass(@Nonnull GriffonApplication application, @Nonnull Class<?> type, @Nonnull String artifactType, @Nonnull String trailingName) {
this.application = requireNonNull(application, ERROR_APPLICATION_NULL);
this.clazz = requireNonNull(type, ERROR_TYPE_NULL);
this.artifactType = requireNonBlank(artifactType, ERROR_ARTIFACT_TYPE_BLANK).trim();
trailingName = isBlank(trailingName) ? "" : trailingName.trim();
fullName = type.getName();
log = LoggerFactory.getLogger(getClass().getSimpleName() + "[" + fullName + "]");
packageName = GriffonClassUtils.getPackageName(type);
naturalName = GriffonNameUtils.getNaturalName(type.getName());
shortName = GriffonClassUtils.getShortClassName(type);
name = GriffonNameUtils.getLogicalName(type, trailingName);
propertyName = getPropertyNameRepresentation(shortName);
if (isBlank(name)) {
logicalPropertyName = propertyName;
} else {
logicalPropertyName = getPropertyNameRepresentation(name);
}
classPropertyFetcher = forClass(type);
}
@Nonnull
public GriffonApplication getApplication() {
return application;
}
@Nullable
@Override
public Object getPropertyValue(@Nonnull String name) {
requireNonBlank(name, ERROR_NAME_BLANK);
return getPropertyOrStaticPropertyOrFieldValue(name, Object.class);
}
@Override
public boolean hasProperty(@Nonnull String name) {
requireNonBlank(name, ERROR_NAME_BLANK);
return classPropertyFetcher.isReadableProperty(name);
}
@Nonnull
@Override
public String getName() {
return name;
}
@Nonnull
@Override
public String getShortName() {
return shortName;
}
@Nonnull
@Override
public String getFullName() {
return fullName;
}
@Nonnull
@Override
public String getPropertyName() {
return propertyName;
}
@Nonnull
@Override
public String getLogicalPropertyName() {
return logicalPropertyName;
}
@Nonnull
@Override
public String getNaturalName() {
return naturalName;
}
@Nonnull
@Override
public String getPackageName() {
return packageName;
}
@Nonnull
@Override
public Class<?> getClazz() {
return clazz;
}
@Nonnull
@Override
public String getArtifactType() {
return artifactType;
}
@Nullable
@Override
public <T> T getPropertyValue(@Nonnull String name, @Nonnull Class<T> type) {
requireNonBlank(name, ERROR_NAME_BLANK);
requireNonNull(type, ERROR_TYPE_NULL);
return null;
}
public String toString() {
return "Artifact[" + artifactType + "] > " + getName();
}
public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!obj.getClass().getName().equals(getClass().getName()))
return false;
GriffonClass gc = (GriffonClass) obj;
return clazz.getName().equals(gc.getClazz().getName());
}
public int hashCode() {
return clazz.hashCode() + artifactType.hashCode();
}
public void resetCaches() {
eventsCache.clear();
}
@Nonnull
public String[] getEventNames() {
if (eventsCache.isEmpty()) {
for (Method method : getClazz().getMethods()) {
String methodName = method.getName();
if (!eventsCache.contains(methodName) &&
isPlainMethod(method) &&
isEventHandler(methodName)) {
eventsCache.add(methodName.substring(2));
}
}
}
return eventsCache.toArray(new String[eventsCache.size()]);
}
/**
* Returns an array of property names that are backed by a filed with a matching
* name.<p>
* Fields must be private and non-static. Names will be returned in the order
* they are declared in the class, starting from the deepest class in the
* class hierarchy up to the topmost superclass != null
*/
public String[] getPropertiesWithFields() {
return classPropertyFetcher.getPropertiesWithFields();
}
public Class<?> getPropertyType(String name) {
return classPropertyFetcher.getPropertyType(name);
}
public boolean isReadableProperty(String name) {
return classPropertyFetcher.isReadableProperty(name);
}
/**
* <p>Looks for a property of the reference instance with a given name and type.</p>
* <p>If found its value is returned. We follow the Java bean conventions with augmentation for groovy support
* and static fields/properties. We will therefore match, in this order:
* </p>
* <ol>
* <li>Public static field
* <li>Public static property with getter method
* <li>Standard public bean property (with getter or just public field, using normal introspection)
* </ol>
*
* @return property value or null if no property or static field was found
*/
protected Object getPropertyOrStaticPropertyOrFieldValue(@SuppressWarnings("hiding") @Nonnull String name, @Nonnull Class<?> type) {
requireNonBlank(name, ERROR_NAME_BLANK);
requireNonNull(type, ERROR_TYPE_NULL);
Object value = classPropertyFetcher.getPropertyValue(name);
return classPropertyFetcher.returnOnlyIfInstanceOf(value, type);
}
}