2008년 12월 10일 수요일

JAVA의 Collections.sort


리스트를 분류하고 섞기 위한 COLLECTIONS의 메소드 이용하기

java.util.Collections 클래스에는 List 인터페이스를 구현하는 객체를 실행하기 위해 만들어진 메소드들이 많이 있다. 이 유틸리티 클래스에는 특정 객체 리스트에서의 이진법적 검색, 한 리스트에서 다른 리스트로의 요소(elements) 복사, 리스트의 특정 위치로의 요소 변환 등을 실행하는 메소드가 포함되어 있다. 이번 테크팁에서는 Collections 클래스 중 shuffle()rotate()라는 두가지 간단한 메소드와, 리스트 안의 아이템들을 분류하는 sort() 메소드에 대해 알아본다. 아이템의 분류를 위해선 반드시 대상을 비교할 방법이 있어야 하는데, 여기서는 ComparableComparator를 사용하게 될 것이다.

다음 예제를 보자. 0부터 5까지 쓰여진 6장의 카드 한 벌이 있다. 처음 카드 상태를 보여주고 카드를2개씩 옮기게 되는데, 이것은 즉 rotate(2)를 호출하는 것이다. 이는 맨 밑의 카드 두 장을 맨 위로 올려놓게 된다. n개의 카드가 있을 때 n보다 작은 수 r개의 카드를 옮긴다. 결과적으로 n-r개의 카드를 맨 위에서 맨 밑으로 보내는 셈이 된다.

rotate(-r)를 대신 호출하면 r개의 카드를 맨 위에서 맨 아래로 옮기게 된다. 이것은 r개의 카드를 반대로 옮긴다고 생각하거나, rotate(n-r)rotate(-r)과 같다고 여길 수 있다.

이 때, 카드를 섞으면 다른 결과를 얻게 된다. Collections 클래스의 자바다큐멘테이션을 보면, 동일한 가능성을 갖는 모든 가능한 순열을 생성해내는 것으로 카드를 섞는 알고리즘을 설명하고 있다.

다음은 한 벌의 카드를 초기화하고 섞는 간단한 프로그램이다.

   import java.util.List;
   import java.util.ArrayList;
   import java.util.Collections;

   public class CutAndShuffle {   

     private static List miniDeck = new ArrayList(6);

     private static void initializeDeck() {
       for (int i = 0; i < 6; i++) {
         miniDeck.add(new Integer(i));
       }
     }

     private static void printDeck(String message) {
       System.out.println(message + "\n");
       for (int i = 0; i < 6; i++) {
         System.out.println("card " + i +
                            " = " + miniDeck.get(i));
       }
       System.out.println("============");
     }

     public static void main(String[] args) {
       initializeDeck();
       printDeck("Initialized Deck:");
       Collections.rotate(miniDeck, 2);
       printDeck("Deck rotated by 2:");
       Collections.rotate(miniDeck, -2);
       printDeck("Deck rotated back by 2:");
       Collections.shuffle(miniDeck);
       printDeck("Deck Shuffled:");
     }
   }

CutAndShuffle 프로그램을 실행하면 다음과 유사한 결과를 얻을 것이다. 앞에서 말해듯이, shuffle() 명령어를 호출한 결과는 다를 것이다.

 Initialized Deck:

   card 0 = 0
   card 1 = 1
   card 2 = 2
   card 3 = 3
   card 4 = 4
   card 5 = 5
   ============
   Deck rotated by 2:

   card 0 = 4
   card 1 = 5
   card 2 = 0
   card 3 = 1
   card 4 = 2
   card 5 = 3
   ============
   Deck rotated back by 2:

   card 0 = 0
   card 1 = 1
   card 2 = 2
   card 3 = 3
   card 4 = 4
   card 5 = 5
   ============
   Deck Shuffled:

   card 0 = 1
   card 1 = 2
   card 2 = 4
   card 3 = 5
   card 4 = 0
   card 5 = 3
   ============

Collectionsshuffle()rotate() 메소드는 요소들을 서로 비교할 수 있는지 없는지의 가능성 여부와 관계가 없다. 그러나 분류는 요소들을 비교하는 방법이 필요하다. sort() 메소드를 구현하기 위해서는 먼저 서로 다른 두개의 객체 중 누가 우선순위인지를 판단해야한다. 이를 위해선 다음과 같은 두가지 방법이 있다.

  • 분류된 대상의 타입이 java.lang.Comparable 인터페이스를 구현하는지 확인한다. 이를 위해선 다음과 같은 메소드가 들어있는 클래스가 필요하다.
         public int compareTo(Object  o)
    
  • Comparator를 실행하는 클래스를 만들고 특정 타입의 두 대상을 비교하는 규칙을 캡슐화한다.

첫번째로, o1o2Comparable를 구현하는 클래스라면, 두 대상이 동일할 때 o1.compareTo(o2)는 0이 된다. o1o2보다 작으면 음수가 되고 o2보다 크면 양수가 된다. 이 때, 숫자의 값보다 음수인지 양수인지가 중요하다. 따라서 o1.compareTo(o2)o2.compareTo(o1)의 부호는 서로 달라야 한다. 세 개의 객체가 있을 때에도 compareTo()는 기본적인 이행성 규칙을 따른다. 다시 말해 o1.compareTo(o2)>0 이고 o2.compareTo(o3)>0이면, o1.compareTo(o3)>0이다.

두번째 접근법은 Integer 클래스를 포함하고 Comparable 인터페이스를 실행하는 래퍼 클래스의 장점을 이용하는 것이다. 결과적으로 Collections.sort() 메소드를 사용하여 Integer타입의 클래스들을 포함한 List를 쉽게 분류할 수 있다. 그 예는 다음과 같다.

   import java.util.List;
   import java.util.ArrayList;
   import java.util.Collections;

   public class ShuffleAndSort {
     List miniDeck = new ArrayList(6);

     void initializeDeck() {
      for (int i = 0; i < 6; i++) {
         createCard(i);
       }
     }

     void printDeck(String message) {
       System.out.println(message);
       for (int i = 0; i < 6; i++) {
         System.out.println("card " + i +
           " = " + miniDeck.get(i));
       }
       System.out.println("============");
     }

     void createCard(int i) {
       miniDeck.add(new Integer(i));
     }

     void sort(){
       Collections.sort(miniDeck);
     }


     void exerciseDeck() {
       initializeDeck();
       Collections.shuffle(miniDeck);
       printDeck("Deck Shuffled:");
       sort();
       printDeck("Deck Sorted:");
     }


     public static void main(String[] args) {
       new ShuffleAndSort().exerciseDeck();
     }
   }

ShuffleAndCode 프로그램은 초과된 메소드를 갖는 것처럼 보인다. 물론 createCard()sort()는 반드시 포함되어야 한다. 각각의 메소드들을 분리해서 만드는 것은 ShuffleAndSort 클래스를 상속하기 쉽게 만들어준다. 어떤 객체들이 카드들에 추가되고, 카드들이 어떻게 분류되는지 수정하기 위해서는 이러한 메소드들을 오버라이딩함으로써 가능하게 된다. 이 경우 카드들이 섞이는 결과를 얻는다. 그런 다음 Collections.sort(miniDeck) 라고 불리는 메소드를 호출하여 카드를 분류할 수 있다.

ShuffleAndSort에서 나온 결과물은 반드시 다음과 같아야한다.

   Deck Shuffled:
   card 0 = 4
   card 1 = 2
   card 2 = 1
   card 3 = 3
   card 4 = 5
   card 5 = 0
   ============
   Deck Sorted:
   card 0 = 0
   card 1 = 1
   card 2 = 2
   card 3 = 3
   card 4 = 4
   card 5 = 5
   ============ 

Integer 클래스가 Comparable 인터페이스를 구현하므로 miniDeck은 자체적으로 분류가 가능하다. 또한, sort() 메소드의 다른 구현을 통해 다르게 분류할 수도 있다. 이 다른 구현은 List를 첫번째 인수로써 분류하고 Comparator을 두 번째 객체로 분류한다.ShuffleAndSortsort() 메소드를 다음과 같이 바꿀 수 있다.

   void sort(){
       Collections.sort(miniDeck,
                        Collections.reverseOrder());
   }  

이번에는 서열이 높은 카드부터 낮은 카드까지 분류해보자.

Comparable를 구현하지 않는 타입의 객체들을 분류하는 것을 상상해보자. 예를 들어 다음과 같은 Rank 클래스를 만들자.

public class Rank {

     private int rank;

     public Rank(int rank) {
       this.rank = rank;
     }

     public int getRankInt(){
       return rank;
     }

     public String toString() {
       return "" + rank;
     }
   }

이제 ShuffleAndSort에서 상속되고, Rank 타입의 객체들을 생성하는 클래스를 만들자. 이는 다음과 같다.

   public class UseRank extends ShuffleAndSort {

     void createCard(int i) {
       miniDeck.add(new Rank(i));
     }

     public static void main(String[] args) {
       new UseRank().exerciseDeck();
     }
   }

UseRank을 컴파일하고 실행하면 ClassCastException이 발생한다. 이 문제를 해결하는 데에는 두가지 방법이 있다.

  • Comparable 버전의 Rank를 만든다.
  • int타입으로 랭크를 축적하고 직접 compareTo()를 구현한다.

첫번째 해결책은 Integer클래스가 Comparable을 실행한다는 사실을 이용하여 실제 랭크를 int가 아닌 Integer로 축적하는 것이다. 그러면 compareTo() 메소드가 Integer 클래스의 compareTo()를 불러온다. 이 접근법은 다음과 같다.

   public class ComparableRank implements Comparable {

     private Integer rank;

     public ComparableRank(int rank) {
       this.rank = new Integer(rank);
     }

     public String toString() {
       return "" + rank;
     }

     public int compareTo(Object o) {
       ComparableRank cr = (ComparableRank) o;
       return (rank.compareTo(cr.rank));
     }
   }

두번째 해결책으로는, int로 랭크를 저장하고 다음과 같이 compareTo()를 구현하는 방법이 있다.

   public int compareTo(Object o) {
     ComparableRank2 cr = (ComparableRank2) o;
     return (rank - cr.rank);
   }

이 팁의 목적은 rankcr.rank 간의 차이를 잘 돌려준다는 것을 보여주는 것이다. rank의 애트리뷰트는 음수가 아니며, 카드의 사이즈가 231-1보다 작아야만 한다. 다음은 compareTo()를 구현하는 좀 더 심화된 예이다.

  public int compareTo(Object o) {
     ComparableRank2 cr = (ComparableRank2) o;
     if (rank < cr.rank) return -1;
     else return 1;
   }

이것으로 한 벌의 카드 안에서 rank 애트리뷰트의 값이 고유하다는 것을 추측해 볼 수 있다. 이와 같은 비교를 확장하여 동일성도 확인해볼 수 있게 만들 수 있다. 즉 이런 경우에는 0을 리턴시키면 된다.

