2015-03-26 14:39

Eclipse Git 基本操作

Git 只有本地端(local)跟遠端(remote)的分別,並不是 client 跟 server 的關係,只要是非專案本身的都可以稱為遠端,例如 A 與 B 專案都在自己的電腦裡,A 專案是可以將 B 專案設為遠端進行版本傳遞。

幾個基本日常操作的名稱意義:
  • commit: 將完成的程式提交到版本控制中
  • revert: 放棄修改,將程式回復
  • merge: 將兩個不同版本的程式進行合併
  • pull: 從遠端取回有差異的程式版本
  • push: 將本地端的版本傳到遠端
  • clone: 從遠端複製一個專案到本地



在專案建立 Git 版本控制


在一個專案上滑鼠右鍵選擇 [團隊]->[共享專案]



先建立 Git 儲存庫





將專案中的異動 commit 到 Git 版本控制


隨便建立幾個文件


在專案上右鍵選擇 [團隊]->[確定]


填寫 commit 的訊息,以及選擇要 commit 的檔案




程式回復到尚未修改時


在要回復的檔案右鍵選擇 [取代為]->[HEAD Revision]




版本合併


在專案上右鍵選擇 [團隊]->[Pull] 從遠端取回有差異的程式



當檔案出現衝突時會顯示紅色的標記,這時候檔案右鍵選擇 [團隊]->[Merge Tool]



接著就會出現合併的工具,修改完儲存關閉


最後檔案右鍵選擇 [團隊]->[Add to Index] 將檔案加到 commit 清單中




將本地端的版本傳到遠端






從遠端複製一個專案






指定來源的遠端


目的地目錄最好修改到 Workspace 中




2015-03-13 13:12

[Java] Ant zip 解壓縮筆記

<unzip dest="./target_dir">

    <!-- 來源的壓縮檔 -->
    <fileset dir="lib">
        <include name="tiles-jsp-*.jar"/>
    </fileset>

    <!-- 要解出的檔案 -->
    <patternset>
        <include name="**/*.tld"/>
    </patternset>

    <!-- 解出路徑的轉換 -->
    <mapper type="flatten"/>

</unzip>

參考文件:
Apache Ant™ User Manual : Unzip Task
Apache Ant™ User Manual : Mapper
2015-03-13 11:59

[Java] Reflection 筆記

@SuppressWarnings("unused")
Object obj = new Object() {
    String id = "123";
    public String name = "Jax";
};

Class<?> cl = obj.getClass();

for (Field field : cl.getFields()) {
    System.out.printf("%s = %s {%s}\n",
        field.getName(), field.get(obj), field.getType());
}

System.out.println("=======================");

for (Field field : cl.getDeclaredFields()) {
    System.out.printf("%s = %s {%s}\n",
        field.getName(), field.get(obj), field.getType());
}

Output:
name = Jax {class java.lang.String}
=======================
id = 123 {class java.lang.String}
name = Jax {class java.lang.String}
2015-03-10 15:18

[Java] 產生驗證碼圖片

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Random;

import javax.imageio.ImageIO;

public class TestCaptchaImage {

    public static void main(String[] args) throws Exception {
        String captcha = "09875";

        int width = 55;
        int height = 20;
        Color fontColor = new Color(36, 85, 92); /*文字顏色*/
        Color lineColor = new Color(65, 161, 175); /*線條顏色*/
        Color bgColor = new Color(208, 226, 229); /*底色*/

        Random random = new Random();
        random.setSeed(System.currentTimeMillis());

        BufferedImage image = new BufferedImage(
            width, height, BufferedImage.TYPE_INT_RGB
        );
        Graphics g = image.getGraphics();

        /* 底色 */
        g.setColor(bgColor);
        g.fillRect(0, 0, width, height);

        /* 畫線 */
        g.setColor(lineColor);
        for (int i=0; i < 20; i++) {
            g.drawLine(
                random.nextInt(width), 0,
                random.nextInt(width), height
            );
        }

        /* 畫出文字 */
        g.setFont(new Font("Atlantic Inline", Font.BOLD, 20));
        g.setColor(fontColor);
        g.drawString(captcha, 0, 18);

        /* 畫線 */
        g.setColor(lineColor);
        for (int i=0; i < 4; i++) {
            g.drawLine(
                0, random.nextInt(height),
                width, random.nextInt(height)
            );
        }

        g.dispose();

        ImageIO.write(image, "png", new File("captcha_image.png"));
    }
}

參考文件:
Graphics (Java 2 Platform SE 6)
2015-03-06 17:12

[Java] 製作縮圖筆記

package test_image;

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;

public class TestImageResize {

    private static int targetWidth = 120;
    private static int targetHeight = 80;
    private static double targetRate = (double) targetWidth / targetHeight;

    public static void main(String[] args) throws Exception {

        System.out.printf("Target w:%s, h:%s, r:%s\n", 
            targetWidth, targetHeight, targetRate);


        BufferedImage image = ImageIO.read(new File("input.jpg"));

        int type = image.getType();
        if(type == 0) { type = BufferedImage.TYPE_INT_ARGB; }

        int width = image.getWidth();
        int height = image.getHeight();
        double rate = (double) width / height;

        System.out.printf("Source w:%s, h:%s, r:%s\n", width, height, rate);



        /* 等比例縮小至指定大小內 */
        int rWidth = targetWidth;
        int rHeight = targetHeight;

        if(width < targetWidth && height < targetHeight) {
            rWidth = width;
            rHeight = height;
        } else if(rate > targetRate) {
            rHeight = (int) (targetWidth / rate);
        } else {
            rWidth = (int) (targetHeight * rate);
        }
        System.out.printf("Resize w:%s, h:%s\n", rWidth, rHeight);

        BufferedImage resize1 = new BufferedImage(rWidth, rHeight, type);
        Graphics g1 = resize1.getGraphics();
        g1.drawImage(image, 0, 0, rWidth, rHeight, null);
        g1.dispose();

        ImageIO.write(resize1, "jpg", new File("output_1.jpg"));




        /* 等比例縮小填滿指定大小 */
        BufferedImage resize2 = new BufferedImage(targetWidth,targetHeight,type);
        Graphics g2 = resize2.getGraphics();

        int startX = 0;
        int startY = 0;
        int size = 0;

        if(rate > targetRate) {
            startX = (int) (width - height * targetRate) / 2;
            size = height;
        } else {
            startY = (int) (height - width / targetRate) / 2;
            size = width;
        }
        System.out.printf("x:%s, y:%s, size:%s\n", startX, startY, size);

        g2.drawImage(
            image,
            0, 0, targetWidth, targetHeight,
            startX, startY, (size + startX), (size + startY),
            null
        );

        g2.dispose();

        ImageIO.write(resize2, "jpg", new File("output_2.jpg"));
    }
}

