1. 程式人生 > >【113】JPlag 重複程式碼段顏色不一致問題的解決方法。

【113】JPlag 重複程式碼段顏色不一致問題的解決方法。

JPlag 是一個用於檢查程式碼相似性的工具。主要用於教育領域,檢測學生的程式碼作業是否有抄襲行為。假如存在兩個學生:student1 和 student2。為這兩個學生各自建立一個資料夾並把程式碼放到資料夾中。檔案結構如下:

E:\ws\jplag\exercise1
                |
                ├─ student1
                |      └─src
                |          └─Abc.java 
                | 
                └─ student2
                       └─src
                           └─Abc.java 

student1 的 Abc.java 檔案內容如下:


public class Abc {
    public static void main(String[] args){
        System.out.println("hello");
        System.out.println("hello");
        System.out.println("hello");
        System.out.println("hello");
        System.out.println("hello");
        System.out
.println("hello"); String a = "asfasdfasfd"; String b = "klsdjfl"; System.out.println(a + b); System.out.println("hello"); System.out.println("hello"); System.out.println("hello"); System.out.println("hello"); } private void m1 () { int
[] arr = new int[]{1,3,2,3}; int max = arr[0]; for (int i : arr) { if (max < i){ max = i; } } } } </body></html>

student2 的 java 檔案內容如下:

public class Abc {
    public static void main(String[] args){
        System.out.println("hello");
        System.out.println("hello");
        System.out.println("hello");
        System.out.println("hello");
        System.out.println("hello");
        System.out.println("hello");
        int a = 1;
        for (int  i = 0; i < 10; i++) {
            a ++;
        }


        System.out.println("hello");
        System.out.println("hello");
        System.out.println("hello");
        System.out.println("hello");
    }

    private void m1 () {
        int[] arr = new int[]{1,3,2,3};
        int max = arr[0];
        for (int i : arr) {
            if (max < i){
                max = i;
            }
        }

    }
}

我現在假設你已經下載好jar包,並把jar檔案放到 E:\ws\jplag 。開啟命令列,cd命令進入jar檔案的目錄,然後執行如下命令:

java -jar jplag-2.11.9-SNAPSHOT-jar-with-dependencies.jar -l java17 -r tmp -s exercise1

如果存在相似程式碼塊,就會生成報告。當然你也可以利用程式執行上面的命令,這樣可以方便的嵌入你的系統之中。

下面簡單解釋一下引數:

  • -l: 使用的語言,這裡的java17 表示 java 1.7 版本。
  • -r:結果報告存放的目錄地址。這裡表示當前目錄的tmp資料夾。
  • -s:要進行檢查的學生作業目錄。

最後生成的結果以HTML檔案的形式存放。HTML展示效果如下圖所示:

這裡寫圖片描述

看到這幅圖,想必你也能發現問題了。JPlag生成的結果,不同的程式碼片段使用不同的顏色表示。使用者希望用同一個顏色展示,此時我們需要對生成的結果進行處理。

進行處理的程式碼分成兩個檔案,分別是 Main.java 、 ColorUtils.java 和 TagUtils.java。其中 Main.java 包含主方法,ColorUtils.java 主要改變程式碼塊的顏色,TagUtils.java 處理HTML標籤。這三個檔案在同一個包內,包名是zhangchao。

Main.java

package zhangchao;

import java.io.File;

public class Main {
    public static void main(String[] args) {
        ColorUtils.changeColor(new File("E:/ws/jplag/tmp/match0-0.html"));
        ColorUtils.changeColor(new File("E:/ws/jplag/tmp/match0-1.html"));
    }

}

ColorUtils.java

package zhangchao;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;

/**
 * 處理程式碼片段的顏色。
 * @author 張超
 *
 */
public final class ColorUtils {

    /**
     * 程式碼片段的顏色值。
     */
    private final static String COLOR = "#ff0000";

