• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanodash / 18557556913

16 Oct 2025 10:00AM UTC coverage: 13.57% (-0.04%) from 13.607%
18557556913

push

github

tkuhn
fix: Properly treat introduced resources that are not URI placeholders

448 of 4202 branches covered (10.66%)

Branch coverage included in aggregate %.

1171 of 7729 relevant lines covered (15.15%)

0.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.29
src/main/java/com/knowledgepixels/nanodash/template/TemplateContext.java
1
package com.knowledgepixels.nanodash.template;
2

3
import com.knowledgepixels.nanodash.NanodashSession;
4
import com.knowledgepixels.nanodash.Utils;
5
import com.knowledgepixels.nanodash.component.PublishForm.FillMode;
6
import com.knowledgepixels.nanodash.component.StatementItem;
7
import org.apache.wicket.Component;
8
import org.apache.wicket.model.IModel;
9
import org.apache.wicket.model.Model;
10
import org.eclipse.rdf4j.model.IRI;
11
import org.eclipse.rdf4j.model.Statement;
12
import org.eclipse.rdf4j.model.Value;
13
import org.eclipse.rdf4j.model.ValueFactory;
14
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
15
import org.eclipse.rdf4j.model.vocabulary.RDFS;
16
import org.nanopub.*;
17
import org.nanopub.vocabulary.NTEMPLATE;
18

19
import java.io.Serializable;
20
import java.util.*;
21

22
/**
23
 * Context for a template, containing all necessary information to fill.
24
 */
