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

knowledgepixels / nanodash / 18220763586

03 Oct 2025 11:16AM UTC coverage: 13.734% (+0.01%) from 13.724%
18220763586

push

github

tkuhn
style: Remove "I"/"me" and just show user name

446 of 4106 branches covered (10.86%)

Branch coverage included in aggregate %.

1154 of 7544 relevant lines covered (15.3%)

0.68 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.TemplateContext;
13
import com.knowledgepixels.nanodash.template.UnificationException;
14
import net.trustyuri.TrustyUriUtils;
15
import org.apache.commons.codec.Charsets;
16
import org.apache.wicket.behavior.AttributeAppender;
17
import org.apache.wicket.markup.html.basic.Label;
18
import org.apache.wicket.markup.html.link.ExternalLink;
19
import org.apache.wicket.markup.html.panel.Panel;
20
import org.apache.wicket.model.IModel;
21
import org.apache.wicket.model.Model;
22
import org.apache.wicket.validation.IValidatable;
23
import org.apache.wicket.validation.IValidator;
24
import org.apache.wicket.validation.Validatable;
25
import org.apache.wicket.validation.ValidationError;
26
import org.eclipse.rdf4j.common.net.ParsedIRI;
27
import org.eclipse.rdf4j.model.IRI;
28
import org.eclipse.rdf4j.model.Literal;
29
import org.eclipse.rdf4j.model.Value;
30
import org.eclipse.rdf4j.model.ValueFactory;
31
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
32
import org.eclipse.rdf4j.model.util.Literals;
33
import org.eclipse.rdf4j.model.vocabulary.XSD;
34
import org.nanopub.Nanopub;
35
import org.nanopub.SimpleCreatorPattern;
36
import org.nanopub.vocabulary.NTEMPLATE;
37
import org.slf4j.Logger;
38
import org.slf4j.LoggerFactory;
39

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

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

50
    // TODO: Make ContextComponent an abstract class with superclass Panel, and move the common code of the form items there.
51

52
    private static final int LONG_LITERAL_LENGTH = 100;
53
    private static final Logger logger = LoggerFactory.getLogger(ReadonlyItem.class);
×
54
    private static final ValueFactory vf = SimpleValueFactory.getInstance();
×
55

56
    private IModel<String> model;
57
    private TemplateContext context;
58
    private String prefix;
59
    private ExternalLink linkComp;
60
    private Label extraComp, languageComp, datatypeComp;
61
    private IModel<String> extraModel, languageModel, datatypeModel;
62
    private IRI iri;
63
    private RestrictedChoice restrictedChoice;
64
    private Label showMoreLabelLiteral, showMoreLabelHTML;
65
    private final Template template;
66

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

91
        final Map<String, String> foafNameMap;
92
        if (context.getExistingNanopub() == null) {
×
93
            foafNameMap = new HashMap<>();
×
94
        } else {
95
            foafNameMap = Utils.getFoafNameMap(context.getExistingNanopub());
×
96
        }
97

98
        prefix = template.getPrefix(iri);
×
99
        if (prefix == null) prefix = "";
×
100
        if (template.isRestrictedChoicePlaceholder(iri)) {
×
101
            restrictedChoice = new RestrictedChoice(iri, context);
×
102
        }
103
        add(new Label("prefix", new Model<String>() {
×
104

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

120
        }));
121

122
        linkComp = new ExternalLink("link", new Model<String>() {
×
123

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

151
        }, new Model<String>() {
×
152

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

179
        });
180
        if (template.isIntroducedResource(iri) || template.isEmbeddedResource(iri)) {
×
181
            linkComp.add(AttributeAppender.append("class", "introduced"));
×
182
        }
183
        add(linkComp);
×
184
        add(new Label("description", new Model<String>() {
×
185

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

208
        }));
209
        Model<String> uriModel = new Model<String>() {
×
210

211
            @Override
212
            public String getObject() {
213
                String obj = getFullValue();
×
214
                if (obj != null && obj.startsWith("\"")) return "";
×
215
                if (isAssertionValue(obj)) {
×
216
                    return getAssertionValue();
×
217
                } else if (isNanopubValue(obj)) {
×
218
                    return getNanopubValue();
×
219
                }
220
                return obj;
×
221
            }
222

223
        };