    /**
     * 統一更改顏色
     * @param file
     */
    public static final void changeColor(final File file) {
        FileInputStream fis = null;
        BufferedReader br = null;
        FileOutputStream fos = null;
        BufferedWriter bw = null;
        try {
            fis = new FileInputStream(file);
            br = new BufferedReader(new InputStreamReader(fis, "UTF8"));
            String str = null;
            StringBuilder htmlSb = new StringBuilder();
            while(null != (str = br.readLine())){
                htmlSb.append(str).append("\n");
            }
            String originHtml = htmlSb.toString(); // 讀取的HTML
            ArrayList<String> strList = new ArrayList<String>();
            int scanIndex = 0;
            while ( scanIndex < originHtml.length()) {
                // 如果存在<Font>標籤,就替換成統一的顏色。
                if (TagUtils.isTag(originHtml, scanIndex)) {
                    scanIndex = TagUtils.increament(originHtml, scanIndex);
                    strList.add("<FONT color=\"" + COLOR + "\">");
                } else {
                    strList.add(originHtml.substring(scanIndex, scanIndex + 1));
                    scanIndex++;
                }
            }

            // 先關閉讀取的流,再開啟寫入的流。
            br.close();
            br = null;
            fis.close();
            fis = null;

            StringBuilder newHtmlSb = new StringBuilder();
            for (String s : strList) {
                newHtmlSb.append(s);
            }
            fos = new FileOutputStream(file);
            bw = new BufferedWriter(new OutputStreamWriter(fos, "UTF8"));
            bw.write(newHtmlSb.toString());

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != fis) {
                    fis.close();
                }
                if (null != br) {
                    br.close();
                }
                if (null != bw) {
                    bw.flush();
                    bw.close();
                }
                if (null != fos) {
                    fos.flush();
                    fos.close();
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

TagUtils.java

package zhangchao;

/**
 * 處理HTML標籤
 * @author 張超
 *
 */
public final class TagUtils {
    static final String TAG_NAME = "font";

    /**
     * 是否有標籤
     * @param originHtml
     * @param scanIndex
     * @return
     */
    public static final boolean isTag(final String originHtml, final int scanIndex){
        if (originHtml.length() - scanIndex < TAG_NAME.length() + 2) {
            return false;
        }
        if(("<"+TAG_NAME).equalsIgnoreCase(originHtml.substring(scanIndex, scanIndex + TAG_NAME.length() + 1))){

        } else {
            return false;
        }
        if (false == Character.isWhitespace(originHtml.charAt(scanIndex + TAG_NAME.length() + 1))) {
            return false;
        }



        int tmpIndex = scanIndex + TAG_NAME.length() + 1;
        boolean isInDoubleQuote = false; // 是否在雙引號中
        while(tmpIndex < originHtml.length()) {
            char ch = originHtml.charAt(tmpIndex);
            if (Character.isWhitespace(ch)) {
                tmpIndex ++;
            }
            // 標籤中出現 ="  表明在雙引號中
            else if (!isInDoubleQuote && ch == '=' && tmpIndex + 1 < originHtml.length() && originHtml.charAt(tmpIndex+1)=='"') {
                tmpIndex += 2;
                isInDoubleQuote = true;
            }
            else if (isInDoubleQuote && ch == '>') {
                tmpIndex ++;
            }
            else if (isInDoubleQuote && ch=='"') {
                tmpIndex ++;
                isInDoubleQuote = false;
            }

            else if (!isInDoubleQuote && ch=='>') { // 標籤結束 >
                return true;
            }

            else {
                tmpIndex ++;
            }
        }

        return false;
    }

    /**
     * 計算標籤要跨過多少字元
     * @param originHtml
     * @param scanIndex
     * @return
     */
    public static final int increament(final String originHtml, final int scanIndex){
        if (("<" + TAG_NAME + ">").equalsIgnoreCase(originHtml.substring(scanIndex, scanIndex) + TAG_NAME.length() + 2)) {
            return TAG_NAME.length() + 2;
        }

        int tmpIndex = scanIndex + TAG_NAME.length() + 1;
        boolean isInDoubleQuote = false; // 是否在雙引號中
        int originHtmlLength = originHtml.length();
        while (tmpIndex < originHtmlLength) {
            char ch = originHtml.charAt(tmpIndex);
            if (Character.isWhitespace(ch)) {
                tmpIndex ++;
            }
            // 標籤中出現 ="  表明在雙引號中
            else if (!isInDoubleQuote && ch == '=' && tmpIndex + 1 < originHtmlLength && originHtml.charAt(tmpIndex+1)=='"') {
                tmpIndex += 2;
                isInDoubleQuote = true;
            }
            else if (isInDoubleQuote && ch == '>') { // 雙引號中出現 > 不能作為標籤結束的標誌
                tmpIndex ++;
            }
            else if (isInDoubleQuote && ch=='"') {  // 找到右邊的雙引號,已經不在雙引號裡面了。
                tmpIndex ++;
                isInDoubleQuote = false;
            }

            else if (!isInDoubleQuote && ch=='>') { // img 標籤結束 >
                tmpIndex ++;
                return tmpIndex;
            }
            // img 標籤結束 />
            else if (!isInDoubleQuote && ch=='/' && tmpIndex + 1 < originHtmlLength && originHtml.charAt(tmpIndex+1)=='>') {
                tmpIndex += 2;
                return tmpIndex;
            }
            else {
                tmpIndex ++;
            }
        }
        return tmpIndex;
    }
}