2015年10月14日 星期三

泛型類別和問號

class Chess<T> {
    private T xxx;
    //setter/getter
}


public class TestGeneric {
    public static void main(String[] args) {
        Chess<Integer> c1 = new Chess<Integer>();
        Chess<String> c2 = new Chess<String>();
        Chess<? extends List<String>> c3 = new Chess<ArrayList<String>>();
        Chess<? extends List<Integer>> c4 = new Chess<LinkedList<Integer>>();
        Chess<? super Integer> c5 = new Chess<Number>();
        c5.setXxx(111);
        System.out.println(c5.getXxx());
        
        Chess c6 = new Chess<>();
        c6.setXxx(123);
        System.out.println(c6.getXxx());
        
        Chess<?> c7 = c6;
        System.out.println(c7.getXxx());
        c7.setXxx(null);
        System.out.println(c7.getXxx());
        //c7.setXxx(456);
    }
}

※Chess類別的T為呼叫者決定什麼型別,就是什麼型別,如c1~c5,如果沒有泛型就要寫很多類似的類別了
※c3、c4只能是List的兒子
※c5只能是Integer的爸爸

※c6和c7的差別在有沒有泛型
  c6(沒有泛型)塞值後給有問號泛型的c7
  c7能印出來,也能把它變null
  但不能塞值,這就是兩者的差別
  如果能塞值也很奇怪,因為宣告問號了,java怎麼知道你要塞什麼值?
  所以直接給編譯錯誤

※問號可以使用強轉,但要轉成正確的型態

※和泛型方法配合使用,泛型方法可以覆蓋泛型類別,但會有警告,叫你用 hiding

※泛型類別不能用在 static 方法

2015年10月13日 星期二

JSP賦值給 javascript

最近在公司寫了如下這一行
var xxx = <%=variable%>;

此時瀏覽器直器當機,看來是語法的錯誤,可是當時居然找不到,靜下心來後,發現原來要這樣子用

var xxx = '<%=variable%>';
        或是
var xxx = "<%=variable%>";

因為javascript原本就可以用單或雙引號,在此做個筆記,以免忘記

泛型方法

※基本

public static void main(String[] args) {
    new Test().<String>xxx("");
    new Test().xxx("");
}
    
private <T> void xxx(T t) {}
    
private <T extends Xxx, U extends String, V extends Animal &Ooo & Oooo> void xxx(T t, U u, V v) {}

※宣告時,寫在回傳值的左邊,而T是隨便打的,反正就是一個型態,型態是什麼由呼叫者決定

※宣告後可以不用,不會出現錯誤或警告,此例使用在參數

※可以在呼叫方法的左邊寫泛型,可以確定是什麼型態,但不寫也不會出現錯誤或警告

※&只能寫在泛型類別和泛型方法,像 List 是不行的
在 & 的右邊只能放 interface,這句話表示 V 必需是 Animal 的子類且還需 implements Ooo 和 Oooo 兩個 interface
.「,」隔開每一個自定的泛型名稱,不能重覆用,如 第一個已經用 T 了,後面就不能用 T 了



※集合

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    
    new Test().<Integer>xxx(list);
    new Test().xxx(list);
}
    
private <T> void xxx(List<T> t) {
    for(T tt:t) {
        System.out.println(tt);
    }
}

※呼叫時的泛型寫的不是 List,而是 List 裡的型態,因為泛型方法的參數 T 在 List 裡,也就是說它只管泛型的部分

2015年10月9日 星期五

JFreeChart (Struts2.3.x 十三)

首先先去這個網站下載
下載後打開jfreechart-1.x.x-demo.jar

打開後,找一個自己喜歡的,然後在source資料夾搜尋你選到的檔名,就有原始檔可看,直接複製到Eclipse,即可得到和畫面一樣的結果

這裡模擬一個較簡單的
DefaultPieDataset dataset = new DefaultPieDataset();
dataset.setValue("Samsung", new Double(27.8));
dataset.setValue("Others", new Double(55.3));
dataset.setValue("Nokia", new Double(16.8));
dataset.setValue("Apple", new Double(17.1));
JFreeChart jfc = ChartFactory.createPieChart("Phone Person", dataset);
try {
    ChartUtilities.saveChartAsJPEG(new File("D:/test.jpg"), jfc, 800, 600);
    System.out.println("成功!");
} catch (IOException e) {
    e.printStackTrace();
    System.err.println("失敗!");
}

