Skip to content

Commit bedaf6d

Browse files
committed
Factor out ProtoDecoderBase for extensibility
To support extensions like Jelly-Patch, it would be useful to factor out the common ProtoDecoder code to a trait that can be extended. More info: Jelly-RDF/jelly-protobuf#11 This is analogous to very similar changes in ProtoEncoder here: #278
1 parent 838eb4c commit bedaf6d

File tree

5 files changed

+214
-107
lines changed

5 files changed

+214
-107
lines changed

core/src/main/java/eu/ostrzyciel/jelly/core/internal/NameDecoder.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* Class for decoding RDF IRIs from their Jelly representation.
1010
* @param <TIri> The type of the IRI in the target RDF library.
1111
*/
12-
final class NameDecoder<TIri> {
12+
final class NameDecoderImpl<TIri> implements NameDecoder<TIri> {
1313
private static final class NameLookupEntry {
1414
// Primary: the actual name
1515
public String name;
@@ -44,7 +44,7 @@ private static final class PrefixLookupEntry {
4444
* @param nameTableSize The size of the name lookup table.
4545
* @param iriFactory A function that creates an IRI from a string.
4646
*/
47-
public NameDecoder(int prefixTableSize, int nameTableSize, Function<String, TIri> iriFactory) {
47+
public NameDecoderImpl(int prefixTableSize, int nameTableSize, Function<String, TIri> iriFactory) {
4848
this.iriFactory = iriFactory;
4949
nameLookup = new NameLookupEntry[nameTableSize + 1];
5050
prefixLookup = new PrefixLookupEntry[prefixTableSize + 1];

core/src/main/scala/eu/ostrzyciel/jelly/core/JellyOptions.scala

+22-15
Original file line numberDiff line numberDiff line change
@@ -7,75 +7,82 @@ import eu.ostrzyciel.jelly.core.proto.v1.{LogicalStreamType, PhysicalStreamType,
77
* None of the presets specifies the stream type – do that with the .withPhysicalType method.
88
*/
99
object JellyOptions:
10+
private[core] inline val bigNameTableSize = 4000
11+
private[core] inline val bigPrefixTableSize = 150
12+
private[core] inline val bigDtTableSize = 32
13+
14+
private[core] inline val smallNameTableSize = 128
15+
private[core] inline val smallPrefixTableSize = 16
16+
private[core] inline val smallDtTableSize = 16
1017

1118
/**
1219
* "Big" preset suitable for high-volume streams and larger machines.
1320
* Does not allow generalized RDF statements.
1421
* @return
1522
*/
16-
def bigStrict: RdfStreamOptions = RdfStreamOptions(
17-
maxNameTableSize = 4000,
18-
maxPrefixTableSize = 150,
19-
maxDatatypeTableSize = 32,
23+
lazy val bigStrict: RdfStreamOptions = RdfStreamOptions(
24+
maxNameTableSize = bigNameTableSize,
25+
maxPrefixTableSize = bigPrefixTableSize,
26+
maxDatatypeTableSize = bigDtTableSize,
2027
)
2128

2229
/**
2330
* "Big" preset suitable for high-volume streams and larger machines.
2431
* Allows generalized RDF statements.
2532
* @return
2633
*/
27-
def bigGeneralized: RdfStreamOptions =
34+
lazy val bigGeneralized: RdfStreamOptions =
2835
bigStrict.withGeneralizedStatements(true)
2936

3037
/**
3138
* "Big" preset suitable for high-volume streams and larger machines.
3239
* Allows RDF-star statements.
3340
* @return
3441
*/
35-
def bigRdfStar: RdfStreamOptions =
42+
lazy val bigRdfStar: RdfStreamOptions =
3643
bigStrict.withRdfStar(true)
3744

3845
/**
3946
* "Big" preset suitable for high-volume streams and larger machines.
4047
* Allows all protocol features (including generalized RDF statements and RDF-star statements).
4148
* @return
4249
*/
43-
def bigAllFeatures: RdfStreamOptions =
50+
lazy val bigAllFeatures: RdfStreamOptions =
4451
bigStrict.withGeneralizedStatements(true).withRdfStar(true)
4552

4653
/**
4754
* "Small" preset suitable for low-volume streams and smaller machines.
4855
* Does not allow generalized RDF statements.
4956
* @return
5057
*/
51-
def smallStrict: RdfStreamOptions = RdfStreamOptions(
52-
maxNameTableSize = 128,
53-
maxPrefixTableSize = 16,
54-
maxDatatypeTableSize = 16,
58+
lazy val smallStrict: RdfStreamOptions = RdfStreamOptions(
59+
maxNameTableSize = smallNameTableSize,
60+
maxPrefixTableSize = smallPrefixTableSize,
61+
maxDatatypeTableSize = smallDtTableSize,
5562
)
5663

5764
/**
5865
* "Small" preset suitable for low-volume streams and smaller machines.
5966
* Allows generalized RDF statements.
6067
* @return
6168
*/
62-
def smallGeneralized: RdfStreamOptions =
69+
lazy val smallGeneralized: RdfStreamOptions =
6370
smallStrict.withGeneralizedStatements(true)
6471

6572
/**
6673
* "Small" preset suitable for low-volume streams and smaller machines.
6774
* Allows RDF-star statements.
6875
* @return
6976
*/
70-
def smallRdfStar: RdfStreamOptions =
77+
lazy val smallRdfStar: RdfStreamOptions =
7178
smallStrict.withRdfStar(true)
7279

7380
/**
7481
* "Small" preset suitable for low-volume streams and smaller machines.
7582
* Allows all protocol features (including generalized RDF statements and RDF-star statements).
7683
* @return
7784
*/
78-
def smallAllFeatures: RdfStreamOptions =
85+
lazy val smallAllFeatures: RdfStreamOptions =
7986
smallStrict.withGeneralizedStatements(true).withRdfStar(true)
8087

8188
/**
@@ -96,7 +103,7 @@ object JellyOptions:
96103
*
97104
* @return
98105
*/
99-
def defaultSupportedOptions: RdfStreamOptions = RdfStreamOptions(
106+
lazy val defaultSupportedOptions: RdfStreamOptions = RdfStreamOptions(
100107
generalizedStatements = true,
101108
rdfStar = true,
102109
maxNameTableSize = 4096,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package eu.ostrzyciel.jelly.core.internal
2+
3+
import eu.ostrzyciel.jelly.core.proto.v1.*
4+
5+
object NameDecoder:
6+
/**
7+
* Create a new NameDecoder.
8+
* @param prefixTableSize size of the prefix table
9+
* @param nameTableSize size of the name table
10+
* @param iriFactory factory for creating IRIs
11+
* @tparam TIri type of the IRI
12+
* @return new NameDecoder
13+
*/
14+
def apply[TIri](
15+
prefixTableSize: Int, nameTableSize: Int, iriFactory: java.util.function.Function[String, TIri]
16+
): NameDecoder[TIri] =
17+
new NameDecoderImpl(prefixTableSize, nameTableSize, iriFactory)
18+
19+
/**
20+
* Interface for NameDecoder exposed for Jelly extensions.
21+
* @tparam TIri type of the IRI
22+
*/
23+
private[core] trait NameDecoder[TIri]:
24+
/**
25+
* Update the name table with a new entry.
26+
* @param nameEntry new name entry
27+
*/
28+
def updateNames(nameEntry: RdfNameEntry): Unit
29+
30+
/**
31+
* Update the prefix table with a new entry.
32+
* @param prefixEntry new prefix entry
33+
*/
34+
def updatePrefixes(prefixEntry: RdfPrefixEntry): Unit
35+
36+
/**
37+
* Reconstruct an IRI from its prefix and name ids.
38+
* @param iri IRI row from the Jelly proto
39+
* @return full IRI combining the prefix and the name
40+
*/
41+
def decode(iri: RdfIri): TIri
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package eu.ostrzyciel.jelly.core.internal
2+
3+
import eu.ostrzyciel.jelly.core.*
4+
import eu.ostrzyciel.jelly.core.JellyExceptions.*
5+
import eu.ostrzyciel.jelly.core.proto.v1.*
6+
7+
import scala.reflect.ClassTag
8+
9+
/**
10+
* Base trait for Jelly proto decoders. Only for internal use.
11+
* @tparam TNode type of RDF nodes in the library
12+
* @tparam TDatatype type of the datatype in the library
13+
* @tparam TTriple type of the triple in the library
14+
* @tparam TQuad type of the quad in the library
15+
*/
16+
private[core] trait ProtoDecoderBase[TNode, TDatatype : ClassTag, +TTriple, +TQuad]:
17+
18+
// To be implemented by the concrete class
19+
protected val converter: ProtoDecoderConverter[TNode, TDatatype, TTriple, TQuad]
20+
protected def getNameTableSize: Int
21+
protected def getPrefixTableSize: Int
22+
protected def getDatatypeTableSize: Int
23+
24+
// Private fields
25+
protected final lazy val nameDecoder =
26+
NameDecoder(getPrefixTableSize, getNameTableSize, converter.makeIriNode)
27+
protected final lazy val dtLookup = new DecoderLookup[TDatatype](getDatatypeTableSize)
28+
29+
protected final val lastSubject: LastNodeHolder[TNode] = new LastNodeHolder()
30+
protected final val lastPredicate: LastNodeHolder[TNode] = new LastNodeHolder()
31+
protected final val lastObject: LastNodeHolder[TNode] = new LastNodeHolder()
32+
protected final val lastGraph: LastNodeHolder[TNode] = new LastNodeHolder()
33+
34+
// Protected final methods
35+
36+
/**
37+
* Convert a GraphTerm message to a node.
38+
* @param graph graph term to convert
39+
* @return converted node
40+
*/
41+
protected final def convertGraphTerm(graph: GraphTerm): TNode =
42+
if graph == null then
43+
throw new RdfProtoDeserializationError("Empty graph term encountered in a GRAPHS stream.")
44+
else if graph.isIri then
45+
nameDecoder.decode(graph.iri)
46+
else if graph.isDefaultGraph then
47+
converter.makeDefaultGraphNode()
48+
else if graph.isBnode then
49+
converter.makeBlankNode(graph.bnode)
50+
else if graph.isLiteral then
51+
convertLiteral(graph.literal)
52+
else
53+
throw new RdfProtoDeserializationError("Unknown graph term type.")
54+
55+
/**
56+
* Convert an SpoTerm message to a node, while respecting repeated terms.
57+
* @param term term to convert
58+
* @param lastNodeHolder holder for the last node
59+
* @return converted node
60+
*/
61+
protected final def convertTermWrapped(term: SpoTerm, lastNodeHolder: LastNodeHolder[TNode]): TNode =
62+
if term == null then
63+
lastNodeHolder.node match
64+
case LastNodeHolder.NoValue =>
65+
throw new RdfProtoDeserializationError("Empty term without previous term.")
66+
case n => n.asInstanceOf[TNode]
67+
else
68+
val node = convertTerm(term)
69+
lastNodeHolder.node = node
70+
node
71+
72+
/**
73+
* Convert a GraphTerm message to a node, while respecting repeated terms.
74+
* @param graph graph term to convert
75+
* @return converted node
76+
*/
77+
protected final def convertGraphTermWrapped(graph: GraphTerm): TNode =
78+
if graph == null then
79+
lastGraph.node match
80+
case LastNodeHolder.NoValue =>
81+
throw new RdfProtoDeserializationError("Empty term without previous graph term.")
82+
case n => n.asInstanceOf[TNode]
83+
else
84+
val node = convertGraphTerm(graph)
85+
lastGraph.node = node
86+
node
87+
88+
/**
89+
* Convert an RdfTriple message, while respecting repeated terms.
90+
* @param triple triple to convert
91+
* @return converted triple
92+
*/
93+
protected final def convertTriple(triple: RdfTriple): TTriple =
94+
converter.makeTriple(
95+
convertTermWrapped(triple.subject, lastSubject),
96+
convertTermWrapped(triple.predicate, lastPredicate),
97+
convertTermWrapped(triple.`object`, lastObject),
98+
)
99+
100+
/**
101+
* Convert an RdfQuad message, while respecting repeated terms.
102+
* @param quad quad to convert
103+
* @return converted quad
104+
*/
105+
protected final def convertQuad(quad: RdfQuad): TQuad =
106+
converter.makeQuad(
107+
convertTermWrapped(quad.subject, lastSubject),
108+
convertTermWrapped(quad.predicate, lastPredicate),
109+
convertTermWrapped(quad.`object`, lastObject),
110+
convertGraphTermWrapped(quad.graph),
111+
)
112+
113+
// Private methods
114+
private final def convertLiteral(literal: RdfLiteral): TNode = literal.literalKind match
115+
case RdfLiteral.LiteralKind.Empty =>
116+
converter.makeSimpleLiteral(literal.lex)
117+
case RdfLiteral.LiteralKind.Langtag(lang) =>
118+
converter.makeLangLiteral(literal.lex, lang)
119+
case RdfLiteral.LiteralKind.Datatype(dtId) =>
120+
converter.makeDtLiteral(literal.lex, dtLookup.get(dtId))
121+
122+
private final def convertTerm(term: SpoTerm): TNode =
123+
if term == null then
124+
throw new RdfProtoDeserializationError("Term value is not set inside a quoted triple.")
125+
else if term.isIri then
126+
nameDecoder.decode(term.iri)
127+
else if term.isBnode then
128+
converter.makeBlankNode(term.bnode)
129+
else if term.isLiteral then
130+
convertLiteral(term.literal)
131+
else if term.isTripleTerm then
132+
val inner = term.tripleTerm
133+
// ! No support for repeated terms in quoted triples
134+
converter.makeTripleNode(
135+
convertTerm(inner.subject),
136+
convertTerm(inner.predicate),
137+
convertTerm(inner.`object`),
138+
)
139+
else
140+
throw new RdfProtoDeserializationError("Unknown term type.")

0 commit comments

Comments
 (0)