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

knowledgepixels / nanodash / 18941803064

30 Oct 2025 01:12PM UTC coverage: 14.299% (+0.1%) from 14.184%
18941803064

push

github

ashleycaselli
refactor(Utils): merge IriItem.getShortNameFromURI with Utils.getShortNameFromURI

512 of 4504 branches covered (11.37%)

Branch coverage included in aggregate %.

1331 of 8385 relevant lines covered (15.87%)

0.71 hits per line

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

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

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

38
import java.io.IOException;
39
import java.io.Serializable;
40
import java.net.URISyntaxException;
41
import java.net.URLDecoder;
42
import java.net.URLEncoder;
43
import java.nio.charset.StandardCharsets;
44
import java.util.*;
45

46
import static java.nio.charset.StandardCharsets.UTF_8;
47

48
/**
49
 * Utility class providing various helper methods for handling nanopublications, URIs, and other related functionalities.
50
 */
51
public class Utils {
52

53
    private Utils() {
54
    }  // no instances allowed
55

56
    /**
57
     * ValueFactory instance for creating RDF model objects.
58
     */
59
    public static final ValueFactory vf = SimpleValueFactory.getInstance();
2✔
60
    private static final Logger logger = LoggerFactory.getLogger(Utils.class);
3✔
61

62
    /**
63
     * Generates a short name from a given IRI object.
64
     *
65
     * @param uri the IRI object
66
     * @return a short representation of the URI
67
     */
68
    public static String getShortNameFromURI(IRI uri) {
69
        return getShortNameFromURI(uri.stringValue());
4✔
70
    }
71

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

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

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

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

128
    /**
129
     * Extracts the artifact code from a given URI or artifact code string.
130
     *
131
     * @param uriOrArtifactCode the URI or artifact code string
132
     * @return the extracted artifact code
133
     */
134
    public static String getArtifactCode(String uriOrArtifactCode) {
135
        return uriOrArtifactCode.replaceFirst("^.*(RA[0-9a-zA-Z\\-_]{43})(\\?.*)?$", "$1");
5✔
136
    }
137

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

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

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

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

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

215
    /**
216
     * Retrieves the name of the public key location based on the public key.
217
     *
218
     * @param pubkeyhash the public key string
219
     * @return the name of the public key location
220
     */
221
    public static String getPubkeyLocationName(String pubkeyhash) {
222
        return getPubkeyLocationName(pubkeyhash, getShortPubkeyName(pubkeyhash));
×
223
    }
224

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

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

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

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

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

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

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

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

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

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

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

377
    /**
378
     * Sanitizes raw HTML input to ensure safe rendering.
379
     *
380
     * @param rawHtml the raw HTML input to sanitize
381
     * @return sanitized HTML string
382
     */
383
    public static String sanitizeHtml(String rawHtml) {
384
        return htmlSanitizePolicy.sanitize(rawHtml);
4✔
385
    }
386

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

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

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

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

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

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

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

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

510
    /**
511
     * Gets a label for a URI.
512
     *
513
     * @param uri the URI to get the label from
514
     * @return a label for the URI, potentially truncated
515
     */
516
    public static String getUriLabel(String uri) {
517
        if (uri == null) return "";
4✔
518
        String uriLabel = uri;
2✔
519
        if (uriLabel.matches(".*[^A-Za-z0-9-_]RA[A-Za-z0-9-_]{43}([^A-Za-z0-9-_].*)?")) {
4✔
520
            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✔
521
            if (newUriLabel.length() <= 70) return newUriLabel;
6!
522
        }
523
        if (uriLabel.length() > 70) return uri.substring(0, 30) + "..." + uri.substring(uri.length() - 30);
16✔
524
        return uriLabel;
2✔
525
    }
526

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

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

549
    private static class UriLabelModel implements IModel<String> {
550

551
        private IModel<String> uriModel;
552

553
        public UriLabelModel(IModel<String> uriModel) {
×
554
            this.uriModel = uriModel;
×
555
        }
×
556

557
        @Override
558
        public String getObject() {
559
            return getUriLabel(uriModel.getObject());
×
560
        }
561

562
    }
563

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

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

591
    /**
592
     * Comparator for sorting ApiResponseEntry objects based on a specified field.
593
     */
594
    // TODO Move this to ApiResponseEntry class?
595
    public static class ApiResponseEntrySorter implements Comparator<ApiResponseEntry>, Serializable {
596

597
        private String field;
598
        private boolean descending;
599

600
        /**
601
         * Constructor for ApiResponseEntrySorter.
602
         *
603
         * @param field      the field to sort by
604
         * @param descending if true, sorts in descending order; if false, sorts in ascending order
605
         */
606
        public ApiResponseEntrySorter(String field, boolean descending) {
×
607
            this.field = field;
×
608
            this.descending = descending;
×
609
        }
×
610

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

627
    }
628

629
    /**
630
     * MIME type for TriG RDF format.
631
     */
632
    public static final String TYPE_TRIG = "application/trig";
633

634
    /**
635
     * MIME type for Jelly RDF format.
636
     */
637
    public static final String TYPE_JELLY = "application/x-jelly-rdf";
638

639
    /**
640
     * MIME type for JSON-LD format.
641
     */
642
    public static final String TYPE_JSONLD = "application/ld+json";
643

644
    /**
645
     * MIME type for N-Quads format.
646
     */
647
    public static final String TYPE_NQUADS = "application/n-quads";
648

649
    /**
650
     * MIME type for Trix format.
651
     */
652
    public static final String TYPE_TRIX = "application/trix";
653

654
    /**
655
     * MIME type for HTML format.
656
     */
657
    public static final String TYPE_HTML = "text/html";
658

659
    /**
660
     * Comma-separated list of supported MIME types for nanopublications.
661
     */
662
    public static final String SUPPORTED_TYPES =
663
            TYPE_TRIG + "," +
664
            TYPE_JELLY + "," +
665
            TYPE_JSONLD + "," +
666
            TYPE_NQUADS + "," +
667
            TYPE_TRIX + "," +
668
            TYPE_HTML;
669

670
    /**
671
     * List of supported MIME types for nanopublications.
672
     */
673
    public static final List<String> SUPPORTED_TYPES_LIST = Arrays.asList(StringUtils.split(SUPPORTED_TYPES, ','));
6✔
674

675
    // TODO Move these to nanopub-java library:
676

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

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

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

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

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

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

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

779
    /**
780
     * Escapes quotes (") and slashes (/) of a literal string.
781
     *
782
     * @param unescapedString un-escaped string
783
     * @return escaped string
784
     */
785
    public static String getEscapedLiteralString(String unescapedString) {
786
        return unescapedString.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\"");
8✔
787
    }
788

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

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

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

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