1 package charactermanaj.model;
\r
3 import java.awt.Dimension;
\r
5 import java.io.IOException;
\r
6 import java.io.Serializable;
\r
8 import java.util.ArrayList;
\r
9 import java.util.Arrays;
\r
10 import java.util.Collection;
\r
11 import java.util.Collections;
\r
12 import java.util.Comparator;
\r
13 import java.util.HashMap;
\r
14 import java.util.Iterator;
\r
15 import java.util.List;
\r
16 import java.util.Map;
\r
17 import java.util.Properties;
\r
18 import java.util.logging.Level;
\r
19 import java.util.logging.Logger;
\r
21 import charactermanaj.model.io.PartsDataLoader;
\r
27 public class CharacterData implements Serializable, PartsSpecResolver {
\r
32 private static final long serialVersionUID = -381763373314240953L;
\r
37 private static final Logger logger = Logger.getLogger(CharacterData.class.getName());
\r
41 * キャラクターデータを表示名順にソートするための比較器.<br>
\r
43 public static final Comparator<CharacterData> SORT_DISPLAYNAME = new Comparator<CharacterData>() {
\r
44 public int compare(CharacterData o1, CharacterData o2) {
\r
45 if (!o1.isValid() || !o2.isValid()) {
\r
46 return o1.isValid() == o2.isValid() ? 0 : o1.isValid() ? 1 : -1;
\r
48 int ret = o1.getName().compareTo(o2.getName());
\r
50 ret = o1.getId().compareTo(o2.getId());
\r
53 ret = o1.getDocBase().toString().compareTo(o2.getDocBase().toString());
\r
61 * キャラクターデータを定義しているXMLの位置.<br>
\r
62 * docBase自身はxml定義には含まれず、xmlをロードした位置を記憶するためにPersistentクラスによって設定される.<br>
\r
64 private URI docBase;
\r
69 * キャラクターデータの内部用ID.<br>
\r
70 * キャラクターデータの構造を判定するために用いる.<br>
\r
75 * キャラクターデータの更新番号.<br>
\r
76 * キャラクターデータの構造が変更されたことを識別するために用いる.<br>
\r
83 private String localizedName;
\r
88 private String author;
\r
93 private String description;
\r
98 private Dimension imageSize;
\r
103 private OrderedMap<String, PartsCategory> partsCategories = OrderedMap.emptyMap();
\r
108 private Properties properties = new Properties();
\r
113 * キーはプリセット自身のID、値はプリセット自身.<br>
\r
115 private HashMap<String, PartsSet> presets = new HashMap<String, PartsSet>();
\r
120 private String defaultPartsSetId;
\r
125 private OrderedMap<String, ColorGroup> colorGroups = OrderedMap.emptyMap();
\r
130 * Ver0.96以前には存在しないのでnullになり得る.
\r
132 private List<RecommendationURL> recommendationURLList;
\r
136 * (非シリアライズデータ、デシリアライズ時には新規インスタンスが作成される).<br>
\r
138 private transient PartsColorManager partsColorMrg = new PartsColorManager(this);
\r
142 * パーツをロードしたときに設定され、リロードするときに使用する.<br>
\r
143 * パーツを一度もロードしていない場合はnull.
\r
144 * (非シリアライズデータ、デシリアライズ時はnullのまま).<br>
\r
146 private transient PartsDataLoader partsDataLoader;
\r
150 * (キャラクターセットはパーツイメージをもったままシリアライズされることは想定していないが、可能ではある。)
\r
152 private Map<PartsCategory, Map<PartsIdentifier, PartsSpec>> images
\r
153 = new HashMap<PartsCategory, Map<PartsIdentifier, PartsSpec>>();
\r
159 * @param stream 出力先
\r
160 * @throws IOException 失敗
\r
162 private void writeObject(java.io.ObjectOutputStream stream) throws IOException {
\r
163 stream.defaultWriteObject();
\r
167 * 基本情報のみをコピーして返します.<br>
\r
168 * DocBase, ID, REV, Name, Author, Description, ImageSize、および, PartsCategory, ColorGroup, PartSetのコレクションがコピーされます.<br>
\r
169 * それ以外のものはコピーされません.<br>
\r
170 * @return 基本情報をコピーした新しいインスタンス
\r
172 public CharacterData duplicateBasicInfo() {
\r
173 return duplicateBasicInfo(true);
\r
177 * 基本情報のみをコピーして返します.<br>
\r
178 * DocBase, ID, REV, Name, Author, Description, ImageSize、および, PartsCategory, ColorGroupがコピーされます.<br>
\r
179 * 引数のneedPartsSetがtrueの場合は,PartSetのコレクションもコピーされます.<br>
\r
180 * ディレクトリの監視状態、お勧めリンクもコピーされます.<br>
\r
181 * それ以外のものはコピーされません.<br>
\r
182 * @param needPartsSets パーツセットもコピーする場合、falseの場合はパーツセットは空となります.
\r
183 * @return 基本情報をコピーした新しいインスタンス
\r
185 public CharacterData duplicateBasicInfo(boolean needPartsSets) {
\r
186 CharacterData cd = new CharacterData();
\r
189 cd.setRev(this.rev);
\r
190 cd.setDocBase(this.docBase);
\r
192 cd.setName(this.localizedName);
\r
194 cd.setAuthor(this.author);
\r
195 cd.setDescription(this.description);
\r
197 cd.setImageSize((Dimension)(this.imageSize == null ? null : this.imageSize.clone()));
\r
199 cd.setWatchDirectory(this.isWatchDirectory());
\r
201 ArrayList<RecommendationURL> recommendationURLList = null;
\r
202 if (this.recommendationURLList != null) {
\r
203 recommendationURLList = new ArrayList<RecommendationURL>();
\r
204 for (RecommendationURL recommendationUrl : this.recommendationURLList) {
\r
205 recommendationURLList.add(recommendationUrl.clone());
\r
208 cd.setRecommendationURLList(recommendationURLList);
\r
210 ArrayList<PartsCategory> partsCategories = new ArrayList<PartsCategory>();
\r
211 partsCategories.addAll(this.getPartsCategories());
\r
212 cd.setPartsCategories(partsCategories.toArray(new PartsCategory[partsCategories.size()]));
\r
214 ArrayList<ColorGroup> colorGroups = new ArrayList<ColorGroup>();
\r
215 colorGroups.addAll(this.getColorGroups());
\r
216 cd.setColorGroups(colorGroups);
\r
218 if (needPartsSets) {
\r
219 for (PartsSet partsSet : this.getPartsSets().values()) {
\r
220 cd.addPartsSet(partsSet.clone());
\r
222 cd.setDefaultPartsSetId(this.defaultPartsSetId);
\r
229 * キャラクターデータが同じ構造であるか?<br>
\r
230 * カラーグループ、カテゴリ、レイヤーの各情報が等しければtrue、それ以外はfalse.<br>
\r
231 * 上記以外の項目(コメントや作者、プリセット等)については判定しない.<br>
\r
232 * サイズ、カラーグループの表示名や順序、カテゴリの順序や表示名、
\r
233 * 複数アイテム可などの違いは構造の変更とみなさない.<br>
\r
234 * レイヤーはレイヤーID、重ね合わせ順、対象ディレクトリの3点が変更されている場合は構造の変更とみなす.<br>
\r
235 * いずれも個数そのものが変わっている場合は変更とみなす.<br>
\r
236 * 自分または相手がValidでなければ常にfalseを返す.<br>
\r
237 * @param other 比較対象, null可
\r
238 * @return 同じ構造であればtrue、そうでなければfalse
\r
240 public boolean isSameStructure(CharacterData other) {
\r
241 if (!this.isValid() || other == null || !other.isValid()) {
\r
242 // 自分または相手がinvalidであれば構造的には常に不一致と見なす.
\r
246 // カラーグループが等しいか? (順序は問わない)
\r
248 ArrayList<ColorGroup> colorGroup1 = new ArrayList<ColorGroup>(getColorGroups());
\r
249 ArrayList<ColorGroup> colorGroup2 = new ArrayList<ColorGroup>(other.getColorGroups());
\r
250 if (colorGroup1.size() != colorGroup2.size()) {
\r
253 if (!colorGroup1.containsAll(colorGroup2)) {
\r
257 // カテゴリが等しいか? (順序は問わない)
\r
259 ArrayList<PartsCategory> categories1 = new ArrayList<PartsCategory>(getPartsCategories());
\r
260 ArrayList<PartsCategory> categories2 = new ArrayList<PartsCategory>(other.getPartsCategories());
\r
261 Comparator<PartsCategory> sortCategoryId = new Comparator<PartsCategory>() {
\r
262 public int compare(PartsCategory o1, PartsCategory o2) {
\r
263 int ret = o1.getCategoryId().compareTo(o2.getCategoryId());
\r
265 ret = o1.getOrder() - o2.getOrder();
\r
270 // カテゴリID順に並び替えて, IDのみを比較する.
\r
271 Collections.sort(categories1, sortCategoryId);
\r
272 Collections.sort(categories2, sortCategoryId);
\r
273 int numOfCategories = categories1.size();
\r
274 if (numOfCategories != categories2.size()) {
\r
278 for (int idx = 0; idx < numOfCategories; idx++) {
\r
279 PartsCategory category1 = categories1.get(idx);
\r
280 PartsCategory category2 = categories2.get(idx);
\r
281 String categoryId1 = category1.getCategoryId();
\r
282 String categoryId2 = category2.getCategoryId();
\r
283 if ( !categoryId1.equals(categoryId2)) {
\r
290 // ID、重ね順序、dirによってのみ判定する.
\r
291 int mx = categories1.size();
\r
292 for (int idx = 0; idx < mx; idx++) {
\r
293 PartsCategory category1 = categories1.get(idx);
\r
294 PartsCategory category2 = categories2.get(idx);
\r
296 ArrayList<Layer> layers1 = new ArrayList<Layer>(category1.getLayers());
\r
297 ArrayList<Layer> layers2 = new ArrayList<Layer>(category2.getLayers());
\r
299 Comparator<Layer> sortLayerId = new Comparator<Layer>() {
\r
300 public int compare(Layer o1, Layer o2) {
\r
301 int ret = o1.getId().compareTo(o2.getId());
\r
303 ret = o1.getOrder() - o2.getOrder();
\r
309 Collections.sort(layers1, sortLayerId);
\r
310 Collections.sort(layers2, sortLayerId);
\r
312 // ID、順序、Dirで判断する.(それ以外のレイヤー情報はequalsでは比較されない)
\r
313 if ( !layers1.equals(layers2)) {
\r
323 * 引数で指定したキャラクター定義とアッパーコンパチブルであるか?<br>
\r
324 * 構造が同一であるか、サイズ違い、もしくはレイヤーの順序、カテゴリの順序、
\r
325 * もしくはレイヤーまたはカテゴリが増えている場合で、減っていない場合はtrueとなる.<br>
\r
326 * 引数がnullの場合は常にfalseとなる.
\r
327 * @param other 前の状態のキャラクター定義、null可
\r
328 * @return アッパーコンパチブルであればtrue、そうでなければfalse
\r
330 public boolean isUpperCompatibleStructure(CharacterData other) {
\r
331 if (!this.isValid() || other == null || !other.isValid()) {
\r
332 // 自分または相手がinvalidであれば構造的には常に互換性なしと見なす.
\r
336 // カラーグループが等しいか? (順序は問わない)
\r
338 ArrayList<ColorGroup> colorGroupNew = new ArrayList<ColorGroup>(getColorGroups());
\r
339 ArrayList<ColorGroup> colorGroupOld = new ArrayList<ColorGroup>(other.getColorGroups());
\r
340 if (!colorGroupNew.containsAll(colorGroupOld)) {
\r
341 // 自分が相手分のすべてのものを持っていなければ互換性なし.
\r
345 // カテゴリをすべて含むか? (順序は問わない)
\r
347 Map<String, PartsCategory> categoriesNew = new HashMap<String, PartsCategory>();
\r
348 for (PartsCategory category : getPartsCategories()) {
\r
349 categoriesNew.put(category.getCategoryId(), category);
\r
351 Map<String, PartsCategory> categoriesOld = new HashMap<String, PartsCategory>();
\r
352 for (PartsCategory category : other.getPartsCategories()) {
\r
353 categoriesOld.put(category.getCategoryId(), category);
\r
355 if ( !categoriesNew.keySet().containsAll(categoriesOld.keySet())) {
\r
356 // 自分が相手のすべてのカテゴリを持っていなければ互換性なし.
\r
361 // ID、Dirによってのみ判定する.
\r
362 for (Map.Entry<String, PartsCategory> categoryOldEntry : categoriesOld.entrySet()) {
\r
363 String categoryId = categoryOldEntry.getKey();
\r
364 PartsCategory categoryOld = categoryOldEntry.getValue();
\r
365 PartsCategory categoryNew = categoriesNew.get(categoryId);
\r
366 if (categoryNew == null) {
\r
370 Map<String, Layer> layersNew = new HashMap<String, Layer>();
\r
371 for (Layer layer : categoryNew.getLayers()) {
\r
372 layersNew.put(layer.getId(), layer);
\r
374 Map<String, Layer> layersOld = new HashMap<String, Layer>();
\r
375 for (Layer layer : categoryOld.getLayers()) {
\r
376 layersOld.put(layer.getId(), layer);
\r
379 if ( !layersNew.keySet().containsAll(layersOld.keySet())) {
\r
380 // 自分が相手のすべてのレイヤー(ID)を持っていなければ互換性なし.
\r
383 for (Map.Entry<String, Layer> layerOldEntry : layersOld.entrySet()) {
\r
384 String layerId = layerOldEntry.getKey();
\r
385 Layer layerOld = layerOldEntry.getValue();
\r
386 Layer layerNew = layersNew.get(layerId);
\r
387 if (layerNew == null) {
\r
390 File dirOld = new File(layerOld.getDir());
\r
391 File dirNew = new File(layerNew.getDir());
\r
392 if ( !dirOld.equals(dirNew)) {
\r
393 // ディレクトリが一致しなければ互換性なし.
\r
403 * キャラクターデータの構造を表す文字列を返す.<br>
\r
404 * カテゴリ、レイヤー、色グループのみで構成される.<br>
\r
405 * id, revなどは含まない.<br>
\r
406 * @return キャラクターデータの構造を表す文字列
\r
408 public String toStructureString() {
\r
410 StringBuilder buf = new StringBuilder();
\r
411 buf.append("{colorGroup:[");
\r
412 for (ColorGroup colorGroup : getColorGroups()) {
\r
413 buf.append(colorGroup.getId());
\r
419 buf.append("category:[");
\r
420 for (PartsCategory category : getPartsCategories()) {
\r
421 buf.append("{id:");
\r
422 buf.append(category.getCategoryId());
\r
424 buf.append(",layer:[");
\r
425 for (Layer layer : category.getLayers()) {
\r
426 buf.append("{id:");
\r
427 buf.append(layer.getId());
\r
428 buf.append(",dir:");
\r
429 buf.append(layer.getDir());
\r
436 return buf.toString();
\r
440 * キャラクターデータのID, REVと構造を識別するシグネチャの文字列を返す.<br>
\r
441 * (構造はカテゴリ、レイヤー、色グループのみ).<br>
\r
442 * @return シグネチャの文字列
\r
444 public String toSignatureString() {
\r
445 StringBuilder buf = new StringBuilder();
\r
446 buf.append("{id:");
\r
447 buf.append(getId());
\r
448 buf.append(",rev:");
\r
449 buf.append(getRev());
\r
450 buf.append(",structure:");
\r
451 buf.append(toStructureString());
\r
453 return buf.toString();
\r
458 * @param stream 入力もと
\r
459 * @throws IOException 失敗
\r
460 * @throws ClassNotFoundException 失敗
\r
462 private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException {
\r
463 stream.defaultReadObject();
\r
464 partsColorMrg = new PartsColorManager(this);
\r
468 * お勧めリンクのリストを取得する.<br>
\r
469 * 古いキャラクターデータで、お勧めリストノードが存在しない場合はnullとなる.<br>
\r
470 * @return お気に入りリンクのリスト、もしくはnull
\r
472 public List<RecommendationURL> getRecommendationURLList() {
\r
473 return recommendationURLList;
\r
477 * お勧めリンクリストを設定する.<br>
\r
478 * @param recommendationURLList、null可
\r
480 public void setRecommendationURLList(
\r
481 List<RecommendationURL> recommendationURLList) {
\r
482 this.recommendationURLList = recommendationURLList;
\r
490 public void setAuthor(String author) {
\r
491 this.author = author;
\r
496 * 説明の改行コードはプラットフォーム固有の改行コードに変換される.<br>
\r
497 * @param description
\r
499 public void setDescription(String description) {
\r
500 if (description != null) {
\r
501 description = description.replace("\r\n", "\n");
\r
502 description = description.replace("\r", "\n");
\r
503 description = description.replace("\n", System.getProperty("line.separator"));
\r
505 this.description = description;
\r
508 public String getAuthor() {
\r
514 * 説明の改行コードはプラットフォーム固有の改行コードとなる.<br>
\r
517 public String getDescription() {
\r
518 return description;
\r
521 public String getId() {
\r
525 public void setId(String id) {
\r
529 public String getRev() {
\r
533 public void setRev(String rev) {
\r
537 public void setDocBase(URI docBase) {
\r
538 this.docBase = docBase;
\r
541 public URI getDocBase() {
\r
546 * ディレクトリを監視するか? (デフォルトは監視する)
\r
547 * @return ディレクトリを監視する場合はtrue
\r
549 public boolean isWatchDirectory() {
\r
551 String value = properties.getProperty("watch-dir");
\r
552 if (value != null) {
\r
553 return Boolean.parseBoolean(value);
\r
555 } catch (RuntimeException ex) {
\r
556 logger.log(Level.WARNING, "watch-dir property is invalid.", ex);
\r
563 * ディレクトリを監視するか指定する.
\r
564 * @param watchDir 監視する場合はtrue、しない場合はfalse
\r
566 public void setWatchDirectory(boolean watchDir) {
\r
567 properties.setProperty("watch-dir", Boolean.toString(watchDir));
\r
570 public String getProperty(String key) {
\r
571 if (key == null || key.trim().length() == 0) {
\r
572 throw new IllegalArgumentException();
\r
574 return properties.getProperty(key.trim());
\r
577 public void setProperty(String key, String value) {
\r
578 if (key == null || key.trim().length() == 0) {
\r
579 throw new IllegalArgumentException();
\r
581 properties.setProperty(key.trim(), value);
\r
584 public Collection<String> getPropertyNames() {
\r
585 ArrayList<String> names = new ArrayList<String>();
\r
586 for (Object key : properties.keySet()) {
\r
587 names.add(key.toString());
\r
593 * 有効なキャラクターデータであるか?
\r
594 * ID, Name, DocBaseが存在するものが有効なキャラクターデータである.<br>
\r
595 * @return 有効であればtrue
\r
597 public boolean isValid() {
\r
598 return id != null && id.length() > 0 && localizedName != null
\r
599 && localizedName.length() > 0 && docBase != null;
\r
604 * まだdocbaseが指定されていない新しいインスタンスであるか、
\r
605 * もしくはdocbaseが実在しファイルであり且つ読み込み可能であるか、
\r
606 * もしくはdocbaseがまだ存在しない場合は、その親ディレクトリが読み書き可能であるか?
\r
607 * @return 編集可能であればtrue
\r
609 public boolean canWrite() {
\r
614 } catch (IOException ex) {
\r
621 * まだdocbaseが指定されていない新しいインスタンスであるか、
\r
622 * もしくはdocbaseが実在しファイルであり且つ読み込み可能であるか、
\r
623 * もしくはdocbaseがまだ存在しない場合は、その親ディレクトリが読み書き可能であるか?
\r
624 * @throws IOException 編集可能でなければIOException例外が発生する.
\r
626 public void checkWritable() throws IOException {
\r
627 if (docBase == null) {
\r
628 throw new IOException("invalid profile: " + this);
\r
631 if ( !"file".equals(docBase.getScheme())) {
\r
632 throw new IOException("ファイルプロトコルではないため書き込みはできません。:" + docBase);
\r
635 File xmlFile = new File(docBase);
\r
636 if (xmlFile.exists()) {
\r
637 // character.xmlファイルがある場合
\r
638 if ( !xmlFile.canWrite() || !xmlFile.canRead()) {
\r
639 throw new IOException("書き込み、もしくは読み込みが禁止されているプロファイルです。" + docBase);
\r
643 // character.xmlファイルが、まだ存在していない場合
\r
644 File parent = xmlFile.getParentFile();
\r
645 if ( !parent.exists()) {
\r
646 throw new IOException("親ディレクトリがありません。" + docBase);
\r
648 if ( !parent.canWrite() || !parent.canRead()) {
\r
649 throw new IOException("親ディレクトリは書き込み、もしくは読み込みが禁止されています。" + docBase);
\r
658 public void setName(String name) {
\r
659 this.localizedName = name;
\r
666 public String getName() {
\r
667 return localizedName;
\r
670 public void setImageSize(Dimension imageSize) {
\r
671 if (imageSize != null) {
\r
672 imageSize = (Dimension) imageSize.clone();
\r
674 this.imageSize = imageSize;
\r
677 public Dimension getImageSize() {
\r
678 return imageSize != null ? (Dimension) imageSize.clone() : null;
\r
681 public void setColorGroups(Collection<ColorGroup> colorGroups) {
\r
682 if (colorGroups == null) {
\r
683 throw new IllegalArgumentException();
\r
686 ArrayList<ColorGroup> colorGroupWithNA = new ArrayList<ColorGroup>();
\r
688 colorGroupWithNA.add(ColorGroup.NA);
\r
689 for (ColorGroup colorGroup : colorGroups) {
\r
690 if (colorGroup.isEnabled()) {
\r
691 colorGroupWithNA.add(colorGroup);
\r
695 OrderedMap<String, ColorGroup> ret = new OrderedMap<String, ColorGroup>(
\r
697 new OrderedMap.KeyDetector<String, ColorGroup>() {
\r
698 public String getKey(ColorGroup data) {
\r
699 return data.getId();
\r
702 this.colorGroups = ret;
\r
706 * カラーグループIDからカラーグループを取得する.<br>
\r
707 * 存在しない場合はN/Aを返す.<br>
\r
708 * @param colorGroupId カラーグループID
\r
711 public ColorGroup getColorGroup(String colorGroupId) {
\r
712 ColorGroup cg = colorGroups.get(colorGroupId);
\r
716 return ColorGroup.NA;
\r
719 public Collection<ColorGroup> getColorGroups() {
\r
720 return colorGroups.values();
\r
723 public PartsCategory getPartsCategory(String categoryId) {
\r
724 if (partsCategories == null) {
\r
727 return partsCategories.get(categoryId);
\r
730 public void setPartsCategories(PartsCategory[] partsCategories) {
\r
731 if (partsCategories == null) {
\r
732 partsCategories = new PartsCategory[0];
\r
734 this.partsCategories = new OrderedMap<String, PartsCategory>(
\r
735 Arrays.asList(partsCategories),
\r
736 new OrderedMap.KeyDetector<String, PartsCategory>() {
\r
737 public String getKey(PartsCategory data) {
\r
738 return data.getCategoryId();
\r
743 public List<PartsCategory> getPartsCategories() {
\r
744 return partsCategories.asList();
\r
748 * パーツデータがロード済みであるか?<br>
\r
749 * 少なくとも{@link #loadPartsData(PartsDataLoader)}が一度呼び出されていればtrueとなる.<br>
\r
750 * falseの場合はパーツローダが設定されていないことを示す.<br>
\r
751 * @return パーツデータがロード済みであればtrue、そうでなければfalse
\r
753 public boolean isPartsLoaded() {
\r
754 return partsDataLoader != null;
\r
758 * パーツデータをロードする.<br>
\r
759 * パーツローダを指定し、このローダはパーツの再ロード時に使用するため保持される.<br>
\r
760 * @param partsDataLoader ローダー
\r
762 public void loadPartsData(PartsDataLoader partsDataLoader) {
\r
763 if (partsDataLoader == null) {
\r
764 throw new IllegalArgumentException();
\r
766 this.partsDataLoader = partsDataLoader;
\r
771 * パーツデータをリロードする.<br>
\r
772 * ロード時に使用したローダーを使ってパーツを再ロードします.<br>
\r
773 * まだ一度もロードしていない場合はIllegalStateException例外が発生します.<br>
\r
774 * @return 変更があった場合はtrue、ない場合はfalse
\r
776 public boolean reloadPartsData() {
\r
777 if (partsDataLoader == null) {
\r
778 throw new IllegalStateException("partsDataLoader is not set.");
\r
782 for (PartsCategory category : partsCategories.asList()) {
\r
783 images.put(category, partsDataLoader.load(category));
\r
785 // NOTE: とりあえずパーツの変更を検査せず、常に変更ありにしておく。とりあえず実害ない。
\r
792 public PartsSpec getPartsSpec(PartsIdentifier partsIdentifier) {
\r
793 if (partsIdentifier == null) {
\r
794 throw new IllegalArgumentException();
\r
796 PartsCategory partsCategory = partsIdentifier.getPartsCategory();
\r
797 Map<PartsIdentifier, PartsSpec> partsSpecMap = images.get(partsCategory);
\r
798 if (partsSpecMap != null) {
\r
799 PartsSpec partsSpec = partsSpecMap.get(partsIdentifier);
\r
800 if (partsSpec != null) {
\r
810 public Map<PartsIdentifier, PartsSpec> getPartsSpecMap(PartsCategory category) {
\r
811 Map<PartsIdentifier, PartsSpec> partsImageMap = images.get(category);
\r
812 if (partsImageMap == null) {
\r
813 return Collections.emptyMap();
\r
815 return partsImageMap;
\r
818 public PartsColorManager getPartsColorManager() {
\r
819 return this.partsColorMrg;
\r
823 * パーツセットを登録します.<br>
\r
824 * お気に入りとプリセットの両方の共用です.<br>
\r
825 * IDおよび名前がないものは登録されず、falseを返します.<br>
\r
826 * パーツセットは、このキャラクター定義に定義されているカテゴリに正規化されます.<br>
\r
827 * 正規化された結果カテゴリが一つもなくなった場合は何も登録されず、falseを返します.<br>
\r
828 * 登録された場合はtrueを返します.<br>
\r
829 * 同一のIDは上書きされます.<br>
\r
831 * @return 登録された場合はtrue、登録できない場合はfalse
\r
833 public boolean addPartsSet(PartsSet partsSet) {
\r
834 if (partsSet == null) {
\r
835 throw new IllegalArgumentException();
\r
837 if (partsSet.getPartsSetId() == null
\r
838 || partsSet.getPartsSetId().length() == 0
\r
839 || partsSet.getLocalizedName() == null
\r
840 || partsSet.getLocalizedName().length() == 0) {
\r
843 PartsSet compatiblePartsSet = partsSet.createCompatible(this);
\r
844 if (compatiblePartsSet.isEmpty()) {
\r
847 presets.put(compatiblePartsSet.getPartsSetId(), compatiblePartsSet);
\r
852 * プリセットパーツおよびパーツセット(Favorites)のコレクション.
\r
853 * @return パーツセットのコレクション
\r
855 public Map<String, PartsSet> getPartsSets() {
\r
860 * プリセットパーツおよびパーツセットをリセットします.<br>
\r
861 * @param noRemovePreset プリセットは削除せず残し、プリセット以外のパーツセットをクリアする場合はtrue、falseの場合は全て削除される.
\r
863 public void clearPartsSets(boolean noRemovePreset) {
\r
864 if (!noRemovePreset) {
\r
867 defaultPartsSetId = null;
\r
870 Iterator<Map.Entry<String, PartsSet>> ite = presets.entrySet().iterator();
\r
871 while (ite.hasNext()) {
\r
872 Map.Entry<String, PartsSet> entry = ite.next();
\r
873 if (!entry.getValue().isPresetParts()) {
\r
874 // デフォルトパーツセットであれば、デフォルトパーツセットもnullにする.
\r
875 // (ただし、デフォルトパーツセットはプリセットであることを想定しているので、この処理は安全策用。)
\r
876 if (entry.getKey().equals(defaultPartsSetId)) {
\r
877 defaultPartsSetId = null;
\r
886 * デフォルトのパーツセットを取得する.<br>
\r
887 * そのパーツセットIDが実在するか、あるいは、それがプリセットであるか、などは一切関知しない.<br>
\r
888 * 呼び出しもとで必要に応じてチェックすること.<br>
\r
889 * @return デフォルトとして指定されているパーツセットのID、なければnull
\r
891 public String getDefaultPartsSetId() {
\r
892 return defaultPartsSetId;
\r
896 * デフォルトのパーツセットIDを指定する.<br>
\r
897 * nullの場合はデフォルトのパーツセットがないことを示す.<br>
\r
898 * パーツセットはプリセットであることが想定されるが、<br>
\r
899 * 実際に、その名前のパーツセットが存在するか、あるいは、そのパーツセットがプリセットであるか、などの判定は一切行わない.<br>
\r
900 * @param defaultPartsSetId パーツセットID、もしくはnull
\r
902 public void setDefaultPartsSetId(String defaultPartsSetId) {
\r
903 this.defaultPartsSetId = defaultPartsSetId;
\r
907 public String toString() {
\r
908 StringBuilder buf = new StringBuilder();
\r
909 buf.append("character-id: " + id);
\r
910 buf.append("/rev:" + rev);
\r
911 buf.append("/name:" + localizedName);
\r
912 buf.append("/image-size:" + imageSize.width + "x" + imageSize.height);
\r
913 buf.append("/docBase:" + docBase);
\r
914 return buf.toString();
\r