두 가지 접근법에서 모두 toString()을 오버라이딩함으로써 앞에서 제시했던 예시들의 포맷과 맞는 결과를 얻을 수 있도록 해야한다. 다음은 두번째 접근법에 대한 예시이다. :

   public class ComparableRank2 implements Comparable {

     private int rank;

     public ComparableRank2(int rank) {
       this.rank = rank;
     }

     public String toString() {
       return "" + rank;
     }

     public int compareTo(Object o) {
      ComparableRank2 cr = (ComparableRank2) o;
       return (rank - cr.rank);
     }
   }

이 버전들은 createCard() 메소드의 내용을 변경하여 ComparableRank타입 또는 ComparableRank2타입의 객체를 로딩함으로써 구동될 수 있다.

두 개의 동등하지 않은 대상물이 최우선적으로 분류되도록 했던 두 번째 방법을 되새겨보자. Comparator를 실행하는 클래스를 만들고 특정 타입의 두 대상을 비교하기위한 규칙을 캡슐화한다. Rank타입의 객체를 가지고 이 방법을 사용해보자. 먼저 그 타입의 객체들을 만든다. 그리고 나서 객체들을 분류하기 위해 사용하는 Comparator를 만든다. 이 솔루션은 변환할 수 없거나 하위 분류하기 어려운 클래스들을 사용하는데 유용하다. 다음에 제시되는 RankComparator 클래스의 compareTo() 메소드는 ComparableRank2Comparator 메소드처럼 필수적이다. 이 compare() 클래스에서 지원하는 객체타입으로 캐스팅한 후 비교로직을 구현해라.

   import java.util.Comparator;

   public class RankComparator implements Comparator {
     public int compare(Object o1, Object o2) {
       Rank r1 = (Rank) o1;
       Rank r2 = (Rank) o2;
       return r1.getRankInt() - r2.getRankInt();
     }
   }

이 새로운 RankComparator를 사용하기 위해서는 UseRanksort() 메소드를 오버라이딩하고 두 번째 인수로 RankComparator를 패스하기만 하면 된다.

   import java.util.Collections;

   public class UseRankComparator extends UseRank {

     void sort() {
       Collections.sort(miniDeck, new RankComparator());
     }

     public static void main(String[] args) {
       new UseRankComparator().exerciseDeck();
     }
   }

UseRankComparator의 결과는 다음과 같아야한다.

   Deck Shuffled:
   card 0 = 0
   card 1 = 4
   card 2 = 5
   card 3 = 3
   card 4 = 1
   card 5 = 2
   ============
   Deck Sorted:
   card 0 = 0
   card 1 = 1
   card 2 = 2
   card 3 = 3
   card 4 = 4
   card 5 = 5
   ============   

List를 분류하기 위해서는 그 리스트 안의 아이템들이 반드시 비교될 수 있어야 한다는 사실을 배웠다. Comparable를 구현하는 타입의 아이템들을 사용할 수 있다. 다행히, 이것은 단순한 타입들을 랩핑하는 것을 포함한다. 두번째 접근법은 Comparator를 실행하는 클래스를 만들고 특정타입의 두 대상을 비교하기 위한 규칙을 캡슐화한다.

Collections에 대한 좀 더 많은 정보를 얻으려면, 자바 튜토이얼의 Collections trail를 참고하기 바란다.

2008년 11월 13일 목요일

XML의 소개

XML의 소개

Extensible Markup Language (XML)은 웹 브라우져를 통해 한 페이지의 내용 요소들을 구분하는 방법입니다. XML의 문법은 HTML과 비슷합니다. 사실, XML은 HTML이 쓰이는 여러 곳에서 사용될 수 있습니다. 아래에 예가 있습니다. JDC Tech Tip의 목차가 HTML이 아니라 XML로 저장되어 있다고 가정합시다. 아래와 같은 HTML 코드 대신

<html>
<body>
<h1>JDC Tech Tip Index</h1>
<ol><li>
<a
href="http://developer.java.sun.com/developer/TechTips/2000/tt0509.html#tip1">
Random Access for Files
</a>
</li></ol>
</body>
</html>

아래와 같이 나타납니다.

<?xml version="1.0" encoding="UTF-8"?>
<tips>
<author id="glen" fullName="Glen McCluskey"/>
<tip title="Random Access for Files"
     author="glen"
     htmlURL="http://developer.java.sun.com/developer/TechTips/2000/tt0509.html#tip1"
     textURL="http://developer.java.sun.com/developer/TechTips/txtarchive/May00_GlenM.txt">
</tip>
</tips>

XML과 HTML의 코드 사이의 유사성을 보십시오. 두가지 모두 문서가 계층적 엘리먼트로 구성되어 있으며, 얼레먼트는 꺾음 괄호로 구분되어 있습니다.HTML 엘리먼트가 거의 그렇듯이, XML 엘리먼트는 시작 태그와 그 뒤에 나타나는 데이터, 그리고 끝 태그로 구성되어 있습니다.

<element>element data</element>

역시 HTML과 마찬가지로, XML 엘리먼트는 속성을 지정할 수 있습니다. 위의 XML의 예에서, 각 <tip> 엘리먼트는 몇개의 속성을 갖고 있습니다. 'title' 속성은 팁의 이름을 나타내며, 'author' 속성은 저자의 이름을 나타냅니다. 그리고 'htmlURL'과 'textURL' 속성은 두가지의 다른 형식으로 저장된 팁의 링크를 나타냅니다.

두개의 마크업 언어 사이의 유사점은 HTML에서 XML로 옮겨가는데 아주 중요한 장점입니다. 어렵게 습득한 HTML 기술들을 그대로 이용할 수 있기 때문입니다. 그러나, "왜 XML로 바꾸라고 해서 고생하게 만들지?"라는 의문이 들것입니다. 이 의문에 대한 대답으로, 위의 XML 예를 다시 한번 보십시오. 그리고 문법적인 측면이 아니라 의미적인 측면에서 살펴보십시오. HTML은 문서를 어떻게 형식화하는지를 말해주지만, XML은 문서의 내용에 대해 말해줍니다. 이 능력은 매우 강력한 것입니다. XML에서, 클라이언트는 그 데이터를 자신에게 가장 적합한 형식으로 재구성할 수 있습니다. 서버가 제공하는 출력 양식에 구애받지 않습니다. 중요한것은, XML 형식은 가독성을 희생시키지 않으면서도 파서의 편의를 위해 설계되었다는 것입니다. XML은 문서의 구조에 대해 반드시 지켜야 할 약속을 가지고 있습니다. 몇가지를 보자면, 시작 태그는 언제나 끝 태그를 가지고 있어야 하며, 엘리먼트는 정확히 내포관계를 가져야 하고, 모든 속성은 값을 가져야 합니다. 이런 엄격함은 XML 문서의 파싱과 변환을 HTML의 변환에 비해서 높은 신뢰성을 갖게 합니다.

XML과 HTML의 유사성은 그 시작이 갖기 때문에 생깁니다. HTML은 강력한 마크업 언어인 SGML을 간략화한 것입니다. SGML은 개발자가 자신만의 어휘, 즉 문법/태그 등을 만들어낼 수도 있는, 거의 무엇이든지 할 수 있는 "주방의 싱크대"같은 마크업 언어입니다. HTML은 미리 정의된 어휘를 가진, SGML에 대한 아주 작은 부분입니다. 따라서 HTML은 대략 1992년 경에나 쓸만했던, 표현을 위한 요소들입니다. SGML과 HTML은 모두 문제가 있습니다. SGML은 모든것을 합니다. 그래서 아주 복잡합니다. HTML은 간단합니다. 그러나 파싱 규칙이 엉성하고, 어휘는 확장을 할 수 없습니다. 이에 비해 XML은 SGML을 합리적으로 간략화했습니다. 이것의 목적은 복잡하지 않으면서도 SGML의 중요한 목적을 모두 지원합니다. SGML이 "주방의 싱크대"라면 XML은 "스위스 아미 나이프-맥가이버칼... -_-" 입니다.

이런 장점 때문에, XML은 몇가지 응용 분야에서는 HTML을 대체하는것 이상의 일을 할 수 있습니다. 이것은 SGML을 대체할 수도 있으며, SGML의 복잡성 때문에 적용이 불가능했던 분야에 대한 새로운 방법이 될 수 있습니다. XML을 어디에 쓸 것인지에 관계없이, 프로그래밍 언어는 자바를 이용할 것입니다. 자바 언어는 Simple API for XML (SAX)와 Document Object Model (DOM) 인터페이스를 이용하여 XML 문서를 파싱하기 위한 고수준의 도구를 제공하기 때문에, XML을 직접 파싱하기 위한 자신만의 코드를 작성할 수 있습니다. SAX와 DOM 파서는 다른 몇가지 언어에서도 구현한 표준입니다. 자바 프로그래밍 언어에서, Java(tm) API for XML Parsing (JAXP)를 이용해서 이런 파서들을 인스턴스화 할 수 있습니다.

이 팁의 코드를 실행하려면, http://java.sun.com/xml/download.html에서 JAXP와 SAX/DOM 파서를 다운로드 받아야 합니다. 그리고 http://www.megginson.com/SAX/Java에서 SAX 2.0도 다운로드 받아야 합니다. 그리고 classpath를 jaxp, 파서, 그리고 sax2의 JAR 파일을 포함하도록 고치는 것을 잊지 마십시오.

 


 

 SAX API의 이용

SAX API는 XML 문서를 다루기 위한 직력 메커니즘을 제공합니다. 이것은 XML-DEV 메일링 리스트의 멤버들에 의해 서로 다른 벤더들이 구현할 수 있는 표준 인터페이스의 집합으로 개발되었습니다. SAX 모델은 파서가 문서를 읽어나가면서 마크업을 만나면 이벤트 핸들러를 호출하는 형식으로 파싱하도록 합니다. SAX의 첫번째 구현은 1998년 5월에 나왔습니다. 그리고 SAX 2.0은 2000년 5월에 나왔습니다. (이 팁에서 사용된 코드들은 SAX2용입니다.)

마크업 발생에 대한 신호를 위해 SAX2를 사용할 때 할 일은, 몇개의 메소드와 인터페이스를 작성하는 것입니다. 이 인터페이스들에서 ContentHandler 인터페이스가 가장 중요합니다. 이것은 XML 문서를 파싱하기 위한 각각의 단계들을 위한 몇개의 메소드를 선언합니다. 많은 경우, 이런 메소드 중에서 몇개만 이용하게 됩니다. 예를 들어, 아래의 코드는 하나의 ContentHandler 메소드 (startElement)만을 이용하며, 이것을 이용하여 XML Tech Tip 목록에서 HTML 페이지를 생성합니다.

    import java.io.*;
    import java.net.*;
    import java.util.*;
    import javax.xml.parsers.*;
    import org.xml.sax.*;
    import org.xml.sax.helpers.*;
    /**
     * 팁의 제목들의 목록을 보여주고 이에 대한 HTML과 text 버젼의 문서에
     * 연결하는 간단한 HTML 페이지를 생성한다.
     */
    public class UseSAX2 extends DefaultHandler
    {
        StringBuffer htmlOut;

        public String toString()
        {
            if (htmlOut != null)
                return htmlOut.toString();
            return super.toString();    
        }
    
        public void startElement(String namespace, String localName, String qName, Attributes atts)
        {
            if (localName.equals("tip")) 
            {
                String title = atts.getValue("title");
                String html = atts.getValue("htmlURL");
                String text = atts.getValue("textURL");
                htmlOut.append("<br>");
                htmlOut.append("<A HREF=");
                htmlOut.append(html);
                htmlOut.append(">HTML</A> <A HREF=");
                htmlOut.append(text);
                htmlOut.append(">TEXT</A> ");
                htmlOut.append(title);
            }
        }

        public void processWithSAX(String urlString) throws Exception
        {
            System.out.println("Processing URL " + urlString);
            htmlOut = new StringBuffer("<HTML><BODY><H1>JDC Tech Tips Archive</H1>");
            SAXParserFactory spf = SAXParserFactory.newInstance();
            SAXParser sp = spf.newSAXParser();
            ParserAdapter pa = new ParserAdapter(sp.getParser());
            pa.setContentHandler(this);
            pa.parse(urlString);
            htmlOut.append("</BODY></HTML>");
        }

        public static void main(String[] args)
        {
            try
            {
                UseSAX2 us = new UseSAX2();
                us.processWithSAX(args[0]);
                String output = us.toString();
                System.out.println("Saving result to " + args[1]);
                FileWriter fw = new FileWriter(args[1]);
                fw.write(output, 0, output.length());
                fw.flush();
            }
            catch (Throwable t)
            {
                t.printStackTrace();
            }
        }
    }

이 프로그램을 테스트하기 위해, 이 팁의 앞부분의 XML의 소개에 있는 XML 문서를 이용하거나, http://staff.develop.com/halloway/TechTips/TechTipArchive.xml에 있는 조금 더 긴 문서를 이용할 수 있습니다. 이렇게 받은 XML 문서를 컴퓨터의 로컬 디렉토리에 TechtipArchive.xml로 저장하십시오. 아래와 같은 명령어로 HTML 문서를 생성할 수 있습니다.

    java UseSAX2 file:TechTipArchive.xml SimpleList.html

그리고 SimpleList.html을 웹브라우져를 통해서 보고, 최근 팁의 텍스트 버젼과 HTML 버젼의 링크를 따라가 보십시오. (실제 시나리오는 위의 코드를 클라이언트의 브라우져나 서버의 서블릿/jsp 안에 합쳐 넣어야 할것입니다.)

위 코드에는 몇가지 흥미로운 부분이 있습니다. 파서를 생성하는 부분을 보십시오.

    SAXParserFactory spf = SAXParserFactory.newInstance();
    SAXParser sp = spf.newSAXParser();

JAXP에서, SAXParser 클래스는 직접 생성하지 않고, factory 메소드인 newSAXParser()를 이용합니다. 이것은 서로 다르게 구현된 코드들이 소스코드의 변경 없이 끼워넣기가 가능하도록 하기 위한 것입니다. factory는 이 외에도 namespace의 지원, 인증 등의 향상된 파싱 요소들을 지원합니다.JAXP 파서의 인스턴스를 가진 뒤에라도, 아직은 파싱할 준비가 되지 않았습니다. 현재의 JAXP 파서는 SAX 1.0만 지원합니다. SAX 2.0을 지원하도록 하기 위해서, 파서를 ParserAdapter로 감싸야 합니다.

    ParserAdapter pa = new ParserAdapter(sp.getParser());

ParserAdapter 클래스는 SAX2 다운로드의 일부분이며, 지금 있는 SAX1 파서에 SAX2의 기능을 추가합니다.

ContentHandler 인터페이스를 구현하는 대신, UseSAXDefaultHandler 클래스를 상속받습니다. DefaultHandler는 모든 ContentHandler의 메소드에 대해 아무것도 하지 않는 비어있는 구현을 제공하는 어댑터 클래스입니다. 따라서 오버라이드 하고싶은 메소드만 구현하면 됩니다.

startElement() 메소드가 실제 작업을 수행합니다. 프로그램은 팁을 제목 목록을 보여주면 되기 때문에, <tip> 엘리먼트만 있으면 되며, <tips>, <author> 엘리먼트는 무시합니다. startElement 메소드는 엘리먼트의 이름을 체크하고 현재 엘리먼트가 <tip>일 때에만 계속해서 작업을 수행합니다. 또 이 메소드는 엘리먼트의 속성을 Attributes 레퍼런스를 통해 접근하도록 합니다. 따라서 팁의 이름과 name, htmlURL, textURL을 추출하는 것이 쉽습니다.

이 예의 결과는 최근의 Tech Tips의 목록을 보여주는 HTML 문서입니다. HTML을 직접 만들어도 됩니다. 하지만 이것을 XML로 하고, SAX 코드를 작성하면 추가적인 유연성을 제공할 수 있습니다. 다른 누군가 Tech Tip을 날짜별로, 저자별로 정렬하거나 몇가지 제약사항을 가지고 필터링을 하고자 한다면, 각각에 대한 파싱 코드를 이용하여 하나의 XML 문서에서 해당 문서를 생성할 수 있습니다.

불행히도, XML 데이터가 복잡해짐에 따라, 위의 샘플도 점점 코딩하고 수정하기가 어려워집니다. 위의 예는 두가지 문제를 가지고 있습니다. 첫번째는, HTML 문서를 생성하기 위한 코드가 단지 문자열을 조작하는 것 뿐이고, 따라서 어딘가에 있을 '>', '/'를 놓지게 된다는 것입니다. 두번째로, SAX API는 많은 것을 기억하지 못합니다. 즉, 앞의 어딘가에 있었던 엘리먼트를 참조하고자 한다면, 앞에서 파싱한 엘리먼트를 저장하기 위한 자신만의 state machine을 만들어야 한다는 뜻입니다.

Document Object Model (DOM) API가 이 두가지 문제점을 해결합니다.

 


 

DOM API의 이용

DOM API는 SAX API와는 완전히 다른 문서 처리 모델을 기반으로 하고 있습니다. (SAX와 같이) 한번에 문서의 한 부분을 읽는 대신, DOM 파서는 문서 전체를 읽습니다. 그리고 읽고 수정할 수 있는 프로그램 코드를 만들 수 있도록 문서 전체를 트리 구조로 만듭니다. SAX와 DOM의 차이점을 간단히 말하자면, 순차적/읽기 전용 접근과 임의/읽기/쓰기 접근의 차이입니다.

DOM API의 핵심은 DocumentNode 인터페이스입니다. Document는 XML 문서를 나타내는 최상위 객체입니다. Document는 데이터를 Node의 트리 형태로 저장하고 있으며, Node는 element, attribute, 또는 다른 형태의 내용물을 저장하는 기본 타입입니다. 또, Document는 새로운 Node를 생성하기 위한 factory로도 동작합니다. Node는 트리에서의 하나의 데이터를 표현하며, 트리에 있어서의 필요한 동작을 제공합니다. 노드에 대해 그 부모/형제/자식 노드를 찾을 수 있습니다. 또 Node를 추가/삭제함으로써 문서를 수정할 수 있습니다.

DOM API의 예제를 보기 위해, 위의 SAX에서 보여졌던 XML 문서를 처리해 봅시다. 이번에는 저자별로 묶어서 출력합니다. 이것은 몇가지의 작업을 더 해야 합니다. 아래에 코드가 있습니다.

    //UseDOM.java
    import java.io.*;
    import java.net.*;
    import java.util.*;
    import javax.xml.parsers.*;
    import org.w3c.dom.*;

    public class UseDOM
    {
        private Document outputDoc;
        private Element body;
        private Element html;

        private HashMap authors = new HashMap ();
    
        public String toString ()
        {
            if (html != null)
            {
                return html.toString ();
            }
            return super.toString();
        }

        public void processWithDOM (String urlString)
            throws Exception
        {
            System.out.println ("Processing URL " + urlString);
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance ();
            DocumentBuilder db = dbf.newDocumentBuilder ();
            Document doc = db.parse (urlString);
            Element elem = doc.getDocumentElement ();
            NodeList nl = elem.getElementsByTagName ("author");
            for (int n=0; n<nl.getLength (); n++)
            {
                Element author = (Element)nl.item (n);
                String id = author.getAttribute ("id");
                String fullName = author.getAttribute ("fullName");
                Element h2 = outputDoc.createElement ("H2");
                body.appendChild (h2);
                h2.appendChild (outputDoc.createTextNode ("by " + fullName));
                Element list = outputDoc.createElement ("OL");
                body.appendChild (list);
                authors.put (id, list);
            }
            NodeList nlTips = elem.getElementsByTagName ("tip");
            for (int i=0; <nlTips.getLength (); i++)
            {
                Element tip = (Element)nlTips.item (i);
                String title = tip.getAttribute ("title");
                String htmlURL = tip.getAttribute ("htmlURL");
                String author = tip.getAttribute ("author");
                Node list = (Node) authors.get (author);
                Node item = list.appendChild (outputDoc.createElement ("LI"));
                Element a = outputDoc.createElement ("A");
                item.appendChild (a);
                a.appendChild(outputDoc.createTextNode (title));
                a.setAttribute ("HREF", htmlURL);
            }
        }

        public void createHTMLDoc (String heading)
            throws ParserConfigurationException  
        {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance ();
            DocumentBuilder db = dbf.newDocumentBuilder ();
            outputDoc = db.newDocument ();
            html = outputDoc.createElement ("HTML");
            outputDoc.appendChild (html);
            body = outputDoc.createElement ("BODY"); 
            html.appendChild (body);
            Element h1 = outputDoc.createElement ("H1");
            body.appendChild (h1);
            h1.appendChild (outputDoc.createTextNode (heading));
        }

        public static void main (String[] args)
        {
            try
            {
                UseDOM ud = new UseDOM ();
                ud.createHTMLDoc ("JDC Tech Tips Archive");
                ud.processWithDOM (args[0]);
                String htmlOut = ud.toString ();
                System.out.println ("Saving result to " + args[1]);
                FileWriter fw = new FileWriter (args[1]);
                fw.write (htmlOut, 0, htmlOut.length ());
                fw.flush ();
            }
            catch (Throwable t)
            {
                t.printStackTrace ();
            }
        }
    }

XML을 TechTipArchive.xml로 저장했다고 가정합시다. 아래와 같은 명령어로 코드를 실행할 수 있습니다.

    java UseDOM file:TechTipArchive.xml ListByAuthor.html

그리고 웹브라우져로 ListByAuthor.html을 열어서 저자별로 구성된 팁의 목록을 봅니다.

