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

knowledgepixels / nanodash / 20268226543

16 Dec 2025 12:39PM UTC coverage: 14.107% (-1.3%) from 15.358%
20268226543

push

github

ashleycaselli
refactor: replace VocabUtils with the ones defined in the nanopub-java library

526 of 4946 branches covered (10.63%)

Branch coverage included in aggregate %.

1452 of 9075 relevant lines covered (16.0%)

2.11 hits per line

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

0.0
src/main/java/com/knowledgepixels/nanodash/component/ReadonlyItem.java
1
package com.knowledgepixels.nanodash.component;
2

3
import com.knowledgepixels.nanodash.LocalUri;
4
import com.knowledgepixels.nanodash.RestrictedChoice;
5
import com.knowledgepixels.nanodash.User;
6
import com.knowledgepixels.nanodash.Utils;
7
import com.knowledgepixels.nanodash.component.StatementItem.RepetitionGroup;
8
import com.knowledgepixels.nanodash.page.ExplorePage;
9
import com.knowledgepixels.nanodash.page.UserPage;
10
import com.knowledgepixels.nanodash.template.ContextType;
11
import com.knowledgepixels.nanodash.template.Template;
12
import com.knowledgepixels.nanodash.template.UnificationException;
13
import net.trustyuri.TrustyUriUtils;
14
import org.apache.commons.codec.Charsets;
15
import org.apache.wicket.behavior.AttributeAppender;
16
import org.apache.wicket.markup.html.basic.Label;
17
import org.apache.wicket.markup.html.link.ExternalLink;
18
import org.apache.wicket.model.IModel;
19
import org.apache.wicket.model.Model;
20
import org.apache.wicket.validation.IValidatable;
21
import org.apache.wicket.validation.IValidator;
22
import org.apache.wicket.validation.Validatable;
23
import org.apache.wicket.validation.ValidationError;
24
import org.eclipse.rdf4j.common.net.ParsedIRI;
25
import org.eclipse.rdf4j.model.IRI;
26
import org.eclipse.rdf4j.model.Literal;
27
import org.eclipse.rdf4j.model.Value;
28
import org.eclipse.rdf4j.model.ValueFactory;
29
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
30
import org.eclipse.rdf4j.model.util.Literals;
31
import org.eclipse.rdf4j.model.vocabulary.XSD;
32
import org.nanopub.Nanopub;
33
import org.nanopub.NanopubUtils;
34
import org.nanopub.SimpleCreatorPattern;
35
import org.nanopub.vocabulary.NTEMPLATE;
36
import org.slf4j.Logger;
37
import org.slf4j.LoggerFactory;
38

39
import java.net.URISyntaxException;
40
import java.net.URLEncoder;
41
import java.util.HashMap;
42
import java.util.Map;
43

44
/**
45
 * ReadonlyItem is a component that displays a read-only item in the form.
46
 */
47
public class ReadonlyItem extends AbstractContextComponent {
48

49
    private static final int LONG_LITERAL_LENGTH = 100;
50
    private static final Logger logger = LoggerFactory.getLogger(ReadonlyItem.class);
×
51
    private static final ValueFactory vf = SimpleValueFactory.getInstance();
×
52

53
    private IModel<String> model;
54
    private String prefix;
55
    private ExternalLink linkComp;
56
    private Label extraComp, languageComp, datatypeComp;
57
    private IModel<String> extraModel, languageModel, datatypeModel;
58
    private IRI iri;
59
    private RestrictedChoice restrictedChoice;
60
    private Label showMoreLabelLiteral, showMoreLabelHTML;
61
    private final Template template;
62

63
    /**
64
     * Constructor for ReadonlyItem.
65
     *
66
     * @param id              the component id
67
     * @param parentId        the parent id (e.g., "subj", "obj")
68
     * @param iriP            the IRI of the item
69
     * @param statementPartId the statement part ID
70
     * @param rg              the repetition group
71
     */
72
    public ReadonlyItem(String id, String parentId, final IRI iriP, IRI statementPartId, final RepetitionGroup rg) {
73
        super(id, rg.getContext());
×
74
        this.iri = iriP;
×
75
        template = context.getTemplate();
×
76
        model = (IModel<String>) context.getComponentModels().get(iri);
×
77
        if (model == null) {
×
78
            model = Model.of("");
×
79
            context.getComponentModels().put(iri, model);
×
80
        }
81
        String postfix = Utils.getUriPostfix(iri);
×
82
        if (context.hasParam(postfix)) {
×
83
            model.setObject(context.getParam(postfix));
×
84
        }
85

86
        final Map<String, String> foafNameMap;
87
        if (context.getExistingNanopub() == null) {
×
88
            foafNameMap = new HashMap<>();
×
89
        } else {
90
            foafNameMap = Utils.getFoafNameMap(context.getExistingNanopub());
×
91
        }
92

93
        prefix = template.getPrefix(iri);
×
94
        if (prefix == null) prefix = "";
×
95
        if (template.isRestrictedChoicePlaceholder(iri)) {
×
96
            restrictedChoice = new RestrictedChoice(iri, context);
×
97
        }
98
        add(new Label("prefix", new Model<String>() {
×
99

100
            @Override
101
            public String getObject() {
102
                String prefixLabel = template.getPrefixLabel(iri);
×
103
                String v = getFullValue();
×
104
                if (prefixLabel == null || User.isUser(v) || foafNameMap.containsKey(v)) {
×
105
                    return "";
×
106
                } else {
107
                    if (!prefixLabel.isEmpty() && parentId.equals("subj") && !prefixLabel.matches("https?://.*")) {
×
108
                        // Capitalize first letter of label if at subject position:
109
                        prefixLabel = prefixLabel.substring(0, 1).toUpperCase() + prefixLabel.substring(1);
×
110
                    }
111
                    return prefixLabel;
×
112
                }
113
            }
114

115
        }));
116

117
        linkComp = new ExternalLink("link", new Model<String>() {
×
118

119
            @Override
120
            public String getObject() {
121
                String obj = getFullValue();
×
122
                if (obj == null) return "";
×
123
                if (obj.equals(LocalUri.of("nanopub").stringValue())) {
×
124
                    if (context.getExistingNanopub() != null) {
×
125
                        obj = context.getExistingNanopub().getUri().stringValue();
×
126
                        return ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(obj, Charsets.UTF_8);
×
127
                    } else {
128
                        return "";
×
129
                    }
130
                } else if (obj.equals(LocalUri.of("assertion").stringValue())) {
×
131
                    if (context.getExistingNanopub() != null) {
×
132
                        obj = context.getExistingNanopub().getAssertionUri().stringValue();
×
133
                        return ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(obj, Charsets.UTF_8);
×
134
                    } else {
135
                        return "";
×
136
                    }
137
                } else if (User.isUser(obj)) {
×
138
                    return UserPage.MOUNT_PATH + "?id=" + URLEncoder.encode(obj, Charsets.UTF_8);
×
139
                } else if (obj.matches("https?://.+")) {
×
140
                    return ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(obj, Charsets.UTF_8);
×
141
                } else {
142
                    return "";
×
143
                }
144
            }
145

146
        }, new Model<String>() {
×
147

148
            @Override
149
            public String getObject() {
150
                String obj = getFullValue();
×
151
                if (obj != null && obj.matches("https?://.+")) {
×
152
                    IRI objIri = vf.createIRI(obj);
×
153
                    if (iri.equals(NTEMPLATE.CREATOR_PLACEHOLDER)) {
×
154
                        // TODO We might want to introduce a "(you)" flag here at some point
155
                        return User.getShortDisplayName(objIri);
×
156
                    } else if (isAssertionValue(objIri)) {
×
157
                        if (context.getType() == ContextType.ASSERTION) {
×
158
                            return "this assertion";
×
159
                        } else {
160
                            return "the assertion above";
×
161
                        }
162
                    } else if (isNanopubValue(objIri)) {
×
163
                        return "this nanopublication";
×
164
                    } else if (User.isUser(obj)) {
×
165
                        return User.getShortDisplayName(objIri);
×
166
                    } else if (foafNameMap.containsKey(obj)) {
×
167
                        return foafNameMap.get(obj);
×
168
                    }
169
                    return getLabelString(objIri);
×
170
                }
171
                return obj;
×
172
            }
173

174
        });
175
        if (template.isIntroducedResource(iri) || template.isEmbeddedResource(iri)) {
×
176
            linkComp.add(AttributeAppender.append("class", "introduced"));
×
177
        }
178
        add(linkComp);
×
179
        add(new Label("description", new Model<String>() {
×
180

181
            @Override
182
            public String getObject() {
183
                String obj = getFullValue();
×
184
                if (obj != null && obj.matches("https?://.+")) {
×
185
                    IRI objIri = vf.createIRI(obj);
×
186
                    if (isAssertionValue(objIri)) {
×
187
                        return "This is the identifier for the assertion of this nanopublication.";
×
188
                    } else if (isNanopubValue(objIri)) {
×
189
                        return "This is the identifier for this whole nanopublication.";
×
190
                    } else if (context.isReadOnly() && obj.startsWith(context.getExistingNanopub().getUri().stringValue())) {
×
191
                        return "This is a local identifier minted within the nanopublication.";
×
192
                    }
193
                    String labelString = getLabelString(objIri);
×
194
                    String description = "";
×
195
                    if (labelString.contains(" - ")) description = labelString.replaceFirst("^.* - ", "");
×
196
                    return description;
×
197
                } else if (obj != null && obj.startsWith("\"")) {
×
198
                    return "(this is a literal)";
×
199
                }
200
                return "";
×
201
            }
202

203
        }));
204
        Model<String> uriModel = new Model<String>() {
×
205

206
            @Override
207
            public String getObject() {
208
                String obj = getFullValue();
×
209
                if (obj != null && obj.startsWith("\"")) return "";
×
210
                if (isAssertionValue(obj)) {
×
211
                    return getAssertionValue();
×
212
                } else if (isNanopubValue(obj)) {
×
213
                    return getNanopubValue();
×
214
                }
215
                return obj;
×
216
            }
217

218
        };
219
        add(Utils.getUriLink("uri", uriModel));
×
220
        extraModel = Model.of("");
×
221
        extraComp = new Label("extra", extraModel);
×
222
        extraComp.setVisible(false);
×
223
        add(extraComp);
×
224
        languageModel = Model.of("");
×
225
        languageComp = new Label("language", languageModel);
×
226
        languageComp.setVisible(false);
×
227
        add(languageComp);
×
228
        datatypeModel = Model.of("");
×
229
        datatypeComp = new Label("datatype", datatypeModel);
×
230
        datatypeComp.setVisible(false);
×
231
        add(datatypeComp);
×
232

233
        showMoreLabelLiteral = new Label("show-more-literal", "");
×
234
        add(showMoreLabelLiteral);
×
235
        showMoreLabelLiteral.setVisible(false);
×
236

237
        showMoreLabelHTML = new Label("show-more-html", "");
×
238
        add(showMoreLabelHTML);
×
239
        showMoreLabelHTML.setVisible(false);
×
240
    }
×
241

242
    /**
243
     * {@inheritDoc}
244
     */
245
    @Override
246
    public void fillFinished() {
247
        String obj = getFullValue();
×
248
        if (obj != null) {
×
249
            if (isAssertionValue(obj)) {
×
250
                linkComp.add(new AttributeAppender("class", "this-assertion"));
×
251
            } else if (isNanopubValue(obj)) {
×
252
                linkComp.add(new AttributeAppender("class", "this-nanopub"));
×
253
            } else if (context.getExistingNanopub() != null) {
×
254
                Nanopub np = context.getExistingNanopub();
×
255
                if (NanopubUtils.getIntroducedIriIds(np).contains(obj) || NanopubUtils.getEmbeddedIriIds(np).contains(obj)) {
×
256
                    linkComp.add(AttributeAppender.append("class", "introduced"));
×
257
                }
258
            }
259
        }
260
    }
×
261

262
    /**
263
     * {@inheritDoc}
264
     */
265
    @Override
266
    public void finalizeValues() {
267
    }
×
268

269
    private String getLabelString(IRI iri) {
270
        if (template.getLabel(iri) != null) {
×
271
            return template.getLabel(iri).replaceFirst(" - .*$", "");
×
272
        } else if (context.getLabel(iri) != null) {
×
273
            return context.getLabel(iri).replaceFirst(" - .*$", "");
×
274
        } else {
275
            return Utils.getShortNameFromURI(iri.stringValue());
×
276
        }
277
    }
278

279
    /**
280
     * {@inheritDoc}
281
     */
282
    @Override
283
    public void removeFromContext() {
284
        // Nothing to be done here.
285
    }
×
286

287
    private String getFullValue() {
288
        String s = model.getObject();
×
289
        if (s == null) return null;
×
290
        if (template.isAutoEscapePlaceholder(iri)) {
×
291
            s = Utils.urlEncode(s);
×
292
        }
293
        if (!prefix.isEmpty()) {
×
294
            s = prefix + s;
×
295
        }
296
        return s;
×
297
    }
298

299
    private boolean isNanopubValue(Object obj) {
300
        if (obj == null) return false;
×
301
        if (obj.toString().equals(LocalUri.of("nanopub").stringValue())) return true;
×
302
        if (context.getExistingNanopub() == null) return false;
×
303
        return obj.toString().equals(context.getExistingNanopub().getUri().stringValue());
×
304
    }
305

306
    private String getNanopubValue() {
307
        if (context.getExistingNanopub() != null) {
×
308
            return context.getExistingNanopub().getUri().stringValue();
×
309
        } else {
310
            return LocalUri.of("nanopub").stringValue();
×
311
        }
312
    }
313

314
    private boolean isAssertionValue(Object obj) {
315
        if (obj == null) return false;
×
316
        if (obj.toString().equals(LocalUri.of("assertion").stringValue())) return true;
×
317
        if (context.getExistingNanopub() == null) return false;
×
318
        return obj.toString().equals(context.getExistingNanopub().getAssertionUri().stringValue());
×
319
    }
320

321
    private String getAssertionValue() {
322
        if (context.getExistingNanopub() != null) {
×
323
            return context.getExistingNanopub().getAssertionUri().stringValue();
×
324
        } else {
325
            return LocalUri.of("assertion").stringValue();
×
326
        }
327
    }
328

329
    /**
330
     * {@inheritDoc}
331
     */
332
    @Override
333
    public boolean isUnifiableWith(Value v) {
334
        if (v == null) return true;
×
335
        if (v instanceof IRI) {
×
336
            String vs = v.stringValue();
×
337
            if (vs.equals(LocalUri.of("nanopub").stringValue())) {
×
338
                vs = getNanopubValue();
×
339
            } else if (vs.equals(LocalUri.of("assertion").stringValue())) {
×
340
                vs = getAssertionValue();
×
341
            }
342
            if (vs.startsWith(prefix)) vs = vs.substring(prefix.length());
×
343
//                        if (Utils.isLocalURI(vs)) vs = vs.replaceFirst("^" + LocalUri.PREFIX, "");
344
            if (template.isAutoEscapePlaceholder(iri)) {
×
345
                vs = Utils.urlDecode(vs);
×
346
            }
347
            Validatable<String> validatable = new Validatable<>(vs);
×
348
//                        if (template.isLocalResource(iri) && !Utils.isUriPostfix(vs)) {
349
//                                vs = Utils.getUriPostfix(vs);
350
//                        }
351
            new Validator().validate(validatable);
×
352
            if (!validatable.isValid()) {
×
353
                return false;
×
354
            }
355
            if (model.getObject().isEmpty()) {
×
356
                return true;
×
357
            }
358
            return vs.equals(model.getObject());
×
359
        } else if (v instanceof Literal vL) {
×
360
            if (template.getRegex(iri) != null && !v.stringValue().matches(template.getRegex(iri))) {
×
361
                return false;
×
362
            }
363
            String languagetag = template.getLanguageTag(iri);
×
364
            IRI datatype = template.getDatatype(iri);
×
365
            if (languagetag != null) {
×
366
                if (vL.getLanguage().isEmpty() || !Literals.normalizeLanguageTag(vL.getLanguage().get()).equals(languagetag)) {
×
367
                    return false;
×
368
                }
369
            } else if (datatype != null) {
×
370
                if (!vL.getDatatype().equals(datatype)) {
×
371
                    return false;
×
372
                }
373
            }
374
            if (linkComp.getDefaultModelObject() == null || linkComp.getDefaultModelObject().toString().isEmpty()) {
×
375
                return true;
×
376
            }
377
            return linkComp.getDefaultModelObject().equals("\"" + v.stringValue() + "\"");
×
378
        }
379
        return false;
×
380
    }
381

382
    /**
383
     * {@inheritDoc}
384
     */
385
    @Override
386
    public void unifyWith(Value v) throws UnificationException {
387
        if (v == null) return;
×
388
        String vs = v.stringValue();
×
389
        if (!isUnifiableWith(v)) {
×
390
            logger.error("Cannot unify {}", v);
×
391
            throw new UnificationException(vs);
×
392
        }
393
        if (v instanceof IRI) {
×
394
            if (vs.equals(LocalUri.of("nanopub").stringValue())) {
×
395
                vs = getNanopubValue();
×
396
            } else if (vs.equals(LocalUri.of("assertion").stringValue())) {
×
397
                vs = getAssertionValue();
×
398
            }
399
            if (!prefix.isEmpty() && vs.startsWith(prefix)) {
×
400
                vs = vs.substring(prefix.length());
×
401
                // With read-only items, we don't need preliminary local identifiers:
402
//                        } else if (Utils.isLocalURI(vs)) {
403
//                                vs = vs.replaceFirst("^" + LocalUri.PREFIX, "");
404
//                        } else if (template.isLocalResource(iri) && !Utils.isUriPostfix(vs)) {
405
//                                vs = Utils.getUriPostfix(vs);
406
            }
407
            if (template.isAutoEscapePlaceholder(iri)) {
×
408
                vs = Utils.urlDecode(vs);
×
409
            }
410
            model.setObject(vs);
×
411
        } else if (v instanceof Literal vL) {
×
412
            if (vs.length() >= LONG_LITERAL_LENGTH) {
×
413
                linkComp.add(AttributeAppender.append("class", "long-literal collapsed"));
×
414
                showMoreLabelLiteral.setVisible(true);
×
415
            }
416
            if (vL.getLanguage().isPresent()) {
×
417
                model.setObject("\"" + vs + "\"");
×
418
                languageModel.setObject("(" + Literals.normalizeLanguageTag(vL.getLanguage().get()) + ")");
×
419
                languageComp.setVisible(true);
×
420
            } else if (!vL.getDatatype().equals(XSD.STRING)) {
×
421
                model.setObject("\"" + vs + "\"");
×
422
                datatypeModel.setObject("(" + vL.getDatatype().stringValue().replace(XSD.NAMESPACE, "xsd:") + ")");
×
423
                datatypeComp.setVisible(true);
×
424
            } else {
425
                model.setObject("\"" + vs + "\"");
×
426
            }
427
            if (Utils.looksLikeHtml(vs)) {
×
428
                linkComp.setVisible(false);
×
429
                extraModel.setObject(Utils.sanitizeHtml(vs));
×
430
                extraComp.setEscapeModelStrings(false);
×
431
                extraComp.setVisible(true);
×
432
                showMoreLabelLiteral.setVisible(false);
×
433
                showMoreLabelHTML.setVisible(true);
×
434
            }
435
        }
436
    }
×
437

438
    /**
439
     * Validator class for validating the input.
440
     */
441
    protected class Validator extends InvalidityHighlighting implements IValidator<String> {
442

443
        /**
444
         * Default constructor for Validator.
445
         */
446
        public Validator() {
×
447
        }
×
448

449
        @Override
450
        public void validate(IValidatable<String> s) {
451
            String sv = s.getValue();
×
452
            String p = prefix;
×
453
            if (template.isAutoEscapePlaceholder(iri)) {
×
454
                sv = Utils.urlEncode(sv);
×
455
            }
456
            if (sv.matches("https?://.+")) {
×
457
                p = "";
×
458
            } else if (sv.contains(":")) {
×
459
                s.error(new ValidationError("Colon character is not allowed in postfix"));
×
460
            }
461
            String iriString = p + sv;
×
462
            if (iriString.matches("[^:# ]+")) {
×
463
                p = LocalUri.PREFIX;
×
464
                iriString = p + sv;
×
465
            }
466
            try {
467
                ParsedIRI piri = new ParsedIRI(iriString);
×
468
                if (!piri.isAbsolute()) {
×
469
                    s.error(new ValidationError("IRI not well-formed"));
×
470
                }
471
                if (p.isEmpty() && !Utils.isLocalURI(sv) && !sv.matches("https?://.+")) {
×
472
                    s.error(new ValidationError("Only http(s):// IRIs are allowed here"));
×
473
                }
474
            } catch (URISyntaxException ex) {
×
475
                s.error(new ValidationError("IRI not well-formed"));
×
476
            }
×
477
            String regex = template.getRegex(iri);
×
478
            if (regex != null) {
×
479
                if (!sv.matches(regex)) {
×
480
                    s.error(new ValidationError("Value '" + sv + "' doesn't match the pattern '" + regex + "'"));
×
481
                }
482
            }
483
            if (template.isRestrictedChoicePlaceholder(iri)) {
×
484
                if (!restrictedChoice.getPossibleValues().contains(iriString) && !restrictedChoice.hasPossibleRefValues()) {
×
485
                    // not checking the possible ref values can overgenerate, but normally works
486
                    s.error(new ValidationError("Invalid choice"));
×
487
                }
488
            }
489
            if (template.isExternalUriPlaceholder(iri)) {
×
490
                if (!iriString.matches("https?://.+")) {
×
491
                    s.error(new ValidationError("Not an external IRI"));
×
492
                }
493
            }
494
            if (template.isTrustyUriPlaceholder(iri)) {
×
495
                if (!TrustyUriUtils.isPotentialTrustyUri(iriString)) {
×
496
                    s.error(new ValidationError("Not a trusty URI"));
×
497
                }
498
            }
499
            if (iri.equals(NTEMPLATE.CREATOR_PLACEHOLDER) && context.getExistingNanopub() != null) {
×
500
                boolean found = false;
×
501
                for (IRI creator : SimpleCreatorPattern.getCreators(context.getExistingNanopub())) {
×
502
                    if (creator.stringValue().equals(iriString)) {
×
503
                        found = true;
×
504
                        break;
×
505
                    }
506
                }
×
507
                if (!found) {
×
508
                    s.error(new ValidationError("Not a creator of nanopub"));
×
509
                }
510
            }
511
        }
×
512

513
    }
514

515
    /**
516
     * <p>toString.</p>
517
     *
518
     * @return a {@link java.lang.String} object
519
     */
520
    public String toString() {
521
        return "[read-only IRI item: " + iri + "]";
×
522
    }
523

524
}
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