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

knowledgepixels / nanodash / 19291868322

12 Nov 2025 09:00AM UTC coverage: 14.211% (+0.3%) from 13.89%
19291868322

push

github

tkuhn
feat(PublishForm): Support for loading values from query result

537 of 4736 branches covered (11.34%)

Branch coverage included in aggregate %.

1377 of 8732 relevant lines covered (15.77%)

0.71 hits per line

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

68.92
src/main/java/com/knowledgepixels/nanodash/Utils.java
1
package com.knowledgepixels.nanodash;
2

3
import com.google.common.collect.ArrayListMultimap;
4
import com.google.common.collect.Multimap;
5
import com.google.common.hash.Hashing;
6
import net.trustyuri.TrustyUriUtils;
7
import org.apache.commons.codec.Charsets;
8
import org.apache.commons.exec.environment.EnvironmentUtils;
9
import org.apache.commons.lang.StringUtils;
10
import org.apache.http.NameValuePair;
11
import org.apache.http.client.utils.URIBuilder;
12
import org.apache.http.client.utils.URLEncodedUtils;
13
import org.apache.wicket.markup.html.link.ExternalLink;
14
import org.apache.wicket.model.IModel;
15
import org.apache.wicket.request.mapper.parameter.PageParameters;
16
import org.apache.wicket.util.string.StringValue;
17
import org.eclipse.rdf4j.model.IRI;
18
import org.eclipse.rdf4j.model.Literal;
19
import org.eclipse.rdf4j.model.Statement;
20
import org.eclipse.rdf4j.model.ValueFactory;
21
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
22
import org.eclipse.rdf4j.model.util.Literals;
23
import org.eclipse.rdf4j.model.vocabulary.FOAF;
24
import org.eclipse.rdf4j.model.vocabulary.XSD;
25
import org.nanopub.Nanopub;
26
import org.nanopub.NanopubUtils;
27
import org.nanopub.extra.security.KeyDeclaration;
28
import org.nanopub.extra.security.MalformedCryptoElementException;
29
import org.nanopub.extra.security.NanopubSignatureElement;
30
import org.nanopub.extra.security.SignatureUtils;
31
import org.nanopub.extra.server.GetNanopub;
32
import org.nanopub.extra.services.ApiResponseEntry;
33
import org.nanopub.extra.services.QueryRef;
34
import org.nanopub.extra.setting.IntroNanopub;
35
import org.nanopub.vocabulary.FIP;
36
import org.nanopub.vocabulary.NPX;
37
import org.owasp.html.HtmlPolicyBuilder;
38
import org.owasp.html.PolicyFactory;
39
import org.slf4j.Logger;
40
import org.slf4j.LoggerFactory;
41
import org.wicketstuff.select2.Select2Choice;
42

43
import java.io.IOException;
44
import java.io.Serializable;
45
import java.net.URISyntaxException;
46
import java.net.URLDecoder;
47
import java.net.URLEncoder;
48
import java.nio.charset.StandardCharsets;
49
import java.util.*;
50

51
import static java.nio.charset.StandardCharsets.UTF_8;
52

53
/**
54
 * Utility class providing various helper methods for handling nanopublications, URIs, and other related functionalities.
55
 */
