001package gu.dtalk; 002 003import java.lang.reflect.Type; 004import java.text.ParseException; 005import java.text.SimpleDateFormat; 006import java.util.Date; 007import java.util.HashSet; 008import java.util.Map; 009import java.util.Set; 010import java.util.concurrent.Callable; 011import java.util.concurrent.ExecutionException; 012 013import com.alibaba.fastjson.JSON; 014import com.alibaba.fastjson.JSONException; 015import com.google.common.base.Function; 016import com.google.common.base.Predicate; 017import com.google.common.base.Throwables; 018import com.google.common.cache.Cache; 019import com.google.common.cache.CacheBuilder; 020import com.google.common.collect.Sets; 021 022import gu.simplemq.json.BaseJsonEncoder; 023import net.gdface.utils.FaceUtilits; 024 025import static gu.dtalk.CommonConstant.*; 026import static com.google.common.base.Preconditions.*; 027 028/** 029 * 选项类型 030 * @author guyadong 031 * 032 */ 033public enum OptionType { 034 /** 字符串 */ 035 STRING(StringOption.class), 036 /** 整数 */ 037 INTEGER(IntOption.class), 038 /** 浮点数 */ 039 FLOAT(FloatOption.class), 040 /** 布尔型 true/false 0/1 */ 041 BOOL(BoolOption.class), 042 /** 日期 yyyy-MM-dd HH:mm:ss */ 043 DATE(DateOption.class), 044 /** url字符串 */ 045 URL(UrlOption.class), 046 /** 密码字符串 */ 047 PASSWORD(PasswordOption.class), 048 /** e-mail地址 */ 049 EMAIL(StringOption.class,"^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"), 050 /** 手机号码(11位) */ 051 MPHONE(StringOption.class,"^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\\d{8}$"), 052 /** 身份证号(15位、18位数字),最后一位是校验位,可能为数字或字符X */ 053 IDNUM(StringOption.class,"(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)"), 054 /** base64 格式二进制数据 */ 055 BASE64(Base64Option.class), 056 /** MAC地址二进制数据 */ 057 MAC(MACOption.class, "^([a-fA-F0-9]{2}[:-]){5}[a-fA-F0-9]{2}$"), 058 /** IP地址二进制数据 */ 059 IP(IPv4Option.class,"^((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))$"), 060 /** base64 格式JPEG/BMP/PNG格式图像 */ 061 IMAGE(ImageOption.class), 062 /** 多选项(n>1) */ 063 @SuppressWarnings("unchecked") 064 MULTICHECK(CheckOption.class,"^\\s*(\\d+)?([,;\\s]+\\d+)?\\s*$"), 065 /** 单选开关(n>1) */ 066 @SuppressWarnings("unchecked") 067 SWITCH(SwitchOption.class,"^\\s*\\d+\\s*$"); 068 final String regex; 069 private volatile Type targetType; 070 /** 071 * 实现数据选项的类 072 */ 073 @SuppressWarnings("rawtypes") 074 final Class<? extends BaseOption> optClass; 075 private <T,B extends BaseOption<T>>OptionType(Class<B> implClass) { 076 this(implClass,""); 077 } 078 private <T,B extends BaseOption<T>>OptionType(Class<B> implClass,String regex) { 079 this.optClass = checkNotNull(implClass,"implClass is null"); 080 this.regex = checkNotNull(regex,"regex is null"); 081 } 082 /** 083 * 字符串验证器,根据正则表达式判断字符串是否符合当前数据类型的格式, 084 * 输入为null或正则表达式不匹配则返回false 085 */ 086 public final Predicate<String> strValidator = new Predicate<String>() { 087 @Override 088 public boolean apply(String input) { 089 if(regex.isEmpty()){ 090 return true; 091 } 092 return input != null && input.matches(regex); 093 } 094 }; 095 private static final Cache<OptionType, Function<String, ?>> cache = CacheBuilder.newBuilder().build(); 096 /** 097 * 返回从字符串转到指定类型的转换器实例<br> 098 * 返回的转换器特征:将字符串转换为指定的类型,如果输入的字符串格式无效则抛出异常 099 * @return 100 */ 101 @SuppressWarnings("unchecked") 102 private <T> Function<String, T> internalTrans(){ 103 104 switch(this){ 105 case EMAIL: 106 case MPHONE: 107 case IDNUM: 108 // 解析字符串为EMAIL类型,允许输入格式为ff:20:20:20:20:20格式的ip地址 109 return (Function<String, T>) new Function<String, String>(){ 110 @Override 111 public String apply(String input) { 112 checkArgument(strValidator.apply(input), 113 "INVALID FORMAT '%s' FOR %s", input,OptionType.this.name()); 114 return input; 115 }}; 116 case DATE: 117 // 解析字符串为DATE类型,允许日期,时间,日期+时间 三种格式 118 return (Function<String, T>) new Function<String, Date>(){ 119 120 @Override 121 public Date apply(String input) { 122 Date date = null; 123 if(null != input){ 124 try { 125 date = new SimpleDateFormat(TIMESTAMP_FORMATTER_STR).parse(input); 126 } catch (ParseException e) { 127 try { 128 date = new SimpleDateFormat(DATE_FORMATTER_STR).parse(input); 129 } catch (ParseException e1) { 130 try { 131 date = new SimpleDateFormat(TIME_FORMATTER_STR).parse(input); 132 } catch (ParseException e2) { 133 throw new IllegalArgumentException(String.format("INVALID FORMAT '%s' FOR %s", input,OptionType.this.name())); 134 } 135 } 136 } 137 } 138 return date; 139 }}; 140 case MAC: 141 // 解析字符串为MAC类型,允许输入格式为ff:20:20:20:20:20格式的ip地址 142 return (Function<String, T>) new Function<String, byte[]>(){ 143 144 @Override 145 public byte[] apply(String input) { 146 checkArgument(strValidator.apply(input), 147 "INVALID FORMAT '%s' FOR %s", input,OptionType.this.name()); 148 String hex = input.replace(":", ""); 149 return FaceUtilits.hex2Bytes(hex); 150 }}; 151 case IP: 152 // 解析字符串为ipv4类型,允许输入格式为127.0.0.1格式的ip地址 153 return (Function<String, T>) new Function<String, byte[]>() { 154 155 @Override 156 public byte[] apply(String input) { 157 checkArgument(strValidator.apply(input), 158 "INVALID FORMAT '%s' FOR %s", input,OptionType.this.name()); 159 160 String[] ip = input.split("\\."); 161 // [192,168,1,1]这样的数组大于127的值直接解析为byte会溢出,所以要先解析为int[]再转为byte[] 162 byte[] parseByte = new byte[ip.length]; 163 for(int i = 0; i < parseByte.length; ++i){ 164 parseByte[i] = (byte) (Integer.valueOf(ip[i]) & 0xff); 165 } 166 return parseByte; 167 168 } 169 }; 170 case MULTICHECK: 171 case SWITCH: 172 // 解析字符串为Set<Integer>类型 173 return (Function<String, T>) new Function<String, Set<Integer>>(){ 174 175 @Override 176 public Set<Integer> apply(String input) { 177 checkArgument(strValidator.apply(input), 178 "INVALID FORMAT '%s' FOR %s", input,OptionType.this.name()); 179 180 String[] numlist = input.split("[;,\\s]+"); 181 HashSet<Integer> set = Sets.newHashSet(); 182 for(String num:numlist){ 183 if(!num.isEmpty()){ 184 set.add(Integer.valueOf(num)); 185 } 186 } 187 return set; 188 }}; 189 default: 190 return new DefaultStringTransformer<>(getTargetType()); 191 } 192 } 193 194 /** 195 * 返回对应类型String到目标数据类型的转换器<br> 196 * 返回的转器实例将字符器转换为当前类型的数据,转换失败则抛出异常 197 * @param <T> 目标数据类型 198 * @return 199 * @see #internalTrans() 200 */ 201 @SuppressWarnings("unchecked") 202 public <T> Function<String, T> trans(){ 203 try { 204 return (Function<String, T>) cache.get(this, new Callable<Function<String, T>>(){ 205 206 @Override 207 public Function<String, T> call() throws Exception { 208 return internalTrans(); 209 }}); 210 } catch (ExecutionException e) { 211 Throwables.throwIfUnchecked(e.getCause()); 212 throw new RuntimeException(e.getCause()); 213 } 214 } 215 /** 216 * 如果有定制转换器(非{@link DefaultStringTransformer}),则尝试重新解析name指定的字段 217 * {@value CommonConstant#OPTION_FIELD_VALUE}, 218 * {@value CommonConstant#OPTION_FIELD_DEFAULT} 219 * @param json 220 * @param name 字段名 221 */ 222 private void refreshValueIfTransPresent(Map<String,Object> json,String name){ 223 Function<String, Object> t = trans(); 224 // 使用默认transformer的类型直接跳过 225 if(!(t instanceof DefaultStringTransformer)){ 226 if(null != json.get(name)){ 227 228 String valuestr = json.get(name).toString(); 229 try { 230 Object parsed = t.apply(valuestr); 231 if(parsed != null){ 232 json.put(name, parsed); 233 } 234 } catch (Exception e) { 235 } 236 237 } 238 } 239 } 240 public <T, O extends BaseOption<T>>OptionBuilder<T, O> builder() { 241 return OptionBuilder.<T,O>builder(this); 242 } 243 /** 244 * 默认字符串到T类型的转换器 245 * @author guyadong 246 * 247 * @param <T> 248 */ 249 private class DefaultStringTransformer<T> implements Function<String, T>{ 250 251 private final Type type; 252 public DefaultStringTransformer(Type type) { 253 super(); 254 this.type = checkNotNull(type,"type is null"); 255 } 256 /** 257 * 调用fastjson对输入字符串解析返回指定类型的对象,如果格式不对则抛出异常 258 * @see com.google.common.base.Function#apply(java.lang.Object) 259 */ 260 @Override 261 public T apply(String input) { 262 try { 263 return BaseJsonEncoder.getEncoder().fromJson(input, type); 264 } catch (JSONException e) { 265 if( !input.startsWith("\"") && !input.endsWith("\"")){ 266 try { 267 return BaseJsonEncoder.getEncoder().fromJson("\"" + input + "\"", type); 268 } catch (JSONException e2) { 269 } 270 } 271 throw e; 272 } 273 } 274 } 275 public static BaseOption<?> parseOption(Map<String,Object> json){ 276 OptionType optionType = OptionType.valueOf((String) json.get(OPTION_FIELD_TYPE)); 277 optionType.refreshValueIfTransPresent(json, OPTION_FIELD_VALUE); 278 optionType.refreshValueIfTransPresent(json, OPTION_FIELD_DEFAULT); 279 return BaseJsonEncoder.getEncoder().fromJson( JSON.toJSONString(json), optionType.optClass); 280 } 281 282 private Type getTargetType() { 283 if(null == targetType){ 284 synchronized (this) { 285 if (targetType == null) { 286 try { 287 targetType = optClass.newInstance().type; 288 } catch (Exception e) { 289 Throwables.throwIfUnchecked(e); 290 throw new RuntimeException(e); 291 } 292 } 293 } 294 } 295 return targetType; 296 } 297}