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

knowledgepixels / nanodash / 17944976193

23 Sep 2025 11:38AM UTC coverage: 13.373% (-0.1%) from 13.476%
17944976193

push

github

tkuhn
Version 3.55

435 of 4090 branches covered (10.64%)

Branch coverage included in aggregate %.

1116 of 7508 relevant lines covered (14.86%)

0.66 hits per line

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

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

3
import static com.knowledgepixels.nanodash.Utils.vf;
4

5
import java.io.Serializable;
6
import java.time.format.DateTimeParseException;
7
import java.util.ArrayList;
8
import java.util.Calendar;
9
import java.util.Collections;
10
import java.util.HashMap;
11
import java.util.HashSet;
12
import java.util.List;
13
import java.util.Map;
14
import java.util.Set;
15

16
import org.eclipse.rdf4j.model.IRI;
17
import org.eclipse.rdf4j.model.Literal;
18
import org.eclipse.rdf4j.model.Statement;
19
import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
20
import org.eclipse.rdf4j.model.vocabulary.OWL;
21
import org.nanopub.Nanopub;
22
import org.nanopub.extra.services.ApiResponse;
23
import org.nanopub.extra.services.ApiResponseEntry;
24
import org.nanopub.extra.services.QueryRef;
25
import org.nanopub.vocabulary.NTEMPLATE;
26
import org.slf4j.Logger;
27
import org.slf4j.LoggerFactory;
28

29
import com.github.jsonldjava.shaded.com.google.common.collect.Ordering;
30
import com.google.common.collect.ArrayListMultimap;
31
import com.google.common.collect.Multimap;
32
import com.knowledgepixels.nanodash.template.Template;
33
import com.knowledgepixels.nanodash.template.TemplateData;
34

35
import jakarta.xml.bind.DatatypeConverter;
36

37
/**
38
 * Class representing a "Space", which can be any kind of collaborative unit, like a project, group, or event.
39
 */
