001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020package org.apache.isis.core.progmodel.facets.object.title.annotation; 021 022import java.lang.reflect.Method; 023import java.util.Collections; 024import java.util.Comparator; 025import java.util.List; 026 027import com.google.common.base.Splitter; 028import com.google.common.collect.Lists; 029 030import org.apache.isis.applib.annotation.Title; 031import org.apache.isis.core.commons.config.IsisConfiguration; 032import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager; 033import org.apache.isis.core.metamodel.adapter.mgr.AdapterManagerAware; 034import org.apache.isis.core.metamodel.facetapi.FacetHolder; 035import org.apache.isis.core.metamodel.facetapi.FacetUtil; 036import org.apache.isis.core.metamodel.facetapi.FeatureType; 037import org.apache.isis.core.metamodel.facetapi.MetaModelValidatorRefiner; 038import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract; 039import org.apache.isis.core.metamodel.methodutils.MethodScope; 040import org.apache.isis.core.metamodel.spec.ObjectSpecification; 041import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorComposite; 042import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorVisiting; 043import org.apache.isis.core.metamodel.specloader.validator.ValidationFailures; 044import org.apache.isis.core.progmodel.facets.MethodFinderUtils; 045import org.apache.isis.core.progmodel.facets.fallback.FallbackFacetFactory; 046import org.apache.isis.core.progmodel.facets.object.title.TitleMethodFacetFactory; 047import org.apache.isis.core.progmodel.facets.object.title.annotation.TitleFacetViaTitleAnnotation.TitleComponent; 048 049public class TitleAnnotationFacetFactory extends FacetFactoryAbstract implements AdapterManagerAware, MetaModelValidatorRefiner { 050 051 private static final String TITLE_METHOD_NAME = "title"; 052 053 private AdapterManager adapterManager; 054 055 public TitleAnnotationFacetFactory() { 056 super(FeatureType.OBJECTS_ONLY); 057 } 058 059 /** 060 * If no method tagged with {@link Title} annotation then will use Facets 061 * provided by {@link FallbackFacetFactory} instead. 062 */ 063 @Override 064 public void process(final ProcessClassContext processClassContext) { 065 final Class<?> cls = processClassContext.getCls(); 066 final FacetHolder facetHolder = processClassContext.getFacetHolder(); 067 068 final List<Method> methods = MethodFinderUtils.findMethodsWithAnnotation(cls, MethodScope.OBJECT, Title.class); 069 070 Collections.sort(methods, new Comparator<Method>() { 071 Comparator<String> comparator = new SequenceComparator(); 072 073 @Override 074 public int compare(final Method o1, final Method o2) { 075 final Title a1 = o1.getAnnotation(Title.class); 076 final Title a2 = o2.getAnnotation(Title.class); 077 return comparator.compare(a1.sequence(), a2.sequence()); 078 } 079 }); 080 if (methods.isEmpty()) { 081 return; 082 } 083 final List<TitleComponent> titleComponents = Lists.transform(methods, TitleComponent.FROM_METHOD); 084 FacetUtil.addFacet(new TitleFacetViaTitleAnnotation(titleComponents, facetHolder, adapterManager)); 085 } 086 087 088 089 090 static class SequenceComparator implements Comparator<String> { 091 092 @Override 093 public int compare(final String sequence1, final String sequence2) { 094 095 final List<String> components1 = componentsFor(sequence1); 096 final List<String> components2 = componentsFor(sequence2); 097 098 final int size1 = components1.size(); 099 final int size2 = components2.size(); 100 101 if (size1 == 0 && size2 == 0) { 102 return 0; 103 } 104 105 // continue to loop until we run out of components. 106 int n = 0; 107 while (true) { 108 final int length = n + 1; 109 // check if run out of components in either side 110 if (size1 < length && size2 >= length) { 111 return -1; // o1 before o2 112 } 113 if (size2 < length && size1 >= length) { 114 return +1; // o2 before o1 115 } 116 if (size1 < length && size2 < length) { 117 // run out of components 118 return 0; 119 } 120 // we have this component on each side 121 int componentCompare = 0; 122 try { 123 final Integer c1 = Integer.valueOf(components1.get(n)); 124 final Integer c2 = Integer.valueOf(components2.get(n)); 125 componentCompare = c1.compareTo(c2); 126 } catch (final NumberFormatException nfe) { 127 // not integers compare as strings 128 componentCompare = components1.get(n).compareTo(components2.get(n)); 129 } 130 131 if (componentCompare != 0) { 132 return componentCompare; 133 } 134 // this component is the same; lets look at the next 135 n++; 136 } 137 } 138 139 private static List<String> componentsFor(final String sequence) { 140 return Lists.newArrayList(Splitter.on('.').split(sequence)); 141 } 142 } 143 144 145 @Override 146 public void setAdapterManager(final AdapterManager adapterMap) { 147 this.adapterManager = adapterMap; 148 } 149 150 /** 151 * Violation if there is a class that has both a <tt>title()</tt> method and also any non-inherited method 152 * annotated with <tt>@Title</tt>. 153 * 154 * <p> 155 * If there are only inherited methods annotated with <tt>@Title</tt> then this is <i>not</i> a violation; but 156 * (from the implementation of {@link TitleMethodFacetFactory} the imperative <tt>title()</tt> method will take 157 * precedence. 158 */ 159 @Override 160 public void refineMetaModelValidator(MetaModelValidatorComposite metaModelValidator, IsisConfiguration configuration) { 161 metaModelValidator.add(new MetaModelValidatorVisiting(new MetaModelValidatorVisiting.Visitor() { 162 163 @Override 164 public boolean visit(ObjectSpecification objectSpec, ValidationFailures validationFailures) { 165 final Class<?> cls = objectSpec.getCorrespondingClass(); 166 167 final Method titleMethod = MethodFinderUtils.findMethod(cls, MethodScope.OBJECT, TITLE_METHOD_NAME, String.class, null); 168 if (titleMethod == null) { 169 return true; 170 } 171 172 // determine if cls contains an @Title annotated method, not inherited from superclass 173 final Class<?> supClass = cls.getSuperclass(); 174 if (supClass == null) { 175 return true; 176 } 177 178 final List<Method> methods = methodsWithTitleAnnotation(cls); 179 final List<Method> superClassMethods = methodsWithTitleAnnotation(supClass); 180 if (methods.size() > superClassMethods.size()) { 181 validationFailures.add( 182 "Conflict for determining a strategy for retrieval of title for class %s " 183 + "that contains a method '%s' and an annotation '@%s'", 184 objectSpec.getIdentifier().getClassName(), TITLE_METHOD_NAME, Title.class.getName()); 185 } 186 187 return true; 188 } 189 190 private List<Method> methodsWithTitleAnnotation(final Class<?> cls) { 191 return MethodFinderUtils.findMethodsWithAnnotation(cls, MethodScope.OBJECT, Title.class); 192 } 193 194 })); 195 } 196}