setValue(Comparable, Number)
Comparable的兒子包括8個基本型別的Wrapper類別,還有String、Date都是

以上設定如果有中文,也不會出錯,但中文會變成「□」,所以要增加了以下的方法:
DefaultPieDataset dataset = new DefaultPieDataset();
dataset.setValue("象棋", 12);
dataset.setValue("跳棋", 25);
dataset.setValue("五子棋", 30);
dataset.setValue("孔明棋", 5);

// 中文標題
JFreeChart jfc = ChartFactory.createPieChart("棋類使用率圖", dataset);
jfc.getTitle().setFont(new Font("標楷體", Font.BOLD, 28));

// 圖裡的中文
PiePlot plot = (PiePlot) jfc.getPlot();
plot.setLabelFont(new Font("微軟正黑體", Font.PLAIN, 16));

// 圖底的中文
jfc.getLegend().setItemFont(new Font("新細明體", Font.ITALIC, 20));
try {
    ChartUtilities.saveChartAsJPEG(new File("D:/test.jpg"), jfc, 800, 600);
    System.out.println("成功!");
} catch (IOException e) {
    e.printStackTrace();
    System.err.println("失敗!");
}


如果本機沒有對應的字體也不會報錯,看起來是有預設的字體

※在Struts2裡使用JFreeChart

1.首先要有jfreechart-1.x.x.jar,還有Struts2合併JFreeChart的struts2-jfreechart-plugin-2.x.x.jar

2.struts.xml
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
<constant name="struts.devMode" value="true" />

<package name="basicstruts2" extends="struts-default"
    namespace="/">
    <result-types>
        <result-type name="chart"
            class="org.apache.struts2.dispatcher.ChartResult">
            <param name="height">200</param>
            <param name="width">200</param>
        </result-type>
    </result-types>
</package>

3.JFreeChartAction.java
@ParentPackage("basicstruts2")
@Namespace("/struts2")
@Action("picture")
@Results({ @Result(name = "success", type = "chart") })
public class JFreeChartAction extends ActionSupport {
    private JFreeChart chart;

    public JFreeChart getChart() {
        return chart;
    }

    public String productPic() {
        DefaultPieDataset dataset = new DefaultPieDataset();
        dataset.setValue("象棋", 12);
        dataset.setValue("跳棋", 25);
        dataset.setValue("五子棋", 30);
        dataset.setValue("孔明棋", 5);

        // 中文標題
        chart = ChartFactory.createPieChart("棋類使用率圖", dataset);
        chart.getTitle().setFont(new Font("標楷體", Font.BOLD, 28));

        // 圖裡的中文
        PiePlot plot = (PiePlot) chart.getPlot();
        plot.setLabelFont(new Font("微軟正黑體", Font.PLAIN, 16));

        // 圖底的中文
        chart.getLegend().setItemFont(new Font("新細明體", Font.ITALIC, 20));
        try {
            ChartUtilities.saveChartAsJPEG(new File("D:/test.jpg"), chart, 200,
                    200);
            System.out.println("成功!");
        } catch (IOException e) {
            e.printStackTrace();
            System.err.println("失敗!");
        }
        return SUCCESS;
    }
}

在瀏覽器輸入http://localhost:8080/Struts2Demo/struts2/picture!productPic.action即可看到畫面
struts.xml裡的寬高是瀏覽器看到的寬高
JFreeChartAction.java的寬高是輸出檔案的寬高
我試的結果,名字一定要叫chart,不然會報錯

官方也有提供做法,但有地方要修改如下:
struts.xml不用動
ViewModerationChartAction.java因為沒有RandomUtils,所以改成以下:
Random random = new Random();
for (int i = 0; i <= 100; i++) {
    dataSeries.add(i, random.nextInt());
}

return SUCCESS即可,不用super,這樣就能產生報表了

其他更深入的寫法,就看JFreeChart官方網站了,它的教學指南是要花錢買的

