Total Pageviews

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. 成本優勢源自四個來源:較便宜的生產流程、較佳的地點、獨特的資產與較大的規模

2018/03/03

[閱讀筆記] Bogle On Mutual Funds: New Perspectives For The Intelligent Investor (1/9)


  1. 因為整個股票市場是由所有投資人集體擁有的,若被動型投資人 (passive investors) 可以獲得與市場相當的稅前收益 (gross return),你就可以打敗大部分的主動型投資人 (active investots)
  2. 被動型投資人 (passive investors) 的管理費用與交易成本一定比主動型投資人 (active investors) 來得低,若兩者獲得的稅前收益 (gross return) 相當,被動型投資人 (passive investors) 的投資效益一定比主動型投資人 (active investors)  來得好
  3. 對於投資人來說,對你的未來財富影響最大的因素是投資費用 (investment expenses),收益減去費用,才是你最後拿到的
  4. 指數型基金 (Index funds) 的設計是用來吸引長期投資人,不是短期殺進殺出的投機客
  5. 避開高費用比 (expense ratios) 的基金,主動型的基金費用比可能會高達 1.35%,但指數型基金 (index funds) 僅有約 0.05 ~ 0.10%
  6. 資產配置 (asset allication),就是股票 (Stock) 與債券 (Bond) 的分配比例,我建議股票與債券的比例的範圍從 70 / 30 到 60 / 40 左右,最好都是購買股票與債券的指數型基金 (stock and bond index funds)
  7. 當基金公司廣告打著新商品要販售時,特別是很夯的新商品 (hot new product),絕對不要投資它
  8. 你必須對你的金錢的運用盡可能地節約 (thrifty),盡可能地為未來儲備更多的現金。但是,也是相等重要的,你也要對你的共同基金經理人進行節約,不要理會他們推薦的高成本基金,只購買低成本的基金 (low-cost funds)
  9. 整個股票市場反應的是,世界各地所有的投資人的知識、希望、恐懼與貪婪
  10. 見日光之下,快跑的未必能贏;力戰的未必得勝;智慧的未必得糧食 (謀事在人,成事在天);明哲的未必得資財;靈巧的未必得喜悅。所臨到眾人的是在乎當時的機會。快跑、力戰、智慧、明哲、靈巧,雖然表面上比較有把握能贏、能戰勝、能得糧食、得貲財、得喜悅,但其實卻未必盡然
  11. 如何做好投資,很難有具體共識,但是根據過去的統計數字來看,可以歸納出幾個罕見的共識
    1. 分散投資 (diversification) 只能降低風險,不能完全剔除風險
    2. 不要將你未來的退休金、買房的預備金或是就學基金拿去投資
    3. 最有效的分散投資的方式就是購買低費用的指數型基金 (low-fee index fund)。根據過去的數據統計,指數型基金的表現會比主動型基金來得好
    4. 只購買低成本 (low-fee)、無銷售費用 (no-load) 的投資標的
  12. 單一選股的投資行為,不適合一般的投資人,一般投資人應該屬於廣泛的、多樣化的投資計劃,而共同基金 (mutual fund) 則是達成分散投資 (diversification)目的的最有效的方法
  13. 資產配置除了考量到『風險』(risk)『報酬』(return) 以外,別忘了還有成本』(cost)
  14. 以共同基金 (mutual funds) 來說,成本包含銷售費用 (sales charges)、operating expenses (運作費用)、顧問費用 (advisory fees) 及年度費用 (annual costs) 等,這些成總加總起來可能會佔 0.3% 到 3% (甚至更高),差距很大。當你購買的共同基金的內扣成本越高,績效就會越差
  15. 當你在計算你的投資效益時,別忘了要扣除該繳的稅金、年度通貨膨脹率以及要給基金公司的費用,扣除後,才是你真的得到的獲利
  16. 對於股市 (stocks) 來說,營收 (earnings) 與股利 (dividens) 會驅動你的回報 (return);債券 (bonds) 與貨幣市場工具 (money market instruments) 則是由利率決定你的回報 (return)。這些都是市場的驅動力,不是什麼神秘的力量,也不是什麼膚淺的情緒力量,如樂觀與悲觀、希望與恐懼、貪婪與滿足
  17. 了解財務的歷史非常重要,歷史可以提供我們有用的觀點。但是歷史猶如掛在船尾的提燈,只能看到過去,無法看清未來。對於歷史要給予應有的尊重,不必太多,也不應太少
  18. 三個影響股票獲利的主要元素
    1. 起始的股息殖利率 (initial dividend yield):即股息 ÷ 股價
    2. 股息的成長 (growth in dividends)
    3. 本利比的變化 (change in price-dividend multiple):本利比 = 股價 ÷ 股利。為獲利率的倒數,是目前每股股票的市價為每股股利的倍數,藉以分析相對於股利而言,股票價格是否被高估以及有無投資價值。以投資的角度來看,本利比越低,投資者對股票的預期收益越高。為什麼我會建議用本利比而非本益比呢?因為本益比的計算是根據不精確的營收數字,本利比則是根據明確的股息分配
  19. 本益比 (price-earning ratio)的波動,其實是跟著投資人的情緒走,與基本面無關
  20. 對長期投資人來說,最重要的就是公司所配發的股利,已購買股票後,接下來的殖利率是否持續成長
  21. 影響債券 (bonds) 收益的三個主要因素有:
    1. 初始的收益率 (initial yield):發行者同意付給投資人的利率,票面利息率 (interest coupon) 是在債券發行時決定,而且通常不會改變。這是影響長期債券收益的最重要的決定因素
    2. 再投資率 (reinvestment rate):債券價格愈高、殖利率愈低,因為在債券價格上漲後,投資人必須要用較高的價格買入,但所領到的配息、與到期日時所得報酬卻沒有比較高;債券價格愈低、殖利率愈高,因為債券價格在下跌後,投資人可以較低的價錢,買到相同的債券。因此,殖利率與債券價格呈反向關係
    3. 利率變化的衝擊 (impact of rate change):利率漲、債券跌;利率跌、債券漲
  22. 財務資產通常都有回歸均值 (regression to the means) 的傾向
    1. 普通股 (common stock) 的績效最終會回歸到長期歷史資料的平均值。因為股票的收益絕大部分是由『殖利率』與『股利』的成長所決定,而這兩個因素的增長,則是基於日益激烈的競爭環境下所掙得的『營收成長』所決定,營收成長是有其極限的
    2. 債券 (bond) 的績效由『利率』所決定,無論是長期、中期或是長期的債券,你無法預測利率的未來走向,利率走向會讓你的績效回歸均值
  23. 複利是很驚人的,時間是你最好的朋友。投資就要從今天開始,不要一直明天再說,你越晚開始,就會追趕得越辛苦
  24. 在投資時,要先問問自己準備好冒哪種風險?風險主要分成兩種:
    1. 無論是哪種投資類別,都有無可避免的通膨 (inflation) 風險。依據過往的歷史表現,通膨攀升及股市上漲之間的連動性呈現交錯不一的結果。雖然,兩者之間似乎應該存在關聯性,因為通膨象徵經濟情勢改善,而股票市場總是歡迎經濟增長的。但通膨對食品包裝或汽車製造商等企業來說,則會因為必須投入的成本增加,而產生反作用力。
    2. 本金 (principal) 與收入 (income) 的損失風險,這則是你可以控制的部分
  25. 實質收益 (real return) = 名目收益 (nominal return) ─ 通膨率 (inflation rate)