25
public class TemplateContext implements Serializable {
26

27
    private static ValueFactory vf = SimpleValueFactory.getInstance();
3✔
28

29
    private final ContextType contextType;
30
    private final Template template;
31
    private final String componentId;
32
    private final Map<String, String> params = new HashMap<>();
×
33
    private List<Component> components = new ArrayList<>();
×
34
    private Map<IRI, IModel<String>> componentModels = new HashMap<>();
×
35
    private Set<IRI> introducedIris = new HashSet<>();
×
36
    private Set<IRI> embeddedIris = new HashSet<>();
×
37
    private List<StatementItem> statementItems;
38
    private Set<IRI> iriSet = new HashSet<>();
×
39
    private Map<IRI, StatementItem> narrowScopeMap = new HashMap<>();
×
40
    private String targetNamespace = Template.DEFAULT_TARGET_NAMESPACE;
×
41
    private Nanopub existingNanopub;
42
    private Map<IRI, String> labels;
43
    private FillMode fillMode = null;
×
44

45
    /**
46
     * Constructor for creating a new template context for filling a template.
47
     *
48
     * @param contextType     the type of context
49
     * @param templateId      the ID of the template to fill
50
     * @param componentId     the ID of the component that will use this context
51
     * @param targetNamespace the target namespace for the template, can be null to use the default namespace
52
     */
53
    public TemplateContext(ContextType contextType, String templateId, String componentId, String targetNamespace) {
54
        this(contextType, templateId, componentId, targetNamespace, null);
×
55
    }
×
56

57
    /**
58
     * Constructor for creating a new template context for filling a template.
59
     *
60
     * @param contextType     the type of context
61
     * @param templateId      the ID of the template to fill
62
     * @param componentId     the ID of the component that will use this context
63
     * @param existingNanopub an existing nanopub to fill, can be null if creating a new nanopub
64
     */
65
    public TemplateContext(ContextType contextType, String templateId, String componentId, Nanopub existingNanopub) {
66
        this(contextType, templateId, componentId, null, existingNanopub);
×
67
    }
×
68

69
    private TemplateContext(ContextType contextType, String templateId, String componentId, String targetNamespace, Nanopub existingNanopub) {
×
70
        this.contextType = contextType;
×
71
        // TODO: check whether template is of correct type:
72
        this.template = TemplateData.get().getTemplate(templateId);
×
73
        this.componentId = componentId;
×
74
        if (targetNamespace != null) {
×
75
            this.targetNamespace = targetNamespace;
×
76
        }
77
        this.existingNanopub = existingNanopub;
×
78
        if (existingNanopub == null && NanodashSession.get().getUserIri() != null) {
×
79
            componentModels.put(NTEMPLATE.CREATOR_PLACEHOLDER, Model.of(NanodashSession.get().getUserIri().stringValue()));
×
80
        }
81
    }
×
82

83
    /**
84
     * Initializes the statements for this context.
85
     */
86
    public void initStatements() {
87
        if (statementItems != null) return;
×
88
        statementItems = new ArrayList<>();
×
89
        for (IRI st : template.getStatementIris()) {
×
90
            StatementItem si = new StatementItem(componentId, st, this);
×
91
            statementItems.add(si);
×
92
            for (IRI i : si.getIriSet()) {
×
93
                if (iriSet.contains(i)) {
×
94
                    narrowScopeMap.remove(i);
×
95
                } else {
96
                    iriSet.add(i);
×
97
                    narrowScopeMap.put(i, si);
×
98
                }
99
            }
×
100
        }
×
101
    }
×
102

103
    /**
104
     * Finalizes the statements by processing all parameters and setting the repetition counts.
105
     */
106
    public void finalizeStatements() {
107
        Map<StatementItem, Integer> finalRepetitionCount = new HashMap<>();
×
108
        for (IRI ni : narrowScopeMap.keySet()) {
×
109
            // TODO: Move all occurrences of this to utility function:
110
            String postfix = Utils.getUriPostfix(ni);
×
111
            StatementItem si = narrowScopeMap.get(ni);
×
112
            int i = si.getRepetitionCount();
×
113
            while (true) {
114
                String p = postfix + "__" + i;
×
115
                if (hasParam(p)) {
×
116
                    si.addRepetitionGroup();
×
117
                } else {
118
                    break;
119
                }
120
                i++;
×
121
            }
×
122
            i = 1;
×
123
            int corr = 0;
×
124
            if (si.isEmpty()) corr = 1;
×
125
            while (true) {
126
                String p = postfix + "__." + i;
×
127
                if (hasParam(p)) {
×
128
                    int absPos = si.getRepetitionCount() + i - 1 - corr;
×
129
                    String param = postfix + "__" + absPos;
×
130
                    if (i - corr == 0) param = postfix;
×
131
                    setParam(param, getParam(p));
×
132
                    finalRepetitionCount.put(si, i - corr);
×
133
                } else {
134
                    break;
135
                }
136
                i++;
×
137
            }
×
138
        }
×
139
        for (StatementItem si : finalRepetitionCount.keySet()) {
×
140
            for (int i = 0; i < finalRepetitionCount.get(si); i++) {
×
141
                si.addRepetitionGroup();
×
142
            }
143
        }
×
144
        for (StatementItem si : statementItems) {
×
145
            si.finalizeValues();
×
146
        }
×
147
    }
×
148

149
    /**
150
     * Sets the fill mode for this context.
151
     *
152
     * @param fillMode the fill mode to set
153
     */
154
    public void setFillMode(FillMode fillMode) {
155
        this.fillMode = fillMode;
×
156
    }
×
157

158
    /**
159
     * Gets the fill mode for this context.
160
     *
161
     * @return the fill mode, or null if not set
162
     */
163
    public FillMode getFillMode() {
164
        return fillMode;
×
165
    }
166

167
    /**
168
     * Returns the type of context.
169
     *
170
     * @return the context type
171
     */
172
    public ContextType getType() {
173
        return contextType;
×
174
    }
175

176
    /**
177
     * Returns the template associated with this context.
178
     *
179
     * @return the template
180
     */
181
    public Template getTemplate() {
182
        return template;
×
183
    }
184

185
    /**
186
     * Returns the ID of the template associated with this context.
187
     *
188
     * @return the template ID
189
     */
190
    public String getTemplateId() {
191
        return template.getId();
×
192
    }
193

194
    /**
195
     * Sets a parameter for this context.
196
     *
197
     * @param name  the name of the parameter
198
     * @param value the value of the parameter
199
     */
200
    public void setParam(String name, String value) {
201
        params.put(name, value);
×
202
    }
×
203

204
    /**
205
     * Gets a parameter value by its name.
206
     *
207
     * @param name the name of the parameter
208
     * @return the value of the parameter, or null if not set
209
     */
210
    public String getParam(String name) {
211
        return params.get(name);
×
212
    }
213

214
    /**
215
     * Checks if a parameter with the given name exists.
216
     *
217
     * @param name the name of the parameter
218
     * @return true if the parameter exists, false otherwise
219
     */
220
    public boolean hasParam(String name) {
221
        return params.containsKey(name);
×
222
    }
223

224
    /**
225
     * Returns the components associated with this context.
226
     *
227
     * @return a list of components
228
     */
229
    public List<Component> getComponents() {
230
        return components;
×
231
    }
232

233
    /**
234
     * Returns the component models associated with this context.
235
     *
236
     * @return a map of IRI to model of strings
237
     */
238
    public Map<IRI, IModel<String>> getComponentModels() {
239
        return componentModels;
×
240
    }
241

242
    /**
243
     * Returns the introduced IRIs in this context.
244
     *
245
     * @return a set of introduced IRIs
246
     */
247
    public Set<IRI> getIntroducedIris() {
248
        return introducedIris;
×
249
    }
250

251
    /**
252
     * Returns the embedded IRIs in this context.
253
     *
254
     * @return a set of embedded IRIs
255
     */
256
    public Set<IRI> getEmbeddedIris() {
257
        return embeddedIris;
×
258
    }
259

260
    /**
261
     * Processes an IRI by applying the template's processing rules.
262
     *
263
     * @param iri the IRI to process
264
     * @return the processed IRI, or null if the processing results in no value
265
     */
266
    public IRI processIri(IRI iri) {
267
        Value v = processValue(iri);
×
268
        if (v == null) return null;
×
269
        if (v instanceof IRI) return (IRI) v;
×
270
        return iri;
×
271
    }
272

273
    /**
274
     * Processes a Value according to the template's rules.
275
     *
276
     * @param value the Value to process
277
     * @return the processed Value, or the original Value if no processing is applicable
278
     */
279
    public Value processValue(Value value) {
280
        if (!(value instanceof IRI)) return value;
×
281
        IRI iri = (IRI) value;
×
282
        if (iri.equals(NTEMPLATE.CREATOR_PLACEHOLDER)) {
×
283
            iri = NanodashSession.get().getUserIri();
×
284
        }
285
        if (iri.equals(NTEMPLATE.ASSERTION_PLACEHOLDER)) {
×
286
            iri = vf.createIRI(targetNamespace + "assertion");
×
287
        } else if (iri.equals(NTEMPLATE.NANOPUB_PLACEHOLDER)) {
×
288
            iri = vf.createIRI(targetNamespace);
×
289
        }
290
        // TODO: Move this code below to the respective placeholder classes:
291
        IModel<String> tf = componentModels.get(iri);
×
292
        Value processedValue = null;
×
293
        if (template.isRestrictedChoicePlaceholder(iri)) {
×
294
            if (tf != null && tf.getObject() != null && !tf.getObject().isEmpty()) {
×
295
                String prefix = template.getPrefix(iri);
×
296
                if (prefix == null) prefix = "";
×
297
                if (template.isLocalResource(iri)) prefix = targetNamespace;
×
298
                if (tf.getObject().matches("https?://.+")) prefix = "";
×
299
                String v = prefix + tf.getObject();
×
300
                if (v.matches("[^:# ]+")) v = targetNamespace + v;
×
301
                if (v.matches("https?://.*")) {
×
302
                    processedValue = vf.createIRI(v);
×
303
                } else {
304
                    processedValue = vf.createLiteral(tf.getObject());
×
305
                }
306
            }
×
307
        } else if (template.isUriPlaceholder(iri)) {
×
308
            if (tf != null && tf.getObject() != null && !tf.getObject().isEmpty()) {
×
309
                String prefix = template.getPrefix(iri);
×
310
                if (prefix == null) prefix = "";
×
311
                if (template.isLocalResource(iri)) prefix = targetNamespace;
×
312
                String v;
313
                if (template.isAutoEscapePlaceholder(iri)) {
×
314
                    v = prefix + Utils.urlEncode(tf.getObject());
×
315
                } else {
316
                    if (tf.getObject().matches("https?://.+")) prefix = "";
×
317
                    v = prefix + tf.getObject();
×
318
                }
319
                if (v.matches("[^:# ]+")) v = targetNamespace + v;
×
320
                processedValue = vf.createIRI(v);
×
321
            }
×
322
        } else if (template.isLocalResource(iri)) {
×
323
            if (template.isIntroducedResource(iri) && fillMode == FillMode.SUPERSEDE) {
×
324
                if (tf != null && tf.getObject() != null && !tf.getObject().isEmpty()) {
×
325
                    processedValue = vf.createIRI(tf.getObject());
×
326
                }
327
            } else {
328
                String prefix = Utils.getUriPrefix(iri);
×
329
                processedValue = vf.createIRI(iri.stringValue().replace(prefix, targetNamespace));
×
330
            }
×
331
        } else if (template.isLiteralPlaceholder(iri)) {
×
332
            IRI datatype = template.getDatatype(iri);
×
333
            String languagetag = template.getLanguageTag(iri);
×
334
            if (tf != null && tf.getObject() != null && !tf.getObject().isEmpty()) {
×
335
                if (datatype != null) {
×
336
                    processedValue = vf.createLiteral(tf.getObject(), datatype);
×
337
                } else if (languagetag != null) {
×
338
                    processedValue = vf.createLiteral(tf.getObject(), languagetag);
×
339
                } else {
340
                    processedValue = vf.createLiteral(tf.getObject());
×
341
                }
342
            }
343

344
        } else if (template.isValuePlaceholder(iri)) {
×
345
            if (tf != null && tf.getObject() != null && !tf.getObject().isEmpty()) {
×
346
                if (Utils.isValidLiteralSerialization(tf.getObject())) {
×
347
                    processedValue = Utils.getParsedLiteral(tf.getObject());
×
348
                } else {
349
                    String v = tf.getObject();
×
350
                    if (v.matches("[^:# ]+")) v = targetNamespace + v;
×
351
                    processedValue = vf.createIRI(v);
×
352
                }
×
353
            }
354
        } else if (template.isSequenceElementPlaceholder(iri)) {
×
355
            if (tf != null && tf.getObject() != null && !tf.getObject().isEmpty()) {
×
356
                processedValue = vf.createIRI(tf.getObject());
×
357
            }
358
        } else {
359
            processedValue = iri;
×
360
        }
361
        if (processedValue instanceof IRI pvIri && template.isIntroducedResource(iri)) {
×
362
            introducedIris.add(pvIri);
×
363
        }
364
        if (processedValue instanceof IRI pvIri && template.isEmbeddedResource(iri)) {
×
365
            embeddedIris.add(pvIri);
×
366
        }
367
        return processedValue;
×
368
    }
369

370
    /**
371
     * Returns the statement items associated with this context.
372
     *
373
     * @return a list of StatementItem objects
374
     */
375
    public List<StatementItem> getStatementItems() {
376
        return statementItems;
×
377
    }
378

379
    /**
380
     * Propagates the statements from this context to a NanopubCreator.
381
     *
382
     * @param npCreator the NanopubCreator to which the statements will be added
383
     * @throws org.nanopub.MalformedNanopubException        if there is an error in the nanopub structure
384
     * @throws org.nanopub.NanopubAlreadyFinalizedException if the nanopub has already been finalized
385
     */
386
    public void propagateStatements(NanopubCreator npCreator) throws MalformedNanopubException, NanopubAlreadyFinalizedException {
387
        if (template.getNanopub() instanceof NanopubWithNs) {
×
388
            NanopubWithNs np = (NanopubWithNs) template.getNanopub();
×
389
            for (String p : np.getNsPrefixes()) {
×
390
                npCreator.addNamespace(p, np.getNamespace(p));
×
391
            }
×
392
        }
393
        for (StatementItem si : statementItems) {
×
394
            si.addTriplesTo(npCreator);
×
395
        }
×
396
    }
×
397

398
    /**
399
     * Checks if the context has a narrow scope for the given IRI.
400
     *
401
     * @param iri the IRI to check
402
     * @return true if there is a narrow scope for the IRI, false otherwise
403
     */
404
    public boolean hasNarrowScope(IRI iri) {
405
        return narrowScopeMap.containsKey(iri);
×
406
    }
407

408
    /**
409
     * Checks if any of the statement items in this context will match any triple.
410
     *
411
     * @return true if any statement item will match any triple, false otherwise
412
     */
413
    public boolean willMatchAnyTriple() {
414
        initStatements();
×
415
        for (StatementItem si : statementItems) {
×
416
            if (si.willMatchAnyTriple()) return true;
×
417
        }
×
418
        return false;
×
419
    }
420

421
    /**
422
     * Fills the context with statements, processing each StatementItem.
423
     *
424
     * @param statements the list of statements to fill
425
     * @throws com.knowledgepixels.nanodash.template.UnificationException if there is an error during unification of statements
426
     */
427
    public void fill(List<Statement> statements) throws UnificationException {
428
        for (StatementItem si : statementItems) {
×
429
            si.fill(statements);
×
430
        }
×
431
        for (StatementItem si : statementItems) {
×
432
            si.fillFinished();
×
433
        }
×
434
    }
×
435

436
    /**
437
     * Returns the existing Nanopub associated with this context, if any.
438
     *
439
     * @return the existing Nanopub, or null if this context is for a new Nanopub
440
     */
441
    public Nanopub getExistingNanopub() {
442
        return existingNanopub;
×
443
    }
444

445
    /**
446
     * Checks if this context is read-only.
447
     *
448
     * @return true if the context is read-only, false otherwise
449
     */
450
    public boolean isReadOnly() {
451
        return existingNanopub != null;
×
452
    }
453

454
    /**
455
     * Returns the label for a given IRI, if available.
456
     *
457
     * @param iri the IRI for which to get the label
458
     * @return the label as a String, or null if no label is found
459
     */
460
    public String getLabel(IRI iri) {
461
        if (existingNanopub == null) return null;
×
462
        if (labels == null) {
×
463
            labels = new HashMap<>();
×
464
            for (Statement st : existingNanopub.getPubinfo()) {
×
465
                if (st.getPredicate().equals(NTEMPLATE.HAS_LABEL_FROM_API) || st.getPredicate().equals(RDFS.LABEL)) {
×
466
                    String label = st.getObject().stringValue();
×
467
                    labels.put((IRI) st.getSubject(), label);
×
468
                }
469
            }
×
470
        }
471
        return labels.get(iri);
×
472
    }
473

474
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc