Total Pageviews

2018/03/07

[commons-validator] numeric-related utilies

Scenario
In real world, we may need some common functions to fulfill our requirement, such as:
1. Validate the number string (including Integer and Double)
2. Convert Integer and Double to String, and apply 1000 sepearator 
3. Convert string to Integer and Double
4. Check the number is in specific range


We can use commons-validator to implement this function easily.


Remember to add dependency in your pom.xml

1
2
3
4
5
  <dependency>
   <groupId>commons-validator</groupId>
   <artifactId>commons-validator</artifactId>
   <version>1.6</version>
  </dependency>



Sample Code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package albert.practice.validation;

import org.apache.commons.validator.routines.DoubleValidator;
import org.apache.commons.validator.routines.IntegerValidator;

public class NumericUtils {

    /**
     * 檢查輸入的 integer string 是否為數字.
     * 
     * @param number
     */
    public static void isValidInteger(String number) {
       if (IntegerValidator.getInstance().validate(number) == null) {
           throw new IllegalArgumentException("您輸入的非數字");
       }
    }

    /**
     * 檢查輸入的 double string 是否為數字.
     * 
     * @param number
    */
    public static void isValidDouble(String number) {
       if (DoubleValidator.getInstance().validate(number) == null) {
           throw new IllegalArgumentException("您輸入的非數字");
       }
    }

    /**
     * 將 integer 加上 1000 separator.
     * 
     * @param number
     * @return
     */
    public static String formatInteger(Integer number) {
        return IntegerValidator.getInstance().format(number, "#,##0");
    }

    /**
     * 將 Double 加上 1000 separator.
     * 
     * @param number
     * @return
     */
    public static String formatDouble(Double number) {
        return DoubleValidator.getInstance().format(number, "#,##0.00");
    }

    /**
     * 將integer string 轉成 Integer.
     * 
     * @param numberStr
     * @return
     */
    public static Integer formatIntegerString(String numberStr) {
        return IntegerValidator.getInstance().validate(numberStr, "#,##0");
    }

    /**
     * 將double string 轉成 Double.
     * 
     * @param numberStr
     * @return
     */
    public static Double formatDoubleString(String numberStr) {
        return DoubleValidator.getInstance().validate(numberStr, "#,##0.00");
    }

    /**
     * 檢查 integer 是否介於指定區間.
     * 
     * @param number
     * @param min
     * @param max
     */
    public static void isIntegerInValidRange(Integer number, int min, int max) {
        if (!IntegerValidator.getInstance().isInRange(number, min, max)) {
            throw new IllegalArgumentException("您輸入的數字必須介於 " + min + " ~ " + max + " 之間");
        }
    }

    /**
     * 檢查 double 是否介於指定區間.
     * 
     * @param number
     * @param min
     * @param max
     */
    public static void isDoubleInValidRange(Double number, double min, double max) {
        if (!DoubleValidator.getInstance().isInRange(number, min, max)) {
            throw new IllegalArgumentException("您輸入的數字必須介於 " + min + " ~ " + max + " 之間");
        }
    }

}



Test Code    
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package albert.practice.validation;

import static org.junit.Assert.assertEquals;

import org.junit.Before;
import org.junit.Test;

public class NumericUtilsTest {

    private String integerString;
    private String invalidIntegerString;

    private String doubleString;
    private String invaliddoubleString;

    @Before
    public void setup() {
       integerString = "12345000";
       invalidIntegerString = "1000a";

       doubleString = "12400.60";
       invaliddoubleString = "12400a.60";
    }

    @Test
    public void testValidInteger() {
       NumericUtils.isValidInteger(integerString);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testInvalidInteger() {
       NumericUtils.isValidInteger(invalidIntegerString);
    }

    @Test
    public void testFormatIntegerToString() {
       String numberString = NumericUtils.formatInteger(12345600);
       assertEquals("12,345,600", numberString);
    }

    @Test
    public void testFormatNumStringToInteger() {
       Integer number = NumericUtils.formatIntegerString("12,345,600");
       assertEquals(new Integer(12345600), number);
    }

    @Test
    public void testIntegerIsInRange() {
       NumericUtils.isIntegerInValidRange(new Integer(22), 20, 30);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testIntegerIsNotInRange() {
       NumericUtils.isIntegerInValidRange(new Integer(35), 20, 30);
    }

    @Test
    public void testValidDouble() {
       NumericUtils.isValidDouble(doubleString);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testInvalidDouble() {
       NumericUtils.isValidDouble(invaliddoubleString);
    }

    @Test
    public void testFormatDouble(){
       String number = NumericUtils.formatDouble(new Double(12300));
       assertEquals("12,300.00", number);
    }
 
    @Test
    public void testFormatStringToDouble() {
       Double number = NumericUtils.formatDoubleString(doubleString);
       assertEquals(new Double(12400.60), number);
    }

    @Test
    public void testDoubleIsInRange() {
       NumericUtils.isDoubleInValidRange(new Double(20.5), 20.0, 30.0);
    }
 
    @Test(expected = IllegalArgumentException.class)
    public void testDoubleIsNotInRange() {
       NumericUtils.isDoubleInValidRange(new Double(30.5), 20.0, 30.0);
    }

}




    

2018/03/06

[commons-validator] Date-related utilies

Scenario
In real world, we may need some common functions to fulfill our requirement, such as:
1. check date string has expected format
2. convert date string to java.util.Date, and vice versa.
3. validate start and date time 


We can use commons-validator to implement this function easily.


Remember to add dependency in your pom.xml
1
2
3
4
5
 <dependency>
  <groupId>commons-validator</groupId>
  <artifactId>commons-validator</artifactId>
  <version>1.6</version>
 </dependency>

Sample code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package albert.practice.validation;

import java.util.Date;

import org.apache.commons.validator.routines.DateValidator;

public class DateUtils {

    /**
     * 檢查 date string 格式是否符合 yyyy/MM/dd 格式.
     * 
     * @param dateString
     */
     public static void validateDate(String dateString) {
         if (!DateValidator.getInstance().isValid(dateString, "yyyy/MM/dd")) {
             throw new IllegalArgumentException("Valid date format is yyyy/MM/dd");
         }
     }

    /**
     * 檢查 date string 格式是否符合 yyyy/MM/dd 格式, 若符合則回傳 java.util.Date
     * 
     * @param dateString
     *            ex. 2017/9/14
     * @return java.util.Date
     */
     public static Date convertToDate(String dateString) {
         validateDate(dateString);
         return DateValidator.getInstance().validate(dateString, "yyyy/MM/dd");
     }

    /**
     * 將 Date 轉成指定格式 (ex. yyyy/MM/dd)的 date string.
     * 
     * @param date
     *            java.util.Date
     * @param pattern
     *            ex. yyyy/MM/dd
     * @return date String
     */
     public static String convertToString(Date date, String pattern) {
         return DateValidator.getInstance().format(date, pattern);
     }

    /**
     * 時間起迄檢查.
     * 
     * @param startDate
     *            開始日期
     * @param endDate
     *            結束日期
     */
     public static void compareDays(Date startDate, Date endDate) {
         int compare = DateValidator.getInstance().compareDates(startDate, endDate, null);
         if (compare > 0) {
             throw new IllegalArgumentException("結束時間不可早於開始時間");
         }
     }
}


Test class:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package albert.practice.validation;

import static org.junit.Assert.assertEquals;

import java.util.Date;

import org.junit.Before;
import org.junit.Test;

public class DateUtilsTest {

    private String validDate = "";
    private String invalidDate = "";
    private String startDate = "";
    private String endDate = "";
    private String invalidEndDate = "";

    @Before
    public void setup() {
        validDate = "2017/09/30";
        invalidDate = "2017/09/31";

        startDate = "2017/9/14";
        endDate = "2017/9/15";
        invalidEndDate = "2017/9/11";
    }

    @Test(expected = IllegalArgumentException.class)
    public void testInvalidDate() {
        DateUtils.validateDate(invalidDate);
    }

    @Test
    public void testValidDate() {
        DateUtils.validateDate(validDate);
    }

    @Test
    public void testDateConversion() {
        Date date = DateUtils.convertToDate(validDate);
        String dateString = DateUtils.convertToString(date, "yyyy/MM/dd");
        assertEquals("2017/09/30", dateString);
    }

    @Test
    public void testValidStartAndEndDate() {
        DateUtils.compareDays(DateUtils.convertToDate(startDate), DateUtils.convertToDate(endDate));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testInvalidStartAndEndDate() {
        DateUtils.compareDays(DateUtils.convertToDate(startDate), DateUtils.convertToDate(invalidEndDate));
    }

}



2018/03/05

[Java] Apply Builder Pattern

Before (do not apply builder pattern)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package albert.practice.designPattern.builder;

import java.io.File;
import java.util.List;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

import lombok.Data;
import lombok.ToString;

@Data
@ToString
public class PlainEmailParam {
    @NotNull(message = "請提供收件者 email")
    private List<String> receiverEmails;
 
    @NotEmpty(message = "主旨不可為空")
    private String subject;
 
    @NotEmpty(message = "內文不可為空")
    private String content;
 
    private List<File> attachments;
}


After (apply builder pattern and apply validation in build method)
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package albert.practice.designPattern.builder;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

import org.apache.commons.validator.routines.EmailValidator;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;

import lombok.Getter;
import lombok.ToString;

@ToString
public class EmailParam {

    @Getter
    @NotNull(message = "請提供收件者 email")
    private List<String> receiverEmails; 

    @Getter
    @NotEmpty(message = "主旨不可為空")
    private String subject;

    @Getter
    @NotEmpty(message = "內文不可為空")
    private String content; 

    @Getter
    private List<File> attachments; 

    private EmailParam(Builder builder) {
        this.receiverEmails = builder.receiverEmails;
        this.subject = builder.subject;
        this.content = builder.content;
        this.attachments = builder.attachments;
    }

    public static class Builder {

        private List<String> receiverEmails; 
        private String subject;
        private String content;
        private List<File> attachments; 

        public Builder receivers(List<String> receiverEmails) {
            this.receiverEmails = receiverEmails;
            return this;
        } 

        public Builder subject(String subject) {
            this.subject = subject;
            return this;
        }

        public Builder content(String content) {
            this.content = content;
            return this;
        }

        public Builder attachments(List<File> attachments) {
            this.attachments = attachments;
            return this;
        }

        public EmailParam build() {
            EmailParam param = new EmailParam(this);
   
            validate(param);
            validateEmails(param);

            return param;
        }

        /**
         * Do email validation.
         * 
         * @param param
         */
        private void validateEmails(EmailParam param) {
            List<String> receivers = param.getReceiverEmails();
            if (receivers != null && receivers.size() > 0) {
                for (String email : receivers) {
                    if (!EmailValidator.getInstance().isValid(email)) {
                        throw new IllegalArgumentException("Email 格式有誤");
                    }
                }
            }
        }

        /**
          * Do parameter validation.
          * 
          * @param obj
         */
         public void validate(Object obj) {
             List<String> errors = new ArrayList<>();

             Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
             Set<ConstraintViolation<Object>> violations = validator.validate(obj);
             for (ConstraintViolation<Object> violation : violations) {
                 errors.add(violation.getMessage());
              }

             String completeErrorMsg = Joiner.on("\n").join(errors);
             if (!Strings.isNullOrEmpty(completeErrorMsg)) {
                 throw new IllegalArgumentException(completeErrorMsg);
             }
          }
  
 }

}



Test Class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package albert.practice.designPattern.builder;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;

import org.apache.commons.validator.routines.EmailValidator;
import org.junit.Test;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class EmailParamTest {
 
 @Test
 public void testPlainEmailParam() {
     PlainEmailParam param = new PlainEmailParam();
     param.setReceiverEmails(Arrays.asList("test1@test.com", "test2@test.com"));
     param.setSubject("test");
     param.setContent("just for testing");
  
     validate(param);
     validateEmails(param);
  
     log.debug(param.toString());
 }
 
 @Test
 public void testEmailParam() {
     List<String> receivers = Arrays.asList("test1@test.com", "test2@test.com");
     EmailParam param
         = new EmailParam.Builder().receivers(receivers).subject("test").content("just for testing")
           .build();
     log.debug(param.toString());
 }
 
 private void validateEmails(PlainEmailParam param) {
     List<String> receivers = param.getReceiverEmails();
     if (receivers != null && receivers.size() > 0) {
         for (String email : receivers) {
             if (!EmailValidator.getInstance().isValid(email)) {
                 throw new IllegalArgumentException("Email 格式有誤");
              }
         }
     }
 }

 private void validate(Object obj) {
     List<String> errors = new ArrayList<>();

     Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
     Set<ConstraintViolation<Object>> violations = validator.validate(obj);
     for (ConstraintViolation<Object> violation : violations) {
         errors.add(violation.getMessage());
     }

     String completeErrorMsg = Joiner.on("\n").join(errors);
     if (!Strings.isNullOrEmpty(completeErrorMsg)) {
         throw new IllegalArgumentException(completeErrorMsg);
     }
 }

}




2018/03/04

[閱讀筆記] The Little Book That Builds Wealth (2/7)

  1. 結構性的競爭優勢的四個來源包含無形資產、客戶轉換成本、網路效應與成本優勢。若你可以找到一家公司擁有穩定的資本報酬,且擁有其中一個結構性競爭優勢的特性,你很有可能就找到一家擁有護城河的公司
  2. 無形資產包含品牌、專利或法規授權販售商品與服務許可等,這些無形資產可以幫企業在市場建立獨特的地位,任何企業擁有其中一個優勢就可以享受到微獨佔 (mini-monopoly) 的好處,可以從客戶身上獲取大量的價值
  3. 當品牌 (brand) 可以增加客戶的購買意願時,此時品牌才能稱為護城河。畢竟,品牌的建立與維持是需要投入金錢的,若投入金錢無法透過價格優勢或重複購買來產生回報的話,品牌無法建立任何競爭優勢
  4. 當兩個互相競爭的品牌公司,若 A 品牌的公司無法靠其品牌索取更高的價格,此品牌可能沒什麼價值。如一樣的珠寶貼上 Tiffany 的標籤,放進 blue box,就可以賣出比較貴的價格 (expensive blue box),這就是有價值品牌的象徵
  5. 若一家公司可以比競爭對手索取更高的價格,此品牌很有可能建立了強大的護城河。例如很多藥廠都有販售 asprin (阿斯匹靈),但是只要冠上 Bayer 的品牌,就可以索取競爭對手兩倍的價格,這就是一個 powerful brand
  6. 靠品牌建立經濟護城河的最大的危險是,品牌對於消費者失去吸引力,此時此品牌就無法販售較高的價格
  7. 若你發現消費者會因為品牌而選購或願意花更多的錢來買此產品或服務,你就可以擁有強烈的證據來證明此品牌是護城河。但是有很多知名品牌還是表現得非常掙扎,無法靠品牌來賺取足夠的回報
  8. 專利 (patents)  無法讓你永遠高枕無憂,因為專利 (patents) 是會過期的、是會被競爭對手挑戰且有可能被撤銷的。公司必須要有廣泛的專利產品,且能持續創新,能夠在未來用心的專利產品來取代現有的專利產品,才能建立持久的競爭優勢,如 3M、Merck (默克集團,為全世界歷史最悠久的化工製藥公司) 或 Eli Lilly (禮來公司,是一家總部設在美國印第安納波利斯的全球性製藥公司,同時也是全世界最大的企業之一)
  9. 評估無形資產 (intangible assets) 的關鍵是,此無形資產可以替公司產生多少價值,以及可以持續多久
  10. 無論是人們多熟悉這個品牌,一個眾所皆知的品牌若無法讓公司訂定更高的價格或鎖住客戶,這個品牌就不會是競爭優勢
  11. 若你可以找到一個可以擁有訂價權力 (pricing power) 的品牌或是可限制競爭的法規許可,或是擁有多樣化的專利與扎實的創新歷史的公司,你就有很高的機率發現一間擁有護城河的公司
  12. 法規 (regulation) 可以限制競爭,最好的法規護城河是,由多條小規模的法規所組成,而不是只有一條可能會被修改的大法規
  13. B2B 之間的關係比較容易建立起昂貴的客戶轉換成本 (customer switching costs),B2C 之間的關係由於取代性高、花的時間與成本也較低,故轉換成本較低,較無法建立起轉換成本的護城河
  14. 轉換成本有好幾種形式,如與客戶的業務緊密整合、貨幣成本 (monetary costs,指顧客購買和使用產品所付出的直接成本和間接成本)、再訓練成本等等
  15. 銀行業從轉換成本 (switching costs) 中賺了很多錢
  16. 網路效應 (network effect) 是指當越多人使用,使產品與服務就越有價值,吸引越多人、增加的價值就愈多,形成一個正向循環,建立起有主宰力的網路。The more people who use these networks, the more valuable they are to others.
  17. 網路效應 (network effect) 可以讓企業建立起自然的壟斷 (monopoly) 與寡頭壟斷 (oligopolies),此效應會帶給企業極大的競爭優勢。以美國國內信用卡交易為例,Visa、MasterCard 、American Express 與 Discover 就吃下 85% 的市場
  18. 在電腦界,網路效應 (network effect) 最明顯的例子就是 Microsoft ,當越多人使用 Windows 、Word、Excel,他的價值就越高,從學校時期就開始教學使用,出社會後變成知識工作者的共同語言,即便有免費的 OpenOffice 也難以撼動 market share
  19. Network-based businesses 並不是偶然發生,網路效應 (network effect) 較常發生在資訊或知識傳遞為基礎的產業,較少發生在以實體資產為主的產業
  20. 以 eBay 為例,在美國拍賣市場的 market share 佔了 85%,訪客在 eBay 每筆交易與競爭對手相比多出 85%,買家會去 eBay 是因為賣家都在那邊,賣家會去 eBay 是因為買家都在那邊,這就是網路效應 (network effect)
  21. 公司的產品或服務,會隨著使用人數的增加,價值而跟著增加,這就是從 network effect 獲得的好處,如信用卡、線上拍賣等
  22. Network effect 是個很強大的競爭優勢,這比較常見於訊息分享或將使用者連結起來的產業,比較罕見於實體物品的產業
  23. 成本優勢 (cost advantages) 有時候是可以持久的,但也有可能快速地消失,端視公司的成本優勢是否容易被競爭者所複製,例如將低階生產線移到中國、印度或菲律賓,這不是個持久的策略,因為你的競爭者也可以把生產線移到這些國家
  24. 在全球化的經濟中,只有在價格敏感的產業採用低成本的策略,才能夠奏效
  25. 成本優勢源自四個來源:較便宜的生產流程、較佳的地點、獨特的資產與較大的規模