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

knowledgepixels / nanodash / 17671368517

12 Sep 2025 10:07AM UTC coverage: 13.687% (-0.01%) from 13.7%
17671368517

push

github

tkuhn
refactor: Generalize pinned templates to pinned resources

432 of 3986 branches covered (10.84%)

Branch coverage included in aggregate %.

1105 of 7244 relevant lines covered (15.25%)

0.68 hits per line

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

0.0
src/main/java/com/knowledgepixels/nanodash/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.util.ArrayList;
7
import java.util.Collections;
8
import java.util.HashMap;
9
import java.util.HashSet;
10
import java.util.List;
11
import java.util.Map;
12
import java.util.Set;
13
import java.util.concurrent.ConcurrentHashMap;
14
import java.util.concurrent.ConcurrentMap;
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.nanopub.Nanopub;
21
import org.nanopub.extra.services.ApiResponse;
22
import org.nanopub.extra.services.ApiResponseEntry;
23
import org.nanopub.vocabulary.NTEMPLATE;
24

25
import com.github.jsonldjava.shaded.com.google.common.collect.Ordering;
26
import com.knowledgepixels.nanodash.template.Template;
27
import com.knowledgepixels.nanodash.template.TemplateData;
28

29
/**
30
 * Class representing a "Space", which can be any kind of collaborative unit, like a project, group, or event.
31
 */
