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 */ 036public interface Constraint<V> { 037 038 /** 039 * Check if the provided deserialized value matches an expected condition. 040 * 041 * @param value value to test 042 * @throws SerializationException if the value falls outside its constraint. 043 * @since 4.0.0 044 */ 045 void validate(@Nullable V value) throws SerializationException; 046 047 /** 048 * Provider for a specific constraint given a field type. 049 * 050 * @param <A> annotation type 051 * @param <V> data type 052 * @since 4.0.0 053 */ 054 interface Factory<A extends Annotation, V> { 055 056 /** 057 * Create a new specialized constraint. 058 * 059 * @param data annotation with metadata 060 * @param type annotated type. is a subtype of {@code V} 061 * @return new constraint 062 * @since 4.0.0 063 */ 064 Constraint<V> make(A data, Type type); 065 } 066 067 /** 068 * Require a value to be present for fields marked with the 069 * annotation {@code T}. 070 * 071 * @param <T> marker annotation type 072 * @return new constraint factory 073 * @since 4.0.0 074 */ 075 static <T extends Annotation> Constraint.Factory<T, Object> required() { 076 return (data, type) -> value -> { 077 if (value == null) { 078 throw new SerializationException("A value is required for this field"); 079 } 080 }; 081 } 082 083 /** 084 * Require values to match the {@link Matches#value() pattern} provided. 085 * 086 * <p>Upon failure, an error message will be taken from the annotation.</p> 087 * 088 * @return factory providing matching pattern test 089 * @since 4.0.0 090 */ 091 static Constraint.Factory<Matches, String> pattern() { 092 return (data, type) -> { 093 final Pattern test = Pattern.compile(data.value(), data.flags()); 094 final MessageFormat format = new MessageFormat(data.failureMessage(), Locale.getDefault()); 095 return value -> { 096 if (value != null) { 097 final Matcher match = test.matcher(value); 098 if (!match.matches()) { 099 throw new SerializationException(format.format(new Object[]{value, data.value()})); 100 } 101 } 102 }; 103 }; 104 } 105 106 /** 107 * Require values to match the {@link Matches#value() pattern} provided. 108 * 109 * <p>Upon failure, an error message will be taken from {@code bundle} with 110 * a key defined in the annotation.</p> 111 * 112 * @param bundle source for localized messages 113 * @return factory providing matching pattern test 114 * @since 4.0.0 115 */ 116 static Constraint.Factory<Matches, String> localizedPattern(final ResourceBundle bundle) { 117 return (data, type) -> { 118 final Pattern test = Pattern.compile(data.value(), data.flags()); 119 final MessageFormat format = new MessageFormat(Localization.key(bundle, data.failureMessage()), bundle.getLocale()); 120 return value -> { 121 if (value != null) { 122 final Matcher match = test.matcher(value); 123 if (!match.matches()) { 124 throw new SerializationException(format.format(new Object[]{value, data.value()})); 125 } 126 } 127 }; 128 }; 129 } 130 131}