224
        add(Utils.getUriLink("uri", uriModel));
×
225
        extraModel = Model.of("");
×
226
        extraComp = new Label("extra", extraModel);
×
227
        extraComp.setVisible(false);
×
228
        add(extraComp);
×
229
        languageModel = Model.of("");
×
230
        languageComp = new Label("language", languageModel);
×
231
        languageComp.setVisible(false);
×
232
        add(languageComp);
×
233
        datatypeModel = Model.of("");
×
234
        datatypeComp = new Label("datatype", datatypeModel);
×
235
        datatypeComp.setVisible(false);
×
236
        add(datatypeComp);
×
237

238
        showMoreLabelLiteral = new Label("show-more-literal", "");
×
239
        add(showMoreLabelLiteral);
×
240
        showMoreLabelLiteral.setVisible(false);
×
241

242
        showMoreLabelHTML = new Label("show-more-html", "");
×
243
        add(showMoreLabelHTML);
×
244
        showMoreLabelHTML.setVisible(false);
×
245
    }
×
246

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

267
    /**
268
     * {@inheritDoc}
269
     */
270
    @Override
271
    public void finalizeValues() {
272
    }
×
273

274
    private String getLabelString(IRI iri) {
275
        if (template.getLabel(iri) != null) {
×
276
            return template.getLabel(iri).replaceFirst(" - .*$", "");
×
277
        } else if (context.getLabel(iri) != null) {
×
278
            return context.getLabel(iri).replaceFirst(" - .*$", "");
×
279
        } else {
280
            return IriItem.getShortNameFromURI(iri.stringValue());
×
281
        }
282
    }
283

284
    /**
285
     * {@inheritDoc}
286
     */
287
    @Override
288
    public void removeFromContext() {
289
        // Nothing to be done here.
290
    }
×
291

292
    private String getFullValue() {
293
        String s = model.getObject();
×
294
        if (s == null) return null;
×
295
        if (template.isAutoEscapePlaceholder(iri)) {
×
296
            s = Utils.urlEncode(s);
×
297
        }
298
        if (!prefix.isEmpty()) {
×
299
            s = prefix + s;
×
300
        }
301
        return s;
×
302
    }
303

304
    private boolean isNanopubValue(Object obj) {
305
        if (obj == null) return false;
×
306
        if (obj.toString().equals(LocalUri.PREFIX + "nanopub")) return true;
×
307
        if (context.getExistingNanopub() == null) return false;
×
308
        return obj.toString().equals(context.getExistingNanopub().getUri().stringValue());
×
309
    }
310

311
    private String getNanopubValue() {
312
        if (context.getExistingNanopub() != null) {
×
313
            return context.getExistingNanopub().getUri().stringValue();
×
314
        } else {
315
            return LocalUri.PREFIX + "nanopub";
×
316
        }
317
    }
318

319
    private boolean isAssertionValue(Object obj) {
320
        if (obj == null) return false;
×
321
        if (obj.toString().equals(LocalUri.PREFIX + "assertion")) return true;
×
322
        if (context.getExistingNanopub() == null) return false;
×
323
        return obj.toString().equals(context.getExistingNanopub().getAssertionUri().stringValue());
×
324
    }
325

326
    private String getAssertionValue() {
327
        if (context.getExistingNanopub() != null) {
×
328
            return context.getExistingNanopub().getAssertionUri().stringValue();
×
329
        } else {
330
            return LocalUri.PREFIX + "assertion";
×
331
        }
332
    }
333

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

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

444
    /**
445
     * Validator class for validating the input.
446
     */
447
    protected class Validator extends InvalidityHighlighting implements IValidator<String> {
448

449
        /**
450
         * Default constructor for Validator.
451
         */
452
        public Validator() {
×
453
        }
×
454

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

519
    }
520

521
    /**
522
     * <p>toString.</p>
523
     *
524
     * @return a {@link java.lang.String} object
525
     */
526
    public String toString() {
527
        return "[read-only IRI item: " + iri + "]";
×
528
    }
529

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