參考文件:
Graphics (Java 2 Platform SE 6)
2015-03-06 15:33

[Java] Jsoup 筆記

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class TestJsoup {

    public static void main(String[] args) throws Exception {

        String url = "http://epg.dishstar.net/calendar.php?s=DISC&d=1";

        // Document doc = Jsoup.parse(new URL(url), 5000);
        Document doc = Jsoup.connect(url)
                            .userAgent("Mozilla/5.0")
                            .timeout(5000).get();

        Elements trs = doc.select("table tr");
        for (Element tr : trs) {
            String time = tr.select("td:eq(0)").text();
            String title = tr.select("td:eq(1)").text();

            System.out.println(time + " <=> " + title);
        }
    }
}
2015-03-06 11:57

Spring JavaMail 筆記

Gmail via SSL
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="host" value="smtp.gmail.com" />
    <property name="port" value="465" />
    <property name="username" value="smtp-user" />
    <property name="password" value="smtp-passwd" />

    <property name="javaMailProperties">
        <props>
            <prop key="mail.smtp.socketFactory.class">javax.net.ssl.SSLSocketFactory</prop>
            <prop key="mail.smtp.auth">true</prop>
        </props>
    </property>
</bean>


Gmail via TLS
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="host" value="smtp.gmail.com" />
    <property name="port" value="587" />
    <property name="username" value="smtp-user" />
    <property name="password" value="smtp-passwd" />
    <property name="javaMailProperties">
        <props>
            <prop key="mail.smtp.starttls.enable">true</prop>
            <prop key="mail.smtp.auth">true</prop>
        </props>
    </property>
</bean>


Sample Code
package test_mail;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;

public class TestSpringMail {

    public static void main( String[] args ) throws Exception {

        AbstractApplicationContext context
            = new ClassPathXmlApplicationContext("test_mail/spring-mail.xml");

        JavaMailSender mailSender 
            = (JavaMailSender) context.getBean("mailSender");

        sample1(mailSender);
        sample2(mailSender);
        sample3(mailSender);

        context.close();
    }


    public static void sample1(JavaMailSender mailSender) throws Exception {
        MimeMessage mimeMessage = mailSender.createMimeMessage();

        mimeMessage.setFrom("from@no-spam.com");
        mimeMessage.setRecipients(
            Message.RecipientType.TO, "to@no-spam.com"
        );
        mimeMessage.setSubject("Testing Subject");
        mimeMessage.setContent(
            "<b>Testing Content.</b>", 
            "text/html; charset=utf-8"
        );

        mailSender.send(mimeMessage);
    }


    public static void sample2(JavaMailSender mailSender) throws Exception {
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper message = new MimeMessageHelper(mimeMessage, "utf-8");

        message.setFrom("from@no-spam.com");
        message.setTo("to@no-spam.com");
        message.setSubject("Testing Subject");
        message.setText("<b>Testing Content.</b>", true);

        mailSender.send(mimeMessage);
    }


    public static void sample3(JavaMailSender mailSender) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom("from@no-spam.com");
        message.setTo("to@no-spam.com");
        message.setSubject("Testing Subject");
        message.setText("Testing Content.");

        mailSender.send(message);
    }

}


參考自:
JavaMail API – Sending email via Gmail SMTP example : Mkyong
Spring – Sending e-mail with attachment : Mkyong
Spring – Define an E-mail template in bean configuration file : Mkyong
Spring – Sending E-mail via Gmail SMTP server with MailSender : Mkyong
2015-03-05 10:24

用 Spring 取得 JSP 執行後的字串內容

為了取得執行後的字串內容,我們需要建立 Response 並且替換 PrintWriter,這樣才有辦法取得執行後的內容。

接著可以從 Spring 的 ViewResolver 去取得 View,再透過 View 去處理 render 前的包裝,最後才由 dispatcher 真正去處理 render 的動作。

想到要建立 Request 跟 Response 就感覺讓人頭痛,還好 Spring 有提供 Mock 的類別可以簡單地去建立 Request 跟 Response。


JspRenderer

package com.orion.webmvc.util;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.ui.Model;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;


public class JspRenderer {

    @Autowired
    private ServletContext servletContext;


    private ViewResolver viewResolver;

    public void setViewResolver(ViewResolver viewResolver) {
        this.viewResolver = viewResolver;
    }


    public String render(String viewName, Model model) 
        throws IOException 
    {
        return render(viewName, model.asMap());
    }

    public String render(String viewName, Map<String,Object> modelMap) 
        throws IOException 
    {

        RendererRequest request = new RendererRequest(servletContext);
        RendererResponse response = new RendererResponse();

        try {
            /* 透過 ViewResolver 取得 View 進行 render 的動作 */
            View view = viewResolver.resolveViewName(
                viewName, Locale.getDefault()
            );
            view.render(modelMap, request, response);

            return response.getContentAsString();
        }
        catch(Exception e) {
            throw new IOException(e);
        }
    }
}


class RendererRequest extends MockHttpServletRequest {

    private ServletContext servletContext;

    public RendererRequest(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    @Override
    public RequestDispatcher getRequestDispatcher(String path) {
        /* 需要透過真實的 RequestDispatcher 進行 Render */
        return servletContext.getRequestDispatcher(path);
    }
}


class RendererResponse extends MockHttpServletResponse {

    private StringWriter writer = new StringWriter();

    @Override
    public PrintWriter getWriter() throws UnsupportedEncodingException {
        /* 用 StringWriter 作為輸出的容器 */
        return new PrintWriter(writer);
    }

    @Override
    public boolean isCommitted() {
        /* true 是為了讓 View 可以採用 include 方式 Render 到 Response */
        return true;
    }

    @Override
    public String getContentAsString() throws UnsupportedEncodingException {
        /* 取得 Render 後的內容 */
        return writer.getBuffer().toString();
    }
}


配置 spring.xml

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".jsp" />
</bean>

<bean id="jspRenderer" class="com.orion.webmvc.util.JspRenderer">
    <property name="viewResolver" ref="viewResolver"/>
</bean>


使用範例

//@Autowired
//private JspRenderer jspRenderer;

Map<String,Object> jspMap = new HashMap<String,Object>();
jspMap.put("jspMessage", "中文訊息測試");
jspMap.put("costMessage", 4567.89);

String jspOutput = jspRenderer.render("/mailer/test", jspMap);
System.out.println(jspOutput);


參考自:Render and capture the output of a JSP as a String | Technological Oddity
2015-03-04 11:33

[Java] FileFilter 筆記

File dir = new File("D:/log");

File[] list = dir.listFiles(new FileFilter(){
    public boolean accept(File file) {
        return file.getName().endsWith(".txt");
    }
});

for(File file : list){
    System.out.println(file.getAbsoluteFile());
}

// 004.txt
2015-03-04 11:27

[Java] 取得本地端 IP 與 MAC

import java.net.InetAddress;
import java.net.NetworkInterface;

public class TestInetAddress {

    public static void main(String[] args) throws Exception {
        InetAddress ip = InetAddress.getLocalHost();

        System.out.println("Current IP address : " + ip.getHostAddress());
        // Current IP address : 192.168.0.109

        System.out.println(ip.getCanonicalHostName());
        // 192.168.0.109

        System.out.println(ip.getHostName());
        // jaxhu-PC



        NetworkInterface network = NetworkInterface.getByInetAddress(ip);

        byte[] mac = network.getHardwareAddress();

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < mac.length; i++) {
            sb.append(String.format("%s%02X", (i > 0 ? "-" : ""), mac[i]));
        }

        System.out.println("Current MAC address : " + sb.toString());
        // Current MAC address : 38-2C-4A-B4-C3-24

        System.out.println(network.getDisplayName());
        // Realtek PCIe GBE Family Controller

        System.out.println(network.getName());
        // eth3
    }
}

參考自:How to get MAC address in Java : Mkyong
2015-03-04 11:23

[Java] Object Serializable 筆記

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class Address implements Serializable {

    private static final long serialVersionUID = 1L;

    String street;
    String country;

    public Address() {}

    public Address(String s, String c) {
        street = s; country = c;
    }

    public void setStreet(String street){ this.street = street; }
    public String getStreet(){ return this.street; }

    public void setCountry(String country){ this.country = country; }
    public String getCountry(){ return this.country; }

    @Override
    public String toString() {
        return String.format("Street : %s Country : %s", street, country);
    }
}


public class TestSerializable {

    public static void main(String[] args) throws Exception {
        Address addr = new Address("wall street", "united state");

        FileOutputStream fout = new FileOutputStream("address.ser");
        ObjectOutputStream oos = new ObjectOutputStream(fout);
        oos.writeObject(addr);
        oos.close();

        FileInputStream fin = new FileInputStream("address.ser");
        ObjectInputStream ois = new ObjectInputStream(fin);
        Address addr2 = (Address) ois.readObject();
        ois.close();

        System.out.println(addr2);
        // Street : wall street Country : united state
    }
}

參考自:
How to read an Object from file in Java : Mkyong
How to write an Object to file in Java : Mkyong
Understand the serialVersionUID : Mkyong
2015-03-04 11:20

[Java] 執行 shell command

Process p = Runtime.getRuntime().exec("ping -n 3 google.com");
p.waitFor();

Scanner sc = new Scanner(p.getInputStream(), "MS950");
String output = sc.useDelimiter("\\Z").next();
sc.close();

System.out.println("exitValue: " + p.exitValue());
System.out.println(output);

Output:
exitValue: 0

Ping google.com [173.194.72.138] (使用 32 位元組的資料):
回覆自 173.194.72.138: 位元組=32 時間=21ms TTL=48
回覆自 173.194.72.138: 位元組=32 時間=20ms TTL=48
回覆自 173.194.72.138: 位元組=32 時間=18ms TTL=48

173.194.72.138 的 Ping 統計資料:
    封包: 已傳送 = 3,已收到 = 3, 已遺失 = 0 (0% 遺失),
大約的來回時間 (毫秒):
    最小值 = 18ms,最大值 = 21ms,平均 = 19ms
2015-03-03 16:40

[Java] 自定字符集 Charset

網路上找到有關自定 Charset 的資料很少,大部分的人都是不建議自行定義一個 Charset,的確沒有什麼特別的理由,實在不需要去做這樣的事,但實際遇到的是工作上用的 DB 實在太舊,還在用 Big5 編碼,以及一堆的自造字,偏偏大部分都還是人名,讓別人的名子變成奇怪的字,實在很不禮貌,基於這個理由我延伸了 Big5 Charset。

要製作一個 Charset 一定要瞭解 Java 的字串核心,如果之前是寫 C 語言的朋友,首先要認知 char 跟 btye 是不一樣的,在 Java 的 char 是固定 Unicode 編碼,所有輸入都會轉成 Unicode,輸出時在轉成指定編碼,如下圖:



那麼要實做哪些東西呢??
class Charset
定義 Charset 的名稱跟提供 Encoder 跟 Decoder,跟據 IANA 的規範自定的 Charset 名稱必須以 X-x- 開頭。
class CharsetDecoder
字符解碼器負責將 byte[] 轉換成 char[]
class CharsetEncoder
字符編碼器負責將 char[] 轉換成 byte[]
class CharsetProvider
Charset 提供者,以名稱提供 Charset,在 Charset.forName("xxx") 調用時會尋訪所有的 Provider 來取得 Charset。
META-INF/services/java.nio.charset.spi.CharsetProvider
告知 JVM 將 CharsetProvider 註冊到延伸清單中,JVM 在啟動的時候會掃描所有這個路徑的檔案。

最後再將 {Charset}.jar 檔複製到 {jre}/lib/ext 目錄下就部署完畢


Big5_Extend

public class Big5_Extend extends Charset {

    private static final String BASE_CHARSET = "Big5";
    private static final String NAME = "X-Big5-Extend";
    private static final String[] ALIASES = { "X-Big5_Extend" };
    private Charset baseCharset;

