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

knowledgepixels / nanodash / 19763098070

28 Nov 2025 11:55AM UTC coverage: 14.373% (+0.001%) from 14.372%
19763098070

push

github

tkuhn
feat(ExplorePage): Recognize target classes of views for redirects

544 of 4876 branches covered (11.16%)

Branch coverage included in aggregate %.

1441 of 8935 relevant lines covered (16.13%)

0.72 hits per line

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

34.21
src/main/java/com/knowledgepixels/nanodash/MaintainedResource.java
1
package com.knowledgepixels.nanodash;
2

3
import com.knowledgepixels.nanodash.vocabulary.KPXL_TERMS;
4
import org.eclipse.rdf4j.model.IRI;
5
import org.nanopub.Nanopub;
6
import org.nanopub.extra.services.ApiResponse;
7
import org.nanopub.extra.services.ApiResponseEntry;
8
import org.nanopub.extra.services.QueryRef;
9
import org.slf4j.Logger;
10
import org.slf4j.LoggerFactory;
11

12
import java.io.Serializable;
13
import java.util.*;
14

15
public class MaintainedResource implements Serializable {
16

17
    private static final Logger logger = LoggerFactory.getLogger(MaintainedResource.class);
3✔
18

19
    private static List<MaintainedResource> resourceList;
20
    private static Map<String, MaintainedResource> resourcesById;
21
    private static Map<String, MaintainedResource> resourcesByNamespace;
22
    private static Map<Space, List<MaintainedResource>> resourcesBySpace;
23
    private static boolean loaded = false;
2✔
24
    private static Long runRootUpdateAfter = null;
3✔
25

26
    public static synchronized void refresh(ApiResponse resp) {
27
        resourceList = new ArrayList<>();
4✔
28
        resourcesById = new HashMap<>();
4✔
29
        resourcesBySpace = new HashMap<>();
4✔
30
        resourcesByNamespace = new HashMap<>();
4✔
31
        for (ApiResponseEntry entry : resp.getData()) {
11✔
32
            Space space = Space.get(entry.get("space"));
5✔
33
            if (space == null) continue;
2!
34
            MaintainedResource resource = new MaintainedResource(entry, space);
6✔
35
            if (resourcesById.containsKey(resource.getId())) continue;
5!
36
            resourceList.add(resource);
4✔
37
            resourcesById.put(resource.getId(), resource);
6✔
38
            resourcesBySpace.computeIfAbsent(space, k -> new ArrayList<>()).add(resource);
12✔
39
            if (resource.getNamespace() != null) {
3✔
40
                // TODO Handle conflicts when two resources claim the same namespace:
41
                resourcesByNamespace.put(resource.getNamespace(), resource);
6✔
42
            }
43
        }
1✔
44
        loaded = true;
2✔
45
    }
1✔
46

47
    /**
48
     * Check if the resources have been loaded.
49
     *
50
     * @return true if loaded, false otherwise.
51
     */
52
    public static boolean isLoaded() {
53
        return loaded;
×
54
    }
55

56
    /**
57
     * Ensure that the resources are loaded, fetching them from the API if necessary.
58
     */
59
    public static void ensureLoaded() {
60
        if (resourceList == null) {
2✔
61
            try {
62
                if (runRootUpdateAfter != null) {
2!
63
                    while (System.currentTimeMillis() < runRootUpdateAfter) {
×
64
                        Thread.sleep(100);
×
65
                    }
66
                    runRootUpdateAfter = null;
×
67
                }
68
            } catch (InterruptedException ex) {
×
69
                logger.error("Interrupted", ex);
×
70
            }
1✔
71
            refresh(QueryApiAccess.forcedGet(new QueryRef("get-maintained-resources")));
6✔
72
        }
73
    }
1✔
74

75
    public static void forceRootRefresh(long waitMillis) {
76
        resourceList = null;
×
77
        runRootUpdateAfter = System.currentTimeMillis() + waitMillis;
×
78
    }
×
79

80
    /**
81
     * Get the list of all maintained resources.
82
     *
83
     * @return List of resources.
84
     */
85
    public static List<MaintainedResource> getResourceList() {
86
        ensureLoaded();
×
87
        return resourceList;
×
88
    }
89

90
    public static List<MaintainedResource> getResourcesBySpace(Space space) {
91
        return resourcesBySpace.computeIfAbsent(space, k -> new ArrayList<>());
×
92
    }
93

94
    /**
95
     * Get a maintained resource by its id.
96
     *
97
     * @param id The id of the resource.
98
     * @return The corresponding MaintainedResource object, or null if not found.
99
     */
100
    public static MaintainedResource get(String id) {
101
        ensureLoaded();
1✔
102
        return resourcesById.get(id);
5✔
103
    }
104

105
    public static MaintainedResource getByNamespace(String namespace) {
106
        return resourcesByNamespace.get(namespace);
×
107
    }
108

109
    public static String getNamespace(Object stringOrIri) {
110
        return stringOrIri.toString().replaceFirst("([#/])[^#/]+$", "$1");
×
111
    }
112

113
    public static void refresh() {
114
        refresh(QueryApiAccess.forcedGet(new QueryRef("get-maintained-resources")));
6✔
115
        for (MaintainedResource resource : resourceList) {
10✔
116
            resource.dataNeedsUpdate = true;
3✔
117
        }
1✔
118
    }
1✔
119

120
    private String id, label, nanopubId, namespace;
121
    private Space space;
122
    private Nanopub nanopub;
123
    private ResourceData data = new ResourceData();
5✔
124

125
    private boolean dataInitialized = false;
3✔
126
    private boolean dataNeedsUpdate = true;
3✔
127

128
    private static class ResourceData implements Serializable {
2✔
129
        List<ViewDisplay> topLevelViews = new ArrayList<>();
5✔
130
        Set<String> topLevelViewKinds = new HashSet<>();
5✔
131
        List<ViewDisplay> partLevelViews = new ArrayList<>();
5✔
132
        Set<String> partLevelViewKinds = new HashSet<>();
6✔
133
    }
134

135
    private MaintainedResource(ApiResponseEntry resp, Space space) {
2✔
136
        this.space = space;
3✔
137
        this.id = resp.get("resource");
5✔
138
        this.label = resp.get("label");
5✔
139
        this.nanopubId = resp.get("np");
5✔
140
        this.namespace = resp.get("namespace");
5✔
141
        if (namespace != null && namespace.isBlank()) namespace = null;
10!
142
        this.nanopub = Utils.getAsNanopub(nanopubId);
5✔
143
    }
1✔
144

145
    public Space getSpace() {
146
        return space;
×
147
    }
148

149
    public String getId() {
150
        return id;
3✔
151
    }
152

153
    public String getNanopubId() {
154
        return nanopubId;
×
155
    }
156

157
    public Nanopub getNanopub() {
158
        return nanopub;
×
159
    }
160

161
    public String getLabel() {
162
        return label;
×
163
    }
164

165
    public String getNamespace() {
166
        return namespace;
3✔
167
    }
168

169
    public boolean isDataInitialized() {
170
        triggerDataUpdate();
×
171
        return dataInitialized;
×
172
    }
173

174
    public List<ViewDisplay> getTopLevelViews() {
175
        triggerDataUpdate();
×
176
        // TODO Check for compliance with target classes here too:
177
        return data.topLevelViews;
×
178
    }
179

180
    public List<ViewDisplay> getPartLevelViews(Set<IRI> classes) {
181
        triggerDataUpdate();
×
182
        List<ViewDisplay> viewDisplays = new ArrayList<>();
×
183
        for (ViewDisplay v : data.partLevelViews) {
×
184
            if (v.getView().hasTargetClasses()) {
×
185
                for (IRI c : classes) {
×
186
                    if (v.getView().hasTargetClass(c)) {
×
187
                        viewDisplays.add(v);
×
188
                        break;
×
189
                    }
190
                }
×
191
            } else {
192
                viewDisplays.add(v);
×
193
            }
194
        }
×
195
        return viewDisplays;
×
196
    }
197

198
    public boolean coversElement(String elementId, Set<IRI> classes) {
199
        triggerDataUpdate();
×
200
        for (ViewDisplay v : data.topLevelViews) {
×
201
            if (v.getView().coversElement(elementId)) return true;
×
202
            for (IRI c : classes) {
×
203
                if (v.getView().hasTargetClass(c)) return true;
×
204
            }
×
205
        }
×
206
        for (ViewDisplay v : data.partLevelViews) {
×
207
            if (v.getView().coversElement(elementId)) return true;
×
208
            for (IRI c : classes) {
×
209
                if (v.getView().hasTargetClass(c)) return true;
×
210
            }
×
211
        }
×
212
        return false;
×
213
    }
214

215
    private synchronized void triggerDataUpdate() {
216
        if (dataNeedsUpdate) {
×
217
            new Thread(() -> {
×
218
                try {
219
                    ResourceData newData = new ResourceData();
×
220

221
                    for (ApiResponseEntry r : QueryApiAccess.get(new QueryRef("get-view-displays", "resource", id)).getData()) {
×
222
                        if (!space.isAdminPubkey(r.get("pubkey"))) continue;
×
223
                        try {
224
                            ViewDisplay vd = ViewDisplay.get(r.get("display"));
×
225
                            if (KPXL_TERMS.PART_LEVEL_VIEW_DISPLAY.stringValue().equals(r.get("displayType"))) {
×
226
                                if (newData.partLevelViewKinds.contains(r.get("viewKind"))) continue;
×
227
                                newData.partLevelViewKinds.add(r.get("viewKind"));
×
228
                                if (KPXL_TERMS.DEACTIVATED_VIEW_DISPLAY.stringValue().equals(r.get("displayMode"))) continue;
×
229
                                newData.partLevelViews.add(vd);
×
230
                            } else {
231
                                if (newData.topLevelViewKinds.contains(r.get("viewKind"))) continue;
×
232
                                newData.topLevelViewKinds.add(r.get("viewKind"));
×
233
                                if (KPXL_TERMS.DEACTIVATED_VIEW_DISPLAY.stringValue().equals(r.get("displayMode"))) continue;
×
234
                                newData.topLevelViews.add(vd);
×
235
                            }
236
                        } catch (IllegalArgumentException ex) {
×
237
                            logger.error("Couldn't generate view display object", ex);
×
238
                        }
×
239
                    }
×
240
                    Collections.sort(newData.topLevelViews);
×
241
                    Collections.sort(newData.partLevelViews);
×
242
                    data = newData;
×
243
                    dataInitialized = true;
×
244
                } catch (Exception ex) {
×
245
                    logger.error("Error while trying to update space data: {}", ex);
×
246
                    dataNeedsUpdate = true;
×
247
                }
×
248
            }).start();
×
249
            dataNeedsUpdate = false;
×
250
        }
251
    }
×
252

253
    @Override
254
    public String toString() {
255
        return id;
×
256
    }
257

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