org.nakedobjects.metamodel.specloader.internal.introspector.JavaIntrospector Maven / Gradle / Ivy
package org.nakedobjects.metamodel.specloader.internal.introspector;
import java.beans.Introspector;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Logger;
import org.nakedobjects.applib.Identifier;
import org.nakedobjects.metamodel.commons.exceptions.NakedObjectException;
import org.nakedobjects.metamodel.commons.lang.JavaClassUtils;
import org.nakedobjects.metamodel.commons.lang.WrapperUtils;
import org.nakedobjects.metamodel.commons.names.NameConvertorUtils;
import org.nakedobjects.metamodel.commons.names.NameUtils;
import org.nakedobjects.metamodel.exceptions.ReflectionException;
import org.nakedobjects.metamodel.facets.FacetFactory;
import org.nakedobjects.metamodel.facets.MethodRemover;
import org.nakedobjects.metamodel.facets.MethodScope;
import org.nakedobjects.metamodel.facets.actcoll.typeof.TypeOfFacet;
import org.nakedobjects.metamodel.facets.object.facets.FacetsFacet;
import org.nakedobjects.metamodel.facets.ordering.OrderSet;
import org.nakedobjects.metamodel.facets.ordering.SimpleOrderSet;
import org.nakedobjects.metamodel.facets.ordering.actionorder.ActionOrderFacet;
import org.nakedobjects.metamodel.facets.ordering.fieldorder.FieldOrderFacet;
import org.nakedobjects.metamodel.facets.ordering.memberorder.DeweyOrderSet;
import org.nakedobjects.metamodel.spec.JavaSpecification;
import org.nakedobjects.metamodel.spec.NakedObjectSpecification;
import org.nakedobjects.metamodel.spec.feature.NakedObjectFeatureType;
import org.nakedobjects.metamodel.specloader.NakedObjectReflectorAbstract;
import org.nakedobjects.metamodel.specloader.SpecificationLoader;
import org.nakedobjects.metamodel.specloader.classsubstitutor.ClassSubstitutor;
import org.nakedobjects.metamodel.specloader.internal.facetprocessor.FacetProcessor;
import org.nakedobjects.metamodel.specloader.internal.peer.JavaNakedObjectActionParamPeer;
import org.nakedobjects.metamodel.specloader.internal.peer.JavaNakedObjectActionPeer;
import org.nakedobjects.metamodel.specloader.internal.peer.JavaOneToManyAssociationPeer;
import org.nakedobjects.metamodel.specloader.internal.peer.JavaOneToOneAssociationPeer;
import org.nakedobjects.metamodel.specloader.internal.peer.NakedObjectActionPeer;
import org.nakedobjects.metamodel.specloader.internal.peer.NakedObjectAssociationPeer;
import org.nakedobjects.metamodel.specloader.internal.peer.NakedObjectMemberPeer;
import org.nakedobjects.metamodel.specloader.traverser.SpecificationTraverser;
public class JavaIntrospector {
private final class JavaIntrospectorMethodRemover implements MethodRemover {
public void removeMethod(
final MethodScope methodScope,
final String methodName,
final Class returnType,
final Class[] parameterTypes) {
MethodFinderUtils.removeMethod(
methods, methodScope, methodName, returnType, parameterTypes);
}
public List removeMethods(
final MethodScope methodScope,
final String prefix,
final Class returnType,
final boolean canBeVoid,
final int paramCount) {
return MethodFinderUtils.removeMethods(methods, methodScope, prefix, returnType, canBeVoid, paramCount);
}
public void removeMethod(final Method method) {
for (int i = 0; i < methods.length; i++) {
if (methods[i] == null) {
continue;
}
if (methods[i].equals(method)) {
methods[i] = null;
}
}
}
public void removeMethods(final List methodList) {
final List methodList2 = methodList;
for (int i = 0; i < methods.length; i++) {
if (methods[i] == null) {
continue;
}
for (final Method method : methodList2) {
if (methods[i].equals(method)) {
methods[i] = null;
break;
}
}
}
}
}
private static final Logger LOG = Logger.getLogger(JavaIntrospector.class);
private static final Object[] NO_PARAMETERS = new Object[0];
private static final Class[] NO_PARAMETERS_TYPES = new Class[0];
private static final String GET_PREFIX = "get";
private static final String IS_PREFIX = "is";
private final String className;
private final Class type;
private final Method methods[];
private OrderSet orderedFields;
private OrderSet orderedObjectActions;
private OrderSet orderedClassActions;
private final NakedObjectReflectorAbstract reflector;
private final JavaSpecification javaSpecification;
// ////////////////////////////////////////////////////////////////////////////
// Constructor
// ////////////////////////////////////////////////////////////////////////////
public JavaIntrospector(final Class type,
final JavaSpecification javaSpecification,
final NakedObjectReflectorAbstract reflector) {
if (LOG.isDebugEnabled()) {
LOG.debug("creating JavaIntrospector for " + type);
}
// String prefix = "java.util.Arrays$";
@SuppressWarnings("unused")
final Class classComponentType = type.getComponentType();
this.type = type;
this.javaSpecification = javaSpecification;
this.reflector = reflector;
// previously we tested that the adapted class was public, as in
// !JavaClassUtils.isPublic(cls). However, there doesn't seem to be any
// good reason to have this restriction, while having it prevents us from
// using third party libraries for value types that have non-public
// interfaces or non-public concrete implementations. Therefore the test
// has been removed.
methods = type.getMethods();
className = type.getName();
}
// ////////////////////////////////////////////////////////////////////////////
// Introspection Control Parameters
// ////////////////////////////////////////////////////////////////////////////
private SpecificationTraverser getSpecificationTraverser() {
return reflector.getSpecificationTraverser();
}
private FacetProcessor getFacetProcessor() {
return reflector.getFacetProcessor();
}
private ClassSubstitutor getClassSubstitutor() {
return reflector.getClassSubstitutor();
}
// ////////////////////////////////////////////////////////////////////////////
// Class and stuff immediately derived from class
// ////////////////////////////////////////////////////////////////////////////
public Class getIntrospectedClass() {
return type;
}
/**
* As per {@link Class#getName()}.
*/
String className() {
return className;
}
public String getFullName() {
return type.getName();
}
public String[] getInterfaces() {
return JavaClassUtils.getInterfaces(type);
}
public String getSuperclass() {
return JavaClassUtils.getSuperclass(type);
}
public boolean isAbstract() {
return JavaClassUtils.isAbstract(type);
}
public boolean isFinal() {
return JavaClassUtils.isFinal(type);
}
public String shortName() {
final String name = type.getName();
return name.substring(name.lastIndexOf('.') + 1);
}
// ////////////////////////////////////////////////////////////////////////////
// introspect
// ////////////////////////////////////////////////////////////////////////////
public void introspectClass() {
LOG.info("introspecting " + className());
if (LOG.isDebugEnabled()) {
LOG.debug("introspecting " + className() + ": class-level details");
}
// process facets at object level
// this will also remove some methods, such as the superclass methods.
final JavaIntrospectorMethodRemover methodRemover = new JavaIntrospectorMethodRemover();
getFacetProcessor().process(type, methodRemover, javaSpecification);
// if this class has additional facets (as per @Facets), then process them.
final FacetsFacet facetsFacet = (FacetsFacet) javaSpecification.getFacet(FacetsFacet.class);
if (facetsFacet != null) {
final Class[] facetFactories = facetsFacet.facetFactories();
for (int i = 0; i < facetFactories.length; i++) {
FacetFactory facetFactory = null;
try {
facetFactory = (FacetFactory) facetFactories[i].newInstance();
} catch (final InstantiationException e) {
throw new NakedObjectException(e);
} catch (final IllegalAccessException e) {
throw new NakedObjectException(e);
}
getFacetProcessor().injectDependenciesInto(facetFactory);
facetFactory.process(type, methodRemover, javaSpecification);
}
}
}
public void introspectPropertiesAndCollections() {
LOG.debug("introspecting " + className() + ": properties and collections");
// find the properties and collections (fields) ...
final NakedObjectAssociationPeer[] findFieldMethods = findAndCreateFieldPeers();
// ... and the ordering of the properties and collections
String fieldOrder;
final FieldOrderFacet fieldOrderFacet = (FieldOrderFacet) javaSpecification.getFacet(FieldOrderFacet.class);
if (fieldOrderFacet == null) {
fieldOrder = null;
} else {
fieldOrder = fieldOrderFacet.value();
}
if (fieldOrder == null) {
// TODO: the calling of fieldOrder() should be a facet
fieldOrder = invokeSortOrderMethod("field");
}
orderedFields = createOrderSet(fieldOrder, findFieldMethods);
}
public void introspectActions() {
LOG.debug("introspecting " + className() + ": actions");
// find the actions ...
final NakedObjectActionPeer[] actionPeers = findActionMethodPeers(MethodScope.OBJECT);
// ... and the ordering of actions ...
String actionOrder;
final ActionOrderFacet actionOrderFacet = (ActionOrderFacet) javaSpecification.getFacet(ActionOrderFacet.class);
if (actionOrderFacet == null) {
actionOrder = null;
} else {
actionOrder = actionOrderFacet.value();
}
if (actionOrder == null) {
// TODO: the calling of actionOrder() should be a facet
actionOrder = invokeSortOrderMethod("action");
}
orderedObjectActions = createOrderSet(actionOrder, actionPeers);
// find the class actions ...
final NakedObjectActionPeer[] findClassActionMethods = findActionMethodPeers(MethodScope.CLASS);
// ... and the ordering of class actions
actionOrder = null;
if (actionOrder == null) {
// TODO: the calling of classActionOrder() should be a facet
actionOrder = invokeSortOrderMethod("classAction");
}
orderedClassActions = createOrderSet(actionOrder, findClassActionMethods);
}
// ////////////////////////////////////////////////////////////////////////////
// find Properties and Collections (fields)
// ////////////////////////////////////////////////////////////////////////////
private NakedObjectAssociationPeer[] findAndCreateFieldPeers() {
LOG.debug(" looking for fields for " + type);
// Ensure all return types are known
final Set propertyOrCollectionCandidates = getFacetProcessor().findPropertyOrCollectionCandidateAccessors(
methods, new HashSet());
List> typesToLoad = new ArrayList>();
for (final Method method : propertyOrCollectionCandidates) {
getSpecificationTraverser().traverseTypes(method, typesToLoad);
}
reflector.loadSpecifications(typesToLoad, type);
// now create FieldPeers for value properties, for collections and for reference properties
final List fieldPeers = new ArrayList();
findAndRemoveCollectionAccessorsAndCreateCorrespondingFieldPeers(fieldPeers);
findAndRemovePropertyAccessorsAndCreateCorrespondingFieldPeers(fieldPeers);
return toArray(fieldPeers);
}
private NakedObjectAssociationPeer[] toArray(final List fields) {
return (NakedObjectAssociationPeer[]) fields.toArray(new NakedObjectAssociationPeer[] {});
}
private void findAndRemoveCollectionAccessorsAndCreateCorrespondingFieldPeers(final List associationPeers) {
final List collectionAccessors = new ArrayList();
getFacetProcessor().findAndRemoveCollectionAccessors(new JavaIntrospectorMethodRemover(), collectionAccessors);
createCollectionPeersFromAccessors(collectionAccessors, associationPeers);
}
/**
* Since the value properties and collections have already been processed, this will pick up the remaining
* reference properties.
*/
private void findAndRemovePropertyAccessorsAndCreateCorrespondingFieldPeers(final List fields) {
final List propertyAccessors = new ArrayList();
getFacetProcessor().findAndRemovePropertyAccessors(new JavaIntrospectorMethodRemover(), propertyAccessors);
findAndRemovePrefixedNonVoidMethods(MethodScope.OBJECT, GET_PREFIX, Object.class, 0, propertyAccessors);
findAndRemovePrefixedNonVoidMethods(MethodScope.OBJECT, IS_PREFIX, Boolean.class, 0, propertyAccessors);
createPropertyPeersFromAccessors(propertyAccessors, fields);
}
private void createCollectionPeersFromAccessors(final List collectionAccessors, final List associationPeerListToAppendto) {
for (final Method getMethod : collectionAccessors) {
LOG.debug(" identified one-many association method " + getMethod);
final String capitalizedName = NameUtils.javaBaseName(getMethod.getName());
final String collectionNameName = Introspector.decapitalize(capitalizedName);
final Identifier identifier = Identifier.propertyOrCollectionIdentifier(className(), collectionNameName);
// create property and add facets
final JavaOneToManyAssociationPeer collection = new JavaOneToManyAssociationPeer(identifier, getSpecificationLoader());
getFacetProcessor().process(getMethod, new JavaIntrospectorMethodRemover(), collection,
NakedObjectFeatureType.COLLECTION);
// figure out what the type is
Class elementType = Object.class;
final TypeOfFacet typeOfFacet = collection.getFacet(TypeOfFacet.class);
if (typeOfFacet != null) {
elementType = typeOfFacet.value();
}
collection.setElementType(elementType);
// skip if class strategy says so.
if (getClassSubstitutor().getClass(elementType) == null) {
continue;
}
associationPeerListToAppendto.add(collection);
}
}
private void createPropertyPeersFromAccessors(final List propertyAccessors, final List associationPeerListToAppendto)
throws ReflectionException {
for (final Method accessorMethod : propertyAccessors) {
LOG.debug(" identified 1-1 association method " + accessorMethod);
final String capitalizedName = NameUtils.javaBaseName(accessorMethod.getName());
final String beanName = Introspector.decapitalize(capitalizedName);
final Class returnType = accessorMethod.getReturnType();
// skip if class strategy says so.
if (getClassSubstitutor().getClass(returnType) == null) {
continue;
}
if (LOG.isDebugEnabled()) {
LOG.debug("one-to-one association " + capitalizedName + " ->" + accessorMethod);
}
final Identifier identifier = Identifier.propertyOrCollectionIdentifier(className, beanName);
// create a 1:1 association peer
final JavaOneToOneAssociationPeer associationPeer = new JavaOneToOneAssociationPeer(identifier, returnType, getSpecificationLoader());
// process facets for the 1:1 association
getFacetProcessor().process(accessorMethod, new JavaIntrospectorMethodRemover(), associationPeer,
NakedObjectFeatureType.PROPERTY);
associationPeerListToAppendto.add(associationPeer);
}
}
// ////////////////////////////////////////////////////////////////////////////
// find Actions
// ////////////////////////////////////////////////////////////////////////////
private NakedObjectActionPeer[] findActionMethodPeers(final MethodScope methodScope) {
if(LOG.isDebugEnabled()) {
LOG.debug(" looking for action methods");
}
final List actionPeers = new ArrayList();
eachMethod:
for (int i = 0; i < methods.length; i++) {
if (methods[i] == null) {
continue;
}
final Method actionMethod = methods[i];
if (!MethodFinderUtils.inScope(methodScope, actionMethod)) {
continue;
}
List> typesToLoad = new ArrayList>();
getSpecificationTraverser().traverseTypes(actionMethod, typesToLoad);
boolean anyLoadedAsNull = reflector.loadSpecifications(typesToLoad);
if (anyLoadedAsNull) {
continue;
}
// build/validate action parameters
// as for return type, if the reflector's class strategy says to skip any of the
// action's parameter types, then just ignore this action altogether.
final Class[] parameterTypes = actionMethod.getParameterTypes();
// previously we wrapped primitives. However, this prevents the lookup of
// actions during remote authorization calls (using Identifier class).
// ... should we remove ... ?
// final Class[] parameterClasses = WrapperUtils.wrapAsNecessary(parameterTypes);
final Class[] parameterClasses = parameterTypes;
final int numParameters = parameterClasses.length;
final JavaNakedObjectActionParamPeer[] actionParams = new JavaNakedObjectActionParamPeer[numParameters];
for (int j = 0; j < numParameters; j++) {
NakedObjectSpecification paramSpec = getSpecificationLoader().loadSpecification(parameterClasses[j]);
if (paramSpec == null) {
continue eachMethod;
}
actionParams[j] = new JavaNakedObjectActionParamPeer(paramSpec);
}
if (getFacetProcessor().recognizes(actionMethod)) {
continue;
}
if (LOG.isDebugEnabled()) {
LOG.debug(" identified action " + actionMethod);
}
methods[i] = null;
// build action
final String fullMethodName = actionMethod.getName();
final Identifier identifier = Identifier.actionIdentifier(className, fullMethodName, parameterTypes);
final JavaNakedObjectActionPeer action = new JavaNakedObjectActionPeer(identifier, actionParams);
// process facets on the action & parameters
getFacetProcessor()
.process(actionMethod, new JavaIntrospectorMethodRemover(), action, NakedObjectFeatureType.ACTION);
for (int j = 0; j < actionParams.length; j++) {
getFacetProcessor().processParams(actionMethod, j, actionParams[j]);
}
actionPeers.add(action);
}
return convertToArray(actionPeers);
}
private NakedObjectActionPeer[] convertToArray(final List actions) {
return (NakedObjectActionPeer[]) actions.toArray(new NakedObjectActionPeer[] {});
}
// ////////////////////////////////////////////////////////////////////////////
// Helpers for finding and removing methods.
// ////////////////////////////////////////////////////////////////////////////
/**
* Searches for specific method and returns it, also removing it from the {@link #methods array of
* methods} if found.
*
* @see MethodFinderUtils#removeMethod(Method[], boolean, String, Class, Class[])
*/
private Method findAndRemoveMethod(
final MethodScope methodScope,
final String name,
final Class returnType,
final Class[] paramTypes) {
return MethodFinderUtils.removeMethod(methods, methodScope, name, returnType, paramTypes);
}
/**
* Searches for all methods matching the prefix and returns them, also removing it from the
* {@link #methods array of methods} if found.
*
*
* Void methods are not allowed.
*
* @see MethodFinderUtils#removeMethods(Method[], boolean, String, Class, boolean, int, ClassSubstitutor)
*/
private List findAndRemovePrefixedNonVoidMethods(
final MethodScope methodScope,
final String prefix,
final Class returnType,
final int paramCount) {
return findAndRemovePrefixedMethods(methodScope, prefix, returnType, false, paramCount);
}
/**
* As per {@link #findAndRemovePrefixedNonVoidMethods(boolean, String, Class, int)}, but appends to
* provided {@link List} (collecting parameter pattern).
*/
private void findAndRemovePrefixedNonVoidMethods(
final MethodScope methodScope,
final String prefix,
final Class returnType,
final int paramCount,
final List methodListToAppendTo) {
final List matchingMethods = findAndRemovePrefixedMethods(methodScope, prefix, returnType, false, paramCount);
methodListToAppendTo.addAll(matchingMethods);
}
/**
* Searches for all methods matching the prefix and returns them, also removing it from the
* {@link #methods array of methods} if found.
* @param objectFactory
*
* @see MethodFinderUtils#removeMethods(Method[], boolean, String, Class, boolean, int, ClassSubstitutor)
*/
private List findAndRemovePrefixedMethods(
final MethodScope methodScope,
final String prefix,
final Class returnType,
final boolean canBeVoid,
final int paramCount) {
return MethodFinderUtils.removeMethods(methods, methodScope, prefix, returnType, canBeVoid, paramCount);
}
// ////////////////////////////////////////////////////////////////////////////
// Sort Member Peers
// ////////////////////////////////////////////////////////////////////////////
private OrderSet createOrderSet(final String order, final NakedObjectMemberPeer[] members) {
if (order != null) {
OrderSet set;
set = SimpleOrderSet.createOrderSet(order, members);
return set;
} else {
final OrderSet set = DeweyOrderSet.createOrderSet(members);
return set;
}
}
private String invokeSortOrderMethod(final String type) {
final Method method = findAndRemoveMethod(MethodScope.CLASS, type + "Order", String.class, NO_PARAMETERS_TYPES);
if (method == null) {
return null;
} else if (!JavaClassUtils.isStatic(method)) {
LOG.warn("method " + className + "." + type + "Order() must be declared as static");
return null;
} else {
String s;
s = (String) invokeMethod(method, NO_PARAMETERS);
if (s.trim().length() == 0) {
return null;
}
return s;
}
}
private Object invokeMethod(final Method method, final Object[] parameters) {
try {
return method.invoke(null, parameters);
} catch (final IllegalAccessException ignore) {
LOG.warn("method " + className + "." + method.getName() + "() must be declared as public");
return null;
} catch (final InvocationTargetException e) {
throw new ReflectionException(e);
}
}
// ////////////////////////////////////////////////////////////////////////////
// Resultant Members
// ////////////////////////////////////////////////////////////////////////////
public OrderSet getFields() {
return orderedFields;
}
public OrderSet getClassActions() {
return orderedClassActions;
}
public OrderSet getObjectActions() {
return orderedObjectActions;
}
// ////////////////////////////////////////////////////////////////////////////
// finalize
// ////////////////////////////////////////////////////////////////////////////
@Override
protected void finalize() throws Throwable {
super.finalize();
LOG.debug("finalizing reflector " + this);
}
// ////////////////////////////////////////////////////////////////////////////
// (not used)
// ////////////////////////////////////////////////////////////////////////////
// REVIEW should we still support ordering of properties/actions via a string (this
// could allow user specified ordered rather than coder based
/**
* This was pulled out of JavaIntrospector, but doesn't seem to be used any more.
*
*
* Have therefore made private.
*
* @param original
* @param order
* @return
*/
private static NakedObjectAssociationPeer[] orderArray(final NakedObjectAssociationPeer[] original, final String[] order) {
if (order == null) {
return original;
} else {
for (int i = 0; i < order.length; i++) {
order[i] = NameConvertorUtils.simpleName(order[i]);
}
final NakedObjectAssociationPeer[] ordered = new NakedObjectAssociationPeer[original.length];
// work through each order element and find, if there is one, a
// matching member.
int orderedIndex = 0;
ordering: for (int orderIndex = 0; orderIndex < order.length; orderIndex++) {
for (int memberIndex = 0; memberIndex < original.length; memberIndex++) {
final NakedObjectAssociationPeer member = original[memberIndex];
if (member == null) {
continue;
}
if (member.getIdentifier().getMemberName().equalsIgnoreCase(order[orderIndex])) {
ordered[orderedIndex++] = original[memberIndex];
original[memberIndex] = null;
continue ordering;
}
}
if (!order[orderIndex].trim().equals("")) {
LOG.warn("invalid ordering element '" + order[orderIndex]);
}
}
final NakedObjectAssociationPeer[] results = new NakedObjectAssociationPeer[original.length];
int index = 0;
for (int i = 0; i < ordered.length; i++) {
final NakedObjectAssociationPeer member = ordered[i];
if (member != null) {
results[index++] = member;
}
}
for (int i = 0; i < original.length; i++) {
final NakedObjectAssociationPeer member = original[i];
if (member != null) {
results[index++] = member;
}
}
return results;
}
}
// ////////////////////////////////////////////////////////////////////////////
// Dependencies (from constructor)
// ////////////////////////////////////////////////////////////////////////////
private SpecificationLoader getSpecificationLoader() {
return reflector;
}
}
// Copyright (c) Naked Objects Group Ltd.