    public Big5_Extend() {
        this(NAME, ALIASES);
    }

    public Big5_Extend(String canonical, String[] aliases) {
        super(canonical, aliases);
        baseCharset = Charset.forName(BASE_CHARSET);
    }

    public boolean contains(Charset cs) {
        return this.getClass().isInstance(cs) ||
                baseCharset.getClass().isInstance(cs);
    }

    public CharsetDecoder newDecoder() {
        return new Decoder(this, baseCharset.newDecoder());
    }

    public CharsetEncoder newEncoder() {
        return new Encoder(this, baseCharset.newEncoder());
    }

    // ...
}
繼承自 Charset,我們只要實作名稱定義跟 Encoder / Decoder 兩件事,當然如果有 char mapping array 也是在這裡初始化,讓所有 Encoder 跟 Decoder 可以共用同一個記憶體空間。


解碼處理

protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
    base.reset(); /* 重置狀態 */

    /* 先用原生的 Big5 進行解碼 */
    CoderResult result = base.decode(in, out, true);
    if(!result.isUnmappable() || in.remaining() < 2){ return result; }


    /* 無法轉換,進一步使用自訂的解碼 */
    int pos = in.position();
    char big5Char = (char)(in.get(pos) << 8 | in.get(pos + 1));
    char outChar;

    switch (big5Char) {
        case '\uFA40': outChar = '\u5803'; break; /* 堃 */
        case '\uFA41': outChar = '\u83D3'; break; /* 菓 */
        case '\uFA42': outChar = '\u854B'; break; /* 蕋 */
        case '\uFA43': outChar = '\u4F8A'; break; /* 侊 */
        default: return result; /* 不在清單內直接回傳 */
    }
    out.put(outChar);

    in.position(pos + 2);
    return decodeLoop(in, out); /* 遞迴處理*/
}
解碼的部分就是先呼叫 big5 原本的 decode,當發生無法解碼的四種狀況就會停止解碼,回傳解碼狀態,我們只要針對 isUnmappable 的狀態接著處裡,base.reset() 是為了清除 Decoder 內部的狀態紀錄,不然會被前一次的解碼結果所影響。


編碼處理

protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
    base.reset(); /* 重置狀態 */

    /* 先用原生的 Big5 進行編碼 */
    CoderResult result = base.encode(in, out, true);
    if(!result.isUnmappable() || out.remaining() < 2){ return result; }


    /* 無法轉換,進一步使用自訂的編碼 */
    int pos = in.position();
    char uniChar = in.get(pos);
    char outChar;

    switch (uniChar) {
        case '\u5803': outChar = '\uFA40'; break; /* 堃 */
        case '\u83D3': outChar = '\uFA41'; break; /* 菓 */
        case '\u854B': outChar = '\uFA42'; break; /* 蕋 */
        case '\u4F8A': outChar ='\uFA43'; break; /* 侊 */
        default: return result; /* 不在清單內直接回傳 */
    }
    out.put((byte)(outChar >> 8));
    out.put((byte)outChar);

    in.position(pos + 1);
    return encodeLoop(in, out); /* 遞迴處理*/
}
編碼的部分跟解碼採用相同的方式,一樣是先呼叫 big5 原本的 encode。


CoderResult 四種狀態

  • UNDERFLOW 欠位
  • OVERFLOW 溢位
  • MALFORMED 有缺陷的輸入
  • UNMAPPABLE 無映射字符


完整的 Big5_Extend

package com.custom.nio.charset;

import java.nio.CharBuffer;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;


public class Big5_Extend extends Charset {

    private static final String BASE_CHARSET = "Big5";
    private static final String NAME = "X-Big5-Extend";
    private static final String[] ALIASES = { "X-Big5_Extend" };
    private Charset baseCharset;

    public Big5_Extend() {
        this(NAME, ALIASES);
    }

    public Big5_Extend(String canonical, String[] aliases) {
        super(canonical, aliases);
        baseCharset = Charset.forName(BASE_CHARSET);
    }

    public boolean contains(Charset cs) {
        return this.getClass().isInstance(cs) ||
                baseCharset.getClass().isInstance(cs);
    }

    public CharsetDecoder newDecoder() {
        return new Decoder(this, baseCharset.newDecoder());
    }

    public CharsetEncoder newEncoder() {
        return new Encoder(this, baseCharset.newEncoder());
    }



    private class Decoder extends CharsetDecoder {
        /* Java 原生的 Big5 解碼器 */
        private final CharsetDecoder base;

        Decoder(Charset cs, CharsetDecoder base) {
            super(cs, base.averageCharsPerByte(), base.maxCharsPerByte());
            this.base = base;
        }

        @Override
        protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
            base.reset(); /* 重置狀態 */

            /* 先用原生的 Big5 進行解碼 */
            CoderResult result = base.decode(in, out, true);
            if(!result.isUnmappable() || in.remaining() < 2){ return result; }


            /* 無法轉換,進一步使用自訂的解碼 */
            int pos = in.position();
            char big5Char = (char)(in.get(pos) << 8 | in.get(pos + 1));
            char outChar;

            switch (big5Char) {
                case '\uFA40': outChar = '\u5803'; break; /* 堃 */
                case '\uFA41': outChar = '\u83D3'; break; /* 菓 */
                case '\uFA42': outChar = '\u854B'; break; /* 蕋 */
                case '\uFA43': outChar = '\u4F8A'; break; /* 侊 */
                default: return result; /* 不在清單內直接回傳 */
            }

            out.put(outChar);

            in.position(pos + 2);
            return decodeLoop(in, out);
        }
    }



    private class Encoder extends CharsetEncoder {
        /* Java 原生的 Big5 編碼器 */
        private final CharsetEncoder base;

        Encoder(Charset cs, CharsetEncoder base) {
            super(cs, base.averageBytesPerChar(), base.maxBytesPerChar());
            this.base = base;
        }

        @Override
        protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
            base.reset(); /* 重置狀態 */

            /* 先用原生的 Big5 進行編碼 */
            CoderResult result = base.encode(in, out, true);
            if(!result.isUnmappable() || out.remaining() < 2){ return result; }


            /* 無法轉換,進一步使用自訂的編碼 */
            int pos = in.position();
            char uniChar = in.get(pos);
            char outChar;

            switch (uniChar) {
                case '\u5803': outChar = '\uFA40'; break; /* 堃 */
                case '\u83D3': outChar = '\uFA41'; break; /* 菓 */
                case '\u854B': outChar = '\uFA42'; break; /* 蕋 */
                case '\u4F8A': outChar ='\uFA43'; break; /* 侊 */
                default: return result; /* 不在清單內直接回傳 */
            }

            out.put((byte)(outChar >> 8));
            out.put((byte)outChar);

            in.position(pos + 1);
            return encodeLoop(in, out);
        }
    }
}