Annotation (Struts2.3.x 十二)

首先必需要有struts2-convention-plugin.jar
Struts2.x(七)為例,可改成以下這個樣子
CRUDAction.java
@Namespaces(value = { 
                @Namespace("/"), 
                @Namespace("/struts2") 
            })
@Actions(value = { 
            @Action(value = "crud1"), 
            @Action(value = "crud2") 
        })
@Results(value = {
            @Result(name = "success", location = "/struts2/success.jsp"),
            @Result(name = "input", location = "/struts2/fail.jsp") 
        })
public class CRUDAction extends ActionSupport {
    @Action(value = "crudI")
    public String insert() {
        System.out.println("增");
        return null;
    }

    @Action(value = "crudD")
    public void delete() {
        System.out.println("刪");
    }

    @Action(value = "crudU")
    public void update() {
        System.out.println("改");
    }

    @Action(value = "crudQ")
    public List<String> query() {
        System.out.println("查");
        return null;
    }
}

@裡面的「value=」,可以不寫,不管它裡面有多少field,不打就是value,可以參考我這篇文章
※測試:
開啟伺服器後,網址列會出現http://localhost:8080/Struts2Demo/開頭,Struts2Demo是我的專案名稱
1.在後面加crudI.acion或struts2/crudI.action都會成功,因為@Namespace有設定兩個路徑,其他刪改查都是一樣的意思

2.在後面加crud1.action或struts2/crud1.action(crud1和crud2都可以)也都會跳轉到成功頁

3.@Namespaces、@Actions、@Results 最後都有個「s」,所以裡面都是沒s的,其實如果只要設定一個,就不需要外層的s,只是我舉這個例子會較容易懂

4.@Action裡面有個results屬性,意思和@Result一樣,看你喜歡寫哪裡都行

5.在後面加crud1!insert.action或struts2/crud1!insert.action(crud1和crud2都可以),還是要在struts.xml或struts.properties設定struts.enable.DynamicMethodInvocation是true才可以

6.@ParentPackage
這個Annotation是繼承另外一個package用的
譬如在xml是設定<package extends="">,Annotation就是用這個

7.@InterceptorRefs
攔截器一樣可以設定,@Action也有interceptorRefs屬性,兩者一樣,看你喜歡寫哪裡都行

8.@ExceptionMappings
@Action也有exceptionMappings屬性,兩者一樣,看你喜歡寫哪裡都行
譬如:
@Action(
    value = "crudI", 
    exceptionMappings = 
        @ExceptionMapping(
            exception = "java.lang.NullPointerException", 
            result = "success", 
            params = {
                "key", "value" 
            }
        ))
public String insert() {
    System.out.println("增");
    return null;
}

當發生NullPointerException,會導到@Result是success的網頁,而且傳參數過去

其他的Annotation,可以參考這個網頁

2015年10月4日 星期日

jQuery 和 javascript 混用問題

最近發現非$(document)開頭的,如$(location).href,裡面是location可以和javascript的location混用
而$(document)就不可以混用,真是太奇怪了
$(document).ready()有三種寫法,其中一種不建議使用,看官網,所以下面的$(function(){}),function(){}被$()包起來,也屬於$(document)

jQuery本身是集合,所以在轉換成DOM時,必需取出其中一項才能轉換
譬如用jQuery的get()或javascript陣列,這時只能用DOM方法(下面的程式第一段)
如果一定要用jQuery的方法,可以用$()包起來(下面的程式第二段)
而jQuery只能用jQuery的方法,以下是個範例(下面的程式第二段)
最後一個each是改良後較佳的寫法,比較不會有混用的情形(下面的程式第三段)
而javascript迴圈的continue和break
在jQuery就是return和return false

<html lang="en">
<head>
    <meta charset="utf-8">
    <title>get demo</title>
    <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
    <script>
        $(function(){
            var aaa = $('[name="aaa"]');
            alert(aaa.get(0).value);
            alert(aaa[0].value);
            alert(aaa.eq(0)[0].value);
            
            aaa.each(function(i){
                alert($(aaa.get(i)).val());
                alert($(aaa[i]).val());
                alert($(aaa.eq(i)[0]).val());
            });
            
            $('[name="aaa"]').each(function(){
                alert($(this).val());
                alert(this.value);
            });
        });
    </script>