56
public class Utils {
57

58
    private Utils() {
59
    }  // no instances allowed
60

61
    /**
62
     * ValueFactory instance for creating RDF model objects.
63
     */
64
    public static final ValueFactory vf = SimpleValueFactory.getInstance();
2✔
65
    private static final Logger logger = LoggerFactory.getLogger(Utils.class);
3✔
66

67
    /**
68
     * Generates a short name from a given IRI object.
69
     *
70
     * @param uri the IRI object
71
     * @return a short representation of the URI
72
     */
73
    public static String getShortNameFromURI(IRI uri) {
74
        return getShortNameFromURI(uri.stringValue());
4✔
75
    }
76

77
    /**
78
     * Generates a short name from a given URI string.
79
     *
80
     * @param uri the URI string
81
     * @return a short representation of the URI
82
     */
83
    public static String getShortNameFromURI(String uri) {
84
        if (uri.startsWith("https://doi.org/") || uri.startsWith("http://dx.doi.org/")) {
8✔
85
            return uri.replaceFirst("^https?://(dx\\.)?doi.org/", "doi:");
5✔
86
        }
87
        uri = uri.replaceFirst("\\?.*$", "");
5✔
88
        uri = uri.replaceFirst("[/#]$", "");
5✔
89
        uri = uri.replaceFirst("^.*[/#]([^/#]*)[/#]([0-9]+)$", "$1/$2");
5✔
90
        if (uri.contains("#")) {
4✔
91
            uri = uri.replaceFirst("^.*#(.*[^0-9].*)$", "$1");
6✔
92
        } else {
93
            uri = uri.replaceFirst("^.*/([^/]*[^0-9/][^/]*)$", "$1");
5✔
94
        }
95
        uri = uri.replaceFirst("((^|[^A-Za-z0-9\\-_])RA[A-Za-z0-9\\-_]{8})[A-Za-z0-9\\-_]{35}$", "$1");
5✔
96
        uri = uri.replaceFirst("(^|[^A-Za-z0-9\\-_])RA[A-Za-z0-9\\-_]{43}[^A-Za-z0-9\\-_](.+)$", "$2");
5✔
97
        uri = URLDecoder.decode(uri, UTF_8);
4✔
98
        return uri;
2✔
99
    }
100

101
    /**
102
     * Generates a short nanopublication ID from a given nanopublication ID or URI.
103
     *
104
     * @param npId the nanopublication ID or URI
105
     * @return the first 10 characters of the artifact code
106
     */
107
    public static String getShortNanopubId(Object npId) {
108
        return TrustyUriUtils.getArtifactCode(npId.toString()).substring(0, 10);
7✔
109
    }
110

111
    private static Map<String, Nanopub> nanopubs = new HashMap<>();
4✔
112

113
    /**
114
     * Retrieves a Nanopub object based on the given URI or artifact code.
115
     *
116
     * @param uriOrArtifactCode the URI or artifact code of the nanopublication
117
     * @return the Nanopub object, or null if not found
118
     */
119
    public static Nanopub getNanopub(String uriOrArtifactCode) {
120
        String artifactCode = GetNanopub.getArtifactCode(uriOrArtifactCode);
3✔
121
        if (!nanopubs.containsKey(artifactCode)) {
4✔
122
            for (int i = 0; i < 3; i++) {  // Try 3 times to get nanopub
5!
123
                Nanopub np = GetNanopub.get(artifactCode);
3✔
124
                if (np != null) {
2!
125
                    nanopubs.put(artifactCode, np);
5✔
126
                    break;
1✔
127
                }
128
            }
129
        }
130
        return nanopubs.get(artifactCode);
5✔
131
    }
132

133
    /**
134
     * URL-encodes the string representation of the given object using UTF-8 encoding.
135
     *
136
     * @param o the object to be URL-encoded
137
     * @return the URL-encoded string
138
     */
139
    public static String urlEncode(Object o) {
140
        return URLEncoder.encode((o == null ? "" : o.toString()), Charsets.UTF_8);
9✔
141
    }
142

143
    /**
144
     * URL-decodes the string representation of the given object using UTF-8 encoding.
145
     *
146
     * @param o the object to be URL-decoded
147
     * @return the URL-decoded string
148
     */
149
    public static String urlDecode(Object o) {
150
        return URLDecoder.decode((o == null ? "" : o.toString()), Charsets.UTF_8);
9✔
151
    }
152

153
    /**
154
     * Generates a URL with the given base and appends the provided PageParameters as query parameters.
155
     *
156
     * @param base       the base URL
157
     * @param parameters the PageParameters to append
158
     * @return the complete URL with parameters
159
     */
160
    public static String getUrlWithParameters(String base, PageParameters parameters) {
161
        try {
162
            URIBuilder u = new URIBuilder(base);
5✔
163
            for (String key : parameters.getNamedKeys()) {
11✔
164
                for (StringValue value : parameters.getValues(key)) {
12✔
165
                    if (!value.isNull()) u.addParameter(key, value.toString());
9!
166
                }
1✔
167
            }
1✔
168
            return u.build().toString();
4✔
169
        } catch (URISyntaxException ex) {
1✔
170
            logger.error("Could not build URL with parameters: {} {}", base, parameters, ex);
17✔
171
            return "/";
2✔
172
        }
173
    }
174

175
    /**
176
     * Generates a short name for a public key or public key hash.
177
     *
178
     * @param pubkeyOrPubkeyhash the public key (64 characters) or public key hash (40 characters)
179
     * @return a short representation of the public key or public key hash
180
     */
181
    public static String getShortPubkeyName(String pubkeyOrPubkeyhash) {
182
        if (pubkeyOrPubkeyhash.length() == 64) {
4!
183
            return pubkeyOrPubkeyhash.replaceFirst("^(.{8}).*$", "$1");
×
184
        } else {
185
            return pubkeyOrPubkeyhash.replaceFirst("^(.).{39}(.{5}).*$", "$1..$2..");
5✔
186
        }
187
    }
188

189
    /**
190
     * Generates a short label for a public key or public key hash, including its status (local or approved).
191
     *
192
     * @param pubkeyOrPubkeyhash the public key (64 characters) or public key hash (40 characters)
193
     * @param user               the IRI of the user associated with the public key
194
     * @return a short label indicating the public key and its status
195
     */
196
    public static String getShortPubkeyhashLabel(String pubkeyOrPubkeyhash, IRI user) {
197
        String s = getShortPubkeyName(pubkeyOrPubkeyhash);
×
198
        NanodashSession session = NanodashSession.get();
×
199
        List<String> l = new ArrayList<>();
×
200
        if (pubkeyOrPubkeyhash.equals(session.getPubkeyString()) || pubkeyOrPubkeyhash.equals(session.getPubkeyhash()))
×
201
            l.add("local");
×
202
        // TODO: Make this more efficient:
203
        String hashed = Utils.createSha256HexHash(pubkeyOrPubkeyhash);
×
204
        if (User.getPubkeyhashes(user, true).contains(pubkeyOrPubkeyhash) || User.getPubkeyhashes(user, true).contains(hashed))
×
205
            l.add("approved");
×
206
        if (!l.isEmpty()) s += " (" + String.join("/", l) + ")";
×
207
        return s;
×
208
    }
209

210
    /**
211
     * Retrieves the name of the public key location based on the public key.
212
     *
213
     * @param pubkeyhash the public key string
214
     * @return the name of the public key location
215
     */
216
    public static String getPubkeyLocationName(String pubkeyhash) {
217
        return getPubkeyLocationName(pubkeyhash, getShortPubkeyName(pubkeyhash));
×
218
    }
219

220
    /**
221
     * Retrieves the name of the public key location, or returns a fallback name if not found.
222
     * If the key location is localhost, it returns "localhost".
223
     *
224
     * @param pubkeyhash the public key string
225
     * @param fallback   the fallback name to return if the key location is not found
226
     * @return the name of the public key location or the fallback name
227
     */
228
    public static String getPubkeyLocationName(String pubkeyhash, String fallback) {
229
        IRI keyLocation = User.getUserData().getKeyLocationForPubkeyhash(pubkeyhash);
×
230
        if (keyLocation == null) return fallback;
×
231
        if (keyLocation.stringValue().equals("http://localhost:37373/")) return "localhost";
×
232
        return keyLocation.stringValue().replaceFirst("https?://(nanobench\\.)?(nanodash\\.)?(.*[^/])/?$", "$3");
×
233
    }
234

235
    /**
236
     * Generates a short label for a public key location, including its status (local or approved).
237
     *
238
     * @param pubkeyhash the public key string
239
     * @param user       the IRI of the user associated with the public key
240
     * @return a short label indicating the public key location and its status
241
     */
242
    public static String getShortPubkeyLocationLabel(String pubkeyhash, IRI user) {
243
        String s = getPubkeyLocationName(pubkeyhash);
×
244
        NanodashSession session = NanodashSession.get();
×
245
        List<String> l = new ArrayList<>();
×
246
        if (pubkeyhash.equals(session.getPubkeyhash())) l.add("local");
×
247
        // TODO: Make this more efficient:
248
        if (User.getPubkeyhashes(user, true).contains(pubkeyhash)) l.add("approved");
×
249
        if (!l.isEmpty()) s += " (" + String.join("/", l) + ")";
×
250
        return s;
×
251
    }
252

253
    /**
254
     * Checks if a given public key has a Nanodash location.
255
     * A Nanodash location is identified by specific keywords in the key location.
256
     *
257
     * @param pubkeyhash the public key to check
258
     * @return true if the public key has a Nanodash location, false otherwise
259
     */
260
    public static boolean hasNanodashLocation(String pubkeyhash) {
261
        IRI keyLocation = User.getUserData().getKeyLocationForPubkeyhash(pubkeyhash);
×
262
        if (keyLocation == null) return true; // potentially a Nanodash location
×
263
        if (keyLocation.stringValue().contains("nanodash")) return true;
×
264
        if (keyLocation.stringValue().contains("nanobench")) return true;
×
265
        if (keyLocation.stringValue().contains(":37373")) return true;
×
266
        return false;
×
267
    }
268

269
    /**
270
     * Retrieves the short ORCID ID from an IRI object.
271
     *
272
     * @param orcidIri the IRI object representing the ORCID ID
273
     * @return the short ORCID ID as a string
274
     */
275
    public static String getShortOrcidId(IRI orcidIri) {
276
        return orcidIri.stringValue().replaceFirst("^https://orcid.org/", "");
6✔
277
    }
278

279
    /**
280
     * Retrieves the URI postfix from a given URI object.
281
     *
282
     * @param uri the URI object from which to extract the postfix
283
     * @return the URI postfix as a string
284
     */
285
    public static String getUriPostfix(Object uri) {
286
        String s = uri.toString();
3✔
287
        if (s.contains("#")) return s.replaceFirst("^.*#(.*)$", "$1");
9✔
288
        return s.replaceFirst("^.*/(.*)$", "$1");
5✔
289
    }
290

291
    /**
292
     * Retrieves the URI prefix from a given URI object.
293
     *
294
     * @param uri the URI object from which to extract the prefix
295
     * @return the URI prefix as a string
296
     */
297
    public static String getUriPrefix(Object uri) {
298
        String s = uri.toString();
3✔
299
        if (s.contains("#")) return s.replaceFirst("^(.*#).*$", "$1");
9✔
300
        return s.replaceFirst("^(.*/).*$", "$1");
5✔
301
    }
302

303
    /**
304
     * Checks if a given string is a valid URI postfix.
305
     * A valid URI postfix does not contain a colon (":").
306
     *
307
     * @param s the string to check
308
     * @return true if the string is a valid URI postfix, false otherwise
309
     */
310
    public static boolean isUriPostfix(String s) {
311
        return !s.contains(":");
8✔
312
    }
313

314
    /**
315
     * Retrieves the location of a given IntroNanopub.
316
     *
317
     * @param inp the IntroNanopub from which to extract the location
318
     * @return the IRI location of the nanopublication, or null if not found
319
     */
320
    public static IRI getLocation(IntroNanopub inp) {
321
        NanopubSignatureElement el = getNanopubSignatureElement(inp);
×
322
        for (KeyDeclaration kd : inp.getKeyDeclarations()) {
×
323
            if (el.getPublicKeyString().equals(kd.getPublicKeyString())) {
×
324
                return kd.getKeyLocation();
×
325
            }
326
        }
×
327
        return null;
×
328
    }
329

330
    /**
331
     * Retrieves the NanopubSignatureElement from a given IntroNanopub.
332
     *
333
     * @param inp the IntroNanopub from which to extract the signature element
334
     * @return the NanopubSignatureElement associated with the nanopublication
335
     */
336
    public static NanopubSignatureElement getNanopubSignatureElement(IntroNanopub inp) {
337
        try {
338
            return SignatureUtils.getSignatureElement(inp.getNanopub());
×
339
        } catch (MalformedCryptoElementException ex) {
×
340
            throw new RuntimeException(ex);
×
341
        }
342
    }
343

344
    /**
345
     * Retrieves a Nanopub object from a given URI if it is a potential Trusty URI.
346
     *
347
     * @param uri the URI to check and retrieve the Nanopub from
348
     * @return the Nanopub object if found, or null if not a known nanopublication
349
     */
350
    public static Nanopub getAsNanopub(String uri) {
351
        if (TrustyUriUtils.isPotentialTrustyUri(uri)) {
3!
352
            try {
353
                return Utils.getNanopub(uri);
3✔
354
            } catch (Exception ex) {
×
355
                logger.error("The given URI is not a known nanopublication: {}", uri, ex);
×
356
            }
357
        }
358
        return null;
×
359
    }
360

361
    private static final PolicyFactory htmlSanitizePolicy = new HtmlPolicyBuilder()
3✔
362
            .allowCommonBlockElements()
1✔
363
            .allowCommonInlineFormattingElements()
15✔
364
            .allowUrlProtocols("https", "http", "mailto")
7✔
365
            .allowElements("a")
7✔
366
            .allowAttributes("href").onElements("a")
14✔
367
            .allowElements("img")
7✔
368
            .allowAttributes("src").onElements("img")
8✔
369
            .requireRelNofollowOnLinks()
1✔
370
            .toFactory();
2✔
371

372
    /**
373
     * Sanitizes raw HTML input to ensure safe rendering.
374
     *
375
     * @param rawHtml the raw HTML input to sanitize
376
     * @return sanitized HTML string
377
     */
378
    public static String sanitizeHtml(String rawHtml) {
379
        return htmlSanitizePolicy.sanitize(rawHtml);
4✔
380
    }
381

382
    /**
383
     * Converts PageParameters to a URL-encoded string representation.
384
     *
385
     * @param params the PageParameters to convert
386
     * @return a string representation of the parameters in URL-encoded format
387
     */
388
    public static String getPageParametersAsString(PageParameters params) {
389
        String s = "";
2✔
390
        for (String n : params.getNamedKeys()) {
11✔
391
            if (!s.isEmpty()) s += "&";
6✔
392
            s += n + "=" + URLEncoder.encode(params.get(n).toString(), Charsets.UTF_8);
10✔
393
        }
1✔
394
        return s;
2✔
395
    }
396

397
    /**
398
     * Sets a minimal escape markup function for a Select2Choice component.
399
     * This function replaces certain characters and formats the display of choices.
400
     *
401
     * @param selectItem the Select2Choice component to set the escape markup for
402
     */
403
    public static void setSelect2ChoiceMinimalEscapeMarkup(Select2Choice<?> selectItem) {
404
        selectItem.getSettings().setEscapeMarkup("function(markup) {" +
×
405
                                                 "return markup" +
406
                                                 ".replaceAll('<','&lt;').replaceAll('>', '&gt;')" +
407
                                                 ".replace(/^(.*?) - /, '<span class=\"term\">$1</span><br>')" +
408
                                                 ".replace(/\\((https?:[\\S]+)\\)$/, '<br><code>$1</code>')" +
409
                                                 ".replace(/^([^<].*)$/, '<span class=\"term\">$1</span>')" +
410
                                                 ";}"
411
        );
412
    }
×
413

414
    /**
415
     * Checks if a nanopublication is of a specific class.
416
     *
417
     * @param np       the nanopublication to check
418
     * @param classIri the IRI of the class to check against
419
     * @return true if the nanopublication is of the specified class, false otherwise
420
     */
421
    public static boolean isNanopubOfClass(Nanopub np, IRI classIri) {
422
        return NanopubUtils.getTypes(np).contains(classIri);
5✔
423
    }
424

425
    /**
426
     * Checks if a nanopublication uses a specific predicate in its assertion.
427
     *
428
     * @param np           the nanopublication to check
429
     * @param predicateIri the IRI of the predicate to look for
430
     * @return true if the predicate is used in the assertion, false otherwise
431
     */
432
    public static boolean usesPredicateInAssertion(Nanopub np, IRI predicateIri) {
433
        for (Statement st : np.getAssertion()) {
11✔
434
            if (predicateIri.equals(st.getPredicate())) {
5✔
435
                return true;
2✔
436
            }
437
        }
1✔
438
        return false;
2✔
439
    }
440

441
    /**
442
     * Retrieves a map of FOAF names from the nanopublication's pubinfo.
443
     *
444
     * @param np the nanopublication from which to extract FOAF names
445
     * @return a map where keys are subjects and values are FOAF names
446
     */
447
    public static Map<String, String> getFoafNameMap(Nanopub np) {
448
        Map<String, String> foafNameMap = new HashMap<>();
4✔
449
        for (Statement st : np.getPubinfo()) {
11✔
450
            if (st.getPredicate().equals(FOAF.NAME) && st.getObject() instanceof Literal objL) {
14✔
451
                foafNameMap.put(st.getSubject().stringValue(), objL.stringValue());
8✔
452
            }
453
        }
1✔
454
        return foafNameMap;
2✔
455
    }
456

457
    /**
458
     * Creates an SHA-256 hash of the string representation of an object and returns it as a hexadecimal string.
459
     *
460
     * @param obj the object to hash
461
     * @return the SHA-256 hash of the object's string representation in hexadecimal format
462
     */
463
    public static String createSha256HexHash(Object obj) {
464
        return Hashing.sha256().hashString(obj.toString(), StandardCharsets.UTF_8).toString();
7✔
465
    }
466

467
    /**
468
     * Gets the types of a nanopublication.
469
     *
470
     * @param np the nanopublication from which to extract types
471
     * @return a list of IRI types associated with the nanopublication
472
     */
473
    public static List<IRI> getTypes(Nanopub np) {
474
        List<IRI> l = new ArrayList<>();
4✔
475
        for (IRI t : NanopubUtils.getTypes(np)) {
11✔
476
            if (t.equals(FIP.AVAILABLE_FAIR_ENABLING_RESOURCE)) continue;
5✔
477
            if (t.equals(FIP.FAIR_ENABLING_RESOURCE_TO_BE_DEVELOPED))
4✔
478
                continue;
1✔
479
            if (t.equals(FIP.AVAILABLE_FAIR_SUPPORTING_RESOURCE)) continue;
4!
480
            if (t.equals(FIP.FAIR_SUPPORTING_RESOURCE_TO_BE_DEVELOPED))
4!
481
                continue;
×
482
            l.add(t);
4✔
483
        }
1✔
484
        return l;
2✔
485
    }
486

487
    /**
488
     * Gets a label for a type IRI.
489
     *
490
     * @param typeIri the IRI of the type
491
     * @return a label for the type, potentially truncated
492
     */
493
    public static String getTypeLabel(IRI typeIri) {
494
        if (typeIri.equals(FIP.FAIR_ENABLING_RESOURCE)) return "FER";
6✔
495
        if (typeIri.equals(FIP.FAIR_SUPPORTING_RESOURCE)) return "FSR";
6✔
496
        if (typeIri.equals(FIP.FAIR_IMPLEMENTATION_PROFILE)) return "FIP";
6✔
497
        if (typeIri.equals(NPX.DECLARED_BY)) return "user intro";
6✔
498
        String l = typeIri.stringValue();
3✔
499
        l = l.replaceFirst("^.*[/#]([^/#]+)[/#]?$", "$1");
5✔
500
        l = l.replaceFirst("^(.+)Nanopub$", "$1");
5✔
501
        if (l.length() > 25) l = l.substring(0, 20) + "...";
10✔
502
        return l;
2✔
503
    }
504

505
    /**
506
     * Gets a label for a URI.
507
     *
508
     * @param uri the URI to get the label from
509
     * @return a label for the URI, potentially truncated
510
     */
511
    public static String getUriLabel(String uri) {
512
        if (uri == null) return "";
4✔
513
        String uriLabel = uri;
2✔
514
        if (uriLabel.matches(".*[^A-Za-z0-9-_]RA[A-Za-z0-9-_]{43}([^A-Za-z0-9-_].*)?")) {
4✔
515
            String newUriLabel = uriLabel.replaceFirst("(.*[^A-Za-z0-9-_]RA[A-Za-z0-9-_]{8})[A-Za-z0-9-_]{35}([^A-Za-z0-9-_].*)?", "$1...$2");
5✔
516
            if (newUriLabel.length() <= 70) return newUriLabel;
6!
517
        }
518
        if (uriLabel.length() > 70) return uri.substring(0, 30) + "..." + uri.substring(uri.length() - 30);
16✔
519
        return uriLabel;
2✔
520
    }
521

522
    /**
523
     * Gets an ExternalLink with a URI label.
524
     *
525
     * @param markupId the markup ID for the link
526
     * @param uri      the URI to link to
527
     * @return an ExternalLink with the URI label
528
     */
529
    public static ExternalLink getUriLink(String markupId, String uri) {
530
        return new ExternalLink(markupId, (Utils.isLocalURI(uri) ? "" : uri), getUriLabel(uri));
13✔
531
    }
532

533
    /**
534
     * Gets an ExternalLink with a model for the URI label.
535
     *
536
     * @param markupId the markup ID for the link
537
     * @param model    the model containing the URI
538
     * @return an ExternalLink with the URI label
539
     */
540
    public static ExternalLink getUriLink(String markupId, IModel<String> model) {
541
        return new ExternalLink(markupId, model, new UriLabelModel(model));
×
542
    }
543

544
    private static class UriLabelModel implements IModel<String> {
545

546
        private IModel<String> uriModel;
547

548
        public UriLabelModel(IModel<String> uriModel) {
×
549
            this.uriModel = uriModel;
×
550
        }
×
551

552
        @Override
553
        public String getObject() {
554
            return getUriLabel(uriModel.getObject());
×
555
        }
556

557
    }
558

559
    /**
560
     * Creates a sublist from a list based on the specified indices.
561
     *
562
     * @param list      the list from which to create the sublist
563
     * @param fromIndex the starting index (inclusive) for the sublist
564
     * @param toIndex   the ending index (exclusive) for the sublist
565
     * @param <E>       the type of elements in the list
566
     * @return an ArrayList containing the elements from the specified range
567
     */
568
    public static <E> ArrayList<E> subList(List<E> list, long fromIndex, long toIndex) {
569
        // So the resulting list is serializable:
570
        return new ArrayList<E>(list.subList((int) fromIndex, (int) toIndex));
×
571
    }
572

573
    /**
574
     * Creates a sublist from an array based on the specified indices.
575
     *
576
     * @param array     the array from which to create the sublist
577
     * @param fromIndex the starting index (inclusive) for the sublist
578
     * @param toIndex   the ending index (exclusive) for the sublist
579
     * @param <E>       the type of elements in the array
580
     * @return an ArrayList containing the elements from the specified range
581
     */
582
    public static <E> ArrayList<E> subList(E[] array, long fromIndex, long toIndex) {
583
        return subList(Arrays.asList(array), fromIndex, toIndex);
×
584
    }
585

586
    /**
587
     * Comparator for sorting ApiResponseEntry objects based on a specified field.
588
     */
589
    // TODO Move this to ApiResponseEntry class?
590
    public static class ApiResponseEntrySorter implements Comparator<ApiResponseEntry>, Serializable {
591

592
        private String field;
593
        private boolean descending;
594

595
        /**
596
         * Constructor for ApiResponseEntrySorter.
597
         *
598
         * @param field      the field to sort by
599
         * @param descending if true, sorts in descending order; if false, sorts in ascending order
600
         */
601
        public ApiResponseEntrySorter(String field, boolean descending) {
×
602
            this.field = field;
×
603
            this.descending = descending;
×
604
        }
×
605

606
        /**
607
         * Compares two ApiResponseEntry objects based on the specified field.
608
         *
609
         * @param o1 the first object to be compared.
610
         * @param o2 the second object to be compared.
611
         * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
612
         */
613
        @Override
614
        public int compare(ApiResponseEntry o1, ApiResponseEntry o2) {
615
            if (descending) {
×
616
                return o2.get(field).compareTo(o1.get(field));
×
617
            } else {
618
                return o1.get(field).compareTo(o2.get(field));
×
619
            }
620
        }
621

622
    }
623

624
    /**
625
     * MIME type for TriG RDF format.
626
     */
627
    public static final String TYPE_TRIG = "application/trig";
628

629
    /**
630
     * MIME type for Jelly RDF format.
631
     */
632
    public static final String TYPE_JELLY = "application/x-jelly-rdf";
633

634
    /**
635
     * MIME type for JSON-LD format.
636
     */
637
    public static final String TYPE_JSONLD = "application/ld+json";
638

639
    /**
640
     * MIME type for N-Quads format.
641
     */
642
    public static final String TYPE_NQUADS = "application/n-quads";
643

644
    /**
645
     * MIME type for Trix format.
646
     */
647
    public static final String TYPE_TRIX = "application/trix";
648

649
    /**
650
     * MIME type for HTML format.
651
     */
652
    public static final String TYPE_HTML = "text/html";
653

654
    /**
655
     * Comma-separated list of supported MIME types for nanopublications.
656
     */
657
    public static final String SUPPORTED_TYPES =
658
            TYPE_TRIG + "," +
659
            TYPE_JELLY + "," +
660
            TYPE_JSONLD + "," +
661
            TYPE_NQUADS + "," +
662
            TYPE_TRIX + "," +
663
            TYPE_HTML;
664

665
    /**
666
     * List of supported MIME types for nanopublications.
667
     */
668
    public static final List<String> SUPPORTED_TYPES_LIST = Arrays.asList(StringUtils.split(SUPPORTED_TYPES, ','));
6✔
669

670
    // TODO Move these to nanopub-java library:
671

672
    /**
673
     * Retrieves a set of introduced IRI IDs from the nanopublication.
674
     *
675
     * @param np the nanopublication from which to extract introduced IRI IDs
676
     * @return a set of introduced IRI IDs
677
     */
678
    public static Set<String> getIntroducedIriIds(Nanopub np) {
679
        Set<String> introducedIriIds = new HashSet<>();
4✔
680
        for (Statement st : np.getPubinfo()) {
11✔
681
            if (!st.getSubject().equals(np.getUri())) continue;
7✔
682
            if (!st.getPredicate().equals(NPX.INTRODUCES)) continue;
6✔
683
            if (st.getObject() instanceof IRI obj) introducedIriIds.add(obj.stringValue());
14✔
684
        }
1✔
685
        return introducedIriIds;
2✔
686
    }
687

688
    /**
689
     * Retrieves a set of embedded IRI IDs from the nanopublication.
690
     *
691
     * @param np the nanopublication from which to extract embedded IRI IDs
692
     * @return a set of embedded IRI IDs
693
     */
694
    public static Set<String> getEmbeddedIriIds(Nanopub np) {
695
        Set<String> embeddedIriIds = new HashSet<>();
4✔
696
        for (Statement st : np.getPubinfo()) {
11✔
697
            if (!st.getSubject().equals(np.getUri())) continue;
7✔
698
            if (!st.getPredicate().equals(NPX.EMBEDS)) continue;
6✔
699
            if (st.getObject() instanceof IRI obj) embeddedIriIds.add(obj.stringValue());
14✔
700
        }
1✔
701
        return embeddedIriIds;
2✔
702
    }
703

704
    /**
705
     * Returns the URL of the default Nanopub Registry as configured by the given instance.
706
     *
707
     * @return Nanopub Registry URL
708
     */
709
    public static String getMainRegistryUrl() {
710
        try {
711
            return EnvironmentUtils.getProcEnvironment().getOrDefault("NANODASH_MAIN_REGISTRY", "https://registry.knowledgepixels.com/");
6✔
712
        } catch (IOException ex) {
×
713
            logger.error("Could not get NANODASH_MAIN_REGISTRY environment variable, using default.", ex);
×
714
            return "https://registry.knowledgepixels.com/";
×
715
        }
716
    }
717

718
    private static final String PLAIN_LITERAL_PATTERN = "^\"(([^\\\\\\\"]|\\\\\\\\|\\\\\")*)\"";
719
    private static final String LANGTAG_LITERAL_PATTERN = "^\"(([^\\\\\\\"]|\\\\\\\\|\\\\\")*)\"@([0-9a-zA-Z-]{2,})$";
720
    private static final String DATATYPE_LITERAL_PATTERN = "^\"(([^\\\\\\\"]|\\\\\\\\|\\\\\")*)\"\\^\\^<([^ ><\"^]+)>";
721

722
    /**
723
     * Checks whether string is valid literal serialization.
724
     *
725
     * @param literalString the literal string
726
     * @return true if valid
727
     */
728
    public static boolean isValidLiteralSerialization(String literalString) {
729
        if (literalString.matches(PLAIN_LITERAL_PATTERN)) {
4✔
730
            return true;
2✔
731
        } else if (literalString.matches(LANGTAG_LITERAL_PATTERN)) {
4✔
732
            return true;
2✔
733
        } else if (literalString.matches(DATATYPE_LITERAL_PATTERN)) {
4✔
734
            return true;
2✔
735
        }
736
        return false;
2✔
737
    }
738

739
    /**
740
     * Returns a serialized version of the literal.
741
     *
742
     * @param literal the literal
743
     * @return the String serialization of the literal
744
     */
745
    public static String getSerializedLiteral(Literal literal) {
746
        if (literal.getLanguage().isPresent()) {
4✔
747
            return "\"" + getEscapedLiteralString(literal.stringValue()) + "\"@" + Literals.normalizeLanguageTag(literal.getLanguage().get());
10✔
748
        } else if (literal.getDatatype().equals(XSD.STRING)) {
5✔
749
            return "\"" + getEscapedLiteralString(literal.stringValue()) + "\"";
5✔
750
        } else {
751
            return "\"" + getEscapedLiteralString(literal.stringValue()) + "\"^^<" + literal.getDatatype() + ">";
8✔
752
        }
753
    }
754

755
    /**
756
     * Parses a serialized literal into a Literal object.
757
     *
758
     * @param serializedLiteral The serialized String of the literal
759
     * @return The parse Literal object
760
     */
761
    public static Literal getParsedLiteral(String serializedLiteral) {
762
        if (serializedLiteral.matches(PLAIN_LITERAL_PATTERN)) {
4✔
763
            return vf.createLiteral(getUnescapedLiteralString(serializedLiteral.replaceFirst(PLAIN_LITERAL_PATTERN, "$1")));
8✔
764
        } else if (serializedLiteral.matches(LANGTAG_LITERAL_PATTERN)) {
4✔
765
            String langtag = serializedLiteral.replaceFirst(LANGTAG_LITERAL_PATTERN, "$3");
5✔
766
            return vf.createLiteral(getUnescapedLiteralString(serializedLiteral.replaceFirst(LANGTAG_LITERAL_PATTERN, "$1")), langtag);
9✔
767
        } else if (serializedLiteral.matches(DATATYPE_LITERAL_PATTERN)) {
4✔
768
            IRI datatype = vf.createIRI(serializedLiteral.replaceFirst(DATATYPE_LITERAL_PATTERN, "$3"));
7✔
769
            return vf.createLiteral(getUnescapedLiteralString(serializedLiteral.replaceFirst(DATATYPE_LITERAL_PATTERN, "$1")), datatype);
9✔
770
        }
771
        throw new IllegalArgumentException("Not a valid literal serialization: " + serializedLiteral);
6✔
772
    }
773

774
    /**
775
     * Escapes quotes (") and slashes (/) of a literal string.
776
     *
777
     * @param unescapedString un-escaped string
778
     * @return escaped string
779
     */
780
    public static String getEscapedLiteralString(String unescapedString) {
781
        return unescapedString.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\"");
8✔
782
    }