CharsetProvider

package com.custom.nio.charset;

import java.nio.charset.Charset;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;

/** 字元編碼器連結器,用來向 JVM 提交自訂的編碼器
 */
public class CharsetProvider extends java.nio.charset.spi.CharsetProvider {

    static Map<String, Charset> name2charset;
    static Collection<Charset> charsets;

    public Charset charsetForName(String charsetName) {
        if (charsets == null){ init(); }
        return name2charset.get(charsetName.toLowerCase());
    }

    public Iterator<Charset> charsets() {
        if (charsets == null){ init(); }
        return charsets.iterator();
    }

    void init() {
        name2charset = new HashMap<String, Charset>();

        charsets = new HashSet<Charset>();
        charsets.add(new Big5_Extend());

        for (Charset charset : charsets) {
            name2charset.put(charset.name().toLowerCase(), charset);
            for (String aliase: charset.aliases()) {
                name2charset.put(aliase.toLowerCase(), charset);
            }
        }
    }
}


java.nio.charset.spi.CharsetProvider

com.custom.nio.charset.CharsetProvider
內容就一行類別定義


測試

public class Test {

    public static void main(String[] args) throws Throwable {
        String charset = "X-Big5-Extend";
        String source = "堃菓蕋侊";

        byte[] bytes = source.getBytes(charset);
        for (byte b : bytes) {
            System.out.printf("%x ", b);
        }
        System.out.println("\n");
        // fa 40 fa 41 fa 42 fa 43

        String result = new String(bytes, charset);
        System.out.println(result);
        // 堃菓蕋侊
    }
}

參考自:Java字符编码解码的实现详解_java_脚本之家
2015-03-03 13:34

[轉載] Spring Collections (List, Set, Map, and Properties)

轉載自:Spring Collections (List, Set, Map, and Properties) example

Spring examples to show you how to inject values into collections type (List, Set, Map, and Properties). 4 major collection types are supported :
  • List – <list/>
  • Set – <set/>
  • Map – <map/>
  • Properties – <props/>


Spring beans

A Customer object, with four collection properties.
package com.mkyong.common;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class Customer
{
    private List<Object> lists;
    private Set<Object> sets;
    private Map<Object, Object> maps;
    private Properties pros;

    //...
}
See different code snippets to declare collection in bean configuration file.


1. List example

<property name="lists">
    <list>
        <value>1</value>
        <ref bean="PersonBean" />
        <bean class="com.mkyong.common.Person">
            <property name="name" value="mkyongList" />
            <property name="address" value="address" />
            <property name="age" value="28" />
        </bean>
    </list>
</property>


2. Set example

<property name="sets">
    <set>
        <value>1</value>
        <ref bean="PersonBean" />
        <bean class="com.mkyong.common.Person">
            <property name="name" value="mkyongSet" />
            <property name="address" value="address" />
            <property name="age" value="28" />
        </bean>
    </set>
</property>


3. Map example

<property name="maps">
    <map>
        <entry key="Key 1" value="1" />
        <entry key="Key 2" value-ref="PersonBean" />
        <entry key="Key 3">
            <bean class="com.mkyong.common.Person">
                <property name="name" value="mkyongMap" />
                <property name="address" value="address" />
                <property name="age" value="28" />
            </bean>
        </entry>
    </map>
</property>


4. Properties example

<property name="pros">
    <props>
        <prop key="admin">admin@nospam.com</prop>
        <prop key="support">support@nospam.com</prop>
    </props>
</property>


Full Spring’s bean configuration file.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="CustomerBean" class="com.mkyong.common.Customer">

        <!-- java.util.List -->
        <property name="lists">
            <list>
                <value>1</value>
                <ref bean="PersonBean" />
                <bean class="com.mkyong.common.Person">
                    <property name="name" value="mkyongList" />
                    <property name="address" value="address" />
                    <property name="age" value="28" />
                </bean>
            </list>
        </property>

        <!-- java.util.Set -->
        <property name="sets">
            <set>
                <value>1</value>
                <ref bean="PersonBean" />
                <bean class="com.mkyong.common.Person">
                    <property name="name" value="mkyongSet" />
                    <property name="address" value="address" />
                    <property name="age" value="28" />
                </bean>
            </set>
        </property>

        <!-- java.util.Map -->
        <property name="maps">
            <map>
                <entry key="Key 1" value="1" />
                <entry key="Key 2" value-ref="PersonBean" />
                <entry key="Key 3">
                    <bean class="com.mkyong.common.Person">
                        <property name="name" value="mkyongMap" />
                        <property name="address" value="address" />
                        <property name="age" value="28" />
                    </bean>
                </entry>
            </map>
        </property>

        <!-- java.util.Properties -->
        <property name="pros">
            <props>
                <prop key="admin">admin@nospam.com</prop>
                <prop key="support">support@nospam.com</prop>
            </props>
        </property>

    </bean>

    <bean id="PersonBean" class="com.mkyong.common.Person">
        <property name="name" value="mkyong1" />
        <property name="address" value="address 1" />
        <property name="age" value="28" />
    </bean>
</beans>

Run it…
package com.mkyong.common;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {

    public static void main( String[] args ) {

        ApplicationContext context 
            = new ClassPathXmlApplicationContext("SpringBeans.xml");

        Customer cust = (Customer)context.getBean("CustomerBean");
        System.out.println(cust);
    }
}

Output
Customer [
    lists=[
        1,
        Person [address=address 1, age=28, name=mkyong1],
        Person [address=address, age=28, name=mkyongList]
    ],

    maps={
        key 1=1,
        key 2=Person [address=address 1, age=28, name=mkyong1],
        key 3=Person [address=address, age=28, name=mkyongMap]
    },

    pros={
        admin=admin@nospam.com,
        support=support@nospam.com
    },

    sets=[
        1,
        Person [address=address 1, age=28, name=mkyong1],
        Person [address=address, age=28, name=mkyongSet]
    ]
]
2015-03-01 20:14

