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

knowledgepixels / nanodash / 19862849016

02 Dec 2025 02:53PM UTC coverage: 15.019% (+0.08%) from 14.944%
19862849016

push

github

tkuhn
fix(Utils): Array length exception when "?" is found at end of string

579 of 4984 branches covered (11.62%)

Branch coverage included in aggregate %.

1539 of 9118 relevant lines covered (16.88%)

0.75 hits per line

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

68.15
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
import java.util.regex.Pattern;
51

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

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

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

62
    /**
63
     * ValueFactory instance for creating RDF model objects.
64
     */
65
    public static final ValueFactory vf = SimpleValueFactory.getInstance();
2✔
66
    private static final Logger logger = LoggerFactory.getLogger(Utils.class);
3✔
67
    private static final Pattern LEADING_TAG = Pattern.compile("^\\s*<(p|div|span|img)(\\s|>|/).*", Pattern.CASE_INSENSITIVE);
4✔
68

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

385
    /**
386
     * Checks if a given string is likely to be HTML content.
387
     *
388
     * @param value the string to check
389
     * @return true if the given string is HTML content, false otherwise
390
     */
391
    public static boolean looksLikeHtml(String value) {
392
        return LEADING_TAG.matcher(value).find();
×
393
    }
394

395
    /**
396
     * Converts PageParameters to a URL-encoded string representation.
397
     *
398
     * @param params the PageParameters to convert
399
     * @return a string representation of the parameters in URL-encoded format
400
     */
401
    public static String getPageParametersAsString(PageParameters params) {
402
        String s = "";
2✔
403
        for (String n : params.getNamedKeys()) {
11✔
404
            if (!s.isEmpty()) s += "&";
6✔
405
            s += n + "=" + URLEncoder.encode(params.get(n).toString(), Charsets.UTF_8);
10✔
406
        }
1✔
407
        return s;
2✔
408
    }
409

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

427
    /**
428
     * Checks if a nanopublication is of a specific class.
429
     *
430
     * @param np       the nanopublication to check
431
     * @param classIri the IRI of the class to check against
432
     * @return true if the nanopublication is of the specified class, false otherwise
433
     */
434
    public static boolean isNanopubOfClass(Nanopub np, IRI classIri) {
435
        return NanopubUtils.getTypes(np).contains(classIri);
5✔
436
    }
437

438
    /**
439
     * Checks if a nanopublication uses a specific predicate in its assertion.
440
     *
441
     * @param np           the nanopublication to check
442
     * @param predicateIri the IRI of the predicate to look for
443
     * @return true if the predicate is used in the assertion, false otherwise
444
     */
445
    public static boolean usesPredicateInAssertion(Nanopub np, IRI predicateIri) {
446
        for (Statement st : np.getAssertion()) {
11✔
447
            if (predicateIri.equals(st.getPredicate())) {
5✔
448
                return true;
2✔
449
            }
450
        }
1✔
451
        return false;
2✔
452
    }
453

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

470
    /**
471
     * Creates an SHA-256 hash of the string representation of an object and returns it as a hexadecimal string.
472
     *
473
     * @param obj the object to hash
474
     * @return the SHA-256 hash of the object's string representation in hexadecimal format
475
     */
476
    public static String createSha256HexHash(Object obj) {
477
        return Hashing.sha256().hashString(obj.toString(), StandardCharsets.UTF_8).toString();
7✔
478
    }
479

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

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

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

535
    /**
536
     * Gets an ExternalLink with a URI label.
537
     *
538
     * @param markupId the markup ID for the link
539
     * @param uri      the URI to link to
540
     * @return an ExternalLink with the URI label
541
     */
542
    public static ExternalLink getUriLink(String markupId, String uri) {
543
        return new ExternalLink(markupId, (Utils.isLocalURI(uri) ? "" : uri), getUriLabel(uri));
13✔
544
    }
545

546
    /**
547
     * Gets an ExternalLink with a model for the URI label.
548
     *
549
     * @param markupId the markup ID for the link
550
     * @param model    the model containing the URI
551
     * @return an ExternalLink with the URI label
552
     */
553
    public static ExternalLink getUriLink(String markupId, IModel<String> model) {
554
        return new ExternalLink(markupId, model, new UriLabelModel(model));
×
555
    }
556