</head>
<body>
    <input type="text" value="1" name="aaa" />
    <input type="text" value="2" name="aaa" />
    <input type="text" value="3" name="aaa" />
</body>
</html>

上傳 (Struts2.3.x 十一)

上傳首先要有兩個jar,看這裡
先寫個type="file",看看Action抓到的情形
upload.jsp
<form action="upload!upload.action" method="post" enctype="multipart/form-data">
    <input type="file" name="uploadFile" /><br /> 
    <input type="submit" />
</form>


struts.xml,因為action沒有result,待會會出錯,但目前只要看控制台的訊息就好
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
<constant name="struts.custom.i18n.resources" value="Message" />
<constant name="struts.devMode" value="true" />

<package name="basicstruts2" extends="struts-default" namespace="/struts2">
    <action name="upload" class="action.UploadAction"></action>
</package>


UploadAction.java,只要繼承ActinSupport
public class UploadAction extends ActionSupport {
    private File uploadFile;
    //setter即可

    public String upload() {
        System.out.println("uploadFile=" + uploadFile);
        return null;
    }
}


抓到的檔案副檔名為tmp,而且控制台部分訊息出現「'uploadFileContentType' on 'class action.UploadAction」和「'uploadFileFileName' on 'class action.UploadAction」的訊息,表示要增加這兩個屬性,所以Action最後的結果要改成如下的方式:

public class UploadAction extends ActionSupport {
    private File uploadFile;
    private String uploadFileContentType;
    private String uploadFileFileName;
    //三個屬性都給setter即可

    public String upload() {
        System.out.println("uploadFile=" + uploadFile);
        System.out.println("uploadFileContentType=" + uploadFileContentType);
        System.out.println("uploadFileFileName=" + uploadFileFileName);
        return null;
    }
}

這裡也有說明

知道這三個屬性的關係後,重新再寫一次

upload.jsp
<%@ page isELIgnored="false"%>

${uploadMsg}
<form action="upload!upload.action" method="post" enctype="multipart/form-data">
    <input type="file" name="uploadFile" /><br /> 
    <input type="submit" />
</form>

struts.xml,constant可以在src下建立一個struts.properties,也是一樣,目前這隻saveDir還沒有作用
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
<constant name="struts.custom.i18n.resources" value="Message" />
<constant name="struts.devMode" value="true" />
<constant name="struts.multipart.saveDir" value="D:\testStruts2" />
<constant name="struts.multipart.maxSize" value="2097152" />

<package name="basicstruts2" extends="struts-default" namespace="/struts2">
    <action name="upload" class="action.UploadAction">
        <result name="success">/struts2/upload.jsp</result>
    </action>
</package>
struts2-core-2.x.x.jar\org.apache.strtus2\default.properties可以找到constant的所有資訊
2097152單位是bit,除兩次1024就是2MB

UploadAction.java
private File uploadFile;
private String uploadFileContentType;
private String uploadFileFileName;
//三個屬性都給setter即可

public String upload() {
    HttpServletRequest request = ServletActionContext.getRequest();

    //可以到「tomcat目錄\conf\web.xml」可看到很多xxx/xxx
    List<String> list = Arrays.asList(new String[] { "image/jpeg",
            "image/bmp", "image/tiff" });

    if (list.contains(this.uploadFileContentType)) {
        ServletContext context = ServletActionContext.getServletContext();
        StringBuffer path = new StringBuffer();
        // 取個資料夾名稱放上傳的檔案
        path.append(context.getRealPath("/uploadPlace"));
        // 用唯一值命名主檔名
        path.append(File.separator + UUID.randomUUID() + ".");
        // 取原本的副檔名
        path.append(this.uploadFileFileName
                .substring(this.uploadFileFileName.lastIndexOf(".") + 1));

        FileControl.saveFile(this.uploadFile, path.toString());
        request.setAttribute("uploadMsg", "上傳成功!");
    } else {
        request.setAttribute("uploadMsg", "上傳非圖片格式!");
    }
    return SUCCESS;
}