코드가 동작하는 방법을 알기 위해, createHTMLDoc 메소드부터 살펴봅시다. 이 메소드는 outputDoc Document를 생성합니다. 이것은 나중에 HTML 출력을 만드는데 이용됩니다. SAX와 마찬가지로, 파서는 factory 메소드를 통해 생성됩니다. 그러나 여기서는 factory 메소드가 DocumentBuilderFactory 클래스에 있습니다. createHTMLDoc의 아래쪽 반은 HTML의 기본적인 요소들을 생성합니다.

    outputDoc.appendChild (html);
    body = outputDoc.createElement ("BODY"); 
    html.appendChild (body);
    Element h1 = outputDoc.createElement ("H1");
    body.appendChild (h1);
    h1.appendChild (outputDoc.createTextNode (heading));

이 코드를 SAX 예제에 있는 HTML의 요소를 생성하는 코드와 비교해 보십시오.

    //direct string manipulation from SAX example
    htmlOut = new StringBuffer ("<HTML><BODY><H1>JDC Tech Tips Archive</H1>");

DOM API를 이용하여 문서를 생성하는 것은 문자열을 직접 다루는 것만큼 간단하거나 빠르지 않습니다. 그러나 (특히 큰 문서에 대해서) 에러의 발생이 적습니다.

useDOM 예제에서 가장 중요한 부분은 processWithDOM 메소드입니다. 이 메소드는 두가지 일을 합니다. (1) 저자 엘리먼트를 찾고 이것을 출력으로 제공합니다. (2) 팁을 찾고 저자별로 구분하여 출력으로 제공합니다. 이 단계들은 각각 문서의 최상위 엘리먼트에 접근할 수 있어야 합니다. 이것은 getDocumentElement () 메소드를 통해 수행됩니다. 저자 정보는 <author> 엘리먼트에 들어있습니다. 이런 엘리먼트는 최상위 레벨에서 getElementsByTagName ("author") 메소드를 호출하면 찾을 수 있습니다. getElementsByTagName 메소드는 NodeList를 리턴합니다. 이것은 간단한 Node의 컬렉션입니다. 그리고 각각의 NodegetAttribute 메소드를 호출하기 위해 Element로 형변환됩니다. getAttribute 메소드는 저자의 아이디와 이름을 입력으로 받습니다. 각각의 저자는 2수준의 머리말로 목록이 만들어집니다. 출력 문서는 저자의 이름을 담고 있는 <H2> 요소를 생성해 냅니다. Node를 추가하기 위해서는 2개의 단계가 필요합니다. 첫번째로 출력 문서에서 createElement와 같은 factory 메소드를 통해 Node를 생성합니다. 다음으로 노드가 appendChild 메소드를 통해 추가됩니다. 노드는 자신을 생성한 문서에만 추가될 수 있습니다.

저자 머리말이 제 위치에 놓이게 되면, 각각의 팁에 링크를 생성할 차례입니다. <tip> 요소를 <author> 엘리먼트를 찾을때와 같은 방법으로 getElementsByTagName을 이용해 찾습니다. 팁 속성을 추출하는 방법도 비슷합니다. 한가지 차이점은 노드를 어디에 추가할 것인가를 결정하는 것입니다. 저자가 다르면 다른 목록에 추가되어야 합니다. 이 작업을 위한 기초 작업으로 저자 엘리먼트를 처리할 때에 <OL> 노드를 추가하고 저자의 아이디로 HashMap 인덱싱을 했습니다. 이제, 팁의 저자 아이디 속성을 팁이 추가될 적당한 <OL> 노드를 찾는데 이용할 수 있습니다.

XML에 대해 더 깊이 다룬 내용을 보시려면, 2000년에 Addison-Wesley에서 출판한 Neil Bradley의 The XML Companion을 참고하십시오. JAXP에 대한 더 자세한 정보는 http://java.sun.com/xml/index.html의 Java(tm) Technology와 XML 페이지를 참고하십시오. SAX2에 대한 더 자세한 정보는 http://www.megginson.com/SAX/index.html을 참고하십시오. DOM 표준은 http://www.w3.org/TR/REC-DOM-Level-1에 있습니다.

2008년 10월 31일 금요일

java zip 압축관련.


import java.io.*;
import java.util.zip.*;

 

public void creatZipFile() throws Exception {
  

    File f = new File("D:/file/fileup");
  
    String path = "D:/file/fileup";

    String files[] = f.list(); // f object 에 있는 파일목록
    
    // Create a buffer for reading the files
    byte[] buf = new byte[1024];
   
    try {

   

        // Create the ZIP file
        String outFilename = "D:/outfile.zip";
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(outFilename));
   
        // Compress the files
        for (int i=0; i<files.length; i++) {
    
            FileInputStream in = new FileInputStream( path + "/" + files[i]);
   
            // Add ZIP entry to output stream.
            out.putNextEntry(new ZipEntry(files[i])); // Zip 파일에 경로를 정하여 저장할수 있다.
    
            // Transfer bytes from the file to the ZIP file
            int len;
            while ((len = in.read(buf)) > 0) {

                out.write(buf, 0, len);
            }
   
            // Complete the entry
            out.closeEntry();
            in.close();
        }
   
        // Complete the ZIP file
        out.close();
    }catch(Exception e) {

        throw e;
    }

}

 

방법2. (BufferedOutputStream, BufferedInputStream 사용)

import java.io.*;
import java.util.zip.*;

 

public void creatZipFile() throws Exception {
  

    File f = new File("D:/file/fileup");

    int size = 1024;
    String path = "D:/file/fileup";

    String files[] = f.list(); // f object 에 있는 파일목록

    // Create a buffer for reading the files
    byte[] buf = new byte[size];
    try {

 

        // Create the ZIP file
        String outFilename = "D:/outfile.zip";
        ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outFilename)));

        // Compress the files
        for (int i=0; i<files.length; i++) {
    
            FileInputStream fs = new FileInputStream( path + "/" + files[i]);

            BufferedInputStream in = new BufferedInputStream(fs, size);


            // Add ZIP entry to output stream.
            out.putNextEntry(new ZipEntry(files[i])); // Zip 파일에 경로를 정하여 저장할수 있다.
    
            // Transfer bytes from the file to the ZIP file
            int len;
            while ((len = in.read(buf, 0, size)) > 0) {

                out.write(buf, 0, len);
            }
   
            // Complete the entry
            out.closeEntry();
            in.close();
        }
   
        // Complete the ZIP file
        out.close();
    }catch(Exception e) {

        throw e;
    }

}

java.util.zip 패키지에서 ZIP 압축 화일 형식을 처리하는 클래스의 사용법


다음 프로그램은 압축 화일을 지원하는 java.util.zip 패키지에서
.zip 압축 화일 형식 (다중 화일 압축 형식의 하나)을 처리하는 클래스의
사용법을 보여줍니다.
덧붙여, File 클래스를 사용하여 화일을 다루는 방법, 예외 클래스
중첩된 클래스, java.text 패키지를 이용한 날짜, 메씨지 형식화 출력
방법도 소개합니다.
현재, 이 패키지의 이해를 돕는 문서와 예제가 없어서 간단하게
만들어 보았습니다.


이 프로그램은 zip, unzip, WinZip 프로그램과 같은 일을 하도록
만들어져 있습니다만,
이 프로그램에서 사용된 FileOutputStream이나 FileInputStream을
URL 혹은 Socket 객체로부터 얻은 InputStream 혹은 OutputStream으로
바꾸어주면 네트워크상에서 여러 화일을 하나의 화일로 압축한 형태로
자료를 전송하고 받아서 처리하여, 네트워크 전송 속도를 향상시킬 수
있습니다.
일반 텍스트 자료를 압축하면 2 ~ 4배로 압축된다고 알려져 있습니다.
또한, 이 클래스들은 체크썸을 사용하여 저장, 전송 오류를 검사해줍니다.


이 프로그램이 압축한 화일을 WinZip이 제대로 처리하지 못한다면,
갖고 계신 WinZip 프로그램 버전에 버그가 있기 때문일 것입니다.
http://www.winzip.com 에서 최신 버전 (버전 6.3 Beta 4)을
다운로드받으실 수 있습니다.


- 테스트된 환경: JDK1.1.3 + 솔라리스, JDK1.1.3 + 윈도우즈


----------------- ZipTest.java --------------------------------------
// Usage: java ZipTest [ -r ] zipfile file1 [ file2 ... ]
import java.io.*;
import java.util.zip.*;


class ZipTest
{
static boolean isRecursive = false;
static ZipOutputStream zos;


public static void main( String[] args )
throws IOException
{
try
{ int argnum = 0;
if ( args[argnum].equals("-r") )
{ isRecursive = true;
argnum++;
}
File zipfile = new File( args[argnum++] );
if ( zipfile.exists() )
throw new Error( "ZIP 화일 " + zipfile + "이 이미 존재함." );
zos = new ZipOutputStream( new FileOutputStream( zipfile ) );
if ( args.length <= argnum )
throw new Error(
"사용법: java ZipTest [ -r ] zipfile file1 [ file2 ... ]" );
for(int i = argnum; i < args.length; ++i)
outputEntry( new File(args[i]) );
zos.close();
} catch( Error ex )
{ System.err.println( "오류: " + ex.getMessage() );
}
}

public static void outputEntry( File f ) throws Error, IOException
{ if ( ! f.exists() )
throw new Error( "화일 " + f + "이 존재하지 않음" );
String adjustedPath = f.getPath().replace(File.separatorChar, '/');
if ( f.isDirectory() && ! adjustedPath.endsWith("/") )
adjustedPath += '/';
ZipEntry entry = new ZipEntry( adjustedPath );
entry.setTime( f.lastModified() );
// File 클래스에서 lastModified() 메쏘드의 시각 기준이 문서화되어
// 있지는 않으나, 솔라리스와 윈도우즈에서는 Date 클래스와 같은
// 시각 기준이 사용되고 있음.
zos.putNextEntry( entry );

if ( f.isDirectory() )
{ System.out.println( "디렉토리 첨가: " + f );
if ( isRecursive )
{ String[] files = f.list();
for( int i = 0; i < files.length; ++i )
outputEntry( new File(f, files[i]) ); // 재귀적 호출 사용
}
} else
{ System.out.println( " 화일 첨가: " + f );
FileInputStream fis = new FileInputStream( f );
byte buf[] = new byte[1024];
for( int cnt; (cnt = fis.read(buf)) != -1; )
zos.write( buf, 0, cnt );
fis.close();
}
}


static class Error extends Exception
{ public Error(String mesg)
{ super(mesg); }
}
}
---------------------------------------------------------------------------------



----------------- UnzipTest.java --------------------------------------
// Usage: java UnzipTest [ -l ] zipfile
import java.io.*;
import java.util.zip.*;
import java.util.*;
import java.text.*;


class UnzipTest
{
public static void main( String[] args )
throws IOException
{
try
{
boolean isListing = false;
int argnum = 0;
if ( args[0].equals("-l") )
{ isListing = true;
argnum++;
}
File zipfile = new File( args[argnum] );
if ( ! zipfile.exists() )
throw new Error( "ZIP 화일 " + zipfile + "이 존재하지 않음." );
ZipInputStream zis
= new ZipInputStream( new FileInputStream( zipfile ) );
if ( isListing )
list(zis);
else
extract(zis);
System.out.close();
} catch( Error ex )
{ System.err.println( "오류: " + ex.getMessage() );
}
}


static void list(ZipInputStream zis) throws IOException
{
ZipEntry entry;
System.out.println( " 크기 (압축 크기) 날짜 시각 이름" );
System.out.println( " ---------------- ---- ---- ----" );
MessageFormat fmt = new MessageFormat(
"{0,date,MM-dd-yy} {0,time,HH:mm} {1}" );
while( (entry = zis.getNextEntry()) != null )
{ zis.closeEntry();
System.out.println(
padToWidth( String.valueOf(entry.getSize()), 7 )
+ padToWidth( "(" + String.valueOf(entry.getCompressedSize())
+ ")", 10 )
+ " " + fmt.format( new Object[] {
new Date( entry.getTime() ),
entry.getName() } )
);
}
}


static String padToWidth(String s, int width)
{ if ( s.length() >= width )
return s;
char padded[] = new char[width - s.length()];
for( int i = 0; i < padded.length; i++ )
padded[i] = ' ';
return padded + s;
}


static void extract(ZipInputStream zis) throws IOException, Error
{
ZipEntry entry;
while( (entry = zis.getNextEntry()) != null )
{ File entryFile
= new File( entry.getName().replace('/', File.separatorChar) );
if ( entry.isDirectory() )
{ if ( ! entryFile.exists() )
{ System.out.println( " 디렉토리 생성: " + entryFile );
entryFile.mkdirs();
}
continue;
}


if ( entryFile.getParent() != null )
{ File parent = new File( entryFile.getParent() );
if ( ! parent.exists() )
parent.mkdirs();
}
if ( entry.getMethod() == ZipEntry.STORED )
{ System.out.println( " 추출: " + entryFile );
} else if ( entry.getMethod() == ZipEntry.DEFLATED )
{ System.out.println( " 압축 풀기: " + entryFile );
} else
throw new Error( entryFile
+ "화일에서 처리할 수 없는 압축 방식이 사용되었음" );
if ( entryFile.exists() )
throw new Error( entryFile + "이 이미 존재함" );
FileOutputStream fos = new FileOutputStream( entryFile );
byte buf[] = new byte[1024];
for( int cnt; (cnt = zis.read(buf)) != -1; )
fos.write( buf, 0, cnt );
fos.close();
}
}


static class Error extends Exception
{ public Error(String mesg)
{ super(mesg); }
}
}
---------------------------------------------------------------------------------



----------- 실행 결과 ------------------------
C:\example\zip> mkdir test
C:\example\zip> copy *.java test
C:\example\zip> dir test
GZIPT~66 JAV 2,333 97-07-24 3:48 GZIPTest.java
UNZIP~U2 JAV 3,637 97-07-24 3:53 UnzipTest.java
ZIPTE~7G JAV 2,479 97-07-24 3:27 ZipTest.java
CHECK~Z6 JAV 1,674 97-07-22 6:11 CheckedStreamTest.java
C:\example\zip> java -Duser.timezone=JST ZipTest -r test.zip test
-------------------> workaround for JDK1.1 timezone bug
디렉토리 첨가: test
화일 첨가: test\GZIPTest.java
화일 첨가: test\CheckedStreamTest.java
화일 첨가: test\ZipTest.java
화일 첨가: test\UnzipTest.java
C:\example\zip> java -Duser.timezone=JST UnzipTest -l test.zip
크기 (압축 크기) 날짜 시각 이름
---------------- ---- ---- ----
0 (2) 07-25-97 01:15 test/
2333 (785) 07-24-97 03:48 test/GZIPTest.java
3637 (1243) 07-24-97 03:53 test/UnzipTest.java
2479 (1027) 07-24-97 03:27 test/ZipTest.java
1674 (586) 07-22-97 06:11 test/CheckedStreamTest.java
C:\example\zip> move test test2
C:\example\zip> java UnzipTest test.zip
디렉토리 생성: test\
압축 풀기: test\GZIPTest.java
압축 풀기: test\UnzipTest.java
압축 풀기: test\ZipTest.java
압축 풀기: test\CheckedStreamTest.java


(WinZip 프로그램과의 호환성 테스트)
C:\example\zip> move test test3
(WinZip에서 test.zip을 Extract한다)
C:\example\zip> dir test
GZIPT~66 JAV 2,333 97-07-24 3:48 GZIPTest.java
UNZIP~U2 JAV 3,637 97-07-24 3:53 UnzipTest.java
ZIPTE~7G JAV 2,479 97-07-24 3:27 ZipTest.java
CHECK~Z6 JAV 1,674 97-07-22 6:11 CheckedStreamTest.java
C:\example\zip> move test test4


(WinZip에서 test2.zip을 생성후, test2\*.*를 첨가)
C:\example\zip> java -Duser.timezone=JST UnzipTest -l test2.zip
크기 (압축 크기) 날짜 시각 이름
---------------- ---- ---- ----
2333 (785) 07-24-97 03:48 GZIPTest.java
3637 (1253) 07-24-97 03:53 UnzipTest.java
2479 (1031) 07-24-97 03:27 ZipTest.java
1674 (586) 07-22-97 06:11 CheckedStreamTest.java
C:\example\zip> mkdir test
C:\example\zip> cd test
C:\example\zip\test> set CLASSPATH=..
C:\example\zip\test> java UnzipTest ..\test2.zip
압축 풀기: GZIPTest.java
압축 풀기: UnzipTest.java
압축 풀기: ZipTest.java
압축 풀기: CheckedStreamTest.java
C:\example\zip\test> dir *.*
GZIPT~66 JAV 2,333 97-07-25 1:28 GZIPTest.java
UNZIP~U2 JAV 3,637 97-07-25 1:28 UnzipTest.java
ZIPTE~7G JAV 2,479 97-07-25 1:28 ZipTest.java
CHECK~Z6 JAV 1,674 97-07-25 1:28 CheckedStreamTest.java

2008년 10월 30일 목요일

Creating ZIP and JAR Files(JAVA)

Creating ZIP and JAR Files

ZIP files offer a packaging mechanism, allowing multiple files to be bundled together as one. Thus, when you need to download a group of files from the web, you can package them into one ZIP file for easier transport as a single file. The bundling can include additional information like directory hierarchy, thus preserving necessary paths for an application or series of resources once unbundled.

This tip will address three aspects of ZIP file usage:

  • creating them
  • adding files to them
  • compressing those added files

Creating Zip Streams

The ZipOutputStream offers a stream for compressing the outgoing bytes. There is a single constructor for ZipOutputStream, one that accepts another OutputStream:

public ZipOutputStream(OutputStream out)

If the constructor argument is the type FileOutputStream, then the compressed bytes written by the ZipOutputStream will be saved to a file. However, you aren't limited to using the ZipOutputStream with a file. You can also use the OutputStream that comes from a socket connection or some other non-file-oriented stream. Thus, for a file-oriented ZipOutputStream, the typical usage will look like this:

String path = "afile.zip"; 
FileOutputStream fos = new FileOutputStream(path);
ZipOutputStream zos = new ZipOutputStream(fos);

Adding Entries

Once created, you don't just write bytes to a ZipOutputStream. Instead, you need to treat the output stream as a collection of components. Each component of a ZipOutputStream is paired with a ZipEntry. It is this ZipEntry that you create and then add to the ZipOutputStream before actually writing its contents to the stream.

String name = ...; 
ZipEntry entry = new ZipEntry(name);
zos.putNextEntry(entry);
zos.write(<< all the bytes for entry >>);

Each entry serves as a marker in the overall stream, where you'll find the bytes related to the entry in the library file. After the ZIP file has been created, when you need to get the entry contents back, just ask for the related input stream:

ZipFile zip = new ZipFile("afile.zip"); 
ZipEntry entry = zip.getEntry("anEntry.name");
InputStream is = zip.getInputStream(entry);

Now that you've seen how to create the zip file and add entries to that file, it is important to point out that the java.util.zip libraries offer some level of control for the added entries of the ZipOutputStream. First, the order you add entries to the ZipOutputStream is the order they are physically located in the .zip file. You can manipulate the enumeration of entries returned back by the entries() method of ZipFile to produce a list in alphabetical or size order, but the entries are still stored in the order they were written to the output stream.

Compressing Files

Files added to a ZIP/JAR file are compressed individually. Unlike Microsoft CAB files which compress the library package as a whole, files in a ZIP/JAR file are each compressed or not compressed separately. Before adding a ZipEntry to the ZipOutputStream, you determine whether its associated bytes are compressed. The setMethod method of ZipEntry allows you to specify which of the two available compression formats to use. Use the STORED constant of ZipEntry to give you an uncompressed file and the DEFLATED setting for a compressed version. You cannot control the compression efficiency. That depends on the type of data in the associated entry. Straight text can be compressed to around 80% of its size quite easily, whereas MP3 or JPEG data will be compressed much less.

While you might think it obvious that everything should be compressed, it does take time to compress and uncompress a file. If the task of compressing is too costly a task to do at the point of creation, it may sometimes be better to just store the data of the whole file in a STORED format, which just stores the raw bytes. The same can be said of the time cost of uncompression. Of course, uncompressed files are larger, and you have to pay the cost with either higher disk space usage or bandwidth when transferring file. Keep in mind that you need to change the setting for each entry, not the ZipFile as a whole. However, it is more typical to compress or not compress a whole ZipFile, as opposed to different settings for each entry.

There is one key thing you need to know if you use the STORED constant for the compression method: you must explicitly set certain attributes of the ZipEntry which are automatically set when the entry is compressed. These are the size, compressed size, and the checksum of the entry's input stream. Assuming an input file, the size and compressed size can just be the file size. To compute the checksum, use the CRC32 class in the java.util.zip package. You cannot just pass in 0 or -1 to ignore the checksum value; the CRC value will be used to validate your input when creating the ZIP and when reading from it later.

ZipEntry entry = new ZipEntry(name);
entry.setMethod(ZipEntry.STORED);
entry.setCompressedSize(file.length());
entry.setSize(file.length());
CRC32 crc = new CRC32();
crc.update(<< all the bytes for entry >>);
entry.setCrc(crc.getValue());
zos.putNextEntry(entry);

To demonstrate, the following program will combine a series of files using the STORED compression method. The first argument to the program will be the ZIP file to create. Remaining arguments represent the files to add. If the ZIP file to create already exists, the program will exit without modifying the file. If you add a non-existing file to the ZIP file, the program will skip the non-existing file, adding any remaining command line arguments to the created ZIP.

