OSDN Git Service

import from Subversion repository 1.401.2
authorOlyutorskii <olyutorskii@users.osdn.me>
Tue, 12 Apr 2011 16:45:54 +0000 (01:45 +0900)
committerOlyutorskii <olyutorskii@users.osdn.me>
Tue, 12 Apr 2011 16:45:54 +0000 (01:45 +0900)
27 files changed:
00README.txt [new file with mode: 0644]
ChangeLog.txt [new file with mode: 0644]
License.txt [new file with mode: 0644]
antbuild.xml [new file with mode: 0644]
depend.properties [new file with mode: 0644]
pom.xml [new file with mode: 0644]
src/main/config/checks.xml [new file with mode: 0644]
src/main/config/pmdrules.xml [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/AvatarData.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/Builder.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/EventData.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/FileArchive.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/Handler.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/HttpAccess.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/JinArchiver.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/MultiPlexer.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/PeriodData.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/PeriodResource.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/TalkData.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/TopicData.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/ValidateTask.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/VillageData.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/Win31j.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/XmlUtils.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/ZipUtils.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/archiver/package-info.java [new file with mode: 0644]
src/main/resources/jp/sourceforge/jindolf/archiver/resources/version.properties [new file with mode: 0644]

diff --git a/00README.txt b/00README.txt
new file mode 100644 (file)
index 0000000..7e5faf3
--- /dev/null
@@ -0,0 +1,92 @@
+$Id: 00README.txt 879 2009-10-25 15:42:50Z olyutorskii $\r
+[UTF-8 Japanese]\r
+\r
+                               JinArchiver\r
+                                  README\r
+\r
+                                              Copyright(c) 2009 olyutorskii\r
+\r
+\r
+=== JinArchiverとは ===\r
+\r
+\r
+ JinArchiverは、人狼BBSの過去ログを独自のXML形式でローカルディスクに\r
+保存するためのツールです。\r
+\r
+※ このアーカイブにはJindolfの実行バイナリは含まれていません。\r
+  Jindolfを動かしたい方は、jindolfで始まり拡張子が*.jarであるファイルを\r
+  別途入手してください。\r
+※ 人狼BBSのURLは [ http://homepage2.nifty.com/ninjinia/ ] まで\r
+※ 人狼BBSを主催するninjin氏は、JinArchiverの製作に一切関与していません。\r
+  JinArchiverに関する問い合わせををninjin氏へ投げかけないように!約束だよ!\r
+\r
+\r
+=== 使い方 ===\r
+\r
+例) ※ F国 1507村 のアーカイブをディレクトリ/tmpに作りたい場合。\r
+\r
+java -jar jinarchiver-X.X.X.jar -land wolff -vid 1507 -outdir /tmp\r
+\r
+オプション詳細は-helpオプションで確認してください。\r
+\r
+\r
+=== ソースコードに関して ===\r
+\r
+ - JinArchiverはJava言語(JLS3)で記述されたプログラムです。\r
+ - JinArchiverはJRE1.5に準拠したJava実行環境で利用できるように作られています。\r
+   原則として、JRE1.5に準拠した実行系であれば、プラットフォームを選びません。\r
+\r
+\r
+=== アーカイブ管理体制 ===\r
+\r
+  このアーカイブは、UTF-8による開発環境を前提として構成されています。\r
+  このアーカイブの原本となる開発資産は、\r
+      http://svn.sourceforge.jp/svnroot/jindolf/JinArchiver/\r
+  を上位に持つSubversionリポジトリで管理されています。\r
+  アーカイブの代わりにSubversionを通じて開発資産にアクセスすることにより、\r
+  任意の文字コードに変換されたJavaソースファイルや各種リソースを\r
+  容易に入手することが可能です。\r
+\r
+\r
+=== 開発プロジェクト運営元 ===\r
+\r
+  http://sourceforge.jp/projects/jindolf/ まで。\r
+\r
+\r
+=== ディレクトリ内訳構成 ===\r
+\r
+./00README.txt\r
+    あなたが今見てるこれ。\r
+\r
+./ChangeLog.txt\r
+    変更履歴。\r
+\r
+./License.txt\r
+    ライセンスに関して。\r
+\r
+./antbuild.xml\r
+    Apache Ant1.7用ビルドファイル。\r
+\r
+./depend.properties\r
+    このパッケージおよびこのパッケージが依存する他パッケージのバージョン情報。\r
+    Antタスクにより自動的にメンテナンスされる。\r
+\r
+./src/\r
+    Javaのソースコード。XMLなどの各種リソース。\r
+\r
+./test/\r
+    JUnit 4.* 用のテストコード。\r
+\r
+./scripts/\r
+    各種ビルド・構成管理に必要なファイル群。\r
+\r
+./scripts/checks.xml\r
+    Checkstyle用configファイル。\r
+\r
+./scripts/fbexfilter.xml\r
+    FindBugs用フィルタファイル。\r
+\r
+./scripts/pmdrules.xml\r
+    PMD用ルール定義ファイル。\r
+\r
+--- EOF ---\r
diff --git a/ChangeLog.txt b/ChangeLog.txt
new file mode 100644 (file)
index 0000000..aaf0b80
--- /dev/null
@@ -0,0 +1,11 @@
+$Id: ChangeLog.txt 879 2009-10-25 15:42:50Z olyutorskii $\r
+[UTF-8 Japanese]\r
+\r
+\r
+JinArchiver 変更履歴\r
+\r
+\r
+1.401.2 (2009-10-26)\r
+    ・初回リリース。\r
+\r
+--- EOF ---\r
diff --git a/License.txt b/License.txt
new file mode 100644 (file)
index 0000000..837e1c3
--- /dev/null
@@ -0,0 +1,34 @@
+$Id: License.txt 879 2009-10-25 15:42:50Z olyutorskii $\r
+[UTF-8 Japanese]\r
+\r
+The MIT License\r
+\r
+\r
+Copyright(c) 2009 olyutorskii\r
+\r
+\r
+Permission is hereby granted, free of charge, to any person obtaining a copy\r
+of this software and associated documentation files (the "Software"), to deal\r
+in the Software without restriction, including without limitation the rights\r
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
+copies of the Software, and to permit persons to whom the Software is\r
+furnished to do so, subject to the following conditions:\r
+\r
+The above copyright notice and this permission notice shall be included in\r
+all copies or substantial portions of the Software.\r
+\r
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
+THE SOFTWARE.\r
+\r
+\r
+JinArchiver作者自身からのコメント:\r
+\r
+  ※ 少なくともこのソフトウェアの実行、複製、配布、改造は自由です。\r
+  ※ 少なくともこのソフトウェアは無保証です。\r
+\r
+--- EOF ---\r
diff --git a/antbuild.xml b/antbuild.xml
new file mode 100644 (file)
index 0000000..3d5a4cd
--- /dev/null
@@ -0,0 +1,576 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    JinArchiverビルドファイル
+
+    Apache Ant [ http://ant.apache.org/ ] 1.7 以降用に記述されています。
+
+    ※各種IDEのビルドファイルと衝突させないため、
+    意図して一般的でないビルドファイル名にしてあります。
+    ant の -buildfile オプションを使ってください。
+
+    Copyright(c) 2009 olyutorskii
+    $Id: antbuild.xml 879 2009-10-25 15:42:50Z olyutorskii $
+-->
+
+<project name="JinArchiver" default="compile">
+
+    <tstamp />
+
+    <description>
+        JinArchive build-file
+    </description>
+
+
+    <property name="title"    value="JinArchiver"                        />
+    <property name="package"  value="jp.sourceforge.jindolf.archiver"         />
+    <property name="jarbase"  value="jinarchiver"                        />
+    <property name="vendor"   value="olyutorskii"                    />
+    <property name="doctitle" value="${title} document"              />
+
+    <property name="entrymain" value="${package}.JinArchiver" />
+
+
+    <property name="src"         location="src/"                   />
+    <property name="testsrc"     location="test/"                  />
+    <property name="build"       location="build/"                 />
+    <property name="classes"     location="${build}/classes/"      />
+    <property name="testclasses" location="${build}/test/classes/" />
+    <property name="dist"        location="dist/"                  />
+    <property name="javadoc"     location="${dist}/javadoc/"       />
+    <property name="scripts"     location="scripts/"               />
+    <property name="depinfo"     location="depend.properties"      />
+
+
+    <property name="build.compiler"          value="modern" />
+    <property name="build.compiler.emacs"    value="true"   />
+    <property name="build.compiler.pedantic" value="true"   />
+
+    <property name="docscope" value="protected" />
+    <property
+        name="jdkhref"
+        value="http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/"
+    />
+<!--
+        value="http://java.sun.com/j2se/1.5.0/docs/api/"
+        value="http://java.sun.com/javase/6/docs/api/"
+        value="http://java.sun.com/javase/6/docs/ja/api/"
+-->
+
+
+    <macrodef name="propertycopy">
+        <attribute name="name" />
+        <attribute name="from" />
+        <sequential>
+            <property name="@{name}" value="${@{from}}" />
+        </sequential>
+    </macrodef>
+
+    <macrodef name="defunset">
+        <attribute name="name" />
+        <sequential>
+            <condition property="@{name}" value="">
+                <not>
+                    <isset property="@{name}" />
+                </not>
+            </condition>
+        </sequential>
+    </macrodef>
+
+    <property environment="myenv" />
+    <macrodef name="envprop">
+        <attribute name="name" />
+        <attribute name="envname" />
+        <sequential>
+            <condition property="@{name}" value="${myenv.@{envname}}">
+                <and>
+                    <not>
+                        <isset property="@{name}" />
+                    </not>
+                    <isset property="myenv.@{envname}" />
+                </and>
+            </condition>
+            <defunset name="@{name}" />
+        </sequential>
+    </macrodef>
+
+    <envprop name="junit.home"      envname="JUNIT_HOME"      />
+    <envprop name="findbugs.home"   envname="FINDBUGS_HOME"   />
+    <envprop name="pmd.home"        envname="PMD_HOME"        />
+    <envprop name="checkstyle.home" envname="CHECKSTYLE_HOME" />
+
+    <property name="findbugs.filter"   location="${scripts}/fbexfilter.xml" />
+    <property name="pmd.rules"         location="${scripts}/pmdrules.xml"   />
+    <property name="checkstyle.checks" location="${scripts}/checks.xml"     />
+
+    <path id="junit.path">
+        <fileset dir="${junit.home}" includes="*.jar, lib/*.jar" />
+    </path>
+
+    <path id="findbugs.path">
+        <fileset dir="${findbugs.home}" includes="*.jar, lib/*.jar" />
+    </path>
+
+    <path id="pmd.path">
+        <fileset dir="${pmd.home}" includes="*.jar, lib/*.jar" />
+    </path>
+
+    <path id="checkstyle.path">
+        <fileset dir="${checkstyle.home}" includes="*.jar, lib/*.jar" />
+    </path>
+
+
+    <propertyset id="pkgversions" dynamic="true">
+        <propertyref regex="^pkg\-version\..*$" />
+    </propertyset>
+
+
+    <fileset id="xmlresources" dir="${src}">
+        <include name="**/*.xsd" />
+        <include name="**/*.xml" />
+    </fileset>
+
+    <fileset id="garbagefiles" dir="${basedir}" defaultexcludes="off">
+        <include name="**/.DS_Store" />
+        <include name="**/Thumbs.db" />
+        <include name="**/core" />
+    </fileset>
+    <defaultexcludes add="**/.DS_Store" />
+    <defaultexcludes add="**/Thumbs.db" />
+    <defaultexcludes add="**/core" />
+
+
+    <target name="init" description="initialize">
+        <echo message="initializing..." />
+    </target>
+
+    <target description="validate XML-resources"
+        name="xmlvalidate" depends="init"
+    >
+        <echo message="validating XML resources..." />
+
+        <xmlvalidate warn="on">
+            <attribute name="http://xml.org/sax/features/validation" value="true" />
+            <attribute name="http://apache.org/xml/features/validation/schema-full-checking" value="true" />
+            <attribute name="http://apache.org/xml/features/validation/dynamic" value="true" />
+            <attribute name="http://apache.org/xml/features/validation/warn-on-undeclared-elemdef" value="true" />
+            <fileset refid="xmlresources" />
+        </xmlvalidate>
+
+        <schemavalidate warn="on" fullchecking="on" >
+            <schema
+                namespace="http://www.w3.org/2001/XMLSchema"
+                url="http://www.w3.org/2001/XMLSchema.xsd"
+            />
+            <attribute name="http://xml.org/sax/features/validation" value="true" />
+            <attribute name="http://apache.org/xml/features/validation/schema-full-checking" value="true" />
+            <attribute name="http://apache.org/xml/features/validation/dynamic" value="true" />
+            <attribute name="http://apache.org/xml/features/validation/warn-on-undeclared-elemdef" value="true" />
+            <fileset refid="xmlresources" />
+        </schemavalidate>
+    </target>
+
+    <target description="compile java-files"
+        name="compile" depends="init"
+    >
+        <echo message="compiling Java sources..." />
+
+        <mkdir dir="${classes}" />
+
+        <javac
+            destdir="${classes}"
+            encoding="UTF-8"
+            target="1.5" debug="on"
+            deprecation="on"
+        >
+            <compilerarg compiler="modern" line="-source 1.5" />
+            <compilerarg compiler="modern" line="-Xlint" />
+            <src path="${src}" />
+            <include name="**/*.java" />
+        </javac>
+
+        <copy todir="${classes}">
+            <fileset dir="${src}">
+                <exclude name="**/*.java" />
+            </fileset>
+        </copy>
+    </target>
+
+    <target description="compile tests pg"
+        name="testcompile" depends="compile"
+    >
+        <echo message="compiling Junit tests..." />
+
+        <mkdir dir="${testclasses}" />
+
+        <javac
+            destdir="${testclasses}"
+            encoding="UTF-8"
+            target="1.5" debug="on"
+            deprecation="on"
+        >
+            <compilerarg compiler="modern" line="-source 1.5" />
+            <compilerarg compiler="modern" line="-Xlint" />
+            <classpath>
+                <path path="${classes}" />
+                <path refid="junit.path" />
+            </classpath>
+            <src path="${testsrc}" />
+            <include name="**/*.java" />
+        </javac>
+
+        <copy todir="${testclasses}">
+            <fileset dir="${testsrc}">
+                <exclude name="**/*.java" />
+            </fileset>
+        </copy>
+    </target>
+
+    <!-- JUnit [ http://www.junit.org/ ] -->
+    <target description="JUnit test"
+        name="test" depends="testcompile"
+    >
+        <echo message="JUnit testing..." />
+
+        <junit
+            printsummary="on"
+            fork="on" forkmode="once"
+            tempdir="${java.io.tmpdir}"
+        >
+            <classpath>
+                <path path="${classes}" />
+                <path path="${testclasses}" />
+                <path refid="junit.path" />
+            </classpath>
+
+            <assertions enableSystemAssertions="true">
+                <enable />
+            </assertions>
+
+            <batchtest>
+                <fileset dir="${testsrc}">
+                    <include name="**/*Test.java" />
+                    <include name="**/Test*.java" />
+                </fileset>
+            </batchtest>
+        </junit>
+    </target>
+
+    <!-- FindBugs [ http://findbugs.sourceforge.net/ ] -->
+    <target description="bytecode analysis"
+        name="findbugs" depends="compile"
+    >
+        <echo message="code analysis with FindBugs..." />
+
+        <taskdef
+            name="findbugs"
+            classpathref="findbugs.path"
+            classname="edu.umd.cs.findbugs.anttask.FindBugsTask"
+        />
+
+        <findbugs
+            home="${findbugs.home}"
+            output="emacs"
+            reportLevel="low"
+            effort="max"
+            excludeFilter="${findbugs.filter}"
+            jvmargs="-Xmx256m"
+        >
+            <sourcePath path="${src}" />
+            <class location="${classes}" />
+        </findbugs>
+    </target>
+
+    <!-- PMD [ http://pmd.sourceforge.net/ ] -->
+    <target description="sourcecode analysis"
+        name="pmd" depends="compile"
+    >
+        <echo message="code analysis with PMD..." />
+
+        <taskdef
+            name="pmd"
+            classpathref="pmd.path"
+            classname="net.sourceforge.pmd.ant.PMDTask"
+        />
+
+        <pmd
+            targetjdk="1.5" encoding="UTF-8"
+            rulesetfiles="${pmd.rules}"
+            shortFilenames="on"
+        >
+            <formatter type="csv" toConsole="true" />
+            <fileset dir="${src}">
+                <include name="**/*.java" />
+            </fileset>
+        </pmd>
+    </target>
+
+    <!-- Checkstyle [ http://checkstyle.sourceforge.net/ ] -->
+    <target description="check source-code..."
+        name="checkstyle" depends="compile"
+    >
+        <echo message="check code style..." />
+
+        <taskdef
+            classpathref="checkstyle.path"
+            resource="checkstyletask.properties"
+        />
+
+        <checkstyle config="${checkstyle.checks}" classpath="${classes}">
+            <fileset dir="${src}">
+                <include name="**/*.java" />
+            </fileset>
+        </checkstyle>
+    </target>
+
+    <target description="resolve depend package info"
+        name="resolvedependpkg" depends="init"
+    >
+        <tempfile
+            property="depversion.temp"
+            prefix="version"
+            suffix=".properties"
+            deleteonexit="on"
+            destdir="${java.io.tmpdir}"
+        />
+
+        <concat destfile="${depversion.temp}" fixlastline="on">
+            <fileset dir="${src}">
+                <include name="**/resources/version.properties" />
+            </fileset>
+        </concat>
+
+        <property file="${depversion.temp}" />
+        <fail unless="pkg-version.${package}">
+            There is no version definition about package [${package}].
+        </fail>
+        <propertycopy name="thisversion" from="pkg-version.${package}" />
+        <echo message="This package was defined as ${thisversion}" />
+
+        <echoproperties destfile="${depversion.temp}" failonerror="on">
+            <propertyset refid="pkgversions" />
+        </echoproperties>
+
+        <concat destfile="${depinfo}">
+            <filterchain>
+                <striplinecomments>
+                    <comment value="#" />
+                </striplinecomments>
+            </filterchain>
+
+            <header
+                trimleading="on" filtering="off"
+                file="${scripts}/depheader.properties"
+            />
+
+            <fileset file="${depversion.temp}" />
+
+            <footer
+                trimleading="on" filtering="off"
+                file="${scripts}/depfooter.properties"
+            />
+        </concat>
+
+        <delete file="${depversion.temp}" />
+    </target>
+
+    <target description="build jar-file"
+        name="jar" depends="sanitize, resolvedependpkg, compile"
+    >
+        <echo message="building JAR archive..." />
+
+        <property
+            name="jarname"
+            location="${dist}/${jarbase}-${thisversion}.jar"
+        />
+
+        <delete file="${jarname}" />
+        <mkdir dir="${dist}" />
+
+        <jar
+            basedir="${classes}"
+            destfile="${jarname}"
+            compress="on"
+            strict="fail"
+        >
+            <manifest>
+                <attribute
+                    name="Main-Class"
+                    value="${entrymain}"
+                />
+                <attribute
+                    name="Implementation-Title"
+                    value="${title}"
+                />
+                <attribute
+                    name="Implementation-Vendor"
+                    value="${vendor}"
+                />
+                <attribute
+                    name="Implementation-Version"
+                    value="${thisversion}"
+                />
+                <attribute
+                    name="Sealed"
+                    value="true"
+                />
+            </manifest>
+        </jar>
+    </target>
+
+    <target description="build javadoc-documents"
+        name="javadocs" depends="init"
+    >
+        <echo message="generating javadoc HTML pages..." />
+
+        <javadoc
+            doctitle="${doctitle}" windowtitle="${doctitle}"
+            destdir="${javadoc}"
+            source="1.5" encoding="UTF-8"
+            charset="UTF-8" docencoding="UTF-8"
+            access="${docscope}"
+            use="on"
+            failonerror="true"
+        >
+            <package name="${package}" />
+            <link href="${jdkhref}" />
+            <fileset dir="${src}">
+                <include name="**/*.java" />
+            </fileset>
+        </javadoc>
+    </target>
+
+    <target description="remove garbage files"
+        name="clean-garbage" depends="init"
+    >
+        <echo message="cleaning garbages..." />
+        <delete verbose="true">
+            <fileset refid="garbagefiles" />
+        </delete>
+    </target>
+
+    <target description="invoke main entry"
+        name="invokemain" depends="compile, testcompile"
+    >
+        <echo message="invoke main entry..." />
+
+        <fail unless="mainclass" />
+
+        <defunset name="linearg" />
+
+        <java classname="${mainclass}" fork="true">
+            <arg line="${linearg}" />
+            <classpath>
+                <pathelement path="${classes}" />
+                <pathelement path="${testclasses}" />
+            </classpath>
+            <assertions enableSystemAssertions="true">
+                <enable />
+            </assertions>
+        </java>
+    </target>
+
+    <target description="sanitize files"
+        name="sanitize" depends="clean-garbage"
+    >
+        <echo message="sanitize now..." />
+
+        <fixcrlf
+            srcDir="${src}" includes="**/*.java"
+            encoding="UTF-8" outputencoding="UTF-8"
+            tablength="4" tab="remove" javafiles="true"
+            eof="remove"
+        />
+
+        <fixcrlf
+            srcDir="${testsrc}" includes="**/*.java"
+            encoding="UTF-8" outputencoding="UTF-8"
+            tablength="4" tab="remove" javafiles="true"
+            eof="remove"
+        />
+
+        <fixcrlf
+            srcDir="${src}" includes="**/*.xml,**/*.xsd"
+            encoding="UTF-8" outputencoding="UTF-8"
+            tablength="8" tab="remove"
+            eol="lf"
+            eof="remove"
+        />
+
+        <fixcrlf
+            srcDir="${testsrc}" includes="**/*.xml,**/*.xsd"
+            encoding="UTF-8" outputencoding="UTF-8"
+            tablength="8" tab="remove"
+            eol="lf"
+            eof="remove"
+        />
+
+        <fixcrlf
+            srcDir="${scripts}" includes="**/*.xml,**/*.xsd"
+            encoding="UTF-8" outputencoding="UTF-8"
+            tablength="8" tab="remove"
+            eol="lf"
+            eof="remove"
+        />
+
+        <fixcrlf
+            srcDir="${src}" includes="**/*.properties"
+            encoding="ISO-8859-1" outputencoding="ISO-8859-1"
+            eof="remove"
+        />
+
+        <fixcrlf
+            srcDir="${testsrc}" includes="**/*.properties"
+            encoding="ISO-8859-1" outputencoding="ISO-8859-1"
+            eof="remove"
+        />
+
+        <fixcrlf
+            srcDir="${scripts}" includes="**/*.properties"
+            encoding="ISO-8859-1" outputencoding="ISO-8859-1"
+            eof="remove"
+        />
+
+        <fixcrlf
+            file="${depinfo}"
+            encoding="ISO-8859-1" outputencoding="ISO-8859-1"
+            eof="remove"
+        />
+
+        <chmod type="file" perm="a-x">
+            <fileset dir="${basedir}" includes="**/*" excludes="**/*.sh" />
+        </chmod>
+    </target>
+
+    <target description="clean-up"
+        name="clean" depends="clean-garbage"
+    >
+        <echo message="cleaning now..." />
+        <delete dir="${build}" />
+        <delete dir="${dist}" />
+        <delete dir="${javadoc}" />
+    </target>
+
+    <target description="All"
+        name="all" depends="xmlvalidate, test, jar, javadocs"
+    />
+
+    <target description="synonym for (compile)"
+        name="build" depends="compile"
+    />
+
+    <target description="synonym for (jar)"
+        name="deploy" depends="jar"
+    />
+
+    <target description="synonym for (javadocs)"
+        name="docs" depends="javadocs"
+    />
+
+    <target description="synonym for (all)"
+        name="main" depends="all"
+    />
+
+</project>
+
+<!-- EOF -->
diff --git a/depend.properties b/depend.properties
new file mode 100644 (file)
index 0000000..362a3d2
--- /dev/null
@@ -0,0 +1,10 @@
+# package dependence informations.\r
+# This file is gererated by Ant, automatically.\r
+#\r
+# $Id: depend.properties 879 2009-10-25 15:42:50Z olyutorskii $\r
+\r
+pkg-version.jp.sourceforge.jindolf.archiver=1.401.2\r
+pkg-version.jp.sourceforge.jindolf.corelib=1.110.2\r
+pkg-version.jp.sourceforge.jindolf.parser=1.358.2\r
+\r
+# EOF #\r
diff --git a/pom.xml b/pom.xml
new file mode 100644 (file)
index 0000000..bdeaae3
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,18 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>jp.sourceforge.jindolf</groupId>
+  <artifactId>JinArchiverHg</artifactId>
+  <packaging>jar</packaging>
+  <version>1.401.2</version>
+  <name>JinArchiverHg</name>
+  <url>http://maven.apache.org</url>
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/src/main/config/checks.xml b/src/main/config/checks.xml
new file mode 100644 (file)
index 0000000..55c809c
--- /dev/null
@@ -0,0 +1,391 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE module PUBLIC
+    "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
+    "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+
+<!--
+    Checkstyle用チェック項目定義。
+
+    Checkstyle5.0以降向けに記述。
+
+    [ http://checkstyle.sourceforge.net/ ]
+
+    Copyright(c) 2009 olyutorskii
+    $Id: checks.xml 835 2009-09-09 11:51:03Z olyutorskii $
+-->
+
+
+<module name="Checker">
+
+    <property name="charset" value="UTF-8" />
+    <property name="severity" value="error" />
+
+
+    <!-- Headers -->
+    <module name="RegexpHeader">
+        <property name="header" value="^/\*$\n^ \*( .*)?$\n^ \*/$\n" />
+        <property name="multiLines" value="2" />
+    </module>
+
+
+    <!-- Javadoc Comments -->
+    <module name="JavadocPackage" />
+
+
+    <!-- Miscellaneous -->
+    <module name="NewlineAtEndOfFile" />
+
+
+    <!-- Regexp -->
+    <module name="RegexpSingleline">
+        <property name="format" value="\s+$" />
+        <property name="minimum" value="0" />
+        <property name="maximum" value="0" />
+    </module>
+
+
+    <!-- Size Violations -->
+    <module name="FileLength">
+        <property name="max" value="2000" />
+    </module>
+
+
+    <!-- Whitespace -->
+    <module name="FileTabCharacter" />
+
+
+<!-- Filters
+    <module name="SeverityMatchFilter" />
+    <module name="SuppressionFilter" />
+    <module name="SuppressionCommentFilter" />
+    <module name="SuppressWithNearbyCommentFilter" />
+-->
+
+
+    <module name="TreeWalker">
+
+        <property name="tabWidth" value="4" />
+
+
+    <!-- Annotations -->
+        <module name="AnnotationUseStyle" />
+        <module name="MissingDeprecated" />
+        <module name="MissingOverride">
+            <property name="javaFiveCompatibility" value="true" />
+        </module>
+        <module name="PackageAnnotation" />
+
+
+    <!-- Block Checks -->
+        <module name="EmptyBlock">
+            <property name="option" value="text" />
+        </module>
+        <module name="LeftCurly">
+            <property name="option" value="eol" />
+        </module>
+        <module name="NeedBraces">
+            <property name="tokens" value="LITERAL_DO" />
+        </module>
+        <module name="RightCurly" />
+        <module name="AvoidNestedBlocks" />
+
+
+    <!-- Class Design -->
+        <module name="VisibilityModifier" />
+        <module name="FinalClass" />
+        <module name="InterfaceIsType" />
+        <module name="HideUtilityClassConstructor" />
+        <module name="MutableException" />
+        <module name="ThrowsCount">
+            <property name="max" value="5" />
+        </module>
+
+
+    <!-- Coding -->
+        <module name="ArrayTrailingComma" />
+        <module name="AvoidInlineConditionals" />
+        <module name="CovariantEquals" />
+        <module name="DoubleCheckedLocking" />
+        <module name="EmptyStatement" />
+        <module name="EqualsHashCode" />
+        <module name="HiddenField">
+            <property name="ignoreConstructorParameter" value="true" />
+            <property name="ignoreSetter" value="true" />
+        </module>
+        <module name="IllegalInstantiation">
+            <property
+                name="classes"
+                value="java.lang.Boolean, java.lang.Integer" />
+        </module>
+        <module name="IllegalToken">
+            <property name="tokens" value="STATIC_IMPORT" />
+        </module>
+        <module name="IllegalTokenText">
+            <property name="tokens" value="NUM_INT, NUM_LONG" />
+            <property name="format" value="^0[^lx]" />
+            <property name="ignoreCase" value="true" />
+        </module>
+        <module name="InnerAssignment" />
+        <module name="MissingSwitchDefault" />
+        <module name="ModifiedControlVariable" />
+        <module name="RedundantThrows">
+            <property name="allowUnchecked" value="true" />
+            <property name="allowSubclasses" value="true" />
+        </module>
+        <module name="SimplifyBooleanExpression" />
+        <module name="SimplifyBooleanReturn" />
+        <module name="StringLiteralEquality" />
+        <module name="NestedIfDepth">
+            <property name="max" value="2" />
+        </module>
+        <module name="NestedTryDepth">
+            <property name="max" value="1" />
+        </module>
+        <module name="SuperClone" />
+        <module name="SuperFinalize" />
+        <module name="IllegalThrows" />
+        <module name="PackageDeclaration" />
+        <module name="JUnitTestCase" />
+        <module name="ReturnCount">
+            <property name="max" value="15" />
+        </module>
+        <module name="ParameterAssignment" />
+        <module name="DefaultComesLast" />
+        <module name="MissingCtor" />
+        <module name="FallThrough" />
+        <module name="MultipleVariableDeclarations" />
+        <module name="UnnecessaryParentheses" />
+
+
+    <!-- Imports -->
+        <module name="AvoidStarImport" />
+        <module name="AvoidStaticImport" />
+        <module name="IllegalImport" />
+        <module name="RedundantImport" />
+        <module name="UnusedImports" />
+        <module name="ImportOrder">
+            <property name="option" value="bottom" />
+        </module>
+
+
+    <!-- Javadoc Comments -->
+        <module name="JavadocType" />
+        <module name="JavadocMethod" />
+        <module name="JavadocVariable">
+            <property name="scope" value="protected" />
+        </module>
+        <module name="JavadocStyle">
+            <property
+                name="endOfSentenceFormat"
+                value="([。.?!][ \t\n\r\f&lt;])|([。.?!]$)" />
+            <property name="checkEmptyJavadoc" value="true" />
+        </module>
+
+
+    <!-- Metrics -->
+        <module name="ClassDataAbstractionCoupling">
+            <property name="max" value="20" />
+        </module>
+
+
+    <!-- Miscellaneous -->
+        <module name="UncommentedMain" />
+        <module name="UpperEll" />
+        <module name="ArrayTypeStyle" />
+        <module name="Regexp">
+            <property name="format" value="^ \* \$Id:[^\$]*\$$" />
+            <property name="duplicateLimit" value="1" />
+        </module>
+        <module name="Regexp">
+            <property name="format" value="@author" />
+            <property name="illegalPattern" value="true" />
+        </module>
+        <module name="Regexp">
+            <property name="format" value="^ \* Copyright\(c\)" />
+        </module>
+
+
+    <!-- Modifiers -->
+        <module name="ModifierOrder" />
+        <module name="RedundantModifier" />
+
+
+    <!-- Naming Conventions -->
+        <module name="AbstractClassName">
+            <property name="format" value="^Abstract.*$|^.*Factory|^.*Adapter$" />
+        </module>
+        <module name="ClassTypeParameterName" />
+        <module name="ConstantName" />
+        <module name="LocalFinalVariableName">
+            <property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$" />
+        </module>
+        <module name="LocalVariableName">
+            <property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$" />
+        </module>
+        <module name="MemberName">
+            <property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$" />
+        </module>
+        <module name="MethodName">
+            <property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$" />
+        </module>
+        <module name="MethodTypeParameterName" />
+        <module name="PackageName" />
+        <module name="ParameterName">
+            <property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$" />
+        </module>
+        <module name="StaticVariableName">
+            <property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$" />
+        </module>
+        <module name="TypeName" />
+
+
+    <!-- Size Violations -->
+        <module name="ExecutableStatementCount">
+            <property name="max" value="150" />
+        </module>
+        <module name="LineLength">
+            <property name="max" value="78" />
+        </module>
+        <module name="MethodLength">
+            <property name="max" value="150" />
+        </module>
+        <module name="AnonInnerLength">
+            <property name="max" value="150" />
+        </module>
+        <module name="ParameterNumber">
+           <property name="max" value="7" />
+        </module>
+        <module name="OuterTypeNumber" />
+
+
+    <!-- Whitespace -->
+        <module name="GenericWhitespace" />
+        <module name="MethodParamPad">
+            <property name="allowLineBreaks" value="false" />
+            <property name="tokens" value="CTOR_DEF, LITERAL_NEW, METHOD_DEF, SUPER_CTOR_CALL" />
+        </module>
+        <module name="NoWhitespaceAfter">
+            <property name="allowLineBreaks" value="false" />
+            <property name="tokens" value="DEC, DOT, INC" />
+        </module>
+        <module name="NoWhitespaceBefore">
+            <property name="allowLineBreaks" value="false" />
+            <property name="tokens" value="POST_DEC, POST_INC" />
+        </module>
+        <module name="NoWhitespaceBefore">
+            <property name="allowLineBreaks" value="true" />
+            <property name="tokens" value="SEMI" />
+        </module>
+        <module name="OperatorWrap">
+            <property name="option" value="eol" />
+            <property
+                name="tokens"
+                value="ASSIGN,
+                       BAND_ASSIGN, BOR_ASSIGN, BXOR_ASSIGN,
+                       PLUS_ASSIGN, MINUS_ASSIGN,
+                       STAR_ASSIGN, DIV_ASSIGN, MOD_ASSIGN,
+                       SL_ASSIGN, SR_ASSIGN"
+            />
+        </module>
+        <module name="OperatorWrap">
+            <property name="option" value="nl" />
+            <property
+                name="tokens"
+                value="BAND, BOR, BXOR,
+                       MINUS, STAR, DIV, MOD,
+                       LAND, LOR,
+                       EQUAL"
+            />
+        </module>
+        <module name="ParenPad">
+            <property name="option" value="nospace" />
+            <property name="tokens" value="CTOR_CALL, METHOD_CALL, SUPER_CTOR_CALL" />
+        </module>
+        <module name="TypecastParenPad">
+            <property name="option" value="nospace" />
+        </module>
+        <module name="WhitespaceAfter">
+            <property name="tokens" value="COMMA, SEMI" />
+        </module>
+        <module name="WhitespaceAround">
+            <property
+                name="tokens"
+                value="ASSIGN,
+                       LAND, LOR,
+                       BAND, BOR, BXOR, BSR,
+                       BAND_ASSIGN, BOR_ASSIGN, BXOR_ASSIGN, BSR_ASSIGN,
+                       SL, SR,
+                       SL_ASSIGN, SR_ASSIGN,
+                       MINUS, STAR, DIV, MOD,
+                       PLUS_ASSIGN, MINUS_ASSIGN, STAR_ASSIGN, DIV_ASSIGN, MOD_ASSIGN,
+                       EQUAL, NOT_EQUAL, GT, GE, LT, LE,
+                       "
+            />
+        </module>
+
+
+<!-- たまに確認したいチェック
+
+Coding
+        <module name="MagicNumber" />
+        <module name="NoClone" />
+        <module name="IllegalCatch" />
+        <module name="IllegalType" />
+        <module name="MultipleStringLiterals" />
+
+Duplicate Code
+        <module name="StrictDuplicateCode" />
+
+Metrics
+        <module name="BooleanExpressionComplexity" />
+        <module name="ClassFanOutComplexity" />
+        <module name="CyclomaticComplexity" />
+        <module name="NPathComplexity" />
+        <module name="JavaNCSS" />
+
+Miscellaneous
+        <module name="TodoComment">
+            <property name="format" value="TODO" />
+        </module>
+        <module name="Indentation">
+            <property name="basicOffset" value="4" />
+            <property name="caseIndent" value="0" />
+        </module>
+        <module name="TrailingComment" />
+
+-->
+
+<!-- 代用品で解決
+        <module name="Header" />
+        <module name="RegexpSingleline" />
+        <module name="RegexpMultiline" />
+        <module name="RegexpSinglelineJava" />
+-->
+
+<!-- ボツチェック
+        <module name="FinalLocalVariable" />
+        <module name="DesignForExtension" />
+        <module name="EqualsAvoidNull" />
+        <module name="NoFinalizer" />
+        <module name="DeclarationOrder" />
+        <module name="ExplicitInitialization" />
+        <module name="ImportControl" />
+        <module name="WriteTag">
+        <module name="Translation" />
+        <module name="FinalParameters" />
+        <module name="DescendantToken">
+        <module name="EmptyForInitializerPad" />
+        <module name="EmptyForIteratorPad" />
+        <module name="SuppressWarnings">
+-->
+
+<!-- バグ?
+        <module name="RequireThis" />
+        <module name="TabCharacter" />
+-->
+
+    </module>
+</module>
+
+<!-- EOF -->
diff --git a/src/main/config/pmdrules.xml b/src/main/config/pmdrules.xml
new file mode 100644 (file)
index 0000000..980b31f
--- /dev/null
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    PMD用ルールセット定義
+
+    PMD [ http://pmd.sourceforge.net/ ] 4.2.5 以降用に記述されています。
+
+    Copyright(c) 2009 olyutorskii
+    $Id: pmdrules.xml 836 2009-09-09 15:58:36Z olyutorskii $
+-->
+
+<ruleset>
+    <rule ref="rulesets/basic.xml">
+        <exclude name="UnnecessaryReturn" />
+    </rule>
+
+    <rule ref="rulesets/braces.xml">
+        <exclude name="IfElseStmtsMustUseBraces" />
+        <exclude name="IfStmtsMustUseBraces" />
+    </rule>
+
+    <rule ref="rulesets/codesize.xml">
+        <exclude name="CyclomaticComplexity" />
+        <exclude name="ExcessiveClassLength" />
+        <exclude name="NPathComplexity" />
+        <exclude name="TooManyFields" />
+        <exclude name="TooManyMethods" />
+    </rule>
+
+    <rule ref="rulesets/clone.xml">
+        <exclude name="CloneThrowsCloneNotSupportedException" />
+    </rule>
+
+    <rule ref="rulesets/coupling.xml" />
+
+    <rule ref="rulesets/design.xml">
+        <exclude name="AvoidReassigningParameters" />
+        <exclude name="ConfusingTernary" />
+        <exclude name="PositionLiteralsFirstInComparisons" />
+        <exclude name="UnnecessaryLocalBeforeReturn" />
+    </rule>
+
+    <rule ref="rulesets/finalizers.xml" />
+
+    <rule ref="rulesets/imports.xml" />
+
+    <rule ref="rulesets/logging-java.xml" />
+
+    <rule ref="rulesets/migrating.xml" />
+
+    <rule ref="rulesets/naming.xml">
+        <exclude name="AbstractNaming" />
+        <exclude name="LongVariable" />
+        <exclude name="ShortVariable" />
+    </rule>
+    <rule ref="rulesets/naming.xml/LongVariable">
+        <properties>
+            <property name="minimum" value="24" />
+        </properties>
+    </rule>
+
+    <rule ref="rulesets/optimizations.xml">
+        <exclude name="LocalVariableCouldBeFinal" />
+        <exclude name="MethodArgumentCouldBeFinal" />
+        <exclude name="UseStringBufferForStringAppends" />
+    </rule>
+
+    <rule ref="rulesets/strictexception.xml">
+        <exclude name="AvoidThrowingNullPointerException" />
+    </rule>
+
+    <rule ref="rulesets/strings.xml">
+        <exclude name="AvoidDuplicateLiterals" />
+    </rule>
+
+    <rule ref="rulesets/sunsecure.xml" />
+
+    <rule ref="rulesets/unusedcode.xml" />
+
+    <rule ref="rulesets/controversial.xml">
+        <exclude name="DataflowAnomalyAnalysis" />
+        <exclude name="NullAssignment" />
+        <exclude name="OnlyOneReturn" />
+    </rule>
+</ruleset>
+
+<!-- EOF -->
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/AvatarData.java b/src/main/java/jp/sourceforge/jindolf/archiver/AvatarData.java
new file mode 100644 (file)
index 0000000..5e902c9
--- /dev/null
@@ -0,0 +1,180 @@
+/*\r
+ * avatar model\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: AvatarData.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.io.IOException;\r
+import java.io.Writer;\r
+import java.util.List;\r
+import javax.xml.parsers.DocumentBuilder;\r
+import javax.xml.parsers.DocumentBuilderFactory;\r
+import jp.sourceforge.jindolf.corelib.PreDefAvatar;\r
+\r
+/**\r
+ * Avatarモデル。\r
+ */\r
+public class AvatarData{\r
+\r
+    private static final List<PreDefAvatar> PREDEF_AVATAR_LIST;\r
+\r
+    static{\r
+        DocumentBuilderFactory factory =\r
+                DocumentBuilderFactory.newInstance();\r
+        try{\r
+            DocumentBuilder builder = factory.newDocumentBuilder();\r
+            PREDEF_AVATAR_LIST = PreDefAvatar.buildPreDefAvatarList(builder);\r
+        }catch(RuntimeException e){\r
+            throw e;\r
+        }catch(Exception e){\r
+            throw new ExceptionInInitializerError(e);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * プリセット済みAvatarをフルネームを用いて取得する。\r
+     * @param seq フルネーム\r
+     * @return 見つかったプリセット済みAvatar。見つからなければnull。\r
+     */\r
+    public static PreDefAvatar getPreDefAvatar(CharSequence seq){\r
+        for(PreDefAvatar avatar : PREDEF_AVATAR_LIST){\r
+            String fullName = avatar.getFullName();\r
+            if(fullName.contentEquals(seq)){\r
+                return avatar;\r
+            }\r
+        }\r
+        return null;\r
+    }\r
+\r
+    private String fullName;\r
+    private String shortName;\r
+    private String avatarId;\r
+    private String faceIconUri;\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     */\r
+    public AvatarData(){\r
+        super();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param predefAvatar プリセット済みAvatar\r
+     */\r
+    public AvatarData(PreDefAvatar predefAvatar){\r
+        this();\r
+\r
+        this.fullName = predefAvatar.getFullName();\r
+        this.shortName = predefAvatar.getShortName();\r
+        this.avatarId = predefAvatar.getAvatarId();\r
+        this.faceIconUri = null;\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * フルネームを取得する。\r
+     * @return フルネーム\r
+     */\r
+    public String getFullName(){\r
+        return this.fullName;\r
+    }\r
+\r
+    /**\r
+     * フルネームを設定する。\r
+     * @param fullName フルネーム\r
+     */\r
+    public void setFullName(String fullName){\r
+        this.fullName = fullName;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 短縮名を取得する。\r
+     * @return 短縮名\r
+     */\r
+    public String getShortName(){\r
+        return this.shortName;\r
+    }\r
+\r
+    /**\r
+     * 短縮名を設定する。\r
+     * @param shortName 短縮名\r
+     */\r
+    public void setShortName(String shortName){\r
+        this.shortName = shortName;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * Avatar識別子を取得する。\r
+     * @return Avatar識別子\r
+     */\r
+    public String getAvatarId(){\r
+        return this.avatarId;\r
+    }\r
+\r
+    /**\r
+     * Avatar識別子を設定する。\r
+     * @param avatarId Avatar識別子\r
+     */\r
+    public void setAvatarId(String avatarId){\r
+        this.avatarId = avatarId;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 顔アイコンURI文字列を取得する。\r
+     * @return 顔アイコンURI文字列\r
+     */\r
+    public String getFaceIconUri(){\r
+        return this.faceIconUri;\r
+    }\r
+\r
+    /**\r
+     * 顔アイコンURI文字列を設定する。\r
+     * @param faceIconUri 顔アイコンURI文字列\r
+     */\r
+    public void setFaceIconUri(String faceIconUri){\r
+        this.faceIconUri = faceIconUri;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * avatar要素をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpXml(Writer writer) throws IOException{\r
+        writer.append("<avatar\n");\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "avatarId", this.avatarId);\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "fullName", this.fullName);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "shortName", this.shortName);\r
+        writer.append('\n');\r
+\r
+        if(this.faceIconUri != null){\r
+            XmlUtils.indent(writer, 1);\r
+            XmlUtils.attrOut(writer, "faceIconURI", this.faceIconUri);\r
+            writer.append('\n');\r
+            // F1014対策\r
+        }\r
+\r
+        writer.append("/>\n");\r
+        writer.flush();\r
+\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/Builder.java b/src/main/java/jp/sourceforge/jindolf/archiver/Builder.java
new file mode 100644 (file)
index 0000000..eae53b9
--- /dev/null
@@ -0,0 +1,93 @@
+/*\r
+ * information builder from input\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: Builder.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.net.URL;\r
+import java.net.URLConnection;\r
+import jp.sourceforge.jindolf.parser.ContentBuilder;\r
+import jp.sourceforge.jindolf.parser.DecodeException;\r
+import jp.sourceforge.jindolf.parser.DecodedContent;\r
+import jp.sourceforge.jindolf.parser.HtmlParseException;\r
+import jp.sourceforge.jindolf.parser.HtmlParser;\r
+import jp.sourceforge.jindolf.parser.SjisDecoder;\r
+import jp.sourceforge.jindolf.parser.StreamDecoder;\r
+\r
+/**\r
+ * 入力から内部構造を生成する。\r
+ */\r
+public final class Builder{\r
+\r
+    /**\r
+     * 入力ストリームをShift_JISでデコードする。\r
+     * @param istream 入力\r
+     * @return デコード結果\r
+     * @throws IOException 入力エラー\r
+     * @throws DecodeException デコードエラー\r
+     */\r
+    public static DecodedContent contentFromStream(InputStream istream)\r
+            throws IOException, DecodeException{\r
+        StreamDecoder decoder = new SjisDecoder();\r
+        ContentBuilder builder = new ContentBuilder();\r
+        decoder.setDecodeHandler(builder);\r
+\r
+        decoder.decode(istream);\r
+\r
+        DecodedContent content = builder.getContent();\r
+\r
+        return content;\r
+    }\r
+\r
+    /**\r
+     * 村の各日々をロードしパースする。\r
+     * @param villageData 村情報\r
+     * @throws IOException 入力エラー\r
+     * @throws DecodeException デコードエラー\r
+     * @throws HtmlParseException パースエラー\r
+     */\r
+    public static void fillVillageData(VillageData villageData)\r
+            throws IOException, DecodeException, HtmlParseException {\r
+        HtmlParser parser = new HtmlParser();\r
+        Handler handler = new Handler();\r
+        parser.setBasicHandler   (handler);\r
+        parser.setTalkHandler    (handler);\r
+        parser.setSysEventHandler(handler);\r
+\r
+        handler.initVillageData(villageData);\r
+\r
+        for(PeriodResource resource : villageData.getPeriodResourceList()){\r
+            handler.initPeriodResource(resource);\r
+            URL url;\r
+            url = resource.getResourceUrl();\r
+            if(url == null){\r
+                url = new URL(resource.getOrigUrlText());\r
+            }\r
+            URLConnection conn = url.openConnection();\r
+            InputStream istream = conn.getInputStream();\r
+            if(resource.getDownTimeMs() <= 0){\r
+                long downTimeMs = conn.getDate();\r
+                resource.setDownTimeMs(downTimeMs);\r
+            }\r
+            DecodedContent content = contentFromStream(istream);\r
+            istream.close();\r
+            parser.parseAutomatic(content);\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 隠れコンストラクタ。\r
+     */\r
+    private Builder(){\r
+        super();\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/EventData.java b/src/main/java/jp/sourceforge/jindolf/archiver/EventData.java
new file mode 100644 (file)
index 0000000..8c2ecf3
--- /dev/null
@@ -0,0 +1,539 @@
+/*\r
+ * system event\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: EventData.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.io.IOException;\r
+import java.io.Writer;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import jp.sourceforge.jindolf.corelib.GameRole;\r
+import jp.sourceforge.jindolf.corelib.SysEventType;\r
+import jp.sourceforge.jindolf.parser.DecodedContent;\r
+\r
+/**\r
+ * システムイベント モデル。\r
+ */\r
+public class EventData extends TopicData{\r
+\r
+    /**\r
+     * イベント種別からXML要素名を取得する。\r
+     * @param type イベント種別\r
+     * @return 要素名\r
+     */\r
+    public static String getTagName(SysEventType type){\r
+        String tagName;\r
+\r
+        switch(type){\r
+        case STARTENTRY:   tagName = "startEntry";   break;\r
+        case ONSTAGE:      tagName = "onStage";      break;\r
+        case STARTMIRROR:  tagName = "startMirror";  break;\r
+        case OPENROLE:     tagName = "openRole";     break;\r
+        case MURDERED:     tagName = "murdered";     break;\r
+        case STARTASSAULT: tagName = "startAssault"; break;\r
+        case SURVIVOR:     tagName = "survivor";     break;\r
+        case COUNTING:     tagName = "counting";     break;\r
+        case NOMURDER:     tagName = "noMurder";     break;\r
+        case SUDDENDEATH:  tagName = "suddenDeath";  break;\r
+        case WINVILLAGE:   tagName = "winVillage";   break;\r
+        case WINWOLF:      tagName = "winWolf";      break;\r
+        case WINHAMSTER:   tagName = "winHamster";   break;\r
+        case PLAYERLIST:   tagName = "playerList";   break;\r
+        case PANIC:        tagName = "panic";        break;\r
+        case ASKENTRY:     tagName = "askEntry";     break;\r
+        case ASKCOMMIT:    tagName = "askCommit";    break;\r
+        case NOCOMMENT:    tagName = "noComment";    break;\r
+        case STAYEPILOGUE: tagName = "stayEpilogue"; break;\r
+        case GAMEOVER:     tagName = "gameOver";     break;\r
+        case GUARD:        tagName = "guard";        break;\r
+        case JUDGE:        tagName = "judge";        break;\r
+        case ASSAULT:      tagName = "assault";      break;\r
+        default: throw new IllegalArgumentException();\r
+        }\r
+\r
+        return tagName;\r
+    }\r
+\r
+    /**\r
+     * 役職からXMLシンボル名を取得する。\r
+     * @param role 役職\r
+     * @return XMLシンボル名\r
+     */\r
+    public static String getRoleAttrValue(GameRole role){\r
+        String roleName;\r
+\r
+        switch(role){\r
+        case INNOCENT: roleName = "innocent"; break;\r
+        case WOLF:     roleName = "wolf";     break;\r
+        case SEER:     roleName = "seer";     break;\r
+        case SHAMAN:   roleName = "shaman";   break;\r
+        case MADMAN:   roleName = "madman";   break;\r
+        case HUNTER:   roleName = "hunter";   break;\r
+        case FRATER:   roleName = "frater";   break;\r
+        case HAMSTER:  roleName = "hamster";  break;\r
+        default: throw new IllegalArgumentException();\r
+        }\r
+\r
+        return roleName;\r
+    }\r
+\r
+    /**\r
+     * avatarRef要素をXML出力する。\r
+     * @param writer 出力先\r
+     * @param avatar Avatar\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void dumpAvatarRef(Writer writer, AvatarData avatar)\r
+            throws IOException{\r
+        writer.append("<avatarRef");\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "avatarId", avatar.getAvatarId());\r
+        writer.append(" />\n");\r
+        return;\r
+    }\r
+\r
+    private final PeriodData periodData;\r
+    private SysEventType eventType = null;\r
+\r
+    private final List<AvatarData> avatarList = new LinkedList<AvatarData>();\r
+    private final List<Integer> intList = new LinkedList<Integer>();\r
+    private final List<GameRole> roleList = new LinkedList<GameRole>();\r
+    private final List<DecodedContent> strList =\r
+            new LinkedList<DecodedContent>();\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param periodData 所属元Period\r
+     */\r
+    public EventData(PeriodData periodData){\r
+        super();\r
+        this.periodData = periodData;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * システムイベント種別を取得する。\r
+     * @return システムイベント種別\r
+     */\r
+    public SysEventType getEventType(){\r
+        return this.eventType;\r
+    }\r
+\r
+    /**\r
+     * システムイベント種別を設定する。\r
+     * @param eventType システムイベント種別\r
+     */\r
+    public void setEventType(SysEventType eventType){\r
+        this.eventType = eventType;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * Avatar情報を追加する。\r
+     * @param avatarData Avatar情報\r
+     */\r
+    public void addAvatarData(AvatarData avatarData){\r
+        this.avatarList.add(avatarData);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 整数情報を追加する。\r
+     * @param intVal 整数情報\r
+     */\r
+    public void addInteger(int intVal){\r
+        this.intList.add(intVal);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 役職情報を追加する。\r
+     * @param role 役職情報\r
+     */\r
+    public void addGameRole(GameRole role){\r
+        this.roleList.add(role);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 文字列情報を追加する。\r
+     * @param seq 文字列情報\r
+     */\r
+    public void addDecodedContent(DecodedContent seq){\r
+        this.strList.add(seq);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * ONSTAGE属性値をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpOnstageAttr(Writer writer) throws IOException{\r
+        int entryNo = this.intList.get(0);\r
+        AvatarData avatarData = this.avatarList.get(0);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "entryNo", Integer.toString(entryNo));\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "avatarId", avatarData.getAvatarId());\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * COUNTING属性値をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpCountingAttr(Writer writer) throws IOException{\r
+        int total = this.avatarList.size();\r
+        if(total % 2 == 1){\r
+            AvatarData victim = this.avatarList.get(total - 1);\r
+            writer.append(' ');\r
+            XmlUtils.attrOut(writer, "victim", victim.getAvatarId());\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * SUDDENDEATH属性値をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpSuddendeathAttr(Writer writer) throws IOException{\r
+        AvatarData avatarData = this.avatarList.get(0);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "avatarId", avatarData.getAvatarId());\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * ASKENTRY属性値をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpAskEntryAttr(Writer writer) throws IOException{\r
+        int hour     = this.intList.get(0);\r
+        int minute   = this.intList.get(1);\r
+        int minLimit = this.intList.get(2);\r
+        int maxLimit = this.intList.get(3);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.timeAttrOut(writer, "commitTime", hour, minute);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "minMembers", Integer.toString(minLimit));\r
+\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "maxMembers", Integer.toString(maxLimit));\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * ASKCOMMIT属性値をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpAskCommitAttr(Writer writer) throws IOException{\r
+        int hour     = this.intList.get(0);\r
+        int minute   = this.intList.get(1);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.timeAttrOut(writer, "limitVote", hour, minute);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.timeAttrOut(writer, "limitSpecial", hour, minute);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * STAYEPILOGUE属性値をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpStayEpilogueAttr(Writer writer) throws IOException{\r
+        GameRole role = this.roleList.get(0);\r
+        int hour   = this.intList.get(0);\r
+        int minute = this.intList.get(1);\r
+\r
+        String winner;\r
+        switch(role){\r
+        case INNOCENT: winner = "village"; break;\r
+        case WOLF:     winner = "wolf";    break;\r
+        case HAMSTER:  winner = "hamster"; break;\r
+        default: throw new IllegalArgumentException();\r
+        }\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "maxMembers", winner);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.timeAttrOut(writer, "limitTime", hour, minute);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * openRole子要素をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpOpenroleElem(Writer writer) throws IOException{\r
+        int num = this.roleList.size();\r
+        for(int index = 0; index < num; index++){\r
+            int heads = this.intList.get(index);\r
+            GameRole role = this.roleList.get(index);\r
+            String roleName = getRoleAttrValue(role);\r
+\r
+            writer.append("<roleHeads");\r
+            writer.append(' ');\r
+            XmlUtils.attrOut(writer, "role", roleName);\r
+            writer.append(' ');\r
+            XmlUtils.attrOut(writer, "heads", Integer.toString(heads));\r
+            writer.append(" />\n");\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * murdered子要素をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpMurderedElem(Writer writer) throws IOException{\r
+        for(AvatarData avatar : this.avatarList){\r
+            dumpAvatarRef(writer, avatar);\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * survivor子要素をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpSurvivorElem(Writer writer) throws IOException{\r
+        for(AvatarData avatar : this.avatarList){\r
+            dumpAvatarRef(writer, avatar);\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * nocomment子要素をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpNoCommentElem(Writer writer) throws IOException{\r
+        for(AvatarData avatar : this.avatarList){\r
+            dumpAvatarRef(writer, avatar);\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * counting子要素をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpCountingElem(Writer writer) throws IOException{\r
+        int total = this.avatarList.size();\r
+        total = total / 2 * 2;\r
+        for(int index = 0; index < total; index += 2){\r
+            AvatarData voteBy = this.avatarList.get(index);\r
+            AvatarData voteTo = this.avatarList.get(index + 1);\r
+            writer.append("<vote");\r
+            writer.append(' ');\r
+            XmlUtils.attrOut(writer, "byWhom", voteBy.getAvatarId());\r
+            writer.append(' ');\r
+            XmlUtils.attrOut(writer, "target", voteTo.getAvatarId());\r
+            writer.append(" />\n");\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * playerlist子要素をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpPlayerlistElem(Writer writer) throws IOException{\r
+        int num = this.avatarList.size();\r
+\r
+        for(int index = 0; index < num; index++){\r
+            AvatarData avatar = this.avatarList.get(index);\r
+            DecodedContent uri     = this.strList.get(index * 2);\r
+            DecodedContent account = this.strList.get(index * 2 + 1);\r
+            int isLiving = this.intList.get(index);\r
+            String survive;\r
+            if(isLiving == 0) survive = "false";\r
+            else              survive = "true";\r
+            GameRole role = this.roleList.get(index);\r
+            String roleName = getRoleAttrValue(role);\r
+\r
+            writer.append("<playerInfo");\r
+            writer.append(' ');\r
+            XmlUtils.attrOut(writer, "playerId", account.toString());\r
+            writer.append(' ');\r
+            XmlUtils.attrOut(writer, "avatarId", avatar.getAvatarId());\r
+            writer.append(' ');\r
+            XmlUtils.attrOut(writer, "survive", survive);\r
+            writer.append(' ');\r
+            XmlUtils.attrOut(writer, "role", roleName);\r
+\r
+            String uriStr = uri.toString();\r
+            uriStr = uriStr.replaceAll("^[\\s]+", "");\r
+            uriStr = uriStr.replaceAll("[\\s]+$", "");\r
+            uriStr = uriStr.replaceAll("[\\s]+", "\u0020");\r
+            if(uriStr.length() > 0){\r
+                writer.append(' ');\r
+                XmlUtils.attrOut(writer, "uri", uriStr);\r
+            }\r
+\r
+            writer.append(" />\n");\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * Avatar間関係の属性値をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpByWhomAttr(Writer writer) throws IOException{\r
+        AvatarData by = this.avatarList.get(0);\r
+        AvatarData to = this.avatarList.get(1);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "byWhom", by.getAvatarId());\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "target", to.getAvatarId());\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * ASSAULT属性値をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpAssaultAttr(Writer writer) throws IOException{\r
+        AvatarData by = this.avatarList.get(0);\r
+        AvatarData to = this.avatarList.get(1);\r
+\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "byWhom", by.getAvatarId());\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "target", to.getAvatarId());\r
+        writer.append('\n');\r
+\r
+        DecodedContent xname = this.strList.get(0);\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "xname", xname);\r
+\r
+        int hour = this.intList.get(0);\r
+        int minute = this.intList.get(1);\r
+        writer.append(' ');\r
+        XmlUtils.timeAttrOut(writer, "time", hour, minute);\r
+        writer.append('\n');\r
+\r
+        String icon = this.strList.get(1).toString();\r
+        if( ! icon.equals(by.getFaceIconUri()) ){\r
+            XmlUtils.indent(writer, 1);\r
+            XmlUtils.attrOut(writer, "faceIconURI", icon);\r
+            writer.append('\n');\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * システムイベント各種要素のXML出力を行う。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpXml(Writer writer) throws IOException{\r
+        String tagName = getTagName(this.eventType);\r
+\r
+        writer.append("<");\r
+        writer.append(tagName);\r
+\r
+        boolean hasAttr = true;\r
+        switch(this.eventType){\r
+        case ONSTAGE:\r
+            dumpOnstageAttr(writer);\r
+            break;\r
+        case COUNTING:\r
+            dumpCountingAttr(writer);\r
+            break;\r
+        case SUDDENDEATH:\r
+            dumpSuddendeathAttr(writer);\r
+            break;\r
+        case ASKENTRY:\r
+            dumpAskEntryAttr(writer);\r
+            break;\r
+        case ASKCOMMIT:\r
+            dumpAskCommitAttr(writer);\r
+            break;\r
+        case STAYEPILOGUE:\r
+            dumpStayEpilogueAttr(writer);\r
+            break;\r
+        case JUDGE:\r
+        case GUARD:\r
+            dumpByWhomAttr(writer);\r
+            break;\r
+        case ASSAULT:\r
+            dumpAssaultAttr(writer);\r
+            break;\r
+        default:\r
+            hasAttr = false;\r
+            break;\r
+        }\r
+\r
+        if(hasAttr) writer.append(' ');\r
+        writer.append(">\n");\r
+\r
+        dumpLines(writer);\r
+\r
+        switch(this.eventType){\r
+        case OPENROLE:\r
+            dumpOpenroleElem(writer);\r
+            break;\r
+        case MURDERED:\r
+            dumpMurderedElem(writer);\r
+            break;\r
+        case SURVIVOR:\r
+            dumpSurvivorElem(writer);\r
+            break;\r
+        case COUNTING:\r
+            dumpCountingElem(writer);\r
+            break;\r
+        case PLAYERLIST:\r
+            dumpPlayerlistElem(writer);\r
+            break;\r
+        case NOCOMMENT:\r
+            dumpNoCommentElem(writer);\r
+            break;\r
+        default:\r
+            break;\r
+        }\r
+\r
+        writer.append("</");\r
+        writer.append(tagName);\r
+        writer.append(">\n");\r
+\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/FileArchive.java b/src/main/java/jp/sourceforge/jindolf/archiver/FileArchive.java
new file mode 100644 (file)
index 0000000..0dbd344
--- /dev/null
@@ -0,0 +1,167 @@
+/*\r
+ * file archive utilities\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: FileArchive.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.InputStreamReader;\r
+import java.io.LineNumberReader;\r
+import java.io.Reader;\r
+import java.net.MalformedURLException;\r
+import java.net.URI;\r
+import java.net.URL;\r
+import java.text.DateFormat;\r
+import java.text.ParseException;\r
+import java.text.SimpleDateFormat;\r
+import java.util.Date;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.Locale;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+import jp.sourceforge.jindolf.corelib.LandDef;\r
+import jp.sourceforge.jindolf.corelib.PeriodType;\r
+\r
+/**\r
+ * ファイルシステム上に納められた\r
+ * 生XHTMLデータおよびログファイルへのアクセス諸々。\r
+ */\r
+public final class FileArchive{\r
+\r
+    private static final Pattern LINE_PATTERN;\r
+    private static final DateFormat ISO_FORMAT;\r
+\r
+    static{\r
+        String fnameRegex =\r
+                 "(jin_([^_]+)_(\\d+)_(\\d+)_"\r
+                +"(?:(prologue)|(progress)|(epilogue))"\r
+                +"\\.html)";\r
+        LINE_PATTERN = Pattern.compile(\r
+            "^" + fnameRegex + "\\s+(\\S+)\\s+(\\S+)\\s+(\\d)" + "$");\r
+\r
+        ISO_FORMAT =\r
+                new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.JAPAN);\r
+    }\r
+\r
+    /**\r
+     * ISO形式の日付時刻情報をパースする。\r
+     * @param text 日付表記\r
+     * @return エポック秒(ms)\r
+     */\r
+    public static long parseISODate(String text){\r
+        Date date;\r
+        try{\r
+            synchronized(ISO_FORMAT){\r
+                date = ISO_FORMAT.parse(text);\r
+            }\r
+        }catch(ParseException e){\r
+            throw new IllegalArgumentException(e);\r
+        }\r
+        return date.getTime();\r
+    }\r
+\r
+    /**\r
+     * ログ記述からリソース情報を生成する。\r
+     * @param logLine 1行に納められたログ記述\r
+     * @return リソース情報\r
+     */\r
+    public static PeriodResource parseDownLogLine(CharSequence logLine){\r
+        PeriodResource result;\r
+\r
+        Matcher matcher = LINE_PATTERN.matcher(logLine);\r
+        if( ! matcher.matches() ) throw new IllegalArgumentException();\r
+\r
+        String fname = matcher.group(1);\r
+        String landId = matcher.group(2);\r
+        int villageId = Integer.parseInt(matcher.group(3));\r
+        int day = Integer.parseInt(matcher.group(4));\r
+\r
+        PeriodType periodType;\r
+        if     (matcher.start(5) >= 0) periodType = PeriodType.PROLOGUE;\r
+        else if(matcher.start(6) >= 0) periodType = PeriodType.PROGRESS;\r
+        else if(matcher.start(7) >= 0) periodType = PeriodType.EPILOGUE;\r
+        else throw new IllegalArgumentException();\r
+\r
+        String uriText = matcher.group(8);\r
+        String dateText = matcher.group(9);\r
+        int hasError = Integer.parseInt(matcher.group(10));\r
+        if(hasError != 0) throw new IllegalArgumentException();\r
+\r
+        long dateMs = parseISODate(dateText);\r
+\r
+        if(landId.equals("wolf0")) landId = "wolf";\r
+        if(landId.equals("wolf1")) landId = "wolf0";\r
+        LandDef landDef = JinArchiver.getLandDef(landId);\r
+\r
+        File file = new File(fname);\r
+        URI fileUri = file.toURI();\r
+        URL fileUrl;\r
+        try{\r
+            fileUrl = fileUri.toURL();\r
+        }catch(MalformedURLException e){\r
+            throw new IllegalArgumentException(e);\r
+        }\r
+\r
+        result = new PeriodResource(landDef,\r
+                                    villageId,\r
+                                    periodType,\r
+                                    day,\r
+                                    uriText,\r
+                                    dateMs,\r
+                                    fileUrl );\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * ログファイルからリソース列を抽出する。\r
+     * @param reader ログファイルの内容\r
+     * @return リソース列\r
+     * @throws IOException 入力エラー\r
+     */\r
+    public static List<PeriodResource> parseDownList(LineNumberReader reader)\r
+            throws IOException{\r
+        List<PeriodResource> result = new LinkedList<PeriodResource>();\r
+\r
+        for(;;){\r
+            String line = reader.readLine();\r
+            if(line == null) break;\r
+            PeriodResource info = parseDownLogLine(line);\r
+            if(info == null){\r
+                throw new IllegalArgumentException();\r
+            }\r
+            result.add(info);\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * ログファイルからリソース列を抽出する。\r
+     * @param istream ログファイルの内容\r
+     * @return リソース列\r
+     * @throws IOException 入力エラー\r
+     */\r
+    public static List<PeriodResource> parseDownloadLog(InputStream istream)\r
+            throws IOException{\r
+        Reader reader = new InputStreamReader(istream, "US-ASCII");\r
+        LineNumberReader lineReader = new LineNumberReader(reader);\r
+        List<PeriodResource> result = parseDownList(lineReader);\r
+        lineReader.close();\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * 隠れコンストラクタ。\r
+     */\r
+    private FileArchive(){\r
+        throw new Error();\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/Handler.java b/src/main/java/jp/sourceforge/jindolf/archiver/Handler.java
new file mode 100644 (file)
index 0000000..9309f4c
--- /dev/null
@@ -0,0 +1,785 @@
+/*\r
+ * parse handler\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: Handler.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+import jp.sourceforge.jindolf.corelib.DisclosureType;\r
+import jp.sourceforge.jindolf.corelib.EventFamily;\r
+import jp.sourceforge.jindolf.corelib.GameRole;\r
+import jp.sourceforge.jindolf.corelib.LandDef;\r
+import jp.sourceforge.jindolf.corelib.PeriodType;\r
+import jp.sourceforge.jindolf.corelib.SysEventType;\r
+import jp.sourceforge.jindolf.corelib.TalkType;\r
+import jp.sourceforge.jindolf.corelib.Team;\r
+import jp.sourceforge.jindolf.corelib.VillageTag;\r
+import jp.sourceforge.jindolf.parser.DecodedContent;\r
+import jp.sourceforge.jindolf.parser.EntityConverter;\r
+import jp.sourceforge.jindolf.parser.HtmlAdapter;\r
+import jp.sourceforge.jindolf.parser.HtmlParseException;\r
+import jp.sourceforge.jindolf.parser.PageType;\r
+import jp.sourceforge.jindolf.parser.SeqRange;\r
+\r
+/**\r
+ * パーサ用ハンドラ。\r
+ */\r
+public class Handler extends HtmlAdapter{\r
+\r
+    private static final Pattern MURDER_PATTERN =\r
+            Pattern.compile("^(.*)\u0020!\u0020今日がお前の命日だ!$");\r
+\r
+    private final EntityConverter converter = new EntityConverter();\r
+\r
+    private VillageData villageData = null;\r
+    private String pageTitle = null;\r
+\r
+    private PeriodData currentPeriod = null;\r
+    private PeriodResource currentResource = null;\r
+\r
+    private TalkData currentTalk = null;\r
+    private EventData currentEvent = null;\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     */\r
+    public Handler(){\r
+        super();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 村情報を初期化。\r
+     * @param villageDataArg 村情報\r
+     */\r
+    public void initVillageData(VillageData villageDataArg){\r
+        this.villageData = villageDataArg;\r
+        this.currentPeriod = null;\r
+        this.pageTitle = null;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * Periodリソース情報を初期化。\r
+     * @param resource リソース情報\r
+     */\r
+    public void initPeriodResource(PeriodResource resource){\r
+        this.currentResource = resource;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void startParse(DecodedContent content) throws HtmlParseException{\r
+        if(this.villageData == null) throw new HtmlParseException();\r
+        this.currentPeriod =\r
+                new PeriodData(this.villageData, this.currentResource);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param titleRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void pageTitle(DecodedContent content, SeqRange titleRange)\r
+            throws HtmlParseException{\r
+        DecodedContent title = this.converter.convert(content, titleRange);\r
+        if(this.pageTitle == null){\r
+            this.pageTitle = title.toString();\r
+        }else{\r
+            if( ! this.pageTitle.contentEquals(title) ){\r
+                throw new HtmlParseException();\r
+            }\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param loginRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void loginName(DecodedContent content, SeqRange loginRange)\r
+            throws HtmlParseException{\r
+        DecodedContent account = this.converter.convert(content, loginRange);\r
+        this.currentPeriod.setLoginName(account);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param type {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void pageType(PageType type) throws HtmlParseException{\r
+        if(type != PageType.PERIOD_PAGE) throw new HtmlParseException();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param villageRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void villageName(DecodedContent content, SeqRange villageRange)\r
+            throws HtmlParseException{\r
+        String vName =\r
+                this.converter.convert(content, villageRange).toString();\r
+        String fullName = this.villageData.getFullName();\r
+        if(fullName.length() <= 0){\r
+            if( ! this.pageTitle.endsWith(vName) ){\r
+                throw new HtmlParseException();\r
+            }\r
+            Pattern ptn = Pattern.compile("^([^0-9]*)([0-9]+)\\s+(\\S+)$");\r
+            Matcher matcher = ptn.matcher(vName);\r
+            if( ! matcher.matches() ) throw new HtmlParseException();\r
+            String prefix = matcher.group(1);\r
+            String vid    = matcher.group(2);\r
+            String vtag   = matcher.group(3);\r
+            LandDef landDef = this.villageData.getLandDef();\r
+            if( ! prefix.equals(landDef.getLandPrefix()) ){\r
+                throw new HtmlParseException();\r
+            }\r
+            if( Integer.parseInt(vid) != this.villageData.getVillageId() ){\r
+                throw new HtmlParseException();\r
+            }\r
+            matcher.reset(vtag);\r
+            if(VillageTag.lookingAtVillageTag(matcher) == null){\r
+                throw new HtmlParseException();\r
+            }\r
+            this.villageData.setFullName(vName);\r
+        }else{\r
+            if( ! vName.equals(fullName) ) throw new HtmlParseException();\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param month {@inheritDoc}\r
+     * @param day {@inheritDoc}\r
+     * @param hour {@inheritDoc}\r
+     * @param minute {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void commitTime(int month, int day, int hour, int minute)\r
+            throws HtmlParseException{\r
+        int commitHour   = this.villageData.getCommitHour();\r
+        int commitMinute = this.villageData.getCommitMinute();\r
+\r
+        if(commitHour < 0){\r
+            this.villageData.setCommitHour(hour);\r
+        }else{\r
+            if(hour != commitHour) throw new HtmlParseException();\r
+        }\r
+\r
+        if(commitMinute < 0){\r
+            this.villageData.setCommitMinute(minute);\r
+        }else{\r
+            if(minute != commitMinute) throw new HtmlParseException();\r
+        }\r
+\r
+        this.currentPeriod.setCommitMonth(month);\r
+        this.currentPeriod.setCommitDay(day);\r
+        this.currentPeriod.setCommitHour(hour);\r
+        this.currentPeriod.setCommitMinute(minute);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param anchorRange {@inheritDoc}\r
+     * @param periodType {@inheritDoc}\r
+     * @param day {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void periodLink(DecodedContent content,\r
+                            SeqRange anchorRange,\r
+                            PeriodType periodType,\r
+                            int day)\r
+            throws HtmlParseException{\r
+        if(anchorRange.isValid()){\r
+            DisclosureType newType;\r
+\r
+            if(periodType == null){\r
+                newType = DisclosureType.COMPLETE;\r
+            }else if(periodType == PeriodType.EPILOGUE\r
+               &&    this.currentResource.getPeriodType()\r
+                  != PeriodType.EPILOGUE){\r
+                newType = DisclosureType.COMPLETE;\r
+            }else if(   periodType != PeriodType.PROLOGUE\r
+               &&    this.currentResource.getPeriodType()\r
+                  == PeriodType.PROLOGUE){\r
+                newType = DisclosureType.COMPLETE;\r
+            }else{\r
+                newType = DisclosureType.UNCOMPLETE;\r
+            }\r
+\r
+            this.currentPeriod.setDisclosureType(newType);\r
+\r
+            return;\r
+        }\r
+\r
+        if(periodType != this.currentResource.getPeriodType()){\r
+            throw new HtmlParseException();\r
+        }\r
+\r
+        if(periodType == PeriodType.PROGRESS\r
+                && day != this.currentResource.getDay()){\r
+            throw new HtmlParseException();\r
+        }\r
+\r
+        this.currentPeriod.setDisclosureType(DisclosureType.HOT);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void startTalk() throws HtmlParseException{\r
+        this.currentTalk = new TalkData(this.currentPeriod);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param avatarRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void talkAvatar(DecodedContent content, SeqRange avatarRange)\r
+            throws HtmlParseException{\r
+        DecodedContent avatarName =\r
+                this.converter.convert(content, avatarRange);\r
+        AvatarData avatar = this.villageData.getAvatarData(avatarName);\r
+        this.currentTalk.setAvatarData(avatar);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param urlRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void talkIconUrl(DecodedContent content, SeqRange urlRange)\r
+            throws HtmlParseException{\r
+        DecodedContent faceIcon = this.converter.convert(content, urlRange);\r
+        this.currentTalk.setFaceIconUri(faceIcon.toString());\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param idRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void talkId(DecodedContent content, SeqRange idRange)\r
+            throws HtmlParseException{\r
+        DecodedContent xname = this.converter.convert(content, idRange);\r
+        this.currentTalk.setXName(xname.toString());\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param hour {@inheritDoc}\r
+     * @param minute {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void talkTime(int hour, int minute) throws HtmlParseException{\r
+        this.currentTalk.setHour(hour);\r
+        this.currentTalk.setMinute(minute);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param type {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void talkType(TalkType type) throws HtmlParseException{\r
+        this.currentTalk.setTalkType(type);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param textRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void talkText(DecodedContent content, SeqRange textRange)\r
+            throws HtmlParseException{\r
+        DecodedContent line = this.converter.convert(content, textRange);\r
+        this.currentTalk.addLine(line);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void talkBreak() throws HtmlParseException{\r
+        this.currentTalk.addBreak();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void endTalk() throws HtmlParseException{\r
+        String faceIcon = this.currentTalk.getFaceIconUri();\r
+        if(this.currentTalk.getTalkType() == TalkType.GRAVE){\r
+            this.villageData.setGraveIconUri(faceIcon);\r
+        }else{\r
+            AvatarData avatar = this.currentTalk.getAvatarData();\r
+            if(avatar.getFaceIconUri() == null){\r
+                avatar.setFaceIconUri(faceIcon);\r
+            }\r
+        }\r
+\r
+        if(   ! this.currentPeriod.hasMurderResult()\r
+           && this.currentTalk.getTalkType() == TalkType.WOLFONLY\r
+           && this.currentTalk.getLineNum() == 1){\r
+            DecodedContent line1st = this.currentTalk.get1stLine();\r
+            Matcher matcher = MURDER_PATTERN.matcher(line1st);\r
+            if(matcher.matches()){\r
+                AvatarData byWhom = this.currentTalk.getAvatarData();\r
+                String avatarName = matcher.group(1);\r
+                AvatarData target =\r
+                        this.villageData.getAvatarData(avatarName);\r
+                String xname = this.currentTalk.getXName();\r
+                int hour = this.currentTalk.getHour();\r
+                int minute = this.currentTalk.getMinute();\r
+                String iconUri = this.currentTalk.getFaceIconUri();\r
+                EventData event = new EventData(this.currentPeriod);\r
+                event.setEventType(SysEventType.ASSAULT);\r
+                event.addLine(line1st);\r
+                event.addAvatarData(byWhom);\r
+                event.addAvatarData(target);\r
+                event.addDecodedContent(new DecodedContent(xname));\r
+                event.addInteger(hour);\r
+                event.addInteger(minute);\r
+                event.addDecodedContent(new DecodedContent(iconUri));\r
+                if(byWhom.getFaceIconUri() == null){\r
+                    byWhom.setFaceIconUri(iconUri);\r
+                }\r
+                this.currentPeriod.addTopicData(event);\r
+                this.currentTalk = null;\r
+                return;\r
+            }\r
+        }\r
+\r
+        this.currentPeriod.addTopicData(this.currentTalk);\r
+        this.currentTalk = null;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param eventFamily {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void startSysEvent(EventFamily eventFamily)\r
+            throws HtmlParseException{\r
+        this.currentEvent = new EventData(this.currentPeriod);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param type {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventType(SysEventType type) throws HtmlParseException{\r
+        this.currentEvent.setEventType(type);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param contentRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventContent(DecodedContent content,\r
+                                  SeqRange contentRange)\r
+            throws HtmlParseException{\r
+        DecodedContent line = this.converter.convert(content, contentRange);\r
+        this.currentEvent.addLine(line);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param anchorRange {@inheritDoc}\r
+     * @param contentRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventContentAnchor(DecodedContent content,\r
+                                      SeqRange anchorRange,\r
+                                      SeqRange contentRange)\r
+            throws HtmlParseException{\r
+        DecodedContent line = this.converter.convert(content, contentRange);\r
+        this.currentEvent.addLine(line);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventContentBreak() throws HtmlParseException{\r
+        this.currentEvent.addBreak();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param entryNo {@inheritDoc}\r
+     * @param avatarRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventOnStage(DecodedContent content,\r
+                                  int entryNo,\r
+                                  SeqRange avatarRange)\r
+            throws HtmlParseException{\r
+        DecodedContent avatarName =\r
+                this.converter.convert(content, avatarRange);\r
+        AvatarData avatar = this.villageData.getAvatarData(avatarName);\r
+        this.currentEvent.addAvatarData(avatar);\r
+        this.currentEvent.addInteger(entryNo);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param role {@inheritDoc}\r
+     * @param num {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventOpenRole(GameRole role, int num)\r
+            throws HtmlParseException{\r
+        this.currentEvent.addGameRole(role);\r
+        this.currentEvent.addInteger(num);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param avatarRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventMurdered(DecodedContent content,\r
+                                   SeqRange avatarRange)\r
+            throws HtmlParseException{\r
+        DecodedContent avatarName =\r
+                this.converter.convert(content, avatarRange);\r
+        AvatarData avatar = this.villageData.getAvatarData(avatarName);\r
+        this.currentEvent.addAvatarData(avatar);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param avatarRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventSurvivor(DecodedContent content,\r
+                                   SeqRange avatarRange)\r
+            throws HtmlParseException{\r
+        DecodedContent avatarName =\r
+                this.converter.convert(content, avatarRange);\r
+        AvatarData avatar = this.villageData.getAvatarData(avatarName);\r
+        this.currentEvent.addAvatarData(avatar);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param voteByRange {@inheritDoc}\r
+     * @param voteToRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventCounting(DecodedContent content,\r
+                                   SeqRange voteByRange,\r
+                                   SeqRange voteToRange)\r
+            throws HtmlParseException{\r
+        AvatarData avatar;\r
+\r
+        if(voteByRange.isValid()){\r
+            DecodedContent voteBy =\r
+                    this.converter.convert(content, voteByRange);\r
+            avatar = this.villageData.getAvatarData(voteBy);\r
+            this.currentEvent.addAvatarData(avatar);\r
+        }\r
+\r
+        DecodedContent voteTo =\r
+                this.converter.convert(content, voteToRange);\r
+        avatar = this.villageData.getAvatarData(voteTo);\r
+        this.currentEvent.addAvatarData(avatar);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param avatarRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventSuddenDeath(DecodedContent content,\r
+                                       SeqRange avatarRange)\r
+            throws HtmlParseException{\r
+        DecodedContent avatarName =\r
+                this.converter.convert(content, avatarRange);\r
+        AvatarData avatar = this.villageData.getAvatarData(avatarName);\r
+        this.currentEvent.addAvatarData(avatar);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param avatarRange {@inheritDoc}\r
+     * @param anchorRange {@inheritDoc}\r
+     * @param loginRange {@inheritDoc}\r
+     * @param isLiving {@inheritDoc}\r
+     * @param role {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventPlayerList(DecodedContent content,\r
+                                      SeqRange avatarRange,\r
+                                      SeqRange anchorRange,\r
+                                      SeqRange loginRange,\r
+                                      boolean isLiving,\r
+                                      GameRole role)\r
+            throws HtmlParseException{\r
+        DecodedContent avatarName =\r
+                this.converter.convert(content, avatarRange);\r
+        AvatarData avatar = this.villageData.getAvatarData(avatarName);\r
+        this.currentEvent.addAvatarData(avatar);\r
+\r
+        DecodedContent uri;\r
+        if(anchorRange.isValid()){\r
+            uri = this.converter.convert(content, anchorRange);\r
+        }else{\r
+            uri = new DecodedContent("");\r
+        }\r
+        Win31j.supplyWin31jChar(uri);\r
+        this.currentEvent.addDecodedContent(uri);\r
+\r
+        DecodedContent account =\r
+                this.converter.convert(content, loginRange);\r
+        Win31j.supplyWin31jChar(account);\r
+        this.currentEvent.addDecodedContent(account);\r
+\r
+        if(isLiving) this.currentEvent.addInteger(1);\r
+        else         this.currentEvent.addInteger(0);\r
+\r
+        this.currentEvent.addGameRole(role);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param hour {@inheritDoc}\r
+     * @param minute {@inheritDoc}\r
+     * @param minLimit {@inheritDoc}\r
+     * @param maxLimit {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventAskEntry(int hour, int minute,\r
+                                 int minLimit, int maxLimit)\r
+            throws HtmlParseException{\r
+        this.currentEvent.addInteger(hour);\r
+        this.currentEvent.addInteger(minute);\r
+        this.currentEvent.addInteger(minLimit);\r
+        this.currentEvent.addInteger(maxLimit);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param hour {@inheritDoc}\r
+     * @param minute {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventAskCommit(int hour, int minute)\r
+            throws HtmlParseException{\r
+        this.currentEvent.addInteger(hour);\r
+        this.currentEvent.addInteger(minute);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param avatarRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventNoComment(DecodedContent content,\r
+                                  SeqRange avatarRange)\r
+            throws HtmlParseException{\r
+        DecodedContent avatarName;\r
+        avatarName = this.converter.convert(content, avatarRange);\r
+        AvatarData avatar = this.villageData.getAvatarData(avatarName);\r
+        this.currentEvent.addAvatarData(avatar);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param winner {@inheritDoc}\r
+     * @param hour {@inheritDoc}\r
+     * @param minute {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventStayEpilogue(Team winner,\r
+                                     int hour, int minute)\r
+            throws HtmlParseException{\r
+        GameRole role;\r
+        switch(winner){\r
+        case VILLAGE: role = GameRole.INNOCENT; break;\r
+        case WOLF:    role = GameRole.WOLF;     break;\r
+        case HAMSTER: role = GameRole.HAMSTER;  break;\r
+        default: throw new IllegalArgumentException();\r
+        }\r
+\r
+        this.currentEvent.addGameRole(role);\r
+        this.currentEvent.addInteger(hour);\r
+        this.currentEvent.addInteger(minute);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param guardByRange {@inheritDoc}\r
+     * @param guardToRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventGuard(DecodedContent content,\r
+                                SeqRange guardByRange,\r
+                                SeqRange guardToRange)\r
+            throws HtmlParseException{\r
+        DecodedContent avatarName;\r
+\r
+        avatarName = this.converter.convert(content, guardByRange);\r
+        AvatarData guardBy = this.villageData.getAvatarData(avatarName);\r
+        this.currentEvent.addAvatarData(guardBy);\r
+\r
+        avatarName = this.converter.convert(content, guardToRange);\r
+        AvatarData guardTo = this.villageData.getAvatarData(avatarName);\r
+        this.currentEvent.addAvatarData(guardTo);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param content {@inheritDoc}\r
+     * @param judgeByRange {@inheritDoc}\r
+     * @param judgeToRange {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void sysEventJudge(DecodedContent content,\r
+                                SeqRange judgeByRange,\r
+                                SeqRange judgeToRange)\r
+            throws HtmlParseException{\r
+        DecodedContent avatarName;\r
+\r
+        avatarName = this.converter.convert(content, judgeByRange);\r
+        AvatarData judgeBy = this.villageData.getAvatarData(avatarName);\r
+        this.currentEvent.addAvatarData(judgeBy);\r
+\r
+        avatarName = this.converter.convert(content, judgeToRange);\r
+        AvatarData judgeTo = this.villageData.getAvatarData(avatarName);\r
+        this.currentEvent.addAvatarData(judgeTo);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void endSysEvent() throws HtmlParseException{\r
+        this.currentPeriod.addTopicData(this.currentEvent);\r
+        this.currentEvent = null;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @throws HtmlParseException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void endParse() throws HtmlParseException{\r
+        this.villageData.addPeriodData(this.currentPeriod);\r
+        this.currentPeriod = null;\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/HttpAccess.java b/src/main/java/jp/sourceforge/jindolf/archiver/HttpAccess.java
new file mode 100644 (file)
index 0000000..475c488
--- /dev/null
@@ -0,0 +1,249 @@
+/*\r
+ * downloader\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: HttpAccess.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.net.URL;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import jp.sourceforge.jindolf.corelib.LandDef;\r
+import jp.sourceforge.jindolf.corelib.LandState;\r
+import jp.sourceforge.jindolf.corelib.PeriodType;\r
+import jp.sourceforge.jindolf.parser.DecodeException;\r
+import jp.sourceforge.jindolf.parser.DecodedContent;\r
+import jp.sourceforge.jindolf.parser.HtmlAdapter;\r
+import jp.sourceforge.jindolf.parser.HtmlParseException;\r
+import jp.sourceforge.jindolf.parser.HtmlParser;\r
+import jp.sourceforge.jindolf.parser.PageType;\r
+import jp.sourceforge.jindolf.parser.SeqRange;\r
+\r
+/**\r
+ * 人狼HTTPサーバ内のリソース情報を展開する。\r
+ */\r
+public final class HttpAccess{\r
+\r
+    /**\r
+     * 日一覧ページ(エピローグの翌日)のURLを得る。\r
+     * @param landDef 国指定\r
+     * @param vid 村番号\r
+     * @return 一覧ページへのURL\r
+     * @throws IOException 入力エラー\r
+     */\r
+    public static URL getPeriodListURL(LandDef landDef, int vid)\r
+            throws IOException{\r
+        StringBuilder urlText = new StringBuilder();\r
+\r
+        urlText.append(landDef.getCgiURI().toASCIIString());\r
+        urlText.append('?').append("vid=").append(vid);\r
+        if(landDef.getLandState() == LandState.ACTIVE){\r
+            urlText.append('&').append("meslog=");\r
+        }\r
+\r
+        URL result = new URL(urlText.toString());\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * 日ページのロード元情報一覧を得る。\r
+     * @param landDef 国指定\r
+     * @param vid 村番号\r
+     * @return ロード元情報一覧\r
+     * @throws DecodeException デコードエラー\r
+     * @throws HtmlParseException パースエラー\r
+     * @throws IOException 入力エラー\r
+     */\r
+    public static List<PeriodResource> loadResourceList(LandDef landDef,\r
+                                                          int vid)\r
+            throws DecodeException,\r
+                   HtmlParseException,\r
+                   IOException {\r
+        URL url = getPeriodListURL(landDef, vid);\r
+\r
+        InputStream istream = url.openStream();\r
+        DecodedContent content = Builder.contentFromStream(istream);\r
+        istream.close();\r
+\r
+        HtmlParser parser = new HtmlParser();\r
+        PeriodListHandler handler = new PeriodListHandler(landDef, vid);\r
+        parser.setBasicHandler(handler);\r
+        parser.setTalkHandler(handler);\r
+        parser.setSysEventHandler(handler);\r
+        parser.parseAutomatic(content);\r
+\r
+        List<PeriodResource> result = handler.getResourceList();\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * 隠しコンストラクタ。\r
+     */\r
+    private HttpAccess(){\r
+        throw new Error();\r
+    }\r
+\r
+    /**\r
+     * 日一覧パース用ハンドラ。\r
+     */\r
+    public static class PeriodListHandler extends HtmlAdapter{\r
+\r
+        private final LandDef landDef;\r
+        private final int vid;\r
+\r
+        private List<PeriodResource> resourceList = null;\r
+\r
+        private int progressDays;\r
+        private boolean hasDone;\r
+\r
+        /**\r
+         * コンストラクタ。\r
+         * @param landDef 国指定\r
+         * @param vid 村番号\r
+         */\r
+        public PeriodListHandler(LandDef landDef, int vid){\r
+            super();\r
+            this.landDef = landDef;\r
+            this.vid = vid;\r
+            return;\r
+        }\r
+\r
+        /**\r
+         * 日ページのURL文字列を生成する。\r
+         * @param type 日種類\r
+         * @param day 日にち\r
+         * @return URL文字列\r
+         */\r
+        public String getURL(PeriodType type, int day){\r
+            String base = this.landDef.getCgiURI().toASCIIString();\r
+            base += "?vid=" + this.vid;\r
+            base += "&meslog=" + this.vid + "_";\r
+            switch(type){\r
+            case PROLOGUE:\r
+                base += "ready_0";\r
+                break;\r
+            case PROGRESS:\r
+                base += "progress_" + (day - 1);\r
+                break;\r
+            case EPILOGUE:\r
+                base += "party_" + (day - 1);\r
+                break;\r
+            default:\r
+                return null;\r
+            }\r
+\r
+            base += "&mes=all";\r
+\r
+            return base;\r
+        }\r
+\r
+        /**\r
+         * PeriodResource一覧を得る。\r
+         * @return PeriodResource一覧\r
+         */\r
+        public List<PeriodResource> getResourceList(){\r
+            return this.resourceList;\r
+        }\r
+\r
+        /**\r
+         * {@inheritDoc}\r
+         * @param content {@inheritDoc}\r
+         * @throws HtmlParseException {@inheritDoc}\r
+         */\r
+        @Override\r
+        public void startParse(DecodedContent content)\r
+                throws HtmlParseException{\r
+            this.resourceList = new LinkedList<PeriodResource>();\r
+            this.progressDays = 0;\r
+            this.hasDone = false;\r
+            return;\r
+        }\r
+\r
+        /**\r
+         * {@inheritDoc}\r
+         * @param type {@inheritDoc}\r
+         * @throws HtmlParseException {@inheritDoc}\r
+         */\r
+        @Override\r
+        public void pageType(PageType type) throws HtmlParseException{\r
+            if(type != PageType.PERIOD_PAGE) throw new HtmlParseException();\r
+            return;\r
+        }\r
+\r
+        /**\r
+         * {@inheritDoc}\r
+         * @param content {@inheritDoc}\r
+         * @param anchorRange {@inheritDoc}\r
+         * @param periodType {@inheritDoc}\r
+         * @param day {@inheritDoc}\r
+         * @throws HtmlParseException {@inheritDoc}\r
+         */\r
+        @Override\r
+        public void periodLink(DecodedContent content,\r
+                                SeqRange anchorRange,\r
+                                PeriodType periodType,\r
+                                int day )\r
+                throws HtmlParseException{\r
+            if(periodType == null){\r
+                this.hasDone = true;\r
+            }else if(periodType == PeriodType.PROGRESS){\r
+                this.progressDays = day;\r
+            }\r
+            return;\r
+        }\r
+\r
+        /**\r
+         * {@inheritDoc}\r
+         * @throws HtmlParseException {@inheritDoc}\r
+         */\r
+        @Override\r
+        public void endParse() throws HtmlParseException{\r
+            if( ! this.hasDone ) throw new HtmlParseException();\r
+\r
+            PeriodResource resource;\r
+\r
+            String prologueURI = getURL(PeriodType.PROLOGUE, 0);\r
+            resource = new PeriodResource(this.landDef,\r
+                                          this.vid,\r
+                                          PeriodType.PROLOGUE,\r
+                                          0,\r
+                                          prologueURI,\r
+                                          0L,\r
+                                          null);\r
+            this.resourceList.add(resource);\r
+\r
+            for(int day = 1; day <= this.progressDays; day++){\r
+                String progressURI = getURL(PeriodType.PROGRESS, day);\r
+                resource = new PeriodResource(this.landDef,\r
+                                              this.vid,\r
+                                              PeriodType.PROGRESS,\r
+                                              day,\r
+                                              progressURI,\r
+                                              0L,\r
+                                              null);\r
+                this.resourceList.add(resource);\r
+            }\r
+\r
+            String epilogueURI = getURL(PeriodType.EPILOGUE,\r
+                                        this.progressDays + 1);\r
+            resource = new PeriodResource(this.landDef,\r
+                                          this.vid,\r
+                                          PeriodType.EPILOGUE,\r
+                                          this.progressDays + 1,\r
+                                          epilogueURI,\r
+                                          0L,\r
+                                          null);\r
+            this.resourceList.add(resource);\r
+\r
+            return;\r
+        }\r
+\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/JinArchiver.java b/src/main/java/jp/sourceforge/jindolf/archiver/JinArchiver.java
new file mode 100644 (file)
index 0000000..e2cb8a7
--- /dev/null
@@ -0,0 +1,323 @@
+/*\r
+ * main entry\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: JinArchiver.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.io.BufferedOutputStream;\r
+import java.io.BufferedWriter;\r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.OutputStream;\r
+import java.io.OutputStreamWriter;\r
+import java.io.Writer;\r
+import java.text.MessageFormat;\r
+import java.util.List;\r
+import javax.xml.parsers.DocumentBuilder;\r
+import javax.xml.parsers.DocumentBuilderFactory;\r
+import jp.sourceforge.jindolf.corelib.LandDef;\r
+import jp.sourceforge.jindolf.parser.DecodeException;\r
+import jp.sourceforge.jindolf.parser.HtmlParseException;\r
+\r
+/**\r
+ * メインエントリ。\r
+ */\r
+public final class JinArchiver{\r
+    /** Generator. */\r
+    public static final String GENERATOR = "JinArchiver 1.401.2";\r
+    private static final List<LandDef> LANDDEF_LIST;\r
+\r
+    static{\r
+        DocumentBuilderFactory factory =\r
+                DocumentBuilderFactory.newInstance();\r
+        try{\r
+            DocumentBuilder builder = factory.newDocumentBuilder();\r
+            LANDDEF_LIST = LandDef.buildLandDefList(builder);\r
+        }catch(RuntimeException e){\r
+            throw e;\r
+        }catch(Exception e){\r
+            throw new ExceptionInInitializerError(e);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * 国IDから国情報を得る。\r
+     * @param landId 国ID\r
+     * @return 国情報\r
+     */\r
+    public static LandDef getLandDef(String landId){\r
+        for(LandDef landDef : LANDDEF_LIST){\r
+            if(landDef.getLandId().equals(landId)) return landDef;\r
+        }\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * ヘルプメッセージ出力。\r
+     */\r
+    private static void helpMessage(){\r
+        System.err.println(\r
+                "\n" + GENERATOR + " 人狼BBS アーカイブ作成ツール\n\n"\r
+                +"-h, -help, -?\n\tヘルプメッセージ\n"\r
+                +"-land 国識別子\n"\r
+                +"-vid 村番号\n"\r
+                +"-outdir 出力ディレクトリ\n"\r
+                +"-stdout\n\t標準出力へ出力\n\n"\r
+                +"※ -outdir と -stdout は排他指定\n"\r
+                );\r
+        StringBuilder landList = new StringBuilder();\r
+        for(LandDef landDef : LANDDEF_LIST){\r
+            landList.append(landDef.getLandId()).append(' ');\r
+        }\r
+        System.err.print("利用可能な国識別子は ");\r
+        System.err.println(landList);\r
+        System.err.println();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * オプション文字列を解析する。\r
+     * @param args オプション文字列\r
+     */\r
+    private static void parseOption(String[] args){\r
+        if(args.length <= 0){\r
+            helpMessage();\r
+            System.exit(0);\r
+            return;\r
+        }\r
+\r
+        LandDef landDef = null;\r
+        int vid = -1;\r
+        String outdir = null;\r
+        boolean stdout = false;\r
+\r
+        for(int pos = 0; pos < args.length; pos++){\r
+            String arg = args[pos];\r
+\r
+            if( ! arg.startsWith("-") ){\r
+                System.err.println("不正なオプションです。 " + arg);\r
+                System.exit(1);\r
+                return;\r
+            }\r
+\r
+            if(arg.equals("-h") || arg.equals("-help") || arg.equals("-?")){\r
+                helpMessage();\r
+                System.exit(0);\r
+                return;\r
+            }\r
+\r
+            if(arg.equals("-stdout")){\r
+                stdout = true;\r
+                outdir = null;\r
+                continue;\r
+            }\r
+\r
+            if(++pos >= args.length){\r
+                System.err.println(\r
+                        "オプション " + arg + " に引数がありません。");\r
+                System.exit(1);\r
+                return;\r
+            }\r
+\r
+            String val = args[pos];\r
+            if(arg.equals("-land")){\r
+                landDef = getLandDef(val);\r
+                if(landDef == null){\r
+                    System.err.println("不正な国識別子です。 " + val);\r
+                    System.exit(1);\r
+                    return;\r
+                }\r
+            }else if(arg.equals("-vid")){\r
+                vid = Integer.parseInt(val);\r
+                if(vid < 0){\r
+                    System.err.println("不正な村番号です。 " + vid);\r
+                    System.exit(1);\r
+                    return;\r
+                }\r
+            }else if(arg.equals("-outdir")){\r
+                outdir = val;\r
+                stdout = false;\r
+            }else{\r
+                System.err.println("不正なオプションです。 " + arg);\r
+                System.exit(1);\r
+                return;\r
+            }\r
+        }\r
+\r
+        if(landDef == null){\r
+            System.err.println(\r
+                    "-land オプションで国識別子を指定してください。");\r
+            System.exit(1);\r
+            return;\r
+        }\r
+\r
+        if(vid < 0){\r
+            System.err.println(\r
+                    "-vid オプションで村番号を指定してください。");\r
+            System.exit(1);\r
+            return;\r
+        }\r
+\r
+        if(   (outdir == null && stdout == false)\r
+           || (outdir != null && stdout == true)  ){\r
+            System.err.println(\r
+                    "-outdir か -stdout のどちらか一方を指定してください。");\r
+            System.exit(1);\r
+            return;\r
+        }\r
+\r
+        Writer writer;\r
+        if(outdir != null){\r
+            writer = getFileWriter(outdir, landDef, vid);\r
+        }else{\r
+            writer = getStdOutWriter();\r
+        }\r
+\r
+        writer = ValidateTask.wrapValidator(writer);\r
+\r
+        try{\r
+            dump(writer, landDef, vid);\r
+        }catch(RuntimeException e){\r
+            throw e;\r
+        }catch(Exception e){\r
+            e.printStackTrace(System.err);\r
+            System.err.println("処理を続行できません。");\r
+            System.exit(1);\r
+            return;\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 主処理。人狼サーバからXHTMLを読み込み。XMLで出力。\r
+     * @param writer 出力先\r
+     * @param landDef 国情報\r
+     * @param vid 村番号\r
+     * @throws IOException 入出力エラー\r
+     * @throws DecodeException デコードエラー\r
+     * @throws HtmlParseException パースエラー\r
+     */\r
+    public static void dump(Writer writer, LandDef landDef, int vid)\r
+            throws IOException, DecodeException, HtmlParseException{\r
+        List<PeriodResource> resourceList =\r
+                HttpAccess.loadResourceList(landDef, vid);\r
+        VillageData village = new VillageData(resourceList);\r
+\r
+        Builder.fillVillageData(village);\r
+        XmlUtils.dumpVillageData(writer, village);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 標準出力への出力先を得る。\r
+     * @return 出力先\r
+     */\r
+    public static Writer getStdOutWriter(){\r
+        OutputStream ostream;\r
+        ostream = new BufferedOutputStream(System.out);\r
+        Writer writer;\r
+        try{\r
+            writer = new OutputStreamWriter(ostream, "UTF-8");\r
+            writer = new BufferedWriter(writer, 4 * 1024);\r
+        }catch(IOException e){\r
+            System.err.println(\r
+                    "標準出力に書き込めません。");\r
+            System.exit(1);\r
+            return null;\r
+        }\r
+        return writer;\r
+    }\r
+\r
+    /**\r
+     * ローカルファイルへの出力先を得る。\r
+     * @param outdir 出力ディレクトリ\r
+     * @param landDef 国情報\r
+     * @param vid 村番号\r
+     * @return 出力先\r
+     */\r
+    public static Writer getFileWriter(String outdir,\r
+                                         LandDef landDef,\r
+                                         int vid ){\r
+            File outFile = new File(outdir);\r
+            if( ! outFile.exists() ){\r
+                System.err.println(\r
+                        outdir + " が存在しません。");\r
+                System.exit(1);\r
+                return null;\r
+            }\r
+            if( ! outFile.isDirectory() ){\r
+                System.err.println(\r
+                        outdir + " はディレクトリではありません。");\r
+                System.exit(1);\r
+                return null;\r
+            }\r
+            if( ! outFile.canWrite() ){\r
+                System.err.println(\r
+                        outdir + " に書き込めません。");\r
+                System.exit(1);\r
+                return null;\r
+            }\r
+            String fname = MessageFormat.format(\r
+                "jin_{0}_{1,number,#00000}.xml", landDef.getLandId(), vid);\r
+            File xmlFile = new File(outFile, fname);\r
+            boolean created;\r
+            try{\r
+                created = xmlFile.createNewFile();\r
+            }catch(IOException e){\r
+                System.err.println(\r
+                        xmlFile.getName() + " が作成できません。");\r
+                System.exit(1);\r
+                return null;\r
+            }\r
+            if( ! created ){\r
+                System.err.println(\r
+                        fname + " が既に" + outdir + "に存在します。");\r
+                System.exit(1);\r
+                return null;\r
+            }\r
+            /* JRE 1.6 only\r
+            xmlFile.setReadable(true);\r
+            xmlFile.setWritable(true);\r
+            xmlFile.setExecutable(false, false);\r
+            */\r
+            Writer writer;\r
+            try{\r
+                OutputStream ostream;\r
+                ostream = new FileOutputStream(xmlFile);\r
+                ostream = new BufferedOutputStream(ostream, 4 * 1024);\r
+                writer = new OutputStreamWriter(ostream, "UTF-8");\r
+                writer = new BufferedWriter(writer, 4 * 1024);\r
+            }catch(IOException e){\r
+                System.err.println(\r
+                        xmlFile.getName() + " に書き込めません。");\r
+                System.exit(1);\r
+                return null;\r
+            }\r
+\r
+            return writer;\r
+    }\r
+\r
+    /**\r
+     * スタートアップエントリ。\r
+     * @param args 引数\r
+     */\r
+    public static void main(String[] args){\r
+        parseOption(args);\r
+        System.exit(0);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 隠しコンストラクタ。\r
+     */\r
+    private JinArchiver(){\r
+        throw new Error();\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/MultiPlexer.java b/src/main/java/jp/sourceforge/jindolf/archiver/MultiPlexer.java
new file mode 100644 (file)
index 0000000..4f8ef68
--- /dev/null
@@ -0,0 +1,201 @@
+/*\r
+ * Multiplex Writer\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: MultiPlexer.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.io.IOException;\r
+import java.io.Writer;\r
+import java.util.Collections;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+\r
+/**\r
+ * Writerのマルチプレクサ。\r
+ */\r
+public class MultiPlexer extends Writer{\r
+\r
+    private final List<Writer> childs = new LinkedList<Writer>();\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     */\r
+    public MultiPlexer(){\r
+        this(null);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param writer 初期Writer\r
+     */\r
+    public MultiPlexer(Writer writer){\r
+        super();\r
+\r
+        if(writer != null){\r
+            this.childs.add(writer);\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * Writerを追加する。\r
+     * @param writer 追加するWriter。nullなら無視。\r
+     */\r
+    public void addWriter(Writer writer){\r
+        if(writer == null) return;\r
+        this.childs.add(writer);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 出力するWriterの一覧を得る。\r
+     * @return Writer一覧。\r
+     */\r
+    public List<Writer> getWriterList(){\r
+        return Collections.unmodifiableList(this.childs);\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @throws IOException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void close() throws IOException{\r
+        for(Writer writer : this.childs){\r
+            writer.close();\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @throws IOException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void flush() throws IOException{\r
+        for(Writer writer : this.childs){\r
+            writer.flush();\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param cbuf {@inheritDoc}\r
+     * @param off {@inheritDoc}\r
+     * @param len {@inheritDoc}\r
+     * @throws IOException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void write(char[] cbuf, int off, int len) throws IOException{\r
+        for(Writer writer : this.childs){\r
+            writer.write(cbuf, off, len);\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param csq {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     * @throws IOException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public Writer append(CharSequence csq) throws IOException{\r
+        for(Writer writer : this.childs){\r
+            writer.append(csq);\r
+        }\r
+        return this;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param csq {@inheritDoc}\r
+     * @param start {@inheritDoc}\r
+     * @param end {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     * @throws IOException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public Writer append(CharSequence csq, int start, int end)\r
+            throws IOException{\r
+        for(Writer writer : this.childs){\r
+            writer.append(csq, start, end);\r
+        }\r
+        return this;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param c {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     * @throws IOException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public Writer append(char c) throws IOException{\r
+        for(Writer writer : this.childs){\r
+            writer.append(c);\r
+        }\r
+        return this;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param c {@inheritDoc}\r
+     * @throws IOException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void write(int c) throws IOException{\r
+        for(Writer writer : this.childs){\r
+            writer.write(c);\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param cbuf {@inheritDoc}\r
+     * @throws IOException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void write(char[] cbuf) throws IOException{\r
+        for(Writer writer : this.childs){\r
+            writer.write(cbuf);\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param str {@inheritDoc}\r
+     * @throws IOException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void write(String str) throws IOException{\r
+        for(Writer writer : this.childs){\r
+            writer.write(str);\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param str {@inheritDoc}\r
+     * @param off {@inheritDoc}\r
+     * @param len {@inheritDoc}\r
+     * @throws IOException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void write(String str, int off, int len) throws IOException{\r
+        for(Writer writer : this.childs){\r
+            writer.write(str, off, len);\r
+        }\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/PeriodData.java b/src/main/java/jp/sourceforge/jindolf/archiver/PeriodData.java
new file mode 100644 (file)
index 0000000..87ad00b
--- /dev/null
@@ -0,0 +1,257 @@
+/*\r
+ * period model\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: PeriodData.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.io.IOException;\r
+import java.io.Writer;\r
+import java.net.URI;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import jp.sourceforge.jindolf.corelib.DisclosureType;\r
+import jp.sourceforge.jindolf.corelib.SysEventType;\r
+import jp.sourceforge.jindolf.parser.DecodedContent;\r
+\r
+/**\r
+ * Periodモデル。\r
+ */\r
+public class PeriodData{\r
+\r
+    private final VillageData parent;\r
+    private final PeriodResource resource;\r
+    private DecodedContent loginName = new DecodedContent("");\r
+    private int commitMonth;\r
+    private int commitDay;\r
+    private int commitHour;\r
+    private int commitMinute;\r
+    private DisclosureType disclosureType = DisclosureType.HOT;\r
+    private boolean hasMurderResult = false;\r
+\r
+    private final List<TopicData> topicList = new LinkedList<TopicData>();\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param parent 所属村\r
+     * @param resource ロード元情報\r
+     */\r
+    public PeriodData(VillageData parent, PeriodResource resource){\r
+        super();\r
+        this.parent = parent;\r
+        this.resource = resource;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * ロード時のログイン名を取得する。\r
+     * @return ログイン名\r
+     */\r
+    public DecodedContent getLoginName(){\r
+        return this.loginName;\r
+    }\r
+\r
+    /**\r
+     * ロード時のログイン名を設定する。\r
+     * @param loginName ログイン名\r
+     */\r
+    public void setLoginName(DecodedContent loginName){\r
+        this.loginName = loginName;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コミット月を取得する。\r
+     * @return コミット月\r
+     */\r
+    public int getCommitMonth(){\r
+        return this.commitMonth;\r
+    }\r
+\r
+    /**\r
+     * コミット月を設定する。\r
+     * @param commitMonth コミット月\r
+     */\r
+    public void setCommitMonth(int commitMonth){\r
+        this.commitMonth = commitMonth;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コミット日を取得する。\r
+     * @return コミット日\r
+     */\r
+    public int getCommitDay(){\r
+        return this.commitDay;\r
+    }\r
+\r
+    /**\r
+     * コミット日を設定する。\r
+     * @param commitDay コミット日\r
+     */\r
+    public void setCommitDay(int commitDay){\r
+        this.commitDay = commitDay;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コミット時を取得する。\r
+     * @return コミット時\r
+     */\r
+    public int getCommitHour(){\r
+        return this.commitHour;\r
+    }\r
+\r
+    /**\r
+     * コミット時を設定する。\r
+     * @param commitHour コミット時\r
+     */\r
+    public void setCommitHour(int commitHour){\r
+        this.commitHour = commitHour;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コミット分を取得する。\r
+     * @return コミット分\r
+     */\r
+    public int getCommitMinute(){\r
+        return this.commitMinute;\r
+    }\r
+\r
+    /**\r
+     * コミット分を設定する。\r
+     * @param commitMinute コミット分\r
+     */\r
+    public void setCommitMinute(int commitMinute){\r
+        this.commitMinute = commitMinute;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 開示状況を取得する。\r
+     * @return 開示状況\r
+     */\r
+    public DisclosureType getDisclosureType(){\r
+        return this.disclosureType;\r
+    }\r
+\r
+    /**\r
+     * 開示状況を設定する。\r
+     * @param type 開示状況\r
+     */\r
+    public void setDisclosureType(DisclosureType type){\r
+        this.disclosureType = type;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 襲撃結果イベントが既に格納されているか確認する。\r
+     * @return 襲撃結果があればtrue\r
+     */\r
+    public boolean hasMurderResult(){\r
+        return this.hasMurderResult;\r
+    }\r
+\r
+    /**\r
+     * TopicDataを追加する。\r
+     * 襲撃結果の有無も判定される。\r
+     * @param topicData TopiData\r
+     */\r
+    public void addTopicData(TopicData topicData){\r
+        this.topicList.add(topicData);\r
+\r
+        if(topicData instanceof EventData){\r
+            EventData event = (EventData) topicData;\r
+            SysEventType type = event.getEventType();\r
+            if(   type == SysEventType.MURDERED\r
+               || type == SysEventType.NOMURDER){\r
+                this.hasMurderResult = true;\r
+            }\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * period要素をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpXml(Writer writer) throws IOException{\r
+        writer.append("<period\n");\r
+\r
+        String ptype;\r
+        switch(this.resource.getPeriodType()){\r
+        case PROLOGUE:\r
+            ptype = "prologue";\r
+            break;\r
+        case PROGRESS:\r
+            ptype = "progress";\r
+            break;\r
+        case EPILOGUE:\r
+            ptype = "epilogue";\r
+            break;\r
+        default:\r
+            throw new IllegalArgumentException();\r
+        }\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "type", ptype);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer,\r
+                "day", Integer.toString(this.resource.getDay()));\r
+        writer.append('\n');\r
+\r
+        if(this.disclosureType != DisclosureType.COMPLETE){\r
+            XmlUtils.indent(writer, 1);\r
+            XmlUtils.attrOut(writer,\r
+                    "disclosure", this.disclosureType.getXmlName());\r
+            writer.append('\n');\r
+        }\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.dateAttrOut(writer, "nextCommitDay",\r
+                             this.commitMonth, this.commitDay);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.timeAttrOut(writer,\r
+                             "commitTime",\r
+                             this.commitHour, this.commitMinute);\r
+        writer.append('\n');\r
+\r
+        URI baseUri   = URI.create(this.parent.getBaseUri());\r
+        URI periodUri = URI.create(this.resource.getOrigUrlText());\r
+        URI relativeUri = baseUri.relativize(periodUri);\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "sourceURI", relativeUri.toString());\r
+        writer.append('\n');\r
+\r
+        long downTimeMs = this.resource.getDownTimeMs();\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.dateTimeAttr(writer, "loadedTime", downTimeMs);\r
+        writer.append('\n');\r
+\r
+        if(this.loginName.length() > 0){\r
+            XmlUtils.indent(writer, 1);\r
+            XmlUtils.attrOut(writer, "loadedBy", this.loginName.toString());\r
+            writer.append('\n');\r
+        }\r
+\r
+        writer.append(">\n\n");\r
+\r
+        for(TopicData topic : this.topicList){\r
+            topic.dumpXml(writer);\r
+            writer.append('\n');\r
+            writer.flush();\r
+        }\r
+\r
+        writer.append("</period>\n");\r
+\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/PeriodResource.java b/src/main/java/jp/sourceforge/jindolf/archiver/PeriodResource.java
new file mode 100644 (file)
index 0000000..f7de117
--- /dev/null
@@ -0,0 +1,131 @@
+/*\r
+ * Period resource\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: PeriodResource.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.net.URL;\r
+import jp.sourceforge.jindolf.corelib.LandDef;\r
+import jp.sourceforge.jindolf.corelib.PeriodType;\r
+\r
+/**\r
+ * Periodのロード元情報。\r
+ */\r
+public class PeriodResource{\r
+\r
+    private final LandDef landDef;\r
+    private final int villageId;\r
+    private final PeriodType periodType;\r
+    private final int day;\r
+    private final String origUrlText;\r
+    private long downTimeMs;\r
+    private URL resourceUrl;\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param landDef 国情報\r
+     * @param villageId 村ID\r
+     * @param periodType Period種別\r
+     * @param day 日付\r
+     * @param origUrlText ロード元URI文字列\r
+     * @param downTimeMs ロード時刻\r
+     * @param resourceUrl ロード元URL\r
+     */\r
+    public PeriodResource(LandDef landDef,\r
+                            int villageId,\r
+                            PeriodType periodType,\r
+                            int day,\r
+                            String origUrlText,\r
+                            long downTimeMs,\r
+                            URL resourceUrl ) {\r
+        super();\r
+\r
+        this.landDef = landDef;\r
+        this.villageId = villageId;\r
+        this.periodType = periodType;\r
+        this.day = day;\r
+        this.origUrlText = origUrlText;\r
+        this.downTimeMs = downTimeMs;\r
+        this.resourceUrl = resourceUrl;\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 国情報を取得する。\r
+     * @return 国情報\r
+     */\r
+    public LandDef getLandDef(){\r
+        return landDef;\r
+    }\r
+\r
+    /**\r
+     * 村番号を取得する。\r
+     * @return 村番号\r
+     */\r
+    public int getVillageId(){\r
+        return villageId;\r
+    }\r
+\r
+    /**\r
+     * Periodの種別を取得する。\r
+     * @return Period種別\r
+     */\r
+    public PeriodType getPeriodType(){\r
+        return periodType;\r
+    }\r
+\r
+    /**\r
+     * 日付を取得する。\r
+     * @return 日付\r
+     */\r
+    public int getDay(){\r
+        return day;\r
+    }\r
+\r
+    /**\r
+     * オリジナルのダウンロード元URL文字列を取得する。\r
+     * @return ダウンロード元URL文字列\r
+     */\r
+    public String getOrigUrlText(){\r
+        return origUrlText;\r
+    }\r
+\r
+    /**\r
+     * オリジナルのダウンロード時刻を取得する。\r
+     * @return ダウンロード時刻。エポック秒(ms)\r
+     */\r
+    public long getDownTimeMs(){\r
+        return this.downTimeMs;\r
+    }\r
+\r
+    /**\r
+     * オリジナルのダウンロード時刻を設定する。\r
+     * @param downTimeMs ダウンロード時刻。エポック秒(ms)\r
+     */\r
+    public void setDownTimeMs(long downTimeMs){\r
+        this.downTimeMs = downTimeMs;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * XHTML格納先URLを取得する。\r
+     * @return 格納先URL\r
+     */\r
+    public URL getResourceUrl(){\r
+        return resourceUrl;\r
+    }\r
+\r
+    /**\r
+     * XHTML格納先URLを設定する。\r
+     * @param resourceUrl 格納先URL\r
+     */\r
+    public void setResourceUrl(URL resourceUrl){\r
+        this.resourceUrl = resourceUrl;\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/TalkData.java b/src/main/java/jp/sourceforge/jindolf/archiver/TalkData.java
new file mode 100644 (file)
index 0000000..12be2db
--- /dev/null
@@ -0,0 +1,195 @@
+/*\r
+ * talk dialog\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: TalkData.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.io.IOException;\r
+import java.io.Writer;\r
+import jp.sourceforge.jindolf.corelib.TalkType;\r
+\r
+/**\r
+ * 発言モデル。\r
+ */\r
+public class TalkData extends TopicData{\r
+\r
+    private final PeriodData parent;\r
+    private TalkType talkType = null;\r
+    private AvatarData avatarData = null;\r
+    private String xName;\r
+    private String faceIconUri = null;\r
+    private int hour;\r
+    private int minute;\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param parent 所属Period\r
+     */\r
+    public TalkData(PeriodData parent){\r
+        super();\r
+        this.parent = parent;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 発言種別を取得する。\r
+     * @return 発言種別\r
+     */\r
+    public TalkType getTalkType(){\r
+        return this.talkType;\r
+    }\r
+\r
+    /**\r
+     * 発言種別を設定する。\r
+     * @param talkType 発言種別\r
+     */\r
+    public void setTalkType(TalkType talkType){\r
+        this.talkType = talkType;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 発言したAvatarを取得する。\r
+     * @return 発言Avatar\r
+     */\r
+    public AvatarData getAvatarData(){\r
+        return this.avatarData;\r
+    }\r
+\r
+    /**\r
+     * 発言したAvatarを設定する。\r
+     * @param avatarData 発言Avatar\r
+     */\r
+    public void setAvatarData(AvatarData avatarData){\r
+        this.avatarData = avatarData;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 元発言のname属性値を取得する。\r
+     * @return name属性値\r
+     */\r
+    public String getXName(){\r
+        return this.xName;\r
+    }\r
+\r
+    /**\r
+     * 元発言のname属性値を設定する。\r
+     * @param xName name属性値\r
+     */\r
+    public void setXName(String xName){\r
+        this.xName = xName;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 顔アイコン画像URI文字列を取得する。\r
+     * @return 顔アイコン画像URI文字列\r
+     */\r
+    public String getFaceIconUri(){\r
+        return this.faceIconUri;\r
+    }\r
+\r
+    /**\r
+     * 顔アイコン画像URI文字列を設定する。\r
+     * @param faceIconUri 顔アイコン画像URI文字列\r
+     */\r
+    public void setFaceIconUri(String faceIconUri){\r
+        this.faceIconUri = faceIconUri;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 発言時を取得する。\r
+     * @return 発言時\r
+     */\r
+    public int getHour(){\r
+        return this.hour;\r
+    }\r
+\r
+    /**\r
+     * 発言時を設定する。\r
+     * @param hour 発言時\r
+     */\r
+    public void setHour(int hour){\r
+        this.hour = hour;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 発言分を取得する。\r
+     * @return 発言分\r
+     */\r
+    public int getMinute(){\r
+        return this.minute;\r
+    }\r
+\r
+    /**\r
+     * 発言分を設定する。\r
+     * @param minute 発言分\r
+     */\r
+    public void setMinute(int minute){\r
+        this.minute = minute;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * talk要素をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    @Override\r
+    public void dumpXml(Writer writer) throws IOException{\r
+        writer.append("<talk\n");\r
+\r
+        String typeStr;\r
+        switch(this.talkType){\r
+        case PUBLIC:\r
+            typeStr = "public";\r
+            break;\r
+        case WOLFONLY:\r
+            typeStr = "wolf";\r
+            break;\r
+        case PRIVATE:\r
+            typeStr = "private";\r
+            break;\r
+        case GRAVE:\r
+            typeStr = "grave";\r
+            break;\r
+        default:\r
+            throw new IllegalArgumentException();\r
+        }\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "type", typeStr);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "avatarId", this.avatarData.getAvatarId());\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "xname", this.xName);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.timeAttrOut(writer, "time", this.hour, this.minute);\r
+        writer.append('\n');\r
+\r
+        if(   this.talkType != TalkType.GRAVE\r
+           && ! this.faceIconUri.equals(this.avatarData.getFaceIconUri()) ){\r
+            XmlUtils.indent(writer, 1);\r
+            XmlUtils.attrOut(writer, "faceIconURI", this.faceIconUri);\r
+            writer.append('\n');\r
+        }\r
+\r
+        writer.append(">\n");\r
+\r
+        dumpLines(writer);\r
+\r
+        writer.append("</talk>\n");\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/TopicData.java b/src/main/java/jp/sourceforge/jindolf/archiver/TopicData.java
new file mode 100644 (file)
index 0000000..316c503
--- /dev/null
@@ -0,0 +1,110 @@
+/*\r
+ * topic data\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: TopicData.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.io.IOException;\r
+import java.io.Writer;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import jp.sourceforge.jindolf.parser.DecodedContent;\r
+\r
+/**\r
+ * テキスト行の集合。\r
+ */\r
+public abstract class TopicData{\r
+\r
+    private static final DecodedContent BREAK = new DecodedContent("\n");\r
+\r
+    private final List<DecodedContent> lineList =\r
+            new LinkedList<DecodedContent>();\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     */\r
+    protected TopicData(){\r
+        super();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 行を追加する。\r
+     * @param content 行を構成する文字列\r
+     */\r
+    public void addLine(DecodedContent content){\r
+        this.lineList.add(content);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 行ブレークを追加する。\r
+     */\r
+    public void addBreak(){\r
+        this.lineList.add(BREAK);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 行数を取得する。\r
+     * @return 行数\r
+     */\r
+    public int getLineNum(){\r
+        return this.lineList.size();\r
+    }\r
+\r
+    /**\r
+     * 最初の行を取得する。\r
+     * @return 最初の行\r
+     */\r
+    public DecodedContent get1stLine(){\r
+        return this.lineList.get(0);\r
+    }\r
+\r
+    /**\r
+     * 1行li要素をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpLines(Writer writer) throws IOException{\r
+        DecodedContent lastLine = null;\r
+        DecodedContent lastContent = null;\r
+\r
+        for(DecodedContent content : this.lineList){\r
+            lastContent = content;\r
+            if(content == BREAK){\r
+                if(lastLine != null){\r
+                    writer.append("</li>\n");\r
+                    lastLine = null;\r
+                }else{\r
+                    writer.append("<li/>\n");\r
+                }\r
+            }else{\r
+                if(lastLine == null){\r
+                    writer.append("<li>");\r
+                }\r
+                XmlUtils.dumpDecodedContent(writer, content);\r
+                lastLine = content;\r
+            }\r
+        }\r
+\r
+        if(lastLine != null){\r
+            writer.append("</li>\n");\r
+        }else if(lastContent == BREAK){\r
+            writer.append("<li/>\n");\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 要素をXML出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public abstract void dumpXml(Writer writer) throws IOException;\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/ValidateTask.java b/src/main/java/jp/sourceforge/jindolf/archiver/ValidateTask.java
new file mode 100644 (file)
index 0000000..b7b50ea
--- /dev/null
@@ -0,0 +1,99 @@
+/*\r
+ * XML-validation task\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: ValidateTask.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.io.IOException;\r
+import java.io.PipedReader;\r
+import java.io.PipedWriter;\r
+import java.io.Reader;\r
+import java.io.Writer;\r
+import javax.xml.XMLConstants;\r
+import javax.xml.transform.Source;\r
+import javax.xml.transform.stream.StreamSource;\r
+import javax.xml.validation.Schema;\r
+import javax.xml.validation.SchemaFactory;\r
+import javax.xml.validation.Validator;\r
+import org.xml.sax.SAXException;\r
+\r
+/**\r
+ * XML検証タスク。\r
+ */\r
+public class ValidateTask implements Runnable{\r
+\r
+    private static final SchemaFactory FACTORY =\r
+            SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);\r
+\r
+    private final Validator validator;\r
+    private final Source source;\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param reader 文字入力\r
+     * @throws SAXException 内部エラー\r
+     */\r
+    protected ValidateTask(Reader reader) throws SAXException{\r
+        super();\r
+        Schema schema = FACTORY.newSchema();\r
+        this.validator = schema.newValidator();\r
+        this.source = new StreamSource(reader);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 検証タスク。\r
+     * {@inheritDoc}\r
+     */\r
+    public void run(){\r
+        try{\r
+            this.validator.validate(this.source);\r
+        }catch(Throwable e){\r
+            e.printStackTrace(System.err);\r
+            System.err.println("XML検証に失敗しました。");\r
+            System.exit(1);\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 文字出力を横取りしバックグラウンドで検証を行うWriterを生成する。\r
+     * @param writer 元出力\r
+     * @return 新しい出力\r
+     */\r
+    public static Writer wrapValidator(Writer writer){\r
+        PipedReader reader = new PipedReader();\r
+        Writer pipeWriter;\r
+        try{\r
+            pipeWriter = new PipedWriter(reader);\r
+        }catch(IOException e){\r
+            e.printStackTrace(System.err);\r
+            System.err.println("処理を続行できません。");\r
+            System.exit(1);\r
+            return null;\r
+        }\r
+\r
+        MultiPlexer mtplx = new MultiPlexer();\r
+        mtplx.addWriter(writer);\r
+        mtplx.addWriter(pipeWriter);\r
+\r
+        Runnable task;\r
+        try{\r
+            task = new ValidateTask(reader);\r
+        }catch(SAXException e){\r
+            e.printStackTrace(System.err);\r
+            System.err.println("処理を続行できません。");\r
+            System.exit(1);\r
+            return null;\r
+        }\r
+        Thread th = new Thread(task);\r
+        th.setDaemon(false);\r
+        th.start();\r
+\r
+        return mtplx;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/VillageData.java b/src/main/java/jp/sourceforge/jindolf/archiver/VillageData.java
new file mode 100644 (file)
index 0000000..5d446df
--- /dev/null
@@ -0,0 +1,482 @@
+/*\r
+ * village data\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: VillageData.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.io.IOException;\r
+import java.io.Writer;\r
+import java.util.Collections;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import jp.sourceforge.jindolf.corelib.DisclosureType;\r
+import jp.sourceforge.jindolf.corelib.LandDef;\r
+import jp.sourceforge.jindolf.corelib.PeriodType;\r
+import jp.sourceforge.jindolf.corelib.PreDefAvatar;\r
+\r
+/**\r
+ * 村のデータモデル。\r
+ * villageタグに相当。\r
+ */\r
+public class VillageData{\r
+\r
+    /**\r
+     * PeriodResourceの組が正当かチェックする。\r
+     * <ul>\r
+     * <li>全て同じ国に属していなければならない\r
+     * <li>全て同じ村に属していなければならない\r
+     * <li>日付は0から始まる連続した数値でなければならない\r
+     * <li>プロローグで始まらなければならない\r
+     * <li>エピローグで終わらなければならない\r
+     * <li>進行日はプロローグとエピローグに挟まれていなければならない\r
+     * </ul>\r
+     * @param list PeriodResource並び\r
+     * @throws IllegalArgumentException 引数が正当でない\r
+     */\r
+    public static void validatePeriodResource(List<PeriodResource> list)\r
+            throws IllegalArgumentException{\r
+        LandDef landDef = null;\r
+        int villageId = -1;\r
+        int lastDay = -1;\r
+        PeriodType periodType = null;\r
+\r
+        for(PeriodResource resource : list){\r
+            if(landDef == null){\r
+                landDef = resource.getLandDef();\r
+            }else if(resource.getLandDef() != landDef){\r
+                throw new IllegalArgumentException();\r
+            }\r
+\r
+            if(villageId < 0){\r
+                villageId = resource.getVillageId();\r
+            }else if(resource.getVillageId() != villageId){\r
+                throw new IllegalArgumentException();\r
+            }\r
+\r
+            if(lastDay < 0){\r
+                lastDay = resource.getDay();\r
+                if(lastDay != 0) throw new IllegalArgumentException();\r
+            }else{\r
+                if(resource.getDay() != lastDay + 1){\r
+                    throw new IllegalArgumentException();\r
+                }\r
+                lastDay = resource.getDay();\r
+            }\r
+\r
+            if(periodType == null){\r
+                periodType = resource.getPeriodType();\r
+                if(periodType != PeriodType.PROLOGUE){\r
+                    throw new IllegalArgumentException();\r
+                }\r
+                if(lastDay != 0) throw new IllegalArgumentException();\r
+            }else if(periodType == PeriodType.PROLOGUE){\r
+                periodType = resource.getPeriodType();\r
+                if(periodType != PeriodType.PROGRESS){\r
+                    throw new IllegalArgumentException();\r
+                }\r
+            }else if(periodType == PeriodType.PROGRESS){\r
+                periodType = resource.getPeriodType();\r
+            }else if(periodType == PeriodType.EPILOGUE){\r
+                throw new IllegalArgumentException();\r
+            }\r
+        }\r
+\r
+        if(lastDay < 0) throw new IllegalArgumentException();\r
+        if(periodType != PeriodType.EPILOGUE){\r
+            throw new IllegalArgumentException();\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 全PeriodResourceから、共通するベースURIを抽出する。\r
+     * @param list PeriodResource並び\r
+     * @return ベースURI文字列\r
+     * @throws IllegalArgumentException ベースURIが一致していない\r
+     */\r
+    public static String getBaseUri(List<PeriodResource> list)\r
+            throws IllegalArgumentException{\r
+        String result = null;\r
+\r
+        for(PeriodResource resource : list){\r
+            String urlText = resource.getOrigUrlText();\r
+            urlText = urlText.replaceAll("[^/]*$", "");\r
+            if(result == null){\r
+                result = urlText;\r
+            }else{\r
+                if( ! result.equals(urlText) ){\r
+                    throw new IllegalArgumentException();\r
+                }\r
+            }\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+    private final List<PeriodResource> resourceList;\r
+\r
+    private final LandDef landDef;\r
+    private final int villageId;\r
+    private final String baseUri;\r
+\r
+    private String fullName = "";\r
+    private int commitHour = -1;\r
+    private int commitMinute = -1;\r
+    private String graveIconUri;\r
+\r
+    private final List<AvatarData> avatarList = new LinkedList<AvatarData>();\r
+    private int undefAvatarNo = 1;\r
+\r
+    private final List<PeriodData> periodList = new LinkedList<PeriodData>();\r
+\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param resourceList PeriodResource並び\r
+     */\r
+    public VillageData(List<PeriodResource> resourceList){\r
+        super();\r
+\r
+        validatePeriodResource(resourceList);\r
+\r
+        this.resourceList = new LinkedList<PeriodResource>(resourceList);\r
+\r
+        PeriodResource resource1st = this.resourceList.get(0);\r
+        this.landDef   = resource1st.getLandDef();\r
+        this.villageId = resource1st.getVillageId();\r
+        this.baseUri = getBaseUri(this.resourceList);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 国情報を取得する。\r
+     * @return 国情報\r
+     */\r
+    public LandDef getLandDef(){\r
+        return this.landDef;\r
+    }\r
+\r
+    /**\r
+     * 村IDを取得する。\r
+     * @return 村ID\r
+     */\r
+    public int getVillageId(){\r
+        return this.villageId;\r
+    }\r
+\r
+    /**\r
+     * ベースURIを取得する。\r
+     * @return ベースURI\r
+     */\r
+    public String getBaseUri(){\r
+        return this.baseUri;\r
+    }\r
+\r
+    /**\r
+     * 村フルネームを取得する。\r
+     * @return 村フルネーム\r
+     */\r
+    public String getFullName(){\r
+        return this.fullName;\r
+    }\r
+\r
+    /**\r
+     * 村フルネームを設定する。\r
+     * @param fullName 村フルネーム\r
+     */\r
+    public void setFullName(String fullName){\r
+        this.fullName = fullName;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 更新時を取得する。\r
+     * @return 更新時\r
+     */\r
+    public int getCommitHour(){\r
+        return this.commitHour;\r
+    }\r
+\r
+    /**\r
+     * 更新時を設定する。\r
+     * @param commitHour 更新時\r
+     */\r
+    public void setCommitHour(int commitHour){\r
+        this.commitHour = commitHour;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 更新分を取得する。\r
+     * @return 更新分\r
+     */\r
+    public int getCommitMinute(){\r
+        return this.commitMinute;\r
+    }\r
+\r
+    /**\r
+     * 更新分を設定する。\r
+     * @param commitMinute 更新分\r
+     */\r
+    public void setCommitMinute(int commitMinute){\r
+        this.commitMinute = commitMinute;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 墓アイコンURIを取得する。\r
+     * @return 墓アイコンURI文字列\r
+     */\r
+    public String getGraveIconUri(){\r
+        if(this.graveIconUri == null){\r
+            return this.landDef.getTombFaceIconURI().toASCIIString();\r
+        }\r
+        return this.graveIconUri;\r
+    }\r
+\r
+    /**\r
+     * 墓アイコンURI文字列を設定する。\r
+     * @param graveIconUri 墓アイコンURI文字列\r
+     */\r
+    public void setGraveIconUri(String graveIconUri){\r
+        this.graveIconUri = graveIconUri;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 全Periodの開示状況から総合開示状況を算出する。\r
+     * @return 公開状況\r
+     */\r
+    public DisclosureType getDisclosureType(){\r
+        DisclosureType result = DisclosureType.COMPLETE;\r
+\r
+        for(PeriodData period : this.periodList){\r
+            DisclosureType type = period.getDisclosureType();\r
+            switch(type){\r
+            case HOT:\r
+                return DisclosureType.HOT;\r
+            case UNCOMPLETE:\r
+                result = DisclosureType.UNCOMPLETE;\r
+                break;\r
+            default:\r
+                break;\r
+            }\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * Periodモデルを追加する。\r
+     * @param period Periodモデル\r
+     */\r
+    public void addPeriodData(PeriodData period){\r
+        this.periodList.add(period);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * PeriodResourcenar並びを取得する。\r
+     * @return PeriodResource並び\r
+     */\r
+    public List<PeriodResource> getPeriodResourceList(){\r
+        return Collections.unmodifiableList(this.resourceList);\r
+    }\r
+\r
+    /**\r
+     * 未知の新規Avatarを生成する。\r
+     * ※ F1556村などへの対処。\r
+     * Avatarのフルネーム、短縮名、識別子が設定される。\r
+     * @param avfullName Avatarのフルネーム\r
+     * @return 新規Avatarモデル\r
+     */\r
+    public AvatarData createAvatar(String avfullName){\r
+        AvatarData avatar = new AvatarData();\r
+\r
+        avatar.setFullName(avfullName);\r
+\r
+        String[] token = avfullName.split("\\s");\r
+        String shortName = token[token.length - 1];\r
+        avatar.setShortName(shortName);\r
+\r
+        String avatarId = "ukavatar" + this.undefAvatarNo;\r
+        this.undefAvatarNo++;\r
+        avatar.setAvatarId(avatarId);\r
+\r
+        return avatar;\r
+    }\r
+\r
+    /**\r
+     * AvatarフルネームからAvatarを得る。\r
+     * まだこの村にいないAvatarならAvatar一覧に登録される。\r
+     * @param seq Avatarフルネーム\r
+     * @return Avatarモデル\r
+     */\r
+    public AvatarData getAvatarData(CharSequence seq){\r
+        for(AvatarData avatar : this.avatarList){\r
+            String avfullName = avatar.getFullName();\r
+            if(avfullName.contentEquals(seq)){\r
+                return avatar;\r
+            }\r
+        }\r
+\r
+        PreDefAvatar predefAvatar =\r
+                AvatarData.getPreDefAvatar(seq);\r
+        if(predefAvatar != null){\r
+            AvatarData avatar = new AvatarData(predefAvatar);\r
+            this.avatarList.add(avatar);\r
+            return avatar;\r
+        }\r
+\r
+        AvatarData avatar = createAvatar(seq.toString());\r
+        this.avatarList.add(avatar);\r
+\r
+        return avatar;\r
+    }\r
+\r
+    /**\r
+     * avatarList要素のXML出力。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpAvatarList(Writer writer) throws IOException{\r
+        writer.append("<avatarList>").append("\n\n");\r
+\r
+        for(AvatarData avatar : this.avatarList){\r
+            avatar.dumpXml(writer);\r
+            writer.append('\n');\r
+        }\r
+\r
+        writer.append("</avatarList>").append('\n');\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 全period要素のXML出力。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpPeriodList(Writer writer) throws IOException{\r
+        for(PeriodData period : this.periodList){\r
+            period.dumpXml(writer);\r
+            writer.append('\n');\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * village要素のXML出力。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void dumpXml(Writer writer) throws IOException{\r
+        writer.append("<village\n");\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.dumpNameSpaceDecl(writer);\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.dumpSiNameSpaceDecl(writer);\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.dumpSchemeLocation(writer);\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "xml:lang", "ja-JP");\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "xml:base", this.baseUri);\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "fullName", this.fullName);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "vid", Integer.toString(this.villageId));\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.timeAttrOut(writer,\r
+                             "commitTime",\r
+                             this.commitHour, this.commitMinute);\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "state", "gameover");\r
+\r
+        DisclosureType type = getDisclosureType();\r
+        if(type != DisclosureType.COMPLETE){\r
+            writer.append(' ');\r
+            XmlUtils.attrOut(writer, "disclosure", type.getXmlName());\r
+        }\r
+\r
+        String isValid;\r
+        if(this.landDef.isValidVillageId(this.villageId)){\r
+            isValid = "true";\r
+        }else{\r
+            isValid = "false";\r
+        }\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "isValid", isValid);\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "landName", this.landDef.getLandName());\r
+\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "formalName", this.landDef.getFormalName());\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "landId", this.landDef.getLandId());\r
+\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer, "landPrefix", this.landDef.getLandPrefix());\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        String locale = this.landDef.getLocale().toString();\r
+        locale = locale.replaceAll("_", "-");\r
+        XmlUtils.attrOut(writer, "locale", locale);\r
+\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer,\r
+                "origencoding", this.landDef.getEncoding().name());\r
+\r
+        writer.append(' ');\r
+        XmlUtils.attrOut(writer,\r
+                "timezone", this.landDef.getTimeZone().getID());\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "graveIconURI", getGraveIconUri());\r
+        writer.append('\n');\r
+\r
+        XmlUtils.indent(writer, 1);\r
+        XmlUtils.attrOut(writer, "generator", JinArchiver.GENERATOR);\r
+        writer.append('\n');\r
+\r
+        writer.append(">").append('\n');\r
+\r
+        writer.append('\n');\r
+        dumpAvatarList(writer);\r
+\r
+        writer.append('\n');\r
+        dumpPeriodList(writer);\r
+\r
+        writer.append("</village>").append("\n");\r
+\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/Win31j.java b/src/main/java/jp/sourceforge/jindolf/archiver/Win31j.java
new file mode 100644 (file)
index 0000000..8c8ab6b
--- /dev/null
@@ -0,0 +1,108 @@
+/*\r
+ * windows-31j encoding utilities\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: Win31j.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.nio.ByteBuffer;\r
+import java.nio.charset.CharacterCodingException;\r
+import java.nio.charset.Charset;\r
+import java.nio.charset.CharsetDecoder;\r
+import java.nio.charset.CodingErrorAction;\r
+import jp.sourceforge.jindolf.parser.DecodeErrorInfo;\r
+import jp.sourceforge.jindolf.parser.DecodedContent;\r
+\r
+/**\r
+ * windows-31jエンコーディング(機種依存文字)に関する諸々。\r
+ *\r
+ * TODO 携帯絵文字サポート\r
+ */\r
+public final class Win31j{\r
+\r
+    /** デフォルト置換文字。 */\r
+    public static final char REP_CHAR = '\ufffd';\r
+\r
+    /** windows-31j Charset. */\r
+    public static final Charset CS_WIN31J = Charset.forName("windows-31j");\r
+\r
+    private static final CharsetDecoder WIN31JDECODER;\r
+    private static final ByteBuffer byteBuffer = ByteBuffer.allocate(2);\r
+\r
+    static{\r
+        WIN31JDECODER = CS_WIN31J.newDecoder();\r
+        WIN31JDECODER.onMalformedInput(CodingErrorAction.REPORT);\r
+        WIN31JDECODER.onUnmappableCharacter(CodingErrorAction.REPORT);\r
+        WIN31JDECODER.reset();\r
+        byteBuffer.clear();\r
+    }\r
+\r
+    /**\r
+     * winsows-31jエンコーディングされたと想定した2バイトデータ\r
+     * の復号を試みる。\r
+     * 復号に失敗すればU+FFFDを返す。\r
+     * @param b1 1バイト目\r
+     * @param b2 2バイト目\r
+     * @return 復号化された1文字\r
+     */\r
+    public static synchronized char getWin31jChar(byte b1, byte b2){\r
+        char replaced;\r
+\r
+        WIN31JDECODER.reset();\r
+        byteBuffer.clear();\r
+        byteBuffer.put(b1).put(b2);\r
+        byteBuffer.flip();\r
+\r
+        try{\r
+            replaced = WIN31JDECODER.decode(byteBuffer).charAt(0);\r
+        }catch(CharacterCodingException e){\r
+            replaced = REP_CHAR;\r
+        }\r
+\r
+        return replaced;\r
+    }\r
+\r
+    /**\r
+     * デコードエラーがwindows-31jに由来する物と仮定して\r
+     * 復号を試みる。\r
+     * 1バイトエラーもしくは復号に失敗すればU+FFFDを返す。\r
+     * @param info デコードエラー\r
+     * @return 復号化された1文字\r
+     */\r
+    public static char getWin31jChar(DecodeErrorInfo info){\r
+        if( ! info.has2nd() ) return REP_CHAR;\r
+\r
+        byte b1 = info.getRawByte1st();\r
+        byte b2 = info.getRawByte2nd();\r
+        char replaceChar = getWin31jChar(b1, b2);\r
+\r
+        return replaceChar;\r
+    }\r
+\r
+    /**\r
+     * デコードエラーを含む文字列に対し、\r
+     * windows-31jによる復号での補完を試みる。\r
+     * @param content 文字列\r
+     */\r
+    public static void supplyWin31jChar(DecodedContent content){\r
+        if( ! content.hasDecodeError() ) return;\r
+\r
+        for(DecodeErrorInfo info : content.getDecodeErrorList()){\r
+            int pos = info.getCharPosition();\r
+            char replaceChar = getWin31jChar(info);\r
+            content.setCharAt(pos, replaceChar);\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 隠しコンストラクタ。\r
+     */\r
+    private Win31j(){\r
+        throw new Error();\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/XmlUtils.java b/src/main/java/jp/sourceforge/jindolf/archiver/XmlUtils.java
new file mode 100644 (file)
index 0000000..ce32923
--- /dev/null
@@ -0,0 +1,496 @@
+/*\r
+ * XML utils\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: XmlUtils.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.io.BufferedOutputStream;\r
+import java.io.BufferedWriter;\r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.OutputStream;\r
+import java.io.OutputStreamWriter;\r
+import java.io.Writer;\r
+import java.text.MessageFormat;\r
+import java.util.Calendar;\r
+import java.util.GregorianCalendar;\r
+import java.util.List;\r
+import java.util.TimeZone;\r
+import jp.sourceforge.jindolf.corelib.LandDef;\r
+import jp.sourceforge.jindolf.parser.DecodeErrorInfo;\r
+import jp.sourceforge.jindolf.parser.DecodedContent;\r
+\r
+/**\r
+ * XML用各種ユーティリティ\r
+ */\r
+public final class XmlUtils{\r
+\r
+    public static final String ORIG_DTD =\r
+            "http://jindolf.sourceforge.jp/xml/dtd/bbsArchive-091001.dtd";\r
+    public static final String ORIG_NS =\r
+            "http://jindolf.sourceforge.jp/xml/ns/401";\r
+    public static final String ORIG_SCHEME =\r
+            "http://jindolf.sourceforge.jp/xml/xsd/bbsArchive-091001.xsd";\r
+    public static final String SCHEMA_NS =\r
+            "http://www.w3.org/2001/XMLSchema-instance";\r
+\r
+    public static final String OUTPATH = "D:\\TEMP\\zxzx\\";\r
+\r
+    private static final String INDENT_UNIT = "\u0020\u0020";\r
+\r
+    private static final TimeZone TZ_TOKYO =\r
+            TimeZone.getTimeZone("Asia/Tokyo");\r
+    private static final GregorianCalendar calendar =\r
+            new GregorianCalendar(TZ_TOKYO);\r
+\r
+    /**\r
+     * DOCTYPE宣言を出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void dumpDocType(Writer writer) throws IOException{\r
+        writer.append("<!DOCTYPE village SYSTEM ");\r
+        writer.append('"');\r
+        writer.append(ORIG_DTD);\r
+        writer.append('"');\r
+        writer.append(" >");\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * オリジナルNameSpace宣言を出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void dumpNameSpaceDecl(Writer writer)\r
+            throws IOException{\r
+        attrOut(writer, "xmlns", ORIG_NS);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * スキーマNameSpace宣言を出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void dumpSiNameSpaceDecl(Writer writer)\r
+            throws IOException{\r
+        attrOut(writer, "xmlns:xsi", SCHEMA_NS);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * スキーマ位置指定を出力する。\r
+     * @param writer 出力先\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void dumpSchemeLocation(Writer writer)\r
+            throws IOException{\r
+        attrOut(writer,\r
+                "xsi:schemaLocation",\r
+                ORIG_NS + " " + ORIG_SCHEME);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * インデント用空白を出力する。\r
+     * ネスト単位は空白2文字\r
+     * @param writer 出力先\r
+     * @param level ネストレベル\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void indent(Writer writer, int level) throws IOException{\r
+        for(int ct = 1; ct <= level; ct++){\r
+            writer.append(INDENT_UNIT);\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * XML数値文字参照を出力する。\r
+     * @param writer 出力先\r
+     * @param chVal 出力文字\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void charRefOut(Writer writer, char chVal)\r
+            throws IOException{\r
+        if(chVal == '\u0020'){\r
+            writer.append("&#x20;");\r
+            return;\r
+        }\r
+\r
+        if(chVal == '\u0009'){\r
+            writer.append("&#x09;");\r
+            return;\r
+        }\r
+\r
+        int ival = 0xffff & ((int) chVal);\r
+        String hex = Integer.toHexString(ival);\r
+        if(hex.length() % 2 != 0) hex = "0" + hex;\r
+\r
+        writer.append("&#x");\r
+        writer.append(hex);\r
+        writer.append(";");\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 不正文字をXML出力する。\r
+     * @param writer 出力先\r
+     * @param chVal 不正文字\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void dumpInvalidChar(Writer writer, char chVal)\r
+            throws IOException{\r
+        int hexVal;\r
+        hexVal = chVal & 0xff;\r
+        String hexBin = Integer.toHexString(hexVal);\r
+        if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;\r
+\r
+        char replaceChar = '\ufffd';\r
+        if('\u0000' <= chVal && chVal <= '\u001f'){\r
+            replaceChar = (char)( chVal + '\u2400' );\r
+        }\r
+\r
+        writer.append("<rawdata");\r
+\r
+        writer.append(' ');\r
+        attrOut(writer, "encoding", "Shift_JIS");\r
+\r
+        writer.append(' ');\r
+        attrOut(writer, "hexBin", hexBin);\r
+\r
+        writer.append(" >");\r
+        writer.append(replaceChar);\r
+        writer.append("</rawdata>");\r
+    }\r
+\r
+    /**\r
+     * 任意の文字がXML規格上のホワイトスペースに属するか判定する。\r
+     * @param chVal 文字\r
+     * @return ホワイトスペースならtrue\r
+     */\r
+    public static boolean isWhiteSpace(char chVal){\r
+        switch(chVal){\r
+        case '\u0020':\r
+        case '\t':\r
+        case '\n':\r
+        case '\r':\r
+            return true;\r
+        default:\r
+            break;\r
+        }\r
+\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * 文字列を出力する。\r
+     * <ul>\r
+     * <li>先頭および末尾のホワイトスペースは強制的に文字参照化される。\r
+     * <li>連続したホワイトスペースの2文字目以降は文字参照化される。\r
+     * <li>スペースでないホワイトスペースは無条件に文字参照化される。\r
+     * <li>{@literal &, <, >, "}は無条件に文字参照化される。\r
+     * </ul>\r
+     * 参考:XML 1.0 規格 3.3.3節\r
+     * @param writer 出力先\r
+     * @param seq CDATA文字列\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void textOut(Writer writer, CharSequence seq)\r
+            throws IOException{\r
+        int len = seq.length();\r
+\r
+        boolean leadSpace = false;\r
+\r
+        for(int pos = 0; pos < len; pos++){\r
+            char chVal = seq.charAt(pos);\r
+\r
+            if(isWhiteSpace(chVal)){\r
+                if(pos == 0 || pos >= len - 1 || leadSpace){\r
+                    charRefOut(writer, chVal);\r
+                }else if(chVal != '\u0020'){\r
+                    charRefOut(writer, chVal);\r
+                }else{\r
+                    writer.append(chVal);\r
+                }\r
+                leadSpace = true;\r
+            }else{\r
+                if(chVal == '&'){\r
+                    writer.append("&amp;");\r
+                }else if(chVal == '<'){\r
+                    writer.append("&lt;");\r
+                }else if(chVal == '>'){\r
+                    writer.append("&gt;");\r
+                }else if(chVal == '"'){\r
+                    writer.append("&quot;");\r
+                }else if(chVal == '\''){\r
+                    writer.append("&apos;");\r
+                }else if(chVal == '\u005c\u005c'){\r
+                    writer.append('\u00a5');\r
+                }else if(chVal == '\u007e'){\r
+                    writer.append('\u203e');\r
+                }else if(Character.isISOControl(chVal)){\r
+                    dumpInvalidChar(writer, chVal);\r
+                }else{\r
+                    writer.append(chVal);\r
+                }\r
+                leadSpace = false;\r
+            }\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 属性を出力する。\r
+     * @param writer 出力先\r
+     * @param name 属性名\r
+     * @param value 属性値\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void attrOut(Writer writer,\r
+                                CharSequence name,\r
+                                CharSequence value)\r
+            throws IOException{\r
+        StringBuilder newValue = new StringBuilder(value);\r
+        for(int pt = 0; pt < newValue.length(); pt++){\r
+            char chVal = newValue.charAt(pt);\r
+            if(chVal == '\n' || chVal == '\r' || chVal == '\t') continue;\r
+            if(Character.isISOControl(chVal)){\r
+                newValue.setCharAt(pt, (char)('\u2400' + chVal));\r
+            }\r
+        }\r
+\r
+        writer.append(name);\r
+        writer.append('=');\r
+        writer.append('"');\r
+        textOut(writer, newValue);\r
+        writer.append('"');\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * xsd:time形式の時刻属性を出力する。\r
+     * タイムゾーンは「+09:00」固定\r
+     * @param writer 出力先\r
+     * @param name 属性名\r
+     * @param hour 時間\r
+     * @param minute 分\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void timeAttrOut(Writer writer,\r
+                                     CharSequence name,\r
+                                     int hour, int minute)\r
+            throws IOException{\r
+        String cmtTime =\r
+                MessageFormat\r
+                .format("{0,number,#00}:{1,number,#00}:00+09:00",\r
+                        hour, minute);\r
+        attrOut(writer, name, cmtTime);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * xsd:gMonthDay形式の日付属性を出力する。\r
+     * タイムゾーンは「+09:00」固定\r
+     * @param writer 出力先\r
+     * @param name 属性名\r
+     * @param month 月\r
+     * @param day 日\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void dateAttrOut(Writer writer,\r
+                                     CharSequence name,\r
+                                     int month, int day)\r
+            throws IOException{\r
+        String dateAttr =\r
+                MessageFormat.format("--{0,number,#00}-{1,number,#00}+09:00",\r
+                                     month, day);\r
+        attrOut(writer, name, dateAttr);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * xsd:dateTime形式の日付時刻属性を出力する。\r
+     * タイムゾーンは「+09:00」固定\r
+     * @param writer 出力先\r
+     * @param name 属性名\r
+     * @param epochMs エポック時刻\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void dateTimeAttr(Writer writer,\r
+                                      CharSequence name,\r
+                                      long epochMs)\r
+            throws IOException{\r
+        synchronized(calendar){\r
+            calendar.setTimeInMillis(epochMs);\r
+            int year = calendar.get(Calendar.YEAR);\r
+            int month = calendar.get(Calendar.MONTH) + 1;\r
+            int day = calendar.get(Calendar.DATE);\r
+            int hour = calendar.get(Calendar.HOUR_OF_DAY);\r
+            int minute = calendar.get(Calendar.MINUTE);\r
+            int sec = calendar.get(Calendar.SECOND);\r
+            int msec = calendar.get(Calendar.MILLISECOND);\r
+\r
+            String attrVal = MessageFormat.format(\r
+                     "{0,number,#0000}-{1,number,#00}-{2,number,#00}"\r
+                    +"T{3,number,#00}:{4,number,#00}:{5,number,#00}"\r
+                    +".{6,number,#000}+09:00",\r
+                    year, month, day, hour, minute, sec, msec);\r
+\r
+            attrOut(writer, name, attrVal);\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * デコードエラー情報をrawdataタグで出力する。\r
+     * 文字列集合に関するエラーの場合、windows31jでのデコード出力を試みる。\r
+     * @param writer 出力先\r
+     * @param errorInfo デコードエラー\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void dumpErrorInfo(Writer writer,\r
+                                       DecodeErrorInfo errorInfo)\r
+            throws IOException{\r
+        int hexVal;\r
+        hexVal = errorInfo.getRawByte1st() & 0xff;\r
+        if(errorInfo.has2nd()){\r
+            hexVal = hexVal << 8;\r
+            hexVal |= errorInfo.getRawByte2nd() & 0xff;\r
+        }\r
+\r
+        String hexBin = Integer.toHexString(hexVal);\r
+        if(hexBin.length() % 2 != 0) hexBin = "0" + hexBin;\r
+\r
+        char replaceChar = Win31j.getWin31jChar(errorInfo);\r
+\r
+        writer.append("<rawdata");\r
+\r
+        writer.append(' ');\r
+        attrOut(writer, "encoding", "Shift_JIS");\r
+\r
+        writer.append(' ');\r
+        attrOut(writer, "hexBin", hexBin);\r
+\r
+        writer.append(" >");\r
+        writer.append(replaceChar);\r
+        writer.append("</rawdata>");\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * デコードエラー込みのテキストを出力する。\r
+     * @param writer 出力先\r
+     * @param content テキスト\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void dumpDecodedContent(Writer writer,\r
+                                             DecodedContent content)\r
+            throws IOException{\r
+        if( ! content.hasDecodeError() ){\r
+            textOut(writer, content);\r
+            return;\r
+        }\r
+\r
+        int last = 0;\r
+\r
+        List<DecodeErrorInfo> errList = content.getDecodeErrorList();\r
+        for(DecodeErrorInfo err : errList){\r
+            int charPos = err.getCharPosition();\r
+            CharSequence line = content.subSequence(last, charPos);\r
+            textOut(writer, line);\r
+            dumpErrorInfo(writer, err);\r
+            last = charPos + 1;\r
+        }\r
+\r
+        CharSequence line = content.subSequence(last, content.length());\r
+        textOut(writer, line);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 村情報をXML形式で出力する。\r
+     * @param writer 出力先\r
+     * @param villageData 村情報\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void dumpVillageData(Writer writer,\r
+                                         VillageData villageData)\r
+            throws IOException{\r
+        writer.append("<?xml");\r
+        writer.append(' ');\r
+        attrOut(writer, "version", "1.0");\r
+        writer.append(' ');\r
+        attrOut(writer, "encoding", "UTF-8");\r
+        writer.append(" ?>\n\n");\r
+\r
+        writer.append("<!--\n");\r
+        writer.append("  人狼BBSアーカイブ\n");\r
+        writer.append("  http://jindolf.sourceforge.jp/\n");\r
+        writer.append("-->\n\n");\r
+\r
+        dumpDocType(writer);\r
+        writer.append("\n\n");\r
+\r
+        villageData.dumpXml(writer);\r
+\r
+        writer.append("\n<!-- EOF -->\n");\r
+\r
+        writer.flush();\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 村情報を反映した出力ファイル名を生成する。\r
+     * @param village 村情報\r
+     * @return XML出力ファイル名\r
+     */\r
+    public static String createOutFileName(VillageData village){\r
+        LandDef landDef = village.getLandDef();\r
+        String landId = landDef.getLandId();\r
+        int vid = village.getVillageId();\r
+\r
+        String fname =\r
+                MessageFormat.format(\r
+                "{0}jin_{1}_{2,number,#00000}.xml", OUTPATH, landId, vid);\r
+        return fname;\r
+    }\r
+\r
+    /**\r
+     * 村情報を反映した出力ファイルへの文字ストリームを生成する。\r
+     * @param village 村情報\r
+     * @return 出力先文字ストリーム\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static Writer createFileWriter(VillageData village)\r
+            throws IOException{\r
+        String fname = createOutFileName(village);\r
+        File file = new File(fname);\r
+\r
+        OutputStream ostream;\r
+        ostream = new FileOutputStream(file);\r
+        ostream = new BufferedOutputStream(ostream, 10000);\r
+        Writer writer;\r
+        writer = new OutputStreamWriter(ostream, "UTF-8");\r
+        writer = new BufferedWriter(writer, 10000);\r
+        return writer;\r
+    }\r
+\r
+    /**\r
+     * 隠れコンストラクタ\r
+     */\r
+    private XmlUtils(){\r
+        throw new Error();\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/ZipUtils.java b/src/main/java/jp/sourceforge/jindolf/archiver/ZipUtils.java
new file mode 100644 (file)
index 0000000..5956f0b
--- /dev/null
@@ -0,0 +1,223 @@
+/*\r
+ * ZIP utils\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: ZipUtils.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.Writer;\r
+import java.net.MalformedURLException;\r
+import java.net.URI;\r
+import java.net.URISyntaxException;\r
+import java.net.URL;\r
+import java.util.Enumeration;\r
+import java.util.Iterator;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+import java.util.zip.ZipEntry;\r
+import java.util.zip.ZipFile;\r
+import jp.sourceforge.jindolf.parser.DecodeException;\r
+import jp.sourceforge.jindolf.parser.HtmlParseException;\r
+\r
+/**\r
+ * ZIPアーカイブされた生XHTML情報へのアクセス諸々。\r
+ */\r
+public final class ZipUtils{\r
+\r
+    /**\r
+     * ZIPファイルに格納された村一覧を抽出する。\r
+     * 各日のロードはまだ行われない。\r
+     * @param zipFile ZIPファイル\r
+     * @return 村一覧\r
+     * @throws IOException 入力エラー\r
+     */\r
+    public static List<VillageData> getVillageDataList(ZipFile zipFile)\r
+            throws IOException{\r
+        List<VillageData> result = new LinkedList<VillageData>();\r
+\r
+        List<ZipEntry> logList = getDownloadLogList(zipFile);\r
+        for(ZipEntry entry : logList){\r
+            VillageData villageData =\r
+                getVillageDataFromLogEntry(zipFile, entry);\r
+            result.add(villageData);\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * ZIPファイルからダウンロードログファイルの一覧を取得する。\r
+     * ダウンロードログファイルは必ず「download.log」の名前を持つ。\r
+     * @param file ZIPファイル\r
+     * @return ログファイル一覧\r
+     */\r
+    public static List<ZipEntry> getDownloadLogList(ZipFile file){\r
+        List<ZipEntry> result = new LinkedList<ZipEntry>();\r
+\r
+        Enumeration<? extends ZipEntry> list = file.entries();\r
+        while(list.hasMoreElements()){\r
+            ZipEntry entry = list.nextElement();\r
+            String name = entry.getName();\r
+            if(name.endsWith("/download.log" )){\r
+                result.add(entry);\r
+            }\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * ログファイルを表すZIPエントリから村情報を抽出する。\r
+     * @param zipFile ZIPファイル\r
+     * @param logEntry ログファイルのZIPエントリ\r
+     * @return 村情報\r
+     * @throws IOException 入力エラー\r
+     */\r
+    public static VillageData getVillageDataFromLogEntry(\r
+            ZipFile zipFile, ZipEntry logEntry)\r
+            throws IOException{\r
+        InputStream istream = zipFile.getInputStream(logEntry);\r
+        List<PeriodResource> list =\r
+                FileArchive.parseDownloadLog(istream);\r
+        istream.close();\r
+\r
+        String baseName = logEntry.getName().replaceAll("/[^/]+$", "/");\r
+        for(PeriodResource resource : list){\r
+            modifyResourceUrl(zipFile, baseName, resource);\r
+        }\r
+\r
+        VillageData villageData = new VillageData(list);\r
+        return villageData;\r
+    }\r
+\r
+    /**\r
+     * ログ記録に書かれたXHTMLファイル名を実際にアクセス可能なURLに修正する。\r
+     * @param zipFile ZIPファイル\r
+     * @param entryBase ファイル名のベース\r
+     * @param resource リソース情報\r
+     * @return 引数と同じ物\r
+     */\r
+    public static PeriodResource modifyResourceUrl(ZipFile zipFile,\r
+                                           String entryBase,\r
+                                           PeriodResource resource ){\r
+        String fileName;\r
+        try{\r
+            URL resUrl = resource.getResourceUrl();\r
+            URI resUri = resUrl.toURI();\r
+            File file = new File(resUri);\r
+            fileName = file.getName();\r
+        }catch(URISyntaxException e){\r
+            throw new IllegalArgumentException(e);\r
+        }\r
+\r
+        String zipUri = new File(zipFile.getName()).toURI().toString();\r
+\r
+        String urlText = "jar:" + zipUri + "!/" + entryBase + fileName;\r
+\r
+        URL zipResource;\r
+        try{\r
+            zipResource = new URL(urlText);\r
+        }catch(MalformedURLException e){\r
+            throw new IllegalArgumentException(e);\r
+        }\r
+\r
+        resource.setResourceUrl(zipResource);\r
+\r
+        return resource;\r
+    }\r
+\r
+    /**\r
+     * 村番号から村情報を得る。\r
+     * @param zipFile ZIPファイル\r
+     * @param vid 村番号\r
+     * @return 村情報\r
+     * @throws IOException 入力エラー\r
+     */\r
+    public static VillageData getVillageData(ZipFile zipFile, int vid)\r
+            throws IOException{\r
+        ZipEntry entry = getDownloadLogEntry(zipFile, vid);\r
+        VillageData result = getVillageDataFromLogEntry(zipFile, entry);\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * 村番号から該当するログファイルエントリを取得する。\r
+     * @param file ZIPファイル\r
+     * @param vid 村番号\r
+     * @return ログファイルのZIPエントリ。見つからなければnull。\r
+     */\r
+    public static ZipEntry getDownloadLogEntry(ZipFile file, int vid){\r
+        Pattern entryPattern =\r
+                Pattern.compile("jin_[^_]+_([0-9]+)/download.log$");\r
+\r
+        Enumeration<? extends ZipEntry> list = file.entries();\r
+        while(list.hasMoreElements()){\r
+            ZipEntry entry = list.nextElement();\r
+            String name = entry.getName();\r
+            Matcher matcher = entryPattern.matcher(name);\r
+            if(matcher.find()){\r
+                String vnum = matcher.group(1);\r
+                if(vid == Integer.parseInt(vnum)) return entry;\r
+            }\r
+        }\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * ZIPファイル中の指定した村番号の村をXML出力する。\r
+     * @param zipFile ZIPファイル\r
+     * @param vid 村番号\r
+     * @param writer 出力先\r
+     * @throws IOException 入出力エラー\r
+     * @throws DecodeException デコードエラー\r
+     * @throws HtmlParseException パースエラー\r
+     */\r
+    public static void dumpZipVid(ZipFile zipFile, int vid, Writer writer)\r
+            throws IOException, DecodeException, HtmlParseException{\r
+        VillageData data = getVillageData(zipFile, vid);\r
+        Builder.fillVillageData(data);\r
+        XmlUtils.dumpVillageData(writer, data);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * ZIPファイル中の全村をXML出力する。\r
+     * @param zipFile ZIPファイル\r
+     * @throws IOException 入出力エラー\r
+     * @throws DecodeException デコードエラー\r
+     * @throws HtmlParseException パースエラー\r
+     */\r
+    public static void dumpZipAll(ZipFile zipFile)\r
+            throws IOException, DecodeException, HtmlParseException{\r
+        List<VillageData> villageDataList;\r
+        villageDataList = ZipUtils.getVillageDataList(zipFile);\r
+        Iterator<VillageData> it = villageDataList.iterator();\r
+        while(it.hasNext()){\r
+            VillageData villageData = it.next();\r
+            Builder.fillVillageData(villageData);\r
+            Writer writer = XmlUtils.createFileWriter(villageData);\r
+            XmlUtils.dumpVillageData(writer, villageData);\r
+            writer.close();\r
+            it.remove();\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 隠れコンストラクタ。\r
+     */\r
+    private ZipUtils(){\r
+        super();\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/archiver/package-info.java b/src/main/java/jp/sourceforge/jindolf/archiver/package-info.java
new file mode 100644 (file)
index 0000000..c2926db
--- /dev/null
@@ -0,0 +1,56 @@
+/*\r
+ * JinArchive パッケージコメント\r
+ *\r
+ * このファイルは、SunJDK5.0以降に含まれるJavadoc用に用意された、\r
+ * 特別な名前を持つソースファイルです。\r
+ * このファイルはソースコードを含まず、\r
+ * パッケージコメントとパッケージ宣言のみが含まれます。\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: package-info.java 877 2009-10-25 15:16:13Z olyutorskii $\r
+ */\r
+\r
+/**\r
+ * このプログラム「JinArchive」は、\r
+ * 人狼BBSサーバへアクセスし任意の村のプレイデータを独自のXML形式で\r
+ * 保存するためのプログラムです。\r
+ *\r
+ * <hr>\r
+ *\r
+ * <p>\r
+ * The MIT License\r
+ * </p>\r
+ * <p>\r
+ * Copyright(c) 2009 olyutorskii\r
+ * </p>\r
+ * <p>\r
+ * Permission is hereby granted, free of charge, to any person obtaining a\r
+ * copy of this software and associated documentation files (the "Software"),\r
+ * to deal in the Software without restriction, including without limitation\r
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,\r
+ * and/or sell copies of the Software, and to permit persons to whom the\r
+ * Software is furnished to do so, subject to the following conditions:\r
+ * </p>\r
+ * <p>\r
+ * The above copyright notice and this permission notice shall be included in\r
+ * all copies or substantial portions of the Software.\r
+ * </p>\r
+ * <p>\r
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\r
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r
+ * OTHER DEALINGS IN THE SOFTWARE.\r
+ * </p>\r
+ *\r
+ * <hr>\r
+ *\r
+ * @see <a href="http://jindolf.sourceforge.jp/">\r
+ * Jindolfポータルサイト</a>\r
+ * @see <a href="http://sourceforge.jp/projects/jindolf/">\r
+ * Jindolf開発プロジェクト</a>\r
+ */\r
+\r
+package jp.sourceforge.jindolf.archiver;\r
diff --git a/src/main/resources/jp/sourceforge/jindolf/archiver/resources/version.properties b/src/main/resources/jp/sourceforge/jindolf/archiver/resources/version.properties
new file mode 100644 (file)
index 0000000..1bd84de
--- /dev/null
@@ -0,0 +1,10 @@
+# package-version definition\r
+#\r
+# this file is referenced by Ant-task\r
+#\r
+# Copyright(c) 2009 olyutorskii\r
+# $Id: version.properties 879 2009-10-25 15:42:50Z olyutorskii $\r
+\r
+pkg-version.jp.sourceforge.jindolf.archiver = 1.401.2\r
+\r
+# EOF #\r