2013年1月10日 星期四

符合Bean Validation規格的身分證字號驗證 (JSR303)

Annotation
package com.gss.gmo.cao.validator.constraints;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

import com.gss.gmo.cao.validator.constraints.impl.IdentificationNumberValidator;

/**
 * The annotated element has to represent a valid identification number.
 *
 * @author linus_chien
 *
 */
@Documented
@Constraint(validatedBy = IdentificationNumberValidator.class)
@Target(value = { METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(value = RUNTIME)
public @interface IdentificationNumber {

    /**
     * Default key:
     * com.gss.gmo.cao.validator.constraints.IdentificationNumber.message.
     *
     * @return
     */
    String message() default "{com.gss.gmo.cao.validator.constraints.IdentificationNumber.message}";

    /**
     * Default {}.
     *
     * @return
     */
    Class<?>[] groups() default {};

    /**
     * Default {}.
     *
     * @return
     */
    Class<? extends Payload>[] payload() default {};

    /**
     * Defines several @IdentificationNumber annotations on the same element.
     *
     * @author linus_chien
     *
     */
    @Documented
    @Target(value = { METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
    @Retention(value = RUNTIME)
    public @interface List {
        /**
         * @return
         */
        IdentificationNumber[] values();
    }

}
ConstraintValidator Implementation
package com.gss.gmo.cao.validator.constraints.impl;

import java.util.regex.Pattern;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import com.gss.gmo.cao.validator.constraints.IdentificationNumber;

/**
 * 身份證字號檢查程式,身份證字號規則: 字母(ABCDEFGHJKLMNPQRSTUVXYWZIO)對應一組數(10~35),
 * 令其十位數為X1,個位數為X2;( 如A:X1=1 , X2=0 );D?表示2~9數字 Y = X1 + 9*X2 + 8*D1 + 7*D2 +
 * 6*D3 + 5*D4 + 4*D5 + 3*D6 + 2*D7+ 1*D8 + D9 如Y能被10整除,則表示該身份證號碼為正確,否則為錯誤。
 * 臺北市(A)、臺中市(B)、基隆市(C)、臺南市(D)、高雄市(E)、臺北縣(F)、
 * 宜蘭縣(G)、桃園縣(H)、嘉義市(I)、新竹縣(J)、苗栗縣(K)、臺中縣(L)、
 * 南投縣(M)、彰化縣(N)、新竹市(O)、雲林縣(P)、嘉義縣(Q)、臺南縣(R)、
 * 高雄縣(S)、屏東縣(T)、花蓮縣(U)、臺東縣(V)、金門縣(W)、澎湖縣(X)、 陽明山(Y)、連江縣(Z)
 *
 * @author linus_chien
 *
 */
public class IdentificationNumberValidator implements ConstraintValidator<IdentificationNumber, String> {

    /**
     * Id pattern.
     */
    public static final Pattern ID_PATTERN = Pattern.compile("[A-Z][12]\\d{8}");

    /**
     * The first chars of a valid id.
     */
    public static final String ID_FIRST_CHARS = "ABCDEFGHJKLMNPQRSTUVXYWZIO";

    @Override
    public void initialize(IdentificationNumber annotation) {
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }

        value = value.toUpperCase();

        if (!ID_PATTERN.matcher(value).matches()) {
            return false;
        }

        char[] numbers = value.toCharArray();

        int code = ID_FIRST_CHARS.indexOf(numbers[0]) + 10;
        int sum = (int) (code / 10) + 9 * (code % 10);

        for (int i = 1; i <= 8; i++) {
            sum += ((numbers[i] - '0') * (9 - i));
        }

        sum += (numbers[9] - '0');

        if (sum % 10 == 0) {
            return true;
        } else {
            return false;
        }
    }

}