/*
* Copyright (c) 2008, 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.komma.generator;
import static java.lang.Character.toLowerCase;
import java.beans.BeanDescriptor;
import java.beans.BeanInfo;
import java.beans.FeatureDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.enilink.composition.annotations.Iri;
import net.enilink.komma.core.IEntityManager;
import net.enilink.komma.core.IReference;
import net.enilink.komma.core.KommaException;
import net.enilink.komma.core.Statement;
import net.enilink.komma.core.URI;
import net.enilink.komma.core.URIs;
import net.enilink.komma.core.visitor.IDataVisitor;
import net.enilink.komma.literals.LiteralConverter;
import net.enilink.vocab.owl.OWL;
import net.enilink.vocab.rdf.RDF;
import net.enilink.vocab.rdfs.RDFS;
import net.enilink.vocab.xmlschema.XMLSCHEMA;
import com.google.inject.Inject;
/**
* Reads BeanInfo objects and writes the class and properties into an OWL
* ontology.
*
*/
public class OwlGenerator {
@Inject
private LiteralConverter lc;
@Inject
IEntityManager manager;
private Map<String, String> namespaces = new HashMap<String, String>();
private Map<String, String> prefixes = new HashMap<String, String>();
private IDataVisitor<?> target;
private URI createURI(Class<?> beanClass) {
if (beanClass.isAnnotationPresent(Iri.class)) {
String value = beanClass.getAnnotation(Iri.class).value();
return URIs.createURI(value);
}
String ns = findNamespace(beanClass);
if (ns == null)
return URIs.createURI("java:" + beanClass.getName());
String localName = beanClass.getSimpleName();
return URIs.createURI(ns + localName);
}
public Set<URI> exportOntology(List<Class<?>> beans, IDataVisitor<?> handler)
throws IntrospectionException {
if (beans.isEmpty())
throw new IllegalArgumentException();
target = handler;
Set<URI> set = new HashSet<URI>();
for (Class<?> bean : beans) {
BeanInfo info = Introspector.getBeanInfo(bean);
set.add(handleBeanClass(info.getBeanDescriptor()));
}
for (Class<?> bean : beans) {
BeanInfo info = Introspector.getBeanInfo(bean);
URI domain = createURI(info.getBeanDescriptor().getBeanClass());
for (PropertyDescriptor desc : info.getPropertyDescriptors()) {
if (desc.getReadMethod() != null
&& !desc.getReadMethod().getDeclaringClass()
.equals(Object.class)) {
handleBeanProperty(domain, desc);
}
}
}
return set;
}
private String findNamespace(Class<?> beanClass) {
String packageName = getPackageName(beanClass);
if (namespaces.containsKey(packageName))
return namespaces.get(packageName);
Package pkg = beanClass.getPackage();
if (pkg != null && pkg.isAnnotationPresent(Iri.class)) {
String name = pkg.getAnnotation(Iri.class).value();
return getNamespace(name);
}
return null;
}
private URI getDatatype(Class<?> range) {
if (range.equals(Object.class))
return RDFS.TYPE_RESOURCE;
if (range.equals(String.class))
return XMLSCHEMA.TYPE_STRING;
try {
net.enilink.komma.core.URI datatype = lc.findDatatype(range);
if (datatype.scheme().equals("java")) {
return null;
}
return datatype;
} catch (KommaException e) {
return null;
}
}
private String getNamespace(Class<?> beanClass) {
String packageName = getPackageName(beanClass);
String ns = findNamespace(beanClass);
if (ns != null)
return ns;
return "java:" + packageName + '#';
}
private String getNamespace(String uri) {
String ns;
if (uri.endsWith("/") || uri.endsWith("#")) {
ns = uri;
} else if (uri.contains("#")) {
ns = uri.substring(0, uri.indexOf('#') + 1);
} else {
ns = uri + '#';
}
return ns;
}
private String getPackageName(Class<?> beanClass) {
if (beanClass.getPackage() != null)
return beanClass.getPackage().getName();
String name = beanClass.getName();
if (name.contains("."))
return name.substring(0, name.indexOf('.'));
return "";
}
private Class<?> getPropertyType(PropertyDescriptor desc) {
Class<?> range = desc.getPropertyType();
if (range.isPrimitive()) {
if (range.equals(Character.TYPE))
return Character.class;
if (range.equals(Byte.TYPE))
return Byte.class;
if (range.equals(Short.TYPE))
return Short.class;
if (range.equals(Integer.TYPE))
return Integer.class;
if (range.equals(Long.TYPE))
return Long.class;
if (range.equals(Float.TYPE))
return Float.class;
if (range.equals(Double.TYPE))
return Double.class;
if (range.equals(Boolean.TYPE))
return Boolean.class;
}
return range;
}
private URI handleBeanClass(BeanDescriptor desc) {
Class<?> beanClass = desc.getBeanClass();
URI uri = createURI(beanClass);
if (beanClass.isAnnotationPresent(Deprecated.class)) {
handleStatement(uri, RDF.PROPERTY_TYPE, OWL.TYPE_DEPRECATEDCLASS);
} else {
handleStatement(uri, RDF.PROPERTY_TYPE, OWL.TYPE_CLASS);
}
handleLabel(uri, desc);
URI ns = URIs.createURI(getNamespace(beanClass));
handleStatement(uri, RDFS.PROPERTY_ISDEFINEDBY, ns);
Class<?> sup = beanClass.getSuperclass();
if (sup != null && !sup.equals(Object.class))
handleStatement(uri, RDFS.PROPERTY_SUBCLASSOF, createURI(sup));
for (Class<?> face : beanClass.getInterfaces()) {
handleStatement(uri, RDFS.PROPERTY_SUBCLASSOF, createURI(face));
}
return ns;
}
private void handleBeanProperty(URI domain, PropertyDescriptor desc) {
URI uri;
if (desc.getReadMethod().isAnnotationPresent(Iri.class)) {
Iri ann = desc.getReadMethod().getAnnotation(Iri.class);
uri = URIs.createURI(ann.value());
} else {
uri = domain.appendLocalPart(desc.getName());
}
URI ns = URIs.createURI(getNamespace(desc.getReadMethod()
.getDeclaringClass()));
Class<?> range = getPropertyType(desc);
if (range.equals(Set.class)) {
range = Object.class;
Type t = desc.getReadMethod().getGenericReturnType();
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) t;
Type[] args = pt.getActualTypeArguments();
if (args.length == 1 && args[0] instanceof Class<?>)
range = (Class<?>) args[0];
}
}
URI datatype = getDatatype(range);
if (datatype == null) {
handleStatement(uri, RDF.PROPERTY_TYPE, OWL.TYPE_OBJECTPROPERTY);
handleStatement(uri, RDFS.PROPERTY_RANGE, createURI(range));
} else if (datatype.equals(RDFS.TYPE_RESOURCE)) {
handleStatement(uri, RDF.PROPERTY_TYPE, RDF.TYPE_PROPERTY);
handleStatement(uri, RDFS.PROPERTY_RANGE, datatype);
} else {
handleStatement(uri, RDF.PROPERTY_TYPE, OWL.TYPE_DATATYPEPROPERTY);
handleStatement(uri, RDFS.PROPERTY_RANGE, datatype);
}
if (desc.getReadMethod().isAnnotationPresent(Deprecated.class)) {
handleStatement(uri, RDF.PROPERTY_TYPE, OWL.TYPE_DEPRECATEDPROPERTY);
}
handleStatement(uri, RDFS.PROPERTY_DOMAIN, domain);
if (!desc.getPropertyType().equals(Set.class)) {
handleStatement(uri, RDF.PROPERTY_TYPE, OWL.TYPE_FUNCTIONALPROPERTY);
}
handleLabel(uri, desc);
handleStatement(uri, RDFS.PROPERTY_ISDEFINEDBY, ns);
}
private void handleLabel(URI uri, FeatureDescriptor desc) {
String prefix = prefixes.get(uri.namespace().toString());
String label = removePrefix(prefix, desc.getDisplayName());
String comment = removePrefix(prefix, desc.getShortDescription());
handleStatement(uri, RDFS.PROPERTY_LABEL, label);
if (!label.equals(comment)) {
handleStatement(uri, RDFS.PROPERTY_COMMENT, comment);
}
}
private void handleStatement(IReference subj, URI pred, Object obj) {
target.visitStatement(new Statement(subj, pred, obj));
}
private String removePrefix(String prefix, String label) {
if (prefix == null)
return label;
int l = prefix.length();
if (label.startsWith(prefix) && label.length() > l) {
label = toLowerCase(label.charAt(l)) + label.substring(l + 1);
}
return label;
}
public void setNamespace(String pkgName, String prefix, String namespace) {
namespaces.put(pkgName, namespace);
prefixes.put(namespace, prefix);
}
}