blob: 4c69fa9e78c87a5a275c455b75dfefa475541d69 [file] [log] [blame]
/*
* 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.riot.lang;
import java.util.HashMap ;
import java.util.Map ;
import java.util.UUID;
import org.apache.jena.graph.Node ;
import org.apache.jena.riot.out.NodeFmtLib ;
import org.apache.jena.riot.out.NodeToLabel ;
import org.apache.jena.riot.system.MapWithScope ;
import org.apache.jena.riot.system.SyntaxLabels ;
/** Allocation Nodes (Bnodes usually) based on the graph and label
* Various different policies.
* See {@link SyntaxLabels#createLabelToNode} for getting a default setup;
* some of the others are for testing and debugging and may not generate
* legal RDF overall (e.g. reparsing the same file gets the same bNodes)
*/
public class LabelToNode extends MapWithScope<String, Node, Node>
{
/** Allocation from a single scope; just the label matters.
* This is the RDF syntax correct policy.
*/
public static LabelToNode createScopeByDocumentHash()
{ return new LabelToNode(new AllocScopePolicy(), nodeAllocatorHash()) ; }
/**
* Allocation from a single scope; just the label matters. Use this policy if repeated runs must give identical allocations
* @param seed Seed
*/
public static LabelToNode createScopeByDocumentHash(UUID seed)
{ return new LabelToNode(new AllocScopePolicy(), nodeAllocatorHash(seed)) ; }
/** The policy up to jena 2.10.0 - problems at very large scale */
public static LabelToNode createScopeGlobal()
{ return new LabelToNode(new SingleScopePolicy(), nodeAllocatorGlobal()) ; }
/** Allocation scoped by graph and label. */
public static LabelToNode createScopeByGraph() {
// Not AllocNodeHash here - that is unique per run, not per graph-in-run.
// Must use an unconditionally unique allocator.
return new LabelToNode(new GraphScopePolicy(), nodeAllocatorByGraph()) ;
}
/** Allocation using syntax label; output is unsafe for reading (use
* {@link #createUseLabelEncoded()} for output-input).
*
* The reverse operation is provided by {@link NodeToLabel#createBNodeByLabelAsGiven()}
* but the pair is <em>unsafe</em> for output-input. Use encoded labels for that.
*
* The main purpose of this LabelToNode is to preserve the used label for debugging.
*/
public static LabelToNode createUseLabelAsGiven()
{ return new LabelToNode(new AllocScopePolicy(), nodeAllocatorRawLabel()) ; }
/** Allocation using an encoded syntax label
* (i.e. _:B&lt;encoded&gt; format from {@link NodeFmtLib#encodeBNodeLabel}).
*
* The reverse operation is provided by {@link NodeToLabel#createBNodeByLabelEncoded()}.
* This pair should be used to write out and recover blank node by internal id.
*/
public static LabelToNode createUseLabelEncoded()
{ return new LabelToNode(new AllocScopePolicy(), nodeAllocatorEncoded()) ; }
/** Allocation, globally scoped, that uses a incrementing field to create new nodes */
public static LabelToNode createIncremental()
{ return new LabelToNode(new SingleScopePolicy(), nodeAllocatorDeterministic()) ; }
// ---- Create fresh allocators per call
// The preferred node allocator - completely scalable.
// Nodes are unique-per-run.
private static Allocator<String, Node, Node> nodeAllocatorHash() {
return new Alloc(new BlankNodeAllocatorHash()) ;
}
private static Allocator<String, Node, Node> nodeAllocatorHash(UUID seed) {
return new Alloc(new BlankNodeAllocatorFixedSeedHash(seed)) ;
}
private static Allocator<String, Node, Node> nodeAllocatorDeterministic() {
return new Alloc(new BlankNodeAllocatorLabel()) ;
}
private static Allocator<String, Node, Node> nodeAllocatorGlobal() {
return new Alloc(new BlankNodeAllocatorGlobal()) ;
}
private static Allocator<String, Node, Node> nodeAllocatorEncoded() {
return new Alloc(new BlankNodeAllocatorLabelEncoded()) ;
}
private static Allocator<String, Node, Node> nodeAllocatorRawLabel() {
return new Alloc(new BlankNodeAllocatorLabel()) ;
}
private static Allocator<String, Node, Node> nodeAllocatorByGraph() {
return new AllocByGraph() ;
}
// ---- The class
public LabelToNode(ScopePolicy<String, Node, Node> scopePolicy, Allocator<String, Node, Node> allocator)
{
super(scopePolicy, allocator) ;
}
// ======== Scope Policies
/** Single scope */
private static class SingleScopePolicy implements ScopePolicy<String, Node, Node>
{
private Map<String, Node> map = new HashMap<>() ;
@Override
public Map<String, Node> getScope(Node scope) { return map ; }
@Override
public void clear() { map.clear(); }
}
/** One scope for labels per graph */
private static class GraphScopePolicy implements ScopePolicy<String, Node, Node>
{
private Map<String, Node> dftMap = new HashMap<>() ;
private Map<Node, Map<String, Node>> map = new HashMap<>() ;
@Override
public Map<String, Node> getScope(Node scope)
{
if ( scope == null )
return dftMap ;
Map<String, Node> x = map.get(scope) ;
if ( x == null )
{
x = new HashMap<>() ;
map.put(scope, x) ;
}
return x ;
}
@Override
public void clear() {
dftMap.clear() ;
map.clear(); }
}
/** No scope - use raw allocator */
private static class AllocScopePolicy implements ScopePolicy<String, Node, Node>
{
@Override
public Map<String, Node> getScope(Node scope) { return null ; }
@Override
public void clear() { }
}
// ======== Node Allocators
// Adapter class from MapWithScope.Allocator to BlankNodeAllocator
// This does not take scope into account when creating nodes.
private static class Alloc implements Allocator<String, Node, Node> {
final BlankNodeAllocator alloc ;
Alloc(BlankNodeAllocator alloc) { this.alloc = alloc ; }
@Override
public Node alloc(Node scope, String label) { return alloc.alloc(label) ; }
@Override
public Node create() { return alloc.create() ; }
@Override
public void reset() { alloc.reset() ; }
}
/** Allocate a fresh blank node each time. */
private static class AllocByGraph implements Allocator<String, Node, Node> {
BlankNodeAllocator dft = make() ;
Map<Node, BlankNodeAllocator> graphs = new HashMap<>() ;
@Override public Node alloc(Node scope, String label)
{
if ( scope == null)
return dft.alloc(label) ;
BlankNodeAllocator alloc = graphs.get(scope) ;
if ( alloc == null ) {
alloc = make() ;
graphs.put(scope, alloc) ;
}
return alloc.alloc(label) ;
}
@Override public Node create() { return dft.create() ; }
@Override public void reset()
{
graphs.clear() ;
dft.reset() ;
}
private BlankNodeAllocator make() { return new BlankNodeAllocatorHash() ; }
}
// TODO Switch to BlankNodeAllocator and a single wrapper.
// variables if the allocator is reusable across runs
// classes if a new one is needed each time.
// Shared ones must be thread-safe.
// /** Allocate bnode using a per-run seed and the label presented.
// * This is the most scalable, always legal allocator.
// * Not thread safe - not reusable.
// * Create a new allocator for each parser run.
// */
// private static class AllocNodeHash implements Allocator<String, Node> {
// private BlankNodeAllocator alloc = new BlankNodeAllocatorHash() ;
//
// @Override public Node alloc(String label) { return alloc.alloc(label) ; }
// @Override public Node create() { return alloc.create() ; }
// @Override public void reset() { alloc.reset() ; }
// } ;
//
//
//
// /** Allocate bnodes using an incremental counter - deterministic for parsing a given file */
// private static Allocator<String, Node> nodeMakerDeterministic = new Allocator<String, Node>()
// {
// private AtomicLong counter = new AtomicLong(0) ;
//
// @Override
// public Node alloc(Node scope, String label) {
// return create() ;
// }
//
// @Override
// public Node create() {
// String $ = format("B0x%04X", counter.incrementAndGet()) ;
// return NodeFactory.createAnon(new AnonId($)) ;
// }
//
// @Override
// public void reset() {}
// } ;
//
// private static Allocator<String, Node> nodeMakerByLabel = new Allocator<String, Node>()
// {
// private AtomicLong counter = new AtomicLong(0) ;
//
// @Override
// public Node alloc(String label)
// {
// return NodeFactory.createAnon(new AnonId(label)) ;
// }
//
// @Override public Node create() { return alloc(null, SysRIOT.BNodeGenIdPrefix+(counter.getAndIncrement())) ; }
//
// @Override
// public void reset() {}
// } ;
//
// private static Allocator<String, Node> nodeMakerByLabelEncoded = new Allocator<String, Node>()
// {
// private AtomicLong counter = new AtomicLong(0) ;
//
// @Override
// public Node alloc(Node scope, String label)
// {
// return NodeFactory.createAnon(new AnonId(NodeFmtLib.decodeBNodeLabel(label))) ;
// }
//
// @Override public Node create() { return alloc(SysRIOT.BNodeGenIdPrefix+(counter.getAndIncrement())) ; }
// @Override
//
// public void reset() {}
// } ;
}