/*
* Copyright 2012-2016 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.springframework.hateoas.core;
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
import static org.springframework.core.annotation.AnnotationUtils.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
import org.springframework.util.Assert;
/**
* {@link MappingDiscoverer} implementation that inspects mappings from a particular annotation.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
public class AnnotationMappingDiscoverer implements MappingDiscoverer {
private static final Pattern MULTIPLE_SLASHES = Pattern.compile("\\/{2,}");
private final Class<? extends Annotation> annotationType;
private final String mappingAttributeName;
/**
* Creates an {@link AnnotationMappingDiscoverer} for the given annotation type. Will lookup the {@code value}
* attribute by default.
*
* @param annotation must not be {@literal null}.
*/
public AnnotationMappingDiscoverer(Class<? extends Annotation> annotation) {
this(annotation, null);
}
/**
* Creates an {@link AnnotationMappingDiscoverer} for the given annotation type and attribute name.
*
* @param annotation must not be {@literal null}.
* @param mappingAttributeName if {@literal null}, it defaults to {@code value}.
*/
public AnnotationMappingDiscoverer(Class<? extends Annotation> annotation, String mappingAttributeName) {
Assert.notNull(annotation, "Annotation must not be null!");
this.annotationType = annotation;
this.mappingAttributeName = mappingAttributeName;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class)
*/
@Override
public String getMapping(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
String[] mapping = getMappingFrom(findMergedAnnotation(type, annotationType));
return mapping.length == 0 ? null : mapping[0];
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.reflect.Method)
*/
@Override
public String getMapping(Method method) {
Assert.notNull(method, "Method must not be null!");
return getMapping(method.getDeclaringClass(), method);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class, java.lang.reflect.Method)
*/
@Override
public String getMapping(Class<?> type, Method method) {
Assert.notNull(type, "Type must not be null!");
Assert.notNull(method, "Method must not be null!");
String[] mapping = getMappingFrom(findMergedAnnotation(method, annotationType));
String typeMapping = getMapping(type);
if (mapping == null || mapping.length == 0) {
return typeMapping;
}
return typeMapping == null || "/".equals(typeMapping) ? mapping[0] : join(typeMapping, mapping[0]);
}
private String[] getMappingFrom(Annotation annotation) {
Object value = mappingAttributeName == null ? getValue(annotation) : getValue(annotation, mappingAttributeName);
if (value instanceof String) {
return new String[] { (String) value };
} else if (value instanceof String[]) {
return (String[]) value;
} else if (value == null) {
return new String[0];
}
throw new IllegalStateException(String.format(
"Unsupported type for the mapping attribute! Support String and String[] but got %s!", value.getClass()));
}
/**
* Joins the given mappings making sure exactly one slash.
*
* @param typeMapping must not be {@literal null} or empty.
* @param mapping must not be {@literal null} or empty.
* @return
*/
private static String join(String typeMapping, String mapping) {
return MULTIPLE_SLASHES.matcher(typeMapping.concat("/").concat(mapping)).replaceAll("/");
}
}