557
    private static class UriLabelModel implements IModel<String> {
558

559
        private IModel<String> uriModel;
560

561
        public UriLabelModel(IModel<String> uriModel) {
×
562
            this.uriModel = uriModel;
×
563
        }
×
564

565
        @Override
566
        public String getObject() {
567
            return getUriLabel(uriModel.getObject());
×
568
        }
569

570
    }
571

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

586
    /**
587
     * Creates a sublist from an array based on the specified indices.
588
     *
589
     * @param array     the array from which to create the sublist
590
     * @param fromIndex the starting index (inclusive) for the sublist
591
     * @param toIndex   the ending index (exclusive) for the sublist
592
     * @param <E>       the type of elements in the array
593
     * @return an ArrayList containing the elements from the specified range
594
     */
595
    public static <E> ArrayList<E> subList(E[] array, long fromIndex, long toIndex) {
596
        return subList(Arrays.asList(array), fromIndex, toIndex);
×
597
    }
598

599
    /**
600
     * Comparator for sorting ApiResponseEntry objects based on a specified field.
601
     */
602
    // TODO Move this to ApiResponseEntry class?
603
    public static class ApiResponseEntrySorter implements Comparator<ApiResponseEntry>, Serializable {
604

605
        private String field;
606
        private boolean descending;
607

608
        /**
609
         * Constructor for ApiResponseEntrySorter.
610
         *
611
         * @param field      the field to sort by
612
         * @param descending if true, sorts in descending order; if false, sorts in ascending order
613
         */
614
        public ApiResponseEntrySorter(String field, boolean descending) {
×
615
            this.field = field;
×
616
            this.descending = descending;
×
617
        }
×
618

619
        /**
620
         * Compares two ApiResponseEntry objects based on the specified field.
621
         *
622
         * @param o1 the first object to be compared.
623
         * @param o2 the second object to be compared.
624
         * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
625
         */
626
        @Override
627
        public int compare(ApiResponseEntry o1, ApiResponseEntry o2) {
628
            if (descending) {
×
629
                return o2.get(field).compareTo(o1.get(field));
×
630
            } else {
631
                return o1.get(field).compareTo(o2.get(field));
×
632
            }
633
        }
634

635
    }
636

637
    /**
638
     * MIME type for TriG RDF format.
639
     */
640
    public static final String TYPE_TRIG = "application/trig";
641

642
    /**
643
     * MIME type for Jelly RDF format.
644
     */
645
    public static final String TYPE_JELLY = "application/x-jelly-rdf";
646

647
    /**
648
     * MIME type for JSON-LD format.
649
     */
650
    public static final String TYPE_JSONLD = "application/ld+json";
651

652
    /**
653
     * MIME type for N-Quads format.
654
     */
655
    public static final String TYPE_NQUADS = "application/n-quads";
656

657
    /**
658
     * MIME type for Trix format.
659
     */
660
    public static final String TYPE_TRIX = "application/trix";
661

662
    /**
663
     * MIME type for HTML format.
664
     */
665
    public static final String TYPE_HTML = "text/html";
666

667
    /**
668
     * Comma-separated list of supported MIME types for nanopublications.
669
     */
670
    public static final String SUPPORTED_TYPES =
671
            TYPE_TRIG + "," +
672
            TYPE_JELLY + "," +
673
            TYPE_JSONLD + "," +
674
            TYPE_NQUADS + "," +
675
            TYPE_TRIX + "," +
676
            TYPE_HTML;
677

678
    /**
679
     * List of supported MIME types for nanopublications.
680
     */
681
    public static final List<String> SUPPORTED_TYPES_LIST = Arrays.asList(StringUtils.split(SUPPORTED_TYPES, ','));
6✔
682

683
    // TODO Move these to nanopub-java library:
684

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

702
    /**
703
     * Retrieves a set of embedded IRI IDs from the nanopublication.
704
     *
705
     * @param np the nanopublication from which to extract embedded IRI IDs
706
     * @return a set of embedded IRI IDs
707
     */
708
    public static Set<String> getEmbeddedIriIds(Nanopub np) {
709
        Set<String> embeddedIriIds = new HashSet<>();
4✔
710
        for (Statement st : np.getPubinfo()) {
11✔
711
            if (!st.getSubject().equals(np.getUri())) continue;
7✔
712
            if (!st.getPredicate().equals(NPX.EMBEDS)) continue;
6✔
713
            if (st.getObject() instanceof IRI obj) embeddedIriIds.add(obj.stringValue());
14✔
714
        }
1✔
715
        return embeddedIriIds;
2✔
716
    }
