첫번째 정리 : http://knight76.tistory.com/entry/프로세싱-언어와-PDE-코드-살펴보기-1

 

2번째 내용 시작

process/app/src 소스를 보면서 접근했다. JDI 코드 부분을 제외한 전반적인 코드를 분석한다.

 

1. Base, Editor 클래스

main 클래스는 processing.app.Base.java 이다.

Base 클래스의 createAndShowGUI() 메서드는 간단하게 버전 정도를 확인하고 swing component를 생성하고 나서 jdk 의 버전을 확인한다. 그리고, 설정 정보(윈도우 크기 등)을 읽는다. OS에 맞는 환경(platform)을 잘 생성하여 어디에서도 문제없이 동작하게 한다. 임시 디렉토리도 확인한 후, Base 클래스의 생성자를 호출한다.

 

* Base 클래스의 main 함수와 관련 메서드

public class Base {


  static public void main(final String[] args) {
    EventQueue.invokeLater(new Runnable() {
        public void run() {
          createAndShowGUI(args);
        }
    });
  }

  static private void createAndShowGUI(String[] args) {
    try {
      File versionFile = getContentFile("lib/version.txt");
      if (versionFile.exists()) {
        String version = PApplet.loadStrings(versionFile)[0];
        if (!version.equals(VERSION_NAME)) {
          VERSION_NAME = version;
          RELEASE = true;
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }

    initPlatform();

    // Use native popups so they don't look so crappy on osx
    JPopupMenu.setDefaultLightWeightPopupEnabled(false);

    // Don't put anything above this line that might make GUI,
    // because the platform has to be inited properly first.

    // Make sure a full JDK is installed
    initRequirements();

    // run static initialization that grabs all the prefs
    Preferences.init(null);

//    String filename = args.length > 1 ? args[0] : null;
    if (!SingleInstance.exists(args)) {
      SingleInstance.createServer(platform);

      // Set the look and feel before opening the window
      try {
        platform.setLookAndFeel();
      } catch (Exception e) {
        String mess = e.getMessage();
        if (mess.indexOf("ch.randelshofer.quaqua.QuaquaLookAndFeel") == -1) {
          System.err.println("Non-fatal error while setting the Look & Feel.");
          System.err.println("The error message follows, however Processing should run fine.");
          System.err.println(mess);
        }
      }

      // Create a location for untitled sketches
      try {
        untitledFolder = Base.createTempFolder("untitled", "sketches");
        untitledFolder.deleteOnExit();
      } catch (IOException e) {
        //e.printStackTrace();
        Base.showError("Trouble without a name",
                       "Could not create a place to store untitled sketches.\n" +
                       "That's gonna prevent us from continuing.", e);
      }

//  System.out.println("about to create base...");
      new Base(args);
//  System.out.println("done creating base...");
    }
  }

 

Base 클래스의 생성자에서는 스케치 북 (프로세싱 디렉토리) 폴더를 확인한다. mode 디렉토리 안에 있는  jar나 zip으로 끝나는 파일들을 모아서 classpath에 넣어서 컴파일시 유용하게 사용할 수 있도록 한다. java/android/javascript를 지원하고 있다.

OS에 맞는 UI (look & Feel)에 맞게 Editor 클래스(JFrame)을 초기화한다. 프로세싱 pde 툴을 작업하고 종료했으면, 기존에 저장된 소스를 불러온다. 만약 그런 경우가 없다면 빈 화면만 보이게 한다.

만약  PDE 개발툴의 업데이트가 존재하면 알려준다.

public Base(String[] args) {
    // Get the sketchbook path, and make sure it's set properly
    determineSketchbookFolder();

    // Delete all modes and tools that have been flagged for deletion before
    // they are initialized by an editor.
    ArrayList<InstalledContribution> contribs = new ArrayList<InstalledContribution>();
    contribs.addAll(ModeContribution.list(this, getSketchbookModesFolder()));
    contribs.addAll(ToolContribution.list(getSketchbookToolsFolder(), false));
    for (InstalledContribution contrib : contribs) {
      if (ContributionManager.isFlaggedForDeletion(contrib)) {
        removeDir(contrib.getFolder());
      }
    }

    buildCoreModes();
    rebuildContribModes();

    libraryManagerFrame = new ContributionManagerDialog("Library Manager",
                                                        new ContributionListing.Filter() {
      public boolean matches(Contribution contrib) {
        return contrib.getType() == Contribution.Type.LIBRARY
            || contrib.getType() == Contribution.Type.LIBRARY_COMPILATION;
      }
    });
    toolManagerFrame = new ContributionManagerDialog("Tool Manager",
                                                     new ContributionListing.Filter() {
      public boolean matches(Contribution contrib) {
        return contrib.getType() == Contribution.Type.TOOL;
      }
    });
    modeManagerFrame = new ContributionManagerDialog("Mode Manager",
                                                     new ContributionListing.Filter() {
      public boolean matches(Contribution contrib) {
        return contrib.getType() == Contribution.Type.MODE;
      }
    });
    updateManagerFrame = new ContributionManagerDialog("Update Manager",
                                                       new ContributionListing.Filter() {
      public boolean matches(Contribution contrib) {
        if (contrib instanceof InstalledContribution) {
          return ContributionListing.getInstance().hasUpdates(contrib);
        }

        return false;
      }
    });

    // Make sure ThinkDifferent has library examples too
    defaultMode.rebuildLibraryList();

    // Put this after loading the examples, so that building the default file
    // menu works on Mac OS X (since it needs examplesFolder to be set).
    platform.init(this);

    toolsFolder = getContentFile("tools");

//    // Get the sketchbook path, and make sure it's set properly
//    determineSketchbookFolder();

//    // Check if there were previously opened sketches to be restored
//    boolean opened = restoreSketches();
    boolean opened = false;

    // Check if any files were passed in on the command line
    for (int i = 0; i < args.length; i++) {
      String path = args[i];
      // Fix a problem with systems that use a non-ASCII languages. Paths are
      // being passed in with 8.3 syntax, which makes the sketch loader code
      // unhappy, since the sketch folder naming doesn't match up correctly.
      // http://dev.processing.org/bugs/show_bug.cgi?id=1089
      if (isWindows()) {
        try {
          File file = new File(args[i]);
          path = file.getCanonicalPath();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      if (handleOpen(path) != null) {
        opened = true;
      }
    }

    // Create a new empty window (will be replaced with any files to be opened)
    if (!opened) {
//      System.out.println("opening a new window");
      handleNew();
//    } else {
//      System.out.println("something else was opened");
    }

    // check for updates
    if (Preferences.getBoolean("update.check")) {
      new UpdateCheck(this);
    }
  }

 

Editor 클래스는 JFrame을 상속했으며, 화면 UI를 담당한다.

public abstract class Editor extends JFrame implements RunnerListener

에디터의 기본 내용들은 processing.app 패키지에 담겨져 있다.

 

2. Contribution

여기에 기존 library외에 특정 폴더에 lib 또는 zip파일을 복사하면 프로세싱 pde 툴이 시작하면서 그 파일들을 모두 읽는다. 이런 파일들을 processing 툴에서는 contribution이라는 단어를 써서 하고 있다. 파일을 다운받고 설치할 수 있기도 하고, 메뉴에서 refresh 해서 다시 library 정보를 취합할 수 있다. 관련 내용은 processing.app.contrib.ContributionManager.java 과 processing.app.contrib.Contribution.java 에 담겨있다.

 

3. Process

안드로이드환경에서 avd를 생성하거나 출력, adb, keytool, mv 명령을 내릴 때 필요한 정보들은 processing.app.exec 패키지에 담겨 있다.

 

4. Platform

linux, mac, window 에 맞는 UI를 실행시키거나 OS-dependent한 코드들을 실행할 때 필요한 내용들이 담겨 있다. 예를 들어 윈도우의 경우 폴더를 실행시킬 때, explorer를 실행시켜야 한다. 또한 msvcrt.dll(printf와 같은 stdio/stdout에 해당되는 c 런타임 라이브러리로서 sta)를 jna로 연결한다. 그리고, 마우스 오른쪽 버튼 연동을 하여 자바 Swing 어플에서 폴더를 열 때, 윈도우에서 폴더 열어서 이것 저것(예, 폴더 생성)을 할 수 있게 처리하는 코드가 담겨 있다.

관련 패키지는 processing.app.windows, processing.app.linux, processing.app.macosx  이다.

 

5. Syntax

processing.app.DefaultInputHander 클래스 소스에서는 Editor를 사용하기 때문에 키보드로부터 다양한 입력이 들어오는데. 미리 저장해둔 키보드 액션 값(Ctrl + C와 같은 복사)인지 코드(Code)인지를 분간한다. 또한, 예전 JEdit Syntax (syntax.jedit.org) 의 Code highlight 형식을 적용하여 예쁘게 색칠해 준다. 키워드일 때는 색깔을 입혀 코드 작성자가 깜박하지 않도록 도와준다.

코드를 토큰을 줄 단위로 나눈 후 토큰을 토큰 리스트(linked list)로 연결했다.

 

관련 코드는 processing.app.syntax에 있다.

 

6. tools

소스를 압축하고, 색깔을 선택하거나, 인코딩, 폰트 설정과 같은 Tool 성격의 클래스가 담겨져 있다.

 

7, mode

프로세싱은 android, javascript, java 중 세가지 모드로 export할 수 있는 코드로 구성되어 있다.

이 중 특별하게 javascript mode는 javascript 코드를 작성하면, 이를 processing.js 파일로 만든 후, 소스는 그냥 PApplet 클래스로 해서  웹 브라우져로 보여주도록 코드 작성이 되어 있다. processing.mode.javascript 패키지에 내용이 담겨 있다.

그리고, android mode는 avd도 연동할 수 있도록 편리한 클래스를 제공하고 있다. processing.mode.android 패키지에 잘 설명이 되어 있다. 완전히 안드로이드 향에서 동작되어 있는 코드로 되어 있다. 자세한 것은 안보고 패쓰.. 중요한 것은 java에 있으니 그곳으로 집중.

마지막으로 남은  java mode를 살펴본다. java mode에서는 antlr과 java debug interface를 사용하고 있었다. (사실 이 것 때문에 소스 분석한 것이긴 하다.)

 

먼저 빌드(app/build.xml) 소스를 확인해 본다.

가정 먼저 antlr 문법에 맞게 전처리 작업을 전행한다. processing/mode/java/preporc/java15.g 파일을 읽어서 소스 generate 한다. 그 다음 같은 디렉토리에 있는 pde.g 파일을 읽고 소스를 generate 한다.

 

 

  <property name="generated"
        value="${basedir}/generated/processing/mode/java/preproc" />
  <mkdir dir="${generated}" />

  <property name="grammars"
        value="${basedir}/src/processing/mode/java/preproc" />

 

……………..

<target name="preproc" description="Compile ANTLR grammar">
    <antlr target="${grammars}/java15.g" outputdirectory="${generated}">
      <classpath path="${antlrjar}" />
    </antlr>
    <antlr target="${grammars}/pde.g"
       outputdirectory="${generated}"
       glib="${grammars}/java15.g">
      <classpath path="${antlrjar}" />
    </antlr>
  </target>

  <target name="compile" depends="preproc" description="Compile sources">
    <condition property="core-built">
      <available file="../core/core.jar" />
    </condition>
    <fail unless="core-built"
      message="Please build the core library first and make sure it sits in ../core/core.jar" />

    <mkdir dir="bin" />

 

java15.g는 antlr이 이해할 수 있는 java1.5 언어 문법 파일이다. 이 파일이 antlr에 의해서 생성되는 클래스는 파서(class JavaRecognizer extends Parser) 과 스캐너 (class JavaLexer extends Lexer) 이다. 그리고, pde.g 은 antlr에 의해서 파서(class PdeRecognizer extends JavaRecognizer)와 스캐너 (class PdeLexer extends JavaLexer)와 토큰에 대한 정의 정보 (interface PdeTokenTypes)가 생성된다.

컴파일된 클래스는 아래 위치에서 확인해 볼 수 있었다.

G:\android\workspace\processing\app\bin\processing\mode\java\preproc

 

프로세싱 PDE에서 application 또는 applet으로 export 할 때, 빌드가 일어나고 실행할 수 있게 되어 있다. processing.mode.java.JavaBuild.java가 중요 핵심 클래스가 된다. 

processing.mode.java.JavaBuild 클래스의 exportApplication() 메서드에서 application빌드하고  exportApplication(File destFolder, int exportPlatform,int exportBits) 메서드에서 실행을 한다.

만약 윈도우라면 아래와 같이 자바가 실행이 된다.


if (exportPlatform == PConstants.WINDOWS) {
      if (exportBits == 64) {
        // We don't yet have a 64-bit launcher, so this is a workaround for now.
        File batFile = new File(destFolder, sketch.getName() + ".bat");
        PrintWriter writer = PApplet.createWriter(batFile);
        writer.println("@echo off");
        writer.println("java -Djava.ext.dirs=lib -Djava.library.path=lib " + sketch.getName());
        writer.flush();
        writer.close();
      } else {
        Base.copyFile(mode.getContentFile("application/template.exe"),
                      new File(destFolder, sketch.getName() + ".exe"));
      }
    }

 

빌드과정을 조금 따라가 본다.

processing.mode.java.JavaBuild.java 파일의 build 파일이 실행되는 시점부터 코드를 따라간다. 먼저 전처리 과정을 한 후, 컴파일을 한다.

  public String build(File srcFolder, File binFolder) throws SketchException {
    this.srcFolder = srcFolder;
    this.binFolder = binFolder;

//    Base.openFolder(srcFolder);
//    Base.openFolder(binFolder);
   
    // run the preprocessor
    String classNameFound = preprocess(srcFolder);

    // compile the program. errors will happen as a RunnerException
    // that will bubble up to whomever called build().
//    Compiler compiler = new Compiler(this);
//    String bootClasses = System.getProperty("sun.boot.class.path");
//    if (compiler.compile(this, srcFolder, binFolder, primaryClassName, getClassPath(), bootClasses)) {
    if (Compiler.compile(this)) {
      sketchClassName = classNameFound;
      return classNameFound;
    }
    return null;
  }

 

processing.mode.java.preproc.PdePreprocessor.preprocess() 메서드가 호출되면, 파서가 가장 먼저 동작한다. 파서가 만든 AST tree를 traverse할 수 있는 pdeEmitter를 사용한다. PrintWriter 객체에 java로 컴파일가능한 소스를 생성한다. 그리고 classpath 에 library를 추가하고, 임시 파일을 생성(Base.saveFile()호출)한다. 그리고, 원래대로 돌아가 컴파일 하는 구조이다.


8. JDI (java debugger interface)
JPDA의 하이 레벨의 인터페이스인 JDI를 정의하는 코드가 processing.mode.java.runner 패키지에 담겨있다.

어플리케이션에서 run 또는 present를 실행시 JavaMode클래스의 handleRun메서드를 실행하고,
Runner  클래스의   launchVirtualMachine()메소드 실행으로 이어진다.
실제 어플리케이션에서 어플리케이션을 실행시키면서 jpda로 연결해서 테스트가 가능할 수 있도록 한다.


 Runner 클래스의 launch 메서드는 다음처럼 먼저 vm을 실행시키고, trace 를 생성한다.

public void launch(boolean presenting) {
    this.presenting = presenting;
   
    // all params have to be stored as separate items,
    // so a growable array needs to be used. i.e. -Xms128m -Xmx1024m
    // will throw an error if it's shoved into a single array element
    //Vector params = new Vector();

    // get around Apple's Java 1.5 bugs
    //params.add("/System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Commands/java");
    //params.add("java");
    //System.out.println("0");

    String[] machineParamList = getMachineParams();
    String[] sketchParamList = getSketchParams();

    vm = launchVirtualMachine(machineParamList, sketchParamList);
    if (vm != null) {
      generateTrace(null);
//      try {
//        generateTrace(new PrintWriter("/Users/fry/Desktop/output.txt"));
//      } catch (Exception e) {
//        e.printStackTrace();
//      }
    }
  }




VM 실행에 대한 코드이다.  debug에 대한 정보가 보인다.


  protected VirtualMachine launchVirtualMachine(String[] vmParams,
                                                String[] classParams) {
    //vm = launchTarget(sb.toString());
    LaunchingConnector connector = (LaunchingConnector)
      findConnector("com.sun.jdi.RawCommandLineLaunch");
    //PApplet.println(connector);  // gets the defaults

    //Map arguments = connectorArguments(connector, mainArgs);
    Map arguments = connector.defaultArguments();

    Connector.Argument commandArg =
      (Connector.Argument)arguments.get("command");
    // Using localhost instead of 127.0.0.1 sometimes causes a
    // "Transport Error 202" error message when trying to run.
    // http://dev.processing.org/bugs/show_bug.cgi?id=895
    String addr = "127.0.0.1:" + (8000 + (int) (Math.random() * 1000));
    //String addr = "localhost:" + (8000 + (int) (Math.random() * 1000));
    //String addr = "" + (8000 + (int) (Math.random() * 1000));

    String commandArgs =
      "java -Xrunjdwp:transport=dt_socket,address=" + addr + ",suspend=y ";
    if (Base.isWindows()) {
      commandArgs =
        "java -Xrunjdwp:transport=dt_shmem,address=" + addr + ",suspend=y ";
    } else if (Base.isMacOS()) {
      commandArgs =
        "java -d" + Base.getNativeBits() + //Preferences.get("run.options.bits") + 
        " -Xrunjdwp:transport=dt_socket,address=" + addr + ",suspend=y ";
    }

    for (int i = 0; i < vmParams.length; i++) {
      commandArgs = addArgument(commandArgs, vmParams[i], ' ');
    }
    if (classParams != null) {
      for (int i = 0; i < classParams.length; i++) {
        commandArgs = addArgument(commandArgs, classParams[i], ' ');
      }
    }
    commandArg.setValue(commandArgs);

    Connector.Argument addressArg =
      (Connector.Argument)arguments.get("address");
    addressArg.setValue(addr);

    //PApplet.println(connector);  // prints the current
    //com.sun.tools.jdi.AbstractLauncher al;
    //com.sun.tools.jdi.RawCommandLineLauncher rcll;

    //System.out.println(PApplet.javaVersion);
    // http://java.sun.com/j2se/1.5.0/docs/guide/jpda/conninv.html#sunlaunch

      return connector.launch(arguments);






trace를 생성하는 메서드이다. VM에 대해서 debug trace를 걸고, event thread 를 생성한다.
VM을 띄운 후, 어플리케이션 출력코드를 다 받아와 editor에 화면에 보여줄 수 있도록 한다.

  /**
   * Generate the trace.
   * Enable events, start thread to display events,
   * start threads to forward remote error and output streams,
   * resume the remote VM, wait for the final event, and shutdown.
   */
  protected void generateTrace(PrintWriter writer) {
    vm.setDebugTraceMode(debugTraceMode);

    EventThread eventThread = null;
    //if (writer != null) {
    eventThread = new EventThread(this, vm, excludes, writer);
    eventThread.setEventRequests(watchFields);
    eventThread.start();
    //}

    //redirectOutput();

    Process process = vm.process();

//  processInput = new SystemOutSiphon(process.getInputStream());
//  processError = new MessageSiphon(process.getErrorStream(), this);

    // Copy target's output and error to our output and error.
//    errThread = new StreamRedirectThread("error reader",
//        process.getErrorStream(),
//        System.err);
    MessageSiphon ms = new MessageSiphon(process.getErrorStream(), this);
    errThread = ms.getThread();

    outThread = new StreamRedirectThread("output reader",
        process.getInputStream(),
        System.out);

    errThread.start();
    outThread.start();

    vm.resume();
    //System.out.println("done with resume");

    // Shutdown begins when event thread terminates
    try {
      if (eventThread != null) eventThread.join();
//      System.out.println("in here");
      // Bug #852 tracked to this next line in the code.
      // http://dev.processing.org/bugs/show_bug.cgi?id=852
      errThread.join(); // Make sure output is forwarded
//      System.out.println("and then");
      outThread.join(); // before we exit
//      System.out.println("out of it");

      // At this point, disable the run button.
      // This happens when the sketch is exited by hitting ESC,
      // or the user manually closes the sketch window.
      // TODO this should be handled better, should it not?
      if (editor != null) {
        editor.deactivateRun();
      }
    } catch (InterruptedException exc) {
      // we don't interrupt
    }
    //System.out.println("and leaving");
    if (writer != null) writer.close();
  }




전형적인 JPDA FE 쓰레드를 사용하고 있으며, 어플리케이션에서 어플리케이션을 띄우고 관련 로그를 부모 어플리케이션으로 보내거나 할 때 많이 유용하다. eclipse 의 어플리케이션 디버그도 이와 비슷하다.



(출처 : http://openjdk.java.net/groups/serviceability/jpdaFEThreads.jpg)
                                               

Posted by '김용환'
,