Skip to content

WW-5695 Derive HTML5 constraint attributes from validators, deprecate the JS validator - #1865

Open
lukaszlenart wants to merge 25 commits into
mainfrom
feature/WW-5695-html5-constraint-validation
Open

WW-5695 Derive HTML5 constraint attributes from validators, deprecate the JS validator#1865
lukaszlenart wants to merge 25 commits into
mainfrom
feature/WW-5695-html5-constraint-validation

Conversation

@lukaszlenart

Copy link
Copy Markdown
Member

Adds HTML5 constraint validation to the html5 theme, and deprecates the generated-JavaScript
client-side validator it replaces.

Fixes WW-5695
Fixes WW-5694

Supersedes WW-2975 (closed Won't Fix), open since 2009.
Removal of the JavaScript validator is tracked separately as
WW-5696 and is not in this PR.

Why

xhtml/form-close-validate.ftl builds its validateForm_<id>() function from the form's tagNames
list, which is populated only when a UI tag finds the form via findAncestor(Form.class). Any input
reaching the markup another way — raw HTML, a custom tag, an included fragment — is silently
unvalidated. That is WW-2975, and it still reproduced on main.

Rather than patch that list, this replaces the mechanism. Constraints now ride on each <input>, so
there is no central field list for a field to be missing from — the root cause disappears instead of
being worked around.

The governing rule: never false-reject

A constraint is emitted only when the browser cannot reject input the server would have accepted.
A browser rejecting what the server allows leaves the user with a form that will not submit and no
explanation. Being conservative merely costs a field its client-side check, which is harmless.

The main consequence: Struts never sets or changes an input's type. Promoting a field to
type="number" would reject 1234,50, which locale-aware conversion accepts in a comma-decimal
locale, and the browsers' email/url grammars differ from EmailValidator/URLValidator.

What is emitted

Validator Emits Condition
requiredstring required text-entry controls and textarea
required required only radio and file
stringlength minlength / maxlength text-entry or textarea, and trim="false"
regex pattern text-entry, caseSensitive="true", trim="false", ECMAScript-safe, not email/creditcard
int, short, long, double min / max only when the control is already type="number"/range; min only when integral
date temporal formatting deferred
email, url, creditcard never
any validator with a message data-msg-<type> always, including those emitting no constraint

Struts ships no JavaScript consuming data-msg-*; they carry the resolved i18n message for an
application's own script.

Three conditions are worth knowing because they limit reach:

  • required is split because RequiredFieldValidator only fails on null/empty-array/empty-collection.
    CheckboxInterceptor substitutes "false" for an unticked box, so the server accepts what a browser
    required would block. Only radio and file omit the parameter entirely when empty.
  • minlength/maxlength and pattern both need trim="false", which is not the default — the
    server measures/matches the trimmed value while the attribute constrains the raw one. Expect both to
    be uncommon until applications opt in.
  • \s/\S are excluded from the ECMAScript-safe allowlist: Java's is ASCII-only by default while
    ECMAScript's is the wider Unicode set, so ^\S+$ would accept a value containing NBSP server-side
    and reject it in the browser.

Compatibility

Off by default (struts.ui.html5.constraints=false), so nothing renders differently on upgrade — the
html5 theme shipped in 7.2.0 and existing forms are untouched. The deprecation is annotations and
documentation only; all four validateForm_ golden files are byte-identical.

The mapping policy is a swappable bean (HtmlConstraintProvider, under struts.htmlConstraintProvider)
for applications wanting a less conservative mapping.

Testing

Full core suite: 3257 tests, 0 failures. mvn apache-rat:check clean. The plugin modules that
consume UIBean (javatemplates, velocity) verified green with -am.

Several tests exist specifically to be able to fail: the hook-ordering test was confirmed to fail when
the hook is moved, the memoisation test when the cache is disabled, and the byte-exact disabled-path
test when the template regresses — the shared golden-file harness normalises whitespace away, so it
cannot catch that class of defect on its own.

Known follow-ups

  • The trim guard over-restricts minlength: browser-reject implies server-reject there, so it could
    safely be emitted even when trimming. Widening it is a follow-up.
  • Temporal min/max needs per-control ISO formatting.
  • The try/catch guarding derivation has no regression test yet.

Documentation is a companion change in struts-site.

🤖 Generated with Claude Code

lukaszlenart and others added 25 commits August 24, 2026 18:17
…re the JS validator

Design covering the replacement of the generated JavaScript client-side
validator with native HTML5 constraint attributes derived from the action's
validators.

Governing rule is never to false-reject: a constraint is emitted only when the
browser cannot reject input the server would accept. That rules out changing an
input's type, since type="number" refuses "1234,50" which locale-aware
conversion accepts, and browser email/url regexes diverge from the framework's.
Struts only adds constraints safe for the type already present.

Also records why WW-2975 is superseded rather than fixed: constraints riding on
each input remove the central tagNames list its root cause depends on.

Covers WW-5694 (deprecate, 7.4.0), WW-5695 (constraints, 7.4.0) and WW-5696
(remove, 8.0.0). The implementation plan drawn from this covers 7.4.0 only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ype resolution

Reading the code turned up two errors in the approved design.

evaluateExtraParams() is the last statement of evaluateParams(), and that is
where TextField sets attributes.type. The hook cannot sit next to the tagNames
block as written, because at that point no text field has a resolved type and
every one of them would look like OTHER. It moves to the end of the method.

attributes.type is set by TextField and nothing else on the input path, so the
control type cannot come from the attribute map alone. Adds a getControlType()
component hook with four overrides; Checkbox, Radio, File and Hidden fall
through to OTHER, which emits nothing and is correct for all four.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nine tasks covering WW-5694 (deprecation, task 1, separately mergeable) and
WW-5695 (tasks 2-9). WW-5696's 8.0.0 removal is out of scope.

Syncs three points where writing the code diverged from the design:

- constraintsFor takes the action as a third parameter; getMessage needs it to
  resolve the i18n text behind the data-msg-* attributes.
- The validator list is memoised on Form component fields rather than the
  attributes map, which is exposed to templates.
- Temporal min/max is deferred. DateRangeFieldValidator emits nothing for now;
  honouring it needs per-control ISO formatting worth doing deliberately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tion

Marks the form tag's validate attribute and the machinery behind it for
removal in 8.0.0. Annotations and documentation only; no behaviour change.

Follows the WW-5510 precedent: annotate both the component and the JSP tag,
and put the notice inside the START SNIPPET block so the website picks it up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e attribute

The javadoc SNIPPET block published on struts.apache.org read as a
blanket deprecation of <s:form> itself, with no replacement mentioned.
Reword and move the banner to the end of the javadoc snippet so it
explicitly scopes the deprecation to the validate attribute and the
client-side JS behind it; the form tag itself is not deprecated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Models the kind of form control a UIBean renders, so constraint derivation can
ask which HTML5 attributes are legal rather than string-matching a type
attribute. Models the control, not the attribute, because textarea and select
have no type yet still accept required.

from() never throws; unknown input becomes OTHER, which supports nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e in the plan

The plan's code blocks omit the ASF header for brevity, and a task reviewer
caught an implementer transcribing that omission into a real test file.

apache-rat-plugin:check runs at prepare-package, so mvn test -DskipAssembly —
the command every task in the plan uses to verify itself — never runs the
licence check. A missing header passes every task-level gate and fails CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rolTypeTest

The task brief's code block omitted the ASF licence header; every sibling
test in this package carries it, and apache-rat:check (bound to
prepare-package) would fail on it in a full build. mvn test -DskipAssembly
never reaches that phase, so the earlier green run was blind to it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Decides whether a Java regex can become an HTML5 pattern attribute without
changing meaning. Allowlist by design: a denylist would violate the
never-false-reject rule the first time it missed a construct, and a regex the
browser reads differently is a rejection the user cannot get past.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…pe allowlist

A task reviewer found a real semantic divergence the design had certified as
portable. Java's \s is ASCII-only by default; ECMAScript's is always the wider
Unicode set. For a rule as ordinary as ^\S+$, a value containing NBSP satisfies
Java's \S and fails the browser's — so the server accepts input the browser
silently refuses to submit, which is exactly the failure this allowlist exists
to prevent.

\d and \w are safe: both engines are ASCII-only for those by default and
JavaScript never widens them, so the fix is scoped to s/S.

The spec listed \s among the safe escapes and the plan's ALLOWED_ESCAPES
transcribed it. Both corrected, plus a regression test. The governing rule
outranks its own example list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…wlist

Java's \s is ASCII-only by default; ECMAScript's is the wider Unicode
whitespace set (NBSP and friends). ^\S+$ therefore accepted a value
containing NBSP on the server while the browser's pattern attribute
rejected it silently - the exact false-true failure this class exists to
prevent. \d and \w stay allowed: both engines are ASCII-only for those and
JavaScript never widens them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…utes

Adds HtmlConstraintProvider and its conservative default implementation, which
never sets or changes an input's type: type=number would reject 1234,50 that
locale-aware conversion accepts, and browser email/url grammars differ from the
framework's validators. Range constraints therefore land only on a control the
developer already made numeric.

Registered as a swappable bean so applications wanting a best-effort mapping
can replace the policy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…in HtmlConstraintProvider

Review found two Critical defects inherited from the task brief and three
Important gaps:

- addPattern ignored RegexFieldValidator's trim=true default: the server
  matches the trimmed value while HTML pattern matches the raw one, so
  "abc " would pass server-side and be blocked client-side. Now guarded on
  isTrimed(), and EmailValidator/CreditCardValidator (whose matching diverges
  from their raw regex) are excluded outright.
- required was emitted wherever RequiredFieldValidator's null-or-empty check
  is looser than the browser's required: an empty text input, a select with
  an empty header option, and an unticked checkbox (CheckboxInterceptor
  substitutes "false") all pass server-side but would be blocked client-side.
  Split into addRequiredString (safe on any text-entry control, since
  requiredstring rejects blank too) and addRequiredField (safe only on RADIO
  and FILE, the only controls that omit the parameter entirely when empty).
- The bean was registered in struts-beans.xml but never aliased to its
  container-default name, so an @Inject HtmlConstraintProvider would not
  resolve. Added STRUTS_HTML_CONSTRAINT_PROVIDER to StrutsConstants, its
  default.properties entry, and the alias() call in
  StrutsBeanSelectionProvider, following the UrlRenderer model.
- Added regression coverage: the temporal-range early return, the new
  trim/EmailValidator/CreditCardValidator pattern exclusions, and the -1
  length sentinel, none of which had a covering test before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… caching

getValidators re-runs the action-mapping lookup and the validator-manager
resolution on every call, so a twenty-field form would do twenty of them. The
new method resolves once per form render and filters per field.

Memoised on component fields rather than the attributes map: a Form component
is built per render, and the attributes map is exposed to templates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…est memoisation

A task reviewer found the plan's own testRepeatedCallsAreConsistent vacuous:
resolution is deterministic, so comparing result sizes across two calls passes
identically against an implementation with no cache at all. Memoisation is the
entire purpose of getFieldValidators, so nothing would have caught it silently
regressing to a full resolution per field.

Replaced with a test that counts resolutions across two different field names,
and recorded the second half of the harness trap: createMocks() never sets a
config on the MockActionProxy, so the validator manager NPEs without an
explicit setConfig.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
testRepeatedCallsAreConsistent only compared list sizes across two calls to
the same field, which a non-memoised implementation would also satisfy since
resolution is deterministic -- it never observed how many times resolution
actually ran.

Replace it with a test that mocks ActionValidatorManager and asserts
getValidators(...) is invoked exactly once across lookups of two different
fields on the same Form instance, which is the actual behaviour
getFieldValidators promises. Verified the new test fails (2 invocations
instead of 1) when the resolveActionValidators() early-return is temporarily
neutralised, then passes again with it restored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
attributes.type is set by TextField and nothing else on the input path, so the
control type cannot come from the attribute map alone. Adds getControlType()
with four overrides; Checkbox, Radio, File and Hidden fall through to OTHER,
which emits nothing and is correct for all four.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…luation

Adds struts.ui.html5.constraints, default false, and wires UIBean to the
constraint provider behind it.

The hook sits at the end of evaluateParams rather than beside the tagNames
block, because evaluateExtraParams is where TextField resolves attributes.type
and it runs last; hooking earlier would make every text field look like OTHER.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ms ordering

Adds a discriminating test: a stringlength validator on an explicit
type="number" control must not emit minlength, since minlength is only
legal once TextField.evaluateExtraParams() has resolved the control type.
The prior two tests could not tell a correctly-placed hook from one hoisted
to the tagNames block, because an untyped field resolves to TEXT either way.

Also rewords the javadoc/properties comment for struts.ui.html5.constraints
to stop committing to a specific future version number; the release version
is chosen at release time, not baked into 7.4.0-SNAPSHOT.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Included from common-attributes.ftl so every html5 input picks the attributes
up without per-template edits. Values rely on FreeMarker's auto-escaping
(HTMLOutputFormat + ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, set globally by
FreemarkerManager): pattern and the data-msg-* text are author-controlled and
land inside an attribute, and the ?html builtin is rejected at parse time
under this configuration ("legacy escaping ... not allowed when
auto-escaping is on with a markup output format") because ${...} is already
escaped. Verified against the actual template with a standalone FreeMarker
render using the same Configuration: '"><script>&' comes out as
&quot;&gt;&lt;script&gt;&amp;.

Covers the regression that matters most - requiredLabel draws an asterisk and
must never emit a required attribute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…idance

The design and plan both prescribed ${value?html} for the constraints
template. That is a parse error in this repo: FreemarkerManager sets
ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY with HTMLOutputFormat, so values are
escaped by configuration and FreeMarker rejects ?html as a double-escape.
Zero templates in the tree use it.

The escaping requirement itself stands and is easy to mistake for absent when
reading the template, so both documents now say why there is no visible escape
and warn against disabling auto-escaping for this file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… input

FreeMarker only strips a line's trailing newline when that line contains
nothing but FTL tags. The <#if>/<#list>/</#if> block was on one physical
line together with the interpolated attribute text, so it failed that test
and the newline was emitted on every html5 input render - including when
struts.ui.html5.constraints is off, breaking the "changes nothing when off"
guarantee. Reformatted to the multi-line idiom already used by the sibling
accesskey block in common-attributes.ftl and by dynamic-attributes.ftl, with
</#list> and </#if> alone on their own lines.

Added testRendersExactMarkupWhenTheConstantIsOff, an assertEquals on the
full rendered string (not a contains check), which is the only kind of
assertion that can catch a stray whitespace byte; the existing suite's
normalize() strips whitespace entirely and could not have caught this.
Verified it discriminates: reverting constraints.ftl to the single-line form
made it fail on the trailing newline, restoring the multi-line form made it
pass again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…aps in HTML5 constraints

Final whole-branch review found five issues in the constraint-derivation path
added for the html5 theme's HTML5 constraint validation:

- A fractional min (e.g. minInclusive=6000.1 from a double validator) becomes
  the HTML step base on type="number"/"range", and with the default step="1"
  the browser then rejects whole numbers the server accepts. addRange and
  addDoubleRange now only emit min when the bound is integral; max is
  unaffected since it does not participate in the step base.
- addRequiredField only matched RADIO/FILE, but no component ever returned
  either control type, making it dead code. Radio and File now override
  getControlType(), the only two controls where an unselected/empty
  submission omits the parameter entirely and so agrees with the server.
  Checkbox and Hidden deliberately stay OTHER: CheckboxInterceptor
  substitutes "false" for an unticked box, so required there would
  false-reject.
- A derived constraint could duplicate a developer-set attribute (maxlength
  from a declared tag attribute, min/max from a dynamic one on a numeric
  textfield), producing invalid markup with the attribute repeated.
  addConstraintAttributes now drops any derived key already present as a
  declared or dynamic attribute, except "required" against the declared
  half: requiredLabel stores an unrelated boolean under that same key to
  draw a label asterisk, and must never suppress a genuine required
  constraint.
- Form.getFieldValidators reaches AnnotationActionValidatorManager, which
  dereferences the current ActionInvocation unconditionally - a path that
  used to need the opt-in validate="true" and now runs for every html5 form.
  Rendering outside action scope, a null validator, or a broken ${} in a
  validator message unbalancing the value stack in
  ValidatorSupport.getMessage would all turn a working page into a 500 for
  a purely decorative feature. addConstraintAttributes now catches broadly
  and logs the field name.
- Two provider tests could never fail: the CreditCardValidator and
  EmailValidator exclusion tests returned early at earlier guards
  (case-sensitivity, then isTrimed()) before ever reaching the exclusion
  they claimed to cover. Both now set caseSensitive/trim so they actually
  exercise it.

Also adds tests for the integral-min guard, the maxlength duplicate
suppression, and pins the data-msg-* escaping against a message containing
a quote and an angle bracket.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… behaviour

The design spec's mapping table and the plan's Task 4/6 code listings still
described the pre-fix behaviour: required and requiredstring both emitting
unconditionally, no EmailValidator/CreditCardValidator pattern exclusion, no
regex trim gate, and Radio/File deliberately left without a getControlType()
override. These are now the only remaining wrong description of the
feature and the input to the follow-up ticket WW-5696, so bring both back
in line with StrutsHtmlConstraintProvider as it stands, including the new
integral-min guard and duplicate-attribute suppression. Narrative sections
are left alone; only the tables and code blocks are corrected, with notes
marking what changed and why.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
No behaviour change; the core suite stays at 3257 tests, 0 failures.

Split EcmaScriptSafeRegex.isSafe into a positioning loop and an isPortable
predicate. The allowlist is the security-relevant half, and it now reads as
one method answering one question, which is the thing a reviewer has to
audit. Dropping the ++i mutation out of the loop header makes the "an escape
consumes two characters" rule explicit rather than a side effect buried in a
condition.

Also: @deprecated Javadoc on the four members deprecated in this branch, each
naming the html5 replacement; a constant for the repeated actionClass and
required literals; renamed the local in TextField.getControlType that shadowed
the field of the same name but held the evaluated value rather than the raw
OGNL expression; and three test nits.

FormTag.populateParams gets @SuppressWarnings("removal") -- the tag has to
keep forwarding validate until both sides go in 8.0.0.

Two findings are deliberately not taken. S1133 ("remove this deprecated code
someday") fires on every @deprecated and deprecation is the point of the
branch. S8924 (static import for verify) is wrong here: AbstractUITagTest
inherits verify(URL), which shadows a static import and fails to compile --
noted in a comment so the next reader does not retry it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds opt-in native HTML5 constraint validation while deprecating the legacy generated-JavaScript validator.

Changes:

  • Introduces validator-to-HTML constraint mapping and control classification.
  • Wires configurable HTML5 template rendering and validator caching.
  • Adds deprecation notices and comprehensive tests.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
docs/superpowers/specs/2026-08-24-html5-constraint-validation-design.md Records the feature design.
docs/superpowers/plans/2026-08-24-html5-constraint-validation.md Provides the implementation plan.
core/src/test/resources/org/apache/struts2/components/ConstraintAction-validation.xml Defines test validators.
core/src/test/java/org/apache/struts2/views/jsp/ui/Html5ConstraintRenderingTest.java Tests rendered attributes and escaping.
core/src/test/java/org/apache/struts2/TestConfigurationProvider.java Registers the test action.
core/src/test/java/org/apache/struts2/components/StrutsHtmlConstraintProviderTest.java Tests constraint mappings.
core/src/test/java/org/apache/struts2/components/HtmlControlTypeTest.java Tests control classification.
core/src/test/java/org/apache/struts2/components/FormFieldValidatorsTest.java Tests validator lookup caching.
core/src/test/java/org/apache/struts2/components/EcmaScriptSafeRegexTest.java Tests regex portability checks.
core/src/test/java/org/apache/struts2/components/ControlTypeTest.java Tests component control types.
core/src/test/java/org/apache/struts2/components/ConstraintAttributesTest.java Tests UIBean derivation wiring.
core/src/test/java/org/apache/struts2/components/ConstraintAction.java Supplies test fields.
core/src/main/resources/template/xhtml/form-close-validate.ftl Documents legacy validator deprecation.
core/src/main/resources/template/html5/constraints.ftl Renders derived attributes.
core/src/main/resources/template/html5/common-attributes.ftl Includes constraint rendering.
core/src/main/resources/struts-beans.xml Registers the default provider.
core/src/main/resources/org/apache/struts2/default.properties Adds feature and provider settings.
core/src/main/java/org/apache/struts2/views/jsp/ui/FormTag.java Deprecates JSP validation API.
core/src/main/java/org/apache/struts2/StrutsConstants.java Defines new configuration keys.
core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java Enables provider selection.
core/src/main/java/org/apache/struts2/components/UIBean.java Derives and filters constraints.
core/src/main/java/org/apache/struts2/components/TextField.java Resolves text input types.
core/src/main/java/org/apache/struts2/components/TextArea.java Identifies textarea controls.
core/src/main/java/org/apache/struts2/components/StrutsHtmlConstraintProvider.java Implements conservative mappings.
core/src/main/java/org/apache/struts2/components/Select.java Identifies select controls.
core/src/main/java/org/apache/struts2/components/Radio.java Identifies radio controls.
core/src/main/java/org/apache/struts2/components/Password.java Identifies password controls.
core/src/main/java/org/apache/struts2/components/HtmlControlType.java Models HTML control capabilities.
core/src/main/java/org/apache/struts2/components/HtmlConstraintProvider.java Defines the extension API.
core/src/main/java/org/apache/struts2/components/Form.java Caches and filters validators.
core/src/main/java/org/apache/struts2/components/File.java Identifies file controls.
core/src/main/java/org/apache/struts2/components/EcmaScriptSafeRegex.java Checks Java/ECMAScript portability.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +950 to +952
try {
Map<String, String> constraints = htmlConstraintProvider.constraintsFor(
form.getFieldValidators(fieldName), getControlType(), stack.peek());
Comment on lines +961 to +963
} catch (Exception e) {
LOG.warn("Failed to derive HTML5 constraint attributes for field [{}], skipping", fieldName, e);
}
* on the server and rejects it in the browser. {@code \d} and {@code \w} are safe — both engines
* are ASCII-only for those, and JavaScript never widens them.
*/
private static final String ALLOWED_ESCAPES = "dDwWbBnrtf\\.*+?()[]{}|^$/-";
Comment on lines +29 to +31
* The default implementation is deliberately conservative — see {@link StrutsHtmlConstraintProvider}.
* Applications wanting a best-effort mapping (an {@code email} validator becoming
* {@code type="email"}, say) should register their own implementation instead.
Comment on lines +978 to 983
private boolean isAlreadyRendered(String attributeName) {
if (dynamicAttributes.containsKey(attributeName)) {
return true;
}
return !"required".equals(attributeName) && getAttributes().containsKey(attributeName);
}
TEXT, SEARCH, TEL, PASSWORD, EMAIL, URL,
NUMBER, RANGE,
DATE, MONTH, WEEK, TIME, DATETIME_LOCAL,
CHECKBOX, RADIO, FILE, HIDDEN, SELECT,
Comment on lines +185 to +191
private boolean isIntegral(Object value) {
if (!(value instanceof java.lang.Number number)) {
return false;
}
double asDouble = number.doubleValue();
return !Double.isNaN(asDouble) && !Double.isInfinite(asDouble) && asDouble == Math.floor(asDouble);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants