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

knowledgepixels / nanodash / 18939026354

30 Oct 2025 11:24AM UTC coverage: 14.184% (-0.1%) from 14.315%
18939026354

push

github

ashleycaselli
Merge branch 'master' of github.com:knowledgepixels/nanodash

507 of 4504 branches covered (11.26%)

Branch coverage included in aggregate %.

1322 of 8391 relevant lines covered (15.75%)

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

26
import java.util.*;
27

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

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

40
    private String prefix;
41

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

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

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

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

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

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

127
        ChoiceProvider<String> choiceProvider = new ChoiceProvider<String>() {
×
128

129
            @Override
130
            public String getDisplayValue(String choiceId) {
131
                if (choiceId == null || choiceId.isEmpty()) return "";
×
132
                String label = getChoiceLabel(choiceId);
×
133
                if (label == null || label.isBlank()) {
×
134
                    return choiceId;
×
135
                }
136
                return label + " (" + choiceId + ")";
×
137
            }
138

139
            @Override
140
            public String getIdValue(String object) {
141
                return object;
×
142
            }
143

144
            // Getting strange errors with Tomcat if this method is not overridden:
145
            @Override
146
            public void detach() {
147
            }
×
148

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

173
            @Override
174
            public Collection<String> toChoices(Collection<String> ids) {
175
                return ids;
×
176
            }
177

178
        };
179
        textfield = new Select2Choice<String>("textfield", model, choiceProvider);
×
180
        textfield.getSettings().getAjax(true).setDelay(500);
×
181
        textfield.getSettings().setCloseOnSelect(true);
×
182
        String placeholder = template.getLabel(iri);
×
183
        if (placeholder == null) placeholder = "";
×
184
        textfield.getSettings().setPlaceholder(placeholder);
×
185
        Utils.setSelect2ChoiceMinimalEscapeMarkup(textfield);
×
186
        textfield.getSettings().setAllowClear(true);
×
187

188
        if (!optional) textfield.setRequired(true);
×
189
        textfield.add(new AttributeAppender("class", " wide"));
×
190
        textfield.add(new Validator(iri, template, prefix, context));
×
191
        context.getComponents().add(textfield);
×
192

193
        tooltipDescription = new Label("description", new IModel<String>() {
×
194

195
            @Override
196
            public String getObject() {
197
                String obj = GuidedChoiceItem.this.getModel().getObject();
×
198
                if (obj == null || obj.isEmpty()) return "choose a value";
×
199
                String label = getChoiceLabel(GuidedChoiceItem.this.getModel().getObject());
×
200
                if (label == null || !label.contains(" - ")) return "";
×
201
                return label.substring(label.indexOf(" - ") + 3);
×
202
            }
203

204
        });
205
        tooltipDescription.setOutputMarkupId(true);
×
206
        add(tooltipDescription);
×
207

208
        tooltipLink = Utils.getUriLink("uri", model);
×
209
        tooltipLink.setOutputMarkupId(true);
×
210
        add(tooltipLink);
×
211

212
        textfield.add(new OnChangeAjaxBehavior() {
×
213

214
            @Override
215
            protected void onUpdate(AjaxRequestTarget target) {
216
                for (Component c : context.getComponents()) {
×
217
                    if (c == textfield) continue;
×
218
                    if (c.getDefaultModel() == textfield.getModel()) {
×
219
                        c.modelChanged();
×
220
                        target.add(c);
×
221
                    }
222
                }
×
223
                target.add(tooltipLink);
×
224
                target.add(tooltipDescription);
×
225
            }
×
226

227
        });
228
        add(textfield);
×
229
    }
×
230

231
    /**
232
     * Get the model associated with this choice item.
233
     *
234
     * @return The model containing the selected value.
235
     */
236
    public IModel<String> getModel() {
237
        return model;
×
238
    }
239

240
    /**
241
     * {@inheritDoc}
242
     */
243
    @Override
244
    public void removeFromContext() {
245
        context.getComponents().remove(textfield);
×
246
    }
×
247

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

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

294
    /**
295
     * {@inheritDoc}
296
     */
297
    @Override
298
    public void fillFinished() {
299
    }
×
300

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

316
    /**
317
     * {@inheritDoc}
318
     */
319
    @Override
320
    public String toString() {
321
        return "[Guided choiced item: " + iri + "]";
×
322
    }
323

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