[Java] Jackson Json Parser 筆記

Object Encode / Decode
import java.util.Arrays;
import java.util.Date;

import com.fasterxml.jackson.databind.ObjectMapper;

class Album {
    private int id;
    private String title;
    private Date date;
    private String[] list;

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }

    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }

    public Date getDate() { return date; }
    public void setDate(Date date) { this.date = date; }

    public String[] getList() { return list; }
    public void setList(String[] list) { this.list = list; }

    @Override
    public String toString() {
        return String.format("id: %s, title: %s, date: %s, list: %s",
            id, title, date, Arrays.toString(list)
        );
    }
}

public class TestJackson {

    public static void main(String[] args) throws Exception {
        Album album = new Album();
        album.setId(1);
        album.setTitle("Go Go Go!");;
        album.setDate(new Date());
        album.setList(new String[]{"Love", "Despair"});

        ObjectMapper jsonMapper = new ObjectMapper();

        String json = jsonMapper.writeValueAsString(album);
        System.out.println(json);
        // {"id":1,"title":"Go Go Go!","date":1425211903948,"list":["Love","Despair"]}

        Album album2 = jsonMapper.readValue(json, Album.class);
        System.out.println(album2);
        // id: 1, title: Go Go Go!, date: Sun Mar 01 20:11:43 CST 2015, list: [Love, Despair]
    }
}


Parser to Map
ObjectMapper jsonMapper = new ObjectMapper();
Map<String,String> map;

map = jsonMapper.readValue(
    "{\"name\":\"jax\", \"age\":\"31\"}",
    new TypeReference<HashMap<String,String>>(){}
);
System.out.println(map);
// {age=31, name=jax}


jsonMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
jsonMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

map = jsonMapper.readValue(
    "{name:'jax', age:'31'}",
    new TypeReference<HashMap<String,String>>(){}
);
System.out.println(map);
// {age=31, name=jax}


Encode Date
Date date = new Date();

String json;
ObjectMapper jsonMapper = new ObjectMapper();

json = jsonMapper.writeValueAsString(date);
System.out.println(json);
// 1425211840183


jsonMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

json = jsonMapper.writeValueAsString(date);
System.out.println(json);
// "2015-03-01T12:10:40.183+0000"

參考自:FasterXML/jackson-databind · GitHub
2015-03-01 18:10

[轉載] Make 命令教程

轉載自:Make 命令教程 - 阮一峰的网络日志

代碼變成可執行文件,叫做編譯(compile);先編譯這個,還是先編譯那個(即編譯的安排),叫做構建(build)。

Make 是最常用的構建工具,誕生於 1977 年,主要用於 C 語言的項目。但是實際上 ,任何只要某個文件有變化,就要重新構建的項目,都可以用 Make 構建。

本文介紹 Make 命令的用法,從簡單的講起,不需要任何基礎,只要會使用命令行,就能看懂。我的參考資料主要是Isaac Schlueter的《Makefile文件教程》《GNU Make手冊》


一、Make 的概念


Make 這個詞,英語的意思是"制作"。Make 命令直接用了這個意思,就是要做出某個文件。比如,要做出文件 a.txt,就可以執行下面的命令。

$ make a.txt

但是,如果你真的輸入這條命令,它並不會起作用。因為 Make 命令本身並不知道,如何做出 a.txt,需要有人告訴它,如何調用其他命令完成這個目標。

比如,假設文件 a.txt 依賴於 b.txt 和 c.txt ,是後面兩個文件連接(cat 命令)的產物。那麼,make 需要知道下面的規則。

a.txt: b.txt c.txt
    cat b.txt c.txt > a.txt

也就是說,make a.txt 這條命令的背後,實際上分成兩步:第一步,確認 b.txt 和 c.txt 必須已經存在,第二步使用 cat 命令 將這個兩個文件合並,輸出為新文件。

像這樣的規則,都寫在一個叫做 Makefile 的文件中,Make 命令依賴這個文件進行構建。Makefile 文件也可以寫為 makefile, 或者用命令行參數指定為其他文件名。

$ make -f rules.txt
# 或者
$ make --file=rules.txt

上面代碼指定 make 命令依據 rules.txt 文件中的規則,進行構建。

總之,make 只是一個根據指定的 Shell 命令進行構建的工具。它的規則很簡單,你規定要構建哪個文件、它依賴哪些源文件,當那些文件有變動時,如何重新構建它。



二、Makefile 文件的格式


構建規則都寫在 Makefile 文件裡面,要學會如何 Make 命令,就必須學會如何編寫 Makefile 文件。


2.1 概述


Makefile 文件由一系列規則(rules)構成。每條規則的形式如下。

<target> : <prerequisites>
[tab]  <commands>

上面第一行冒號前面的部分,叫做"目標"(target),冒號後面的部分叫做"前置條件"(prerequisites);第二行必須由一個tab鍵起首,後面跟著"命令"(commands)。

"目標"是必需的,不可省略;"前置條件"和"命令"都是可選的,但是兩者之中必須至少存在一個。

每條規則就明確兩件事:構建目標的前置條件是什麼,以及如何構建。下面就詳細講解,每條規則的這三個組成部分。


2.2 目標(target)


一個目標(target)就構成一條規則。目標通常是文件名,指明Make命令所要構建的對像,比如上文的 a.txt 。目標可以是一個文件名,也可以是多個文件名,之間用空格分隔。

除了文件名,目標還可以是某個操作的名字,這稱為"偽目標"(phony target)。

clean:
    rm *.o

上面代碼的目標是 clean,它不是文件名,而是一個操作的名字,屬於"偽目標 ",作用是刪除對像文件。

$ make  clean

但是,如果當前目錄中,正好有一個文件叫做 clean,那麼這個命令不會執行。因為 Make 發現 clean 文件已經存在,就認為沒有必要重新構建了,就不會執行指定的 rm 命令。

為了避免這種情況,可以明確聲明 clean 是"偽目標",寫法如下。

.PHONY: clean
clean:
    rm *.o temp

聲明 clean 是"偽目標"之後,make 就不會去檢查是否存在一個叫做 clean 的文件,而是每次運行都執行對應的命令。像 .PHONY 這樣的內置目標名還有不少,可以查看手冊

