/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2005/07/12, 19:34
!  AUTHOR(S): KOGA, Junichiro
!  File : ProjectInfo.java
!  
!  Contact address :  Phase System Consortium
!                     E-mail: phase_system@nims.go.jp URL https://azuma.nims.go.jp
!
!
!   Since 2002, this program set had been intensively developed as a part of the following 
!  national projects supported by the Ministry of Education, Culture, Sports, Science and
!  Technology (MEXT) of Japan; "Frontier Simulation Software for Industrial Science
!  (FSIS)" from 2002 to 2005, "Revolutionary Simulation Software (RSS21)" from 2006 to
!  2008. "Research and Development of Innovative Simulation Software (RISS)" from 2008
!  to 2013. These projects is lead by the Center for Research on Innovative Simulation 
!  Software (CISS), the Institute of Industrial Science (IIS), the University of Tokyo.
!   Since 2013, this program set has been further developed centering on PHASE System
!  Consortium. 
!   The activity of development of this program set has been supervised by Takahisa Ohno.
!
!=======================================================================
 */

package ciss.phase_viewer.projectbrowser;

import java.awt.Dimension;
import java.io.File;
import java.io.Serializable;
import java.net.URL;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;

import ciss.phase_viewer.file.ChaseFileManager;
import ciss.phase_viewer.inputinterface.DataManager;
import ciss.phase_viewer.inputinterface.InputInterface;
import ciss.phase_viewer.jdom.MyElement;
import ciss.phase_viewer.jobcontrol.DefaultScriptCreator;
import ciss.phase_viewer.outputinterface.OutputInterface;
import ciss.phase_viewer.ssh.hosts.HostInfo;
import ciss.phase_viewer.ssh.hosts.HostList;

/**
 * プロジェクト情報を保持する, 構造体のようなクラス.
 * 
 * "ProjectInfo" は, 下記がある.
 * 
 * 1. $HOME/.phase-viewer/my_projects.xmlに記述してある情報: name: プロジェクト名 date: 作成された日付
 * projectType: 「プロジェクト種」, 例: phase projdir: プロジェクトディレクトリー hasSubs: 下位構造を持つか否か
 * infoType: BASEかSUB, BASEとは下位構造を持ちうるプロジェクト, SUBは持たないプロジェクト comment:
 * プロジェクトに付随するコメント.
 * 
 * 2. プロジェクトロード後に作成されるインスタンスへの参照: parent: このクラスの情報は,
 * その多くが「ProjectBrowser」オブジェクトにおいて定義され, また利用される. その大事な大事なオブジェクトへの参照.
 * inputInterface: InputInterfaceオブジェクト. phase入力ファイル形式の読み/書きが可能 dm:
 * inputInterfaceを保持する, DataManagerクラスのオブジェクト. ディスク上のファイルはこのクラスを介する.
 * outputInterface: 出力データを統一的に扱うための, OutputInterfaceオブジェクト.
 * defaultScriptCreator: ジョブ制御用のデフォルトスクリプトを作成するオブジェクト. projectdescriptionURL :
 * プロジェクトに纏わる内容が記述してあるHTMLファイルへのURL (nullならそれはそれでよい)
 * 
 * 3. 動的に変更される情報: subprojects: 下位のProjectInfoオブジェクトを格納するためのVectorオブジェクト. この操作は,
 * 主にparentから行われる. targetDir: ホスト情報が変更されるたびに変わる.
 * 
 * @author KOGA, Junichiro
 */
