suffixList = Arrays.asList(suffixs);
802 | for (Object fileName : names) {
803 | String fileName1 = (String) fileName;
804 | if (fileName1.contains(".")) {
805 | String fileSuffix = fileName1.split("\\.")[1];
806 | if (!(fileSuffix != null && suffixList.contains(fileSuffix))) {
807 | return Boolean.FALSE;
808 | }
809 | } else {
810 | return Boolean.FALSE;
811 | }
812 | }
813 | return Boolean.TRUE;
814 | }
815 |
816 | private static void judgeLen(Object tmpvalue, List names) {
817 | if (tmpvalue instanceof File) {
818 | long length = ((File) tmpvalue).length();
819 | names.add(length);
820 | } else if (tmpvalue instanceof MultipartFile) {
821 | long size = ((MultipartFile) tmpvalue).getSize();
822 | names.add(size);
823 | } else {
824 | throw new ParamsCheckException("the field type is wrong, we need a File or MultipartFile ");
825 | }
826 | }
827 |
828 | private static void judge(Object tmpvalue, List names) {
829 | if (tmpvalue instanceof File) {
830 | String filename = ((File) tmpvalue).getName();
831 | names.add(filename);
832 | } else if (tmpvalue instanceof MultipartFile) {
833 | String filename = ((MultipartFile) tmpvalue).getOriginalFilename();
834 | names.add(filename);
835 | } else if (tmpvalue instanceof String) {
836 | names.add(tmpvalue);
837 | } else {
838 | throw new ParamsCheckException("the field type is wrong, we need a File or MultipartFile or String ");
839 | }
840 | }
841 |
842 |
843 | /**
844 | * 判断是否Equal指定的值
845 | * 支持String、Integer、Long、Short、Float、Double、BigDecimal
846 | * 支持Collection,判断size的值
847 | *
848 | * @param value
849 | * @param express
850 | * @return
851 | */
852 | public static Boolean isEqual(Object value, String express) {
853 | if (value == null) {
854 | return Boolean.FALSE;
855 | }
856 | if (value instanceof String) {
857 | return ((String) value).equals(express);
858 | }
859 | if (value instanceof Integer) {
860 | return ((Integer) value).equals(Integer.valueOf(express));
861 | }
862 | if (value instanceof Long) {
863 | return ((Long) value).equals(Long.valueOf(express));
864 | }
865 | if (value instanceof Short) {
866 | return ((Short) value).equals(Short.valueOf(express));
867 | }
868 | if (value instanceof Float) {
869 | return ((Float) value).equals(Float.valueOf(express));
870 | }
871 | if (value instanceof Double) {
872 | return ((Double) value).equals(Double.valueOf(express));
873 | }
874 | if (value instanceof BigDecimal) {
875 | return ((BigDecimal) value).compareTo(new BigDecimal(express)) == 0;
876 | }
877 | if (value instanceof Collection) {
878 | return ((Collection) value).size() == Integer.valueOf(express);
879 | }
880 | return Boolean.FALSE;
881 | }
882 |
883 |
884 | /**
885 | * 判断String是否满足正则表达式
886 | *
887 | * @param value
888 | * @param regEx 正则表达式
889 | * @return
890 | */
891 | public static Boolean isPattern(Object value, String regEx) {
892 | if (isNull(value, null)) {
893 | return Boolean.FALSE;
894 | }
895 | if (value instanceof String) {
896 | Pattern p = Pattern.compile(regEx);
897 | Matcher m = p.matcher((String) value);
898 | if (m.matches()) {
899 | return Boolean.TRUE;
900 | }
901 | }
902 | return Boolean.FALSE;
903 | }
904 |
905 |
906 | /**
907 | * 时间毫秒值
908 | *
909 | * @param value
910 | * @param regEx
911 | * @return
912 | */
913 | public static Boolean isTimeMillSeconds(Object value, String regEx) {
914 | Boolean number = isNumber(value, regEx);
915 | if (number) {
916 | if (String.valueOf(value).length() == 13) {
917 | return true;
918 | }
919 | }
920 | return false;
921 |
922 | }
923 |
924 | /**
925 | * 是否是身份证号
926 | *
927 | * @param value
928 | * @param regEx
929 | * @return
930 | */
931 | public static Boolean isIdCard(Object value, String regEx) {
932 | if (value == null) {
933 | return Boolean.FALSE;
934 | }
935 | return CITIZEN_ID.matcher(String.valueOf(value)).matches();
936 | }
937 |
938 | //
939 | // /**
940 | // * 是否是有效的统一社会信用代码
941 | // *
942 | // * @param value
943 | // * @param regEx
944 | // * @return
945 | // */
946 | // public static Boolean isCreditCode(Object value, String regEx) {
947 | // if (value == null) {
948 | // return Boolean.FALSE;
949 | // }
950 | // return Validator.isCreditCode(String.valueOf(value));
951 | // }
952 |
953 | /**
954 | * 是否是中国邮编
955 | *
956 | * @param value
957 | * @param regEx
958 | * @return
959 | */
960 | public static Boolean isChinesePostCode(Object value, String regEx) {
961 | if (value == null) {
962 | return Boolean.FALSE;
963 | }
964 | return ZIP_CODE.matcher(String.valueOf(value)).matches();
965 | }
966 |
967 | /**
968 | * 是否是Ipv4
969 | *
970 | * @param value
971 | * @param regEx
972 | * @return
973 | */
974 | public static Boolean isIpv4(Object value, String regEx) {
975 | if (value == null) {
976 | return Boolean.FALSE;
977 | }
978 | return IPV4.matcher(String.valueOf(value)).matches();
979 | }
980 |
981 | /**
982 | * 是否是Ipv6
983 | *
984 | * @param value
985 | * @param regEx
986 | * @return
987 | */
988 | public static Boolean isIpv6(Object value, String regEx) {
989 | if (value == null) {
990 | return Boolean.FALSE;
991 | }
992 | return IPV6.matcher(String.valueOf(value)).matches();
993 | }
994 |
995 | /**
996 | * 是否是汉字
997 | *
998 | * @param value
999 | * @param regEx
1000 | * @return
1001 | */
1002 | public static Boolean isChinese(Object value, String regEx) {
1003 | if (value == null) {
1004 | return Boolean.FALSE;
1005 | }
1006 | return CHINESES.matcher(String.valueOf(value)).matches();
1007 | }
1008 |
1009 | /**
1010 | * 验证是否为英文字母 、数字和下划线
1011 | *
1012 | * @param value
1013 | * @param regEx
1014 | * @return
1015 | */
1016 | public static Boolean isGeneral(Object value, String regEx) {
1017 | if (value == null) {
1018 | return Boolean.FALSE;
1019 | }
1020 | return GENERAL.matcher(String.valueOf(value)).matches();
1021 | }
1022 |
1023 | /**
1024 | * 验证是否为MAC地址
1025 | *
1026 | * @param value
1027 | * @param regEx
1028 | * @return
1029 | */
1030 | public static Boolean isMac(Object value, String regEx) {
1031 | if (value == null) {
1032 | return Boolean.FALSE;
1033 | }
1034 | return MAC_ADDRESS.matcher(String.valueOf(value)).matches();
1035 | }
1036 |
1037 | /**
1038 | * 验证是否为中国车牌号
1039 | *
1040 | * @param value
1041 | * @param regEx
1042 | * @return
1043 | */
1044 | public static Boolean isPlateNumber(Object value, String regEx) {
1045 | if (value == null) {
1046 | return Boolean.FALSE;
1047 | }
1048 | return PLATE_NUMBER.matcher(String.valueOf(value)).matches();
1049 | }
1050 |
1051 | /**
1052 | * 验证是否为URL
1053 | *
1054 | * @param value
1055 | * @param regEx
1056 | * @return
1057 | */
1058 | public static Boolean isUrl(Object value, String regEx) {
1059 | if (value == null) {
1060 | return Boolean.FALSE;
1061 | }
1062 | try {
1063 | new java.net.URL(String.valueOf(value));
1064 | } catch (MalformedURLException e) {
1065 | return false;
1066 | }
1067 | return true;
1068 | }
1069 |
1070 | /**
1071 | * 验证是否是图书的合法的ISBN号码 包括10或者13位的ISBN
1072 | *
1073 | * @param value
1074 | * @param regEx
1075 | * @return
1076 | * @since 0.5.0
1077 | */
1078 | @SuppressWarnings("warn")
1079 | public static Boolean isISBN(Object value, String regEx) {
1080 | if (value == null) {
1081 | return Boolean.FALSE;
1082 | }
1083 | return RegexPattern.ISBN_REGEX.matcher(String.valueOf(value)).matches();
1084 | }
1085 |
1086 | /**
1087 | * 是否是银行卡号
1088 | * Luhn算法来验证:
1089 | * 1、从卡号最后一位数字开始,逆向将奇数位(1、3、5等等)相加。
1090 | * 2、从卡号最后一位数字开始,逆向将偶数位数字,先乘以2(如果乘积为两位数,则将其减去9),再求和。
1091 | * 3、将奇数位总和加上偶数位总和,结果应该可以被10整除。
1092 | *
1093 | * @param value
1094 | * @return
1095 | */
1096 | public static Boolean isBankNumber(Object value, String regEx) {
1097 | if (value == null) {
1098 | return Boolean.FALSE;
1099 | }
1100 | String number = String.valueOf(value);
1101 | if (number.length() != 16 && number.length() != 19) {
1102 | return false;
1103 | }
1104 | if (!number.matches("\\d+")) {
1105 | return false;
1106 | }
1107 |
1108 | char[] digits = number.toCharArray();
1109 | int len = number.length();
1110 | int numSum = 0;
1111 | for (int i = len - 1, j = 1; i >= 0; i--, j++) {
1112 | int value0 = digits[i] - '0';
1113 | if (j % 2 == 0) {
1114 | value0 *= 2;
1115 | if (value0 > 9) {
1116 | value0 -= 9;
1117 | }
1118 | }
1119 | numSum += value0;
1120 | }
1121 | return numSum % 10 == 0;
1122 | }
1123 |
1124 | /**
1125 | * 验证是否为UUID
1126 | * 包括带横线标准格式和不带横线的简单模式
1127 | *
1128 | * @param value
1129 | * @param regEx
1130 | * @return
1131 | */
1132 | @SuppressWarnings("warn")
1133 | public static Boolean isUUID(Object value, String regEx) {
1134 | if (value == null) {
1135 | return Boolean.FALSE;
1136 | }
1137 | return RegexPattern.UUID.matcher(String.valueOf(value)).matches() || UUID_SIMPLE.matcher(String.valueOf(value)).matches();
1138 | }
1139 |
1140 | /**
1141 | * 验证是否为生日
1142 | * 只支持以下几种格式:
1143 | *
1144 | * - yyyyMMdd
1145 | * - yyyy-MM-dd
1146 | * - yyyy/MM/dd
1147 | * - yyyy.MM.dd
1148 | * - yyyy年MM月dd日
1149 | *
1150 | *
1151 | * @param value 值
1152 | * @return 是否为生日
1153 | */
1154 | public static Boolean isBirthday(Object value, String regEx) {
1155 | if (value == null) {
1156 | return Boolean.FALSE;
1157 | }
1158 | final Matcher matcher = BIRTHDAY.matcher(String.valueOf(value));
1159 | if (matcher.find()) {
1160 | int year = Integer.parseInt(matcher.group(1));
1161 | int month = Integer.parseInt(matcher.group(3));
1162 | int day = Integer.parseInt(matcher.group(5));
1163 | // 验证年
1164 | Calendar calendar = Calendar.getInstance();
1165 | int thisYear = calendar.get(Calendar.YEAR);
1166 | if (year < 1900 || year > thisYear) {
1167 | return false;
1168 | }
1169 |
1170 | // 验证月
1171 | if (month < 1 || month > 12) {
1172 | return false;
1173 | }
1174 |
1175 | // 验证日
1176 | if (day < 1 || day > 31) {
1177 | return false;
1178 | }
1179 | // 检查几个特殊月的最大天数
1180 | if (day == 31 && (month == 4 || month == 6 || month == 9 || month == 11)) {
1181 | return false;
1182 | }
1183 | return true;
1184 | }
1185 | return false;
1186 | }
1187 |
1188 | /**
1189 | * 相关正则表达式
1190 | */
1191 | public static class RegexPattern {
1192 | /**
1193 | * 邮箱
1194 | */
1195 | public final static Pattern EMAIL = Pattern.compile("^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$");
1196 |
1197 | /**
1198 | * 英文字母 、数字和下划线
1199 | */
1200 | public final static Pattern GENERAL = Pattern.compile("^\\w+$");
1201 |
1202 | /**
1203 | * 中文汉字
1204 | */
1205 | public final static Pattern CHINESES = Pattern.compile("[\u4E00-\u9FFF]+");
1206 |
1207 | /**
1208 | * IP v4
1209 | */
1210 | public final static Pattern IPV4 = Pattern.compile("\\b((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\b");
1211 | /**
1212 | * IP v6
1213 | */
1214 | public final static Pattern IPV6 = Pattern.compile("(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))");
1215 | /**
1216 | * 货币
1217 | */
1218 | public final static Pattern MONEY = Pattern.compile("^(\\d+(?:\\.\\d+)?)$");
1219 |
1220 | /**
1221 | * 移动电话
1222 | */
1223 | public final static Pattern MOBILE = Pattern.compile("(?:0|86|\\+86)?1[3456789]\\d{9}");
1224 | /**
1225 | * 18位身份证号码
1226 | */
1227 | public final static Pattern CITIZEN_ID = Pattern.compile("[1-9]\\d{5}[1-2]\\d{3}((0\\d)|(1[0-2]))(([012]\\d)|3[0-1])\\d{3}(\\d|X|x)");
1228 | /**
1229 | * 邮编
1230 | */
1231 | public final static Pattern ZIP_CODE = Pattern.compile("[1-9]\\d{5}(?!\\d)");
1232 | /**
1233 | * 生日
1234 | */
1235 | public final static Pattern BIRTHDAY = Pattern.compile("^(\\d{2,4})([/\\-.年]?)(\\d{1,2})([/\\-.月]?)(\\d{1,2})日?$");
1236 |
1237 |
1238 | /**
1239 | * 中文字、英文字母、数字和下划线
1240 | */
1241 | public final static Pattern GENERAL_WITH_CHINESE = Pattern.compile("^[\u4E00-\u9FFF\\w]+$");
1242 |
1243 | /**
1244 | * ISBN-10 OR ISBN-13
1245 | */
1246 | public final static Pattern ISBN_REGEX = Pattern.compile("^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$");
1247 |
1248 | /**
1249 | * UUID
1250 | */
1251 | public final static Pattern UUID = Pattern.compile("^[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$");
1252 | /**
1253 | * 不带横线的UUID
1254 | */
1255 | public final static Pattern UUID_SIMPLE = Pattern.compile("^[0-9a-z]{32}$");
1256 | /**
1257 | * MAC地址正则
1258 | */
1259 | public static final Pattern MAC_ADDRESS = Pattern.compile("((?:[A-F0-9]{1,2}[:-]){5}[A-F0-9]{1,2})|(?:0x)(\\d{12})(?:.+ETHER)", Pattern.CASE_INSENSITIVE);
1260 |
1261 | /**
1262 | * 允许的文件后缀
1263 | */
1264 | public static final String[] DEFAULT_ALLOWED_EXTENSION = {
1265 | // 图片
1266 | "bmp", "gif", "jpg", "jpeg", "png", "blob", "webp", "svg", "pcx", "ico",
1267 | // word excel powerpoint
1268 | "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
1269 | // 压缩文件
1270 | "rar", "zip", "gz", "bz2", "7z", "tar.gz",
1271 | "xml",
1272 | // pdf
1273 | "pdf",
1274 | //视频
1275 | "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
1276 | "asf", "rm", "rmvb", "mp4", "mov"
1277 | };
1278 | /**
1279 | * 中国车牌号码(兼容新能源车牌)
1280 | */
1281 | public final static Pattern PLATE_NUMBER = Pattern.compile(
1282 | //https://gitee.com/loolly/hutool/issues/I1B77H?from=project-issue
1283 | "^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[ABCDEFGHJK])|([ABCDEFGHJK]([A-HJ-NP-Z0-9])[0-9]{4})))|" +
1284 | //https://gitee.com/loolly/hutool/issues/I1BJHE?from=project-issue
1285 | "([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领]\\d{3}\\d{1,3}[领])|" +
1286 | "([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$");
1287 |
1288 |
1289 | /**
1290 | * 社会统一信用代码
1291 | *
1292 | * 第一部分:登记管理部门代码1位 (数字或大写英文字母)
1293 | * 第二部分:机构类别代码1位 (数字或大写英文字母)
1294 | * 第三部分:登记管理机关行政区划码6位 (数字)
1295 | * 第四部分:主体标识码(组织机构代码)9位 (数字或大写英文字母)
1296 | * 第五部分:校验码1位 (数字或大写英文字母)
1297 | *
1298 | */
1299 | public static final Pattern CREDIT_CODE = Pattern.compile("^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$");
1300 |
1301 | /**
1302 | * 数字
1303 | */
1304 | public static final Pattern NUMBER_CODE = Pattern.compile("\\d+(\\.\\d+)?");
1305 |
1306 | /**
1307 | * 默认文件大小 10M
1308 | */
1309 | public static final Long DEFAULT_FILE_SIZE = 1024 * 1024 * 10L;
1310 |
1311 | }
1312 |
1313 | }
1314 |
--------------------------------------------------------------------------------
/src/main/java/com/github/liangbaika/validate/utils/SpringContextHolder.java:
--------------------------------------------------------------------------------
1 | package com.github.liangbaika.validate.utils;
2 |
3 | import org.springframework.beans.BeansException;
4 | import org.springframework.context.ApplicationContext;
5 | import org.springframework.context.ApplicationContextAware;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * Spring容器工具类
10 | *
11 | * @author liangbaiakai
12 | * @version 0.1.0
13 | * @date 2020/5/15 18:17
14 | */
15 | @Component
16 | public class SpringContextHolder implements ApplicationContextAware {
17 |
18 | private static ApplicationContext applicationContext;
19 |
20 | @Override
21 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
22 | SpringContextHolder.applicationContext = applicationContext;
23 | }
24 |
25 | public static ApplicationContext getApplicationContext() {
26 | assertApplicationContext(null, null);
27 | return applicationContext;
28 | }
29 |
30 | @SuppressWarnings("unchecked")
31 | public static T getBean(String beanName) {
32 | assertApplicationContext(beanName, null);
33 | return (T) applicationContext.getBean(beanName);
34 | }
35 |
36 | public static T getBean(Class requiredType) {
37 | assertApplicationContext(null, requiredType);
38 | return applicationContext.getBean(requiredType);
39 | }
40 |
41 | private static void assertApplicationContext(String beanName, Class requiredType) {
42 | if (SpringContextHolder.applicationContext == null) {
43 | throw new RuntimeException("ApplicaitonContext property is NULL, please check whether SpringContextHolder is injected!");
44 | }
45 | if (beanName != null) {
46 | boolean have = applicationContext.containsBean(beanName);
47 | if (!have) {
48 | throw new RuntimeException("This bean is not managed by the Spring container ");
49 | }
50 | }
51 | if (requiredType != null) {
52 | String[] beanNamesForType = applicationContext.getBeanNamesForType(requiredType);
53 | if (beanNamesForType == null || beanNamesForType.length == 0) {
54 | throw new RuntimeException("This bean is not managed by the Spring container ");
55 | }
56 | if (beanNamesForType.length != 1) {
57 | throw new RuntimeException("Class type fetching is not supported for multiple types of beans. Use the name to get this bean");
58 | }
59 | boolean have = applicationContext.containsBean(beanNamesForType[0]);
60 | if (!have) {
61 | throw new RuntimeException("This bean is not managed by the Spring container ");
62 | }
63 | }
64 |
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/spring.factories:
--------------------------------------------------------------------------------
1 | # Auto Configure
2 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
3 | com.github.liangbaika.validate.config.SpringValidateAutoConfig
--------------------------------------------------------------------------------
/src/test/java/com/github/liangbaika/validate/test/Tests.java:
--------------------------------------------------------------------------------
1 | package com.github.liangbaika.validate.test;
2 |
3 | import com.github.liangbaika.validate.core.ValidateBuilder;
4 | import org.junit.Test;
5 |
6 | import static com.github.liangbaika.validate.enums.Check.*;
7 |
8 | /**
9 | * 测试
10 | *
11 | * @author lq
12 | * @version 1.0
13 | * @date 2021/1/29 21:44
14 | */
15 | public class Tests {
16 |
17 | @Test
18 | public void testValidateBuilder() {
19 | ValidateBuilder validateBuilder = ValidateBuilder.build();
20 | int failedCounts = validateBuilder
21 | .vali(ne, "3", "3", "不能等于")
22 | .vali(Chinese, "测试中文")
23 | .vali(isBirthdaystr, "1992-12-09")
24 | .vali(isUrl, "https://baidu.com")
25 | .doCheck()
26 | .getFailedCounts();
27 | assert failedCounts == 1;
28 | assert validateBuilder.getSuccedCounts() == 3;
29 |
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------