Java tutorial
/** * Apache License * Version 2.0, January 2004 * http://www.apache.org/licenses/ * * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION * * 1. Definitions. * * "License" shall mean the terms and conditions for use, reproduction, * and distribution as defined by Sections 1 through 9 of this document. * * "Licensor" shall mean the copyright owner or entity authorized by * the copyright owner that is granting the License. * * "Legal Entity" shall mean the union of the acting entity and all * other entities that control, are controlled by, or are under common * control with that entity. For the purposes of this definition, * "control" means (i) the power, direct or indirect, to cause the * direction or management of such entity, whether by contract or * otherwise, or (ii) ownership of fifty percent (50%) or more of the * outstanding shares, or (iii) beneficial ownership of such entity. * * "You" (or "Your") shall mean an individual or Legal Entity * exercising permissions granted by this License. * * "Source" form shall mean the preferred form for making modifications, * including but not limited to software source code, documentation * source, and configuration files. * * "Object" form shall mean any form resulting from mechanical * transformation or translation of a Source form, including but * not limited to compiled object code, generated documentation, * and conversions to other media types. * * "Work" shall mean the work of authorship, whether in Source or * Object form, made available under the License, as indicated by a * copyright notice that is included in or attached to the work * (an example is provided in the Appendix below). * * "Derivative Works" shall mean any work, whether in Source or Object * form, that is based on (or derived from) the Work and for which the * editorial revisions, annotations, elaborations, or other modifications * represent, as a whole, an original work of authorship. For the purposes * of this License, Derivative Works shall not include works that remain * separable from, or merely link (or bind by name) to the interfaces of, * the Work and Derivative Works thereof. * * "Contribution" shall mean any work of authorship, including * the original version of the Work and any modifications or additions * to that Work or Derivative Works thereof, that is intentionally * submitted to Licensor for inclusion in the Work by the copyright owner * or by an individual or Legal Entity authorized to submit on behalf of * the copyright owner. For the purposes of this definition, "submitted" * means any form of electronic, verbal, or written communication sent * to the Licensor or its representatives, including but not limited to * communication on electronic mailing lists, source code control systems, * and issue tracking systems that are managed by, or on behalf of, the * Licensor for the purpose of discussing and improving the Work, but * excluding communication that is conspicuously marked or otherwise * designated in writing by the copyright owner as "Not a Contribution." * * "Contributor" shall mean Licensor and any individual or Legal Entity * on behalf of whom a Contribution has been received by Licensor and * subsequently incorporated within the Work. * * 2. Grant of Copyright License. Subject to the terms and conditions of * this License, each Contributor hereby grants to You a perpetual, * worldwide, non-exclusive, no-charge, royalty-free, irrevocable * copyright license to reproduce, prepare Derivative Works of, * publicly display, publicly perform, sublicense, and distribute the * Work and such Derivative Works in Source or Object form. * * 3. Grant of Patent License. Subject to the terms and conditions of * this License, each Contributor hereby grants to You a perpetual, * worldwide, non-exclusive, no-charge, royalty-free, irrevocable * (except as stated in this section) patent license to make, have made, * use, offer to sell, sell, import, and otherwise transfer the Work, * where such license applies only to those patent claims licensable * by such Contributor that are necessarily infringed by their * Contribution(s) alone or by combination of their Contribution(s) * with the Work to which such Contribution(s) was submitted. If You * institute patent litigation against any entity (including a * cross-claim or counterclaim in a lawsuit) alleging that the Work * or a Contribution incorporated within the Work constitutes direct * or contributory patent infringement, then any patent licenses * granted to You under this License for that Work shall terminate * as of the date such litigation is filed. * * 4. Redistribution. You may reproduce and distribute copies of the * Work or Derivative Works thereof in any medium, with or without * modifications, and in Source or Object form, provided that You * meet the following conditions: * * (a) You must give any other recipients of the Work or * Derivative Works a copy of this License; and * * (b) You must cause any modified files to carry prominent notices * stating that You changed the files; and * * (c) You must retain, in the Source form of any Derivative Works * that You distribute, all copyright, patent, trademark, and * attribution notices from the Source form of the Work, * excluding those notices that do not pertain to any part of * the Derivative Works; and * * (d) If the Work includes a "NOTICE" text file as part of its * distribution, then any Derivative Works that You distribute must * include a readable copy of the attribution notices contained * within such NOTICE file, excluding those notices that do not * pertain to any part of the Derivative Works, in at least one * of the following places: within a NOTICE text file distributed * as part of the Derivative Works; within the Source form or * documentation, if provided along with the Derivative Works; or, * within a display generated by the Derivative Works, if and * wherever such third-party notices normally appear. The contents * of the NOTICE file are for informational purposes only and * do not modify the License. You may add Your own attribution * notices within Derivative Works that You distribute, alongside * or as an addendum to the NOTICE text from the Work, provided * that such additional attribution notices cannot be construed * as modifying the License. * * You may add Your own copyright statement to Your modifications and * may provide additional or different license terms and conditions * for use, reproduction, or distribution of Your modifications, or * for any such Derivative Works as a whole, provided Your use, * reproduction, and distribution of the Work otherwise complies with * the conditions stated in this License. * * 5. Submission of Contributions. Unless You explicitly state otherwise, * any Contribution intentionally submitted for inclusion in the Work * by You to the Licensor shall be under the terms and conditions of * this License, without any additional terms or conditions. * Notwithstanding the above, nothing herein shall supersede or modify * the terms of any separate license agreement you may have executed * with Licensor regarding such Contributions. * * 6. Trademarks. This License does not grant permission to use the trade * names, trademarks, service marks, or product names of the Licensor, * except as required for reasonable and customary use in describing the * origin of the Work and reproducing the content of the NOTICE file. * * 7. Disclaimer of Warranty. Unless required by applicable law or * agreed to in writing, Licensor provides the Work (and each * Contributor provides its Contributions) on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied, including, without limitation, any warranties or conditions * of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A * PARTICULAR PURPOSE. You are solely responsible for determining the * appropriateness of using or redistributing the Work and assume any * risks associated with Your exercise of permissions under this License. * * 8. Limitation of Liability. In no event and under no legal theory, * whether in tort (including negligence), contract, or otherwise, * unless required by applicable law (such as deliberate and grossly * negligent acts) or agreed to in writing, shall any Contributor be * liable to You for damages, including any direct, indirect, special, * incidental, or consequential damages of any character arising as a * result of this License or out of the use or inability to use the * Work (including but not limited to damages for loss of goodwill, * work stoppage, computer failure or malfunction, or any and all * other commercial damages or losses), even if such Contributor * has been advised of the possibility of such damages. * * 9. Accepting Warranty or Additional Liability. While redistributing * the Work or Derivative Works thereof, You may choose to offer, * and charge a fee for, acceptance of support, warranty, indemnity, * or other liability obligations and/or rights consistent with this * License. However, in accepting such obligations, You may act only * on Your own behalf and on Your sole responsibility, not on behalf * of any other Contributor, and only if You agree to indemnify, * defend, and hold each Contributor harmless for any liability * incurred by, or claims asserted against, such Contributor by reason * of your accepting any such warranty or additional liability. * * END OF TERMS AND CONDITIONS * * APPENDIX: How to apply the Apache License to your work. * * To apply the Apache License to your work, attach the following * boilerplate notice, with the fields enclosed by brackets "{}" * replaced with your own identifying information. (Don't include * the brackets!) The text should be enclosed in the appropriate * comment syntax for the file format. We also recommend that a * file or class name and description of purpose be included on the * same "printed page" as the copyright notice for easier * identification within third-party archives. * * Copyright {yyyy} {name of copyright owner} * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.thesoftwarefactory.vertx.web.model; import java.beans.PropertyDescriptor; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.InvalidPropertyException; public class Form<T> { private static final DateTimeFormatter INSTANT_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy"); private static String format(Instant instant) { LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.of("+2")); return INSTANT_FORMATTER.format(ldt); } public class Field { private Set<String> errors = null; private String name = null; private Collection<PossibleValue> possibleValues = null; private boolean readonly = false; private String type = null; private String value = null; private boolean multiple = false; public Field(String name, String type) { Objects.requireNonNull(name); Objects.requireNonNull(type); this.name = name; this.type = type; } public Field addError(String error) { if (errors == null) { errors = new HashSet<>(); } errors.add(error); return this; } public Field addPossibleValue(PossibleValue value) { if (possibleValues == null) { possibleValues = new ArrayList<PossibleValue>(); } possibleValues.add(value); return this; } /** * return a copy of the field * */ @Override public Field clone() { Field result = new Field(name, type); // copy errors for (String error : errors()) { result.addError(error); } // copy the value result.value = value; return result; } public Collection<String> errors() { if (errors == null) { return Collections.emptyList(); } return errors; } public boolean hasErrors() { return errors != null && errors.size() > 0; } public String name() { return name; } public Collection<PossibleValue> possibleValues() { if (possibleValues == null) { return Collections.emptyList(); } return possibleValues; } public boolean readonly() { return Form.this.isReadonly() || readonly; } public void readonly(boolean readOnly) { this.readonly = readOnly; } public String type() { return type; } public Field type(String type) { this.type = type; return this; } public String value() { return value; } public Field value(String value) { this.value = value; return this; } public boolean multiple() { return multiple; } public Field multiple(boolean multiple) { this.multiple = multiple; return this; } @Override public String toString() { return "Field [errors=" + errors + ", name=" + name + ", possibleValues=" + possibleValues + ", readonly=" + readonly + ", type=" + type + ", value=" + value + "]"; } } public static class PossibleValue { private String label = null; private String value = null; public PossibleValue(String value, String label) { Objects.requireNonNull(value); Objects.requireNonNull(label); this.value = value; this.label = label; } public String getLabel() { return label; } public String getValue() { return value; } } final static Logger logger = Logger.getLogger(Form.class.getName()); private static Validator validator; private static ValidatorFactory validatorFactory; public final static String fieldTypefromClass(Class<?> cls) { Objects.requireNonNull(cls); // by default return TEXT String result = "text"; if (cls.equals(Boolean.class) || cls.equals(boolean.class)) { result = "checkbox"; } else if (Number.class.isAssignableFrom(cls)) { result = "number"; } else if (cls.isEnum()) { result = "select"; } return result; } /** * Build a new Form instance from the specified object * * @param object * @param fieldPrefix: if not null, the prefix is prepended to each field name * @return */ public final static <T> Form<T> fromBean(T object) { return fromBean(object, null); } /** * Build a new Form instance from the specified object * * @param object * @param fieldPrefix: if not null, the prefix is prepended to each field name * @return */ public final static <T> Form<T> fromBean(T object, String fieldPrefix) { Objects.requireNonNull(object); @SuppressWarnings("unchecked") Form<T> result = (Form<T>) Form.fromClass(object.getClass(), fieldPrefix); return result.bindObject(object); } /** * Build a new Form instance from the specified class * * @param cls * @param fieldPrefix: if not null, the prefix is prepended to each field name * @return */ public final static <T> Form<T> fromClass(Class<T> cls) { return fromClass(cls, null); } /** * Build a new Form instance from the specified class * * @param cls * @param fieldPrefix: if not null, the prefix is prepended to each field name * @return */ public final static <T> Form<T> fromClass(Class<T> cls, String fieldPrefix) { Objects.requireNonNull(cls); Form<T> result = new Form<T>(); if (fieldPrefix != null) { result.fieldPrefix = fieldPrefix; } // discover properties for (PropertyDescriptor property : BeanUtils.getPropertyDescriptors(cls)) { if (property.getReadMethod() != null && property.getWriteMethod() != null) { // the property has a getter and setter String fieldName = fieldPrefix != null ? fieldPrefix + property.getName() : property.getName(); result.addField(result.new Field(fieldName, Form.fieldTypefromClass(property.getPropertyType()))); } } return result; } /** * validate the specified bean, optionally validating only the specified properties * * @return */ public final static Set<ConstraintViolation<?>> validate(Validator validator, Object object, String... properties) { Set<ConstraintViolation<?>> result = null; if (validator != null && object != null) { if (properties.length > 0) { // validate only the specified properties result = new HashSet<>(); for (String property : properties) { try { Set<ConstraintViolation<Object>> constraintViolations = validator.validateProperty(object, property); result.addAll(constraintViolations); } catch (Throwable t) { logger.log(Level.FINE, "Could not validate property ", t); } } } else { // validate the object try { Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object); result = new HashSet<>(); result.addAll(constraintViolations); } catch (Throwable t) { logger.log(Level.FINE, "Could not validate Object ", t); } } } return result != null ? result : Collections.emptySet(); } private final static Validator validator() { if (validator == null) { validator = validatorFactory().getValidator(); } return validator; } private final static ValidatorFactory validatorFactory() { if (validatorFactory == null) { validatorFactory = Validation.buildDefaultValidatorFactory(); } return validatorFactory; } private Set<String> errors = null; private String fieldPrefix = null; private Map<String, Field> fields = null; private boolean isValidated = false; private T object = null; private boolean readonly = false; private String referer = null; private String url = null; public Form() { } public Form<T> addError(String error) { if (errors == null) { errors = new HashSet<>(); } errors.add(error); return this; } public Form<T> addField(Field field) { Objects.requireNonNull(field); Objects.requireNonNull(field.name()); if (fields == null) { fields = new LinkedHashMap<>(); } fields.put(field.name(), field); return this; } public Field addField(String name, String type) { Field field = new Field(name, type); addField(field); return field; } /** * bind all or only the specified properties of the object to this Form. * Validation is automatically performed after the binding, validating all or only the specified properties. * The result of the validation is available by calling form.hasErrors() and/or field.hasErrors() * * @param object * @param properties * @return */ public Form<T> bindObject(T object, String... properties) { if (object != null) { this.object = object; BeanWrapper beanWrapper = new BeanWrapperImpl(object); for (Field field : fields()) { // if applicable, remove the fieldPrefix to get the bean field name String fieldName = fieldPrefix != null ? field.name().substring(fieldPrefix.length()) : field.name(); PropertyDescriptor property = beanWrapper.getPropertyDescriptor(fieldName); if (property != null && property.getReadMethod() != null) { Object propertyValue = beanWrapper.getPropertyValue(fieldName); String strPropValue = propertyValue != null ? (propertyValue instanceof java.time.Instant ? format((java.time.Instant) propertyValue) : propertyValue.toString()) : null; field.value(strPropValue); } } } return this; } /** * return a copy of the form * */ @Override public Form<T> clone() { // make a copy of the form Form<T> result = new Form<T>(); for (Field field : fields()) { result.addField(field.clone()); } return result; } /** * Creates a Bean of the given class type from the form values * @param cls class of the result bean * @param <R> type of the bean * @return */ public <R> R deriveBean(Class<R> cls) { R result = null; try { result = cls.newInstance(); } catch (InstantiationException | IllegalAccessException e) { logger.log(Level.WARNING, "Could not instantiate derived bean", e); return null; } for (Field field : fields()) { // if applicable, remove the fieldPrefix to get the bean field name String fieldName = fieldPrefix != null ? field.name().substring(fieldPrefix.length()) : field.name(); BeanWrapper beanWrapper = new BeanWrapperImpl(result); try { PropertyDescriptor property = beanWrapper.getPropertyDescriptor(fieldName); if (property != null && property.getReadMethod() != null) { beanWrapper.setPropertyValue(fieldName, field.value()); } } catch (InvalidPropertyException ex) { logger.log(Level.WARNING, "Form property not in bean"); } } return result; } public Collection<String> errors() { if (errors == null) { return Collections.emptyList(); } return errors; } public Field field(String fieldName) { if (fields != null) { return fields.get(fieldName); } return null; } public Iterable<Field> fields() { if (fields != null) { return fields.values(); } return Collections.emptyList(); } /** * Creates a Bean with the given class from the from values * @param object Source object we're taking the values from * @return this object */ public Form<T> fillFromBean(Object object) { BeanWrapper beanWrapper = new BeanWrapperImpl(object); for (Field field : fields()) { // if applicable, remove the fieldPrefix to get the bean field name String fieldName = fieldPrefix != null ? field.name().substring(fieldPrefix.length()) : field.name(); try { PropertyDescriptor property = beanWrapper.getPropertyDescriptor(fieldName); if (property != null && property.getReadMethod() != null) { Object propertyValue = beanWrapper.getPropertyValue(fieldName); String strPropValue = propertyValue != null ? propertyValue.toString() : null; // If the field hasn't got a value already, clear the potential errors as they will be meaningless // and the set the field value if (field.value() == null) { field.value(strPropValue); field.errors().clear(); } } } catch (InvalidPropertyException e) { logger.log(Level.WARNING, "Could not find property " + fieldName + " for bean " + object.getClass().getName(), e); continue; } } return this; } /** * remove all fields whose name is not contained in the specified properties * * @param properties * @return */ public Form<T> filter(String... properties) { Iterator<Entry<String, Field>> entries = fields.entrySet().iterator(); while (entries.hasNext()) { Entry<String, Field> entry = entries.next(); boolean foundKey = false; for (String property : properties) { foundKey = property != null && property.equalsIgnoreCase(entry.getKey()); if (foundKey) { break; } } // if we didn't find the property, remove it from the set if (!foundKey) { entries.remove(); } } return this; } public T getObject() { return object; } public boolean hasErrors() { // make sure the bean is validated if (!isValidated) { validate(); } if (errors != null && errors.size() > 0) { return true; } if (fields != null) { for (Field field : fields.values()) { if (field.hasErrors()) { return true; } } } return false; } public boolean isReadonly() { return readonly; } /** * Overwrites the values of the bean in parameter with the form values * @param source * @return */ public <R> void mergeInto(R source) { for (Field field : fields()) { // if applicable, remove the fieldPrefix to get the bean field name String fieldName = fieldPrefix != null ? field.name().substring(fieldPrefix.length()) : field.name(); BeanWrapper beanWrapper = new BeanWrapperImpl(source); try { PropertyDescriptor property = beanWrapper.getPropertyDescriptor(fieldName); if (property != null && property.getReadMethod() != null) { beanWrapper.setPropertyValue(fieldName, field.value()); } } catch (InvalidPropertyException ex) { logger.log(Level.WARNING, "Form property not in source"); } } } public String referer() { return referer; } // should not be accessible from the outside to cleanly encapsulate and prevent mistakes public Form<T> referer(String referer) { this.referer = referer; return this; } public Form<T> setReadonly(boolean readonly) { this.readonly = readonly; return this; } public String url() { return url; } public Form<T> url(String url) { this.url = url; return this; } public Form<T> validate(String... propertyNames) { Validator validator = validator(); Set<ConstraintViolation<?>> constraintViolations = validate(validator, object, propertyNames); for (ConstraintViolation<?> constraintViolation : constraintViolations) { if (constraintViolation.getPropertyPath().toString().isEmpty()) { // Form error addError(constraintViolation.getMessage()); } else { // Field error String fieldName = fieldPrefix != null ? fieldPrefix + constraintViolation.getPropertyPath().toString() : constraintViolation.getPropertyPath().toString(); Field field = field(fieldName); if (field != null) { field.addError(constraintViolation.getMessage()); } } } isValidated = true; return this; } @Override public String toString() { return "Form [errors=" + errors + ", fieldPrefix=" + fieldPrefix + ", fields=" + fields + ", isValidated=" + isValidated + ", object=" + object + ", readonly=" + readonly + ", referer=" + referer + ", url=" + url + "]"; } }