| /* |
| * Copyright (c) 2014, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Marcel Valovy - 2.6 - initial implementation |
| package org.eclipse.persistence.jaxb.plugins; |
| |
| import static com.sun.xml.xsom.XSFacet.FACET_FRACTIONDIGITS; |
| import static com.sun.xml.xsom.XSFacet.FACET_LENGTH; |
| import static com.sun.xml.xsom.XSFacet.FACET_MAXEXCLUSIVE; |
| import static com.sun.xml.xsom.XSFacet.FACET_MAXINCLUSIVE; |
| import static com.sun.xml.xsom.XSFacet.FACET_MAXLENGTH; |
| import static com.sun.xml.xsom.XSFacet.FACET_MINEXCLUSIVE; |
| import static com.sun.xml.xsom.XSFacet.FACET_MININCLUSIVE; |
| import static com.sun.xml.xsom.XSFacet.FACET_MINLENGTH; |
| import static com.sun.xml.xsom.XSFacet.FACET_PATTERN; |
| import static com.sun.xml.xsom.XSFacet.FACET_TOTALDIGITS; |
| |
| import java.io.IOException; |
| import java.security.AccessController; |
| import java.security.PrivilegedActionException; |
| import java.security.PrivilegedExceptionAction; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import jakarta.xml.bind.annotation.XmlElement; |
| |
| import org.xml.sax.ErrorHandler; |
| import org.xml.sax.SAXParseException; |
| |
| import com.sun.codemodel.JAnnotationArrayMember; |
| import com.sun.codemodel.JAnnotationUse; |
| import com.sun.codemodel.JClass; |
| import com.sun.codemodel.JCodeModel; |
| import com.sun.codemodel.JExpressionImpl; |
| import com.sun.codemodel.JFieldVar; |
| import com.sun.codemodel.JFormatter; |
| import com.sun.codemodel.JType; |
| import com.sun.tools.xjc.BadCommandLineException; |
| import com.sun.tools.xjc.Options; |
| import com.sun.tools.xjc.Plugin; |
| import com.sun.tools.xjc.model.CAttributePropertyInfo; |
| import com.sun.tools.xjc.model.CCustomizable; |
| import com.sun.tools.xjc.model.CElementPropertyInfo; |
| import com.sun.tools.xjc.model.CPluginCustomization; |
| import com.sun.tools.xjc.model.CPropertyInfo; |
| import com.sun.tools.xjc.model.CPropertyVisitor2; |
| import com.sun.tools.xjc.model.CReferencePropertyInfo; |
| import com.sun.tools.xjc.model.CValuePropertyInfo; |
| import com.sun.tools.xjc.outline.ClassOutline; |
| import com.sun.tools.xjc.outline.Outline; |
| import com.sun.xml.xsom.XSAttributeUse; |
| import com.sun.xml.xsom.XSElementDecl; |
| import com.sun.xml.xsom.XSFacet; |
| import com.sun.xml.xsom.XSParticle; |
| import com.sun.xml.xsom.XSRestrictionSimpleType; |
| import com.sun.xml.xsom.XSSimpleType; |
| import com.sun.xml.xsom.XSTerm; |
| import com.sun.xml.xsom.XSType; |
| import com.sun.xml.xsom.impl.parser.DelayedRef; |
| |
| |
| /** |
| * XJC Plugin for generation of JSR349 (Bean Validation) annotations. |
| * <p> |
| * Has two mods: |
| * <blockquote><pre> |
| * "jsr303" - enables backward compatibility. |
| * "simpleRegex" - disables translation of UNICODE XML regex into UNICODE Java regex. |
| * Java ASCII regex are shorter to read. |
| * </pre></blockquote> |
| * Is capable of generating the following annotations: |
| * <blockquote><pre> |
| * - @DecimalMax |
| * - @DecimalMin |
| * - @Digits |
| * - @NotNull |
| * - @Pattern |
| * - @Size |
| * - @Valid |
| * - @AssertTrue |
| * - @AssertFalse |
| * - @Future |
| * - @Past |
| * </pre></blockquote> |
| * Reacts to the following XSD restrictions and facets: |
| * <blockquote><pre> |
| * - maxExclusive |
| * - minExclusive |
| * - maxInclusive |
| * - minInclusive |
| * - nillable |
| * - pattern |
| * - length |
| * - maxLength |
| * - minLength |
| * - minOccurs |
| * - maxOccurs |
| * </pre></blockquote> |
| * Basic usage: |
| * <blockquote><pre> |
| * {@code xjc file.xsd -XBeanVal} |
| * </pre></blockquote> |
| * Example usage with mods: |
| * <blockquote><pre> |
| * {@code xjc file.xsd -XBeanVal jsr303 simpleRegex} |
| * </pre></blockquote> |
| * Programmatic usage, with mods and extensions enabled: |
| * <blockquote><pre> |
| * Driver.run(new String[] { schemaPath, "-extension", "-XBeanVal", "jsr303", "simpleRegex" }, System.out, |
| * System.out); |
| * </pre></blockquote> |
| * Supports setting Target groups and Error message through binding customizations. Example: |
| * <blockquote><pre> |
| * {@code <xs:appinfo> |
| * <jxb:bindings node="/xs:schema/xs:complexType/xs:sequence/xs:element[@name='generic']"> |
| * <bv:facet type="maxLength" message="Hello, world!" groups="Object"/> |
| * <bv:facet type="future" message="Welcome to the Future!"/> |
| * </jxb:bindings> |
| * </xs:appinfo>} |
| * </pre></blockquote> |
| * <p> |
| * Supports custom-created BV annotations. Example: |
| * <blockquote><pre> |
| * {@code <xs:appinfo> |
| * <jxb:bindings node="/xs:schema/xs:complexType/xs:sequence/xs:element[@name='generic']"> |
| * <bv:facet type="org.eclipse.persistence.annotations.AdditionalCriteria" value="This is a real custom annotation."/> |
| * </jxb:bindings> |
| * </xs:appinfo>} |
| * </pre></blockquote> |
| * |
| * @author Marcel Valovy - marcel.valovy@oracle.com |
| */ |
| @java.lang.SuppressWarnings("squid:S1191") |
| public class BeanValidationPlugin extends Plugin { |
| |
| /* ######### LAUNCHING ######### */ |
| public static final String PLUGIN_OPTION = "XBeanVal"; |
| public static final String JSR_303_MOD = "jsr303"; |
| public static final String SIMPLE_REGEX_MOD = "simpleRegex"; |
| public static final String NS_URI = "http://jaxb.dev.java.net/plugin/bean-validation"; |
| public static final String FACET = "facet"; |
| |
| private static final String VALUE = "value"; |
| |
| private boolean jsr303 = false; |
| private boolean simpleRegex = false; |
| |
| @Override |
| public String getOptionName() { |
| return PLUGIN_OPTION; |
| } |
| |
| @Override |
| public String getUsage() { |
| return " -" + PLUGIN_OPTION + " : convert xsd restrictions to" + |
| " jakarta.validation annotations. Usage with mods: -" + PLUGIN_OPTION |
| + " " + JSR_303_MOD + " " + SIMPLE_REGEX_MOD; |
| } |
| |
| @Override |
| public List<String> getCustomizationURIs() { |
| return Collections.singletonList(NS_URI); |
| } |
| |
| @Override |
| public boolean isCustomizationTagName(String nsUri, String localName) { |
| return nsUri.equals(NS_URI) && localName.equals(FACET); |
| } |
| |
| @Override |
| public int parseArgument(Options opt, String[] args, int i) throws BadCommandLineException, IOException { |
| int mods = 0; |
| int argNumber = i; |
| if (("-" + PLUGIN_OPTION).equals(args[i])) { |
| while (++argNumber < args.length) { |
| if (args[argNumber].contains(JSR_303_MOD)) { |
| jsr303 = true; |
| mods++; |
| } else if (args[argNumber].contains(SIMPLE_REGEX_MOD)) { |
| simpleRegex = true; |
| mods++; |
| } |
| } |
| return 1 + mods; |
| } |
| return 0; |
| } |
| |
| /* ######### CORE FUNCTIONALITY ######### */ |
| private static final String PATTERN_ANNOTATION_NOT_APPLICABLE = "Facet \"pattern\" was detected on a DOM node with non-string base type. Annotation was not generated, because it is not supported by the Bean Validation specification."; |
| private final boolean securityEnabled = System.getSecurityManager() != null; |
| |
| private static final JClass ANNOTATION_VALID; |
| private static final JClass ANNOTATION_NOTNULL; |
| private static final JClass ANNOTATION_SIZE; |
| private static final JClass ANNOTATION_DECIMALMIN; |
| private static final JClass ANNOTATION_DECIMALMAX; |
| private static final JClass ANNOTATION_DIGITS; |
| private static final JClass ANNOTATION_PATTERN; |
| private static final JClass ANNOTATION_PATTERNLIST; |
| private static final JClass ANNOTATION_ASSERTFALSE; |
| private static final JClass ANNOTATION_ASSERTTRUE; |
| private static final JClass ANNOTATION_FUTURE; |
| private static final JClass ANNOTATION_PAST; |
| private static final JClass ANNOTATION_XMLELEMENT; |
| |
| // We want this plugin to work without requiring the presence of JSR-303/349 jar. |
| private static final JCodeModel CODEMODEL = new JCodeModel(); |
| |
| static { |
| ANNOTATION_VALID = CODEMODEL.ref("jakarta.validation.Valid"); |
| ANNOTATION_NOTNULL = CODEMODEL.ref("jakarta.validation.constraints.NotNull"); |
| ANNOTATION_SIZE = CODEMODEL.ref("jakarta.validation.constraints.Size"); |
| ANNOTATION_DECIMALMIN = CODEMODEL.ref("jakarta.validation.constraints.DecimalMin"); |
| ANNOTATION_DECIMALMAX = CODEMODEL.ref("jakarta.validation.constraints.DecimalMax"); |
| ANNOTATION_DIGITS = CODEMODEL.ref("jakarta.validation.constraints.Digits"); |
| ANNOTATION_PATTERN = CODEMODEL.ref("jakarta.validation.constraints.Pattern"); |
| ANNOTATION_PATTERNLIST = CODEMODEL.ref("jakarta.validation.constraints.Pattern.List"); |
| ANNOTATION_ASSERTFALSE = CODEMODEL.ref("jakarta.validation.constraints.AssertFalse"); |
| ANNOTATION_ASSERTTRUE = CODEMODEL.ref("jakarta.validation.constraints.AssertTrue"); |
| ANNOTATION_FUTURE = CODEMODEL.ref("jakarta.validation.constraints.Future"); |
| ANNOTATION_PAST = CODEMODEL.ref("jakarta.validation.constraints.Past"); |
| ANNOTATION_XMLELEMENT = CODEMODEL.ref("jakarta.xml.bind.annotation.XmlElement"); |
| } |
| |
| @Override |
| public boolean run(Outline outline, Options opts, ErrorHandler errorHandler) { |
| final Visitor visitor = this.new Visitor(); |
| for (ClassOutline classOutline : outline.getClasses()) { |
| for (CPropertyInfo property : classOutline.target.getProperties()) { |
| property.accept(visitor, classOutline); |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Processes an xsd value in form of xsd attribute from extended base. |
| * <p> |
| * Example: |
| * <pre>{@code |
| * <xsd:complexType name="Employee"> |
| * <xsd:simpleContent> |
| * <xsd:extension base="a:ShortId"> <- xsd extension base |
| * <xsd:attribute name="id" type="xsd:string" use="optional"/> |
| * </xsd:extension> |
| * </xsd:simpleContent> |
| * </xsd:complexType> |
| * <p> |
| * <xsd:simpleType name="ShortId"> |
| * <xsd:restriction base="xsd:string"> |
| * <xsd:minLength value="1"/> <- This is a special field that is added to the generated class, called "value" (corresponds to the valuePropertyName), |
| * <xsd:maxLength value="5"/> it gets processed by this method and the "value" field receives @Size(min = 1, max = 5). |
| * </xsd:restriction> |
| * </xsd:simpleType>}</pre> |
| */ |
| private void processValueFromExtendedBase(CValuePropertyInfo valueProperty, ClassOutline classOutline, List<FacetCustomization> customizations) { |
| String valuePropertyName = valueProperty.getName(false); |
| |
| JFieldVar fieldVar = classOutline.implClass.fields().get(valuePropertyName); |
| XSSimpleType type = ((XSRestrictionSimpleType) valueProperty.getSchemaComponent()).asSimpleType(); |
| |
| processSimpleType(null, type, fieldVar, customizations); |
| } |
| |
| /** |
| * Processes an xsd attribute. |
| * <p> |
| * Example: |
| * <pre>{@code |
| * <xsd:complexType name="Employee"> |
| * <xsd:simpleContent> |
| * <xsd:extension base="a:Person"> |
| * <xsd:attribute name="id" type="xsd:string" use="optional"/> << "id" is the attributePropertyName |
| * </xsd:extension> |
| * </xsd:simpleContent> |
| * </xsd:complexType>}</pre> |
| */ |
| private void processAttribute(CAttributePropertyInfo attributeProperty, ClassOutline classOutline, List<FacetCustomization> customizations) { |
| String attributePropertyName = attributeProperty.getName(false); |
| JFieldVar fieldVar = classOutline.implClass.fields().get(attributePropertyName); |
| |
| XSAttributeUse attribute = (XSAttributeUse) attributeProperty.getSchemaComponent(); |
| XSSimpleType type = attribute.getDecl().getType(); |
| |
| // Use="required". It makes sense to annotate a required attribute with @NotNull even though it's not 100 % semantically equivalent. |
| if (attribute.isRequired() && !fieldVar.type().isPrimitive()) { |
| notNullAnnotate(fieldVar); |
| } |
| |
| processSimpleType(null, type, fieldVar, customizations); |
| } |
| |
| /** |
| * Processes an xsd element. |
| * <p> |
| * Example: |
| * {@code <xsd:element name="someCollection" minOccurs="1" maxOccurs="unbounded"/>} |
| */ |
| private void processElement(CElementPropertyInfo propertyInfo, ClassOutline co, List<FacetCustomization> customizations) { |
| XSParticle particle = (XSParticle) propertyInfo.getSchemaComponent(); |
| JFieldVar fieldVar = co.implClass.fields().get(propertyInfo.getName(false)); |
| |
| processMinMaxOccurs(particle, fieldVar); |
| |
| XSTerm term = particle.getTerm(); |
| if (term instanceof XSElementDecl) { |
| processTermElement(particle, fieldVar, (XSElementDecl) term, customizations); |
| // When a complex type resides inside another complex type and thus gets lazily loaded or processed. |
| } else if (term instanceof DelayedRef.Element) { |
| processTermElement(particle, fieldVar, ((DelayedRef.Element) term).get(), customizations); |
| } |
| } |
| |
| private void processTermElement(XSParticle particle, JFieldVar fieldVar, XSElementDecl element, List<FacetCustomization> customizations) { |
| final int minOccurs = particle.getMinOccurs().intValue(); |
| XSType elementType = element.getType(); |
| |
| if (elementType.isComplexType()) { |
| validAnnotate(fieldVar); |
| if (!element.isNillable() && minOccurs > 0) { |
| notNullAnnotate(fieldVar); |
| } |
| if (elementType.getBaseType().isSimpleType()) { |
| processSimpleType(particle, elementType.getBaseType().asSimpleType(), fieldVar, customizations); |
| } |
| } else { |
| processSimpleType(particle, elementType.asSimpleType(), fieldVar, customizations); |
| } |
| } |
| |
| private void processSimpleType(XSParticle particle, XSSimpleType simpleType, JFieldVar fieldVar, List<FacetCustomization> customizations) { |
| Map<JAnnotationUse, FacetType> annotationsAndTheirOrigin = new HashMap<>(); |
| |
| applyAnnotations(particle, simpleType, fieldVar, annotationsAndTheirOrigin); |
| applyCustomizations(fieldVar, customizations, annotationsAndTheirOrigin); |
| } |
| |
| /* |
| * Reads supported facets from the simpleType. |
| * Converts them to corresponding bean validation annotations. |
| * Annotations are applied on the fieldVar if it isn't yet annotated by them. |
| * Why? If there is something else (e.g. plugin) which generates BV annotations, |
| * we mustn't interfere with it. Two annotations of the same kind on a Java field |
| * do not compile. |
| * Stores the applied annotations and their origin into the map arg. |
| */ |
| private void applyAnnotations(XSParticle particle, XSSimpleType simpleType, JFieldVar fieldVar, Map<JAnnotationUse, FacetType> a) { |
| XSFacet facet = null; // Auxiliary field. |
| JType fieldType = fieldVar.type(); |
| if (notAnnotated(fieldVar, ANNOTATION_SIZE) && isSizeAnnotationApplicable(fieldType)) { |
| try { |
| if ((facet = simpleType.getFacet(FACET_LENGTH)) != null) { |
| int length = Integer.parseInt(facet.getValue().value); |
| a.put(fieldVar.annotate(ANNOTATION_SIZE).param("min", length).param("max", length), FacetType.length); |
| } else { |
| Integer minLength = (facet = simpleType.getFacet(FACET_MINLENGTH)) != null ? Integer.parseInt(facet.getValue().value) : null; |
| Integer maxLength = (facet = simpleType.getFacet(FACET_MAXLENGTH)) != null ? Integer.parseInt(facet.getValue().value) : null; |
| |
| // Note: If using both minLength + maxLength, the minLength's customizations are considered. |
| if (minLength != null && maxLength != null) { |
| a.put(fieldVar.annotate(ANNOTATION_SIZE).param("min", minLength).param("max", maxLength), FacetType.minLength); |
| } else if (minLength != null) { |
| a.put(fieldVar.annotate(ANNOTATION_SIZE).param("min", minLength), FacetType.minLength); |
| } else if (maxLength != null) { |
| a.put(fieldVar.annotate(ANNOTATION_SIZE).param("max", maxLength), FacetType.maxLength); |
| } |
| } |
| } catch (NumberFormatException nfe) { |
| if (facet != null) { |
| String msg = "'" + facet.getName() + "' in '" + simpleType.getName() + "' cannot be parsed."; |
| throw new RuntimeException(new SAXParseException(msg, facet.getLocator(), nfe)); |
| } |
| } |
| } |
| |
| if ((facet = simpleType.getFacet(FACET_MAXINCLUSIVE)) != null && isNumberOrCharSequence(fieldType, false)) { |
| String maxIncValue = facet.getValue().value; |
| if (notAnnotatedAndNotDefaultBoundary(fieldVar, ANNOTATION_DECIMALMAX, maxIncValue)) { |
| a.put(fieldVar.annotate(ANNOTATION_DECIMALMAX).param(VALUE, maxIncValue), FacetType.maxInclusive); |
| convertToElement(particle, fieldVar); |
| } |
| } |
| |
| if ((facet = simpleType.getFacet(FACET_MININCLUSIVE)) != null && isNumberOrCharSequence(fieldType, false)) { |
| String minIncValue = facet.getValue().value; |
| if (notAnnotatedAndNotDefaultBoundary(fieldVar, ANNOTATION_DECIMALMIN, minIncValue)) { |
| a.put(fieldVar.annotate(ANNOTATION_DECIMALMIN).param(VALUE, minIncValue), FacetType.minInclusive); |
| convertToElement(particle, fieldVar); |
| } |
| } |
| |
| if ((facet = simpleType.getFacet(FACET_MAXEXCLUSIVE)) != null && isNumberOrCharSequence(fieldType, false)) { |
| String maxExcValue = facet.getValue().value; |
| if (!jsr303) { // ~ if jsr349 |
| if (notAnnotatedAndNotDefaultBoundary(fieldVar, ANNOTATION_DECIMALMAX, maxExcValue)) { |
| a.put(fieldVar.annotate(ANNOTATION_DECIMALMAX).param(VALUE, maxExcValue).param("inclusive", false), FacetType.maxExclusive); |
| convertToElement(particle, fieldVar); |
| } |
| } else { |
| Integer intMaxExc = Integer.parseInt(maxExcValue) - 1; |
| maxExcValue = intMaxExc.toString(); |
| if (notAnnotatedAndNotDefaultBoundary(fieldVar, ANNOTATION_DECIMALMAX, maxExcValue)) { |
| a.put(fieldVar.annotate(ANNOTATION_DECIMALMAX).param(VALUE, maxExcValue), FacetType.maxExclusive); |
| convertToElement(particle, fieldVar); |
| } |
| } |
| } |
| |
| if ((facet = simpleType.getFacet(FACET_MINEXCLUSIVE)) != null && isNumberOrCharSequence(fieldType, false)) { |
| String minExcValue = facet.getValue().value; |
| if (!jsr303) { // ~ if jsr349 |
| if (notAnnotatedAndNotDefaultBoundary(fieldVar, ANNOTATION_DECIMALMIN, minExcValue)) { |
| a.put(fieldVar.annotate(ANNOTATION_DECIMALMIN).param(VALUE, minExcValue).param("inclusive", false), FacetType.minExclusive); |
| convertToElement(particle, fieldVar); |
| } else { |
| Integer intMinExc = Integer.parseInt(minExcValue) + 1; |
| minExcValue = intMinExc.toString(); |
| if (notAnnotatedAndNotDefaultBoundary(fieldVar, ANNOTATION_DECIMALMIN, minExcValue)) { |
| a.put(fieldVar.annotate(ANNOTATION_DECIMALMIN).param(VALUE, minExcValue), FacetType.minExclusive); |
| convertToElement(particle, fieldVar); |
| } |
| } |
| } |
| } |
| |
| if ((facet = simpleType.getFacet(FACET_TOTALDIGITS)) != null && isNumberOrCharSequence(fieldType, true)) { |
| Integer digits = Integer.valueOf(facet.getValue().value); |
| if (digits != null) { |
| XSFacet fractionDigits = simpleType.getFacet(FACET_FRACTIONDIGITS); |
| int fractionDigs = 0; |
| if (fractionDigits != null) { |
| try { |
| fractionDigs = Integer.parseInt(fractionDigits.getValue().value); |
| } catch (NumberFormatException nfe) { |
| fractionDigs = 0; |
| } |
| } |
| if (notAnnotated(fieldVar, ANNOTATION_DIGITS)) { |
| a.put(fieldVar.annotate(ANNOTATION_DIGITS).param("integer", (digits - fractionDigs)).param("fraction", fractionDigs), FacetType.totalDigits); |
| } |
| } |
| } |
| |
| List<XSFacet> patternList = simpleType.getFacets(FACET_PATTERN); |
| if (patternList.size() > 1) { |
| if (notAnnotated(fieldVar, ANNOTATION_PATTERNLIST)) { |
| JAnnotationUse list = fieldVar.annotate(ANNOTATION_PATTERNLIST); |
| JAnnotationArrayMember listValue = list.paramArray(VALUE); |
| |
| for (XSFacet xsFacet : patternList) |
| // If corresponds to <xsd:restriction base="xsd:string">. |
| if ("String".equals(fieldType.name())) { |
| a.put(listValue.annotate(ANNOTATION_PATTERN).param("regexp", eliminateShorthands(xsFacet.getValue().value)), FacetType.pattern); |
| } else { |
| Logger.getLogger(this.getClass().getName()).log(Level.WARNING, PATTERN_ANNOTATION_NOT_APPLICABLE); |
| } |
| } |
| } else if ((facet = simpleType.getFacet(FACET_PATTERN)) != null) { |
| if ("String".equals(fieldType.name())) { // <xsd:restriction base="xsd:string"> |
| if (notAnnotated(fieldVar, ANNOTATION_PATTERN)) { |
| a.put(fieldVar.annotate(ANNOTATION_PATTERN).param("regexp", eliminateShorthands(facet.getValue().value)), FacetType.pattern); |
| } |
| } else { |
| Logger.getLogger(this.getClass().getName()).log(Level.WARNING, PATTERN_ANNOTATION_NOT_APPLICABLE); |
| } |
| } |
| } |
| |
| /** |
| * Implements the GoF Visitor pattern. |
| */ |
| private final class Visitor implements CPropertyVisitor2<Void, ClassOutline> { |
| @Override |
| public Void visit(CElementPropertyInfo t, ClassOutline p) { |
| processElement(t, p, detectCustomizations(t)); |
| return null; |
| } |
| |
| @Override |
| public Void visit(CAttributePropertyInfo t, ClassOutline p) { |
| processAttribute(t, p, detectCustomizations(t)); |
| return null; |
| } |
| |
| @Override |
| public Void visit(CValuePropertyInfo t, ClassOutline p) { |
| processValueFromExtendedBase(t, p, detectCustomizations(t)); |
| return null; |
| } |
| |
| @Override |
| public Void visit(CReferencePropertyInfo t, ClassOutline p) { |
| return null; |
| } |
| |
| /* |
| * Scans the input DOM node for custom bindings. |
| * If found, converts them into {@link FacetCustomization} objects |
| * and returns them. |
| */ |
| private List<FacetCustomization> detectCustomizations(CCustomizable ca) { |
| List<FacetCustomization> facetCustomizations = new ArrayList<>(); |
| List<CPluginCustomization> pluginCustomizations = ca.getCustomizations(); |
| if (pluginCustomizations != null) |
| for (CPluginCustomization c : pluginCustomizations) { |
| c.markAsAcknowledged(); |
| |
| String groups = c.element.getAttribute("groups"); |
| String message = c.element.getAttribute("message"); |
| String type = c.element.getAttribute("type"); |
| if ("".equals(type)) { |
| throw new RuntimeException("DOM attribute \"type\" is required in custom facet declarations."); |
| } |
| String value = c.element.getAttribute(VALUE); |
| facetCustomizations.add(new FacetCustomization(groups, message, type, value)); |
| } |
| |
| return facetCustomizations; |
| } |
| } |
| |
| /* ######### CUSTOMIZATIONS FUNCTIONALITY ######### */ |
| /* |
| * Applies the input customizations on the fieldVar: |
| * - Detects custom facets and applies them. |
| * - Matches standard facets with corresponding facet customizations. |
| */ |
| private void applyCustomizations(JFieldVar fieldVar, List<FacetCustomization> customizations, Map<JAnnotationUse, FacetType> a) { |
| for (FacetCustomization c : customizations) { |
| // Programming by exception is a bad programming practice, however |
| // it is the best available solution here. Catching IAE means that |
| // we have encountered a custom facet. |
| try { |
| switch (FacetType.valueOf(c.type)) { |
| case assertFalse: |
| customizeAnnotation(fieldVar.annotate(ANNOTATION_ASSERTFALSE), c); |
| continue; |
| case assertTrue: |
| customizeAnnotation(fieldVar.annotate(ANNOTATION_ASSERTTRUE), c); |
| continue; |
| case future: |
| customizeAnnotation(fieldVar.annotate(ANNOTATION_FUTURE), c); |
| continue; |
| case past: |
| customizeAnnotation(fieldVar.annotate(ANNOTATION_PAST), c); |
| continue; |
| } |
| } catch (IllegalArgumentException programmingByException) { |
| JAnnotationUse annotationUse = fieldVar.annotate(CODEMODEL.ref(c.type)); |
| if (!c.value.equals("")) annotationUse.param(VALUE, c.value); |
| customizeAnnotation(annotationUse, c); |
| continue; |
| } |
| customizeRegularAnnotations(a, c); |
| } |
| } |
| |
| /** |
| * Reads attributes "groups" and "message" from facet customization |
| * and if they don't contain empty values, applies them to annotation use |
| * as a parameter. |
| */ |
| private void customizeAnnotation(JAnnotationUse a, final FacetCustomization c) { |
| /** |
| * Transposes an array of Strings into a single String containing |
| * the Strings, separated by comma + space (i.e. ", ") and with ".class" |
| * extension. |
| */ |
| final class GroupsParser extends JExpressionImpl { |
| @Override |
| public void generate(JFormatter f) { |
| if (c.groups.length == 1) |
| f.p(c.groups[0] + ".class"); |
| else { |
| StringBuilder b = new StringBuilder(c.groups.length * 64); |
| b.append('{'); |
| int i = 0; |
| for (; i < c.groups.length - 1; i++) |
| b.append(c.groups[i]).append(".class, "); |
| b.append(c.groups[i]).append(".class}"); |
| f.p(b.toString()); |
| } |
| } |
| } |
| if (c.groups != null && c.groups.length != 0) a.param("groups", new GroupsParser()); |
| if (!c.message.equals("")) a.param("message", c.message); |
| } |
| |
| /* Note: |
| * Keep in mind that the only facet that has maxOccurs > 1 is xs:pattern. |
| * If multiple patterns are present on an element, they all must share the |
| * same validation group, because they all are included in the XML |
| * Validation process. |
| */ |
| private void customizeRegularAnnotations(Map<JAnnotationUse, FacetType> annotations, FacetCustomization c) { |
| for (Map.Entry<JAnnotationUse, FacetType> e : annotations.entrySet()) |
| if (FacetType.valueOf(c.type) == e.getValue()) |
| customizeAnnotation(e.getKey(), c); |
| } |
| |
| /** |
| * Value Object for Facet Customizations and Custom Facets. |
| */ |
| private static final class FacetCustomization { |
| private final String[]/*@NotEmpty*/groups; |
| private final String message; |
| private final String type; |
| private final String value; |
| |
| private FacetCustomization(String groups, String message, String type, String value) { |
| this.groups = groups.isEmpty() ? null : groups(groups); |
| this.message = message; |
| this.type = type; |
| this.value = value; |
| } |
| |
| private String[] groups(String groups) { |
| return ClassNameTrimmer.trim(groups).split(","); |
| } |
| |
| private static final class ClassNameTrimmer { |
| private static final Pattern ws = Pattern.compile("[\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u180E\\" + |
| "u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+"); |
| |
| private static String trim(String s) { |
| return ws.matcher(s).replaceAll("").replace('$', '.'); |
| } |
| } |
| } |
| |
| private static enum FacetType { |
| // XML Facets + Restrictions. |
| maxExclusive, |
| minExclusive, |
| maxInclusive, |
| minInclusive, |
| nillable, |
| pattern, |
| length, |
| maxLength, |
| minLength, |
| minOccurs, |
| maxOccurs, |
| totalDigits, |
| fractionDigits, |
| // Custom facets. |
| assertFalse, |
| assertTrue, |
| future, |
| past |
| } |
| |
| /* ######### AUXILIARY FUNCTIONALITY ######### */ |
| |
| /** |
| * Processes minOccurs and maxOccurs attributes of XS Element. |
| * If the values are not default, the property will be annotated with @Size. |
| */ |
| private void processMinMaxOccurs(XSParticle particle, JFieldVar fieldVar) { |
| final int maxOccurs = particle.getMaxOccurs().intValue(); |
| final int minOccurs = particle.getMinOccurs().intValue(); |
| if (maxOccurs > 1) { |
| if (notAnnotated(fieldVar, ANNOTATION_SIZE)) |
| fieldVar.annotate(ANNOTATION_SIZE).param("min", minOccurs).param("max", maxOccurs); |
| } else if (maxOccurs == -1) // maxOccurs -1 = "unbounded" |
| if (notAnnotated(fieldVar, ANNOTATION_SIZE)) |
| fieldVar.annotate(ANNOTATION_SIZE).param("min", minOccurs); |
| } |
| |
| |
| /** |
| * Annotates the field with @XmlElement. Without this functionality, the |
| * field wouldn't be recognized by Schemagen. @XmlElement annotation may |
| * also trigger change of the field's type from primitive to object. |
| */ |
| private void convertToElement(XSParticle particle, JFieldVar fieldVar) { |
| if (notAnnotated(fieldVar, ANNOTATION_XMLELEMENT)) { |
| fieldVar.annotate(XmlElement.class); |
| if (particle != null && particle.getMinOccurs().intValue() > 0) { |
| notNullAnnotate(fieldVar); |
| } |
| } |
| } |
| |
| private void validAnnotate(JFieldVar fieldVar) { |
| if (notAnnotated(fieldVar, ANNOTATION_VALID)) |
| fieldVar.annotate(ANNOTATION_VALID); |
| } |
| |
| private void notNullAnnotate(JFieldVar fieldVar) { |
| if (notAnnotated(fieldVar, ANNOTATION_NOTNULL)) |
| fieldVar.annotate(ANNOTATION_NOTNULL); |
| } |
| |
| private boolean notAnnotated(JFieldVar fieldVar, JClass annotationClass) { |
| for (JAnnotationUse annotationUse : fieldVar.annotations()) |
| if ((annotationUse.getAnnotationClass().toString().equals(annotationClass.toString()))) |
| return false; |
| return true; |
| } |
| |
| /** |
| * Checks if the desired annotation is not a boundary of a primitive type and |
| * Checks if the fieldVar is already annotated with the desired annotation. |
| * |
| * @return true if the fieldVar should be annotated, false if the fieldVar is |
| * already annotated or the value is a default boundary. |
| */ |
| private boolean notAnnotatedAndNotDefaultBoundary(JFieldVar fieldVar, JClass annotationClass, String boundaryValue) { |
| if (isDefaultBoundary(fieldVar.type().name(), annotationClass.fullName(), boundaryValue)) |
| return false; |
| |
| for (JAnnotationUse annotationUse : fieldVar.annotations()) { |
| if ((annotationUse.getAnnotationClass().toString().equals(annotationClass.toString()))) { |
| boolean previousAnnotationRemoved = false; |
| String annotationName = annotationUse.getAnnotationClass().fullName(); |
| if (annotationName.equals(ANNOTATION_DECIMALMIN.fullName())) |
| previousAnnotationRemoved = isMoreSpecificBoundary(fieldVar, boundaryValue, annotationUse, false); |
| else if (annotationName.equals(ANNOTATION_DECIMALMAX.fullName())) |
| previousAnnotationRemoved = isMoreSpecificBoundary(fieldVar, boundaryValue, annotationUse, true); |
| // If the previous field's annotation was removed, the fieldVar |
| // now is not annotated and should be given a new annotation, |
| // i.e. return true. |
| return previousAnnotationRemoved; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * @param xorComplement - flips the result of compareTo (set to true for decimalMaxAnn and false for decimalMinAnn). |
| */ |
| private boolean isMoreSpecificBoundary(JFieldVar fieldVar, String boundaryValue, JAnnotationUse annotationUse, |
| boolean xorComplement) { |
| String existingBoundaryValue = annotationUse.getAnnotationMembers().get(VALUE).toString(); |
| |
| if (existingBoundaryValue == null) return true; |
| else if (Long.valueOf(boundaryValue).compareTo(Long.valueOf(existingBoundaryValue)) > 0 ^ xorComplement) |
| return fieldVar.removeAnnotation(annotationUse); |
| return false; |
| } |
| |
| private boolean isSizeAnnotationApplicable(JType jType) { |
| if (jType.isArray()) return true; |
| |
| Class<?> clazz = loadClass(jType.fullName()); |
| return clazz != null && (CharSequence.class.isAssignableFrom(clazz) || Collection.class.isAssignableFrom(clazz)); |
| } |
| |
| /* ######### GENERAL UTILITIES ######### */ |
| private Class<?> loadClass(String className) { |
| Class<?> clazz = null; |
| if (securityEnabled) try { |
| clazz = AccessController.doPrivileged(ForNameActionExecutor.INSTANCE.with(className)); |
| } catch (PrivilegedActionException ignored) { |
| // - Can be only of type ClassNotFoundException, no check needed, see AccessController.doPrivileged(). |
| /* - ClassNotFoundException for us "means" that the fieldVar is of some unknown class - not an issue to be |
| solved by this plugin. */ |
| } |
| else try { |
| clazz = loadClassInternal(className); |
| } catch (ClassNotFoundException ignored) { |
| /* - ClassNotFoundException for us "means" that the fieldVar is of some unknown class - not an issue to be |
| solved by this plugin. */ |
| } |
| return clazz; |
| } |
| |
| private String eliminateShorthands(String regex) { |
| return regexMutator.mutate(regex); |
| } |
| |
| private final RegexMutator regexMutator = this.new RegexMutator(); |
| |
| /** |
| * Provides means for maintaining compatibility between XML Schema regex and Java Pattern regex. |
| * Replaces Java regex shorthands, which support only ASCII encoding, with full UNICODE equivalent. |
| * <p> |
| * Also replaces the two special XML regex character sets which aren't supported in Java, \i and \c. |
| * <p> |
| * Replaced shorthands and their negations: |
| * <blockquote><pre> |
| * \i - Matches any character that may be the first character of an XML name. |
| * \c - Matches any character that may occur after the first character in an XML name. |
| * \d - All digits. |
| * \w - Word character. |
| * \s - Whitespace character. |
| * \b, \B - Boundary definitions. |
| * \h - Horizontal whitespace character - Java does not support, changed in Java 8 though. |
| * \v - Vertical whitespace character - Java translates the shorthand to \cK only, meaning changed in Java 8 though. |
| * \X - Extended grapheme cluster. |
| * \R - Carriage return. |
| * </pre></blockquote> |
| * <p> |
| * Changes to this class should also be reflected in the opposite |
| * {@link org.eclipse.persistence.jaxb.compiler.SchemaGenerator.RegexMutator} class within SchemaGen. |
| * |
| * @see <a href="http://stackoverflow.com/questions/4304928/unicode-equivalents-for-w-and-b-in-java-regular-expressions">tchrist's work</a> |
| * @see <a href="http://www.regular-expressions.info/shorthand.html#xml">Special shorthands in XML Schema.</a> |
| */ |
| private final class RegexMutator { |
| private final Map<Pattern, String> shorthandReplacements = simpleRegex |
| ? new LinkedHashMap<Pattern, String>(8) {{ |
| put(Pattern.compile("\\\\i"), "[_:A-Za-z]"); |
| put(Pattern.compile("\\\\I"), "[^:A-Z_a-z]"); |
| put(Pattern.compile("\\\\c"), "[-.0-9:A-Z_a-z]"); |
| put(Pattern.compile("\\\\C"), "[^-.0-9:A-Z_a-z]"); |
| }} |
| : new LinkedHashMap<Pattern, String>(32) {{ |
| put(Pattern.compile("\\\\i"), "[:A-Z_a-z\\\\u00C0-\\\\u00D6\\\\u00D8-\\\\u00F6\\\\u00F8-\\\\u02FF\\\\u0370-\\\\u037D\\\\u037F-\\\\u1FFF\\\\u200C-\\\\u200D\\\\u2070-\\\\u218F\\\\u2C00-\\\\u2FEF\\\\u3001-\\\\uD7FF\\\\uF900-\\\\uFDCF\\\\uFDF0-\\\\uFFFD]"); |
| put(Pattern.compile("\\\\I"), "[^:A-Z_a-z\\\\u00C0-\\\\u00D6\\\\u00D8-\\\\u00F6\\\\u00F8-\\\\u02FF\\\\u0370-\\\\u037D\\\\u037F-\\\\u1FFF\\\\u200C-\\\\u200D\\\\u2070-\\\\u218F\\\\u2C00-\\\\u2FEF\\\\u3001-\\\\uD7FF\\\\uF900-\\\\uFDCF\\\\uFDF0-\\\\uFFFD]"); |
| put(Pattern.compile("\\\\c"), "[-.0-9:A-Z_a-z\\\\u00B7\\\\u00C0-\\\\u00D6\\\\u00D8-\\\\u00F6\\\\u00F8-\\\\u037D\\\\u037F-\\\\u1FFF\\\\u200C-\\\\u200D\\\\u203F\\\\u2040\\\\u2070-\\\\u218F\\\\u2C00-\\\\u2FEF\\\\u3001-\\\\uD7FF\\\\uF900-\\\\uFDCF\\\\uFDF0-\\\\uFFFD]"); |
| put(Pattern.compile("\\\\C"), "[^-.0-9:A-Z_a-z\\\\u00B7\\\\u00C0-\\\\u00D6\\\\u00D8-\\\\u00F6\\\\u00F8-\\\\u037D\\\\u037F-\\\\u1FFF\\\\u200C-\\\\u200D\\\\u203F\\\\u2040\\\\u2070-\\\\u218F\\\\u2C00-\\\\u2FEF\\\\u3001-\\\\uD7FF\\\\uF900-\\\\uFDCF\\\\uFDF0-\\\\uFFFD]"); |
| put(Pattern.compile("\\\\s"), "[\\\\u0009-\\\\u000D\\\\u0020\\\\u0085\\\\u00A0\\\\u1680\\\\u180E\\\\u2000-\\\\u200A\\\\u2028\\\\u2029\\\\u202F\\\\u205F\\\\u3000]"); |
| put(Pattern.compile("\\\\S"), "[^\\\\u0009-\\\\u000D\\\\u0020\\\\u0085\\\\u00A0\\\\u1680\\\\u180E\\\\u2000-\\\\u200A\\\\u2028\\\\u2029\\\\u202F\\\\u205F\\\\u3000]"); |
| put(Pattern.compile("\\\\v"), "[\\\\u000A-\\\\u000D\\\\u0085\\\\u2028\\\\u2029]"); |
| put(Pattern.compile("\\\\V"), "[^\\\\u000A-\\\\u000D\\\\u0085\\\\u2028\\\\u2029]"); |
| put(Pattern.compile("\\\\h"), "[\\\\u0009\\\\u0020\\\\u00A0\\\\u1680\\\\u180E\\\\u2000-\\\\u200A\\\\u202F\\\\u205F\\\\u3000]"); |
| put(Pattern.compile("\\\\H"), "[^\\\\u0009\\\\u0020\\\\u00A0\\\\u1680\\\\u180E\\\\u2000\\\\u2001-\\\\u200A\\\\u202F\\\\u205F\\\\u3000]"); |
| put(Pattern.compile("\\\\w"), "[\\\\pL\\\\pM\\\\p{Nd}\\\\p{Nl}\\\\p{Pc}[\\\\p{InEnclosedAlphanumerics}&&\\\\p{So}]]"); |
| put(Pattern.compile("\\\\W"), "[^\\\\pL\\\\pM\\\\p{Nd}\\\\p{Nl}\\\\p{Pc}[\\\\p{InEnclosedAlphanumerics}&&\\\\p{So}]]"); |
| put(Pattern.compile("\\\\b"), "(?:(?<=[\\\\pL\\\\pM\\\\p{Nd}\\\\p{Nl}\\\\p{Pc}[\\\\p{InEnclosedAlphanumerics}&&\\\\p{So}]])(?![\\\\pL\\\\pM\\\\p{Nd}\\\\p{Nl}\\\\p{Pc}[\\\\p{InEnclosedAlphanumerics}&&\\\\p{So}]])|(?<![\\\\pL\\\\pM\\\\p{Nd}\\\\p{Nl}\\\\p{Pc}[\\\\p{InEnclosedAlphanumerics}&&\\\\p{So}]])(?=[\\\\pL\\\\pM\\\\p{Nd}\\\\p{Nl}\\\\p{Pc}[\\\\p{InEnclosedAlphanumerics}&&\\\\p{So}]]))"); |
| put(Pattern.compile("\\\\B"), "(?:(?<=[\\\\pL\\\\pM\\\\p{Nd}\\\\p{Nl}\\\\p{Pc}[\\\\p{InEnclosedAlphanumerics}&&\\\\p{So}]])(?=[\\\\pL\\\\pM\\\\p{Nd}\\\\p{Nl}\\\\p{Pc}[\\\\p{InEnclosedAlphanumerics}&&\\\\p{So}]])|(?<![\\\\pL\\\\pM\\\\p{Nd}\\\\p{Nl}\\\\p{Pc}[\\\\p{InEnclosedAlphanumerics}&&\\\\p{So}]])(?![\\\\pL\\\\pM\\\\p{Nd}\\\\p{Nl}\\\\p{Pc}[\\\\p{InEnclosedAlphanumerics}&&\\\\p{So}]]))"); |
| put(Pattern.compile("\\\\d"), "\\\\p{Nd}"); |
| put(Pattern.compile("\\\\D"), "\\\\P{Nd}"); |
| put(Pattern.compile("\\\\R"), "(?:(?>\\\\u000D\\\\u000A)|[\\\\u000A\\\\u000B\\\\u000C\\\\u000D\\\\u0085\\\\u2028\\\\u2029])"); |
| put(Pattern.compile("\\\\X"), "(?:(?:\\\\u000D\\\\u000A)|(?:[\\\\u0E40\\\\u0E41\\\\u0E42\\\\u0E43\\\\u0E44\\\\u0EC0\\\\u0EC1\\\\u0EC2\\\\u0EC3\\\\u0EC4\\\\uAAB5\\\\uAAB6\\\\uAAB9\\\\uAABB\\\\uAABC]*(?:[\\\\u1100-\\\\u115F\\\\uA960-\\\\uA97C]+|([\\\\u1100-\\\\u115F\\\\uA960-\\\\uA97C]*((?:[[\\\\u1160-\\\\u11A2\\\\uD7B0-\\\\uD7C6][\\\\uAC00\\\\uAC1C\\\\uAC38]][\\\\u1160-\\\\u11A2\\\\uD7B0-\\\\uD7C6]*|[\\\\uAC01\\\\uAC02\\\\uAC03\\\\uAC04])[\\\\u11A8-\\\\u11F9\\\\uD7CB-\\\\uD7FB]*))|[\\\\u11A8-\\\\u11F9\\\\uD7CB-\\\\uD7FB]+|[^[\\\\p{Zl}\\\\p{Zp}\\\\p{Cc}\\\\p{Cf}&&[^\\\\u000D\\\\u000A\\\\u200C\\\\u200D]]\\\\u000D\\\\u000A])[[\\\\p{Mn}\\\\p{Me}\\\\u200C\\\\u200D\\\\u0488\\\\u0489\\\\u20DD\\\\u20DE\\\\u20DF\\\\u20E0\\\\u20E2\\\\u20E3\\\\u20E4\\\\uA670\\\\uA671\\\\uA672\\\\uFF9E\\\\uFF9F][\\\\p{Mc}\\\\u0E30\\\\u0E32\\\\u0E33\\\\u0E45\\\\u0EB0\\\\u0EB2\\\\u0EB3]]*)|(?s:.))"); |
| }}; |
| |
| /** |
| * @param xmlRegex XML regex |
| * @return Java regex |
| */ |
| private String mutate(String xmlRegex) { |
| for (Map.Entry<Pattern, String> entry : shorthandReplacements.entrySet()) { |
| Matcher m = entry.getKey().matcher(xmlRegex); |
| xmlRegex = m.replaceAll(entry.getValue()); |
| } |
| return xmlRegex; |
| } |
| } |
| |
| private boolean isDefaultBoundary(String fieldVarType, String annotationClass, String boundaryValue) { |
| if (unboundedDigitsClasses.contains(fieldVarType)) { |
| return false; |
| } |
| return ANNOTATION_DECIMALMIN.fullName().equals(annotationClass) |
| && nonFloatingDigitsClassesBoundaries.get(fieldVarType).min.equals(boundaryValue) |
| || (ANNOTATION_DECIMALMAX.fullName().equals(annotationClass) |
| && nonFloatingDigitsClassesBoundaries.get(fieldVarType).max.equals(boundaryValue)); |
| } |
| |
| private boolean isNumberOrCharSequence(JType jType, boolean supportsFloating) { |
| String shortClazzName = jType.name(); |
| if (nonFloatingDigitsClasses.contains(shortClazzName)) |
| return true; |
| if (supportsFloating && floatingDigitsClasses.contains(shortClazzName)) |
| return true; |
| |
| Class<?> clazz = loadClass(jType.fullName()); |
| return clazz != null && CharSequence.class.isAssignableFrom(clazz); |
| } |
| |
| private static final Set<String> nonFloatingDigitsClasses; |
| |
| static { |
| Set<String> set = new HashSet<>(); |
| set.add("byte"); |
| set.add("Byte"); |
| set.add("short"); |
| set.add("Short"); |
| set.add("int"); |
| set.add("Integer"); |
| set.add("long"); |
| set.add("Long"); |
| set.add("BigDecimal"); |
| set.add("BigInteger"); |
| nonFloatingDigitsClasses = Collections.unmodifiableSet(set); |
| } |
| |
| private static final Set<String> unboundedDigitsClasses; |
| |
| static { |
| Set<String> set = new HashSet<>(); |
| set.add("BigInteger"); |
| set.add("BigDecimal"); |
| unboundedDigitsClasses = Collections.unmodifiableSet(new HashSet<>(set)); |
| } |
| |
| private static final Set<String> floatingDigitsClasses; |
| |
| static { |
| Set<String> set = new HashSet<>(); |
| set.add("float"); |
| set.add("Float"); |
| set.add("double"); |
| set.add("Double"); |
| floatingDigitsClasses = Collections.unmodifiableSet(new HashSet<>(set)); |
| } |
| |
| private static final Map<String, MinMaxTuple> nonFloatingDigitsClassesBoundaries; |
| |
| static { |
| HashMap<String, MinMaxTuple> map = new HashMap<>(); |
| map.put("byte", new MinMaxTuple<>(Byte.MIN_VALUE, Byte.MAX_VALUE)); |
| map.put("Byte", new MinMaxTuple<>(Byte.MIN_VALUE, Byte.MAX_VALUE)); |
| map.put("short", new MinMaxTuple<>(Short.MIN_VALUE, Short.MAX_VALUE)); |
| map.put("Short", new MinMaxTuple<>(Short.MIN_VALUE, Short.MAX_VALUE)); |
| map.put("int", new MinMaxTuple<>(Integer.MIN_VALUE, Integer.MAX_VALUE)); |
| map.put("Integer", new MinMaxTuple<>(Integer.MIN_VALUE, Integer.MAX_VALUE)); |
| map.put("long", new MinMaxTuple<>(Long.MIN_VALUE, Long.MAX_VALUE)); |
| map.put("Long", new MinMaxTuple<>(Long.MIN_VALUE, Long.MAX_VALUE)); |
| nonFloatingDigitsClassesBoundaries = Collections.unmodifiableMap(map); |
| } |
| |
| private static final class MinMaxTuple<T extends Number> { |
| private final String min; |
| private final String max; |
| |
| private MinMaxTuple(T min, T max) { |
| this.min = String.valueOf(min); |
| this.max = String.valueOf(max); |
| } |
| } |
| |
| private static final class ForNameActionExecutor { |
| |
| private interface PrivilegedExceptionActionWith<T> extends PrivilegedExceptionAction<T> { |
| PrivilegedExceptionAction<T> with(String className); |
| } |
| |
| private static final PrivilegedExceptionActionWith<Class<?>> INSTANCE = new PrivilegedExceptionActionWith<Class<?>>() { |
| private String className; |
| |
| @Override |
| public Class<?> run() throws ClassNotFoundException { |
| return loadClassInternal(className); |
| } |
| |
| @Override |
| public PrivilegedExceptionActionWith<Class<?>> with(String className) { |
| this.className = className; |
| return this; |
| } |
| }; |
| } |
| |
| private static Class<?> loadClassInternal(String className) throws ClassNotFoundException { |
| return Class.forName(className); |
| } |
| |
| } |