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

knowledgepixels / nanodash / 21596202545

02 Feb 2026 03:29PM UTC coverage: 14.306% (+0.02%) from 14.286%
21596202545

push

github

web-flow
Merge pull request #349 from knowledgepixels/343-domain-model-refactor

Domain model refactor to avoid confusion around different meanings

570 of 5258 branches covered (10.84%)

Branch coverage included in aggregate %.

1554 of 9589 relevant lines covered (16.21%)

2.1 hits per line

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

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

3
import com.knowledgepixels.nanodash.LocalUri;
4
import com.knowledgepixels.nanodash.ResourceWithProfile;
5
import com.knowledgepixels.nanodash.Utils;
6
import com.knowledgepixels.nanodash.component.IriTextfieldItem.Validator;
7
import com.knowledgepixels.nanodash.template.Template;
8
import com.knowledgepixels.nanodash.template.TemplateContext;
9
import com.knowledgepixels.nanodash.template.UnificationException;
10
import org.apache.wicket.Component;
11
import org.apache.wicket.ajax.AjaxRequestTarget;
12
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
13
import org.apache.wicket.behavior.AttributeAppender;
14
import org.apache.wicket.markup.html.basic.Label;
15
import org.apache.wicket.markup.html.link.ExternalLink;
16
import org.apache.wicket.model.IModel;
17
import org.apache.wicket.model.Model;
18
import org.apache.wicket.validation.Validatable;
19
import org.eclipse.rdf4j.model.IRI;
20
import org.eclipse.rdf4j.model.Value;
21
import org.slf4j.Logger;
22
import org.slf4j.LoggerFactory;
23
import org.wicketstuff.select2.ChoiceProvider;
24
import org.wicketstuff.select2.Response;
25
import org.wicketstuff.select2.Select2Choice;
26

27
import java.util.*;
28

29
/**
30
 * A guided choice item that allows users to select from a list of predefined values.
31
 */
32
public class GuidedChoiceItem extends AbstractContextComponent {
33

34
    private Select2Choice<String> textfield;
35
    private ExternalLink tooltipLink;
36
    private Label tooltipDescription;
37
    private IRI iri;
38
    private IModel<String> model;
39
    private static final Logger logger = LoggerFactory.getLogger(GuidedChoiceItem.class);
×
40

41
    private String prefix;
42

43
    // TODO: This map being static could mix up labels if the same URI is described at different places:
44
    // TODO: This should maybe go into a different class?
45
    private static Map<String, String> labelMap = new HashMap<>();
×
46

47
    /**
48
     * Get the label for a given value.
49
     *
50
     * @param value The value for which to get the label.
51
     * @return The label associated with the value, or null if not found.
52
     */
53
    public static String getLabel(String value) {
54
        return labelMap.get(value);
×
55
    }
56

57
    /**
58
     * Set a label for a given value.
59
     *
60
     * @param value The value for which to set the label.
61
     * @param label The label to associate with the value.
62
     * @return The previous label associated with the value, or null if there was none.
63
     */
64
    public static String setLabel(String value, String label) {
65
        return labelMap.put(value, label);
×
66
    }
67

68
    private String getChoiceLabel(String choiceId) {
69
        Template template = context.getTemplate();
×
70
        String label = null;
×
71
        if (choiceId.matches("https?://.+") && template.getLabel(vf.createIRI(choiceId)) != null) {
×
72
            label = template.getLabel(vf.createIRI(choiceId));
×
73
        } else if (labelMap.containsKey(choiceId)) {
×
74
            label = labelMap.get(choiceId);
×
75
            if (label.length() > 160) label = label.substring(0, 157) + "...";
×
76
        }
77
        return label;
×
78
    }
79

80
    /**
81
     * Constructor for the GuidedChoiceItem.
82
     *
83
     * @param id       The Wicket component ID.
84
     * @param parentId The parent ID, used for context.
85
     * @param iriP     The IRI associated with this choice item.
86
     * @param optional Whether the choice is optional or required.
87
     * @param context  The template context containing the template and models.
88
     */
89
    public GuidedChoiceItem(String id, String parentId, final IRI iriP, boolean optional, final TemplateContext context) {
90
        super(id, context);
×
91
        this.iri = iriP;
×
92
        final Template template = context.getTemplate();
×
93
        model = (IModel<String>) context.getComponentModels().get(iri);
×
94
        if (model == null) {
×
95
            model = Model.of("");
×
96
            context.getComponentModels().put(iri, model);
×
97
        }
98
        String postfix = Utils.getUriPostfix(iri);
×
99
        if (context.hasParam(postfix)) {
×
100
            String objId = context.getParam(postfix);
×
101
            if (ResourceWithProfile.get(objId) != null) {
×
102
                labelMap.put(objId, ResourceWithProfile.get(objId).getLabel());
×
103
            }
104
            model.setObject(objId);
×
105
        }
106
        final List<String> possibleValues = new ArrayList<>();
×
107
        for (Value v : template.getPossibleValues(iri)) {
×
108
            possibleValues.add(v.toString());
×
109
        }
×
110

111
        prefix = template.getPrefix(iri);
×
112
        if (prefix == null) prefix = "";
×
113
        String prefixLabel = template.getPrefixLabel(iri);
×
114
        Label prefixLabelComp;
115
        if (prefixLabel == null) {
×
116
            prefixLabelComp = new Label("prefix", "");
×
117
            prefixLabelComp.setVisible(false);
×
118
        } else {
119
            if (!prefixLabel.isEmpty() && parentId.equals("subj") && !prefixLabel.matches("https?://.*")) {
×
120
                // Capitalize first letter of label if at subject position:
121
                prefixLabel = prefixLabel.substring(0, 1).toUpperCase() + prefixLabel.substring(1);
×
122
            }
123
            prefixLabelComp = new Label("prefix", prefixLabel);
×
124
        }
125
        add(prefixLabelComp);
×
126
        String prefixTooltip = prefix;
×
127
        if (!prefix.isEmpty()) {
×
128
            prefixTooltip += "...";
×
129
        }
130
        add(new Label("prefixtooltiptext", prefixTooltip));
×
131

132
        ChoiceProvider<String> choiceProvider = new ChoiceProvider<String>() {
×
133

134
            @Override
135
            public String getDisplayValue(String choiceId) {
136
                if (choiceId == null || choiceId.isEmpty()) return "";
×
137
                String label = getChoiceLabel(choiceId);
×
138
                if (label == null || label.isBlank()) {
×
139
                    return choiceId;
×
140
                }
141
                return label + " (" + choiceId + ")";
×
142
            }
143

144
            @Override
145
            public String getIdValue(String object) {
146
                return object;
×
147
            }
148

149
            // Getting strange errors with Tomcat if this method is not overridden:
150
            @Override
151
            public void detach() {
152
            }
×
153

154
            @Override
155
            public void query(String term, int page, Response<String> response) {
156
                if (term == null) {
×
157
                    response.addAll(possibleValues);
×
158
                    return;
×
159
                }
160
                if (term.startsWith("https://") || term.startsWith("http://")) {
×
161
                    if (prefix == null || term.startsWith(prefix)) {
×
162
                        response.add(term);
×
163
                    }
164
                }
165
                Map<String, Boolean> alreadyAddedMap = new HashMap<>();
×
166
                term = term.toLowerCase();
×
167
                for (String s : possibleValues) {
×
168
                    if (s.toLowerCase().contains(term) || getDisplayValue(s).toLowerCase().contains(term)) {
×
169
                        response.add(s);
×
170
                        alreadyAddedMap.put(s, true);
×
171
                    }
172
                }
×
173
                for (String v : context.getTemplate().getPossibleValuesFromApi(iri, term, labelMap)) {
×
174
                    if (!alreadyAddedMap.containsKey(v)) response.add(v);
×
175
                }
×
176
            }
×
177

178
            @Override
179
            public Collection<String> toChoices(Collection<String> ids) {
180
                return ids;
×
181
            }
182

183
        };
184
        textfield = new Select2Choice<String>("textfield", model, choiceProvider);
×
185
        textfield.getSettings().getAjax(true).setDelay(500);
×
186
        textfield.getSettings().setCloseOnSelect(true);
×
187
        String placeholder = template.getLabel(iri);
×
188
        if (placeholder == null) placeholder = "";
×
189
        textfield.getSettings().setPlaceholder(placeholder);
×
190
        Utils.setSelect2ChoiceMinimalEscapeMarkup(textfield);
×
191
        textfield.getSettings().setAllowClear(true);
×
192

193
        if (!optional) textfield.setRequired(true);
×
194
        textfield.add(new AttributeAppender("class", " wide"));
×
195
        textfield.add(new Validator(iri, template, prefix, context));
×
196
        context.getComponents().add(textfield);
×
197

198
        tooltipDescription = new Label("description", new IModel<String>() {
×
199

200
            @Override
201
            public String getObject() {
202
                String obj = GuidedChoiceItem.this.getModel().getObject();
×
203
                if (obj == null || obj.isEmpty()) return "choose a value";
×
204
                String label = getChoiceLabel(GuidedChoiceItem.this.getModel().getObject());
×
205
                if (label == null || !label.contains(" - ")) return "";
×
206
                return label.substring(label.indexOf(" - ") + 3);
×
207
            }
208

209
        });
210
        tooltipDescription.setOutputMarkupId(true);
×
211
        add(tooltipDescription);
×
212

213
        tooltipLink = Utils.getUriLink("uri", model);
×
214
        tooltipLink.setOutputMarkupId(true);
×
215
        add(tooltipLink);
×
216

217
        textfield.add(new OnChangeAjaxBehavior() {
×
218

219
            @Override
220
            protected void onUpdate(AjaxRequestTarget target) {
221
                for (Component c : context.getComponents()) {
×
222
                    if (c == textfield) continue;
×
223
                    if (c.getDefaultModel() == textfield.getModel()) {
×
224
                        c.modelChanged();
×
225
                        target.add(c);
×
226
                    }
227
                }
×
228
                target.add(tooltipLink);
×
229
                target.add(tooltipDescription);
×
230
            }
×
231

232
        });
233
        add(textfield);
×
234
    }
×
235

236
    /**
237
     * Get the model associated with this choice item.
238
     *
239
     * @return The model containing the selected value.
240
     */
241
    public IModel<String> getModel() {
242
        return model;
×
243
    }
244

245
    /**
246
     * {@inheritDoc}
247
     */
248
    @Override
249
    public void removeFromContext() {
250
        context.getComponents().remove(textfield);
×
251
    }
×
252

253
    /**
254
     * {@inheritDoc}
255
     */
256
    @Override
257
    public boolean isUnifiableWith(Value v) {
258
        if (v == null) return true;
×
259
        if (v instanceof IRI) {
×
260
            String vs = v.stringValue();
×
261
            if (vs.startsWith(prefix)) vs = vs.substring(prefix.length());
×
262
            if (Utils.isLocalURI(vs)) vs = vs.replaceFirst("^" + LocalUri.PREFIX, "");
×
263
            Validatable<String> validatable = new Validatable<>(vs);
×
264
            if (context.getTemplate().isLocalResource(iri) && !Utils.isUriPostfix(vs)) {
×
265
                vs = Utils.getUriPostfix(vs);
×
266
            }
267
            new Validator(iri, context.getTemplate(), prefix, context).validate(validatable);
×
268
            if (!validatable.isValid()) {
×
269
                return false;
×
270
            }
271
            if (textfield.getModelObject() == null || textfield.getModelObject().isEmpty()) {
×
272
                return true;
×
273
            }
274
            return vs.equals(textfield.getModelObject());
×
275
        }
276
        return false;
×
277
    }
278

279
    /**
280
     * {@inheritDoc}
281
     */
282
    @Override
283
    public void unifyWith(Value v) throws UnificationException {
284
        if (v == null) return;
×
285
        if (!isUnifiableWith(v)) throw new UnificationException(v.stringValue());
×
286
        String vs = v.stringValue();
×
287
        if (prefix != null && vs.startsWith(prefix)) {
×
288
            vs = vs.substring(prefix.length());
×
289
        } else if (Utils.isLocalURI(vs)) {
×
290
            vs = vs.replaceFirst("^" + LocalUri.PREFIX, "");
×
291
        }
292
        textfield.setModelObject(vs);
×
293
        // TODO: This should be done differently, at a different place (can slow down unification):
294
        if (!labelMap.containsKey(vs)) {
×
295
            context.getTemplate().getPossibleValuesFromApi(iri, vs, labelMap);
×
296
        }
297
    }
×
298

299
    /**
300
     * {@inheritDoc}
301
     */
302
    @Override
303
    public void fillFinished() {
304
    }
×
305

306
    /**
307
     * {@inheritDoc}
308
     */
309
    @Override
310
    public void finalizeValues() {
311
        Value defaultValue = context.getTemplate().getDefault(iri);
×
312
        if (isUnifiableWith(defaultValue)) {
×
313
            try {
314
                unifyWith(defaultValue);
×
315
            } catch (UnificationException ex) {
×
316
                logger.error("Could not unify default value: {}", defaultValue, ex);
×
317
            }
×
318
        }
319
    }
×
320

321
    /**
322
     * {@inheritDoc}
323
     */
324
    @Override
325
    public String toString() {
326
        return "[Guided choiced item: " + iri + "]";
×
327
    }
328

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