如果 Make 命令運行時沒有指定目標,默認會執行 Makefile 文件的第一個目標。

$ make

上面代碼執行 Makefile 文件的第一個目標。


2.3 前置條件(prerequisites)


前置條件通常是一組文件名,之間用空格分隔。它指定了"目標"是否重新構建的判斷標准:只要有一個前置文件不存在,或者有過更新(前置文件的 last-modification 時間戳比目標的時間戳新),"目標"就需要重新構建。

result.txt: source.txt
    cp source.txt result.txt

上面代碼中,構建 result.txt 的前置條件是 source.txt 。如果當前目錄中,source.txt 已經存在,那麼 make result.txt 可以正常運行,否則必須再寫一條規則,來生成 source.txt 。

source.txt:
    echo "this is the source" > source.txt

上面代碼中,source.txt 後面沒有前置條件,就意味著它跟其他文件都無關,只要這個文件還不存在,每次調用 make source.txt,它都會生成。

$ make result.txt
$ make result.txt

上面命令連續執行兩次 make result.txt。第一次執行會先新建 source.txt,然後再新建 result.txt。第二次執行,Make 發現 source.txt 沒有變動(時間戳晚於 result.txt),就不會執行任何操作,result.txt 也不會重新生成。

如果需要生成多個文件,往往采用下面的寫法。

source: file1 file2 file3

上面代碼中,source 是一個偽目標,只有三個前置文件,沒有任何對應的命令。

$ make source

執行 make source 命令後,就會一次性生成 file1,file2,file3 三個文件。這比下面的寫法要方便很多。

$ make file1
$ make file2
$ make file3


2.4 命令(commands)


命令(commands)表示如何更新目標文件,由一行或多行的 Shell 命令組成。它是構建"目標"的具體指令,它的運行結果通常就是生成目標文件。

每行命令之前必須有一個 tab 鍵。如果想用其他鍵,可以用內置變量 .RECIPEPREFIX 聲明。

.RECIPEPREFIX = >
all:
> echo Hello, world

上面代碼用 .RECIPEPREFIX 指定,大於號(>)替代 tab 鍵。所以,每一行命令的起首變成了大於號,而不是 tab 鍵。

需要注意的是,每行命令在一個單獨的 shell 中執行。這些 Shell 之間沒有繼承關系。

var-lost:
    export foo=bar
    echo "foo=[$$foo]"

上面代碼執行後(make var-lost),取不到 foo 的值。因為兩行命令在兩個不同的進程執行。一個解決辦法是將兩行命令寫在一行,中間用分號分隔。

var-kept:
    export foo=bar; echo "foo=[$$foo]"

另一個解決辦法是在換行符前加反斜杠轉義。

var-kept:
    export foo=bar; \
    echo "foo=[$$foo]"

最後一個方法是加上 .ONESHELL: 命令。

.ONESHELL:
var-kept:
    export foo=bar;
    echo "foo=[$$foo]"



三、Makefile文件的語法


3.1 注釋


