/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 /////////////// package jena; import static org.apache.jena.atlas.logging.LogCtl.setCmdLogging; import java.io.ByteArrayOutputStream ; import java.io.File ; import java.io.FileOutputStream ; import java.io.PrintStream ; import java.net.MalformedURLException ; import java.net.URL ; import java.text.SimpleDateFormat ; import java.util.* ; import java.util.regex.Pattern ; import java.util.regex.PatternSyntaxException ; import org.apache.jena.ontology.Individual ; import org.apache.jena.ontology.OntModel ; import org.apache.jena.ontology.OntModelSpec ; import org.apache.jena.rdf.model.* ; import org.apache.jena.shared.JenaException ; import org.apache.jena.util.FileManager ; import org.apache.jena.util.iterator.ExtendedIterator ; import org.apache.jena.util.iterator.WrappedIterator ; import org.apache.jena.vocabulary.OWL ; import org.apache.jena.vocabulary.RDF ; import org.apache.jena.vocabulary.RDFS ; import org.apache.jena.vocabulary.XSD ; import org.apache.xerces.util.XMLChar ; /** * <p> * A vocabulary generator, that will consume an ontology or other vocabulary file, * and generate a Java file with the constants from the vocabulary compiled in. * Designed to be highly flexible and customisable. * </p> */ public class schemagen { static { setCmdLogging(); } // Constants ////////////////////////////////// /** The namespace for the configuration model is {@value} */ public static final String NS = "http://jena.hpl.hp.com/2003/04/schemagen#"; /** The default location of the configuration model is {@value} */ public static final String DEFAULT_CONFIG_URI = "file:schemagen.rdf"; /** The default marker string for denoting substitutions is {@value} */ public static final String DEFAULT_MARKER = "%"; /** Default template for writing out value declarations */ public static final String DEFAULT_TEMPLATE = "public static final %valclass% %valname% = M_MODEL.%valcreator%( \"%valuri%\" );"; /** Default template for writing out individual declarations */ public static final String DEFAULT_INDIVIDUAL_TEMPLATE = "public static final %valclass% %valname% = M_MODEL.%valcreator%( \"%valuri%\", %valtype% );"; /** Default template for writing out individual declarations for non-ontology vocabularies */ public static final String DEFAULT_RDFS_INDIVIDUAL_TEMPLATE = "public static final %valclass% %valname% = M_MODEL.%valcreator%( \"%valuri%\" );"; /** Default template for the file header */ public static final String DEFAULT_HEADER_TEMPLATE = "/* CVS $" + "Id: $ */%nl%%package% %nl%%imports% %nl%/**%nl% * Vocabulary definitions from %sourceURI% %nl% * @author Auto-generated by schemagen on %date% %nl% */"; /** Default line length for comments before wrap */ public static final int COMMENT_LENGTH_LIMIT = 80; /** List of Java reserved keywords, see <a href="http://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html">this list</a>. */ public static final String[] JAVA_KEYWORDS = { "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", "long", "strictfp", "volatile", "const", "float", "native", "super", "while" }; // Static variables ////////////////////////////////// private static List<String> KEYWORD_LIST; static { KEYWORD_LIST = Arrays.asList( JAVA_KEYWORDS ); } // Instance variables ////////////////////////////////// /** Schemagen options interface */ protected SchemagenOptions m_options; /** The model that contains the input source */ protected OntModel m_source; /** The output stream we write to */ protected PrintStream m_output; /** Option definitions */ /** Stack of replacements to apply */ protected List<Replacement> m_replacements = new ArrayList<>(); /** Output file newline char - default is Unix, override with --dos */ protected String m_nl = "\n"; /** Size of indent step */ protected int m_indentStep = 4; /** Set of names used so far */ protected Set<String> m_usedNames = new HashSet<>(); /** Map from resources to java names */ protected Map<Resource,String> m_resourcesToNames = new HashMap<>(); /** List of allowed namespace URI strings for admissible values */ protected List<String> m_includeURI = new ArrayList<>(); // Constructors ////////////////////////////////// // External signature methods ////////////////////////////////// /* Main entry point. See Javadoc for details of the many command line arguments */ public static void main( String... args ) { try { new schemagen().go( args ); } catch (SchemagenException e) { System.err.println( "Schemagen failed to run:" ); System.err.println( e.getMessage() ); if (e.getCause() != null) { System.err.println( "Caused by: " + e.getCause().getMessage() ); } System.exit( 1 ); } } // Internal implementation methods ////////////////////////////////// /** Read the configuration parameters and do setup */ protected void go( String[] args ) { go( new SchemagenOptionsImpl( args ) ); } /** Handle initial configuration options, then initiate processing */ protected void go( SchemagenOptions options ) { m_options = options; // check for user requesting help if (m_options.hasHelpOption()) { usage(); } // got the configuration, now we can begin processing processInput(); } /** The sequence of steps to process an entire file */ protected void processInput() { addIncludes(); determineLanguage(); selectInput(); selectOutput(); setGlobalReplacements(); processHeader(); writeClassDeclaration(); writeInitialDeclarations(); writeProperties(); writeClasses(); writeIndividuals(); writeDatatypes(); writeClassClose(); processFooter(); closeOutput(); } /** Add the included files */ protected void addIncludes() { // add any extra uri's that are allowed in the filter m_includeURI.addAll( m_options.getIncludeOption() ); } /** Create the source model after determining which input language */ protected void determineLanguage() { OntModelSpec s = null; if (m_options.hasLangRdfsOption()) { // RDFS language specified if (m_options.hasUseInfOption()) { s = OntModelSpec.RDFS_MEM_RDFS_INF; } else { s = OntModelSpec.RDFS_MEM; } } else { // owl is the default // s = OntModelSpec.getDefaultSpec( ProfileRegistry.OWL_LANG ); if (m_options.hasUseInfOption()) { s = OntModelSpec.OWL_MEM_RULE_INF; } else { s = OntModelSpec.OWL_MEM; } } m_source = ModelFactory.createOntologyModel( s, null ); m_source.getDocumentManager().setProcessImports( false ); // turn off strict checking on request if (m_options.hasNoStrictOption()) { m_source.setStrictMode( false ); } } /** Identify the URL that is to be read in and translated to a vocabulary file, and load the source into the source model */ protected void selectInput() { if (!m_options.hasInputOption()) { usage(); } String input = SchemagenUtils.urlCheck( m_options.getInputOption().getURI() ); String syntax = m_options.getEncodingOption(); try { FileManager.get().readModel( m_source, input, syntax ); } catch (JenaException e) { abort( "Failed to read input source " + input, e ); } } /** Identify the file we are to write the output to */ protected void selectOutput() { String outFile = m_options.getOutputOption(); if (outFile == null) { m_output = System.out; } else { try { // check for package name String packageName = m_options.getPackagenameOption(); if (packageName != null) { String packagePath = ""; // build the package path (e.g. com.foo.bar -> /com/foo/bar) for (String p: packageName.split( "\\." )) { packagePath = packagePath + File.separator + p; } if (!outFile.endsWith( packagePath )) { outFile = outFile + packagePath; } } File out = new File( outFile ); if (!out.exists() && !outFile.endsWith( ".java" )) { // create the directory if needed out.mkdirs(); } if (out.isDirectory()) { // create a file in this directory named classname.java String fileName = outFile + File.separator + getClassName() + ".java"; out = new File( fileName ); } m_output = new PrintStream( new FileOutputStream( out ) ); } catch (Exception e) { abort( "I/O error while trying to open file for writing: " + outFile, e ); } } // check for DOS line endings if (m_options.hasDosOption()) { m_nl = "\r\n"; } } /** Process the header at the start of the file, if defined */ protected void processHeader() { String header = m_options.hasHeaderOption() ? m_options.getHeaderOption() : DEFAULT_HEADER_TEMPLATE; // user can turn of header processing, default is to have it on if (!m_options.hasNoheaderOption()) { writeln( 0, substitute( header ) ); } else { // we have to do the imports at least writeln( 0, "import org.apache.jena.rdf.model.*;" ); if (m_options.hasOntologyOption()) { writeln( 0, "import org.apache.jena.ontology.*;" ); } if (m_options.hasIncludeSourceOption()) { writeln( 0, "import java.io.ByteArrayInputStream;" ); } } } /** Process the footer at the end of the file, if defined */ protected void processFooter() { String footer = m_options.getFooterOption(); if (footer != null) { writeln( 0, substitute( footer ) ); } } /** The list of replacements that are always available */ protected void setGlobalReplacements() { addReplacementPattern( "date", new SimpleDateFormat( "dd MMM yyyy HH:mm").format( new Date() ) ); addReplacementPattern( "package", m_options.hasPackagenameOption() ? ("package " + m_options.getPackagenameOption() + ";") : "" ); addReplacementPattern( "imports", getImports() ); addReplacementPattern( "classname", getClassName() ); addReplacementPattern( "nl", m_nl ); // protect \ in Windows file pathnames // looks for file:.* or C:.* (or variants thereof) String source = m_options.getInputOption().getURI(); if (source.matches( "(file:|[A-Za-z]:).*$" )) { source = source.replace( "\\", "\\\\" ); } addReplacementPattern( "sourceURI", source ); } /** Add a pattern-value pair to the list of available patterns */ protected void addReplacementPattern( String key, String replacement ) { if (replacement != null && key != null) { String marker = m_options.getMarkerOption(); marker = (marker == null) ? DEFAULT_MARKER : marker; try { m_replacements.add( new Replacement( Pattern.compile( marker + key + marker ), replacement ) ); } catch (PatternSyntaxException e) { abort( "Malformed regexp pattern " + marker + key + marker, e ); } } } /** Pop n replacements off the stack */ protected void pop( int n ) { for (int i = 0; i < n; i++) { m_replacements.remove( m_replacements.size() - 1 ); } } /** Close the output file */ protected void closeOutput() { m_output.flush(); m_output.close(); } /** Abort due to exception */ protected void abort( String msg, Exception cause ) { throw new SchemagenException( msg, cause ); } /** Print usage message and abort */ protected void usage() { System.err.println( "Usage:" ); System.err.println( " java jena.schemagen [options ...]" ); System.err.println(); System.err.println( "Commonly used options include:" ); System.err.println( " -i <input> the source document as a file or URL." ); System.err.println( " -n <name> the name of the created Java class." ); System.err.println( " -a <uri> the namespace URI of the source document." ); System.err.println( " -o <file> the file to write the generated class into." ); System.err.println( " -o <dir> the directory in which the generated Java class is created." ); System.err.println( " By default, output goes to stdout." ); System.err.println( " -e <encoding> the encoding of the input document (N3, RDF/XML, etc)." ); System.err.println( " -c <config> a filename or URL for an RDF document containing " ); System.err.println( " configuration parameters." ); System.err.println(); System.err.println( "Many other options are available. See the schemagen HOWTO in the " ); System.err.println( "Jena documentation for full details." ); System.exit( 1 ); } /** Use the current replacements list to do the subs in the given string */ protected String substitute( String sIn ) { String s = sIn; for ( Replacement r : m_replacements ) { s = r.pattern.matcher( s ).replaceAll( r.sub ); } return s; } /** Add the appropriate indent to a buffer */ protected int indentTo( int i, StringBuffer buf ) { int indent = i * m_indentStep; for (int j = 0; j < indent; j++) { buf.append( ' ' ); } return indent; } /** Write a blank line, with indent and newline */ protected void writeln( int indent ) { writeln( indent, "" ); } /** Write out the given string with n spaces of indent, with newline */ protected void writeln( int indent, String s ) { write( indent, s ); m_output.print( m_nl ); } /** Write out the given string with n spaces of indent */ protected void write( int indentLevel, String s ) { for (int i = 0; i < (m_indentStep * indentLevel); i++) { m_output.print( " " ); } m_output.print( s ); } /** Determine the list of imports to include in the file */ protected String getImports() { StringBuffer buf = new StringBuffer(); buf.append( "import org.apache.jena.rdf.model.*;" ); buf.append( m_nl ); if (useOntology()) { buf.append( "import org.apache.jena.ontology.*;" ); buf.append( m_nl ); } if (includeSource()) { buf.append( "import java.io.ByteArrayInputStream;" ); buf.append( m_nl ); } return buf.toString(); } /** Determine the class name of the vocabulary from the URI */ protected String getClassName() { // if a class name is given, just use that if (m_options.hasClassnameOption()) { return m_options.getClassnameOption(); } // otherwise, we generate a name based on the URI String uri = m_options.getInputOption().getURI(); // remove any suffixes uri = (uri.endsWith( "#" )) ? uri.substring( 0, uri.length() - 1 ) : uri; uri = (uri.endsWith( ".daml" )) ? uri.substring( 0, uri.length() - 5 ) : uri; uri = (uri.endsWith( ".owl" )) ? uri.substring( 0, uri.length() - 4 ) : uri; uri = (uri.endsWith( ".rdf" )) ? uri.substring( 0, uri.length() - 4 ) : uri; uri = (uri.endsWith( ".rdfs" )) ? uri.substring( 0, uri.length() - 5 ) : uri; uri = (uri.endsWith( ".n3" )) ? uri.substring( 0, uri.length() - 3 ) : uri; uri = (uri.endsWith( ".xml" )) ? uri.substring( 0, uri.length() - 4 ) : uri; uri = (uri.endsWith( ".ttl" )) ? uri.substring( 0, uri.length() - 4 ) : uri; // now work back to the first non name character from the end int i = uri.length() - 1; for (; i > 0; i--) { if (!Character.isUnicodeIdentifierPart( uri.charAt( i ) ) && uri.charAt( i ) != '-') { i++; break; } } String name = uri.substring( i ); // optionally add name suffix if (m_options.hasClassnameSuffixOption()) { name = name + m_options.getClassnameSuffixOption(); } // now we make the name into a legal Java identifier return asLegalJavaID( name, true ); } /** Answer true if we are using ontology terms in this vocabulary */ protected boolean useOntology() { return m_options.hasOntologyOption(); } /** Answer true if all comments are suppressed */ protected boolean noComments() { return m_options.hasNoCommentsOption(); } /** Answer true if ontology source code is to be included */ protected boolean includeSource() { return m_options.hasIncludeSourceOption(); } /** Converts to a legal Java identifier; capitalise first char if cap is true */ protected String asLegalJavaID( String s, boolean cap ) { StringBuffer buf = new StringBuffer(); int i = 0; // treat the first character specially - must be able to start a Java ID, may have to up-case try { for (; !Character.isJavaIdentifierStart( s.charAt( i )); i++) { /**/ } } catch (StringIndexOutOfBoundsException e) { System.err.println( "Could not identify legal Java identifier start character in '" + s + "', replacing with __" ); return "__"; } buf.append( cap ? Character.toUpperCase( s.charAt( i ) ) : s.charAt( i ) ); // copy the remaining characters - replace non-legal chars with '_' for (++i; i < s.length(); i++) { char c = s.charAt( i ); buf.append( Character.isJavaIdentifierPart( c ) ? c : '_' ); } // check for illegal keyword if (KEYWORD_LIST.contains( buf.toString() )) { buf.append( '_' ); } return buf.toString(); } /** The opening class declaration */ protected void writeClassDeclaration() { write( 0, "public class " ); write( 0, getClassName() ); write( 0, " " ); if (m_options.hasClassdecOption()) { write( 0, m_options.getClassdecOption() ); } writeln( 0, "{" ); } /** The close of the class decoration */ protected void writeClassClose() { writeln( 0, "}" ); } /** Write the declarations at the head of the class */ protected void writeInitialDeclarations() { writeModelDeclaration(); writeSource(); writeNamespace(); writeOntologyVersionInfo(); if (m_options.hasDeclarationsOption()) { writeln( 0, m_options.getDeclarationsOption() ); } } /** Write the declaration of the model */ protected void writeModelDeclaration() { if (useOntology()) { String lang = "OWL"; if (m_options.hasLangRdfsOption()) { lang = "RDFS"; } writeln( 1, "/** <p>The ontology model that holds the vocabulary terms</p> */" ); writeln( 1, "private static final OntModel M_MODEL = ModelFactory.createOntologyModel( OntModelSpec." + lang + "_MEM, null );" ); } else { writeln( 1, "/** <p>The RDF model that holds the vocabulary terms</p> */" ); writeln( 1, "private static final Model M_MODEL = ModelFactory.createDefaultModel();" ); } writeln( 1 ); } /** Write the source code of the input model into the file itself */ protected void writeSource() { if (includeSource()) { // first save a copy of the source in compact form into a buffer ByteArrayOutputStream bos = new ByteArrayOutputStream(); RDFWriter rw = m_source.getWriter( "Turtle" ); rw.setProperty( "objectLists", Boolean.FALSE.toString() ); rw.write( m_source, bos, null ); String output = bos.toString(); // now we embed each line of the source in the output writeln( 1, "private static final String SOURCE = " ); boolean first = true; StringTokenizer st = new StringTokenizer( output, "\n" ); while (st.hasMoreTokens()) { String tok = st.nextToken(); if (tok.endsWith( "\r" )) { tok = tok.substring( 0, tok.length() - 1 ); } write( 2, first ? " " : " + " ); write( 0, "\"" ); write( 0, protectQuotes( tok ) ); writeln( 2, "\\n\"" ); first = false; } // then we reference the string constant when reading the source // note that we avoid StringReader due to charset encoding issues writeln( 1, ";" ); writeln( 0, "" ); writeln( 1, "/** Read the ontology definition into the source model */ " ); writeln( 1, "static { " ); writeln( 2, "M_MODEL.read( new ByteArrayInputStream( SOURCE.getBytes() ), null, \"N3\" );" ); writeln( 1, "}" ); writeln( 0, "" ); } } /** Protect any double quotes in the given string so that it's a legal Java String */ private String protectQuotes( String s ) { return s.replaceAll( "\\\\", "\\\\\\\\" ).replaceAll( "\"", "\\\\\"" ); } /** Write owl:versionInfo string if it exists **/ protected void writeOntologyVersionInfo() { String versionInfo = getOntologyElementVersionInfo(); if (null != versionInfo) { writeln( 1, "/** <p>The ontology's owl:versionInfo as a string</p> */" ); writeln( 1, "public static final String VERSION_INFO = \"" + protectQuotes(versionInfo) + "\";" ); writeln( 1 ); } } /** Write the string and resource that represent the namespace */ protected void writeNamespace() { String nsURI = determineNamespaceURI(); writeln( 1, "/** <p>The namespace of the vocabulary as a string</p> */" ); writeln( 1, "public static final String NS = \"" + nsURI + "\";" ); writeln( 1 ); writeln( 1, "/** <p>The namespace of the vocabulary as a string</p>" ); writeln( 1, " * @return namespace as String" ); writeln( 1, " * @see #NS */" ); writeln( 1, "public static String getURI() {return NS;}" ); writeln( 1 ); writeln( 1, "/** <p>The namespace of the vocabulary as a resource</p> */" ); writeln( 1, "public static final Resource NAMESPACE = M_MODEL.createResource( NS );" ); writeln( 1 ); } /** Determine what the namespace URI for this vocabulary is */ protected String determineNamespaceURI() { // we have a sequence of strategies for determining the ontology namespace String ns = getOptionNamespace(); if (ns == null) { ns = getDefaultPrefixNamespace(); } if (ns == null) { ns = getOntologyElementNamespace(); } if (ns == null) { ns = guessNamespace(); } // did we get one? if (ns == null) { abort( "Could not determine the base URI for the input vocabulary", null ); } m_includeURI.add( ns ); return ns; } /** User has set namespace via a schemagen option */ protected String getOptionNamespace() { return m_options.hasNamespaceOption() ? m_options.getNamespaceOption().getURI() : null; } /** Document has set an empty prefix for the model */ protected String getDefaultPrefixNamespace() { // alternatively, the default namespace may be set in the prefix mapping read from the input document String defaultNS = m_source.getNsPrefixURI( "" ); if (defaultNS == null) { defaultNS = m_source.getBaseModel().getNsPrefixURI( "" ); } return defaultNS; } /** Document has an owl:Ontology or daml:Ontology element */ protected String getOntologyElementVersionInfo() { String versionInfo = null; Resource ontologyClass = m_source.getProfile().ONTOLOGY(); if (null != ontologyClass) { StmtIterator i = m_source.getBaseModel().listStatements( null, RDF.type, ontologyClass ); if (i.hasNext()) { Resource ont = i.nextStatement().getSubject(); StmtIterator j = m_source.getBaseModel().listStatements( ont, OWL.versionInfo, (RDFNode)null ); if (j.hasNext()) { versionInfo = j.nextStatement().getObject().asLiteral().getLexicalForm(); // check for ambiguous answers if (j.hasNext()) { System.err.println( "Warning: ambiguous owl:versionInfo - there are more than one owl:versionInfo statements." ); System.err.println( "Picking first choice: " + versionInfo + ". Other choices are:" ); while (j.hasNext()) { System.err.print( " " ); System.err.print( j.nextStatement().getObject().toString() ); } System.err.println(); } } // check for ambiguous answers if (i.hasNext()) { System.err.println( "Warning: ambiguous owl:versionInfo - there is more than one owl:Ontology element." ); System.err.println( "Picking first choice: " + ont.getURI() + ". Other choices are:" ); while (i.hasNext()) { System.err.print( " " ); System.err.print( i.nextStatement().getObject().toString() ); } System.err.println(); } } } return versionInfo; } /** Document has an owl:Ontology or daml:Ontology element */ protected String getOntologyElementNamespace() { // if we are using an ontology model, we can get the namespace URI from the ontology element String uri = null; StmtIterator i = m_source.getBaseModel() .listStatements( null, RDF.type, m_source.getProfile().ONTOLOGY() ); if (i.hasNext()) { Resource ont = i.nextStatement().getSubject(); uri = ont.getURI(); // ensure ends with namespace separator char char ch = uri.charAt( uri.length() - 1 ); boolean endsWithNCNameCh = XMLChar.isNCName( ch ); uri = endsWithNCNameCh ? uri + "#" : uri; // check for ambiguous answers if (i.hasNext()) { System.err.println( "Warning: ambiguous default namespace - there is more than one owl:Ontology element." ); System.err.println( "Picking first choice: " + uri + ". Other choices are:" ); while (i.hasNext()) { System.err.print( " " ); System.err.print( i.nextStatement().getString() ); } System.err.println(); System.err.println( "Use the -a option to specify a particular namespace if required." ); } } return uri; } /** Guess the URI from the most prevalent URI */ protected String guessNamespace() { Map<String,Integer> nsCount = new HashMap<>(); // count all of the namespaces used in the model for (StmtIterator i = m_source.listStatements(); i.hasNext(); ) { Statement s = i.next(); countNamespace( s.getSubject(), nsCount ); countNamespace( s.getPredicate(), nsCount ); if (s.getObject().isResource()) { countNamespace( s.getResource(), nsCount ); } } // now find the maximal element String ns = null; int max = 0; for ( String nsKey : nsCount.keySet() ) { // we ignore the usual suspects if ( !( OWL.getURI().equals( nsKey ) || RDF.getURI().equals( nsKey ) || RDFS.getURI().equals( nsKey ) || XSD.getURI().equals( nsKey ) ) ) { // not an ignorable namespace int count = nsCount.get( nsKey ).intValue(); if ( count > max ) { // highest count seen so far max = count; ns = nsKey; } } } return ns; } /** Record a use of the given namespace in the count map */ private void countNamespace( Resource r, Map<String,Integer> nsCount ) { if (!r.isAnon()) { String ns = r.getNameSpace(); // increment the count for this namespace Integer count = nsCount.containsKey( ns ) ? (Integer) nsCount.get( ns ) : new Integer( 0 ); Integer count1 = new Integer( count.intValue() + 1 ); nsCount.put( ns, count1 ); } } /** Write the list of properties */ protected void writeProperties() { if (m_options.hasNopropertiesOption()) { return; } if (m_options.hasPropertySectionOption()) { writeln( 0, m_options.getPropertySectionOption()); } if (useOntology()) { writeObjectProperties(); writeDatatypeProperties(); writeAnnotationProperties(); // we also write out the RDF properties, to mop up any props that are not stated as // object, datatype or annotation properties writeRDFProperties( true ); } else { writeRDFProperties( false ); } } /** Write any object properties in the vocabulary */ protected void writeObjectProperties() { String template = m_options.hasPropTemplateOption() ? m_options.getPropTemplateOption() : DEFAULT_TEMPLATE; if (!m_options.hasLangRdfsOption()) { for (Iterator<? extends RDFNode> i = sorted( m_source.listObjectProperties() ); i.hasNext(); ) { writeValue( (Resource) i.next(), template, "ObjectProperty", "createObjectProperty", "_PROP" ); } } } /** Write any datatype properties in the vocabulary */ protected void writeDatatypeProperties() { String template = m_options.hasPropTemplateOption() ? m_options.getPropTemplateOption() : DEFAULT_TEMPLATE; if (!m_options.hasLangRdfsOption()) { for (Iterator<? extends RDFNode> i = sorted( m_source.listDatatypeProperties() ); i.hasNext(); ) { writeValue( (Resource) i.next(), template, "DatatypeProperty", "createDatatypeProperty", "_PROP" ); } } } /** Write any annotation properties in the vocabulary */ protected void writeAnnotationProperties() { String template = m_options.hasPropTemplateOption() ? m_options.getPropTemplateOption() : DEFAULT_TEMPLATE; if (!m_options.hasLangRdfsOption()) { for (Iterator<? extends RDFNode> i = sorted( m_source.listAnnotationProperties() ); i.hasNext(); ) { writeValue( (Resource) i.next(), template, "AnnotationProperty", "createAnnotationProperty", "_PROP" ); } } } /** Write any vanilla RDF properties in the vocabulary */ protected void writeRDFProperties( boolean useOntProperty ) { String template = m_options.hasPropTemplateOption() ? m_options.getPropTemplateOption() : DEFAULT_TEMPLATE; String propType = useOntProperty ? "OntProperty" : "Property"; // select the appropriate properties based on the language choice Resource[] props; if (m_options.hasLangOwlOption()) { props = new Resource[] {OWL.ObjectProperty, OWL.DatatypeProperty, RDF.Property}; } else { props = new Resource[] {RDF.Property}; } // collect the properties to be written List<Resource> propertyResources = new ArrayList<>(); for ( Resource prop : props ) { for ( StmtIterator i = m_source.listStatements( null, RDF.type, prop ); i.hasNext(); ) { propertyResources.add( i.nextStatement().getSubject() ); } } // now write the properties for (Iterator<? extends RDFNode> i = sorted( propertyResources ); i.hasNext(); ) { writeValue( (Resource) i.next(), template, propType, "create" + propType, "_PROP" ); } } /** Write any classes in the vocabulary */ protected void writeClasses() { if (m_options.hasNoclassesOption()) { return; } if (m_options.hasClassSectionOption()) { writeln( 0, m_options.getClassSectionOption()); } if (useOntology()) { writeOntClasses(); } else { writeRDFClasses(); } } /** Write classes as ontology terms */ protected void writeOntClasses() { String template = m_options.hasClassTemplateOption() ? m_options.getClassTemplateOption() : DEFAULT_TEMPLATE; for (Iterator<? extends RDFNode> i = sorted( m_source.listClasses() ); i.hasNext(); ) { writeValue( (Resource) i.next(), template, "OntClass", "createClass", "_CLASS" ); } } /** Write classes as vanilla RDF terms */ protected void writeRDFClasses() { String template = m_options.hasClassTemplateOption() ? m_options.getClassTemplateOption() : DEFAULT_TEMPLATE; // make sure we're looking for the appropriate type of class Resource cls = OWL.Class; if (m_options.hasLangRdfsOption()) { cls = RDFS.Class; } // collect the classes to list List<Resource> classes = m_source.listStatements( null, RDF.type, cls ).mapWith( s -> s.getSubject()).toList(); for (Iterator<? extends RDFNode> i = sorted( classes ); i.hasNext(); ) { writeValue( (Resource) i.next(), template, "Resource", "createResource", "_CLASS" ); } } /** Write any instances (individuals) in the vocabulary */ protected void writeIndividuals() { if (m_options.hasNoindividualsOption()) { return; } if (m_options.hasIndividualsSectionOption()) { writeln( 0, m_options.getIndividualsSectionOption() ); } if (useOntology()) { writeOntIndividuals(); } else { writeRDFIndividuals(); } } /** Write individuals as ontology terms */ protected void writeOntIndividuals() { String template = m_options.hasIndividualTemplateOption() ? m_options.getIndividualTemplateOption() : DEFAULT_INDIVIDUAL_TEMPLATE; for (Iterator<? extends RDFNode> i = selectIndividuals(); i.hasNext(); ) { Individual ind = ((Resource) i.next()).as( Individual.class ); // do we have a local class resource Resource cls = ind.getOntClass(); if (cls == null) { cls = OWL.Thing; } String varName = m_resourcesToNames.get( cls ); String valType = (varName != null) ? varName : "M_MODEL.createClass( \"" + cls.getURI() + "\" )"; // push the individuals type onto the stack addReplacementPattern( "valtype", valType ); writeValue( ind, template, "Individual", "createIndividual", "_INSTANCE" ); pop( 1 ); } } /** Write individuals as vanilla RDF terms */ protected void writeRDFIndividuals() { String template = m_options.hasIndividualTemplateOption() ? m_options.getIndividualTemplateOption() : DEFAULT_RDFS_INDIVIDUAL_TEMPLATE; for (Iterator<? extends RDFNode> i = selectIndividuals(); i.hasNext(); ) { writeValue( (Resource) i.next(), template, "Resource", "createResource", "_INSTANCE" ); } } /** Answer an iterator over the individuals selected for output */ protected ExtendedIterator<? extends RDFNode> selectIndividuals() { List<Resource> candidates = new ArrayList<>(); for (StmtIterator i = m_source.listStatements( null, RDF.type, (RDFNode) null ); i.hasNext(); ) { Statement candidate = i.nextStatement(); if (candidate.getObject().isResource()) { Resource candObj = candidate.getResource(); Resource candSubj = candidate.getSubject(); // ignore RDFS and OWL builtins if (!candObj.isAnon()) { String candTypeURI = candObj.getURI(); if (candTypeURI.startsWith( RDF.getURI() ) || candTypeURI.startsWith( OWL.getURI() ) || candTypeURI.startsWith( RDFS.getURI() )) { continue; } } // note that whether candSubj is included is tested later on by {@link #filter} if (!candSubj.isAnon() && (isIncluded( candObj ) || isIncluded( candSubj )) && !candidates.contains( candSubj )) { candidates.add( candSubj ); } } } return sorted( candidates ); } /** * Answer true if the given resource is accepted for presentation in the output, which * is true iff it is a URI node, whose namespace is one of the accepted namespaces in * {@link #m_includeURI}. * @param r A resource to test * @return True if the resource is to be included in the generated output */ protected boolean isIncluded( Resource r ) { boolean accepted = false; if (!r.isAnon()) { String uri = r.getURI(); for (Iterator<String> j = m_includeURI.iterator(); !accepted && j.hasNext(); ) { accepted = uri.startsWith( j.next() ); } } return accepted; } /** Write any datatypes in the vocabulary */ protected void writeDatatypes() { if (m_options.hasNodatatypesOption()) { return; } if (m_options.hasDatatypesSectionOption()) { writeln( 0, m_options.getDatatypesSectionOption() ); } String template = m_options.hasDatatypeTemplateOption() ? m_options.getDatatypeTemplateOption() : DEFAULT_TEMPLATE; // Cannot create a full RDFDatatype object since we don't know how to parse these custom types, but we can at least specify a Resource for (Iterator<? extends RDFNode> i = selectDatatypes(); i.hasNext(); ) { writeValue( (Resource) i.next(), template, "Resource", "createResource", "_DATATYPE" ); } } /** Answer an iterator over the datatypes selected for output */ protected ExtendedIterator<? extends RDFNode> selectDatatypes() { List<Resource> candidates = new ArrayList<>(); for (StmtIterator i = m_source.listStatements( null, RDF.type, RDFS.Datatype ); i.hasNext(); ) { Statement candidate = i.nextStatement(); if (candidate.getObject().isResource()) { Resource candSubj = candidate.getSubject(); // ignore XSD builtins if (!candSubj.isAnon()) { String candTypeURI = candSubj.getURI(); if (candTypeURI.startsWith( XSD.getURI() )) { continue; } } // note that whether candSubj is included is tested later on by {@link #filter} if (!candSubj.isAnon() && !candidates.contains( candSubj )) { candidates.add( candSubj ); } } } return sorted( candidates ); } /** Write the value declaration out using the given template, optionally creating comments */ protected void writeValue( Resource r, String template, String valueClass, String creator, String disambiguator ) { if (!filter( r )) { if (!noComments() && hasComment( r )) { writeln( 1, formatComment( getComment( r ) ) ); } // push the local bindings for the substitution onto the stack addReplacementPattern( "valuri", r.getURI() ); addReplacementPattern( "valname", getValueName( r, disambiguator )); addReplacementPattern( "valclass", valueClass ); addReplacementPattern( "valcreator", creator ); // write out the value writeln( 1, substitute( template ) ); writeln( 1 ); // pop the local replacements off the stack pop( 4 ); } } /** Answer true if the given resource has an rdf:comment */ protected boolean hasComment( Resource r ) { return r.hasProperty( RDFS.comment ) ; } /** Answer all of the commentary on the given resource, as a string */ protected String getComment( Resource r ) { StringBuffer comment = new StringBuffer(); // collect any RDFS or DAML comments attached to the node for (NodeIterator ni = m_source.listObjectsOfProperty( r, RDFS.comment ); ni.hasNext(); ) { RDFNode n = ni.nextNode(); if (n instanceof Literal) { comment.append( ((Literal) n).getLexicalForm().trim() ); } else { System.err.println( "Warning: Comment on resource <" + r.getURI() + "> is not a literal: " + n ); } } return comment.toString(); } /** Format the comment as Javadoc, and limit the line width */ protected String formatComment( String comment ) { StringBuffer buf = new StringBuffer(); buf.append( "/** <p>" ); boolean inSpace = false; int pos = buf.length(); boolean singleLine = true; // now format the comment by compacting whitespace and limiting the line length // add the prefix to the start of each line for (int i = 0; i < comment.length(); i++ ) { char c = comment.charAt( i ); // compress whitespace if (Character.isWhitespace( c )) { if (inSpace) { continue; // more than one space is ignored } else { c = ' '; // map all whitespace to 0x20 inSpace = true; } } else { inSpace = false; } // escapes? if (c == '\\') { c = comment.charAt( ++i ); switch (c) { case 'n': buf.append( m_nl ); pos = indentTo( 1, buf ); buf.append( " * " ); pos += 3; singleLine = false; break; default: // add other escape sequences above break; } } else if (c == '<') { buf.append( "<" ); pos += 4; } else if (c == '>') { buf.append( ">" ); pos += 4; } else if (c == '&') { buf.append( "&" ); pos += 5; } else { // add the char buf.append( c ); pos++; } // wrap any very long lines at 120 chars if ((pos > COMMENT_LENGTH_LIMIT) && (inSpace)) { buf.append( m_nl ); pos = indentTo( 1, buf ); buf.append( " * " ); pos += 3; singleLine = false; } } buf.append( "</p>" ); buf.append( singleLine ? "" : m_nl ); indentTo( singleLine ? 0 : 1, buf ); buf.append( " */" ); return buf.toString(); } /** Answer true if resource r <b>does not</b> show in output */ protected boolean filter( Resource r ) { if (r.isAnon()) { return true; } // if we've already processed this resource once, ignore it next time if (m_resourcesToNames.containsKey( r )) { return true; } // search the allowed URI's for ( String uri : m_includeURI ) { if ( r.getURI().startsWith( uri ) ) { // in return false; } } // we allow individuals whose class is not in the included NS's, unless opt strict-individuals is true */ if (!m_options.hasStrictIndividualsOption()) { for (StmtIterator j = r.listProperties( RDF.type ); j.hasNext(); ) { // we search the rdf:types of this resource Resource typeRes = j.nextStatement().getResource(); if (!typeRes.isAnon()) { String typeURI = typeRes.getURI(); // for any type that is in a permitted NS for ( String uri : m_includeURI ) { if ( typeURI.startsWith( uri ) ) { // in return false; } } } } } // default is out return true; } /** Answer the Java value name for the URI */ protected String getValueName( Resource r, String disambiguator ) { // the id name is basically the local name of the resource, possibly in upper case String name = m_options.hasUcNamesOption() ? getUCValueName( r ) : r.getLocalName(); // must be legal java name = asLegalJavaID( name, false ); // must not clash with an existing name int attempt = 0; String baseName = name; while (m_usedNames.contains( name )) { name = (attempt == 0) ? (name + disambiguator) : (baseName + disambiguator + attempt); attempt++; } // record this name so that we don't use it again (which will stop the vocabulary from compiling) m_usedNames.add( name ); // record the mapping from resource to name m_resourcesToNames.put( r, name ); return name; } /** Answer the local name of resource r mapped to upper case */ protected String getUCValueName( Resource r ) { StringBuffer buf = new StringBuffer(); String localName = r.getLocalName(); char lastChar = 0; for (int i = 0; i < localName.length(); i++) { char c = localName.charAt(i); if (Character.isLowerCase(lastChar) && Character.isUpperCase(c)) { buf.append( '_' ); } buf.append( Character.toUpperCase(c) ); lastChar = c; } return buf.toString(); } /** Answer an iterator that contains the elements of the given list, but sorted by URI */ protected ExtendedIterator<? extends RDFNode> sorted( ExtendedIterator<? extends RDFNode> i ) { return sorted( i.toList() ); } /** Answer an iterator that contains the elements of the given iterator, but sorted by URI */ protected ExtendedIterator<? extends RDFNode> sorted( List<? extends RDFNode> members ) { Collections.sort( members, new Comparator<RDFNode>() { @Override public int compare( RDFNode n0, RDFNode n1 ) { if (n0.isLiteral() || n1.isLiteral()) { if (n0.isLiteral() && n1.isLiteral()) { // two literals Literal l0 = (Literal) n0; Literal l1 = (Literal) n1; return l0.getLexicalForm().compareTo( l1.getLexicalForm() ); } else { return n0.isLiteral() ? -1 : 1; } } else { Resource r0 = (Resource) n0; Resource r1 = (Resource) n1; if (r0.isAnon() && r1.isAnon()) { // two anonID's - the order is important as long as its consistent return r0.getId().toString().compareTo( r1.getId().toString() ); } else if (r0.isAnon()) { return -1; } else if (r1.isAnon()) { return 1; } else { // two named resources return r0.getURI().compareTo( r1.getURI() ); } } }} ); return WrappedIterator.create( members.iterator() ); } //============================================================================== // Inner class definitions //============================================================================== public interface SchemagenOptions { /* Constants for the various options we can set */ public enum OPT { /** Select an alternative config file; use <code>-c <filename></code> on command line */ CONFIG_FILE, /** Turn off all comment output; use <code>--nocomments</code> on command line; use <code>sgen:noComments</code> in config file */ NO_COMMENTS, /** Nominate the URL of the input document; use <code>-i <URL></code> on command line; use <code>sgen:input</code> in config file */ INPUT, /** Specify that the language of the source is DAML+OIL; use <code>--daml</code> on command line; use <code>sgen:daml</code> in config file */ LANG_DAML, /** Specify that the language of the source is OWL (the default); use <code>--owl</code> on command line; use <code>sgen:owl</code> in config file */ LANG_OWL, /** Specify that the language of the source is RDFS; use <code>--rdfs</code> on command line; use <code>sgen:rdfs</code> in config file */ LANG_RDFS, /** Specify that destination file; use <code>-o <fileName></code> on command line; use <code>sgen:output</code> in config file */ OUTPUT, /** Specify the file header; use <code>--header "..."</code> on command line; use <code>sgen:header</code> in config file */ HEADER, /** Specify the file footer; use <code>--footer "..."</code> on command line; use <code>sgen:footer</code> in config file */ FOOTER, /** Specify the uri of the configuration root node; use <code>--root <URL></code> on command line */ ROOT, /** Specify the marker string for substitutions, default is '%'; use <code>-m "..."</code> on command line; use <code>sgen:marker</code> in config file */ MARKER, /** Specify the packagename; use <code>--package <packagename></code> on command line; use <code>sgen:package</code> in config file */ PACKAGENAME, /** Use ontology terms in preference to vanilla RDF; use <code>--ontology</code> on command line; use <code>sgen:ontology</code> in config file */ ONTOLOGY, /** The name of the generated class; use <code>-n <classname></code> on command line; use <code>sgen:classname</code> in config file */ CLASSNAME, /** Additional decoration for class header (such as implements); use <code>--classdec <classname></code> on command line; use <code>sgen:classdec</code> in config file */ CLASSDEC, /** The namespace URI for the vocabulary; use <code>-a <uri></code> on command line; use <code>sgen:namespace</code> in config file */ NAMESPACE, /** Additional declarations to add at the top of the class; use <code>--declarations <...></code> on command line; use <code>sgen:declarations</code> in config file */ DECLARATIONS, /** Section declaration for properties section; use <code>--propSection <...></code> on command line; use <code>sgen:propSection</code> in config file */ PROPERTY_SECTION, /** Section declaration for class section; use <code>--classSection <...></code> on command line; use <code>sgen:classSection</code> in config file */ CLASS_SECTION, /** Section declaration for individuals section; use <code>--individualsSection <...></code> on command line; use <code>sgen:individualsSection</code> in config file */ INDIVIDUALS_SECTION, /** Section declaration for datatypes section; use <code>--datatypesSection <...></code> on command line; use <code>sgen:datatypesSection</code> in config file */ DATATYPES_SECTION, /** Option to suppress properties in vocab file; use <code>--noproperties <...></code> on command line; use <code>sgen:noproperties</code> in config file */ NOPROPERTIES, /** Option to suppress classes in vocab file; use <code>--noclasses <...></code> on command line; use <code>sgen:noclasses</code> in config file */ NOCLASSES, /** Option to suppress individuals in vocab file; use <code>--noindividuals <...></code> on command line; use <code>sgen:noindividuals</code> in config file */ NOINDIVIDUALS, /** Option to suppress datatypes in vocab file; use <code>--nodatatypes <...></code> on command line; use <code>sgen:nodatatypes</code> in config file */ NODATATYPES, /** Option for no file header; use <code>--noheader <...></code> on command line; use <code>sgen:noheader</code> in config file */ NOHEADER, /** Template for writing out property declarations; use <code>--propTemplate <...></code> on command line; use <code>sgen:propTemplate</code> in config file */ PROP_TEMPLATE, /** Template for writing out class declarations; use <code>--classTemplate <...></code> on command line; use <code>sgen:classTemplate</code> in config file */ CLASS_TEMPLATE, /** Template for writing out individual declarations; use <code>--individualTemplate <...></code> on command line; use <code>sgen:individualTemplate</code> in config file */ INDIVIDUAL_TEMPLATE, /** Template for writing out datatype declarations; use <code>--datatypeTemplate <...></code> on command line; use <code>sgen:datatypeTemplate</code> in config file */ DATATYPE_TEMPLATE, /** Option for mapping constant names to uppercase; use <code>--uppercase <...></code> on command line; use <code>sgen:uppercase</code> in config file */ UC_NAMES, /** Option for including non-local URI's in vocabulary; use <code>--include <uri></code> on command line; use <code>sgen:include</code> in config file */ INCLUDE, /** Option for adding a suffix to the generated class name; use <code>--classnamesuffix <uri></code> on command line; use <code>sgen:classnamesuffix</code> in config file */ CLASSNAME_SUFFIX, /** Option for the presentation syntax (encoding) of the file; use <code>-e <i>encoding</i></code> on command line; use <code>sgen:encoding</code> in config file */ ENCODING, /** Option to show the usage message; use --help on command line */ HELP, /** Option to generate an output file with DOS (\r\n) line endings. Default is Unix line endings. */ DOS, /** Option to generate to force the model to perform inference, off by default. */ USE_INF, /** Option to exclude instances of classes in the allowed namespaces, where the individuals themselves are in other namespaces; use <code>--strictIndividuals</code> on command line; use <code>sgen:strictIndividuals</code> in config file */ STRICT_INDIVIDUALS, /** Option to include the ontology source code in the generated file */ INCLUDE_SOURCE, /** Option to turn off strict checking in .a() */ NO_STRICT } public static final Object[][] m_optionDefinitions = new Object[][] { {OPT.CONFIG_FILE, new OptionDefinition( "-c", "configFile" ) }, {OPT.ROOT, new OptionDefinition( "-r", "root" ) }, {OPT.NO_COMMENTS, new OptionDefinition( "--nocomments", "noComments" ) }, {OPT.INPUT, new OptionDefinition( "-i", "input" ) }, {OPT.LANG_DAML, new OptionDefinition( "--daml", "daml" ) }, {OPT.LANG_OWL, new OptionDefinition( "--owl", "owl" ) }, {OPT.LANG_RDFS, new OptionDefinition( "--rdfs", "rdfs" ) }, {OPT.OUTPUT, new OptionDefinition( "-o", "output" ) }, {OPT.HEADER, new OptionDefinition( "--header", "header" ) }, {OPT.FOOTER, new OptionDefinition( "--footer", "footer" ) }, {OPT.MARKER, new OptionDefinition( "--marker", "marker" ) }, {OPT.PACKAGENAME, new OptionDefinition( "--package", "package" ) }, {OPT.ONTOLOGY, new OptionDefinition( "--ontology", "ontology" ) }, {OPT.CLASSNAME, new OptionDefinition( "-n", "classname" ) }, {OPT.CLASSDEC, new OptionDefinition( "--classdec", "classdec" ) }, {OPT.NAMESPACE, new OptionDefinition( "-a", "namespace" ) }, {OPT.DECLARATIONS, new OptionDefinition( "--declarations", "declarations" ) }, {OPT.PROPERTY_SECTION, new OptionDefinition( "--propSection", "propSection" ) }, {OPT.CLASS_SECTION, new OptionDefinition( "--classSection", "classSection" ) }, {OPT.INDIVIDUALS_SECTION, new OptionDefinition( "--individualsSection", "individualsSection" ) }, {OPT.DATATYPES_SECTION, new OptionDefinition( "--datatypesSection", "datatypesSection" ) }, {OPT.NOPROPERTIES, new OptionDefinition( "--noproperties", "noproperties" ) }, {OPT.NOCLASSES, new OptionDefinition( "--noclasses", "noclasses" ) }, {OPT.NOINDIVIDUALS, new OptionDefinition( "--noindividuals", "noindividuals" ) }, {OPT.NODATATYPES, new OptionDefinition( "--nodatatypes", "nodatatypes" ) }, {OPT.PROP_TEMPLATE, new OptionDefinition( "--propTemplate", "propTemplate" ) }, {OPT.CLASS_TEMPLATE, new OptionDefinition( "--classTemplate", "classTemplate" ) }, {OPT.INDIVIDUAL_TEMPLATE, new OptionDefinition( "--individualTemplate", "individualTemplate" ) }, {OPT.DATATYPE_TEMPLATE, new OptionDefinition( "--datatypeTemplate", "datatypeTemplate" ) }, {OPT.UC_NAMES, new OptionDefinition( "--uppercase", "uppercase" ) }, {OPT.INCLUDE, new OptionDefinition( "--include", "include" ) }, {OPT.CLASSNAME_SUFFIX, new OptionDefinition( "--classnamesuffix", "classnamesuffix" )}, {OPT.NOHEADER, new OptionDefinition( "--noheader", "noheader" )}, {OPT.ENCODING, new OptionDefinition( "-e", "encoding" )}, {OPT.HELP, new OptionDefinition( "--help", "help" )}, {OPT.DOS, new OptionDefinition( "--dos", "dos" )}, {OPT.USE_INF, new OptionDefinition( "--inference", "inference" )}, {OPT.STRICT_INDIVIDUALS, new OptionDefinition( "--strictIndividuals", "strictIndividuals" )}, {OPT.INCLUDE_SOURCE, new OptionDefinition( "--includeSource", "includeSource" )}, {OPT.NO_STRICT, new OptionDefinition( "--nostrict", "noStrict")}, }; public boolean hasConfigFileOption(); public String getConfigFileOption(); public boolean hasRootOption(); public String getRootOption(); public boolean hasNoCommentsOption(); public String getNoCommentsOption(); public boolean hasInputOption(); public Resource getInputOption(); public boolean hasLangOwlOption(); public String getLangOwlOption(); public boolean hasLangRdfsOption(); public String getLangRdfsOption(); public boolean hasOutputOption(); public String getOutputOption(); public boolean hasHeaderOption(); public String getHeaderOption(); public boolean hasFooterOption(); public String getFooterOption(); public boolean hasMarkerOption(); public String getMarkerOption(); public boolean hasPackagenameOption(); public String getPackagenameOption(); public boolean hasOntologyOption(); public String getOntologyOption(); public boolean hasClassnameOption(); public String getClassnameOption(); public boolean hasClassdecOption(); public String getClassdecOption(); public boolean hasNamespaceOption(); public Resource getNamespaceOption(); public boolean hasDeclarationsOption(); public String getDeclarationsOption(); public boolean hasPropertySectionOption(); public String getPropertySectionOption(); public boolean hasClassSectionOption(); public String getClassSectionOption(); public boolean hasIndividualsSectionOption(); public String getIndividualsSectionOption(); public boolean hasDatatypesSectionOption(); public String getDatatypesSectionOption(); public boolean hasNopropertiesOption(); public boolean hasNoclassesOption(); public boolean hasNoindividualsOption(); public boolean hasNodatatypesOption(); public boolean hasPropTemplateOption(); public String getPropTemplateOption(); public boolean hasClassTemplateOption(); public String getClassTemplateOption(); public boolean hasIndividualTemplateOption(); public String getIndividualTemplateOption(); public boolean hasDatatypeTemplateOption(); public String getDatatypeTemplateOption(); public boolean hasUcNamesOption(); public boolean hasIncludeOption(); public List<String> getIncludeOption(); public boolean hasClassnameSuffixOption(); public String getClassnameSuffixOption(); public boolean hasNoheaderOption(); public boolean hasEncodingOption(); public String getEncodingOption(); public boolean hasHelpOption(); public String getHelpOption(); public boolean hasDosOption(); public boolean hasUseInfOption(); public boolean hasStrictIndividualsOption(); public boolean hasIncludeSourceOption(); public boolean hasNoStrictOption(); } public static class SchemagenOptionsImpl implements SchemagenOptions { // Instance variables /** The list of command line arguments */ private List<String> m_cmdLineArgs = new ArrayList<>(); /** The root of the options in the config file */ private Resource m_root; /** The model that contains the configuration information */ private Model m_config = ModelFactory.createDefaultModel(); // Constructor public SchemagenOptionsImpl( String[] args ) { m_cmdLineArgs = Arrays.asList( args ); // check to see if there's a specified config file String configURL = DEFAULT_CONFIG_URI; if (hasConfigFileOption()) { // check for protocol; add file: if not specified configURL = SchemagenUtils.urlCheck( getConfigFileOption() ); } // try to read the config URI try { FileManager.get().readModel( m_config, configURL ); } catch (Exception e) { // if the user left the default config URI in place, it's not an error to fail to read it if (!configURL.equals( DEFAULT_CONFIG_URI )) { throw new SchemagenException( "Failed to read configuration from URL: " + configURL, e ); } } // ensure we have a root URI for the configuration model determineConfigRoot(); } /** * Return the configuration model used to hold config information * @return Model */ protected Model getConfigModel() { return m_config; } /** * Return the root resource to which configuration information is attached * @return Resource */ protected Resource getConfigRoot() { if (m_root == null) { determineConfigRoot(); } return m_root; } // Internal implementation methods /** Determine the root resource in the configuration file */ protected void determineConfigRoot() { if (hasValue( OPT.ROOT )) { m_root = m_config.getResource( getStringValue( OPT.ROOT ) ); } else { // no specified root, we assume there is only one with type sgen:Config StmtIterator i = m_config.listStatements( null, RDF.type, m_config.getResource( NS + "Config" ) ); if (i.hasNext()) { m_root = i.nextStatement().getSubject(); } else { // no configuration root, so we invent one m_root = m_config.createResource(); } } } /** Answer true if the given option is set to true */ protected boolean isTrue( OPT option ) { return getOpt( option ).isTrue( m_cmdLineArgs, m_root ); } /** Answer true if the given option has value */ protected boolean hasValue( OPT option ) { return getOpt( option ).hasValue( m_cmdLineArgs, m_root ); } /** Answer the value of the option or null */ protected RDFNode getValue( OPT option ) { return getOpt( option ).getValue( m_cmdLineArgs, m_root ); } /** Answer the String value of the option or null */ protected String getStringValue( OPT option ) { return getOpt( option ).getStringValue( m_cmdLineArgs, m_root ); } /** Answer true if the given option has a resource value */ protected boolean hasResourceValue( OPT option ) { return getOpt( option ).hasResourceValue( m_cmdLineArgs, m_root ); } /** Answer the value of the option or null */ protected Resource getResource( OPT option ) { return getOpt( option ).getResource( m_cmdLineArgs, m_root ); } /** Answer all values for the given options as Strings */ protected List<String> getAllValues( OPT option ) { List<String> values = new ArrayList<>(); OptionDefinition opt = getOpt( option ); // look in the command line arguments for (Iterator<String> i = m_cmdLineArgs.iterator(); i.hasNext(); ) { String s = i.next(); if (s.equals( opt.m_cmdLineForm )) { // next iterator value is the arg value values.add( i.next() ); } } // now look in the config file for (StmtIterator i = m_root.listProperties( opt.m_prop ); i.hasNext(); ) { Statement s = i.nextStatement(); if (s.getObject() instanceof Literal) { values.add( s.getString() ); } else { values.add( s.getResource().getURI() ); } } return values; } /** Answer the option object for the given option */ protected OptionDefinition getOpt( OPT option ) { for ( Object[] m_optionDefinition : m_optionDefinitions ) { if ( m_optionDefinition[0] == option ) { return (OptionDefinition) m_optionDefinition[1]; } } return null; } // External interface methods @Override public boolean hasConfigFileOption() { return hasValue( OPT.CONFIG_FILE ); } @Override public String getConfigFileOption() { return getStringValue( OPT.CONFIG_FILE ); } @Override public boolean hasRootOption() { return hasValue( OPT.ROOT ); } @Override public String getRootOption() { return getStringValue( OPT.ROOT ); } @Override public boolean hasNoCommentsOption() { return isTrue( OPT.NO_COMMENTS ); } @Override public String getNoCommentsOption() { return getStringValue( OPT.NO_COMMENTS ); } @Override public boolean hasInputOption() { return hasValue( OPT.INPUT ); } @Override public Resource getInputOption() { return getResource( OPT.INPUT ); } @Override public boolean hasLangOwlOption() { return isTrue( OPT.LANG_OWL ); } @Override public String getLangOwlOption() { return getStringValue( OPT.LANG_OWL ); } @Override public boolean hasLangRdfsOption() { return isTrue( OPT.LANG_RDFS ); } @Override public String getLangRdfsOption() { return getStringValue( OPT.LANG_RDFS ); } @Override public boolean hasOutputOption() { return hasValue( OPT.OUTPUT ); } @Override public String getOutputOption() { return getStringValue( OPT.OUTPUT ); } @Override public boolean hasHeaderOption() { return isTrue( OPT.HEADER ); } @Override public String getHeaderOption() { return getStringValue( OPT.HEADER ); } @Override public boolean hasFooterOption() { return isTrue( OPT.FOOTER ); } @Override public String getFooterOption() { return getStringValue( OPT.FOOTER ); } @Override public boolean hasMarkerOption() { return hasValue( OPT.MARKER ); } @Override public String getMarkerOption() { return getStringValue( OPT.MARKER ); } @Override public boolean hasPackagenameOption() { return hasValue( OPT.PACKAGENAME ); } @Override public String getPackagenameOption() { return getStringValue( OPT.PACKAGENAME ); } @Override public boolean hasOntologyOption() { return isTrue( OPT.ONTOLOGY ); } @Override public String getOntologyOption() { return getStringValue( OPT.ONTOLOGY ); } @Override public boolean hasClassnameOption() { return hasValue( OPT.CLASSNAME ); } @Override public String getClassnameOption() { return getStringValue( OPT.CLASSNAME ); } @Override public boolean hasClassdecOption() { return hasValue( OPT.CLASSDEC ); } @Override public String getClassdecOption() { return getStringValue( OPT.CLASSDEC ); } @Override public boolean hasNamespaceOption() { return hasValue( OPT.NAMESPACE ); } @Override public Resource getNamespaceOption() { return getResource( OPT.NAMESPACE ); } @Override public boolean hasDeclarationsOption() { return hasValue( OPT.DECLARATIONS ); } @Override public String getDeclarationsOption() { return getStringValue( OPT.DECLARATIONS ); } @Override public boolean hasPropertySectionOption() { return hasValue( OPT.PROPERTY_SECTION ); } @Override public String getPropertySectionOption() { return getStringValue( OPT.PROPERTY_SECTION ); } @Override public boolean hasClassSectionOption() { return hasValue( OPT.CLASS_SECTION ); } @Override public String getClassSectionOption() { return getStringValue( OPT.CLASS_SECTION ); } @Override public boolean hasIndividualsSectionOption() { return hasValue( OPT.INDIVIDUALS_SECTION ); } @Override public String getIndividualsSectionOption() { return getStringValue( OPT.INDIVIDUALS_SECTION ); } @Override public boolean hasDatatypesSectionOption() { return hasValue( OPT.DATATYPES_SECTION ); } @Override public String getDatatypesSectionOption() { return getStringValue( OPT.DATATYPES_SECTION ); } @Override public boolean hasNopropertiesOption() { return isTrue( OPT.NOPROPERTIES ); } @Override public boolean hasNoclassesOption() { return isTrue( OPT.NOCLASSES ); } @Override public boolean hasNoindividualsOption() { return isTrue( OPT.NOINDIVIDUALS ); } @Override public boolean hasNodatatypesOption() { return isTrue( OPT.NODATATYPES ); } @Override public boolean hasPropTemplateOption() { return hasValue( OPT.PROP_TEMPLATE ); } @Override public String getPropTemplateOption() { return getStringValue( OPT.PROP_TEMPLATE ); } @Override public boolean hasClassTemplateOption() { return hasValue( OPT.CLASS_TEMPLATE ); } @Override public String getClassTemplateOption() { return getStringValue( OPT.CLASS_TEMPLATE ); } @Override public boolean hasIndividualTemplateOption() { return hasValue( OPT.INDIVIDUAL_TEMPLATE ); } @Override public String getIndividualTemplateOption() { return getStringValue( OPT.INDIVIDUAL_TEMPLATE ); } @Override public boolean hasDatatypeTemplateOption() { return hasValue( OPT.DATATYPE_TEMPLATE ); } @Override public String getDatatypeTemplateOption() { return getStringValue( OPT.DATATYPE_TEMPLATE ); } @Override public boolean hasUcNamesOption() { return isTrue( OPT.UC_NAMES ); } @Override public boolean hasIncludeOption() { return hasValue( OPT.INCLUDE ); } @Override public List<String> getIncludeOption() { return getAllValues( OPT.INCLUDE ); } @Override public boolean hasClassnameSuffixOption() { return hasValue( OPT.CLASSNAME_SUFFIX ); } @Override public String getClassnameSuffixOption() { return getStringValue( OPT.CLASSNAME_SUFFIX ); } @Override public boolean hasNoheaderOption() { return isTrue( OPT.NOHEADER ); } @Override public boolean hasEncodingOption() { return hasValue( OPT.ENCODING ); } @Override public String getEncodingOption() { return getStringValue( OPT.ENCODING ); } @Override public boolean hasHelpOption() { return hasValue( OPT.HELP ); } @Override public String getHelpOption() { return getStringValue( OPT.HELP ); } @Override public boolean hasDosOption() { return isTrue( OPT.DOS ); } @Override public boolean hasUseInfOption() { return isTrue( OPT.USE_INF ); } @Override public boolean hasStrictIndividualsOption() { return isTrue( OPT.STRICT_INDIVIDUALS ); } @Override public boolean hasIncludeSourceOption() { return isTrue( OPT.INCLUDE_SOURCE ); } @Override public boolean hasNoStrictOption() { return isTrue( OPT.NO_STRICT ); } } /** An option that can be set either on the command line or in the RDF config */ public static class OptionDefinition { protected String m_cmdLineForm; protected Property m_prop; protected OptionDefinition( String cmdLineForm, String name ) { m_cmdLineForm = cmdLineForm; if (name != null) { m_prop = ResourceFactory.createProperty( NS, name ); } } /** * Return the RDF property that is used when configuring this option * via a {@link Model} * @return The declaration property, or null */ public Property getDeclarationProperty() { return m_prop; } /** * Return the command line form of this option * @return The command line form as a String */ public String getCommandLineForm() { return m_cmdLineForm; } /** * Answer true if this option is set to true, either on the command line * or in the config model * * @return boolean */ protected boolean isTrue( List<String> cmdLineArgs, Resource confRoot ) { if (cmdLineArgs.contains( m_cmdLineForm )) { return true; } if (confRoot.hasProperty( m_prop )) { return confRoot.getRequiredProperty( m_prop ).getBoolean(); } return false; } /** * Answer the string value of the parameter if set, or null otherwise. Note command line * has precedence. * * @return String */ protected String getStringValue( List<String> cmdLineArgs, Resource confRoot ) { RDFNode n = getValue( cmdLineArgs, confRoot ); return (n == null) ? null : (n.isLiteral() ? n.asLiteral().getLexicalForm() : n.toString() ); } /** * Return the value of the parameter if set, or null otherwise. Note command line * has precedence. * * @return The argument value as an RDFNode */ protected RDFNode getValue( List<String> cmdLineArgs, Resource confRoot ) { int index = cmdLineArgs.indexOf( m_cmdLineForm ); if (index >= 0) { try { return ResourceFactory.createPlainLiteral( cmdLineArgs.get( index + 1 ) ); } catch (IndexOutOfBoundsException e) { throw new SchemagenException( "Value for parameter " + m_cmdLineForm + " not set! Aborting.", e ); } } if (m_prop != null && confRoot != null && confRoot.hasProperty( m_prop )) { return confRoot.getRequiredProperty( m_prop ).getObject(); } // not set return null; } /** * Answer true if the parameter has a value at all. * * @return boolean */ protected boolean hasValue( List<String> cmdLineArgs, Resource confRoot ) { return getValue( cmdLineArgs, confRoot ) != null; } /** * Answer the resource value of the parameter if set, or null otherwise. * * @return String */ protected Resource getResource( List<String> cmdLineArgs, Resource confRoot ) { int index = cmdLineArgs.indexOf( m_cmdLineForm ); if (index >= 0) { try { return confRoot.getModel().getResource( cmdLineArgs.get( index + 1 ) ); } catch (IndexOutOfBoundsException e) { System.err.println( "Value for parameter " + m_cmdLineForm + " not set! Aborting."); } } if (m_prop != null && confRoot.hasProperty( m_prop )) { return confRoot.getRequiredProperty( m_prop ).getResource(); } // not set return null; } /** * Answer true if the parameter has a value at all. * * @return boolean */ protected boolean hasResourceValue( List<String> cmdLineArgs, Resource confRoot ) { return getResource( cmdLineArgs, confRoot ) != null; } } // end inner class OptionDefinition /** A pairing of pattern and substitution we want to apply to output */ protected class Replacement { protected String sub; protected Pattern pattern; protected Replacement( Pattern pattern, String sub) { this.sub = sub; this.pattern = pattern; } } // end inner class Replacement /** * <p>Schemagen runtime exception</p> */ public static class SchemagenException extends RuntimeException { public SchemagenException( String msg, Throwable cause ) { super( msg, cause ); } } /** Utility method container */ public static class SchemagenUtils { /** Return a URI formed from the given string, unchanged if it's already a URI or * converted to a file URI otherwise. If not recognisable as a URL, abort. */ public static String urlCheck( String uriOrFile ) { boolean legal = true; String url = uriOrFile; // is it a URI already? to check, we make a URL and see what happens! try { new URL( url ); } catch (MalformedURLException ignore) { legal = false; } // if not a legal url, assume it's a file if (!legal) { legal = true; String slash = System.getProperty( "file.separator" ); url = "file:" + (uriOrFile.startsWith( slash ) ? (slash + slash) : "") + uriOrFile; try { new URL( url ); } catch (MalformedURLException ignore) { legal = false; } } if (!legal) { throw new SchemagenException( "Could not parse " + uriOrFile + " as a legal URL or a file reference. Aborting.", null ); } return url; } } /* End class SchemagenUtils */ }