/*
* 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 org.apache.jena.assembler;
import java.lang.reflect.Constructor ;
import java.lang.reflect.Method ;
import java.util.ArrayList ;
import java.util.HashSet ;
import java.util.List ;
import java.util.Set ;
import org.apache.jena.assembler.assemblers.AssemblerGroup ;
import org.apache.jena.assembler.exceptions.AmbiguousSpecificTypeException ;
import org.apache.jena.atlas.logging.Log ;
import org.apache.jena.datatypes.xsd.XSDDatatype ;
import org.apache.jena.rdf.model.* ;
import org.apache.jena.shared.BadDescriptionMultipleRootsException ;
import org.apache.jena.shared.BadDescriptionNoRootException ;
import org.apache.jena.shared.JenaException ;
import org.apache.jena.shared.PrefixMapping ;
import org.apache.jena.vocabulary.RDF ;
import org.apache.jena.vocabulary.RDFS ;
/**
AssemblerHelp provides utility methods used by, and useful for working with,
the Assembler code, including the methods that expand a model to include the
required inferences and to find the most specific type of a root in an
assembler specification.
*/
public class AssemblerHelp
{
/**
A useful constant for <code>listStatements(S, P, O)</code>.
*/
protected static final Property ANY = null;
/**
Answer a Resource .equals() to <code>root</code>, but in the expanded
model.
*/
public static Resource withFullModel( Resource root )
{ return root.inModel( fullModel( root.getModel() ) ); }
/**
Answer the full model of <code>m</code>, with all its imports included and
with the necessary properties added from the JA schema. However, if
the magic footprint triple (ja:this, rdf:type, ja:Expanded) is present in the
model, it is returned unchanged. Imports are managed by the shared
<code>ImportManager.instance</code>.
*/
public static Model fullModel( Model m )
{ return fullModel( ImportManager.instance, m ); }
/**
Answer the full model of <code>m</code>, with all its imports included and
with the necessary properties added from the JA schema. However, if
the magic footprint triple (ja:this, rdf:type, ja:Expanded) is present in the
model, it is returned unchanged. Imports are managed by <code>im</code>.
*/
public static Model fullModel( ImportManager im, Model m )
{
return m.contains( JA.This, RDF.type, JA.Expanded )
? m
: (Model) ModelExpansion.withSchema( im.withImports( m ), JA.getSchema() )
.add( JA.This, RDF.type, JA.Expanded )
.setNsPrefixes( PrefixMapping.Extended )
.setNsPrefixes( m )
;
}
/**
Load all the classes which are objects of any (t, ja:loadClass, S)
statements in <code>m</code>. The order in which the classes are
loaded is not specified, and loading stops immediately if any class
cannot be loaded.
<p>
Contrast with <code>loadClasses(AssemblerGroup,Model)</code>,
which loads classes and assumes that those classes are assemblers to
be added to the group.
*/
public static void loadArbitraryClasses( AssemblerGroup g, Model m )
{
StmtIterator it = m.listStatements( null, JA.loadClass, ANY );
while (it.hasNext()) loadArbitraryClass( g, it.nextStatement() );
}
/**
Load all the classes which are objects of any (t, ja:assembler, S) statements
in <code>m</code>. <code>group.implementWIth(t,c)</code> is called
for each statement, where <code>c</code> is an instance of the class named
by <code>S</code>. The order in which the classes are loaded is not
specified, and loading stops immediately if any class cannot be loaded.
*/
public static void loadAssemblerClasses( AssemblerGroup group, Model m )
{
StmtIterator it = m.listStatements( ANY, JA.assembler, ANY );
while (it.hasNext()) loadAssemblerClass( group, it.nextStatement() );
}
/**
Load the class named by the object of <code>s</code>, run its
<code>whenRequiredByAssembler</code> method if any, and
register an <code>implementWith</code> for the subject of
<code>s</code> and an instance of the class.
*/
private static void loadAssemblerClass( AssemblerGroup group, Statement s )
{
Class<?> c = loadArbitraryClass( group, s );
runAnyAssemblerConstructor( group, s, c );
}
/**
Load the class named by the object of <code>s</code> if necessary.
If that class has a static method <code>whenRequiredByAssembler</code>
with an <code>AssemblerGroup</code> argument, call that method
passing it <code>ag</code>.
*/
private static Class<?> loadArbitraryClass( AssemblerGroup ag, Statement s )
{
Class<?> loaded = loadClassNamedBy( s );
try
{
Method m = loaded.getDeclaredMethod( "whenRequiredByAssembler", new Class[] {AssemblerGroup.class} );
m.invoke( null, ag );
}
catch (NoSuchMethodException e)
{ /* that's OK */ }
catch (Exception e)
{ throw new JenaException( e ); }
return loaded;
}
private static Class<?> loadClassNamedBy( Statement s )
{
String x = getString( s ) ;
// Jena2 -> Jena3 transition
if ( x.startsWith("com.hp.hpl.jena") ) {
String x1 = x.replaceFirst("com.hp.hpl.jena", "org.apache.jena") ;
Log.warnOnce(AssemblerHelp.class, "ja:loadClass: Migration to Jena3: Converting "+x+" to "+x1, x) ;
x = x1 ;
}
try { return Class.forName(x); }
catch (Exception e) { throw new JenaException( e ); }
}
private static void runAnyAssemblerConstructor( AssemblerGroup group, Statement s, Class<?> c )
{
try
{
Resource type = s.getSubject();
Constructor<?> con = getResourcedConstructor( c );
if (con == null)
establish( group, type, c.newInstance() );
else
establish( group, type, con.newInstance( s.getSubject() ) );
}
catch (Exception e)
{ throw new JenaException( e ); }
}
private static void establish( AssemblerGroup group, Resource type, Object x )
{
if (x instanceof Assembler)
group.implementWith( type, (Assembler) x );
else
throw new JenaException( "constructed entity is not an Assembler: " + x );
}
private static Constructor<?> getResourcedConstructor( Class<?> c )
{
try { return c.getConstructor( new Class[] { Resource.class } ); }
catch (SecurityException e) { return null; }
catch (NoSuchMethodException e) { return null; }
}
/**
Answer the most specific type of <code>root</code> that is a subclass of
ja:Object. If there are no candidate types, answer <code>givenType</code>.
If there is more than one type, throw a NoSpecificTypeException.
*/
public static Resource findSpecificType( Resource root )
{ return findSpecificType( root, JA.Object ); }
/**
Answer the most specific type of <code>root</code> that is a subclass of
<code>givenType</code>. If there are no candidate types, answer
<code>givenType</code>. If there is more than one type, throw a
NoSpecificTypeException.
*/
public static Resource findSpecificType( Resource root, Resource baseType )
{
Set<Resource> types = findSpecificTypes( root, baseType );
if (types.size() == 1)
return types.iterator().next();
if (types.size() == 0)
return baseType;
throw new AmbiguousSpecificTypeException( root, new ArrayList<>( types ) );
}
/**
Answer all the types of <code>root</code> which are subtypes of
<code>baseType</code> and which do not have subtypes which are
also types of <code>root</code>.
*/
public static Set<Resource> findSpecificTypes( Resource root, Resource baseType )
{
List<RDFNode> types = root.listProperties( RDF.type ).mapWith( Statement::getObject ).toList();
Set<Resource> results = new HashSet<>();
for (int i = 0; i < types.size(); i += 1)
{
Resource candidate = (Resource) types.get( i );
if (candidate.hasProperty( RDFS.subClassOf, baseType ))
if (hasNoCompetingSubclass( types, candidate ))
results.add( candidate );
}
return results;
}
private static boolean hasNoCompetingSubclass( List<RDFNode> types, Resource candidate )
{
for ( RDFNode type : types )
{
Resource other = (Resource) type;
if ( other.hasProperty( RDFS.subClassOf, candidate ) && !candidate.equals( other ) )
{
return false;
}
}
return true;
}
/**
Answer the resource that is the object of the statement <code>s</code>. If
the object is not a resource, throw a BadObjectException with that statement.
*/
public static Resource getResource( Statement s )
{
RDFNode ob = s.getObject();
if (ob.isLiteral()) throw new BadObjectException( s );
return (Resource) ob;
}
/**
Answer the plain string object of the statement <code>s</code>. If the
object is not a string literal, throw a BadObjectException with that statement.
*/
public static String getString( Statement s )
{
RDFNode ob = s.getObject();
if (ob.isResource()) throw new BadObjectException( s );
Literal L = (Literal) ob;
if (!L.getLanguage().equals( "" )) throw new BadObjectException( s );
if (L.getDatatype() == null) return L.getLexicalForm();
if (L.getDatatype() == XSDDatatype.XSDstring) return L.getLexicalForm();
throw new BadObjectException( s );
}
/**
Answer the String value of the literal <code>L</code>, which is the
object of the Statement <code>s</code>. If the literal is not an
XSD String or a plain string without a language code, throw a
BadObjectException.
*/
public static String getString( Statement s, Literal L )
{
if (!L.getLanguage().equals( "" )) throw new BadObjectException( s );
if (L.getDatatype() == null) return L.getLexicalForm();
if (L.getDatatype() == XSDDatatype.XSDstring) return L.getLexicalForm();
throw new BadObjectException( s );
}
/**
Answer a Set of the ja:Object resources in the full expansion of
the assembler specification model <code>model</code>.
*/
public static Set<Resource> findAssemblerRoots( Model model )
{ return findAssemblerRoots( model, JA.Object ); }
/**
Answer a Set of the objects in the full expansion of the assembler
specification <code>model</code> which have rdf:type <code>type</code>,
which <i>must</i> be a subtype of <code>ja:Object</code>.
*/
public static Set<Resource> findAssemblerRoots( Model model, Resource type )
{ return fullModel( model ).listResourcesWithProperty( RDF.type, type ).toSet(); }
/**
Answer the single resource in <code>singleRoot</code> of type
<code>ja:Model</code>. Otherwise throw an exception.
*/
public static Resource singleModelRoot( Model singleRoot )
{ return singleRoot( singleRoot, JA.Model ); }
/**
Answer the single resource in <code>singleRoot</code> of type
<code>type</code>. Otherwise throw an exception.
*/
public static Resource singleRoot( Model singleRoot, Resource type )
{
Set<Resource> roots = findAssemblerRoots( singleRoot, type );
if (roots.size() == 1) return roots.iterator().next();
if (roots.size() == 0) throw new BadDescriptionNoRootException( singleRoot, type );
throw new BadDescriptionMultipleRootsException( singleRoot, type );
}
}