/*
* Copyright (c) 2009, 2010, James Leigh All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.enilink.composition.mappers;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import net.enilink.composition.annotations.Iri;
import net.enilink.composition.exceptions.ConfigException;
import net.enilink.composition.vocabulary.MSG;
import net.enilink.composition.vocabulary.OWL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tracks the annotation, concept, and behaviour classes and what rdf:type they
* should be used with.
*
*/
public class DefaultRoleMapper<T> implements Cloneable, RoleMapper<T> {
private static Logger logger = LoggerFactory
.getLogger(DefaultRoleMapper.class);
private Map<Method, T> annotations = new HashMap<Method, T>();
private Map<Class<?>, Class<?>> complements = new ConcurrentHashMap<Class<?>, Class<?>>();
private Map<T, List<Class<?>>> instances = new ConcurrentHashMap<T, List<Class<?>>>(
256);
private Map<Class<?>, List<Class<?>>> intersections = new ConcurrentHashMap<Class<?>, List<Class<?>>>();
private RoleMatcher matches = new RoleMatcher();
private HierarchicalRoleMapper<T> roleMapper = new HierarchicalRoleMapper<T>();
private TypeFactory<T> typeFactory;
public DefaultRoleMapper(TypeFactory<T> typeFactory) {
this.typeFactory = typeFactory;
roleMapper.setTypeFactory(typeFactory);
}
@Override
public void addAnnotation(Class<?> annotation) {
for (Method m : annotation.getDeclaredMethods()) {
if (!m.isAnnotationPresent(Iri.class)) {
String msg = "@" + Iri.class.getSimpleName()
+ " annotation required in " + m.toGenericString();
throw new IllegalArgumentException(msg);
}
String uri = m.getAnnotation(Iri.class).value();
addAnnotation(m, typeFactory.createType(uri));
}
}
@Override
public void addAnnotation(Class<?> annotation, T uri) {
if (annotation.getDeclaredMethods().length != 1)
throw new IllegalArgumentException(
"Must specify annotation method if multiple methods exist: "
+ annotation);
addAnnotation(annotation.getDeclaredMethods()[0], uri);
}
@Override
public void addAnnotation(Method annotation) {
if (!annotation.isAnnotationPresent(Iri.class))
throw new IllegalArgumentException("@" + Iri.class.getSimpleName()
+ " annotation required in " + annotation.toGenericString());
String uri = annotation.getAnnotation(Iri.class).value();
addAnnotation(annotation, typeFactory.createType(uri));
}
@Override
public void addAnnotation(Method annotation, T uri) {
annotations.put(annotation, uri);
if (annotation.isAnnotationPresent(Iri.class)) {
String Iri = annotation.getAnnotation(Iri.class).value();
if (!uri.toString().equals(Iri)) {
addAnnotation(annotation);
}
}
}
@Override
public void addBehaviour(Class<?> role) throws ConfigException {
assertBehaviour(role);
// behaviour is mapped explicitly with an annotation
if (recordRole(role, role, null, false)) {
return;
}
// behaviour is mapped by implementing a concept
boolean hasType = false;
for (Class<?> face : role.getInterfaces()) {
boolean recorded = recordRole(role, face, null, false);
if (recorded && hasType) {
throw new ConfigException(role.getSimpleName()
+ " can only implement one concept");
} else {
hasType |= recorded;
}
}
if (!hasType)
throw new ConfigException(role.getSimpleName()
+ " must implement a concept or mapped explicitly");
}
@Override
public void addBehaviour(Class<?> role, T type) throws ConfigException {
assertBehaviour(role);
recordRole(role, null, type, false);
}
@Override
public void addConcept(Class<?> role) throws ConfigException {
recordRole(role, role, null, true);
}
@Override
public void addConcept(Class<?> role, T type) throws ConfigException {
recordRole(role, role, type, false);
}
private void addInterfaces(Set<Class<?>> set, Class<?>... list) {
for (Class<?> c : list) {
if (c != null && set.add(c)) {
addInterfaces(set, c.getSuperclass());
addInterfaces(set, c.getInterfaces());
}
}
}
private void addIntersectionsAndComplements(Collection<Class<?>> roles) {
for (Map.Entry<Class<?>, List<Class<?>>> e : intersections.entrySet()) {
Class<?> inter = e.getKey();
List<Class<?>> of = e.getValue();
if (!roles.contains(inter) && intersects(roles, of)) {
roles.add(inter);
}
}
boolean complementAdded = false;
for (Map.Entry<Class<?>, Class<?>> e : complements.entrySet()) {
Class<?> comp = e.getKey();
Class<?> of = e.getValue();
if (!roles.contains(comp) && !contains(roles, of)) {
complementAdded = true;
roles.add(comp);
}
}
if (complementAdded) {
for (Map.Entry<Class<?>, List<Class<?>>> e : intersections
.entrySet()) {
Class<?> inter = e.getKey();
List<Class<?>> of = e.getValue();
if (!roles.contains(inter) && intersects(roles, of)) {
roles.add(inter);
}
}
}
}
private void assertBehaviour(Class<?> role) throws ConfigException {
if (role.isInterface())
throw new ConfigException(role.getSimpleName()
+ " is an interface and not a behaviour");
// for (Method method : role.getDeclaredMethods()) {
// if (isAnnotationPresent(method)
// && method.getName().startsWith("get"))
// throw new ConfigException(role.getSimpleName()
// + " cannot have a property annotation");
// }
}
/*
* (non-Javadoc)
*
* @see net.enilink.composition.mappers.RoleMapper#clone()
*/
@Override
public DefaultRoleMapper<T> clone() {
try {
@SuppressWarnings("unchecked")
DefaultRoleMapper<T> cloned = (DefaultRoleMapper<T>) super.clone();
cloned.roleMapper = roleMapper.clone();
cloned.matches = matches.clone();
cloned.annotations = new HashMap<Method, T>(annotations);
cloned.complements = new ConcurrentHashMap<Class<?>, Class<?>>(
complements);
cloned.intersections = clone(intersections);
return cloned;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
private <K, V> Map<K, List<V>> clone(Map<K, List<V>> map) {
Map<K, List<V>> cloned = new ConcurrentHashMap<K, List<V>>(map);
for (Map.Entry<K, List<V>> e : cloned.entrySet()) {
e.setValue(new CopyOnWriteArrayList<V>(e.getValue()));
}
return cloned;
}
private boolean contains(Collection<Class<?>> roles, Class<?> of) {
for (Class<?> type : roles) {
if (of.isAssignableFrom(type))
return true;
}
return false;
}
private void findAdditionalRoles(Collection<Class<?>> classes) {
if (complements.isEmpty()) {
return;
}
addIntersectionsAndComplements(classes);
}
private Collection<Class<?>> findAllRoles(T type) {
Set<Class<?>> set = new HashSet<Class<?>>();
for (Class<?> role : findRoles(type, new HashSet<Class<?>>())) {
if (set.add(role)) {
addInterfaces(set, role.getSuperclass());
addInterfaces(set, role.getInterfaces());
}
}
return set;
}
@Override
public T findAnnotation(Method ann) {
return annotations.get(ann);
}
private T findDefaultType(Class<?> role, AnnotatedElement element) {
if (element.isAnnotationPresent(Iri.class)) {
String value = element.getAnnotation(Iri.class).value();
if (value != null) {
return typeFactory.createType(value);
}
}
return null;
}
@Override
public Collection<Class<?>> findIndividualRoles(T instance,
Collection<Class<?>> classes) {
List<Class<?>> list = instances.get(instance);
if (list != null) {
classes.addAll(list);
}
matches.findRoles(typeFactory.toString(instance), classes);
return classes;
}
@Override
public Class<?> findInterfaceConcept(T uri) {
Class<?> concept = null;
Class<?> mapped = null;
Collection<Class<?>> rs = findAllRoles(uri);
for (Class<?> r : rs) {
T type = findType(r);
if (r.isInterface() && type != null) {
concept = r;
if (uri.equals(type)) {
mapped = r;
break;
}
}
}
if (mapped != null)
return mapped;
if (concept != null)
return concept;
return null;
}
/*
* (non-Javadoc)
*
* @see
* net.enilink.composition.mappers.RoleMapper#findRoles(java.util.Collection
* , java.util.Collection)
*/
@Override
public Collection<Class<?>> findRoles(Collection<T> types,
Collection<Class<?>> roles) {
roleMapper.findRoles(types, roles);
for (T type : types) {
matches.findRoles(typeFactory.toString(type), roles);
}
findAdditionalRoles(roles);
return roles;
}
/*
* (non-Javadoc)
*
* @see net.enilink.composition.mappers.RoleMapper#findRoles(T)
*/
@Override
public Collection<Class<?>> findRoles(T type, Collection<Class<?>> roles) {
roleMapper.findRoles(type, roles);
matches.findRoles(typeFactory.toString(type), roles);
findAdditionalRoles(roles);
return roles;
}
/*
* (non-Javadoc)
*
* @see
* net.enilink.composition.mappers.RoleMapper#findSubTypes(java.lang.Class,
* java.util.Collection)
*/
@Override
public Collection<T> findSubTypes(Class<?> role, Collection<T> rdfTypes) {
return roleMapper.findSubTypes(role, rdfTypes);
}
/*
* (non-Javadoc)
*
* @see net.enilink.composition.mappers.RoleMapper#findType(java.lang.Class)
*/
@Override
public T findType(Class<?> concept) {
return roleMapper.findType(concept);
}
private boolean intersects(Collection<Class<?>> roles, List<Class<?>> ofs) {
for (Class<?> of : ofs) {
if (!contains(roles, of))
return false;
}
return true;
}
@Override
public boolean isIndividualRolesPresent(T instance) {
return !matches.isEmpty() || !instances.isEmpty()
&& instances.containsKey(instance);
}
@Override
public boolean isRecordedConcept(T type) {
if (roleMapper.isTypeRecorded(type)) {
for (Class<?> role : findAllRoles(type)) {
if (findType(role) != null) {
return true;
}
}
}
return false;
}
private boolean recordAnonymous(Class<?> role, Class<?> elm)
throws ConfigException {
boolean recorded = false;
for (Annotation ann : elm.getAnnotations()) {
for (Method m : ann.annotationType().getDeclaredMethods()) {
try {
T name = findAnnotation(m);
if (name == null && m.isAnnotationPresent(Iri.class)) {
addAnnotation(m);
name = findAnnotation(m);
}
if (name == null) {
continue;
}
Object value = m.invoke(ann);
recorded |= recordAnonymous(role, name, value);
} catch (Exception e) {
logger.error(e.getMessage(), e);
continue;
}
}
}
return recorded;
}
private boolean recordAnonymous(Class<?> role, T name, Object value)
throws ConfigException {
boolean recorded = false;
try {
String nameStr = typeFactory.toString(name);
if (MSG.MATCHING.equals(nameStr)) {
String[] values = (String[]) value;
for (String pattern : values) {
matches.addRoles(pattern, role);
recorded = true;
}
}
if (OWL.ONEOF.equals(nameStr)) {
String[] values = (String[]) value;
for (String instance : values) {
T uri = typeFactory.createType(instance);
List<Class<?>> list = instances.get(uri);
if (list == null) {
list = new CopyOnWriteArrayList<Class<?>>();
instances.put(uri, list);
}
list.add(role);
recorded = true;
}
}
if (OWL.COMPLEMENTOF.equals(nameStr)) {
if (value instanceof Class<?>) {
Class<?> concept = (Class<?>) value;
recordRole(concept, concept, null, true);
complements.put(role, concept);
recorded = true;
} else {
for (Class<?> concept : findRoles(
typeFactory.createType((String) value),
new HashSet<Class<?>>())) {
complements.put(role, concept);
recorded = true;
}
}
}
if (OWL.INTERSECTIONOF.equals(nameStr)) {
List<Class<?>> ofs = new ArrayList<Class<?>>();
for (Object v : (Object[]) value) {
if (v instanceof Class<?>) {
Class<?> concept = (Class<?>) v;
recordRole(concept, concept, null, true);
ofs.add(concept);
} else {
ofs.addAll(findRoles(
typeFactory.createType((String) v),
new HashSet<Class<?>>()));
}
}
intersections.put(role, ofs);
recorded = true;
}
if (OWL.UNIONOF.equals(nameStr)) {
for (Object v : (Object[]) value) {
if (v instanceof Class<?>) {
Class<?> concept = (Class<?>) v;
recordRole(concept, concept, null, true);
recorded |= recordRole(role, concept, null, true);
} else {
for (Class<?> concept : findRoles(
typeFactory.createType((String) v),
new HashSet<Class<?>>())) {
if (!role.equals(concept)) {
recorded |= recordRole(role, concept, null,
true);
}
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return recorded;
}
private boolean recordRole(Class<?> role, Class<?> element, T rdfType,
boolean base) throws ConfigException {
T defType = element == null ? null : findDefaultType(role, element);
boolean hasType = false;
if (rdfType != null) {
if (role.isInterface()) {
roleMapper.recordConcept(role, rdfType);
} else {
roleMapper.recordBehaviour(role, rdfType);
}
hasType = true;
} else if (defType != null) {
if (role.isInterface()) {
roleMapper.recordConcept(role, defType);
} else {
roleMapper.recordBehaviour(role, defType);
}
hasType = true;
} else if (element != null) {
hasType = recordAnonymous(role, element);
}
if (!hasType && element != null) {
for (Class<?> face : element.getInterfaces()) {
hasType |= recordRole(role, face, null, false);
}
}
if (!hasType && base) {
throw new ConfigException(role.getSimpleName()
+ " does not have an RDF type mapping");
}
return hasType;
}
}