/** * Copyright 2013 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 io.neba.core.resourcemodels.metadata; import io.neba.api.annotations.Unmapped; import io.neba.core.util.Annotations; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.ReflectionUtils; import javax.annotation.Resource; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import static io.neba.core.util.Annotations.annotations; import static org.springframework.util.ReflectionUtils.doWithFields; import static org.springframework.util.ReflectionUtils.doWithMethods; /** * Represents meta-data of a {@link io.neba.api.annotations.ResourceModel}. Used * to retain the result of costly reflection on resource models. Instances are model-scoped, i.e. there is exactly one * {@link ResourceModelMetaData} instance per detected {@link io.neba.api.annotations.ResourceModel}. * * @see ResourceModelMetaDataRegistrar * * @author Olaf Otto */ public class ResourceModelMetaData { /** * @author Olaf Otto */ private static class MethodMetadataCreator implements ReflectionUtils.MethodCallback { private final Collection<MethodMetaData> postMappingMethods = new ArrayList<>(32); private final Collection<MethodMetaData> preMappingMethods = new ArrayList<>(32); @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { MethodMetaData methodMetaData = new MethodMetaData(method); if (methodMetaData.isPostMappingCallback()) { this.postMappingMethods.add(methodMetaData); } if (methodMetaData.isPreMappingCallback()) { this.preMappingMethods.add(methodMetaData); } } private MethodMetaData[] getPostMappingMethods() { return this.postMappingMethods.toArray(new MethodMetaData[this.postMappingMethods.size()]); } private MethodMetaData[] getPreMappingMethods() { return this.preMappingMethods.toArray(new MethodMetaData[this.preMappingMethods.size()]); } } /** * @author Olaf Otto */ private static class FieldMetadataCreator implements ReflectionUtils.FieldCallback { private final Collection<MappedFieldMetaData> mappableFields = new ArrayList<>(); private final Class<?> modelType; FieldMetadataCreator(Class<?> modelType) { if (modelType == null) { throw new IllegalArgumentException("Constructor parameter modelType must not be null."); } this.modelType = modelType; } @Override public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { if (isMappingCandidate(field)) { MappedFieldMetaData fieldMetaData = new MappedFieldMetaData(field, this.modelType); this.mappableFields.add(fieldMetaData); } } private MappedFieldMetaData[] getMappableFields() { return this.mappableFields.toArray(new MappedFieldMetaData[this.mappableFields.size()]); } /** * Determines whether a given field's value * can be injected from either the current resource properties * or another (e.g. referenced) resource. */ private boolean isMappingCandidate(Field field) { return !isStatic(field) && !isFinal(field) && !isUnmapped(field); } private boolean isFinal(Field field) { return Modifier.isFinal(field.getModifiers()); } private boolean isStatic(Field field) { return Modifier.isStatic(field.getModifiers()); } /** * @param field must not be <code>null</code>. * @return whether the field is explicitly excluded from OCM, e.g. via @{@link Unmapped}. */ private boolean isUnmapped(Field field) { final Annotations annotations = annotations(field); return annotations.contains(Unmapped.class) || annotations.containsName("javax.inject.Inject") || // @Inject is an optional dependency, thus using a name constant annotations.containsName(Autowired.class.getName()) || annotations.containsName(Resource.class.getName()); } } private final MappedFieldMetaData[] mappableFields; private final MethodMetaData[] postMappingMethods; private final MethodMetaData[] preMappingMethods; private final String typeName; private final ResourceModelStatistics statistics = new ResourceModelStatistics(); public ResourceModelMetaData(Class<?> modelType) { FieldMetadataCreator fc = new FieldMetadataCreator(modelType); doWithFields(modelType, fc); MethodMetadataCreator mc = new MethodMetadataCreator(); doWithMethods(modelType, mc); this.mappableFields = fc.getMappableFields(); this.preMappingMethods = mc.getPreMappingMethods(); this.postMappingMethods = mc.getPostMappingMethods(); this.typeName = modelType.getName(); } public MappedFieldMetaData[] getMappableFields() { return mappableFields; } public MethodMetaData[] getPostMappingMethods() { return postMappingMethods; } public MethodMetaData[] getPreMappingMethods() { return preMappingMethods; } public String getTypeName() { return typeName; } public ResourceModelStatistics getStatistics() { return statistics; } @Override public String toString() { return getClass().getSimpleName() + "[" + getTypeName() + ']'; } }