783

784
    /**
785
     * Un-escapes quotes (") and slashes (/) of a literal string.
786
     *
787
     * @param escapedString escaped string
788
     * @return un-escaped string
789
     */
790
    public static String getUnescapedLiteralString(String escapedString) {
791
        return escapedString.replaceAll("\\\\(\\\\|\\\")", "$1");
5✔
792
    }
793

794
    /**
795
     * Checks if a given IRI is a local URI.
796
     *
797
     * @param uri the IRI to check
798
     * @return true if the IRI is a local URI, false otherwise
799
     */
800
    public static boolean isLocalURI(IRI uri) {
801
        return uri != null && isLocalURI(uri.stringValue());
10✔
802
    }
803

804
    /**
805
     * Checks if a given string is a local URI.
806
     *
807
     * @param uriAsString the string to check
808
     * @return true if the string is a local URI, false otherwise
809
     */
810
    public static boolean isLocalURI(String uriAsString) {
811
        return !uriAsString.isBlank() && uriAsString.startsWith(LocalUri.PREFIX);
11✔
812
    }
813

814
    // TODO Move this to QueryRef class in nanopub-java
815
    public static QueryRef parseQueryRef(String queryRefUrlString) {
816
        if (queryRefUrlString.contains("?")) {
×
817
            String queryName = queryRefUrlString.split("\\?")[0];
×
818
            Multimap<String,String> queryParams = ArrayListMultimap.create();
×
819
            for (NameValuePair nvp : URLEncodedUtils.parse(queryRefUrlString.split("\\?")[1], Charsets.UTF_8)) {
×
820
                queryParams.put(nvp.getName(), nvp.getValue());
×
821
            }
×
822
            return new QueryRef(queryName, queryParams);
×
823
        } else {
824
            return new QueryRef(queryRefUrlString);
×
825
        }
826
    }
827

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