使用servlet建立在线评测系统(1)

        呵呵,很就之前就想在CSDN上发些文章,无奈水平有限,没什么好说的,笔者今年大三了,现在着手做我平生第一个软件(算是比较正式的,因为正式答应帮学校做,小玩意我也做过,大多为了讨好老婆^_^),为我们学校建立一个ACM在线评测系统,在这里我想把做这个系统的整个过程记录下来,毕竟是我第一个做的系统,也为了这样能够督促下自己,不要放弃.

        关于ACM竞赛,应该会有很多人听过并且参加过,我是在大二刚开始听说的,那是本学院第一次举办编程竞赛,我不小心拿了个二等奖,不过现在回想起来,那界竞赛的水平真的非常得低,最难的题也不过是处理下字符串,算法也基本没什么涉及,不过刚学完C还在上学校的C++课程的我也只能做出三题而已,现在想起来,短短一年时间,水平真的有天渊之别,友队这次杀入了成都赛区和北京赛区的决赛并且获奖,本队只差了四名没能去成,真是羡慕他们,呵呵.

       建立ACM在线评测系统的想法早在我第一次在网上答题便产生了,那时候去的是汕头大学的ACM网站,如果大家感兴趣,我把地址给大家http://acm.stu.edu.cn/,不过这个想法对于当时的我实在太困难了,我只在初高中玩过做网页,所谓玩,不过表格,填色,CSS,仅此而已,我是个好奇心不太强烈的人,并没有因为做网页而涉入ASP编程,的确有点可惜了初高中的大好时光.后来教练老师也有这个想法,问过我和几个同学,我们都觉得太过困难,没有答应下来.

        直到后来学习JAVA的时候学到了Servlet技术和JSP技术,才对如何做这么一个系统有了大致的想法,又逢教练老师下决心要做这个在线评测系统,原来负责的同学又遇到很多技术上的问题,我就加入了开发这个系统的小组,没想到同学分析了下需求,后来的系统概要设计,数据库设计,到一些关键性的技术都成了我的工作,现在他们回家过年,也只有我留在学校继续开发这个系统.虽然我对于servlet和jsp都属于一知半解的水平,但是有句话说得不错,程序员的工作就是学习,研究,应用,再学习,研究,应用.想想光是这么多技术,又有谁在不用的情况下去把每一样都学会呢,呵呵.

         照理,我应该按照一个项目的开发过程,从需求分析,概要设计,详细设计一步步说明,不过今天刚解决了我最没有把握的问题---关于怎么调用GCC和FPC编译器的问题,前面的部分,在以后的开发过程记录中慢慢分析好了,因为我做得很粗糙,随时都会修改自己的想法(哈哈,如果是大型系统,我就死定了).

        首先是使用JAVA,怎么在一个应用程序中调用其他的可执行程序的问题,执行可执行程序.在JAVA中,这个是由JAVA运行时环境完成的,那么怎么让应用程序和运行时环境相互作用呢?JAVA就提供了一个Runtime类.在JDK的文档中,关于这个类有以下说明:

Every Java application has a single instance of class Runtime that allows the application to interface with the environment in which the application is running. The current runtime can be obtained from the getRuntime method.

         就是说每个application都有一个Runtime的实例和当前运行该application的环境产生交互作用,如果要调用另外的可执行文件,就应该先获得这个实例,通过exec()方法来调用文件.

        下面是一个调用可执行文件的代码示例:

public static String[] runCommand(String cmd) throws IOException
    {
        // set up list to capture command output lines
        String str;
        ArrayList list = new ArrayList();

        // start command running
        Process proc = Runtime.getRuntime().exec(cmd);
        // get command's output stream and
        // put a buffered reader input stream on it
        InputStream istr = proc.getInputStream();
        BufferedReader br =new BufferedReader(new InputStreamReader(istr));

        // read output lines from command
        while ((str = br.readLine()) != null) {
            list.add(str);
        }
        // wait for command to terminate
        try {
            proc.waitFor();
        }
        catch (InterruptedException e) {
            System.err.println("process was interrupted");
        }
        // check its exit value
        if (proc.exitValue() != 0) {
            System.err.println("exit value was non-zero");
        }
        // close stream
        br.close();
        // return list of strings to caller
        return (String[])list.toArray(new String[0]);
    }

     这段代码是我参考别人的一篇BLOG以后实现的,cmd参数就是要执行的文件的路径名.

        解决了调用的问题后,剩下的就是FPC和GCC的参数问题了.GCC还好,可以去http://www.gnu.org/manual/manual.html找使用手册,FPC我就不知道哪有手册了,最后想出的办法是,我在PKU的ACM在线系统页面(http://acm.pku.edu.cn)上下载了他们的免费版在线系统实现,呵呵,国内各高校中只有PKU的系统也是用servlet技术实现的,怪不得只有他们能提供下载版.据说这个系统是PKU的老师带头带着几个学生做的,真是比我孤家寡人要幸福多了,还好有他们的成果可以作为实现参考,终于在PKU系统的属性文档里找到了他们调用FPC的参数,虽然不全,但是极其类似的系统,他们这么调用,我自然也可以这么调用了,这是FPC的调用参数:

PascalCompileCmd=C/://JudgeOnline//bin//fpc//fpc.exe -Sg -dONLINE_JUDGE %PATH%%NAME%.%EXT%

        这是直接写在文档内的,如果你觉得"/"多的话请参考JAVA 的IO,其实和C++的一样,"/"被当作转义符使用了.

        在PKU的实现中,调用编译器使用了一个com.exe的shell,我不清楚这个是否必须是这样,先依样画个葫芦,是否能够直接调用编译器,就等这篇文章写完后试一下,留待下次再说吧.为了方便调试,我今天用JAVA做了个记事本,直接编译在里面写的代码,下面是记事本的源代码:

package runtest;

import java.io.*;
       
import java.awt.BorderLayout;
import java.awt.Dimension;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class ExtraEditor extends JFrame {
    private final static String FILE_PATH="D://CompileTest";
    private final static String FILE_NAME="Main";
    private final static String CPLUS_FILE_EXT="cpp";
    private static int sequenceNumber;
    JPanel contentPane;
    BorderLayout borderLayout1 = new BorderLayout();
    JMenuBar menuBar = new JMenuBar();
    JScrollPane textScrollPane = new JScrollPane();
    JMenu menuFile=new JMenu("文件");
    JMenu menuEdit=new JMenu("编辑");
    JMenu menuRun=new JMenu("运行");
    JMenu menuHelp=new JMenu("帮助");
    JMenuItem fileNew=new JMenuItem("新建(N)",'N');
    JMenuItem fileOpen=new JMenuItem("打开(O)",'O');
    JMenuItem fileSave=new JMenuItem("保存(S)",'S');
    JMenuItem fileSaveAs=new JMenuItem("另存为(A)",'A');
    JMenuItem fileClose=new JMenuItem("关闭(C)",'C');
    JMenuItem applicationExit=new JMenuItem("退出(X)",'X');
    JCheckBoxMenuItem editAutoWrap=new JCheckBoxMenuItem("自动换行");
    JMenuItem runComplieFile=new JMenuItem("编译文件(F)",'F');
    JMenuItem runRunProcess=new JMenuItem("运行(R)",'R');
    JMenuItem helpShowManual=new JMenuItem("使用手册(H)",'H');
    JTextArea sourceArea = new JTextArea();

    public ExtraEditor() {
        try {
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            jbInit();
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    /**
     * Component initialization.
     *
     * @throws java.lang.Exception
     */
    private void jbInit() throws Exception {
        sequenceNumber=1;
        contentPane = (JPanel) getContentPane();
        contentPane.setLayout(borderLayout1);
        this.setJMenuBar(menuBar);
        setSize(new Dimension(800, 600));
        setTitle("记事本-plus");
        sourceArea.setText("jTextArea1");
        runComplieFile.addActionListener(new
                ExtraEditor_runComplieFile_actionAdapter(this));
        menuBar.add(menuFile);
        menuBar.add(menuEdit);
        menuBar.add(menuRun);
        menuBar.add(menuHelp);
        menuFile.add(fileNew);
        menuFile.add(fileOpen);
        menuFile.add(fileClose);
        menuFile.addSeparator();
        menuFile.add(fileSave);
        menuFile.add(fileSaveAs);
        menuFile.addSeparator();
        menuFile.add(applicationExit);
        menuEdit.add(editAutoWrap);
        menuRun.add(runComplieFile);
        menuRun.add(runRunProcess);
        menuHelp.add(helpShowManual);
        menuFile.getPopupMenu().setLightWeightPopupEnabled(false);
        menuEdit.getPopupMenu().setLightWeightPopupEnabled(false);
        menuRun.getPopupMenu().setLightWeightPopupEnabled(false);
        menuHelp.getPopupMenu().setLightWeightPopupEnabled(false);
        contentPane.add(textScrollPane, java.awt.BorderLayout.CENTER);
        textScrollPane.getViewport().add(sourceArea);
        sourceArea.setText("");
    }

    private void fileDelete(File fileToDelete)
    {
        if(fileToDelete.isFile())
        {
            fileToDelete.delete();
            return;
        }
        else
        {
            File[] files=fileToDelete.listFiles();
            for(int i=0;i<files.length;i++)
            {
                fileDelete(files[i]);
            }
            fileToDelete.delete();
        }
    }
   
    private File makeDirs()
    {
        File fileToDelete;
        if(sequenceNumber==1)
        {
            fileToDelete=new File((new StringBuilder()).append(FILE_PATH).toString());
            File[] files=fileToDelete.listFiles();
            for(int i=0;i<files.length;i++)
            {
                fileDelete(files[i]);
            }
            //System.out.println("Delete the files1 and sequenceNumber:"+sequenceNumber);
        }
        else
        {
            fileToDelete=new File((new StringBuilder()).append(FILE_PATH).append("//").append(sequenceNumber-1).toString());
            fileDelete(fileToDelete);
            //System.out.println("Delete the files2 and sequenceNumber:"+sequenceNumber);
        }
        File filePath=new File((new StringBuilder()).append(FILE_PATH).append("//").append(sequenceNumber).toString());
        if(!filePath.mkdirs())
        {
            System.err.println("Can't build the directory");
            return null;
        }
        sequenceNumber++;
        return filePath;
    }
   
    private String doCmd(String initCmd,String toBeInsteaded,String insteadOf)
    {
        int i=0;
        int j=initCmd.indexOf(toBeInsteaded);
        String cmd="";
//        System.out.println("initCmd: "+initCmd);
        while((j=initCmd.indexOf(toBeInsteaded,i)) != -1)
        {
            cmd=(new StringBuilder()).append(cmd).append(initCmd.substring(i,j)).append(insteadOf).toString();
            i=j+toBeInsteaded.length();
        }
        return (new StringBuilder()).append(cmd).append(initCmd.substring(i)).toString();
    }
   
    public void runComplieFile_actionPerformed(ActionEvent e) {
        File filePath;
        File fileToBeCompiled;
        if((filePath=makeDirs())!=null)
        {
            //makeFile();
            fileToBeCompiled=new File((new StringBuilder()).append(filePath.getAbsolutePath()).append("//"+FILE_NAME).append(".").append(CPLUS_FILE_EXT).toString());
            try{
                fileToBeCompiled.createNewFile();
                FileOutputStream fileOutputStream=new FileOutputStream(fileToBeCompiled);
                fileOutputStream.write(sourceArea.getText().getBytes());
                fileOutputStream.flush();
                fileOutputStream.close();
            }
            catch(IOException ex){
                System.err.println(ex.getMessage());
            }
            //excuteComplie();
            String compileCmd="C://JudgeOnline//bin//gcc//bin//g++.exe -fno-asm -s -w -O1 -DONLINE_JUDGE -o %PATH%%NAME% %PATH%%NAME%.%EXT%";
            String comShell="C://JudgeOnline//bin//com.exe";
            String path=(new StringBuilder()).append(filePath).append("//").toString();
//            System.out.println(path);
            compileCmd=doCmd(compileCmd,"%PATH%",path);
//            System.out.println(complieCmd+"1");
            compileCmd=doCmd(compileCmd,"%NAME%",FILE_NAME);
//            System.out.println(complieCmd+"2");
            compileCmd=doCmd(compileCmd,"%EXT%",CPLUS_FILE_EXT);
//           System.out.println(complieCmd);
            try{
                Runtime runtime=Runtime.getRuntime();
                Process process=runtime.exec(comShell);
                OutputStream compileStream=process.getOutputStream();
                compileStream.write((new StringBuilder()).append(compileCmd).append("/n").toString().getBytes());
                compileStream.flush();
                compileStream.close();
                BufferedReader errorMessageReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                String errorMessageLine;
                String errorMessage="";
                for(long i=System.currentTimeMillis();i+10000>System.currentTimeMillis()&&(errorMessageLine=errorMessageReader.readLine())!=null;)
                    errorMessage=(new StringBuilder()).append(errorMessage).append(errorMessageLine).toString();
                System.out.println(errorMessage);
                errorMessageReader.close();
                try{
                    process.waitFor();
                }
                catch(InterruptedException ie){}
            }
            catch(IOException ex)
            {
                System.err.println(ex.getMessage());
            }
            //showmessage
        }
    }
}


class ExtraEditor_runComplieFile_actionAdapter implements ActionListener {
    private ExtraEditor adaptee;
    ExtraEditor_runComplieFile_actionAdapter(ExtraEditor adaptee) {
        this.adaptee = adaptee;
    }

    public void actionPerformed(ActionEvent e) {
        adaptee.runComplieFile_actionPerformed(e);
    }
}

        我只实现了"编译文件"一个按钮的功能,并且只编译C++,其他的也要看以后有没有兴趣做下去,不过脑子里突然有了个想法,如果实现一个线程来进行提示,另一个线程来在编写代码的时候纠错,把组件都换换,要做一个IDE出来似乎也并不是非常困难的事情,不是吗?呵呵.

        在调用G++编译器之前,建立一个名字为Main.cpp的文件,从组件里面抽取出源代码:fileOutputStream.write(sourceArea.getText().getBytes());并写入文件,如此,就可以直接调用G++编译这个CPP文件了,这和IDE工具的调用方式是一样的.创建文件的功能由makeDirs()来实现.

        调用编译器的问题解决以后,最困难的部分就是判定结果是否符合要求了,要解决这个问题,要先分析ACM的答题规则,再设计算法解决问题,这个问题,就在下一篇中讲吧,我还没有开始做呢.

                                                                                                                                    作者:enigma_zhou

                                                                                               如果您要转载,请与我联系enigma_zhou@163.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值