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

knowledgepixels / nanodash / 17380144000

01 Sep 2025 02:12PM UTC coverage: 12.03% (+0.05%) from 11.978%
17380144000

push

github

ashleycaselli
refactor: replace printStackTrace with logger.error for better error handling

330 of 3850 branches covered (8.57%)

Branch coverage included in aggregate %.

958 of 6857 relevant lines covered (13.97%)

0.62 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.Utils;
4
import com.knowledgepixels.nanodash.component.IriTextfieldItem.Validator;
5
import com.knowledgepixels.nanodash.template.Template;
6
import com.knowledgepixels.nanodash.template.TemplateContext;
7
import com.knowledgepixels.nanodash.template.UnificationException;
8
import org.apache.wicket.Component;
9
import org.apache.wicket.ajax.AjaxRequestTarget;
10
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
11
import org.apache.wicket.behavior.AttributeAppender;
12
import org.apache.wicket.markup.html.basic.Label;
13
import org.apache.wicket.markup.html.link.ExternalLink;
14
import org.apache.wicket.markup.html.panel.Panel;
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.eclipse.rdf4j.model.ValueFactory;
21
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
22
import org.slf4j.Logger;
23
import org.slf4j.LoggerFactory;
24
import org.wicketstuff.select2.ChoiceProvider;
25
import org.wicketstuff.select2.Response;
26
import org.wicketstuff.select2.Select2Choice;
27

28
import java.util.*;
29

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

35
    private static final long serialVersionUID = 1L;
36
    private TemplateContext context;
37
    private Select2Choice<String> textfield;
38
    private ExternalLink tooltipLink;
39
    private Label tooltipDescription;
40
    private IRI iri;
41
    private IModel<String> model;
42
    private static final Logger logger = LoggerFactory.getLogger(GuidedChoiceItem.class);
×
43

44
    private String prefix;
45

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

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

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

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

83
    /**
84
     * Constructor for the GuidedChoiceItem.
85
     *
86
     * @param id       The Wicket component ID.
87
     * @param parentId The parent ID, used for context.
88
     * @param iriP     The IRI associated with this choice item.
89
     * @param optional Whether the choice is optional or required.
90
     * @param context  The template context containing the template and models.
91
     */
92
    public GuidedChoiceItem(String id, String parentId, final IRI iriP, boolean optional, final TemplateContext context) {
93
        super(id);
×
94
        this.context = context;
×
95
        this.iri = iriP;
×
96
        final Template template = context.getTemplate();
×
97
        model = context.getComponentModels().get(iri);
×
98
        if (model == null) {
×
99
            model = Model.of("");
×
100
            context.getComponentModels().put(iri, model);
×
101
        }
102
        String postfix = Utils.getUriPostfix(iri);
×
103
        if (context.hasParam(postfix)) {
×
104
            model.setObject(context.getParam(postfix));
×
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
            private static final long serialVersionUID = 1L;
135

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

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

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

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

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

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

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

200
        tooltipDescription = new Label("description", new IModel<String>() {
×
201

202
            private static final long serialVersionUID = 1L;
203

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

213
        });
214
        tooltipDescription.setOutputMarkupId(true);
×
215
        add(tooltipDescription);
×
216

217
        tooltipLink = Utils.getUriLink("uri", model);
×
218
        tooltipLink.setOutputMarkupId(true);
×
219
        add(tooltipLink);
×
220

221
        textfield.add(new OnChangeAjaxBehavior() {
×
222

223
            private static final long serialVersionUID = 1L;
224

225
            @Override
226
            protected void onUpdate(AjaxRequestTarget target) {
227
                for (Component c : context.getComponents()) {
×
228
                    if (c == textfield) continue;
×
229
                    if (c.getDefaultModel() == textfield.getModel()) {
×
230
                        c.modelChanged();
×
231
                        target.add(c);
×
232
                    }
233
                }
×
234
                target.add(tooltipLink);
×
235
                target.add(tooltipDescription);
×
236
            }
×
237

238
        });
239
        add(textfield);
×
240
    }
×
241

242
    /**
243
     * Get the model associated with this choice item.
244
     *
245
     * @return The model containing the selected value.
246
     */
247
    public IModel<String> getModel() {
248
        return model;
×
249
    }
250

251
    /**
252
     * {@inheritDoc}
253
     */
254
    @Override
255
    public void removeFromContext() {
256
        context.getComponents().remove(textfield);
×
257
    }
×
258

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

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

305
    /**
306
     * {@inheritDoc}
307
     */
308
    @Override
309
    public void fillFinished() {
310
    }
×
311

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

327
    private static ValueFactory vf = SimpleValueFactory.getInstance();
×
328

329
    /**
330
     * {@inheritDoc}
331
     */
332
    @Override
333
    public String toString() {
334
        return "[Guided choiced item: " + iri + "]";
×
335
    }
336

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