1 package charactermanaj.model.io;
4 import java.awt.image.BufferedImage;
5 import java.io.ByteArrayOutputStream;
7 import java.io.FileFilter;
8 import java.io.FileInputStream;
9 import java.io.FileOutputStream;
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.io.OutputStream;
15 import java.text.SimpleDateFormat;
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.Date;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.NoSuchElementException;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.ExecutorService;
24 import java.util.concurrent.Executors;
25 import java.util.concurrent.Future;
26 import java.util.concurrent.TimeUnit;
27 import java.util.concurrent.TimeoutException;
28 import java.util.concurrent.atomic.AtomicBoolean;
29 import java.util.logging.Level;
30 import java.util.logging.Logger;
32 import org.w3c.dom.Node;
33 import org.w3c.dom.NodeList;
35 import charactermanaj.graphics.io.FileImageResource;
36 import charactermanaj.graphics.io.ImageLoader;
37 import charactermanaj.graphics.io.ImageSaveHelper;
38 import charactermanaj.graphics.io.LoadedImage;
39 import charactermanaj.model.AppConfig;
40 import charactermanaj.model.CharacterData;
41 import charactermanaj.model.Layer;
42 import charactermanaj.model.PartsCategory;
43 import charactermanaj.model.io.CharacterDataDefaultProvider.DefaultCharacterDataVersion;
44 import charactermanaj.util.DirectoryConfig;
45 import charactermanaj.util.FileNameNormalizer;
46 import charactermanaj.util.FileUserData;
47 import charactermanaj.util.UserData;
49 public class CharacterDataPersistent {
54 public static final String CONFIG_FILE = "character.xml";
57 * キャラクターなんとか機用のiniファイル名
59 public static final String COMPATIBLE_CONFIG_NAME = "character.ini";
64 private static final String FAVORITES_FILE_NAME = "favorites.xml";
69 private static final String SAMPLE_IMAGE_FILENAME = "preview.png";
74 private static final Logger logger = Logger
75 .getLogger(CharacterDataPersistent.class.getName());
78 * キャラクターデータを格納したXMLのリーダー
80 private final CharacterDataXMLReader characterDataXmlReader = new CharacterDataXMLReader();
83 * キャラクターデータを格納したXMLのライタ
85 private final CharacterDataXMLWriter characterDataXmlWriter = new CharacterDataXMLWriter();
88 * プロファイルの列挙時のエラーハンドラ.<br>
92 public interface ProfileListErrorHandler {
102 void occureException(File baseDir, Throwable ex);
107 * シングルトン実装であるため、一度だけ呼び出される.
109 private CharacterDataPersistent() {
116 private static final CharacterDataPersistent singleton = new CharacterDataPersistent();
123 public static CharacterDataPersistent getInstance() {
128 * キャラクターデータを新規に保存する.<br>
129 * REVがnullである場合は保存に先立ってランダムにREVが設定される.<br>
130 * 保存先ディレクトリはユーザー固有のキャラクターデータ保存先のディレクトリにキャラクター定義のIDを基本とする ディレクトリを作成して保存される.<br>
131 * ただし、そのディレクトリがすでに存在する場合はランダムな名前で決定される.<br>
132 * 実際のxmlの保存先にあわせてDocBaseが設定されて返される.<br>
134 * @param characterData
135 * キャラクターデータ (IDは設定済みであること.それ以外はvalid, editableであること。)
136 * @throws IOException
139 public void createProfile(CharacterData characterData) throws IOException {
140 if (characterData == null) {
141 throw new IllegalArgumentException();
144 String id = characterData.getId();
145 if (id == null || id.trim().length() == 0) {
146 throw new IOException("missing character-id:" + characterData);
149 // ユーザー個別のキャラクターデータ保存先ディレクトリを取得
150 DirectoryConfig dirConfig = DirectoryConfig.getInstance();
151 File charactersDir = dirConfig.getCharactersDir();
152 if (!charactersDir.exists()) {
153 if (!charactersDir.mkdirs()) {
154 throw new IOException("can't create the characters directory. "
158 if (logger.isLoggable(Level.FINE)) {
159 logger.log(Level.FINE, "check characters-dir: " + charactersDir
160 + ": exists=" + charactersDir.exists());
164 // 同じ名前のディレクトリがある場合は日付+連番をつけて衝突を回避する
167 String name = characterData.getName();
169 // 表示名が定義されていなければIDで代用する.(IDは必須)
170 name = characterData.getId();
172 for (int retry = 0;; retry++) {
173 baseDir = new File(charactersDir, name + suffix);
174 if (!baseDir.exists()) {
178 throw new IOException("character directory conflict.:"
182 suffix = generateSuffix(retry);
184 if (!baseDir.exists()) {
185 if (!baseDir.mkdirs()) {
186 throw new IOException("can't create directory. " + baseDir);
188 logger.log(Level.INFO, "create character-dir: " + baseDir);
192 File characterPropXML = new File(baseDir, CONFIG_FILE);
193 if (characterPropXML.exists() && !characterPropXML.isFile()) {
194 throw new IOException(CONFIG_FILE + " is not a regular file.:"
197 if (characterPropXML.exists() && !characterPropXML.canWrite()) {
198 throw new IOException("character.xml is not writable.:"
203 URI docBase = characterPropXML.toURI();
204 characterData.setDocBase(docBase);
206 // リビジョンが指定されてなければ新規にリビジョンを割り当てる。
207 if (characterData.getRev() == null) {
208 characterData.setRev(generateRev());
212 saveCharacterDataToXML(characterData);
215 preparePartsDir(characterData);
223 public String generateRev() {
224 SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd_HHmmss");
225 return fmt.format(new Date());
235 protected String generateSuffix(int retryCount) {
236 SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd_HHmmss");
237 String suffix = "_" + fmt.format(new Date());
238 if (retryCount > 0) {
239 suffix = suffix + "_" + retryCount;
247 * @param characterData
248 * キャラクターデータ(有効かつ編集可能であること)
249 * @throws IOException
252 public void updateProfile(CharacterData characterData) throws IOException {
253 if (characterData == null) {
254 throw new IllegalArgumentException();
257 characterData.checkWritable();
258 if (!characterData.isValid()) {
259 throw new IOException("invalid profile: " + characterData);
263 saveCharacterDataToXML(characterData);
266 preparePartsDir(characterData);
270 * キャラクターデータのパーツイメージを保存するディレクトリを準備する
272 * @param characterData
276 * @throws IOException
279 protected void preparePartsDir(CharacterData characterData)
281 if (characterData == null) {
282 throw new IllegalArgumentException();
285 characterData.checkWritable();
286 if (!characterData.isValid()) {
287 throw new IOException("invalid profile: " + characterData);
290 URI docBase = characterData.getDocBase();
291 if (!"file".equals(docBase.getScheme())) {
292 throw new IOException("ファイル以外はサポートしていません。:" + docBase);
294 File docBaseFile = new File(docBase);
295 File baseDir = docBaseFile.getParentFile();
297 if (!baseDir.exists()) {
298 if (!baseDir.mkdirs()) {
299 throw new IOException("can't create directory. " + baseDir);
302 for (PartsCategory partsCategory : characterData.getPartsCategories()) {
303 for (Layer layer : partsCategory.getLayers()) {
304 File layerDir = new File(baseDir, layer.getDir());
305 if (!layerDir.exists()) {
306 if (!layerDir.mkdirs()) {
307 throw new IOException("can't create directory. "
316 * キャラクターデータを読み込んだ場合に返されるコールバック
318 public interface ListProfileCallback {
321 * キャラクターデータを読み込んだ場合.<br>
322 * 戻り値がfalseの場合は読み込みを以降の読み込みを中断します.<br>
323 * (ただし、すでに読み込み開始している分については中断されません.)
325 * @param characterData
326 * @return 継続する場合はtrue、中止する場合はfalse
328 boolean receiveCharacterData(CharacterData characterData);
331 * キャラクターデータの読み込みに失敗した場合.<br>
332 * 戻り値がfalseの場合は読み込みを以降の読み込みを中断します.<br>
333 * (ただし、すでに読み込み開始している分については中断されません.)
339 * @return 継続する場合はtrue、中止する場合はfalse
341 boolean occureException(File dir, Exception ex);
345 * 指定したディレクトリの下のサブフォルダに、character.iniがある場合は、
346 * キャラクターなんとか機のディレクトリとして、標準のcharacter.xmlを生成する.<br>
347 * ただし、書き込み禁止の場合は何もしない.<br>
348 * すでにcharacter.xmlがある場合も何もしない.<br>
351 * キャラクターなんとか機のデータディレクトリ
353 public void convertCharacterNantokaIniToXml(File dataDir) {
354 if (dataDir == null || !dataDir.isDirectory() || !dataDir.canWrite()) {
358 File[] dirs = dataDir.listFiles();
362 for (File dir : dirs) {
363 if (!dir.isDirectory()) {
366 File characterXmlFile = new File(dir,
367 CharacterDataPersistent.CONFIG_FILE);
368 if (characterXmlFile.exists()) {
369 // すでにキャラクター定義XMLが存在する場合はスキップする.
373 File characterIniFile = new File(dir,
374 CharacterDataPersistent.COMPATIBLE_CONFIG_NAME);
375 if (characterIniFile.exists() && characterIniFile.canWrite()
377 // character.iniが存在し、書き込み可能であれば、それをcharacter.xmlに変換する.
379 // eye_colorフォルダがあるか?
380 File eyeColorFolder = new File(dir, "eye_color");
381 boolean hasEyeColorFolder = eyeColorFolder.exists()
382 && eyeColorFolder.isDirectory();
384 DefaultCharacterDataVersion version;
385 if (hasEyeColorFolder) {
386 // eye_colorフォルダがあればver3形式とみなす.
387 version = DefaultCharacterDataVersion.V3;
389 version = DefaultCharacterDataVersion.V2;
393 String readme = null;
394 File readmeFile = new File(dir, "readme.txt");
395 if (readmeFile.exists() && readmeFile.canRead()) {
397 readme = TextReadHelper
398 .readTextTryEncoding(new FileInputStream(
401 } catch (IOException ex) {
402 logger.log(Level.WARNING, ex.toString(), ex);
407 convertFromCharacterIni(characterIniFile, characterXmlFile,
410 } catch (Exception ex) {
411 logger.log(Level.WARNING, "character.xmlの生成に失敗しました。:"
412 + characterXmlFile, ex);
419 * キャラクターデータを非同期に読み込む.<br>
420 * 読み込み完了したものが随時、コールバックに渡される.
423 * @return すべての読み込みが完了したか判定し待機することのできるFuture
425 public Future<?> listProfileAsync(final ListProfileCallback callback) {
426 if (callback == null) {
427 throw new IllegalArgumentException();
430 // キャラクターデータが格納されている親ディレクトリ
431 DirectoryConfig dirConfig = DirectoryConfig.getInstance();
432 File baseDir = dirConfig.getCharactersDir();
434 // キャラクターなんとか機のcharacter.iniがあれば、character.xmlに変換する.
435 convertCharacterNantokaIniToXml(baseDir);
438 FileNameNormalizer normalizer = FileNameNormalizer.getDefault();
441 final AtomicBoolean cancelled = new AtomicBoolean(false);
443 // 有効な論理CPU(CORE)数のスレッドで同時実行させる
444 int numOfProcessors = Runtime.getRuntime().availableProcessors();
445 final ExecutorService executorSrv = Executors
446 .newFixedThreadPool(numOfProcessors);
448 // キャラクターデータ対象ディレクトリを列挙し、並列に解析する
449 File[] dirs = baseDir.listFiles(new FileFilter() {
450 public boolean accept(File pathname) {
451 boolean accept = pathname.isDirectory()
452 && !pathname.getName().startsWith(".");
454 File configFile = new File(pathname, CONFIG_FILE);
455 accept = configFile.exists() && configFile.canRead();
463 for (File dir : dirs) {
464 String path = normalizer.normalize(dir.getPath());
465 final File normDir = new File(path);
467 executorSrv.submit(new Runnable() {
469 boolean terminate = false;
470 File characterDataXml = new File(normDir, CONFIG_FILE);
471 if (characterDataXml.exists()) {
473 File docBaseFile = new File(normDir,
475 URI docBase = docBaseFile.toURI();
476 CharacterData characterData = loadProfile(docBase);
477 terminate = !callback
478 .receiveCharacterData(characterData);
480 } catch (Exception ex) {
481 terminate = !callback.occureException(normDir,
486 // 中止が指示されたらスレッドプールを終了する
487 logger.log(Level.FINE, "shutdownNow listProfile");
488 executorSrv.shutdownNow();
495 // タスクの登録を受付終了し、現在のすべてのタスクが完了したらスレッドは終了する.
496 executorSrv.shutdown();
499 // タスクの終了を待機できる疑似フューチャーを作成する.
500 Future<Object> awaiter = new Future<Object>() {
501 public boolean cancel(boolean mayInterruptIfRunning) {
502 if (executorSrv.isTerminated()) {
506 executorSrv.shutdownNow();
511 public boolean isCancelled() {
512 return cancelled.get();
515 public boolean isDone() {
516 return executorSrv.isTerminated();
519 public Object get() throws InterruptedException, ExecutionException {
521 return get(300, TimeUnit.SECONDS);
523 } catch (TimeoutException ex) {
524 throw new ExecutionException(ex);
528 public Object get(long timeout, TimeUnit unit)
529 throws InterruptedException, ExecutionException,
531 executorSrv.shutdown();
532 if (!executorSrv.isTerminated()) {
533 executorSrv.awaitTermination(timeout, unit);
544 * 読み取りに失敗した場合はエラーハンドラに通知されるが例外は返されない.<br>
545 * 一つも正常なプロファイルがない場合は空のリストが返される.<br>
546 * エラーハンドラの通知は非同期に行われる.
548 * @param errorHandler
550 * @return プロファイルのリスト(表示名順)、もしくは空
552 public List<CharacterData> listProfiles(
553 final ProfileListErrorHandler errorHandler) {
555 final List<CharacterData> profiles = new ArrayList<CharacterData>();
557 Future<?> awaiter = listProfileAsync(new ListProfileCallback() {
559 public boolean receiveCharacterData(CharacterData characterData) {
560 synchronized (profiles) {
561 profiles.add(characterData);
566 public boolean occureException(File dir, Exception ex) {
567 if (errorHandler != null) {
568 errorHandler.occureException(dir, ex);
574 // すべてのキャラクターデータが読み込まれるまで待機する.
578 } catch (Exception ex) {
579 logger.log(Level.WARNING, "listProfile abort.", ex);
582 Collections.sort(profiles, CharacterData.SORT_DISPLAYNAME);
584 return Collections.unmodifiableList(profiles);
587 public CharacterData loadProfile(URI docBase) throws IOException {
588 if (docBase == null) {
589 throw new IllegalArgumentException();
593 CharacterData characterData = characterDataXmlReader
594 .loadCharacterDataFromXML(docBase);
596 return characterData;
599 protected void saveCharacterDataToXML(CharacterData characterData)
601 if (characterData == null) {
602 throw new IllegalArgumentException();
605 characterData.checkWritable();
606 if (!characterData.isValid()) {
607 throw new IOException("invalid profile: " + characterData);
610 URI docBase = characterData.getDocBase();
611 if (!"file".equals(docBase.getScheme())) {
612 throw new IOException("ファイル以外はサポートしていません: " + docBase);
616 ByteArrayOutputStream bos = new ByteArrayOutputStream();
618 characterDataXmlWriter.writeXMLCharacterData(characterData, bos);
624 File characterPropXML = new File(docBase);
625 File baseDir = characterPropXML.getParentFile();
626 if (!baseDir.exists()) {
627 if (!baseDir.mkdirs()) {
628 logger.log(Level.WARNING, "can't create directory. " + baseDir);
632 FileOutputStream fos = new FileOutputStream(characterPropXML);
634 fos.write(bos.toByteArray());
640 public void saveFavorites(CharacterData characterData) throws IOException {
641 if (characterData == null) {
642 throw new IllegalArgumentException();
646 UserData favoritesData = getFavoritesUserData(characterData);
647 OutputStream os = favoritesData.getOutputStream();
649 characterDataXmlWriter.saveFavorites(characterData, os);
656 private UserData getFavoritesUserData(CharacterData characterData) {
657 if (characterData == null) {
658 throw new IllegalArgumentException();
661 // xml形式の場合、キャラクターディレクトリ上に設定する.
662 URI docBase = characterData.getDocBase();
663 File characterDir = new File(docBase).getParentFile();
664 return new FileUserData(new File(characterDir, FAVORITES_FILE_NAME));
669 * お気に入り(Favorites)を読み込む.<br>
670 * 現在のパーツセットに追加する形で読み込まれ、同じパーツセットIDのものは上書きされます.<br>
672 * @param characterData
674 * @throws IOException
677 public void loadFavorites(CharacterData characterData) throws IOException {
678 if (characterData == null) {
679 throw new IllegalArgumentException();
682 UserData favoritesXml = getFavoritesUserData(characterData);
683 if (favoritesXml.exists() && favoritesXml.length() > 0) {
684 InputStream is = favoritesXml.openStream();
686 characterDataXmlReader.loadPartsSet(characterData, is);
696 * 既存のキャラクター定義を削除する.<br>
697 * 有効なdocBaseがあり、そのxmlファイルが存在するものについて、削除を行う.<br>
698 * forceRemoveがtrueでない場合はキャラクター定義 character.xmlファイルの拡張子を
699 * リネームすることでキャラクター定義として認識させなくする.<br>
700 * forceRevmoeがtrueの場合は実際にファイルを削除する.<br>
701 * character.xml、favorites、workingsetのキャッシュも削除される.<br>
706 * ファイルを削除する場合はtrue、リネームして無効にするだけならfalse
707 * @throws IOException
710 public void remove(CharacterData cd, boolean forceRemove)
712 if (cd == null || cd.getDocBase() == null) {
713 throw new IllegalArgumentException();
716 URI docBase = cd.getDocBase();
717 File xmlFile = new File(docBase);
718 if (!xmlFile.exists() || !xmlFile.isFile()) {
725 UserData[] favoritesDatas = new UserData[]{getFavoritesUserData(cd)};
726 for (UserData favoriteData : favoritesDatas) {
727 if (favoriteData != null && favoriteData.exists()) {
728 logger.log(Level.INFO, "remove file: " + favoriteData);
729 favoriteData.delete();
735 // XML形式でのワーキングセットの保存
736 WorkingSetPersist workingSetPersist = WorkingSetPersist.getInstance();
737 workingSetPersist.removeWorkingSet(cd);
739 // xmlファイルの拡張子を変更することでキャラクター定義として認識させない.
740 // (削除に失敗するケースに備えて先にリネームする.)
741 String suffix = "." + System.currentTimeMillis() + ".deleted";
742 File bakFile = new File(xmlFile.getPath() + suffix);
743 if (!xmlFile.renameTo(bakFile)) {
744 throw new IOException("can not rename configuration file.:"
749 File baseDir = xmlFile.getParentFile();
752 // 削除されたディレクトリであることを識別できるようにディレクトリ名も変更する.
753 File parentBak = new File(baseDir.getPath() + suffix);
754 if (!baseDir.renameTo(parentBak)) {
755 throw new IOException("can't rename directory. " + baseDir);
760 removeRecursive(baseDir);
765 * 指定したファイルを削除します.<br>
766 * 指定したファイルがディレクトリを示す場合、このディレクトリを含む配下のすべてのファイルとディレクトリを削除します.<br>
770 * @throws IOException
773 protected void removeRecursive(File file) throws IOException {
775 throw new IllegalArgumentException();
777 if (!file.exists()) {
780 if (file.isDirectory()) {
781 File[] children = file.listFiles();
782 if (children != null) {
783 for (File child : children) {
784 removeRecursive(child);
788 if (!file.delete()) {
789 throw new IOException("can't delete file. " + file);
793 protected Iterable<Node> iterable(final NodeList nodeList) {
795 if (nodeList == null) {
798 mx = nodeList.getLength();
800 return new Iterable<Node>() {
801 public Iterator<Node> iterator() {
802 return new Iterator<Node>() {
804 public boolean hasNext() {
809 throw new NoSuchElementException();
811 return nodeList.item(idx++);
813 public void remove() {
814 throw new UnsupportedOperationException();
821 protected URL getEmbeddedResourceURL(String schemaName) {
822 return this.getClass().getResource(schemaName);
827 * ピクチャが存在しなければnullを返す. キャラクター定義がValidでない場合は常にnullを返す.<br>
829 * @param characterData
833 * @return ピクチャのイメージ、もしくはnull
834 * @throws IOException
837 public BufferedImage loadSamplePicture(CharacterData characterData,
838 ImageLoader loader) throws IOException {
839 if (characterData == null || loader == null) {
840 throw new IllegalArgumentException();
842 if (!characterData.isValid()) {
846 File sampleImageFile = getSamplePictureFile(characterData);
847 if (sampleImageFile != null && sampleImageFile.exists()) {
848 LoadedImage loadedImage = loader.load(new FileImageResource(
850 return loadedImage.getImage();
856 * キャラクターのサンプルピクチャが登録可能であるか?<br>
857 * キャラクターデータが有効であり、且つ、ファイルの書き込みが可能であればtrueを返す.<br>
858 * キャラクターデータがnullもしくは無効であるか、ファイルプロトコルでないか、ファイルが書き込み禁止であればfalseょ返す.<br>
860 * @param characterData
862 * @return 書き込み可能であればtrue、そうでなければfalse
864 public boolean canSaveSamplePicture(CharacterData characterData) {
865 if (characterData == null || !characterData.isValid()) {
868 File sampleImageFile = getSamplePictureFile(characterData);
869 if (sampleImageFile != null) {
870 if (sampleImageFile.exists() && sampleImageFile.canWrite()) {
873 if (!sampleImageFile.exists()) {
874 File parentDir = sampleImageFile.getParentFile();
875 if (parentDir != null) {
876 return parentDir.canWrite();
884 * サンプルピクチャとして認識されるファイル位置を返す.<br>
885 * ファイルが実在するかは問わない.<br>
886 * DocBaseが未設定であるか、ファィルプロトコルとして返せない場合はnullを返す.<br>
888 * @param characterData
890 * @return サンプルピクチャの保存先のファイル位置、もしくはnull
892 protected File getSamplePictureFile(CharacterData characterData) {
893 if (characterData == null) {
894 throw new IllegalArgumentException();
896 URI docBase = characterData.getDocBase();
897 if (docBase != null && "file".endsWith(docBase.getScheme())) {
898 File docBaseFile = new File(docBase);
899 return new File(docBaseFile.getParentFile(), SAMPLE_IMAGE_FILENAME);
907 * @param characterData
909 * @param samplePicture
911 * @throws IOException
914 public void saveSamplePicture(CharacterData characterData,
915 BufferedImage samplePicture) throws IOException {
916 if (!canSaveSamplePicture(characterData)) {
917 throw new IOException("can not write a sample picture.:"
920 File sampleImageFile = getSamplePictureFile(characterData); // canSaveSamplePictureで書き込み先検証済み
922 if (samplePicture != null) {
925 // pngで保存するので背景色は透過になるが、一応、コードとしては入れておく。
926 AppConfig appConfig = AppConfig.getInstance();
927 Color sampleImageBgColor = appConfig.getSampleImageBgColor();
929 ImageSaveHelper imageSaveHelper = new ImageSaveHelper();
930 imageSaveHelper.savePicture(samplePicture, sampleImageBgColor,
931 sampleImageFile, null);
935 if (sampleImageFile.exists()) {
936 if (!sampleImageFile.delete()) {
937 throw new IOException("sample pucture delete failed. :"
948 * character.iniを読み取り、character.xmlを生成します.<br>
949 * すでにcharacter.xmlがある場合は上書きされます.<br>
950 * 途中でエラーが発生した場合はcharacter.xmlは削除されます.<br>
952 * @param characterIniFile
953 * 読み取るcharatcer.iniファイル
954 * @param characterXmlFile
955 * 書き込まれるcharacter.xmlファイル
957 * デフォルトキャラクターセットのバージョン
960 * @throws IOException
963 public void convertFromCharacterIni(File characterIniFile,
964 File characterXmlFile, DefaultCharacterDataVersion version,
967 if (characterIniFile == null || characterXmlFile == null
968 || version == null) {
969 throw new IllegalArgumentException();
972 // character.iniから、character.xmlの内容を構築する.
973 FileInputStream is = new FileInputStream(characterIniFile);
974 CharacterData characterData;
976 CharacterDataIniReader iniReader = new CharacterDataIniReader();
977 characterData = iniReader.readCharacterDataFromIni(is, version);
984 if (description != null) {
985 characterData.setDescription(description);
989 URI docBase = characterXmlFile.toURI();
990 characterData.setDocBase(docBase);
992 // character.xmlの書き込み
993 boolean succeeded = false;
995 FileOutputStream outstm = new FileOutputStream(characterXmlFile);
997 characterDataXmlWriter.writeXMLCharacterData(characterData,
1007 // 途中で失敗した場合は生成ファイルを削除しておく.
1009 if (characterXmlFile.exists()) {
1010 characterXmlFile.delete();
1013 } catch (Exception ex) {
1014 logger.log(Level.WARNING, "ファイルの削除に失敗しました。:"
1015 + characterXmlFile, ex);
1022 * お勧めリンクリストが設定されていない場合(nullの場合)、デフォルトのお勧めリストを設定する.<br>
1023 * すでに設定されている場合(空を含む)は何もしない.<br>
1025 * おすすめリンクがサポートされてなかったころのデータは、おすすめリンク用のタグそのものが存在せずnullとなる.<br>
1026 * サポート後のデータでリンクを未設定にしている場合は、空のリストとなる.<br>
1027 * したがって、nullの場合のみ、おすすめリンクを補完する.<br>
1029 * @param characterData
1032 public void compensateRecommendationList(CharacterData characterData) {
1033 if (characterData == null) {
1034 throw new IllegalArgumentException();
1036 if (characterData.getRecommendationURLList() != null) {
1040 CharacterDataDefaultProvider defProv = new CharacterDataDefaultProvider();
1041 CharacterData defaultCd = defProv
1042 .createDefaultCharacterData(DefaultCharacterDataVersion.V3);
1043 characterData.setRecommendationURLList(defaultCd
1044 .getRecommendationURLList());