View Javadoc

1   /*
2    * Copyright (c) 2007, James Leigh All rights reserved.
3    * 
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions are met:
6    * 
7    * - Redistributions of source code must retain the above copyright notice, this
8    *   list of conditions and the following disclaimer.
9    * - Redistributions in binary form must reproduce the above copyright notice,
10   *   this list of conditions and the following disclaimer in the documentation
11   *   and/or other materials provided with the distribution. 
12   * - Neither the name of the openrdf.org nor the names of its contributors may
13   *   be used to endorse or promote products derived from this software without
14   *   specific prior written permission.
15   * 
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26   * POSSIBILITY OF SUCH DAMAGE.
27   * 
28   */
29  package org.openrdf.elmo.codegen;
30  
31  import java.beans.BeanDescriptor;
32  import java.beans.BeanInfo;
33  import java.beans.IntrospectionException;
34  import java.beans.Introspector;
35  import java.beans.PropertyDescriptor;
36  import java.lang.reflect.Method;
37  import java.lang.reflect.ParameterizedType;
38  import java.lang.reflect.Type;
39  import java.util.HashMap;
40  import java.util.List;
41  import java.util.Map;
42  import java.util.Set;
43  
44  import org.openrdf.elmo.LiteralManager;
45  import org.openrdf.elmo.annotations.complementOf;
46  import org.openrdf.elmo.annotations.disjointWith;
47  import org.openrdf.elmo.annotations.intersectionOf;
48  import org.openrdf.elmo.annotations.inverseOf;
49  import org.openrdf.elmo.annotations.oneOf;
50  import org.openrdf.elmo.annotations.rdf;
51  import org.openrdf.elmo.exceptions.ElmoConversionException;
52  import org.openrdf.model.BNode;
53  import org.openrdf.model.Literal;
54  import org.openrdf.model.Resource;
55  import org.openrdf.model.URI;
56  import org.openrdf.model.Value;
57  import org.openrdf.model.ValueFactory;
58  import org.openrdf.model.impl.ValueFactoryImpl;
59  import org.openrdf.model.vocabulary.OWL;
60  import org.openrdf.model.vocabulary.RDF;
61  import org.openrdf.model.vocabulary.RDFS;
62  import org.openrdf.model.vocabulary.XMLSchema;
63  import org.openrdf.rio.RDFHandler;
64  import org.openrdf.rio.RDFHandlerException;
65  
66  /**
67   * Reads BeanInfo objects and writes the class and properties into an OWL
68   * ontology.
69   * 
70   * @author James Leigh
71   * 
72   */
73  public class OwlGenerator {
74  	private LiteralManager<URI, Literal> lc;
75  
76  	private Map<String, String> namespaces = new HashMap<String, String>();
77  
78  	private RDFHandler target;
79  
80  	private ValueFactory vf = new ValueFactoryImpl();
81  
82  	public void setLiteralManager(LiteralManager<URI, Literal> lc) {
83  		this.lc = lc;
84  	}
85  
86  	public void setNamespace(String pkgName, String namespace) {
87  		namespaces.put(pkgName, namespace);
88  	}
89  
90  	public void exportOntology(List<Class<?>> beans, RDFHandler handler)
91  			throws IntrospectionException, RDFHandlerException {
92  		if (beans.isEmpty())
93  			throw new IllegalArgumentException();
94  		target = handler;
95  		for (Class<?> bean : beans) {
96  			BeanInfo info = Introspector.getBeanInfo(bean);
97  			handleBeanClass(info.getBeanDescriptor());
98  		}
99  		for (Class<?> bean : beans) {
100 			BeanInfo info = Introspector.getBeanInfo(bean);
101 			URI domain = createURI(info.getBeanDescriptor().getBeanClass());
102 			for (PropertyDescriptor desc : info.getPropertyDescriptors()) {
103 				handleBeanProperty(domain, desc);
104 			}
105 		}
106 	}
107 
108 	private String getNamespace(String uri) {
109 		String ns;
110 		if (uri.endsWith("/") || uri.endsWith("#")) {
111 			ns = uri;
112 		} else if (uri.contains("#")) {
113 			ns = uri.substring(0, uri.indexOf('#') + 1);
114 		} else {
115 			ns = uri + '#';
116 		}
117 		return ns;
118 	}
119 
120 	private URI createURI(Object obj) {
121 		if (obj instanceof Class)
122 			return createURI((Class<?>) obj);
123 		return vf.createURI(obj.toString());
124 	}
125 
126 	private URI createURI(Class<?> beanClass) {
127 		if (beanClass.isAnnotationPresent(rdf.class)) {
128 			String[] values = beanClass.getAnnotation(rdf.class).value();
129 			if (values.length > 0)
130 				return vf.createURI(values[0]);
131 		}
132 		String ns = getNamespace(beanClass);
133 		String localName = beanClass.getSimpleName();
134 		return vf.createURI(ns, localName);
135 	}
136 
137 	private String getNamespace(Class<?> beanClass) {
138 		String packageName = beanClass.getPackage().getName();
139 		if (namespaces.containsKey(packageName))
140 			return namespaces.get(packageName);
141 		Package pkg = beanClass.getPackage();
142 		if (pkg.isAnnotationPresent(rdf.class)) {
143 			String name = pkg.getAnnotation(rdf.class).value()[0];
144 			return getNamespace(name);
145 		}
146 		return "java:" + packageName + '#';
147 	}
148 
149 	private void handleBeanClass(BeanDescriptor desc)
150 			throws RDFHandlerException {
151 		Class<?> beanClass = desc.getBeanClass();
152 		URI uri = createURI(beanClass);
153 		handleStatement(uri, RDF.TYPE, OWL.CLASS);
154 		handleStatement(uri, RDFS.LABEL, desc.getDisplayName());
155 		handleStatement(uri, RDFS.COMMENT, desc.getShortDescription());
156 		URI ns = vf.createURI(getNamespace(beanClass));
157 		handleStatement(uri, RDFS.ISDEFINEDBY, ns);
158 		Class<?> sup = beanClass.getSuperclass();
159 		if (sup != null && !sup.equals(Object.class))
160 			handleStatement(uri, RDFS.SUBCLASSOF, createURI(sup));
161 		for (Class<?> face : beanClass.getInterfaces()) {
162 			handleStatement(uri, RDFS.SUBCLASSOF, createURI(face));
163 		}
164 		if (beanClass.isAnnotationPresent(rdf.class)) {
165 			String[] eq = beanClass.getAnnotation(rdf.class).value();
166 			for (int i = 1; i < eq.length; i++) {
167 				handleStatement(uri, OWL.EQUIVALENTCLASS, vf.createURI(eq[i]));
168 			}
169 		}
170 		if (beanClass.isAnnotationPresent(disjointWith.class)) {
171 			disjointWith dw = beanClass.getAnnotation(disjointWith.class);
172 			for (Class<?> c : dw.value()) {
173 				handleStatement(uri, OWL.DISJOINTWITH, createURI(c));
174 			}
175 		}
176 		if (beanClass.isAnnotationPresent(intersectionOf.class)) {
177 			intersectionOf io = beanClass.getAnnotation(intersectionOf.class);
178 			BNode list = createList(io.value());
179 			handleStatement(uri, OWL.INTERSECTIONOF, list);
180 		}
181 		if (beanClass.isAnnotationPresent(complementOf.class)) {
182 			complementOf co = beanClass.getAnnotation(complementOf.class);
183 			handleStatement(uri, OWL.COMPLEMENTOF, createURI(co.value()));
184 		}
185 	}
186 
187 	private void handleBeanProperty(URI domain, PropertyDescriptor desc)
188 			throws RDFHandlerException {
189 		URI uri;
190 		if (desc.getReadMethod().isAnnotationPresent(rdf.class)) {
191 			rdf ann = desc.getReadMethod().getAnnotation(rdf.class);
192 			uri = vf.createURI(ann.value()[0]);
193 		} else {
194 			uri = vf.createURI(domain.getNamespace(), desc.getName());
195 		}
196 		URI ns = vf.createURI(uri.getNamespace());
197 		Class<?> range = desc.getPropertyType();
198 		if (range.equals(Set.class)) {
199 			range = Object.class;
200 			Type t = desc.getReadMethod().getGenericReturnType();
201 			if (t instanceof ParameterizedType) {
202 				ParameterizedType pt = (ParameterizedType) t;
203 				Type[] args = pt.getActualTypeArguments();
204 				if (args.length == 1 && args[0] instanceof Class)
205 					range = (Class) args[0];
206 			}
207 		}
208 		URI datatype = getDatatype(range);
209 		if (datatype == null) {
210 			handleStatement(uri, RDF.TYPE, OWL.OBJECTPROPERTY);
211 			handleStatement(uri, RDFS.RANGE, createURI(range));
212 		} else if (datatype.equals(RDFS.RESOURCE)) {
213 			handleStatement(uri, RDF.TYPE, RDF.PROPERTY);
214 			handleStatement(uri, RDFS.RANGE, datatype);
215 		} else {
216 			handleStatement(uri, RDF.TYPE, OWL.DATATYPEPROPERTY);
217 			handleStatement(uri, RDFS.RANGE, datatype);
218 		}
219 		handleStatement(uri, RDFS.DOMAIN, domain);
220 		if (range.equals(desc.getPropertyType()))
221 			handleStatement(uri, RDF.TYPE, OWL.FUNCTIONALPROPERTY);
222 		handleStatement(uri, RDFS.LABEL, desc.getDisplayName());
223 		handleStatement(uri, RDFS.COMMENT, desc.getShortDescription());
224 		handleStatement(uri, RDFS.ISDEFINEDBY, ns);
225 		Method getter = desc.getReadMethod();
226 		if (getter.isAnnotationPresent(rdf.class)) {
227 			String[] eq = getter.getAnnotation(rdf.class).value();
228 			for (int i = 1; i < eq.length; i++) {
229 				handleStatement(uri, RDFS.SUBPROPERTYOF, vf.createURI(eq[i]));
230 			}
231 		}
232 		if (getter.isAnnotationPresent(inverseOf.class)) {
233 			inverseOf io = getter.getAnnotation(inverseOf.class);
234 			for (String inv : io.value()) {
235 				handleStatement(uri, OWL.INVERSEOF, vf.createURI(inv));
236 			}
237 		}
238 		Method setter = desc.getWriteMethod();
239 		if (setter.isAnnotationPresent(oneOf.class)) {
240 			oneOf oo = setter.getAnnotation(oneOf.class);
241 			String dt = oo.datatype();
242 			BNode list = createList(oo.label(), dt);
243 			if (list != null) {
244 				handleStatement(uri, OWL.ONEOF, list);
245 			}
246 			list = createList(oo.value());
247 			if (list != null) {
248 				handleStatement(uri, OWL.ONEOF, list);
249 			}
250 		}
251 	}
252 
253 	private BNode createList(Object[] objects) throws RDFHandlerException {
254 		BNode list = null, origin = null;
255 		for (Object obj : objects) {
256 			if (list == null) {
257 				origin = list = vf.createBNode();
258 				handleStatement(list, RDF.TYPE, RDF.LIST);
259 			} else {
260 				BNode rest = vf.createBNode();
261 				handleStatement(list, RDF.REST, rest);
262 				list = rest;
263 				handleStatement(list, RDF.TYPE, RDF.LIST);
264 			}
265 			handleStatement(list, RDF.FIRST, createURI(obj));
266 		}
267 		if (list != null) {
268 			handleStatement(list, RDF.REST, RDF.NIL);
269 		}
270 		return origin;
271 	}
272 
273 	private BNode createList(String[] labels, String datatype)
274 			throws RDFHandlerException {
275 		BNode list = null, origin = null;
276 		for (String label : labels) {
277 			if (list == null) {
278 				origin = list = vf.createBNode();
279 				handleStatement(list, RDF.TYPE, RDF.LIST);
280 			} else {
281 				BNode rest = vf.createBNode();
282 				handleStatement(list, RDF.REST, rest);
283 				list = rest;
284 				handleStatement(list, RDF.TYPE, RDF.LIST);
285 			}
286 			handleStatement(list, RDF.FIRST, vf.createLiteral(label, datatype));
287 		}
288 		if (list != null) {
289 			handleStatement(list, RDF.REST, RDF.NIL);
290 		}
291 		return origin;
292 	}
293 
294 	private URI getDatatype(Class<?> range) {
295 		if (range.equals(Object.class))
296 			return RDFS.RESOURCE;
297 		if (range.equals(String.class))
298 			return XMLSchema.STRING;
299 		try {
300 			URI datatype = lc.getDatatype(range);
301 			if (datatype.getNamespace().equals("java:"))
302 				return null;
303 			return datatype;
304 		} catch (ElmoConversionException e) {
305 			return null;
306 		}
307 	}
308 
309 	private void handleStatement(Resource subj, URI pred, String obj)
310 			throws RDFHandlerException {
311 		handleStatement(subj, pred, vf.createLiteral(obj));
312 	}
313 
314 	private void handleStatement(Resource subj, URI pred, Value obj)
315 			throws RDFHandlerException {
316 		target.handleStatement(vf.createStatement(subj, pred, obj));
317 	}
318 
319 }