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

knowledgepixels / nanodash / 17760079101

16 Sep 2025 08:42AM UTC coverage: 13.879% (+0.08%) from 13.799%
17760079101

push

github

ashleycaselli
refactor: replace hardcoded local URI prefix with LocalUri constant and checks with Utils methods

443 of 4012 branches covered (11.04%)

Branch coverage included in aggregate %.

1126 of 7293 relevant lines covered (15.44%)

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/component/IriTextfieldItem.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.template.Template;
6
import com.knowledgepixels.nanodash.template.TemplateContext;
7
import com.knowledgepixels.nanodash.template.UnificationException;
8
import net.trustyuri.TrustyUriUtils;
9
import org.apache.wicket.AttributeModifier;
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.form.TextField;
16
import org.apache.wicket.markup.html.link.ExternalLink;
17
import org.apache.wicket.markup.html.panel.Panel;
18
import org.apache.wicket.model.IModel;
19
import org.apache.wicket.model.Model;
20
import org.apache.wicket.validation.IValidatable;
21
import org.apache.wicket.validation.IValidator;
22
import org.apache.wicket.validation.Validatable;
23
import org.apache.wicket.validation.ValidationError;
24
import org.eclipse.rdf4j.common.net.ParsedIRI;
25
import org.eclipse.rdf4j.model.IRI;
26
import org.eclipse.rdf4j.model.Value;
27
import org.nanopub.SimpleCreatorPattern;
28
import org.nanopub.vocabulary.NTEMPLATE;
29
import org.slf4j.Logger;
30
import org.slf4j.LoggerFactory;
31

32
import java.net.URISyntaxException;
33

34
/**
35
 * A text field for entering IRIs, with a prefix label and validation.
36
 */
37
public class IriTextfieldItem extends Panel implements ContextComponent {
38

39
    // TODO: Make ContextComponent an abstract class with superclass Panel, and move the common code of the form items there.
40

41
    private static final long serialVersionUID = 1L;
42

43
    private String prefix;
44
    private TemplateContext context;
45
    private TextField<String> textfield;
46
    private IRI iri;
47
    private static final Logger logger = LoggerFactory.getLogger(IriTextfieldItem.class);
×
48

49
    /**
50
     * Constructor for creating an IRI text field item.
51
     *
52
     * @param id       the component ID
53
     * @param parentId the parent ID (e.g., "subj", "pred", "obj")
54
     * @param iriP     the IRI placeholder for this item
55
     * @param optional whether the field is optional
56
     * @param context  the template context containing models and components
57
     */
58
    public IriTextfieldItem(String id, String parentId, final IRI iriP, boolean optional, final TemplateContext context) {
59
        super(id);
×
60
        this.context = context;
×
61
        this.iri = iriP;
×
62
        final Template template = context.getTemplate();
×
63
        IModel<String> model = context.getComponentModels().get(iri);
×
64
        if (model == null) {
×
65
            model = Model.of("");
×
66
            context.getComponentModels().put(iri, model);
×
67
        }
68
        String postfix = Utils.getUriPostfix(iri);
×
69
        if (context.hasParam(postfix)) {
×
70
            model.setObject(context.getParam(postfix));
×
71
        }
72
        prefix = template.getPrefix(iri);
×
73
        if (prefix == null) prefix = "";
×
74
        if (template.isLocalResource(iri)) {
×
75
            prefix = Utils.getUriPrefix(iri);
×
76
        }
77
        String prefixLabel = template.getPrefixLabel(iri);
×
78
        Label prefixLabelComp;
79
        if (prefixLabel == null) {
×
80
            prefixLabelComp = new Label("prefix", "");
×
81
            prefixLabelComp.setVisible(false);
×
82
        } else {
83
            if (!prefixLabel.isEmpty() && parentId.equals("subj") && !prefixLabel.matches("https?://.*")) {
×
84
                // Capitalize first letter of label if at subject position:
85
                prefixLabel = prefixLabel.substring(0, 1).toUpperCase() + prefixLabel.substring(1);
×
86
            }
87
            prefixLabelComp = new Label("prefix", prefixLabel);
×
88
        }
89
        add(prefixLabelComp);
×
90
        String prefixTooltip = prefix;
×
91
        if (!prefix.isEmpty()) {
×
92
            prefixTooltip += "...";
×
93
            if (template.isLocalResource(iri)) {
×
94
                prefixTooltip = LocalUri.PREFIX + "...";
×
95
            }
96
        }
97
        add(new ExternalLink("prefixtooltiptext", "", prefixTooltip));
×
98
        textfield = new TextField<>("textfield", model);
×
99
        if (!optional) textfield.setRequired(true);
×
100
        if (template.isLocalResource(iri) || !prefix.isEmpty()) {
×
101
            textfield.add(new AttributeAppender("class", " short"));
×
102
        }
103
        textfield.add(new Validator(iri, template, prefix, context));
×
104
        context.getComponents().add(textfield);
×
105
        if (template.getLabel(iri) != null) {
×
106
            textfield.add(new AttributeModifier("placeholder", template.getLabel(iri).replaceFirst(" - .*$", "")));
×
107
            textfield.setLabel(Model.of(template.getLabel(iri)));
×
108
        }
109
        textfield.add(new OnChangeAjaxBehavior() {
×
110

111
            private static final long serialVersionUID = 1L;
112

113
            @Override
114
            protected void onUpdate(AjaxRequestTarget target) {
115
                for (Component c : context.getComponents()) {
×
116
                    if (c == textfield) continue;
×
117
                    if (c.getDefaultModel() == textfield.getModel()) {
×
118
                        c.modelChanged();
×
119
                        target.add(c);
×
120
                    }
121
                }
×
122
            }
×
123

124
        });
125
        if (template.isIntroducedResource(iri) || template.isEmbeddedResource(iri)) {
×
126
            textfield.add(AttributeAppender.append("class", "introduced"));
×
127
        }
128
        add(textfield);
×
129
    }
×
130

131
    /**
132
     * {@inheritDoc}
133
     */
134
    @Override
135
    public void removeFromContext() {
136
        context.getComponents().remove(textfield);
×
137
    }
×
138

139
    /**
140
     * {@inheritDoc}
141
     */
142
    @Override
143
    public boolean isUnifiableWith(Value v) {
144
        if (v == null) return true;
×
145
        if (v instanceof IRI) {
×
146
            String vs = v.stringValue();
×
147
            if (vs.startsWith(prefix)) vs = vs.substring(prefix.length());
×
148
            if (Utils.isLocalURI(vs)) vs = vs.replaceFirst("^" + LocalUri.PREFIX, "");
×
149
            if (context.getTemplate().isAutoEscapePlaceholder(iri)) {
×
150
                vs = Utils.urlDecode(vs);
×
151
            }
152
            Validatable<String> validatable = new Validatable<>(vs);
×
153
            if (context.getTemplate().isLocalResource(iri) && !Utils.isUriPostfix(vs)) {
×
154
                vs = Utils.getUriPostfix(vs);
×
155
            }
156
            new Validator(iri, context.getTemplate(), prefix, context).validate(validatable);
×
157
            if (!validatable.isValid()) {
×
158
                return false;
×
159
            }
160
            if (textfield.getModelObject().isEmpty()) {
×
161
                return true;
×
162
            }
163
            return vs.equals(textfield.getModelObject());
×
164
        }
165
        return false;
×
166
    }
167

168
    /**
169
     * {@inheritDoc}
170
     */
171
    @Override
172
    public void unifyWith(Value v) throws UnificationException {
173
        if (v == null) return;
×
174
        String vs = v.stringValue();
×
175
        if (!isUnifiableWith(v)) throw new UnificationException(vs);
×
176
        if (!prefix.isEmpty() && vs.startsWith(prefix)) {
×
177
            vs = vs.substring(prefix.length());
×
178
        } else if (Utils.isLocalURI(vs)) {
×
179
            vs = vs.replaceFirst("^" + LocalUri.PREFIX, "");
×
180
        } else if (context.getTemplate().isLocalResource(iri) && !Utils.isUriPostfix(vs)) {
×
181
            vs = Utils.getUriPostfix(vs);
×
182
        }
183
        if (context.getTemplate().isAutoEscapePlaceholder(iri)) {
×
184
            vs = Utils.urlDecode(vs);
×
185
        }
186
        textfield.setModelObject(vs);
×
187
    }
×
188

189
    /**
190
     * Validator class for validating IRI text fields.
191
     */
192
    protected static class Validator extends InvalidityHighlighting implements IValidator<String> {
193

194
        private static final long serialVersionUID = 1L;
195

196
        private IRI iri;
197
        private Template template;
198
        private String prefix;
199
        private TemplateContext context;
200

201
        /**
202
         * Constructor for creating a validator for an IRI text field.
203
         *
204
         * @param iri      the IRI placeholder for this item
205
         * @param template the template containing validation rules
206
         * @param prefix   the prefix to be used in validation
207
         * @param context  the template context containing models and components
208
         */
209
        public Validator(IRI iri, Template template, String prefix, TemplateContext context) {
×
210
            this.iri = iri;
×
211
            this.template = template;
×
212
            this.prefix = prefix;
×
213
            this.context = context;
×
214
        }
×
215

216
        @Override
217
        public void validate(IValidatable<String> s) {
218
            String sv = s.getValue();
×
219
            String p = prefix;
×
220
            if (template.isAutoEscapePlaceholder(iri)) {
×
221
                sv = Utils.urlEncode(sv);
×
222
            }
223
            if (sv.matches("https?://.+")) {
×
224
                p = "";
×
225
            } else if (sv.contains(":")) {
×
226
                s.error(new ValidationError("Colon character is not allowed in postfix"));
×
227
            }
228
            String iriString = p + sv;
×
229
            if (iriString.matches("[^:# ]+")) {
×
230
                p = LocalUri.PREFIX;
×
231
                iriString = p + sv;
×
232
            }
233
            try {
234
                ParsedIRI piri = new ParsedIRI(iriString);
×
235
                if (!piri.isAbsolute()) {
×
236
                    s.error(new ValidationError("IRI not well-formed"));
×
237
                }
238
                if (p.isEmpty() && !Utils.isLocalURI(sv) && !sv.matches("https?://.+")) {
×
239
                    s.error(new ValidationError("Only http(s):// IRIs are allowed here"));
×
240
                }
241
            } catch (URISyntaxException ex) {
×
242
                s.error(new ValidationError("IRI not well-formed"));
×
243
            }
×
244
            String regex = template.getRegex(iri);
×
245
            if (regex != null) {
×
246
                if (!sv.matches(regex)) {
×
247
                    s.error(new ValidationError("Value '" + sv + "' doesn't match the pattern '" + regex + "'"));
×
248
                }
249
            }
250
            if (template.isExternalUriPlaceholder(iri)) {
×
251
                if (!iriString.matches("https?://.+")) {
×
252
                    s.error(new ValidationError("Not an external IRI"));
×
253
                }
254
            }
255
            if (template.isTrustyUriPlaceholder(iri)) {
×
256
                if (!TrustyUriUtils.isPotentialTrustyUri(iriString)) {
×
257
                    s.error(new ValidationError("Not a trusty URI"));
×
258
                }
259
            }
260
            if (iri.equals(NTEMPLATE.CREATOR_PLACEHOLDER) && context.getExistingNanopub() != null) {
×
261
                boolean found = false;
×
262
                for (IRI creator : SimpleCreatorPattern.getCreators(context.getExistingNanopub())) {
×
263
                    if (creator.stringValue().equals(iriString)) {
×
264
                        found = true;
×
265
                        break;
×
266
                    }
267
                }
×
268
                if (!found) {
×
269
                    s.error(new ValidationError("Not a creator of nanopub"));
×
270
                }
271
            }
272
        }
×
273

274
    }
275

276
    /**
277
     * {@inheritDoc}
278
     */
279
    @Override
280
    public void fillFinished() {
281
    }
×
282

283
    /**
284
     * {@inheritDoc}
285
     */
286
    @Override
287
    public void finalizeValues() {
288
        Value defaultValue = context.getTemplate().getDefault(iri);
×
289
        if (isUnifiableWith(defaultValue)) {
×
290
            try {
291
                unifyWith(defaultValue);
×
292
            } catch (UnificationException ex) {
×
293
                logger.error("Could not unify default value {} with text field {}", defaultValue, this, ex);
×
294
            }
×
295
        }
296
    }
×
297

298
    /**
299
     * <p>toString.</p>
300
     *
301
     * @return a {@link java.lang.String} object
302
     */
303
    public String toString() {
304
        return "[IRI textfield item: " + iri + "]";
×
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