import java.util.zip.*;
import java.io.*;

public class ZipIt {
public static void main(String args[]) throws IOException {
if (args.length < 2) {
System.err.println("usage: java ZipIt Zip.zip file1 file2 file3");
System.exit(-1);
}
File zipFile = new File(args[0]);
if (zipFile.exists()) {
System.err.println("Zip file already exists, please try another");
System.exit(-2);
}
FileOutputStream fos = new FileOutputStream(zipFile);
ZipOutputStream zos = new ZipOutputStream(fos);
int bytesRead;
byte[] buffer = new byte[1024];
CRC32 crc = new CRC32();
for (int i=1, n=args.length; i < n; i++) {
String name = args[i];
File file = new File(name);
if (!file.exists()) {
System.err.println("Skipping: " + name);
continue;
}
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(file));
crc.reset();
while ((bytesRead = bis.read(buffer)) != -1) {
crc.update(buffer, 0, bytesRead);
}
bis.close();
// Reset to beginning of input stream
bis = new BufferedInputStream(
new FileInputStream(file));
ZipEntry entry = new ZipEntry(name);
entry.setMethod(ZipEntry.STORED);
entry.setCompressedSize(file.length());
entry.setSize(file.length());
entry.setCrc(crc.getValue());
zos.putNextEntry(entry);
while ((bytesRead = bis.read(buffer)) != -1) {
zos.write(buffer, 0, bytesRead);
}
bis.close();
}
zos.close();
}
}

More Information

For more information on JAR files, including how to seal and version them, be sure to visit the "Packing Programs in JAR Files" lesson in The Java Tutorial

2008년 10월 21일 화요일

Oracle Sequence 와 Trigger 생성

Sequence 생성
create sequence autonum6 increment by 1 start with 1 nomaxvalue nocycle nocache;

Triger  생성
create trigger auto_trigger6
before insert on Reservation
for each row
begin
select autonum6.nextval into :new.id from dual;
end;
/

시퀀스를 만들고 트리거를 실행하면 자동으로 증가한다.

auto_increment를 사용해도 된다.
예를들어  시퀀스만을 만들고(트리거는 안씀)
insert into test (num)  values (autonum.nextval); 도 가능하다.

CYON Cinema Friday 이벤트(26회 바디 오브 라이즈) 당첨

안녕하세요. 싸이언 운영자입니다.

CYON Cinema Friday 이벤트 당첨을 진심으로 축하드립니다.

CYON Cinema Friday '26th Choice 바디 오브 라이즈'에 응모해주신 모든 분들께 감사드리며,
앞으로 계속되는 Cinema Friday에 많은참여 바랍니다.

* 21~23일, 당첨자 확인전화 연결이 안되신 분은 자동으로 당첨취소가 되므로 연락을 못받으신분은 23일까지 연락주시기 바랍니다. (Tel.080-639-5555)


감사합니다.

<바디 오브 라이즈 영화상영 안내>

* 일시 : 10월 24일 금요일 8시
* 장소 : 서울 코엑스 메가박스
* 경품내역 : 바디 오브 라이즈 영화 관람권 (1인2매)
* 티켓 수령처 : 코엑스 메가박스 CYON PLANET(영화매표소 커피빈 옆)
* 티켓 수령방법 : 영화 상영일부터 30분전까지 티켓수령 가능(신분증 확인 후 수령, 신분증 필지참)


2008년 10월 1일 수요일

Transaction Control Language (TCL)

Transaction Control Language (TCL)

Transaction control statements manage changes made by DML statements.

What is a Transaction?

A transaction is a set of SQL statements which Oracle treats as a Single Unit. i.e. all the statements should execute successfully or none of the statements should execute.

To control transactions Oracle does not made permanent any DML statements unless you commit it. If you don’t commit the transaction and power goes off or system crashes then the transaction is roll backed.

TCL Statements available in Oracle are

COMMIT       :Make changes done in  transaction permanent.

ROLLBACK  :Rollbacks the state of database to the last commit point.

SAVEPOINT :Use to specify a point in transaction to which later you can rollback.

COMMIT

To make the changes done in a transaction permanent issue the COMMIT statement.

The syntax of COMMIT Statement is

COMMIT  [WORK]  [COMMENT ‘your comment’];

WORK is optional.

COMMENT is also optional, specify this if you want to identify this transaction in data dictionary DBA_2PC_PENDING.

Example

insert into emp (empno,ename,sal) values (101,’Abid’,2300);

commit;

 

ROLLBACK

 

To rollback the changes done in a transaction give rollback statement. Rollback restore the state of the database to the last commit point.

 

 

Example :

delete from emp;

rollback;          /* undo the changes */

 

 

SAVEPOINT

 

Specify a point in a transaction to which later you can roll back.

 

Example

 

insert into emp (empno,ename,sal) values (109,’Sami’,3000);

savepoint a;

insert into dept values (10,’Sales’,’Hyd’);

savepoint b;

insert into salgrade values (‘III’,9000,12000);

 

Now if you give

 

rollback to a;

 

Then  row from salgrade table and dept will be roll backed. Now you can commit the row inserted into emp table or rollback the transaction.

 

If you give

 

rollback to b;

 

Then row inserted into salgrade table will be roll backed. Now you can commit the row inserted into dept table and emp table or rollback to savepoint a or completely roll backed the transaction.

 

If you give

 

rollback;

 

Then the whole transactions is roll backed.

 

If you give

 

commit;

 

Then the whole transaction is committed and all savepoints are removed.


Oracle Isolation Level

Transaction Isolation Level(격리수준)을 이해하기
 
이번 기사에서는 지난 Deadlock 발생을 감소시키는 방법 기사에서 언급되었던 한 세션 상의 모든 SELECT 문장에 대한 디폴트 트랜잭션 잠금 동작을 제어하는 Transaction Isolation Level에 대하여 알아보자.
 
트랜잭션에서 일관성이 없는 데이터를 허용하도록 하는 수준을 Isolation Level(격리수준)이라고 한다. 예를 들어, 한 사용자가 어떠한 데이터를 수정하고 있는 경우 다른 사용자들이 그 데이터에 접근하는 것을 차단함으로써 완전한 데이터만을 사용자들에게 제공하게 된다. 또한, 많은 사용자들의 수정 작업으로 인하여 통계 자료를 작성할 수 없는 사용자를 위하여 읽기 작업을 수행할 수 있도록 Isolation Level을 변경할 수 있다.
 
ANSI에서 작성된 SQL-92 표준은 네 종류의 Isolation Level을 정의하고 있으며 SQL Server 7.0, 2000은 이 표준을 준수한다. Isolation Level을 조정하는 경우 동시성이 증가되는데 반해 데이터의 무결성에 문제가 발생할 수 있거나, 데이터의 무결성을 완벽하게 유지하는 데 반하여 동시성이 떨어질 수 있으므로 그 기능을 완벽하게 이해한 후에 사용해야 한다.
 
현재 설정된 Isolation Level을 보고자 하는 경우 DBCC USEROPTIONS 를 이용한다.
 
다음은 SQL Server에서 지원하는 네 종류의 Transaction Isolation Level이다.
l        Read Uncommitted
l        Read Committed
l        Repeatable Read
l        Serializable
 
Read Uncommitted Isolation Level
SELECT 문장을 수행하는 경우 해당 데이터에 Shared Lock이 걸리지 않는 Level이다. 따라서, 어떤 사용자가 A라는 데이터를 B라는 데이터로 변경하는 동안 다른 사용자는 B라는 아직 완료되지 않은(Uncommitted 혹은 Dirty) 데이터 B를 읽을 수 있다.
 
Read Committed Isolation Level
SQL Server Default로 사용하는 Isolation Level이다. Level에선 SELECT 문장이 수행되는 동안 해당 데이터에 Shared Lock이 걸린다. 그러므로, 어떠한 사용자가 A라는 데이터를 B라는 데이터로 변경하는 동안 다른 사용자는 해당 데이터에 접근할 수 없다.
 
Repeatable Read Isolation Level
트랜잭션이 완료될 때까지 SELECT 문장이 사용하는 모든 데이터에 Shared Lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정이 불가능하다. 가령, Select col1 from A where col1 between 1 and 10을 수행하였고 이 범위에 해당하는 데이터가 2건이 있는 경우(col1=1 5) 다른 사용자가 col1 1이나 5 Row에 대한 UPDATE가 불가능하다. 하지만, col1 1 5를 제외한 나머지 이 범위에 해당하는 Row INSERT하는 것이 가능하다.
 
Serializable Isolation Level
트랜잭션이 완료될 때까지 SELECT 문장이 사용하는 모든 데이터에 Shared Lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정 및 입력이 불가능하다. 예를 들어, Repeatable Read의 경우 1에서 10 사이에 해당되는 데이터에 대한 UPADTE가 가능하였다. 하지만 이 Level에서는 UPDATE 작업도 허용하지 않는다.
 
사용 방법
SET TRANSACTION ISOLATION LEVEL
{
READ COMMITTED
| READ UNCOMMITTED
| REPEATABLE READ
| SERIALIZABLE
}
, SET 문장을 이용하여 Transaction Isolation Level을 정의하게 되므로 해당 Session에서만 Isolation Level이 적용된다.
 
사용 예제
다음 예제는 세션에 대해 TRANSACTION ISOLATION LEVEL을 설정한다. 뒤따르는 각 Transact-SQL 문에 대해 SQL Server는 트랜잭션이 끝날 때까지 모든 공유 잠금을 유지한다.
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
GO
BEGIN TRANSACTION
SELECT * FROM publishers
SELECT * FROM authors
...
COMMIT TRANSACTION

2008년 9월 17일 수요일

jboss 설정

 

# jboss설치
-http://labs.jboss.com/ 에서 최근버전을 받아서 압축을 푼다
-JBOSS_HOME/bin/run.bat 실행 후 http://localhost:8080/으로 확인한다.
-JBOSS_HOME/server/default/deploy/jbossweb-tomcat55.sar/root.war 이 경로가 디폴트로 보여진다.

# jboss deploy 폴더 설정
-JBOSS_HOME/server/default/conf/jboss-service.xml의 아래 예시와 같이 콤마(,)를 구분자로 하여 배포파일 또는 폴더를 추가한다.
-디폴트는 deploy/ 폴더이고 경로의 끝에 슬래쉬(/)를 붙이면 폴더로 인식한다.
-주의할 점은 jar, war, ear같은 파일뒤에 슬래쉬를 붙이면 deploy 되지 않지만 JBOSS_HOME/server/default/deploy폴더 속에 aaa.jar 과 같은 이름의 폴더는 deploy 가능하다.

      <attribute name="URLs">
        deploy/,
        file:///D:/aa/bbb/ccccejb.jar
      </attribute>
     
# jboss 오라클 드라이버 설정
-ojdbc14.zip를 JBOSS_HOME/server/default/lib 폴더에 복사
-JBOSS_HOME/docs/examples/jca/oracle-ds.xml을 JBOSS_HOME/server/default/deploy 폴더에 복사
-oracle-ds.xml를 아래와 같이 수정
  <local-tx-datasource>
    <jndi-name>oracle</jndi-name>
    <connection-url>jdbc:oracle:thin:@127.0.0.1:1521:sid</connection-url>
    <driver-class>oracle.jdbc.driver.OracleDriver</driver-class>
    <user-name>xxx</user-name>
    <password>yyy</password>
    <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter</exception-sorter-class-name>
      <metadata>
         <type-mapping>Oracle9i</type-mapping>
      </metadata>
  </local-tx-datasource>
 
-jboss를 restart하면 콘솔에서
[WrapperDataSourceService] Bound ConnectionManager 'jboss.jca:service=DataSourceBinding,name=oracle' to JNDI name 'java:oracle'
메시지 확인 가능 
 
-WEB-INF/web.xml 수정
 <resource-ref>
  <res-ref-name>jdbc/oracle</res-ref-name>
  <res-type>javax.sql.DataSource</res-type>
  <res-auth>Container</res-auth>
 </resource-ref>
 
-WEB-INF/jboss-web.xml
 <resource-ref>
  <res-ref-name>jdbc/oracle</res-ref-name>
  <jndi-name>java:oracle</jndi-name>
 </resource-ref>
 
# 기타 라이브러리 추가
서비스시 필요한 라이브러리는 JBOSS_HOME/server/default/lib 폴더에 복사한다

# 서비스 루트 설정
WEB-INF/jboss-web.xml 아래 소스 추가 또는 수정
 <context-root>/</context-root>

2008년 9월 2일 화요일

데이터 추적 기회

Sharable Content Object Reference Model (SCORM) Run-Time Environment Data Model에는 SCO 실행시간 동안 학습 관리 시스템(LMS)에 있는 Sharable Content Objects (SCOs)를 추적할 수 있는 데이터 모델 요소 세트가 포함돼 있다. 이 데이터 모델 요소 세트는 상태, 점수, 상호작용, 목표 등의 항목을 추적할 때 필요하다. 본문에서는 상세 수준의 요소 설명을 다루기로 한다.
SCORM Run-Time Environment Data Model를 학습함으로써 프로젝트 팀은 SCORM 프로젝트가 다룰 수 있는 정확한 데이터를 추적할 수 있는 기회를 가질 수 있다. 이 정보를 통해 프로젝트 팀은 프로젝트의 선행 분석 단계에서 프로젝트 관계자들의 요구사항을 수집한다. 프로젝트 초기단계에서부터 데이터 추적의 요구사항을 감안해 커뮤니케이션하면 SCORM Run-Time Environment Data Model Element을 성공적으로 구현하는 데 충분한 시간을 투자할 수 있다.
아래의 표는 SCORM 을 준수한 콘텐츠를 추적할 수 있는 유형을 설명한다. 일부 데이터 모델 요소는 서로에게 영향을 주고, 또 다른 일부 데이터 모델 요소는 다른 요소와 함께 사용할 수도 있다. SCORM은 데이터를 추적하는 방법을 지정하는 반면, LMS가 데이터 결과를 렌더링하는 방법은 지정하지 않는다는 사실을 주목해야 한다. 따라서 프로젝트 팀이 SCORM 콘텐츠를 제공하는 타깃 LMS나 다중 LMS 의 기능을 완벽하게 이해하는 것이 매우 중요하다.
표 1: SCORM 2004 Run-Time Environment Version 1.3.1 Data Model Elements
Data Model Element 기능
Comments from Learner 학습자 텍스트를 포함하고 있다.
Comments from LMS 학습자가 사용할 코멘트 및 주석을 포함하고 있다.
Completion Status 학습자가 SCO를 완료했는지 표시한다.
Completion Threshold SCO를 비교할 수 있도록 학습자의 완료진행사항의 측정 값을 식별하여 SCO가 완벽하게 고려해야 하는지 결정한다.
Credit 학습자가 SCO에서 작업수행능력 점수를 획득하는지 표시한다.
Entry 학습자가 이전에 SCO에 접근한 적이 있는지 여부를 표시하는 정보가 포함되어 있다.
Exit 학습자가 어떤 방식으로, 그리고 왜 SCO를 빠져나왔는지 표시한다.
Interactions 측정 또는 평가를 목적으로 상호작용에 속하는 정보를 정의한다.
Launch Data 초기화에 필요한 사용되는 SCO 고유의 데이터를 제공한다.
Learner Id SCO 인스턴스가 새롭게 등장한 학습자를 인지한다.
Learner Name 학습자 이름을 표시한다.
Learner Preference 학습자의 SCO 사용과 관련된 기본 설정을 지정한다.
Location SCO의 위치를 표시한다.
Maximum Time Allowed 학습자 시도(learner attempt)에서 학습자가 SCO를 사용할 수 있는 시간을 표시한다.
Mode '브라우징(탐색중)', '리뷰(복습)' 및 '정상' 등 SCO가 학습자에게 제공하는 모드를 식별한다.
Objectives SCO와 관련된 학습 또는 작업수행 목표를 지정한다.
Progress Measure 학습자의 SCO 완료진행상황 측정을 체크한다.
Scaled Passing Score SCO의 조정 가능한 전달 점수를 체크한다.
Score 학습자의 SCO 점수를 체크한다.
Session Time 학습자가 SCO의 현재 학습자 세션에서 시간을 얼마나 썼는지 알려준다.
Success Status 학습자가 SCO를 마스터했는지 표시한다.
Suspend Data SCO와 학습자가 접근하거나 또는 상호작용한 결과로 SCO가 생성할 수 있는 정보를 제공한다.
Time Limit Action 최대 허용 시간이 초과될 경우, SCO가 무엇을 해야 할지 표시한다.
Total Time 현재 학습자 세션 이전에 학습자 시도에 누적돼 있된 학습자 세션의 시간을 합한 것을 표시한다.
Version SCORM이 구현중인 버전을 식별한다.
이 문서에서는 SCORM의 데이터 추적 기회에 대한 상세한 수준의 정보를 제공한다. SCORM 2004 Run-Time Environment에는 데이터 모델 요소를 구현하는 방법을 설명하는 Data Model에 대한 섹션이 포함되어 있다. ADL은 또한 SCORM Run-Time Environment Data Model Elements를 설명하는 SCORM 준수 예제를 제공한다. SCORM 2004 Data Model Content Example은 자세히 설명돼 있고, 개발자가 데이터 모델 요소 코딩을 연습해볼 수 있도록 응용프로그램 섹션도 제공한다.

 

Custom Inter-SCO Navigation

SCORM을 사용한 경험이 있다면 JavaScript가 LMS와 서로 값을 가져오고 설정하는데 중요한 역할을 한다는 사실을 알 수 있다. 과거에는 플래시를 섞어서 사용하는 경우 걱정이 앞섰을 것이다.
그러나 이제부터는 걱정하지 않아도 된다! 플래시 인터페이스에서 Inter-SCO 위치 를 확인해주는 요소를 보여주거나 숨기는 작업이 곧 가능해 질 것이기 때문이다.
이 본문에서 사용한 방법론은 다양한 응용프로그램에 그대로 적용하여 JavaScript의 변수를 알 수 있다. 단순히 플래시의 다음/뒤로 버튼을 바꾸는 작업을 말하는 것이 아니다. 이 예를 든 이유는 실행하기 가장 간단할 뿐더러, 내비게이션 요소의 적용은 SCORM 2004에서 새로 도입된 기능이기 때문이다.
flash_example.html, flash_example.fla, flash_actions.as, course_functions.js 파일을 참조한다.
기억해야 할 점은 여기서 설명하는 방법이 유일한 실행 방법은 아니라는 것이다. 이는 알려진 방법일 뿐이다. 같은 작업을 수행할 수 있는 더 나은 방법이 있을 것이며 이를 찾아내면 ADLNet.org(http://http://www.adlnet.org)를 통해 새로운 방법을 공유하도록 한다.
HTML 설정
예를 들어, SCO를 구성하는 파일인 HTML 페이지에 포함된 플래시 파일이 있다. APIWrapper.js와 course_functions.js를 호출해서 SCO가 LMS와 상호작용 할 수 있게 하려고 한다.
LMS에서 값을 가져오는데 JavaScript를 사용하고 "다음"이나 "뒤로" 버튼을 누를 경우, 이를 SCO에 전달한다. 이는 showPrevious와 showContinue 변수를 진행 함수인 renderPreviousButton()과 renderContinueButton()으로 환원된 값을 설정함으로써 가능하다.
이와 같은 내용은 flash_example.html의 섹션에서 확인할 수 있다.

<script type="text/javascript">
 var showPrevious;
 var showContinue;

 function init( )
 {
    initialize();

    // Ask the LMS if there is was preceding SCO
    // that was launched prior to
    // this SCO resulting in the need to render a previous button.
    showPrevious = renderPreviousButton();
    window.document.FlashObject.SetVariable("showPrevious", showPrevious);

   // Ask the LMS if there is a succeeding SCO to be launched after this
   // SCO resulting in the need to render a continue button.
   showContinue = renderContinueButton();
   window.document.FlashObject.SetVariable("showContinue", showContinue);
 }
</script>

init() 함수는 HTML 페이지가 로드될 때 호출된다. 실행되면 이 함수는 다음과 같은 작업을 수행한다.
1) SCO가 LMS에 통신 세션 초기화를 전송한다. 초기화가 성공하면 "true" 문자열이 리턴된다.
2) SCO는 사용자가 접근할 수 있는 이전 SCO나 Asset이 존재하는지 LMS에 묻는다.
존재하는 경우 "true" 문자열이 리턴된다.
3) "FlashObject"라는 플래시 무비에 변수가 설정되어 "showPrevious"라는 변수를 JavaScript 변수 값인
showPrevious로 설정한다.
4) SCO는 사용자가 접근할 수 있는 다음 SCO나 Asset이 존재하는지 LMS에 묻는다.
존재하는 경우 "true" 문자열이 리턴된다.
5) "FlashObject"라는 플래시 무비에 변수가 설정되어 "showContinue"라는 변수를 JavaScript 변수 값인
showContinue로 설정한다.
주: showPrevious와 showContinue의 JavaScript와 플래시 변수에 같은 이름을 사용할 필요는 없다. 변수 이름에 일관성을 기하기 위해서 같은 이름을 사용했다.
HTML 페이지가 시작되면 바로 init() 함수를 호출한다.
<body onload="javascript:init();" onunload="javascript:completedExit();">
앞에서와 같이 플래시 무비를 "FlashObject"라고 했는데 이를 위해서 OBJECT와 EMBED 태그의 ID 파라미터를 "FlashObject"와 같게 설정했다. 이는 다음과 같이 강조된다.
><object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"  
     codebase="http://download.macromedia.com/pub/shockwave
     /cabs/flash/swflash.cab#version=6,0,0,0" width="600" height="400"
     id="FlashObject">
        <param name="movie" value="flash_example.swf">
        <param name="quality" value="high">
        <param name="bgcolor" value="#103C00">
        <param name="swliveconnect" value="true">
        <embed src="flash_example.swf" quality="high"
               width="600" height="400"
               id="FlashObject" align=""
               type="application/x-shockwave-flash"
               swliveconnect="true"
               pluginspage="http://www.macromedia.com/go/getflashplayer">
        </embed>
