/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.isis.core.metamodel.facets.object.title.annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import org.apache.isis.applib.annotation.Title;
import org.apache.isis.core.commons.config.IsisConfiguration;
import org.apache.isis.core.metamodel.facetapi.FacetHolder;
import org.apache.isis.core.metamodel.facetapi.FacetUtil;
import org.apache.isis.core.metamodel.facetapi.FeatureType;
import org.apache.isis.core.metamodel.facetapi.MetaModelValidatorRefiner;
import org.apache.isis.core.metamodel.facets.Annotations;
import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
import org.apache.isis.core.metamodel.facets.MethodFinderUtils;
import org.apache.isis.core.metamodel.facets.fallback.FallbackFacetFactory;
import org.apache.isis.core.metamodel.methodutils.MethodScope;
import org.apache.isis.core.metamodel.services.ServicesInjector;
import org.apache.isis.core.metamodel.services.persistsession.PersistenceSessionServiceInternal;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorComposite;
import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorVisiting;
import org.apache.isis.core.metamodel.specloader.validator.ValidationFailures;
public class TitleAnnotationFacetFactory extends FacetFactoryAbstract implements MetaModelValidatorRefiner {
private static final String TITLE_METHOD_NAME = "title";
public TitleAnnotationFacetFactory() {
super(FeatureType.OBJECTS_ONLY);
}
/**
* If no method tagged with {@link Title} annotation then will use Facets
* provided by {@link FallbackFacetFactory} instead.
*/
@Override
public void process(final ProcessClassContext processClassContext) {
final Class<?> cls = processClassContext.getCls();
final FacetHolder facetHolder = processClassContext.getFacetHolder();
final List<Annotations.Evaluator<Title>> evaluators = Annotations.getEvaluators(cls, Title.class);
if (evaluators.isEmpty()) {
return;
}
sort(evaluators);
final List<TitleFacetViaTitleAnnotation.TitleComponent> titleComponents = Lists.transform(evaluators, TitleFacetViaTitleAnnotation.TitleComponent.FROM_EVALUATORS);
FacetUtil.addFacet(new TitleFacetViaTitleAnnotation(titleComponents, facetHolder, adapterManager));
}
public static void sort(final List<Annotations.Evaluator<Title>> evaluators) {
Collections.sort(evaluators, new Comparator<Annotations.Evaluator<Title>>() {
Comparator<String> comparator = new SequenceComparator();
@Override
public int compare(final Annotations.Evaluator<Title> o1, final Annotations.Evaluator<Title> o2) {
final Title a1 = o1.getAnnotation();
final Title a2 = o2.getAnnotation();
return comparator.compare(a1.sequence(), a2.sequence());
}
});
}
static class SequenceComparator implements Comparator<String> {
@Override
public int compare(final String sequence1, final String sequence2) {
final List<String> components1 = componentsFor(sequence1);
final List<String> components2 = componentsFor(sequence2);
final int size1 = components1.size();
final int size2 = components2.size();
if (size1 == 0 && size2 == 0) {
return 0;
}
// continue to loop until we run out of components.
int n = 0;
while (true) {
final int length = n + 1;
// check if run out of components in either side
if (size1 < length && size2 >= length) {
return -1; // o1 before o2
}
if (size2 < length && size1 >= length) {
return +1; // o2 before o1
}
if (size1 < length && size2 < length) {
// run out of components
return 0;
}
// we have this component on each side
int componentCompare = 0;
try {
final Integer c1 = Integer.valueOf(components1.get(n));
final Integer c2 = Integer.valueOf(components2.get(n));
componentCompare = c1.compareTo(c2);
} catch (final NumberFormatException nfe) {
// not integers compare as strings
componentCompare = components1.get(n).compareTo(components2.get(n));
}
if (componentCompare != 0) {
return componentCompare;
}
// this component is the same; lets look at the next
n++;
}
}
private static List<String> componentsFor(final String sequence) {
return Lists.newArrayList(Splitter.on('.').split(sequence));
}
}
/**
* Violation if there is a class that has both a <tt>title()</tt> method and also any non-inherited method
* annotated with <tt>@Title</tt>.
*
* <p>
* If there are only inherited methods annotated with <tt>@Title</tt> then this is <i>not</i> a violation; but
* (from the implementation of {@link org.apache.isis.core.metamodel.facets.object.title.methods.TitleFacetViaMethodsFactory} the imperative <tt>title()</tt> method will take
* precedence.
*/
@Override
public void refineMetaModelValidator(MetaModelValidatorComposite metaModelValidator, IsisConfiguration configuration) {
metaModelValidator.add(new MetaModelValidatorVisiting(new MetaModelValidatorVisiting.Visitor() {
@Override
public boolean visit(ObjectSpecification objectSpec, ValidationFailures validationFailures) {
final Class<?> cls = objectSpec.getCorrespondingClass();
final Method titleMethod = MethodFinderUtils.findMethod(cls, MethodScope.OBJECT, TITLE_METHOD_NAME, String.class, null);
if (titleMethod == null) {
return true;
}
// determine if cls contains an @Title annotated method, not inherited from superclass
final Class<?> supClass = cls.getSuperclass();
if (supClass == null) {
return true;
}
final List<Method> methods = methodsWithTitleAnnotation(cls);
final List<Method> superClassMethods = methodsWithTitleAnnotation(supClass);
if (methods.size() > superClassMethods.size()) {
validationFailures.add(
"%s: conflict for determining a strategy for retrieval of title for class, contains a method '%s' and an annotation '@%s'",
objectSpec.getIdentifier().getClassName(),
TITLE_METHOD_NAME,
Title.class.getName());
}
return true;
}
private List<Method> methodsWithTitleAnnotation(final Class<?> cls) {
return MethodFinderUtils.findMethodsWithAnnotation(cls, MethodScope.OBJECT, Title.class);
}
}));
}
@Override
public void setServicesInjector(final ServicesInjector servicesInjector) {
super.setServicesInjector(servicesInjector);
adapterManager = servicesInjector.getPersistenceSessionServiceInternal();
}
PersistenceSessionServiceInternal adapterManager;
}