001/* 002 * Configurate 003 * Copyright (C) zml and Configurate contributors 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.spongepowered.configurate.objectmapping.meta; 018 019import org.checkerframework.checker.nullness.qual.Nullable; 020import org.spongepowered.configurate.serialize.SerializationException; 021 022import java.lang.annotation.Annotation; 023import java.lang.reflect.Type; 024import java.text.MessageFormat; 025import java.util.Locale; 026import java.util.ResourceBundle; 027import java.util.regex.Matcher; 028import java.util.regex.Pattern; 029 030/** 031 * Perform a validation on data upon load. 032 * 033 * @param <V> value type 034 * @since 4.0.0 035 */ 036@FunctionalInterface 037public interface Constraint<V> { 038 039 /** 040 * Check if the provided deserialized value matches an expected condition. 041 * 042 * @param value value to test 043 * @throws SerializationException if the value falls outside its constraint. 044 * @since 4.0.0 045 */ 046 void validate(@Nullable V value) throws SerializationException; 047 048 /** 049 * Provider for a specific constraint given a field type. 050 * 051 * @param <A> annotation type 052 * @param <V> data type 053 * @since 4.0.0 054 */ 055 @FunctionalInterface 056 interface Factory<A extends Annotation, V> { 057 058 /** 059 * Create a new specialized constraint. 060 * 061 * @param data annotation with metadata 062 * @param type annotated type. is a subtype of {@code V} 063 * @return new constraint 064 * @since 4.0.0 065 */ 066 Constraint<V> make(A data, Type type); 067 } 068 069 /** 070 * Require a value to be present for fields marked with the 071 * annotation {@code T}. 072 * 073 * @param <T> marker annotation type 074 * @return new constraint factory 075 * @since 4.0.0 076 */ 077 static <T extends Annotation> Constraint.Factory<T, Object> required() { 078 return (data, type) -> value -> { 079 if (value == null) { 080 throw new SerializationException("A value is required for this field"); 081 } 082 }; 083 } 084 085 /** 086 * Require values to match the {@link Matches#value() pattern} provided. 087 * 088 * <p>Upon failure, an error message will be taken from the annotation.</p> 089 * 090 * @return factory providing matching pattern test 091 * @since 4.0.0 092 */ 093 static Constraint.Factory<Matches, String> pattern() { 094 return (data, type) -> { 095 final Pattern test = Pattern.compile(data.value(), data.flags()); 096 final MessageFormat format = new MessageFormat(data.failureMessage(), Locale.getDefault()); 097 return value -> { 098 if (value != null) { 099 final Matcher match = test.matcher(value); 100 if (!match.matches()) { 101 throw new SerializationException(format.format(new Object[]{value, data.value()})); 102 } 103 } 104 }; 105 }; 106 } 107 108 /** 109 * Require values to match the {@link Matches#value() pattern} provided. 110 * 111 * <p>Upon failure, an error message will be taken from {@code bundle} with 112 * a key defined in the annotation.</p> 113 * 114 * @param bundle source for localized messages 115 * @return factory providing matching pattern test 116 * @since 4.0.0 117 */ 118 static Constraint.Factory<Matches, String> localizedPattern(final ResourceBundle bundle) { 119 return (data, type) -> { 120 final Pattern test = Pattern.compile(data.value(), data.flags()); 121 final MessageFormat format = new MessageFormat(Localization.key(bundle, data.failureMessage()), bundle.getLocale()); 122 return value -> { 123 if (value != null) { 124 final Matcher match = test.matcher(value); 125 if (!match.matches()) { 126 throw new SerializationException(format.format(new Object[]{value, data.value()})); 127 } 128 } 129 }; 130 }; 131 } 132 133}