Skip to content

Commit defca2c

Browse files
committed
Initial commit
0 parents  commit defca2c

16 files changed

+602
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.idea
2+
*.iml
3+
target

pom.xml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>org.javawebstack</groupId>
8+
<artifactId>Validator</artifactId>
9+
<version>1.0-SNAPSHOT</version>
10+
11+
<repositories>
12+
<repository>
13+
<id>javawebstack</id>
14+
<url>https://repo.javawebstack.org</url>
15+
</repository>
16+
</repositories>
17+
18+
<dependencies>
19+
<dependency>
20+
<groupId>com.google.code.gson</groupId>
21+
<artifactId>gson</artifactId>
22+
<version>2.8.6</version>
23+
</dependency>
24+
<dependency>
25+
<groupId>org.javawebstack</groupId>
26+
<artifactId>Graph</artifactId>
27+
<version>1.0-SNAPSHOT</version>
28+
</dependency>
29+
<dependency>
30+
<groupId>org.junit.jupiter</groupId>
31+
<artifactId>junit-jupiter-engine</artifactId>
32+
<version>5.4.2</version>
33+
<scope>test</scope>
34+
</dependency>
35+
</dependencies>
36+
37+
<build>
38+
<plugins>
39+
<plugin>
40+
<groupId>org.apache.maven.plugins</groupId>
41+
<artifactId>maven-compiler-plugin</artifactId>
42+
<configuration>
43+
<source>8</source>
44+
<target>8</target>
45+
</configuration>
46+
</plugin>
47+
<plugin>
48+
<groupId>org.apache.maven.plugins</groupId>
49+
<artifactId>maven-surefire-plugin</artifactId>
50+
<version>2.22.1</version>
51+
</plugin>
52+
<plugin>
53+
<artifactId>maven-deploy-plugin</artifactId>
54+
<version>3.0.0-M1</version>
55+
<executions>
56+
<execution>
57+
<id>default-deploy</id>
58+
<phase>deploy</phase>
59+
<goals>
60+
<goal>deploy</goal>
61+
</goals>
62+
</execution>
63+
</executions>
64+
</plugin>
65+
</plugins>
66+
</build>
67+
68+
<distributionManagement>
69+
<snapshotRepository>
70+
<id>javawebstack-snapshots</id>
71+
<url>https://nexus.javawebstack.org/repository/javawebstack-snapshots</url>
72+
</snapshotRepository>
73+
<repository>
74+
<id>javawebstack-releases</id>
75+
<url>https://nexus.javawebstack.org/repository/javawebstack-releases</url>
76+
</repository>
77+
</distributionManagement>
78+
79+
</project>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.javawebstack.validator;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.RUNTIME)
9+
@Target(ElementType.FIELD)
10+
public @interface Rule {
11+
String[] value();
12+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.javawebstack.validator;
2+
3+
public class ValidationException extends RuntimeException {
4+
5+
private final ValidationResult result;
6+
7+
public ValidationException(ValidationResult result){
8+
this.result = result;
9+
}
10+
11+
public ValidationResult getResult() {
12+
return result;
13+
}
14+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.javawebstack.validator;
2+
3+
import java.util.HashMap;
4+
import java.util.List;
5+
import java.util.Map;
6+
7+
public class ValidationResult {
8+
9+
private final Map<String[], List<String>> errors;
10+
11+
ValidationResult(Map<String[], List<String>> errors){
12+
this.errors = errors;
13+
}
14+
15+
public Map<String[], List<String>> getErrors(){
16+
return errors;
17+
}
18+
19+
public Map<String, List<String>> getErrorMap(){
20+
Map<String, List<String>> errorMap = new HashMap<>();
21+
getErrors().forEach((k,v) -> errorMap.put(String.join(".", k), v));
22+
return errorMap;
23+
}
24+
25+
public boolean isValid(){
26+
return errors.size() == 0;
27+
}
28+
29+
}
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
package org.javawebstack.validator;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.JsonArray;
5+
import org.javawebstack.graph.GraphArray;
6+
import org.javawebstack.graph.GraphElement;
7+
import org.javawebstack.graph.GraphMapper;
8+
import org.javawebstack.validator.rule.*;
9+
10+
import java.lang.reflect.Constructor;
11+
import java.lang.reflect.Field;
12+
import java.lang.reflect.InvocationTargetException;
13+
import java.sql.Timestamp;
14+
import java.util.*;
15+
16+
public class Validator {
17+
18+
private static final Map<String, Constructor<? extends ValidationRule>> validationRules = new HashMap<>();
19+
private static final Map<Class<?>, Validator> validators = new HashMap<>();
20+
21+
static {
22+
registerRuleType("string", StringRule.class);
23+
registerRuleType("boolean", BooleanRule.class);
24+
registerRuleType("enum", EnumRule.class);
25+
registerRuleType("required", RequiredRule.class);
26+
registerRuleType("ipv4", IPv4AddressRule.class);
27+
registerRuleType("ipv6", IPv6AddressRule.class);
28+
registerRuleType("integer", IntegerRule.class);
29+
registerRuleType("timestamp", TimestampRule.class);
30+
}
31+
32+
public static void registerRuleType(String name, Class<? extends ValidationRule> type){
33+
try {
34+
Constructor<? extends ValidationRule> constructor = type.getDeclaredConstructor(String[].class);
35+
constructor.setAccessible(true);
36+
validationRules.put(name, constructor);
37+
return;
38+
} catch (NoSuchMethodException ignored) {}
39+
try {
40+
Constructor<? extends ValidationRule> constructor = type.getDeclaredConstructor();
41+
constructor.setAccessible(true);
42+
validationRules.put(name, constructor);
43+
} catch (NoSuchMethodException ignored) {}
44+
}
45+
46+
public static ValidationRule makeRule(String name, String[] params){
47+
Constructor<? extends ValidationRule> constructor = validationRules.get(name);
48+
if(constructor == null)
49+
return null;
50+
if(constructor.getParameterCount() == 0){
51+
try {
52+
return constructor.newInstance();
53+
} catch (InstantiationException | InvocationTargetException | IllegalAccessException ignored) {}
54+
}else{
55+
try {
56+
return constructor.newInstance((Object) params);
57+
} catch (InstantiationException | InvocationTargetException | IllegalAccessException ignored) {}
58+
}
59+
return null;
60+
}
61+
62+
public static ValidationRule makeRule(String source){
63+
String[] params = null;
64+
if(source.contains("(")){
65+
String[] spl = source.split("\\(", 2);
66+
source = spl[0];
67+
String s = spl[1];
68+
s = s.substring(0, s.length()-1);
69+
GraphArray array = GraphArray.fromJson(new Gson().fromJson("["+s+"]", JsonArray.class));
70+
if(array.stream().filter(e -> e.isPrimitive()).count() == array.size()){
71+
params = new String[array.size()];
72+
for(int i=0; i<params.length; i++)
73+
params[i] = array.get(i).toString();
74+
}
75+
}
76+
if(params == null)
77+
params = new String[0];
78+
return makeRule(source, params);
79+
}
80+
81+
public static ValidationRule[] makeRules(String... sources){
82+
ValidationRule[] rules = new ValidationRule[sources.length];
83+
for(int i=0; i<rules.length; i++)
84+
rules[i] = makeRule(sources[i]);
85+
return rules;
86+
}
87+
88+
public static Validator getValidator(Class<?> type){
89+
Validator validator = validators.get(type);
90+
if(validator == null){
91+
validator = new Validator();
92+
getClassRules(type).forEach(validator::rule);
93+
validators.put(type, validator);
94+
}
95+
return validator;
96+
}
97+
98+
public static <T> T map(Class<T> type, GraphElement element, GraphMapper mapper){
99+
Validator validator = getValidator(type);
100+
ValidationResult result = validator.validate(element);
101+
if(!result.isValid())
102+
throw new ValidationException(result);
103+
return mapper.fromGraph(element, type);
104+
}
105+
106+
public static <T> T map(Class<T> type, GraphElement element){
107+
return map(type, element, new GraphMapper());
108+
}
109+
110+
private final Map<String[], List<ValidationRule>> rules = new HashMap<>();
111+
112+
public Validator rule(String[] key, ValidationRule... rules){
113+
return rule(key, Arrays.asList(rules));
114+
}
115+
116+
public Validator rule(String[] key, List<ValidationRule> rules){
117+
this.rules.put(key, rules);
118+
return this;
119+
}
120+
121+
public Validator rule(String key, List<ValidationRule> rules){
122+
return rule(Arrays.stream(key.split("\\.")).map(k -> k.equals("*") ? null : k).toArray(String[]::new), rules);
123+
}
124+
125+
public Validator rule(String key, ValidationRule... rules){
126+
return rule(key, Arrays.asList(rules));
127+
}
128+
129+
public ValidationResult validate(GraphElement rootElement){
130+
Map<String[], List<String>> errors = new HashMap<>();
131+
for(String[] sourceKey : rules.keySet()){
132+
Map<String[], GraphElement> map = matchKeys(sourceKey, rootElement);
133+
List<ValidationRule> validators = rules.get(sourceKey);
134+
map.forEach((key, value) -> {
135+
List<String> fieldErrors = new ArrayList<>();
136+
for(ValidationRule rule : validators){
137+
String error = rule.validate(this, value);
138+
if(error != null)
139+
fieldErrors.add(error);
140+
}
141+
if(fieldErrors.size() > 0)
142+
errors.put(key, fieldErrors);
143+
});
144+
}
145+
return new ValidationResult(errors);
146+
}
147+
148+
private Map<String[], GraphElement> matchKeys(String[] keys, GraphElement element){
149+
Map<String[], GraphElement> result = new HashMap<>();
150+
if(keys.length == 0){
151+
result.put(keys, element);
152+
return result;
153+
}
154+
if(element.isArray())
155+
element = element.object();
156+
if(element.isObject()){
157+
String currentKey = keys[0];
158+
String[] newKeys = new String[keys.length-1];
159+
System.arraycopy(keys, 1, newKeys, 0, newKeys.length);
160+
if(currentKey != null){
161+
matchKeys(newKeys, element.object().get(currentKey)).forEach((nk, actualValue) -> {
162+
String[] actualKey = new String[nk.length + 1];
163+
actualKey[0] = currentKey;
164+
System.arraycopy(nk, 0, actualKey, 1, nk.length);
165+
result.put(actualKey, actualValue);
166+
});
167+
}else{
168+
element.object().forEach((k, value) -> {
169+
matchKeys(newKeys, value).forEach((nk, actualValue) -> {
170+
String[] actualKey = new String[nk.length + 1];
171+
actualKey[0] = k;
172+
System.arraycopy(nk, 0, actualKey, 1, nk.length);
173+
result.put(actualKey, actualValue);
174+
});
175+
});
176+
}
177+
}
178+
return result;
179+
}
180+
181+
private static Map<String[], List<ValidationRule>> getClassRules(Class<?> type){
182+
Map<String[], List<ValidationRule>> rules = new HashMap<>();
183+
if(type.isAnnotation())
184+
return rules;
185+
if(type.equals(String.class))
186+
return rules;
187+
if(type.equals(Timestamp.class) || type.equals(Date.class)){
188+
rules.put(new String[0], Collections.singletonList(new TimestampRule()));
189+
return rules;
190+
}
191+
if(type.equals(Boolean.class)){
192+
rules.put(new String[0], Collections.singletonList(new BooleanRule()));
193+
return rules;
194+
}
195+
if(type.equals(Integer.class)){
196+
rules.put(new String[0], Collections.singletonList(new IntegerRule()));
197+
return rules;
198+
}
199+
if(type.isEnum()){
200+
rules.put(new String[0], Collections.singletonList(new EnumRule((Class<? extends Enum<?>>) type)));
201+
return rules;
202+
}
203+
if(type.isArray()){
204+
getClassRules(type.getComponentType()).forEach((key, validators) -> {
205+
String[] actualKey = new String[key.length+1];
206+
actualKey[0] = "*";
207+
System.arraycopy(key, 0, actualKey, 1, key.length);
208+
rules.put(actualKey, validators);
209+
});
210+
return rules;
211+
}
212+
for(Field field : getFieldsRecursive(type)){
213+
String name = field.getName();
214+
getClassRules(field.getType()).forEach((key, validators) -> {
215+
String[] actualKey = new String[key.length+1];
216+
actualKey[0] = name;
217+
System.arraycopy(key, 0, actualKey, 1, key.length);
218+
rules.put(actualKey, validators);
219+
});
220+
field.setAccessible(true);
221+
Rule[] ruleAnnotations = field.getDeclaredAnnotationsByType(Rule.class);
222+
if(ruleAnnotations.length > 0){
223+
List<ValidationRule> r = new ArrayList<>();
224+
for(String source : ruleAnnotations[0].value()){
225+
ValidationRule rule = Validator.makeRule(source);
226+
if(rule != null)
227+
r.add(rule);
228+
}
229+
if(r.size() > 0)
230+
rules.put(new String[]{name}, r);
231+
}
232+
}
233+
return rules;
234+
}
235+
236+
private static List<Field> getFieldsRecursive(Class<?> type){
237+
List<Field> fields;
238+
if(type.getSuperclass() != null && !type.getSuperclass().equals(Object.class))
239+
fields = getFieldsRecursive(type.getSuperclass());
240+
else
241+
fields = new ArrayList<>();
242+
fields.addAll(Arrays.asList(type.getDeclaredFields()));
243+
return fields;
244+
}
245+
246+
}

0 commit comments

Comments
 (0)