井號(#)在 Makefile 中表示注釋。

# 这是注释
result.txt: source.txt
    # 这是注释
    cp source.txt result.txt # 这也是注释


3.2 回聲(echoing)


正常情況下,make 會打印每條命令,然後再執行,這就叫做回聲(echoing)。

test:
    # 这是测试

執行上面的規則,會得到下面的結果。

$ make test
# 这是测试

在命令的前面加上 @,就可以關閉回聲。

test:
    @# 这是测试

現在再執行 make test,就不會有任何輸出。

由於在構建過程中,需要了解當前在執行哪條命令,所以通常只在注釋和純顯示的 echo 命令前面加上 @

test:
    @# 这是测试
    @echo TODO


3.3 通配符


通配符(wildcard)用來指定一組符合條件的文件名。Makefile 的通配符與 Bash 一致,主要有星號(*)、問號(?)和 [...] 。比如, *.o 表示所有後綴名為 o 的文件。

clean:
    rm -f *.o


3.4 模式匹配


Make 命令允許對文件名,進行類似正則運算的匹配,主要用到的匹配符是 %。比如,假定當前目錄下有 f1.c 和 f2.c 兩個源碼文件,需要將它們編譯為對應的對像文件。

%.o: %.c

等同於下面的寫法。

f1.o: f1.c
f2.o: f2.c

使用匹配符 %,可以將大量同類型的文件,只用一條規則就完成構建。


3.5 變量和賦值符


Makefile 允許使用等號自定義變量。

txt = Hello World
test:
    @echo $(txt)

上面代碼中,變量 txt 等於 Hello World。調用時,變量需要放在 $( ) 之中。

調用Shell變量,需要在美元符號前,再加一個美元符號,這是因為Make命令會對美元符號轉義。

test:
    @echo $$HOME

有時,變量的值可能指向另一個變量。

v1 = $(v2)

上面代碼中,變量 v1 的值是另一個變量 v2。這時會產生一個問題,v1 的值到底在定義時擴展(靜態擴展),還是在運行時擴展(動態擴展)?如果 v2 的值是動態的,這兩種擴展方式的結果可能會差異很大。

為了解決類似問題,Makefile 一共提供了四個賦值運算符 (=、:=、?=、+=),它們的區別請看 StackOverflow

VARIABLE = value
# 在执行时扩展,允许递归扩展。

VARIABLE := value
# 在定义时扩展。

VARIABLE ?= value
# 只有在该变量为空时才设置值。

VARIABLE += value
# 将值追加到变量的尾端。


3.6 內置變量(Implicit Variables)


Make命令提供一系列內置變量,比如,$(CC) 指向當前使用的編譯器,$(MAKE) 指向當前使用的Make工具。這主要是為了跨平台的兼容性,詳細的內置變量清單見手冊

output:
    $(CC) -o output input.c


3.7 自動變量(Automatic Variables)


Make 命令還提供一些自動變量,它們的值與當前規則有關。主要有以下幾個。


(1)$@

$@指代當前目標,就是Make命令當前構建的那個目標。比如,make foo的 $@ 就指代foo。

a.txt b.txt:
    touch $@

等同於下面的寫法。

a.txt:
    touch a.txt
b.txt:
    touch b.txt


(2)$<

$< 指代第一個前置條件。比如,規則為 t: p1 p2,那麼$< 就指代p1。

a.txt: b.txt c.txt
    cp $< $@

等同於下面的寫法。

a.txt: b.txt c.txt
    cp b.txt a.txt


(3)$?

$? 指代比目標更新的所有前置條件,之間以空格分隔。比如,規則為 t: p1 p2,其中 p2 的時間戳比 t 新,$?就指代p2。


(4)$^

$^ 指代所有前置條件,之間以空格分隔。比如,規則為 t: p1 p2,那麼 $^ 就指代 p1 p2 。


(5)$*

$* 指代匹配符 % 匹配的部分, 比如% 匹配 f1.txt 中的f1 ,$* 就表示 f1。


(6)$(@D) 和 $(@F)

$(@D) 和 $(@F) 分別指向 $@ 的目錄名和文件名。比如,$@是 src/input.c,那麼$(@D) 的值為 src ,$(@F) 的值為 input.c。


(7)$(<D) 和 $(<F)

$(<D) 和 $(<F) 分別指向 $< 的目錄名和文件名。

所有的自動變量清單,請看手冊。下面是自動變量的一個例子。

dest/%.txt: src/%.txt
    @[ -d dest ] || mkdir dest
    cp $< $@

上面代碼將 src 目錄下的 txt 文件,拷貝到 dest 目錄下。首先判斷 dest 目錄是否存在,如果不存在就新建,然後,$< 指代前置文件(src/%.txt), $@ 指代目標文件(dest/%.txt)。


3.8 判斷和循環


Makefile使用 Bash 語法,完成判斷和循環。

ifeq ($(CC),gcc)
    libs=$(libs_for_gcc)
else
    libs=$(normal_libs)
endif

上面代碼判斷當前編譯器是否 gcc ,然後指定不同的庫文件。

LIST = one two three
all:
    for i in $(LIST); do \
        echo $$i; \
    done

# 等同于

all:
    for i in one two three; do \
        echo $i; \
    done

上面代碼的運行結果。

one
two
three


3.9 函數


Makefile 還可以使用函數,格式如下。

$(function arguments)
# 或者
${function arguments}

Makefile提供了許多內置函數,可供調用。下面是幾個常用的內置函數。


(1)shell 函數

shell 函數用來執行 shell 命令

srcfiles := $(shell echo src/{00..99}.txt)


(2)wildcard 函數

wildcard 函數用來在 Makefile 中,替換 Bash 的通配符。

srcfiles := $(wildcard src/*.txt)


(3)subst 函數

subst 函數用來文本替換,格式如下。

$(subst from,to,text)

下面的例子將字符串 "feet on the street" 替換成 "fEEt on the strEEt"。

$(subst ee,EE,feet on the street)

下面是一個稍微復雜的例子。

comma:= ,
empty:=
# space变量用两个空变量作为标识符,当中是一个空格
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
# bar is now `a,b,c'.


(4)patsubst 函數

patsubst 函數用於模式匹配的替換,格式如下。

$(patsubst pattern,replacement,text)

下面的例子將文件名 "x.c.c bar.c",替換成 "x.c.o bar.o"。

$(patsubst %.c,%.o,x.c.c bar.c)


(5)替換後綴名

替換後綴名函數的寫法是:變量名 + 冒號 + 後綴名替換規則。它實際上 patsubst 函數的一種簡寫形式。

min: $(OUTPUT:.js=.min.js)

上面代碼的意思是,將變量 OUTPUT 中的後綴名 .js 全部替換成 .min.js 。


四、Makefile 的實例



(1)執行多個目標

.PHONY: cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff
    rm program

cleanobj :
    rm *.o

cleandiff :
    rm *.diff

上面代碼可以調用不同目標,刪除不同後綴名的文件,也可以調用一個目標(cleanall),刪除所有指定類型的文件。


(2)編譯C語言項目

edit : main.o kbd.o command.o display.o
    cc -o edit main.o kbd.o command.o display.o

main.o : main.c defs.h
    cc -c main.c
kbd.o : kbd.c defs.h command.h
    cc -c kbd.c
command.o : command.c defs.h command.h
    cc -c command.c
display.o : display.c defs.h
    cc -c display.c

clean :
    rm edit main.o kbd.o command.o display.o

.PHONY: edit clean
2015-03-01 15:36

Spring Security 存取控制表示式

常用内建表示式

ps: 定義在 SecurityExpressionRoot

表示式說明
hasRole('role')當前的 User 擁有指定的 Role 就回傳 true
hasAnyRole('role1', 'role2')當前的 User 擁有任一個 Role 就回傳 true
principal當前的 User 的 Principal 物件
authentication當前的 User 的 Authentication 物件
permitAll總是為 true
denyAll總是為 false
isAnonymous()當前的 User 是匿名登入就回傳 true
isRememberMe()當前的 User 是透過 remember-me 登入就回傳 true
isAuthenticated()當前的 User 不是匿名登入就回傳 true
isFullyAuthenticated()當前的 User 不是匿名登入或 remember-me 登入就回傳 true



在方法執行前的驗證


驗證 User 角色
@PreAuthorize("hasRole('ROLE_USER')")
public void create(Contact contact);

驗證參數值是否等於 User 名稱
@PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);

驗證 User 角色以及來源 IP 區間
@PreAuthorize("hasRole('admin') and hasIpAddress('192.168.1.0/24')")
public void doSomething(Contact contact);



在方法內的驗證


取得角色驗證
@RequestMapping("/index")
public void index(HttpServletRequest request) {
    System.out.println(request.isUserInRole("ROLE_USER"));

    if (request.isUserInRole("admin")) {
        System.out.println("is admin");
    }
}



在 JSP 的驗證


取得 User 名稱
<sec:authentication property="name"/>
<sec:authentication property="principal.username"/>

取得 User IP
<sec:authentication property="details.remoteAddress"/>

取得 User SessionId
<sec:authentication property="details.sessionId"/>

驗證角色為 admin 才顯示
<sec:authorize access="hasRole('admin')">
    <div>is admin</div>
</sec:authorize>

驗證角色為 admin 存入變數 isAdmin
<sec:authorize var="isAdmin" access="hasRole('admin')" />
<c:if test="isAdmin">
    <div>is admin</div>
</c:if>


參考自:15. Expression-Based Access Control