public class ProjectInfo implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 6020378635554945716L;
    public static final int BASE = 1;
    public static final int SUB = 2;

    private static Logger logger = Logger
            .getLogger(ProjectInfo.class.getName());

    private URL projectdescriptionURL = null;

    private String projdir;
    private String projectType;
    private int infoType = BASE;
    private String name;
    private String date;
    private Vector subprojects = new Vector();

    private Element element;

    private ChaseFileManager chaseFileManager; // new style

    private ProjectBrowser parent;
    private Document doc;

    private InputInterface inputInterface;
    private OutputInterface outputInterface;

    private DataManager dm;

    private DefaultScriptCreator defaultScriptCreator;

    private String targetDir;
    private String parentDir = null;

    private String comment;
    private boolean readProjectPropertyDoc = false;

    private String rootDir = System.getProperty("user.home");
    private HostInfo hostInfo;

    private boolean editable = true;

    private Dimension initBrowserSize = new Dimension(680, 500);

    private Class[] stdoutGUIs;

    private HashMap guiComponents = new HashMap();

    /**
     * Creates a new instance of ProjectInfo
     * 
     * @param element
     *            ディスクより読み込んだプロジェクト情報を持つElementオブジェクト.
     */
    public ProjectInfo(Element element) {
        this.element = element;
        init();
    }

    /**
     * プロジェクトディレクトリー下のphase-viewer.xmlファイルも読み込むかどうかを指定できる
     * 
     * @param element
     *            myproject.xmlより読み込んだプロジェクト情報を持つElement
     * @param readProjectPropertyDoc
     *            プロジェクトリーディレクトリー下のphase-viewer.xmlも読み込むかどうかを指定
     */
    public ProjectInfo(Element element, boolean readProjectPropertyDoc) {
        this.element = element;
        this.readProjectPropertyDoc = readProjectPropertyDoc;
        init();
    }

    /**
     * subprojectのProjectInfoを足しこむ.
     * 
     * @param info
     *            足しこみたいProjectInfoオブジェクト
     */
    public void addSubProjectInfo(ProjectInfo info) {
        if (this.infoType == BASE) {
            subprojects.addElement(info);
        }
    }

    /**
     * 計算プログラムが利用する入出力ファイルに関わる情報を保持する, ChaseFileManagerオブジェクトへの 参照を取得する.
     * 
     * @return このプロジェクトに関連付けられたChaseFileManagerオブジェクト.
     */
    public ChaseFileManager getChaseFileManager() {
        logger.debug("at getChaseFileManager:");
        if (this.chaseFileManager == null) {
            logger.debug("chasefilemanager is null...");
        }
        return this.chaseFileManager;
    }

    /**
     * プロジェクトに付随するコメントをゲット. 長さが0の場合nullを返す.
     * 
     * @return コメント
     */
    public String getComment() {
        if (comment == null || comment.trim().length() == 0) {
            return null;
        }
        return this.comment;
    }

    /**
     * このプロジェクトの作成日時を返す.
     * 
     * @param 文字列表記の作成日時
     *            .
     */
    public String getDate() {
        return date;
    }

    /**
     * ジョブ制御用の, デフォルトスクリプトを作成するオブジェクトをゲット.
     * 
     * @return デフォルトのスクリプトを作成するオブジェクト.
     */
    public DefaultScriptCreator getDefaultScriptCreator() {
        return this.defaultScriptCreator;
    }

    /**
     * 登録されているGUIのインスタンスを取得. JComponentのサブクラスで無い場合nullを返す.
     * 
     * @param clazz
     *            登録されているGUIのクラス
     * @return 登録されているGUI
     */
    public Object getGUI(Class clazz) {
        Object obj = guiComponents.get(clazz.getName());
        return obj;
    }

    /**
     * 現在のプロジェクトが関連付けられているホスト情報をゲット
     * 
     * @return 現在のプロジェクトのターゲットホスト
     */
    public HostInfo getHostInfo() {
        return this.hostInfo;
    }

    /**
     * 「プロジェクト種」を返す. ここで「プロジェクト種」はBASEかSUBのいずれかで, BASEの場合はさらに下位のプロジェクトを持ちえる.
     * 
     * @return プロジェクト種.
     */
    public int getInfoType() {
        return infoType;
    }

    /**
     * プロジェクトブラウザーの初期サイズを取得; デフォルトは800x600
     * 
     * @return プロジェクトブラウザーの初期サイズ (ユーザーによるリサイズが行われた場合, そちらのサイズが保存されて優先される)
     */
    public Dimension getInitialBrowserSize() {
        return initBrowserSize;
    }

    /**
     * InputInterfaceオブジェクトを格納した, DataManagerオブジェクトへの参照を取得する.
     * InputInterfaceオブジェクトを一から作り直す必要がある場合 (例: テンプレートファイルコピー直後)などの場合に使う.
     * 
     * @return このプロジェクトに関連付けら得れたDataManagerオブジェクト.
     */
    public DataManager getInputDataManager() {
        return this.dm;
    }

    /**
     * このクラスに関連付けられた, 入力ファイルを処理するクラスである, InputInterfaceオブジェクトを 得るメソッド.
     * 入力ファイル編集用GUIは, このメソッドを介して同一の入力ファイルにアクセスすることが できる.
     * 
     * @return このプロジェクトに関連付けられたInputInterfaceオブジェクトへの参照.
     */
    public InputInterface getInputInterface() {
        return this.inputInterface;
    }

    /**
     * 出力データを統一的に扱うためのOutputInterfaceオブジェクトへの参照を取得する.
     * 
     * @return このプロジェクトに関連付けられているOutputInterfaceオブジェクトへの参照.
     */
    public OutputInterface getOutputInterface() {
        return outputInterface;
    }

    /**
     * ProjectBrowserへの参照を取得する. このオブジェクトがこのクラスのインスタンスから情報を 取得し,
     * 様々な表示やアクションが決定されるようになっている.
     * 
     * @return このプロジェクトに関連付けられたProjectBrowserへの参照
     */
    public ProjectBrowser getParent() {
        return this.parent;
    }

    /**
     * このプロジェクトの「親ディレクトリー」を返す. 親ディレクトリーはekcalやuvsorなどの 場合のSCF計算を行ったディレクトリーを指す.
     * PHASEの場合はnullを返す.
     * 
     * @return 親ディレクトリーへのパス(相対パス)
     */
    public String getParentDir() {
        return parentDir;
    }

    private String getPathFromElement(String delimiter) {
        Element parent = element;
        String dir = "";
        Vector vector = new Vector();
        // vector.addElement(MyElement.decode(element.getChildTextTrim("name")));
        vector.addElement(getProjectName());
        while ((parent = parent.getParentElement()) != null
                && !parent.getChildTextTrim("type").equals("root")) {
            vector.addElement(MyElement.decode(parent.getChildTextTrim("name")));
        }
        for (int i = vector.size() - 1; i >= 0; i--) {
            dir += (String) vector.get(i) + delimiter;
        }
        return dir;
    }

    /**
     * プロジェクトの説明などが記述してあるHTMLファイルへのURLを取得.
     * 
     * @return プロジェクトに纏わる情報が記述してあるHTMLファイル
     */
    public URL getProjectDescriptionURL() {
        return this.projectdescriptionURL;
    }

    /**
     * このプロジェクトのベースとなるディレクトリーへのパスを返す. プロジェクトのツリー表示と実際のディスク上のファイルの位置は同じになるようにしている
     * 
     * @return プロジェクトディレクトリーへのパス.
     */
    public String getProjectDirectory() {
        // return this.projdir;
        if (isRoot()) {
            return rootDir;
        }
        String dir = getPathFromElement(System.getProperty("file.separator"));
        logger.debug("ProjectDirectory: " + dir);
        return rootDir + System.getProperty("file.separator") + dir;
    }

    /**
     * my_projects.xmlに記載のある, このProjectInfoのElementを取得
     * 
     * @return このプロジェクトの情報が記載されたElement.
     */
    public Element getProjectElement() {
        return this.element;
    }

    /**
     * プロジェクト名を返す.
     * 
     * @return プロジェクト名
     */
    public String getProjectName() {
        return name;
    }

    /**
     * 外部ファイルにプロジェクト情報を書き出す際に必要なDocumentオブジェクトへの参照を取得する.
     * 
     * @return このクラスに関連付けられたDocumentオブジェクト.
     */
    public Document getProjectPropertyDocument() {
        return this.doc;
    }

    /**
     * 「プロジェクトタイプ」を返す. プロジェクトタイプとは, たとえばphaseなら "phase" という文字列.
     * 具体的にはプラグインでの定義による.
     * 
     * @return 「プロジェクトタイプ」
     */
    public String getProjectType() {
        return projectType;
    }

    /**
     * 標準出力を表示するGUIのClassを取得. nullを返すかも.
     * 
     * @return 上記の通り
     */
    public Class[] getStdoutGUIs() {
        return this.stdoutGUIs;
    }

    /**
     * サブプロジェクトのProjectInfoオブジェクトを格納したjava.util.Vectorオブジェクトを返す.
     * 
     * @return サブプロジェクトのProjectInfoオブジェクトを格納したVector.
     */
    public Vector getSubProjects() {
        return subprojects;
    }

    /**
     * ターゲットディレクトリーを返す. ProjectDirectoryはローカルホストのディレクトリーであるのに対し,
     * TargetDirectoryはリモートホストの実行ディレクトリー.
     */
    public String getTargetDirectory() {
        return this.targetDir;
    }

    /**
     * まずは, とにもかくもディスク上の情報を読み込む.
     */
    private void init() {
        hostInfo = HostList.getHostList().getHostInfo("localhost");
        String bdir = hostInfo.getProperty("basedir");
        if (bdir != null && bdir.trim().length() != 0
                && new File(bdir).exists() && new File(bdir).isDirectory()) {
            rootDir = bdir;
        }

        String elemName = element.getName();
        if (elemName.equals("project")) {
            infoType = BASE;
        } else if (elemName.equals("subproject")) {
            infoType = SUB;
        }
        name = MyElement.decode(element.getChildTextTrim("name"));
        date = MyElement.decode(element.getChildTextTrim("date"));
        projectType = MyElement.decode(element.getChildTextTrim("type"));
        projdir = MyElement.decode(element.getChildTextTrim("directory"));
        comment = MyElement.decode(element.getChildTextTrim("comment"));

        Element edi = element.getChild("editable");
        if (edi != null) {
            editable = new Boolean(edi.getTextTrim()).booleanValue();
        }

        if (isRoot()) { // rootだけは少しヘンテコな処理が必要
            name = new File(rootDir).getName();
            Element en = element.getChild("name");
            if (en == null) {
                en = new Element("name");
            }
            en.setText(MyElement.encode((name)));
            logger.debug("root name: " + name);

            projdir = rootDir;
            Element dir = element.getChild("directory");
            if (dir == null) {
                dir = new Element("directory");
                element.addContent(dir);
            }
            dir.setText(MyElement.encode(projdir));
            if (date == null || date.trim().length() == 0) {
                date = Calendar.getInstance().getTime().toString();
                Element de = element.getChild("date");
                if (de == null) {
                    de = new Element("date");
                    element.addContent(de);
                }
                de.setText(MyElement.encode(date));
            }
        }

        Element associated = element.getChild("associated_directory");
        if (associated != null) {
            Attribute attr = associated.getAttribute("type");
            if (attr != null && attr.getValue().trim().equals("parent")) {
                parentDir = MyElement.decode(associated.getTextTrim());
            }
        }
        String hasSubs = element.getAttributeValue("hassub");

        boolean bhasSubs = false;
        if (hasSubs != null) {
            bhasSubs = new Boolean(hasSubs).booleanValue();
        }
        if (bhasSubs) {
            String subident = element.getAttributeValue("subident");
            if (subident == null) {
                logger.error("invalid `subident' attribute");
            }
            List list = element.getChildren(subident);
            if (list != null) {
                for (int i = 0; i < list.size(); i++) {
                    subprojects.addElement(new ProjectInfo((Element) list
                            .get(i), readProjectPropertyDoc));
                }
            }
        }

        if (infoType == SUB && readProjectPropertyDoc) {
            // String chaseXML = getProjectDirectory() +
            // System.getProperty("file.separator") + "phase-viewer.xml";
            // File filechaseXML = new File(chaseXML);
            // Document doc = null;
            // if ( filechaseXML.exists() ) {
            // doc =
            // ciss.phase_viewer.jdom.XMLUtils.getDocumentFromFile(filechaseXML);
            // }
            // if ( doc == null ) {
            // java.net.URL defchaseXML =
            // getClass().getResource("/ciss/phase_viewer/projectbrowser/phase-viewer.xml");
            // doc =
            // ciss.phase_viewer.jdom.XMLUtils.getDocumentFromURL(defchaseXML);
            // String projchaseXML =
            // getProjectDirectory()+System.getProperty("file.separator")+"phase-viewer.xml";
            // ciss.phase_viewer.jdom.XMLUtils.saveDocumentTo(doc,new
            // File(projchaseXML));
            // }
            // setProjectPropertyDocument(doc);
        }
    }

    /**
     * このプロジェクトは「編集不可」な場合falseを返す. たとえば擬ポテンシャルディレクトリーの下に他のプロジェクトを作ったりは許したくない.
     */
    public boolean isEditable() {
        return editable;
    }

    public boolean isRoot() {
        return projectType.equals("root");
    }

    /**
     * GUIのインスタンスを取っておきたいときに使うと良い. さしあたり, 一クラス一つ登録できる仕組み;
     * プロジェクトの中のみで通用するシングルトンのような扱い.
     * 
     * @param component
     *            取っておきたいコンポーネント
     */
    public void registerGUI(Object component) {
        guiComponents.put(component.getClass().getName(), component);
    }

    private HashMap objMap = new HashMap();

    /**
     * キーを元にオブジェクトを登録; HashMapのラッパー
     * 
     * @param key
     *            キー
     * @param obj
     *            登録したいオブジェクト
     */
    public void put(Object key, Object obj) {
        objMap.put(key, obj);
    }

    /**
     * キーを元にオブジェクトをゲット; HashMapのラッパー
     * 
     * @param key
     *            キー
     */
    public Object get(Object key) {
        return objMap.get(key);
    }

    /**
     * 保持するsubproject情報を削除する.
     * 
     * @param info
     *            削除したいsubproject情報.
     */
    public void removeSubProjectInfo(ProjectInfo info) {
        if (this.infoType == BASE) {
            subprojects.remove(info);
        }
    }

    /**
     * 外部ファイルに, 保存できる情報は保存する. そのパスは, projdir/phase-viewer.xmlである.
     */
    public void saveProjectPropertyDocument() {
        String chaseXML = getProjectDirectory()
                + System.getProperty("file.separator") + "phase-viewer.xml";
        ciss.phase_viewer.jdom.XMLUtils.saveDocumentTo(doc, chaseXML);
        setProjectPropertyDocument(doc);
    }

    /**
     * 計算プログラムが利用する入出力ファイルに関わる情報を保持する, ChaseFileManagerオブジェクトを セットする.
     */
    public void setChaseFileManager(ChaseFileManager chaseFileManager) {
        this.chaseFileManager = chaseFileManager;
        if (chaseFileManager == null) {
            logger.error("ChaseFileManager for this project seems to be null...");
        } else {
            chaseFileManager.setProjectInfo(this);
        }
        changed();
    }

    /**
     * プロジェクトに付随するコメントをセット.
     * 
     * @param comment
     *            コメント
     */
    public void setComment(String comment) {
        this.comment = comment;
        changed();
    }

    /**
     * ジョブ制御用の, デフォルトスクリプトを作成するオブジェクトをセット.
     * 
     * @param defaultScriptCreator
     *            デフォルトのスクリプトを作成するオブジェクト.
     */
    public void setDefaultScriptCreator(
            DefaultScriptCreator defaultScriptCreator) {
        this.defaultScriptCreator = defaultScriptCreator;
        changed();
    }

    /**
     * 現在のプロジェクトが関連付けられているホスト情報をセット
     * 
     * @param hostInfo
     *            現在のプロジェクトのターゲットホスト
     */
    public void setHostInfo(HostInfo hostInfo) {
        this.hostInfo = hostInfo;
        setTargetDirectory(hostInfo);
        changed();
    }

    /**
     * プロジェクトブラウザーの初期サイズをセット.
     * 
     * @param プロジェクトブラウザーの初期サイズ
     */
    public void setInitialBrowserSize(Dimension dim) {
        this.initBrowserSize = dim;
        changed();
    }

    /**
     * InputInterfaceオブジェクトを格納した, DataManagerオブジェクトへの参照をセットする.
     * DataManagerオブジェクトは, 主にディスク上のファイルの読み込み/書き出しなどを行う.
     * InputInterfaceの初期化などにおいては必要なので, このクラスでもその参照を定義する.
     * 
     * @param dm
     *            このクラスに関連付けたいDataManagerオブジェクトへの参照.
     */
    public void setInputDataManager(DataManager dm) {
        this.dm = dm;
        changed();
    }

    /**
     * 入力ファイルを処理するクラスである, InputInterfaceオブジェクトをこのクラスに関連付けるメソッド.
     * ここでセットされた情報を手繰って入力ファイルの情報を様々なオブジェクトが取得する.
     * 
     * @param inputInterface
     *            このクラスに関連付けられたInputInterfaceオブジェクト.
     */
    public void setInputInterface(InputInterface inputInterface) {
        this.inputInterface = inputInterface;
        changed();
    }

    /**
     * 出力データを統一的に扱うためのOutputInterfaceオブジェクトへの参照をセットする.
     * 
     * @param outputInterface
     *            このプロジェクトに関連付けられているOutputInterfaceオブジェクトへの参照.
     */
    public void setOutputInterface(OutputInterface outputInterface) {
        this.outputInterface = outputInterface;
        outputInterface.setProjectInfo(this);
        changed();
    }

    /**
     * ProjectBrowserへの参照をセットする. このオブジェクトがこのクラスのインスタンスから情報を 取得し,
     * 様々な表示やアクションが決定されるようになっている.
     * 
     * @param parent
     *            セットしたいProjectBrowserオブジェクトへの参照.
     */
    public void setParent(ProjectBrowser parent) {
        this.parent = parent;
        changed();
    }

    /**
     * このプロジェクトの「親ディレクトリー」をセットする。ただしすでにセット済みな場合何もしない。
     * 
     * @param parentDir
     *            このプロジェクトの「親ディレクトリー」
     */
    public void setParentDir(String parentDir) {
        if (this.parentDir != null) {
            return;
        }
        this.parentDir = parentDir;
        changed();
    }

    /**
     * プロジェクトの説明などが記述してあるHTMLファイルへのURLをセットする.
     * 
     * @param projectdescriptionURL
     *            プロジェクトに纏わる情報が記述してあるHTMLファイル
     */
    public void setProjectDescriptionURL(URL projectdescriptionURL) {
        this.projectdescriptionURL = projectdescriptionURL;
        changed();
    }

    /**
     * このプロジェクトのベースとなるディレクトリーへのパスをセット. 使用には注意を払うこと.
     * 
     * @param projdir
     *            プロジェクトのベースとなるディレクトリー
     */
    public void setProjectDirectory(String projdir) {
        this.projdir = projdir;
        this.name = new File(projdir).getName();
        setTargetDirectory(hostInfo);
        Element nam = element.getChild("name");
        if (nam != null) {
            nam.setText(MyElement.encode(name));
        } else {
            Element ele = new Element("name").setText(MyElement.encode(name));
            element.addContent(ele);
        }
        changed();
    }

    /**
     * プロジェクト名を明示的にセット. 使用には注意を払うこと.
     * 
     * @param name
     *            プロジェクトの名前
     */
    public void setProjectName(String name) {
        this.name = name;
        changed();
    }

    /**
     * 外部ファイルにプロジェクト情報を書き出す際に必要なDocumentオブジェクトへの参照をセットする.
     * 
     * @param doc
     *            このクラスに関連付けたいDocumentオブジェクト. ProjectInfoのディスクへの書き出しを
     *            行ったりするのに必要.
     */
    public void setProjectPropertyDocument(Document doc) {
        this.doc = doc;
        // if ( doc != null ) {
        // Element element = doc.getRootElement();
        // String comm = MyElement.decode(element.getChildTextTrim("comment"));
        // if ( comm != null && comm.length() != 0 ) {
        // setComment(comm);
        // }
        // }
        changed();
    }

    /**
     * 標準出力を表示するGUIのClassを設定する.
     * 
     * @param clazz
     *            上記の通り
     */
    public void setStdoutGUIs(Class[] clazz) {
        this.stdoutGUIs = clazz;
        changed();
    }

    /**
     * 引数で与えられたホスト情報からリモートのターゲットディレクトリーを作成. localホストの場合ProjectDirectoryと同じ.
     * 
     * @param hostInfo
     *            ホスト情報
     */
    public void setTargetDirectory(HostInfo hostInfo) {
        this.hostInfo = hostInfo;
        String baseDir = hostInfo.getProperty("basedir");
        if (baseDir == null || baseDir.trim().length() == 0) {
            baseDir = "$HOME";
        }
        if (hostInfo.getName().equals("localhost")) {
            targetDir = new String(getProjectDirectory());
        } else {
            String dir = getPathFromElement("/");
            targetDir = baseDir + "/" + dir; // we assume that a remote target
                                             // directory's file separator is
                                             // '/'
        }
        logger.debug("set targetdir: " + targetDir);
        changed();
    }

    public String toString() {
        String ls = System.getProperty("line.separator");
        String retValue = "project info: " + ls;
        if (infoType == BASE) {
            retValue += " project info type: base" + ls;
        } else if (infoType == SUB) {
            retValue += " project info type: sub" + ls;
        }
        retValue += "name: " + name + " date: " + date + " type: "
                + projectType + " dir: " + projdir;
        return retValue;
    }

    /**
     * Elementに変更があってそれを反映させる場合に呼ぶ.
     */
    public void update() {
        init();
        changed();
    }

    private Vector listeners;

    /**
     * ProjectInfoの変更を通知してほしいListenerを登録する.
     * 
     * @param 登録したいリスナー
     */
    public void addProjectInfoListener(ProjectInfoListener listener) {
        if (listeners == null)
            listeners = new Vector();
        listeners.add(listener);
    }

    /**
     * 登録されたリスナーの登録を解除.
     * 
     * @param listener
     *            解除したいリスナー
     */
    public void removeProjectInfoListener(ProjectInfoListener listener) {
        if (listeners == null)
            return;
        listeners.remove(listener);
    }

    private boolean callingListener = false;

    private void changed() {
        if (listeners == null || callingListener)
            return;
        callingListener = true;
        ProjectInfoChangeEvent ev = new ProjectInfoChangeEvent(this);
        for (int i = 0; i < listeners.size(); i++)
            ((ProjectInfoListener) listeners.get(i)).projectInfoChanged(ev);
        callingListener = false;
    }

}