40
public class Space implements Serializable {
41

42
    private static final Logger logger = LoggerFactory.getLogger(Space.class);
×
43

44
    /**
45
     * The predicate to assign the admins of the space.
46
     */
47
    public static final IRI HAS_ADMIN = vf.createIRI("https://w3id.org/kpxl/gen/terms/hasAdmin");
×
48

49
    /**
50
     * The predicate for pinned templates in the space.
51
     */
52
    public static final IRI HAS_PINNED_TEMPLATE = vf.createIRI("https://w3id.org/kpxl/gen/terms/hasPinnedTemplate");
×
53

54
    /**
55
     * The predicate for pinned queries in the space.
56
     */
57
    public static final IRI HAS_PINNED_QUERY = vf.createIRI("https://w3id.org/kpxl/gen/terms/hasPinnedQuery");
×
58

59
    private static List<Space> spaceList;
60
    private static Map<String, List<Space>> spaceListByType;
61
    private static Map<String,Space> spacesByCoreInfo = new HashMap<>();
×
62
    private static Map<String,Space> spacesById;
63
    private static Map<Space,Set<Space>> subspaceMap;
64
    private static Map<Space,Set<Space>> superspaceMap;
65
    private static boolean loaded = false;
×
66

67
    public static synchronized void refresh(ApiResponse resp) {
68
        spaceList = new ArrayList<>();
×
69
        spaceListByType = new HashMap<>();
×
70
        Map<String,Space> prevSpacesByCoreInfoPrev = spacesByCoreInfo;
×
71
        spacesByCoreInfo = new HashMap<>();
×
72
        spacesById = new HashMap<>();
×
73
        subspaceMap = new HashMap<>();
×
74
        superspaceMap = new HashMap<>();
×
75
        for (ApiResponseEntry entry : resp.getData()) {
×
76
            Space space = new Space(entry);
×
77
            Space prevSpace = prevSpacesByCoreInfoPrev.get(space.getCoreInfoString());
×
78
            if (prevSpace != null) space = prevSpace;
×
79
            spaceList.add(space);
×
80
            spaceListByType.computeIfAbsent(space.getType(), k -> new ArrayList<>()).add(space);
×
81
            spacesByCoreInfo.put(space.getCoreInfoString(), space);
×
82
            spacesById.put(space.getId(), space);
×
83
        }
×
84
        for (Space space : spaceList) {
×
85
            Space superSpace = space.getIdSuperspace();
×
86
            if (superSpace == null) continue;
×
87
            subspaceMap.computeIfAbsent(superSpace, k -> new HashSet<>()).add(space);
×
88
            superspaceMap.computeIfAbsent(space, k -> new HashSet<>()).add(superSpace);
×
89
        }
×
90
        loaded = true;
×
91
    }
×
92

93
    public static boolean isLoaded() {
94
        return loaded;
×
95
    }
96

97
    public static void ensureLoaded() {
98
        if (spaceList == null) {
×
99
            refresh(QueryApiAccess.forcedGet(new QueryRef("get-spaces")));
×
100
        }
101
    }
×
102

103
    public static List<Space> getSpaceList() {
104
        ensureLoaded();
×
105
        return spaceList;
×
106
    }
107

108
    public static List<Space> getSpaceList(String type) {
109
        ensureLoaded();
×
110
        return spaceListByType.computeIfAbsent(type, k -> new ArrayList<>());
×
111
    }
112

113
    public static Space get(String id) {
114
        ensureLoaded();
×
115
        return spacesById.get(id);
×
116
    }
117

118
    public static void refresh() {
119
        ensureLoaded();
×
120
        for (Space space : spaceList) {
×
121
            space.dataNeedsUpdate = true;
×
122
        }
×
123
    }
×
124

125
    private String id, label, rootNanopubId, type;
126
    private Nanopub rootNanopub = null;
×
127
    private SpaceData data = new SpaceData();
×
128

129
    private static class SpaceData implements Serializable {
×
130

131
        List<String> altIds = new ArrayList<>();
×
132
    
133
        String description = null;
×
134
        Calendar startDate, endDate;
135
        IRI defaultProvenance = null;
×
136

137
        List<IRI> admins = new ArrayList<>();
×
138
        Map<IRI,Set<SpaceMemberRole>> members = new HashMap<>();
×
139
        List<SpaceMemberRole> roles = new ArrayList<>();
×
140
        Map<IRI,SpaceMemberRole> roleMap = new HashMap<>();
×
141

142
        Map<String,IRI> adminPubkeyMap = new HashMap<>();
×
143
        List<Serializable> pinnedResources = new ArrayList<>();
×
144
        List<GrlcQuery> views = new ArrayList<>();
×
145
        Set<String> pinGroupTags = new HashSet<>();
×
146
        Map<String, List<Serializable>> pinnedResourceMap = new HashMap<>();
×
147

148
        void addAdmin(IRI admin) {
149
            // TODO This isn't efficient for long owner lists:
150
            if (admins.contains(admin)) return;
×
151
            admins.add(admin);
×
152
            UserData ud = User.getUserData();
×
153
            for (String pubkeyhash : ud.getPubkeyhashes(admin, true)) {
×
154
                adminPubkeyMap.put(pubkeyhash, admin);
×
155
            }
×
156
        }
×
157

158
    }
159

160
    private boolean dataInitialized = false;
×
161
    private boolean dataNeedsUpdate = true;
×
162

163
    private Space(ApiResponseEntry resp) {
×
164
        this.id = resp.get("space");
×
165
        this.label = resp.get("label");
×
166
        this.type = resp.get("type");
×
167
        this.rootNanopubId = resp.get("np");
×
168
        this.rootNanopub = Utils.getAsNanopub(rootNanopubId);
×
169
        setCoreData(data);
×
170
    }
×
171

172
    public String getId() {
173
        return id;
×
174
    }
175

176
    public String getRootNanopubId() {
177
        return rootNanopubId;
×
178
    }
179

180
    public String getCoreInfoString() {
181
        return id + " " + rootNanopubId;
×
182
    }
183

184
    public Nanopub getRootNanopub() {
185
        return rootNanopub;
×
186
    }
187

188
    public String getLabel() {
189
        return label;
×
190
    }
191

192
    public String getType() {
193
        return type;
×
194
    }
195

196
    public Calendar getStartDate() {
197
        return data.startDate;
×
198
    }
199

200
    public Calendar getEndDate() {
201
        return data.endDate;
×
202
    }
203

204
    public String getTypeLabel() {
205
        return type.replaceFirst("^.*/", "");
×
206
    }
207

208
    public String getDescription() {
209
        return data.description;
×
210
    }
211

212
    public boolean isDataInitialized() {
213
        triggerDataUpdate();
×
214
        return dataInitialized;
×
215
    }
216

217
    public List<IRI> getAdmins() {
218
        triggerDataUpdate();
×
219
        return data.admins;
×
220
    }
221

222
    public List<IRI> getMembers() {
223
        triggerDataUpdate();
×
224
        List<IRI> members = new ArrayList<IRI>(data.members.keySet());
×
225
        members.sort(User.getUserData().userComparator);
×
226
        return members;
×
227
    }
228

229
    public Set<SpaceMemberRole> getMemberRoles(IRI memberId) {
230
        return data.members.get(memberId);
×
231
    }
232

233
    public boolean isMember(IRI userId) {
234
        triggerDataUpdate();
×
235
        return data.members.containsKey(userId);
×
236
    }
237

238
    public List<Serializable> getPinnedResources() {
239
        triggerDataUpdate();
×
240
        return data.pinnedResources;
×
241
    }
242

243
    public Set<String> getPinGroupTags() {
244
        triggerDataUpdate();
×
245
        return data.pinGroupTags;
×
246
    }
247

248
    public Map<String, List<Serializable>> getPinnedResourceMap() {
249
        triggerDataUpdate();
×
250
        return data.pinnedResourceMap;
×
251
    }
252

253
    public List<GrlcQuery> getViews() {
254
        return data.views;
×
255
    }
256

257
    public IRI getDefaultProvenance() {
258
        return data.defaultProvenance;
×
259
    }
260

261
    public List<SpaceMemberRole> getRoles() {
262
        return data.roles;
×
263
    }
264

265
    public String getSuperId() {
266
        return null;
×
267
    }
268

269
    public Space getIdSuperspace() {
270
        if (!id.matches("https?://[^/]+/.*/[^/]*/?")) return null;
×
271
        String superId = id.replaceFirst("(https?://[^/]+/.*)/[^/]*/?", "$1");
×
272
        if (spacesById.containsKey(superId)) {
×
273
            return spacesById.get(superId);
×
274
        }
275
        return null;
×
276
    }
277

278
    public List<Space> getSuperspaces() {
279
        if (superspaceMap.containsKey(this)) {
×
280
            List<Space> superspaces = new ArrayList<>(superspaceMap.get(this));
×
281
            Collections.sort(superspaces, Ordering.usingToString());
×
282
            return superspaces;
×
283
        }
284
        return new ArrayList<>();
×
285
    }
286

287
    public List<Space> getSubspaces() {
288
        if (subspaceMap.containsKey(this)) {
×
289
            List<Space> subspaces = new ArrayList<>(subspaceMap.get(this));
×
290
            Collections.sort(subspaces, Ordering.usingToString());
×
291
            return subspaces;
×
292
        }
293
        return new ArrayList<>();
×
294
    }
295

296
    public List<Space> getSubspaces(String type) {
297
        List<Space> l = new ArrayList<>();
×
298
        for (Space s : getSubspaces()) {
×
299
            if (s.getType().equals(type)) l.add(s);
×
300
        }
×
301
        return l;
×
302
    }
303

304
    public List<String> getAltIDs() {
305
        return data.altIds;
×
306
    }
307

308
    private synchronized void triggerDataUpdate() {
309
        if (dataNeedsUpdate) {
×
310
            new Thread(() -> {
×
311
                try {
312
                    SpaceData newData = new SpaceData();
×
313
                    setCoreData(newData);
×
314
    
315
                    newData.roles.add(SpaceMemberRole.ADMIN_ROLE);
×
316
                    newData.roleMap.put(SpaceMemberRole.ADMIN_ROLE_IRI, SpaceMemberRole.ADMIN_ROLE);
×
317

318
                    Multimap<String, String> spaceIds = ArrayListMultimap.create();
×
319
                    spaceIds.put("space", id);
×
320
                    for (String id : newData.altIds) {
×
321
                        spaceIds.put("space", id);
×
322
                    }
×
323
    
324
                    for (ApiResponseEntry r : QueryApiAccess.get(new QueryRef("get-admins", spaceIds)).getData()) {
×
325
                        String pubkeyhash = r.get("pubkey");
×
326
                        if (newData.adminPubkeyMap.containsKey(pubkeyhash)) {
×
327
                            IRI adminId = Utils.vf.createIRI(r.get("admin"));
×
328
                            newData.addAdmin(adminId);
×
329
                            newData.members.computeIfAbsent(adminId, (k) -> new HashSet<>()).add(SpaceMemberRole.ADMIN_ROLE);
×
330
                        }
331
                    }
×
332
                    newData.admins.sort(User.getUserData().userComparator);
×
333
    
334
                    Multimap<String, String> getSpaceMemberParams = ArrayListMultimap.create(spaceIds);
×
335
    
336
                    for (ApiResponseEntry r : QueryApiAccess.get(new QueryRef( "get-space-member-roles", spaceIds)).getData()) {
×
337
                        if (!newData.adminPubkeyMap.containsKey(r.get("pubkey"))) continue;
×
338
                        SpaceMemberRole role = new SpaceMemberRole(r);
×
339
                        newData.roles.add(role);
×
340
    
341
                        // TODO Handle cases of overlapping properties:
342
                        newData.roleMap.put(role.getMainProperty(), role);
×
343
                        for (IRI p : role.getEquivalentProperties()) newData.roleMap.put(p, role);
×
344
                        for (IRI p : role.getInverseProperties()) newData.roleMap.put(p, role);
×
345
        
346
                        role.addRoleParams(getSpaceMemberParams);
×
347
                    }
×
348
    
349
                    for (ApiResponseEntry r : QueryApiAccess.get(new QueryRef("get-space-members", getSpaceMemberParams)).getData()) {
×
350
                        IRI memberId = Utils.vf.createIRI(r.get("member"));
×
351
                        SpaceMemberRole role = newData.roleMap.get(Utils.vf.createIRI(r.get("role")));
×
352
                        newData.members.computeIfAbsent(memberId, (k) -> new HashSet<>()).add(role);
×
353
                    }
×
354
    
355
                    for (ApiResponseEntry r : QueryApiAccess.get(new QueryRef("get-pinned-templates", spaceIds)).getData()) {
×
356
                        if (!newData.adminPubkeyMap.containsKey(r.get("pubkey"))) continue;
×
357
                        Template t = TemplateData.get().getTemplate(r.get("template"));
×
358
                        if (t == null) continue;
×
359
                        newData.pinnedResources.add(t);
×
360
                        String tag = r.get("tag");
×
361
                        if (tag != null && !tag.isEmpty()) {
×
362
                            newData.pinGroupTags.add(r.get("tag"));
×
363
                            newData.pinnedResourceMap.computeIfAbsent(tag, k -> new ArrayList<>()).add(TemplateData.get().getTemplate(r.get("template")));
×
364
                        }
365
                    }
×
366
                    for (ApiResponseEntry r : QueryApiAccess.get(new QueryRef("get-pinned-queries", spaceIds)).getData()) {
×
367
                        if (!newData.adminPubkeyMap.containsKey(r.get("pubkey"))) continue;
×
368
                        GrlcQuery query = GrlcQuery.get(r.get("query"));
×
369
                        if (query == null) continue;
×
370
                        newData.pinnedResources.add(query);
×
371
                        String tag = r.get("tag");
×
372
                        if (tag != null && !tag.isEmpty()) {
×
373
                            newData.pinGroupTags.add(r.get("tag"));
×
374
                            newData.pinnedResourceMap.computeIfAbsent(tag, k -> new ArrayList<>()).add(query);
×
375
                        }
376
                    }
×
377
                    for (ApiResponseEntry r : QueryApiAccess.get(new QueryRef("get-views-for-space", spaceIds)).getData()) {
×
378
                        if (!newData.adminPubkeyMap.containsKey(r.get("pubkey"))) continue;
×
379
                        GrlcQuery query = GrlcQuery.get(r.get("query"));
×
380
                        if (query == null) continue;
×
381
                        newData.views.add(query);
×
382
                    }
×
383
                    data = newData;
×
384
                    dataInitialized = true;
×
385
                } catch (Exception ex) {
×
386
                    logger.error("Error while trying to update space data: {}", ex);
×
387
                }
×
388
            }).start();
×
389
            dataNeedsUpdate = false;
×
390
        }
391
    }
×
392

393
    private void setCoreData(SpaceData data) {
394
        for (Statement st : rootNanopub.getAssertion()) {
×
395
            if (st.getSubject().stringValue().equals(getId())) {
×
396
                if (st.getPredicate().equals(OWL.SAMEAS) && st.getObject() instanceof IRI objIri) {
×
397
                    data.altIds.add(objIri.stringValue());
×
398
                } else if (st.getPredicate().equals(DCTERMS.DESCRIPTION)) {
×
399
                    data.description = st.getObject().stringValue();
×
400
                } else if (st.getPredicate().stringValue().equals("http://schema.org/startDate")) {
×
401
                    try {
402
                        data.startDate = DatatypeConverter.parseDateTime(st.getObject().stringValue());
×
403
                    } catch (DateTimeParseException ex) {
×
404
                        logger.error("Failed to parse date {}", st.getObject().stringValue());
×
405
                    }
×
406
                } else if (st.getPredicate().stringValue().equals("http://schema.org/endDate")) {
×
407
                    try {
408
                        data.endDate = DatatypeConverter.parseDateTime(st.getObject().stringValue());
×
409
                    } catch (IllegalArgumentException ex) {
×
410
                        logger.error("Failed to parse date {}", st.getObject().stringValue());
×
411
                    }
×
412
                } else if (st.getPredicate().equals(HAS_ADMIN) && st.getObject() instanceof IRI obj) {
×
413
                    data.addAdmin(obj);
×
414
                } else if (st.getPredicate().equals(HAS_PINNED_TEMPLATE) && st.getObject() instanceof IRI obj) {
×
415
                    data.pinnedResources.add(TemplateData.get().getTemplate(obj.stringValue()));
×
416
                } else if (st.getPredicate().equals(HAS_PINNED_QUERY) && st.getObject() instanceof IRI obj) {
×
417
                    data.pinnedResources.add(GrlcQuery.get(obj.stringValue()));
×
418
                } else if (st.getPredicate().equals(NTEMPLATE.HAS_DEFAULT_PROVENANCE) && st.getObject() instanceof IRI obj) {
×
419
                    data.defaultProvenance = obj;
×
420
                }
421
            } else if (st.getPredicate().equals(NTEMPLATE.HAS_TAG) && st.getObject() instanceof Literal l) {
×
422
                data.pinGroupTags.add(l.stringValue());
×
423
                List<Serializable> list = data.pinnedResourceMap.get(l.stringValue());
×
424
                if (list == null) {
×
425
                    list = new ArrayList<>();
×
426
                    data.pinnedResourceMap.put(l.stringValue(), list);
×
427
                }
428
                list.add(TemplateData.get().getTemplate(st.getSubject().stringValue()));
×
429
            }
430
        }
×
431
    }
×
432

433
    @Override
434
    public String toString() {
435
        return id;
×
436
    }
437

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