32
public class Space implements Serializable {
33

34
    /**
35
     * The predicate for the owner of the space.
36
     */
37
    public static final IRI HAS_OWNER = vf.createIRI("https://w3id.org/kpxl/gen/terms/hasOwner");
×
38

39
    /**
40
     * The predicate for pinned templates in the space.
41
     */
42
    public static final IRI HAS_PINNED_TEMPLATE = vf.createIRI("https://w3id.org/kpxl/gen/terms/hasPinnedTemplate");
×
43

44
    /**
45
     * The predicate for pinned queries in the space.
46
     */
47
    public static final IRI HAS_PINNED_QUERY = vf.createIRI("https://w3id.org/kpxl/gen/terms/hasPinnedQuery");
×
48

49
    private static List<Space> spaceList;
50
    private static Map<String,Space> spacesByCoreInfo = new HashMap<>();
×
51
    private static Map<String,Space> spacesById;
52
    private static Map<Space,Set<Space>> subspaceMap;
53
    private static Map<Space,Set<Space>> superspaceMap;
54

55
    public static synchronized void refresh(ApiResponse resp) {
56
        spaceList = new ArrayList<>();
×
57
        Map<String,Space> prevSpacesByCoreInfoPrev = spacesByCoreInfo;
×
58
        spacesByCoreInfo = new HashMap<>();
×
59
        spacesById = new HashMap<>();
×
60
        subspaceMap = new HashMap<>();
×
61
        superspaceMap = new HashMap<>();
×
62
        for (ApiResponseEntry entry : resp.getData()) {
×
63
            Space space = new Space(entry);
×
64
            Space prevSpace = prevSpacesByCoreInfoPrev.get(space.getCoreInfoString());
×
65
            if (prevSpace != null) space = prevSpace;
×
66
            spaceList.add(space);
×
67
            spacesByCoreInfo.put(space.getCoreInfoString(), space);
×
68
            spacesById.put(space.getId(), space);
×
69
        }
×
70
        for (Space space : spaceList) {
×
71
            Space superSpace = space.getIdSuperspace();
×
72
            if (superSpace == null) continue;
×
73
            subspaceMap.computeIfAbsent(superSpace, k -> new HashSet<>()).add(space);
×
74
            superspaceMap.computeIfAbsent(space, k -> new HashSet<>()).add(superSpace);
×
75
        }
×
76
    }
×
77

78
    public static void ensureLoaded() {
79
        if (spaceList == null) {
×
80
            refresh(QueryApiAccess.forcedGet("get-spaces"));
×
81
        }
82
    }
×
83

84
    public static List<Space> getSpaceList() {
85
        ensureLoaded();
×
86
        return spaceList;
×
87
    }
88

89
    public static Space get(String id) {
90
        ensureLoaded();
×
91
        return spacesById.get(id);
×
92
    }
93

94
    public static void refresh() {
95
        ensureLoaded();
×
96
        for (Space space : spaceList) {
×
97
            space.dataNeedsUpdate = true;
×
98
        }
×
99
    }
×
100

101
    private String id, label, rootNanopubId, type;
102
    private Nanopub rootNanopub = null;
×
103

104
    private String description = null;
×
105
    private List<IRI> owners = new ArrayList<>();
×
106
    private List<IRI> members = new ArrayList<>();
×
107
    private ConcurrentMap<String,IRI> ownerPubkeyMap = new ConcurrentHashMap<>();
×
108
    private List<Serializable> pinnedResources = new ArrayList<>();
×
109
    private Set<String> pinGroupTags = new HashSet<>();
×
110
    private ConcurrentMap<String, List<Serializable>> pinnedResourceMap = new ConcurrentHashMap<>();
×
111
    private List<IRI> queryIds = new ArrayList<>();
×
112
    private IRI defaultProvenance = null;
×
113

114
    private boolean dataInitialized = false;
×
115
    private boolean dataNeedsUpdate = true;
×
116

117
    private Space(ApiResponseEntry resp) {
×
118
        this.id = resp.get("space");
×
119
        this.label = resp.get("label");
×
120
        this.type = resp.get("type");
×
121
        this.rootNanopubId = resp.get("np");
×
122
        this.rootNanopub = Utils.getAsNanopub(rootNanopubId);
×
123

124
        for (Statement st : rootNanopub.getAssertion()) {
×
125
            if (st.getSubject().stringValue().equals(getId())) {
×
126
                if (st.getPredicate().equals(DCTERMS.DESCRIPTION)) {
×
127
                    description = st.getObject().stringValue();
×
128
                } else if (st.getPredicate().equals(HAS_OWNER) && st.getObject() instanceof IRI obj) {
×
129
                    addOwner(obj);
×
130
                } else if (st.getPredicate().equals(HAS_PINNED_TEMPLATE) && st.getObject() instanceof IRI obj) {
×
131
                    pinnedResources.add(TemplateData.get().getTemplate(obj.stringValue()));
×
132
                } else if (st.getPredicate().equals(HAS_PINNED_QUERY) && st.getObject() instanceof IRI obj) {
×
133
                    queryIds.add(obj);
×
134
                } else if (st.getPredicate().equals(NTEMPLATE.HAS_DEFAULT_PROVENANCE) && st.getObject() instanceof IRI obj) {
×
135
                    defaultProvenance = obj;
×
136
                }
137
            } else if (st.getPredicate().equals(NTEMPLATE.HAS_TAG) && st.getObject() instanceof Literal l) {
×
138
                pinGroupTags.add(l.stringValue());
×
139
                List<Serializable> list = pinnedResourceMap.get(l.stringValue());
×
140
                if (list == null) {
×
141
                    list = new ArrayList<>();
×
142
                    pinnedResourceMap.put(l.stringValue(), list);
×
143
                }
144
                list.add(TemplateData.get().getTemplate(st.getSubject().stringValue()));
×
145
            }
146
        }
×
147
    }
×
148

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

159
    public String getId() {
160
        return id;
×
161
    }
162

163
    public String getRootNanopubId() {
164
        return rootNanopubId;
×
165
    }
166

167
    public String getCoreInfoString() {
168
        return id + " " + rootNanopubId;
×
169
    }
170

171
    public Nanopub getRootNanopub() {
172
        return rootNanopub;
×
173
    }
174

175
    public String getLabel() {
176
        return label;
×
177
    }
178

179
    public String getType() {
180
        return type;
×
181
    }
182

183
    public String getTypeLabel() {
184
        return type.replaceFirst("^.*/", "");
×
185
    }
186

187
    public String getDescription() {
188
        return description;
×
189
    }
190

191
    public boolean isDataInitialized() {
192
        triggerDataUpdate();
×
193
        return dataInitialized;
×
194
    }
195

196
    public List<IRI> getOwners() {
197
        triggerDataUpdate();
×
198
        return owners;
×
199
    }
200

201
    public List<IRI> getMembers() {
202
        triggerDataUpdate();
×
203
        return members;
×
204
    }
205

206
    public List<Serializable> getPinnedResources() {
207
        triggerDataUpdate();
×
208
        return pinnedResources;
×
209
    }
210

211
    public Set<String> getPinGroupTags() {
212
        triggerDataUpdate();
×
213
        return pinGroupTags;
×
214
    }
215

216
    public ConcurrentMap<String, List<Serializable>> getPinnedResourceMap() {
217
        triggerDataUpdate();
×
218
        return pinnedResourceMap;
×
219
    }
220

221
    public List<IRI> getQueryIds() {
222
        return queryIds;
×
223
    }
224

225
    public IRI getDefaultProvenance() {
226
        return defaultProvenance;
×
227
    }
228

229
    public String getSuperId() {
230
        return null;
×
231
    }
232

233
    public Space getIdSuperspace() {
234
        if (!id.matches("https?://[^/]+/.*/[^/]*/?")) return null;
×
235
        String superId = id.replaceFirst("(https?://[^/]+/.*)/[^/]*/?", "$1");
×
236
        if (spacesById.containsKey(superId)) {
×
237
            return spacesById.get(superId);
×
238
        }
239
        return null;
×
240
    }
241

242
    public List<Space> getSuperspaces() {
243
        if (superspaceMap.containsKey(this)) {
×
244
            List<Space> superspaces = new ArrayList<>(superspaceMap.get(this));
×
245
            Collections.sort(superspaces, Ordering.usingToString());
×
246
            return superspaces;
×
247
        }
248
        return new ArrayList<>();
×
249
    }
250

251
    public List<Space> getSubspaces() {
252
        if (subspaceMap.containsKey(this)) {
×
253
            List<Space> subspaces = new ArrayList<>(subspaceMap.get(this));
×
254
            Collections.sort(subspaces, Ordering.usingToString());
×
255
            return subspaces;
×
256
        }
257
        return new ArrayList<>();
×
258
    }
259

260
    private synchronized void triggerDataUpdate() {
261
        if (dataNeedsUpdate) {
×
262
            new Thread(() -> {
×
263
                for (ApiResponseEntry r : QueryApiAccess.forcedGet("get-owners", "unit", id).getData()) {
×
264
                    String pubkeyhash = r.get("pubkeyhash");
×
265
                    if (ownerPubkeyMap.containsKey(pubkeyhash)) {
×
266
                        addOwner(Utils.vf.createIRI(r.get("owner")));
×
267
                    }
268
                }
×
269
                members = new ArrayList<>();
×
270
                for (ApiResponseEntry r : QueryApiAccess.forcedGet("get-members", "unit", id).getData()) {
×
271
                    IRI memberId = Utils.vf.createIRI(r.get("member"));
×
272
                    // TODO These checks are inefficient for long member lists:
273
                    if (owners.contains(memberId)) continue;
×
274
                    if (members.contains(memberId)) continue;
×
275
                    members.add(memberId);
×
276
                }
×
277
                owners.sort(User.getUserData().userComparator);
×
278
                members.sort(User.getUserData().userComparator);
×
279

280
                for (ApiResponseEntry r : QueryApiAccess.forcedGet("get-pinned-templates", "space", id).getData()) {
×
281
                    if (!ownerPubkeyMap.containsKey(r.get("pubkey"))) continue;
×
282
                    Template t = TemplateData.get().getTemplate(r.get("template"));
×
283
                    if (t == null) continue;
×
284
                    pinnedResources.add(t);
×
285
                    String tag = r.get("tag");
×
286
                    if (tag != null && !tag.isEmpty()) {
×
287
                        pinGroupTags.add(r.get("tag"));
×
288
                        List<Serializable> list = pinnedResourceMap.get(r.get("tag"));
×
289
                        if (list == null) {
×
290
                            list = new ArrayList<>();
×
291
                            pinnedResourceMap.put(r.get("tag"), list);
×
292
                        }
293
                        list.add(TemplateData.get().getTemplate(r.get("template")));
×
294
                    }
295
                }
×
296
                dataInitialized = true;
×
297
            }).start();
×
298
            dataNeedsUpdate = false;
×
299
        }
300
    }
×
301

302
    @Override
303
    public String toString() {
304
        return id;
×
305
    }
306

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