717

718
    /**
719
     * Returns the URL of the default Nanopub Registry as configured by the given instance.
720
     *
721
     * @return Nanopub Registry URL
722
     */
723
    public static String getMainRegistryUrl() {
724
        try {
725
            return EnvironmentUtils.getProcEnvironment().getOrDefault("NANODASH_MAIN_REGISTRY", "https://registry.knowledgepixels.com/");
6✔
726
        } catch (IOException ex) {
×
727
            logger.error("Could not get NANODASH_MAIN_REGISTRY environment variable, using default.", ex);
×
728
            return "https://registry.knowledgepixels.com/";
×
729
        }
730
    }
731

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

736
    /**
737
     * Checks whether string is valid literal serialization.
738
     *
739
     * @param literalString the literal string
740
     * @return true if valid
741
     */
742
    public static boolean isValidLiteralSerialization(String literalString) {
743
        if (literalString.matches(PLAIN_LITERAL_PATTERN)) {
4✔
744
            return true;
2✔
745
        } else if (literalString.matches(LANGTAG_LITERAL_PATTERN)) {
4✔
746
            return true;
2✔
747
        } else if (literalString.matches(DATATYPE_LITERAL_PATTERN)) {
4✔
748
            return true;
2✔
749
        }
750
        return false;
2✔
751
    }
752

753
    /**
754
     * Returns a serialized version of the literal.
755
     *
756
     * @param literal the literal
757
     * @return the String serialization of the literal
758
     */
759
    public static String getSerializedLiteral(Literal literal) {
760
        if (literal.getLanguage().isPresent()) {
4✔
761
            return "\"" + getEscapedLiteralString(literal.stringValue()) + "\"@" + Literals.normalizeLanguageTag(literal.getLanguage().get());
10✔
762
        } else if (literal.getDatatype().equals(XSD.STRING)) {
5✔
763
            return "\"" + getEscapedLiteralString(literal.stringValue()) + "\"";
5✔
764
        } else {
765
            return "\"" + getEscapedLiteralString(literal.stringValue()) + "\"^^<" + literal.getDatatype() + ">";
8✔
766
        }
767
    }
768

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

788
    /**
789
     * Escapes quotes (") and slashes (/) of a literal string.
790
     *
791
     * @param unescapedString un-escaped string
792
     * @return escaped string
793
     */
794
    public static String getEscapedLiteralString(String unescapedString) {
795
        return unescapedString.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\"");
8✔
796
    }
797

798
    /**
799
     * Un-escapes quotes (") and slashes (/) of a literal string.
800
     *
801
     * @param escapedString escaped string
802
     * @return un-escaped string
803
     */
804
    public static String getUnescapedLiteralString(String escapedString) {
805
        return escapedString.replaceAll("\\\\(\\\\|\\\")", "$1");
5✔
806
    }
807

808
    /**
809
     * Checks if a given IRI is a local URI.
810
     *
811
     * @param uri the IRI to check
812
     * @return true if the IRI is a local URI, false otherwise
813
     */
814
    public static boolean isLocalURI(IRI uri) {
815
        return uri != null && isLocalURI(uri.stringValue());
10✔
816
    }
817

818
    /**
819
     * Checks if a given string is a local URI.
820
     *
821
     * @param uriAsString the string to check
822
     * @return true if the string is a local URI, false otherwise
823
     */
824
    public static boolean isLocalURI(String uriAsString) {
825
        return !uriAsString.isBlank() && uriAsString.startsWith(LocalUri.PREFIX);
11✔
826
    }
827

828
    // TODO Move this to QueryRef class in nanopub-java
829
    public static QueryRef parseQueryRef(String queryRefUrlString) {
830
        if (queryRefUrlString.contains("?")) {
×
831
            String queryName = queryRefUrlString.split("\\?")[0];
×
832
            Multimap<String,String> queryParams = ArrayListMultimap.create();
×
833
            if (!queryRefUrlString.endsWith("?")) {
×
834
                for (NameValuePair nvp : URLEncodedUtils.parse(queryRefUrlString.split("\\?")[1], Charsets.UTF_8)) {
×
835
                    queryParams.put(nvp.getName(), nvp.getValue());
×
836
                }
×
837
            }
838
            return new QueryRef(queryName, queryParams);
×
839
        } else {
840
            return new QueryRef(queryRefUrlString);
×
841
        }
842
    }
843

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