</object>
Flash와 JavaScript 의 "대화"
다음 코드는 flash_actions.as ActionScript 파일에서 나왔다. ActionScript 코드를 소스 플래시 파일(.fla)과 반드시 별도로 보관할 필요는 없다. 코드를 재사용하기 위해 편하게 사용하는 방법일 뿐이다.
// Initialize variables we'll use in our Flash movie(s)
var intervalID
var i

// Set an interval to "catch" variables from the LMS at the launch of the SCO
intervalID = setInterval( DoStuff, 500 );

// Create a function "DoStuff" to handle turning visibility on and/or off
// for our back and next buttons inside the Flash interface
function DoStuff ()
{
     // increment the variable i
     i++;
     if (i>10){
          // after 10 loops through this function, cancel it out.
         clearInterval(intervalID);
     }
     // if "showPrevious" comes in from the LMS as "false", turn the visibility off
     if (showPrevious == "false") {
          back_btn._visible = false;
     } else {
          back_btn._visible = true;
     }
    
     // if "showContinue" comes in from the LMS as “false”, turn the visibility off
     if (showContinue == "false") {
          next_btn._visible = false;
     } else {
          next_btn._visible = true;
     }
}

// assign the action for the back_btn
back_btn.onPress = function (){
     getURL ('javascript:goToPreviousSCO();');
}

// assign the action for the next_btn
next_btn.onPress = function (){
     getURL ('javascript:goToNextSCO();');
}
그렇다면 이제 ActionScript에서 할 일은 무엇인가? setInterval()을 이용해서 SCO 시작 이후에 설정된 변수를 알아 내는 것이다. 플래시 무비가 HTML 페이지에서 시작될 때 순서를 제어하지 않았기 때문에 이 플래시 콘텐츠가 renderPreviousButton(), renderContinueButton(), initialize()가 완료되기 전에 시작될 것인지 후에 시작될 것인지 알 수 없다.
이 방법이 변수를 JavaScript에서 플래시로 가져오는 가장 간단한 방법은 아니다. 지금은 문제가 되는 변수 값의 변화를 알기 위해 반복 실행을 하는 것이다. ActionScript내의 Object.watch()를 실행하는 방법이 더 좋다. 이 경우 가시적 이벤트 구동이 가능하다.
setVariable() [in JavaScript]와 setInterval/clearInterval [in ActionScript]를 결합하는 것도 좋다.
작동하는 코드가 모든 이론에 우선한다.
flash_example.fla를 보면 코드가 거의 없다. 사실 여기 샘플에서 보이는 유일한 코드는 수행 시에 actions.as 파일을 호출하는 #include뿐이다.
버튼 작동을 제대로 하기 위해 아래 그림처럼 다음, 뒤로 버튼에 인스턴스 이름을 부여했다.
정리
지금까지 우리가 무엇을 했는지 본 장에서 설명한 프로세스를 요약해 보겠다.
1) SCO는 시작할 때 초기화된다.
2)SCO는 선행하는 SCO가 있는지 뒤에 다른 SCO가 있는지 알아보기 위해 LMS에 요청한다.
3) 이러한 값은 JavaScript에 리턴되고 JavaScript는 정보 요청을 한다.
4) JavaScript는 HTML 페이지에서 플래시 무비에 이러한 변수의 값을 적는다.
HTML 페이지는 미리 규정된 간격으로 이러한 변수를 반복하여 찾는다.
5) 플래시가 이러한 이름/값 쌍을 잡아내면 이를 이용하여 "뒤로"와 "다음" 버튼이 보이도록 한다.
앞서 언급한 바와 같이 같은 종류의 작업을 수행할 수 있는 방법은 다양하다. 이 예에서는 HTML 페이지가 SCO의 초기화와 종료를 처리했지만 플래시가 이와 같은 작업을 수행하는 JavaScript를 호출할 수도 있다.
요점을 이해하는 것이 중요하다. SCO의 콘텐츠가 우선 플래시에 저장되면 완전히 로드된 후에는 플래시에서 초기화 호출을 하는 것이 더 좋다. 플래시가 호출을 할 경우에는 이 호출에 대한 결과로 리스너(Listener)를 만들 수 있고 플래시 내에 setInterval()이 있어야 할 필요가 없어진다.
이러한 제안이 오히려 작업하는 데 있어서 걸림돌로 작용해서는 안된다. 이를 언급한 이유는 Flash/SCORM 2004에서 문제가 되는 부분은 스스로 해결할 수 있도록 하기 위해서다. 앞에서 언급했다시피 Flash와 SCORM2004로 문제를 해결하는 방법은 매우 다양하다. 작은 설명이나마 시작하는데 도움이 되었으면 하는 바람이다.

 

차세대 브라우저 구글 크롬

차세대 브라우저 구글 크롬을 만나보세요!

날짜: 2008년 9월 2일 화요일

구글에는 "빨리 런칭하고 빨리 배우자" 라는 구글만의 격언이 있습니다. 사실 이 말은 저희 개발 엔지니어들에게만 해당이 되는 것이였는데, 이번 경우에는 저희에게도 해당이 되는군요. 다른 블로그 글에서 읽으신 분도 계시겠지만 저희가 발표하려던 오픈소스 브라우저인 구글 크롬 소개자료(카툰)가 예상보다 일찍 공개하게 되었습니다. 이제 모든 분들이 다알고 계시기 때문에 공식적으로 구글 크롬 소개자료(카툰형식)를 공개합니다. 구글은 100여개가 넘는 국가에서 구글 크롬 베타버전을 런칭할 예정입니다.

구글이 구글 크롬을 왜 런칭하는 걸까요?
저희는 저희의 노력이 사용자들에게 가치를 더할 수 있고, 더 나아가 웹의 혁신을 이끌어 낼수 있다고 확신하기 때문입니다.

저희 구글 직원들은 업무 상 인터넷 브라우저를 많이 사용합니다. 브라우저에서 검색도 하고, 채팅도 하고, 이메일도 보냅니다. 근무시간 외에는 다른 사람들과 마찬가지로 인터넷 쇼핑을 하기도 하고 인터넷 뱅킹을 이용하며, 뉴스를 검색합니다. 친구들과 온라인으로 네트워킹도 즐기기도 하죠. 이 모든 활동들이 브라우저에서 이루어집니다. 이렇듯 인터넷 사용시간은 점점 늘어나고 있으며 인터넷이 처음 탄생한 15년 전에는 상상조차 할 수 없었던 것이 가능해 진 것입니다. 인터넷에서 보내는 시간이 워낙 많다보니 최고의 요소만을 모아 구글이 직접 브라우저를 만든다면 어떤 브라우저가 탄생할까 저희는 진지하게 생각하게 되었습니다.

웹은 단순한 텍스트 페이지에서 복잡하고 쌍방향적인 애플리케이션으로 발전을 해왔습니다. 그럼 브라우저도 이에 발맞춰 변화해야 하지 않을까요? 우리에게 필요한 것은 단순한 브라우저가 아닌 웹페이지와 애플리케이션을 위한 새로운 플랫폼인 것입니다. 구글은 바로 그 플랫폼을 만들기로 결심했습니다.

생각이 현실이 되어 이제 새로운 오픈 소스 브라우저인 구글 크롬(Google Chrome) 베타 버전을 선보이게 되었습니다.

먼저 외적인 측면에서 구글 크롬의 브라우저 창은 깔끔하고 간결합니다. 인터넷 사용자들에게 브라우저는 중요한 것이 아닙니다. 웹을 구성하는 페이지, 사이트, 애플리케이션과 같은 중요한 것들을 실행하는 도구일 뿐입니다. 구글 크롬은 구글의 기본 홈페이지처럼 심플하고 빠릅니다. 방해 요소를 최소화하여 사용자가 원하는 곳으로 바로 데려다 줍니다.

내용적인 측면에서 구글 크롬은 오늘날의 복잡한 웹 애플리케이션을 좀더 원활하게 실행시킬 수 있는 기반을 갖추었습니다. 예를 들어, 각각의 탭은 서로 다른 "샌드박스"에서 독립적으로 운영되기 때문에 하나의 탭에서 에러가 발생해도 다른 탭은 영향을 받지 않습니다. 안전하지 않은 사이트에 대한 보안도 강화되었습니다. 또한 브라우저의 속도를 전면 향상시켰습니다. 마지막으로 기존 브라우저에서는 불가능한 차세대 웹 애플리케이션을 가능하게 하는 강력한 자바스크립트 엔진인 V8을 구현하였습니다.

하지만 이것은 시작일 뿐입니다. 구글 크롬은 아직 향상될 부분이 많이 있습니다. 가능한 빨리 여러분의 의견을 수렴하고 좀더 폭넓은 논의를 시작하기 위해 윈도우용 베타 버전을 우선 출시했습니다. 맥과 리룩스용 버전은 현재 개발 중에 있습니다. 구글 크롬은 앞으로 더 빠르고 더 강력해 질 것입니다.

구글은 오픈 소스 프로젝트로부터 많은 도움을 얻었고 그렇기 때문에 오픈 소스 프로젝트의 발전에 기여를 하고자 합니다. 구글 크롬은 애플의 웹킷과 모질라 파이어폭스 등 여러 브라우저 구성 요소를 적극 활용하여 만들어졌습니다. 그렇기 때문에 구글 크롬의 모든 코드는 공개되어 있습니다. 구글은 웹의 발전이라는 공동의 목표를 가지고 개발자 커뮤니티와 함께 하고자 합니다. 다양한 선택권과 혁신은 웹을 더욱 발전시킵니다. 구글 크롬은 사용자의 선택권을 넓혀줄 것이며 웹 발전에 기여할 것으로 기대됩니다.

이제 여러분의 몫입니다. 구글 크롬을 직접 테스트 해보십시오. 직접 사용해 보고 소중한 의견을 보내주시기 바랍니다.

내일부터 구글크롬을 다운받을 수 있습니다. 더 자세한 내용은 내일 또 발표하겠습니다.

작성자: 프로덕트 매니지먼트 부사장 선더 피카이, 엔지니어링 디렉터 리너스 업슨

-----------------------------------------------------------------------------------------------------
내일부터 받을수 있다네..한번 써봐야지..^^
http://googlekoreablog.blogspot.com/