FileControl.java,裡面只有兩個靜態方法
public static boolean saveFile(File file, String path) {
    File outFile = new File(path);
    if (!outFile.getParentFile().exists()) {
        outFile.getParentFile().mkdirs();
    }
    InputStream input = null;
    OutputStream output = null;
    try {
        input = new FileInputStream(file);
        output = new FileOutputStream(outFile);
        byte[] data = new byte[1024];
        int temp = 0;
        while ((temp = input.read(data)) != -1) {
            output.write(data, 0, temp);
        }
        input.close();
        output.close();
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

public static boolean deleteFile(String path) {
    File file = new File(path);
    if (file.exists()) {
        file.delete();
        return true;
    }
    return false;
}
這個class就隨個人發揮了

以上雖然完成了功能,但是是屬於自己寫的苦功,所以stuts.xml的saveDir(儲存路徑)沒有效果,當然路徑也可以用Action裡的ServletActionContext.getServletContext().getRealPath("")取得檔案內容,然後進一步取得路徑

※用struts2的攔截器來達成上傳的功能

upload.jsp
${fieldErrors},<br />
${fieldErrors['uploadFile']},<br />
${fieldErrors['uploadFile'][0]}<br />
<form action="upload!upload.action" method="post" enctype="multipart/form-data">
    <input type="file" name="uploadFile" /><br /> 
    <input type="submit" />
</form>

struts.xml,saveDir是暫存目錄,不加會在control有警告「Unable to find 'struts.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir」,但不影響上傳功能,而這個資料夾,上傳完成後,我也沒看到有什麼檔案在裡面
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
<constant name="struts.custom.i18n.resources" value="Message" />
<constant name="struts.devMode" value="false" />
<constant name="struts.multipart.saveDir" value="D:/testStruts2" />

<package name="basicstruts2" extends="struts-default"
        namespace="/struts2">
    <action name="upload" class="action.UploadAction">
        <interceptor-ref name="fileUpload">
            <param name="allowedTypes">
                image/jpeg,image/bmp,image/tiff,image/png,image/gif
            </param>
            <param name="maximumSize">1048576</param>
        </interceptor-ref>
        <interceptor-ref name="basicStack"/>
        <result name="success">/struts2/upload.jsp</result>
    </action>
</package>

UploadAction
private File uploadFile;
private String uploadFileContentType;
private String uploadFileFileName;
//都給setter即可

public String upload() {
    ServletContext context = ServletActionContext.getServletContext();
    StringBuffer path = new StringBuffer();
    path.append(context.getRealPath("/uploadPlace"));
    path.append(File.separator + this.uploadFileFileName);
    System.out.println("path=" + path);
    FileControl.saveFile(this.uploadFile, path.toString());
    return SUCCESS;
}

Message.properties,顯示錯誤訊息用,不加也有預設的錯誤訊息
struts.messages.error.uploading=aaa
struts.messages.error.file.too.large=bbb
struts.messages.error.content.type.not.allowed=ccc
struts.messages.error.file.extension.not.allowed=ddd

※多檔案上傳

以上個檔案為例,其實只要變成陣列的型式就可以了
upload.jsp
<form action="upload!upload.action" method="post" enctype="multipart/form-data">
    <input type="file" name="uploadFile" />${fieldErrors['uploadFile'][0]}<br /> 
    <input type="file" name="uploadFile" />${fieldErrors['uploadFile'][1]}<br /> 
    <input type="file" name="uploadFile" />${fieldErrors['uploadFile'][2]}<br /> 
    <input type="submit" />
</form>

UploadAction.java
private File[] uploadFile;
private String[] uploadFileContentType;
private String[] uploadFileFileName;
//都給setter即可

public String upload() {
    ServletContext context = ServletActionContext.getServletContext();
    for (int i = 0; i < uploadFile.length; i++) {
        StringBuffer path = new StringBuffer();
        path.append(context.getRealPath("/uploadPlace"));
        path.append(File.separator + this.uploadFileFileName[i]);
        System.out.println("path=" + path);
        FileControl.saveFile(this.uploadFile[i], path.toString());
    }
    return SUCCESS;
}