/*
* Copyright 2012 Kazumune Katagiri. (http://d.hatena.ne.jp/nemuzuka)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package jp.co.nemuzuka.service.impl;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jp.co.nemuzuka.dao.MilestoneDao;
import jp.co.nemuzuka.dao.TicketDao.Param;
import jp.co.nemuzuka.entity.ChildKeyListEntity;
import jp.co.nemuzuka.entity.TicketModelEx;
import jp.co.nemuzuka.model.MilestoneModel;
import jp.co.nemuzuka.service.GanttService;
import jp.co.nemuzuka.service.TicketService;
import jp.co.nemuzuka.utils.ConvertUtils;
import jp.co.nemuzuka.utils.CurrentDateUtils;
import jp.co.nemuzuka.utils.DateTimeUtils;
import org.apache.commons.lang.StringUtils;
import com.google.appengine.api.datastore.Key;
/**
* GanttServiceの実装クラス.
* @author k-katagiri
*/
public class GanttServiceImpl implements GanttService {
TicketService ticketService = TicketServiceImpl.getInstance();
MilestoneDao milestoneDao = MilestoneDao.getInstance();
private static GanttServiceImpl impl = new GanttServiceImpl();
/**
* インスタンス取得.
* @return インスタンス
*/
public static GanttServiceImpl getInstance() {
return impl;
}
/**
* デフォルトコンストラクタ.
*/
private GanttServiceImpl(){}
/* (非 Javadoc)
* @see jp.co.nemuzuka.service.GanttService#getList(jp.co.nemuzuka.dao.TicketDao.Param)
*/
@Override
public Result getList(Param param) {
//一覧取得
List<TicketModelEx> ticketList = ticketService.getList(param, "", false);
reCreateTicketList(ticketList);
Result result = new Result();
result.ticketList = ticketList;
//マイルストーン情報取得
Date startDate = null;
Date endDate = null;
if(StringUtils.isNotEmpty(param.milestone)) {
MilestoneModel milestone = milestoneDao.get(param.milestone);
if(milestone != null) {
startDate = milestone.getStartDate();
endDate = milestone.getEndDate();
result.milestoneName = milestone.getMilestoneName();
SimpleDateFormat sdf = DateTimeUtils.createSdf("yyyyMMdd");
result.startDate = ConvertUtils.toString(startDate, sdf);
result.endDate = ConvertUtils.toString(endDate, sdf);
result.milestoneStartDate = ConvertUtils.toString(startDate, sdf);
result.milestoneEndDate = ConvertUtils.toString(endDate, sdf);
}
}
//チケットの日付を再設定する
setDate(result, startDate, endDate);
reCreateTicketList(result.ticketList);
return result;
}
/**
* チケット一覧再作成.
* 子チケットを意識してチケット一覧を最生成します。
* @param ticketList チケット一覧
*/
void reCreateTicketList(List<TicketModelEx> ticketList) {
//Keyに紐付くTicketMapと、親Keyに紐付く子Ticket管理用Entityを生成
Map<Key, TicketModelEx> map = new HashMap<Key, TicketModelEx>();
Map<Key, ChildKeyListEntity> parentMap = new LinkedHashMap<Key, ChildKeyListEntity>();
for(TicketModelEx target : ticketList) {
Key key = target.getModel().getKey();
map.put(key, target);
Key parentKey = target.getModel().getParentTicketKey();
ChildKeyListEntity entity = parentMap.get(parentKey);
if(entity == null) {
entity = new ChildKeyListEntity();
parentMap.put(parentKey, entity);
}
int index = entity.childKeys.size();
entity.childKeys.add(key);
entity.childMap.put(key, index);
entity.grandchildList.add(null);
}
boolean isContinue = true;
Set<Key> doneParentKeySet = new HashSet<Key>();
while(isContinue) {
int beforeSize = parentMap.size();
for(Map.Entry<Key, ChildKeyListEntity> e : parentMap.entrySet()) {
Key parentKey = e.getKey();
if(parentKey == null) {
//親Keyが存在しないTicket群の場合、次のレコードへ
continue;
}
if(doneParentKeySet.contains(parentKey)) {
//処理済みのKeyの場合、次のレコードへ
continue;
}
//処理対象の親Keyが子供になる箇所を検索し、移動する
if(moveParentKey(parentMap, parentKey, e.getValue())) {
//存在したので、Mapから削除し、loop終了
parentMap.remove(parentKey);
break;
} else {
//処理対象の親Keyが存在しなかったので、次のレコードが処理対象
doneParentKeySet.add(parentKey);
}
}
int afterSize = parentMap.size();
if(beforeSize == afterSize) {
//処理前と処理後のMapのサイズが等しい場合、処理終了
isContinue = false;
}
}
//ソートしたMapを元に、ネストを設定する
createList(ticketList, map, parentMap);
return;
}
/**
* TicketList再設定.
* 親Ticket管理Mapを元に、ソート済みのListを再設定します。
* @param ticketList 設定対象TicketList
* @param map 全Ticket管理Map
* @param parentMap 親Ticket管理Map
*/
private void createList(List<TicketModelEx> ticketList,
Map<Key, TicketModelEx> map, Map<Key, ChildKeyListEntity> parentMap) {
ticketList.clear();
for(Map.Entry<Key, ChildKeyListEntity> e : parentMap.entrySet()) {
ChildKeyListEntity targetEntity = e.getValue();
int nestingLevel = 0;
setTicketList(targetEntity, nestingLevel, ticketList, map);
}
}
/**
* List設定.
* 子TicketKeyListを元に戻り値Listを再設定します。
* 孫Ticketが存在する場合、戻り値Listに含めます。
* ※再帰呼び出しします。
* @param targetEntity 対象子Ticket管理用Entity
* @param nestingLevel ネストの深さ
* @param ticketList 設定対象TicketList
* @param map 全Ticket管理Map
*/
void setTicketList(ChildKeyListEntity targetEntity, int nestingLevel,
List<TicketModelEx> ticketList, Map<Key, TicketModelEx> map) {
//子TicketKeyListを元に設定
int index = 0;
for(Key key : targetEntity.childKeys) {
TicketModelEx targetModelEx = map.get(key);
targetModelEx.setNestingLevel(nestingLevel);
ticketList.add(targetModelEx);
//孫TicketKeyに対して設定
ChildKeyListEntity grandchild = targetEntity.grandchildList.get(index);
index++;
if(grandchild == null) {
continue;
}
setTicketList(grandchild, (nestingLevel + 1), ticketList, map);
}
}
/**
* 子要素参照変更.
* 対象のKey情報が子要素となる位置を参照し、存在する場合参照を変更します。
* @param parentMap 元Map
* @param targetKey 対象Key
* @param targetEntity 対象参照元
* @return 対象のKey要素が子要素となる箇所が存在した場合、true
*/
boolean moveParentKey(Map<Key, ChildKeyListEntity> parentMap,
Key targetKey, ChildKeyListEntity targetEntity) {
for(Map.Entry<Key, ChildKeyListEntity> e : parentMap.entrySet()) {
//自分の要素の場合は次の処理へ
if(e.getKey() != null && e.getKey().equals(targetKey)) {
continue;
}
ChildKeyListEntity entity = e.getValue();
if(moveParentKey(targetKey, entity, targetEntity)) {
return true;
}
}
return false;
}
/**
* 子要素参照設定.
* 引数の対象Key情報が子要素となる位置を参照し、
* 存在する場合参照を設定します。
* 存在しない場合、孫要素に対して同処理を行います。
* @param targetKey 対象Key
* @param checkTarget チェック対象子Ticket管理用Entity
* @param targetEntity 対象参照元
* @return 対象のKey要素が子要素となる箇所が存在した場合、true
*/
boolean moveParentKey(Key targetKey, ChildKeyListEntity checkTarget,
ChildKeyListEntity targetEntity) {
//対象要素の中に、自分の親Keyが存在する場合参照を変更する
Integer index = checkTarget.childMap.get(targetKey);
if(index != null) {
checkTarget.grandchildList.set(index, targetEntity);
return true;
} else {
//対象要素の孫Ticket管理用EntityListに対して同じ処理を繰り返し行う
for(ChildKeyListEntity target : checkTarget.grandchildList) {
if(target != null) {
if(moveParentKey(targetKey, target, targetEntity)) {
return true;
}
}
}
}
return false;
}
/**
* チケット開始、終了日付再設定.
* @param result ガントチャート出力情報
* @param startDate マイルストーン開始日
* @param endDate マイルストーン終了日
*/
void setDate(Result result, Date startDate,
Date endDate) {
Date targetStartDate = startDate;
Date targetEndDate = endDate;
for(TicketModelEx target : result.ticketList) {
Date ticketStartDate = target.getModel().getStartDate();
Date ticketEndDate = target.getModel().getPeriod();
if(ticketEndDate != null && ticketStartDate == null) {
//開始日が未設定で終了日が設定されている場合、比較対象として終了日を設定する
//このケースの時に開始日の最小値の可能性があるため
ticketStartDate = ticketEndDate;
}
targetStartDate = checkDate(ticketStartDate, targetStartDate, true);
targetEndDate = checkDate(ticketEndDate, targetEndDate, false);
}
//開始日がnullの場合、システム日付を設定する
if(targetStartDate == null) {
targetStartDate = CurrentDateUtils.getInstance().getCurrentDate();
}
//終了日がnullの場合、開始日の1ヶ月後を設定する
if(targetEndDate == null) {
targetEndDate = DateTimeUtils.addMonths(targetStartDate, 1);
}
//日数を算出して、開始日〜終了日までの期間が15日以下の場合、終了日を開始日から15日後に設定
if(DateTimeUtils.getDays(targetStartDate, targetEndDate) < 15) {
targetEndDate = DateTimeUtils.addDays(targetStartDate, 15);
}
//Ticketの開始日、期限がnullのものに対して値を設定する
replaceNullDate(result.ticketList, targetStartDate, targetEndDate);
//チャートの開始日・終了日を設定
SimpleDateFormat sdf = DateTimeUtils.createSdf("yyyyMMdd");
result.startDate = ConvertUtils.toString(targetStartDate, sdf);
result.endDate = ConvertUtils.toString(targetEndDate, sdf);
//マイルストーンが設定されていて、
//マイルストーンの開始日・終了日が空の場合、設定
if(StringUtils.isNotEmpty(result.milestoneName)) {
if(StringUtils.isEmpty(result.milestoneStartDate)) {
result.milestoneStartDate = result.startDate;
result.updateStartDate = true;
}
if(StringUtils.isEmpty(result.milestoneEndDate)) {
result.milestoneEndDate = result.endDate;
result.updateEndDate = true;
}
}
}
/**
* Ticket開始日、期限null値再設定.
* Ticketの開始日がnullのもの、期限がnullのものを置き換えます。
* @param ticketList Ticket一覧
* @param startDate 置換対象開始日
* @param endDate 置換対象終了日
*/
private void replaceNullDate(List<TicketModelEx> ticketList,
Date startDate, Date endDate) {
SimpleDateFormat sdf = DateTimeUtils.createSdf("yyyyMMdd");
String start = ConvertUtils.toString(startDate, sdf);
String end = ConvertUtils.toString(endDate, sdf);
for(TicketModelEx target : ticketList) {
if(StringUtils.isEmpty(target.getStartDate())) {
target.setStartDate(start);
target.setUpdateStartDate(true);
}
if(StringUtils.isEmpty(target.getPeriod())) {
target.setPeriod(end);
target.setUpdatePeriod(true);
}
}
}
/**
* 日付チェック.
* 開始日のチェックを行う場合、
* チェック対象日 < 基準日の場合、チェック対象日を返却します。
* 終了日のチェックを行う場合、
* チェック対象日 > 基準日の場合、チェック対象日を返却します。
* @param targetDate チェック対象日
* @param baseDate 基準日
* @param startDate 開始日のチェックを行う場合、true
* @return 設定日付
*/
private Date checkDate(Date targetDate, Date baseDate, boolean startDate) {
if(baseDate == null) {
return targetDate;
}
if(targetDate == null) {
return baseDate;
}
if(startDate) {
if(targetDate.getTime() < baseDate.getTime()) {
return targetDate;
}
} else {
if(targetDate.getTime() > baseDate.getTime()) {
return targetDate;
}
}
return baseDate;
}
}