001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.rdf.owl2java; 031 032import java.io.File; 033import java.io.FileInputStream; 034import java.io.IOException; 035import java.io.InputStream; 036import java.util.Collection; 037import java.util.HashMap; 038import java.util.Map; 039 040import org.apache.commons.io.FileUtils; 041import org.kohsuke.args4j.Argument; 042import org.kohsuke.args4j.CmdLineException; 043import org.kohsuke.args4j.CmdLineParser; 044import org.kohsuke.args4j.Option; 045import org.openrdf.model.URI; 046import org.openrdf.query.BindingSet; 047import org.openrdf.query.MalformedQueryException; 048import org.openrdf.query.QueryEvaluationException; 049import org.openrdf.query.QueryLanguage; 050import org.openrdf.query.TupleQuery; 051import org.openrdf.query.TupleQueryResult; 052import org.openrdf.repository.Repository; 053import org.openrdf.repository.RepositoryConnection; 054import org.openrdf.repository.RepositoryException; 055import org.openrdf.repository.sail.SailRepository; 056import org.openrdf.rio.RDFFormat; 057import org.openrdf.rio.RDFParseException; 058import org.openrdf.sail.memory.MemoryStore; 059 060import com.google.common.base.Charsets; 061import com.google.common.io.Resources; 062 063/** 064 * The main class of the OWL 2 Java tool. This class provides a main method that 065 * will convert an OWL/RDFS file to a set of Java code files that compile to 066 * provide an object representation of the ontology. 067 * 068 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 069 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 070 * @created 29 Oct 2012 071 * @version $Author$, $Revision$, $Date$ 072 */ 073public class Generator 074{ 075 /** 076 * Options for the generation of the Java classes. 077 * 078 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 079 * @created 30 Oct 2012 080 * @version $Author$, $Revision$, $Date$ 081 */ 082 public static class GeneratorOptions implements Cloneable 083 { 084 /** The RDF file to convert */ 085 @Argument(metaVar = "RDF-FILE", index = 0, usage = "The RDF file to convert", required = true) 086 public String rdfFile; 087 088 /** The target directory for the output */ 089 @Argument( 090 metaVar = "TARGET-DIR", 091 index = 1, 092 usage = "The output directory for the generated class files", 093 required = true) 094 public String targetDirectory; 095 096 /** Whether to generate @@Predicate annotations. */ 097 @Option(name = "-annotate", aliases = "-a", usage = "Generate @Predicate annotations") 098 public boolean generateAnnotations = true; 099 100 /** Whether to flatten the properties in the class structure */ 101 @Option(name = "-flatten", aliases = "-f", usage = "Flatten the properties in the generated classes") 102 public boolean flattenClassStructure = false; 103 104 /** Whether to put the implementation files in a separate package */ 105 @Option(name = "-separate", aliases = "-s", usage = "Put implementations in a separate package") 106 public boolean separateImplementations = true; 107 108 /** Whether to create a pom.xml file for the output files */ 109 @Option(name = "-maven", aliases = "-m", usage = "Create a Maven project with this groupId", metaVar = "GROUP-ID") 110 public String mavenProject = null; 111 112 /** The artifact identifier for the maven project, if -maven is used */ 113 @Option( 114 name = "-artifactId", 115 usage = "Specify the artifact identifier for the maven project", 116 metaVar = "ARTIFACT-ID") 117 public String mavenArtifactId = "generated-rdf"; 118 119 /** The version number for the maven project, if -maven is used */ 120 @Option(name = "-version", usage = "Specify the version for the maven project", metaVar = "VERSION-NUMBER") 121 public String mavenVersionNumber = "1.0"; 122 123 /** If a mavenParent is to be added to the pom.xml, the GAV is put here */ 124 @Option( 125 name = "-mavenParent", 126 aliases = "-p", 127 usage = "The mavenParent artifact GAV to add to the pom.xml (g:a:v)") 128 public String mavenParent = null; 129 130 /** Skip generation of the maven pom */ 131 @Option(name = "-skipPom", usage = "Skip generation of the maven pom", required = false) 132 public boolean skipPom = false; 133 134 @Override 135 public Object clone() throws CloneNotSupportedException { 136 return super.clone(); 137 } 138 } 139 140 /** 141 * Useful little debug method that will print out all the triples for a 142 * given URI from the given repository. 143 * 144 * @param uri 145 * The URI 146 * @param conn 147 * The connection 148 */ 149 protected static void debugAllTriples(final URI uri, final RepositoryConnection conn) 150 { 151 try 152 { 153 // SPARQL query to get the properties and property comments 154 final String query = "SELECT ?uri ?pred ?obj WHERE { ?uri ?pred ?obj. } "; 155 156 // Prepare the query 157 final TupleQuery preparedQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, query); 158 159 // Execute the query 160 final TupleQueryResult res = preparedQuery.evaluate(); 161 162 System.out.println("----------------------------------------------"); 163 System.out.println("Triples for " + uri); 164 System.out.println("----------------------------------------------"); 165 166 // Loop through the results 167 while (res.hasNext()) 168 { 169 final BindingSet bindingSet = res.next(); 170 System.out.println("( " + 171 bindingSet.getBinding("uri").getValue() + " " + 172 bindingSet.getBinding("pred").getValue() + " " + 173 bindingSet.getBinding("obj").getValue() + " )" 174 ); 175 } 176 177 res.close(); 178 179 System.out.println("----------------------------------------------\n"); 180 } catch (final RepositoryException e) 181 { 182 e.printStackTrace(); 183 } catch (final MalformedQueryException e) 184 { 185 e.printStackTrace(); 186 } catch (final QueryEvaluationException e) 187 { 188 e.printStackTrace(); 189 } 190 } 191 192 /** 193 * Creates a cache of mappings that map URIs to package names. 194 * 195 * @param classes 196 * The list of classes to generate the package names for 197 * @return A map of class URI to package name 198 */ 199 protected static Map<URI, String> generatePackageMappings( 200 final Collection<ClassDef> classes) 201 { 202 final Map<URI, String> packageMapping = new HashMap<URI, String>(); 203 204 for (final ClassDef cd : classes) 205 { 206 if (!packageMapping.containsKey(cd.uri)) 207 { 208 packageMapping.put(cd.uri, Generator.getPackageName(cd.uri)); 209 } 210 } 211 return packageMapping; 212 } 213 214 /** 215 * From the given URI will attempt to create a java package name by 216 * reversing all the elements and separating with dots. 217 * 218 * @param uri 219 * The URI to get a package name for. 220 * @return The Java package name 221 */ 222 protected static String getPackageName(final URI uri) 223 { 224 return Generator.getPackageName(uri, true); 225 } 226 227 /** 228 * From the given URI will attempt to create a java package name by 229 * reversing all the elements and separating with dots. 230 * 231 * @param uri 232 * The URI to get a package name for. 233 * @param removeWWW 234 * Whether to remove www. parts of URIs 235 * @return The Java package name 236 */ 237 protected static String getPackageName(final URI uri, final boolean removeWWW) 238 { 239 String ns = uri.getNamespace(); 240 241 // Remove the protocol 242 if (ns.contains("//")) 243 ns = ns.substring(ns.indexOf("//") + 2); 244 245 // If there's no path component, return it as it is 246 if (!ns.contains("/")) 247 return ns; 248 249 // Get the path component 250 String last = ns.substring(ns.indexOf("/") + 1); 251 252 // Remove any anchors 253 if (last.contains("#")) 254 last = last.substring(0, last.indexOf("#")); 255 256 // Remove any file extensions 257 if (last.contains(".")) 258 last = last.substring(0, last.indexOf(".")); 259 260 // Replace all the path separators by dots 261 last = last.replace("/", "."); 262 263 // Remove invalid characters 264 // Replace dashes with underscores 265 last = last.replace("-", "_"); 266 267 // Get the server part (the bit we're going to reverse) 268 String serverName = ns.substring(0, ns.indexOf("/")); 269 270 // Split the server name and reverse it. 271 final String[] parts = serverName.split("\\."); 272 serverName = ""; 273 int count = 0; 274 for (int i = parts.length - 1; i >= 0; i--) 275 { 276 // Replace any invalid characters with underscores 277 if (parts[i].charAt(0) < 65 || parts[i].charAt(0) > 122) 278 serverName += "_"; 279 280 if (!removeWWW || !parts[i].equals("www")) 281 { 282 if (count != 0 && i != 0) 283 serverName += "."; 284 serverName += parts[i]; 285 count++; 286 } 287 } 288 289 // We'll also need to replace any invalid characters (or names) with 290 // new names 291 String lastBit = ""; 292 if (last.indexOf(".") == -1) 293 lastBit = last; 294 else 295 { 296 for (final String s : last.split("\\.")) 297 { 298 lastBit += "."; 299 if (s.charAt(0) < 65 || s.charAt(0) > 122) 300 lastBit += "_" + s; 301 else 302 lastBit += s; 303 } 304 } 305 306 return serverName + lastBit; 307 } 308 309 /** 310 * Creates a pom.xml file in the given directory that contains the necessary 311 * dependencies to make the source files compile. It uses a template pom.xml 312 * that is in the resource directory. 313 * 314 * @param targetDir 315 * The target directory 316 * @param groupId 317 * The groupId of the maven artifact 318 * @param artifactId 319 * The artifactId of the maven project 320 * @param versionNumber 321 * The version number of the maven project 322 * @param parentGAV 323 * The GAV of the mavenParent project 324 */ 325 private static void createPOM(final File targetDir, final String groupId, 326 final String artifactId, final String versionNumber, final String parentGAV) 327 { 328 try 329 { 330 String s = Resources.toString(Resources.getResource("pom.xml"), Charsets.UTF_8); 331 s = Generator.replaceCodes(groupId, artifactId, versionNumber, s); 332 333 // Determine if we need to add the parent element into the pom 334 if (parentGAV != null) 335 { 336 String ps = Resources.toString(Resources.getResource("pom_xml_parent.xml"), Charsets.UTF_8); 337 final String[] splits = parentGAV.split(":"); 338 ps = Generator.replaceCodes(splits[0], splits[1], splits[2], ps); 339 s = s.replaceAll("\\{!p!\\}", ps); 340 } 341 else { 342 s = s.replaceAll("\\{!p!\\}", ""); 343 } 344 345 FileUtils.writeStringToFile(new File(targetDir, "pom.xml"), s, "UTF-8"); 346 } catch (final IOException e) 347 { 348 e.printStackTrace(); 349 } 350 } 351 352 /** 353 * Replaces GAV codes in the given string. 354 * 355 * @param groupId 356 * The group identifier 357 * @param artifactId 358 * The artifact identifier 359 * @param versionNumber 360 * The version number 361 * @param s 362 * The string to replace them in 363 * @return The string with the codes replaced 364 */ 365 public static String replaceCodes(final String groupId, 366 final String artifactId, final String versionNumber, String s) 367 { 368 s = s.replaceAll("\\{!g!\\}", groupId); 369 s = s.replaceAll("\\{!a!\\}", artifactId); 370 s = s.replaceAll("\\{!v!\\}", versionNumber); 371 return s; 372 } 373 374 /** 375 * Returns the Java type name for a given URI 376 * 377 * @param s 378 * The URI 379 * @return The Java type name 380 */ 381 protected static String getTypeName(final URI s) 382 { 383 return s.getLocalName().substring(0, 1).toUpperCase() + s.getLocalName().substring(1); 384 } 385 386 /** 387 * Generate the classes for the RDF information that's read from the given 388 * input stream. 389 * 390 * @param is 391 * The input stream to find the RDF description 392 * @param go 393 * The options for the generator 394 * @throws RepositoryException 395 * If the repository cannot be created 396 * @throws IOException 397 * If the InputStream cannot be read 398 * @throws RDFParseException 399 * If the RDF is malformed 400 * @throws QueryEvaluationException 401 * If the query for classes fails 402 * @throws MalformedQueryException 403 * If the query for classes fails 404 */ 405 public static void generate(final InputStream is, final GeneratorOptions go) 406 throws RepositoryException, RDFParseException, IOException, 407 MalformedQueryException, QueryEvaluationException 408 { 409 // If we're going to create a Maven project, we'll put all the source 410 // files into a src directory, so we need to alter the target directory. 411 if (go.mavenProject != null && !go.skipPom) 412 { 413 // Create the pom.xml file 414 Generator.createPOM(new File(go.targetDirectory), go.mavenProject, 415 go.mavenArtifactId, go.mavenVersionNumber, go.mavenParent); 416 417 go.targetDirectory = go.targetDirectory + 418 File.separator + "src" + File.separator + "main" + 419 File.separator + "java"; 420 new File(go.targetDirectory).mkdirs(); 421 } 422 423 // Create a new memory store into which we'll plonk all the RDF 424 final Repository repository = new SailRepository(new MemoryStore()); 425 repository.initialize(); 426 427 // Plonk all the RDF into the store 428 final RepositoryConnection conn = repository.getConnection(); 429 conn.add(is, "", RDFFormat.RDFXML); 430 431 // Now we'll get all the classes from the ontology 432 final Map<URI, ClassDef> classes = ClassDef.loadClasses(go, conn); 433 434 // Try to generate the package mappings for the classes 435 final Map<URI, String> pkgs = Generator.generatePackageMappings( 436 classes.values()); 437 438 // Now we'll go through each of the class definitions and generate 439 // interfaces and classes 440 for (final ClassDef cd : classes.values()) 441 { 442 cd.generateInterface(new File(go.targetDirectory), pkgs, classes); 443 cd.generateClass(new File(go.targetDirectory), pkgs, classes, 444 go.flattenClassStructure, go.generateAnnotations, 445 go.separateImplementations); 446 } 447 } 448 449 /** 450 * 451 * @param args 452 * @throws RepositoryException 453 */ 454 public static void main(final String[] args) throws RepositoryException 455 { 456 final GeneratorOptions go = new GeneratorOptions(); 457 final CmdLineParser parser = new CmdLineParser(go); 458 459 try 460 { 461 parser.parseArgument(args); 462 } catch (final CmdLineException e) 463 { 464 System.err.println(e.getMessage()); 465 System.err.println("java Generator RDF-FILE TARGET-DIR [options]"); 466 parser.printUsage(System.err); 467 System.exit(1); 468 } 469 470 final File rdfFile = new File(go.rdfFile); 471 final File targetDir = new File(go.targetDirectory); 472 473 if (!rdfFile.exists()) 474 { 475 System.out.println("The RDF file does not exist: " + rdfFile); 476 System.exit(1); 477 } 478 479 if (!targetDir.exists()) 480 { 481 System.out.println("The target directory does not exist: " 482 + targetDir); 483 System.exit(1); 484 } 485 486 try 487 { 488 Generator.generate(new FileInputStream(rdfFile), go); 489 } catch (final Exception e) 490 { 491 e.printStackTrace(); 492 } 493 } 494}