/*
* CompanionSupportFacadeImpl.java
* Copyright 2012 (C) Connor Petty <cpmeister@users.sourceforge.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Created on Mar 18, 2012, 11:38:13 PM
*/
package pcgen.gui2.facade;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import pcgen.cdom.base.CDOMObject;
import pcgen.cdom.base.Constants;
import pcgen.cdom.list.CompanionList;
import pcgen.core.FollowerOption;
import pcgen.core.Globals;
import pcgen.core.PlayerCharacter;
import pcgen.core.Race;
import pcgen.core.character.Follower;
import pcgen.core.display.CharacterDisplay;
import pcgen.facade.core.CharacterFacade;
import pcgen.facade.core.CompanionFacade;
import pcgen.facade.core.CompanionStubFacade;
import pcgen.facade.core.CompanionSupportFacade;
import pcgen.facade.core.PartyFacade;
import pcgen.facade.core.RaceFacade;
import pcgen.facade.util.ReferenceFacade;
import pcgen.facade.util.event.ListEvent;
import pcgen.facade.util.event.ListListener;
import pcgen.facade.util.event.ReferenceEvent;
import pcgen.facade.util.event.ReferenceListener;
import pcgen.facade.util.DefaultListFacade;
import pcgen.facade.util.DefaultMapFacade;
import pcgen.facade.util.ListFacade;
import pcgen.facade.util.MapFacade;
import pcgen.system.CharacterManager;
import pcgen.util.Logging;
import pcgen.util.enumeration.Tab;
/**
* This class implements the basic CompanionSupportFacade
* for a given
* {@code PlayerCharacter} and is
* used to help implement companion support for the
* CharacterFacade.
* @see pcgen.gui2.facade.CharacterFacadeImpl
* @author Connor Petty <cpmeister@users.sourceforge.net>
*/
public class CompanionSupportFacadeImpl implements CompanionSupportFacade, ListListener<CharacterFacade>
{
private DefaultListFacade<CompanionFacadeDelegate> companionList;
private final PlayerCharacter theCharacter;
private final CharacterDisplay charDisplay;
private DefaultListFacade<CompanionStubFacade> availCompList;
private DefaultMapFacade<String, Integer> maxCompanionsMap;
private Map<String, CompanionList> keyToCompanionListMap;
private final TodoManager todoManager;
private final CharacterFacadeImpl pcFacade;
/**
* Create a new instance of CompanionSupportFacadeImpl
* @param theCharacter The character to be represented.
* @param todoManager The user tasks tracker.
* @param nameRef The reference to the character's name.
* @param fileRef The reference to the character's file.
* @param pcFacade The UI facade for the master.
*/
public CompanionSupportFacadeImpl(PlayerCharacter theCharacter,
TodoManager todoManager, ReferenceFacade<String> nameRef,
ReferenceFacade<File> fileRef,
CharacterFacadeImpl pcFacade)
{
this.theCharacter = theCharacter;
this.pcFacade = pcFacade;
this.charDisplay = theCharacter.getDisplay();
this.todoManager = todoManager;
this.companionList = new DefaultListFacade<>();
this.availCompList = new DefaultListFacade<>();
this.maxCompanionsMap = new DefaultMapFacade<>();
this.keyToCompanionListMap = new HashMap<>();
initCompData(true);
CharacterManager.getCharacters().addListListener(this);
addMasterListeners(nameRef, fileRef);
}
/**
* Add listeners to the master name and file that will update the master
* information of all companions when the master changes.
* @param nameRef The reference to the character's name.
* @param fileRef The reference to the character's file.
*/
private void addMasterListeners(ReferenceFacade<String> nameRef,
ReferenceFacade<File> fileRef)
{
nameRef.addReferenceListener(new ReferenceListener<String>()
{
@Override
public void referenceChanged(ReferenceEvent<String> e)
{
String newName = e.getNewReference();
for (CompanionFacadeDelegate delegate : companionList)
{
CharacterFacade companion =
CharacterManager.getCharacterMatching(delegate);
if (companion != null)
{
CharacterFacadeImpl compFacadeImpl =
(CharacterFacadeImpl) companion;
Follower follower =
compFacadeImpl.getTheCharacter().getDisplay().getMaster();
follower.setName(newName);
}
}
}
});
fileRef.addReferenceListener(new ReferenceListener<File>()
{
@Override
public void referenceChanged(ReferenceEvent<File> e)
{
File newFile = e.getNewReference();
for (CompanionFacadeDelegate delegate : companionList)
{
CharacterFacade companion =
CharacterManager.getCharacterMatching(delegate);
if (companion != null)
{
CharacterFacadeImpl compFacadeImpl =
(CharacterFacadeImpl) companion;
Follower follower =
compFacadeImpl.getTheCharacter().getDisplay().getMaster();
follower.setFileName(newFile.getAbsolutePath());
}
}
}
});
}
/**
* Refresh the character's companion information, reflecting any changes in
* the character's qualification for companions.
*/
void refreshCompanionData()
{
initCompData(false);
for (CompanionFacadeDelegate delegate : companionList)
{
CompanionFacade compFacade = delegate.getDelegate();
if (compFacade instanceof CharacterFacadeImpl)
{
CharacterFacadeImpl compFacadeImpl = (CharacterFacadeImpl) compFacade;
PlayerCharacter pc = compFacadeImpl.getTheCharacter();
pc.setMaster(pc.getDisplay().getMaster());
compFacadeImpl.refreshClassLevelModel();
compFacadeImpl.postLevellingUpdates();
}
}
}
/**
* Initialisation of the character's companion data.
* @param rebuildCompanionList Should the list of the character;s companions be rebuilt?
*/
private void initCompData(boolean rebuildCompanionList)
{
List<CompanionStub> companions = new ArrayList<>();
for (CompanionList compList : Globals.getContext().getReferenceContext()
.getConstructedCDOMObjects(CompanionList.class))
{
keyToCompanionListMap.put(compList.getKeyName(), compList);
Map<FollowerOption, CDOMObject> fMap = charDisplay.getAvailableFollowers(compList.getKeyName(), null);
for (FollowerOption followerOpt : fMap.keySet())
{
if (followerOpt.getRace() != Globals.s_EMPTYRACE && followerOpt.qualifies(theCharacter, null))
{
companions.add(new CompanionStub(followerOpt.getRace(), compList.getKeyName()));
}
}
int maxVal = theCharacter.getMaxFollowers(compList);
if (maxVal == 0)
{
maxCompanionsMap.removeKey(compList.toString());
}
else
{
maxCompanionsMap.putValue(compList.toString(), maxVal);
}
}
availCompList.updateContents(companions);
//Logging.debugPrint("Available comps " + availCompList);
//Logging.debugPrint("Max comps " + maxCompanionsMap);
if (rebuildCompanionList)
{
for (Follower follower : charDisplay.getFollowerList())
{
CompanionFacade comp =
new CompanionNotLoaded(follower.getName(), new File(
follower.getFileName()), follower.getRace(), follower
.getType().toString());
CompanionFacadeDelegate delegate = new CompanionFacadeDelegate();
delegate.setCompanionFacade(comp);
companionList.addElement(delegate);
}
}
//Logging.debugPrint("Companion list " + companionList);
for (CompanionList compList : Globals.getContext().getReferenceContext()
.getConstructedCDOMObjects(CompanionList.class))
{
updateCompanionTodo(compList.toString());
}
}
private void updateCompanionTodo(String companionType)
{
Integer max = maxCompanionsMap.getValue(companionType);
int maxCompanions = max == null ? 0 : max;
int numCompanions = 0;
for (CompanionFacadeDelegate cfd : companionList)
{
if (cfd.getCompanionType().equals(companionType))
{
numCompanions++;
}
}
if (maxCompanions > -1 && maxCompanions < numCompanions)
{
todoManager.addTodo(new TodoFacadeImpl(Tab.COMPANIONS,
companionType, "in_companionTodoTooMany", companionType, 1)); //$NON-NLS-1$
todoManager.removeTodo("in_companionTodoRemain", companionType); //$NON-NLS-1$
}
else if (maxCompanions > -1 && maxCompanions > numCompanions)
{
todoManager.addTodo(new TodoFacadeImpl(Tab.COMPANIONS,
companionType, "in_companionTodoRemain", companionType, 1)); //$NON-NLS-1$
todoManager.removeTodo("in_companionTodoTooMany", companionType); //$NON-NLS-1$
}
else
{
todoManager.removeTodo("in_companionTodoRemain", companionType); //$NON-NLS-1$
todoManager.removeTodo("in_companionTodoTooMany", companionType); //$NON-NLS-1$
}
}
@Override
public void addCompanion(CharacterFacade companion, String companionType)
{
if (companion == null || !(companion instanceof CharacterFacadeImpl))
{
return;
}
CharacterFacadeImpl compFacadeImpl = (CharacterFacadeImpl) companion;
CompanionList compList = keyToCompanionListMap.get(companionType);
Race compRace = (Race) compFacadeImpl.getRaceRef().get();
FollowerOption followerOpt = getFollowerOpt(compList, compRace);
if (followerOpt == null)
{
Logging.errorPrint("Unable to find follower option for companion " //$NON-NLS-1$
+ companion + " of race " + compRace); //$NON-NLS-1$
return;
}
if (!followerOpt.qualifies(theCharacter, null))
{
Logging.errorPrint("Not qualified to take companion " //$NON-NLS-1$
+ companion + " of race " + compRace); //$NON-NLS-1$
return;
}
// Update the companion with the master details
Logging.log(Logging.INFO,
"Setting master to " + charDisplay.getName() //$NON-NLS-1$
+ " for character " + compFacadeImpl); //$NON-NLS-1$
final Follower newMaster =
new Follower(charDisplay.getFileName(), charDisplay.getName(), compList);
newMaster.setAdjustment(followerOpt.getAdjustment());
compFacadeImpl.getTheCharacter().setMaster(newMaster);
compFacadeImpl.refreshClassLevelModel();
compFacadeImpl.postLevellingUpdates();
// Update the master with the new companion
File compFile = compFacadeImpl.getFileRef().get();
String compFilename = StringUtils.isEmpty(compFile.getPath()) ? "" : compFile.getAbsolutePath();
Follower follower =
new Follower(compFilename, compFacadeImpl.getNameRef()
.get(), compList);
follower.setRace(compRace);
theCharacter.addFollower(follower);
theCharacter.setCalcFollowerBonus();
theCharacter.calcActiveBonuses();
pcFacade.postLevellingUpdates();
CompanionFacadeDelegate delegate = new CompanionFacadeDelegate();
delegate.setCompanionFacade(companion);
companionList.addElement(delegate);
// Watch companion file name and character name to update follower record
companion.getFileRef().addReferenceListener(new DelegateFileListener(follower));
companion.getNameRef().addReferenceListener(new DelegateNameListener(follower));
updateCompanionTodo(companionType);
}
private FollowerOption getFollowerOpt(CompanionList compList, Race compRace)
{
FollowerOption followerOpt = null;
Map<FollowerOption, CDOMObject> fMap = charDisplay.getAvailableFollowers(compList.getKeyName(), null);
for (FollowerOption fOpt : fMap.keySet())
{
if (compRace == fOpt.getRace())
{
followerOpt = fOpt;
break;
}
}
return followerOpt;
}
@Override
public void removeCompanion(CompanionFacade companion)
{
if (!(companion instanceof CompanionFacadeDelegate))
{
return;
}
File compFile = companion.getFileRef().get();
for (Follower follower : charDisplay.getFollowerList())
{
File followerFile = new File(follower.getFileName());
if (followerFile.equals(compFile))
{
theCharacter.delFollower(follower);
break;
}
}
companionList.removeElement((CompanionFacadeDelegate) companion);
updateCompanionTodo(companion.getCompanionType());
}
/**
* Adds a newly opened character into the existing
* companion framework. This character will replace
* the dummy CompanionFacade that has the same
* file name. This should typically be called
* when a character is opened from one of the follower stubs
* @param character the character to link
*/
private void linkCompanion(CharacterFacade character)
{
for (CompanionFacadeDelegate delegate : companionList)
{
File file = delegate.getFileRef().get();
String name = delegate.getNameRef().get();
RaceFacade race = delegate.getRaceRef().get();
if (file.equals(character.getFileRef().get())
&& name.equals(character.getNameRef().get())
&& (race == null || race.equals(character.getRaceRef().get())))
{
String companionType = delegate.getCompanionType();
delegate.setCompanionFacade(character);
// Check for a companion being loaded that is not properly linked to the master.
// Note: When creating a companion we leave the linking to the create code.
if (character.getMaster() == null
&& character.getRaceRef().get() != null
&& !Constants.NONESELECTED.equals(character.getRaceRef()
.get().getKeyName()))
{
CompanionList compList = keyToCompanionListMap.get(companionType);
final Follower newMaster =
new Follower(charDisplay.getFileName(), charDisplay.getName(), compList);
FollowerOption followerOpt =
getFollowerOpt(compList, (Race) character
.getRaceRef().get());
if (followerOpt != null)
{
newMaster.setAdjustment(followerOpt.getAdjustment());
}
else
{
Logging.log(Logging.WARNING,
"Failed to find FollowerOption for complist "
+ compList + " and race "
+ character.getRaceRef().get());
}
((CharacterFacadeImpl) character).getTheCharacter()
.setMaster(newMaster);
}
return;
}
}
}
/**
* Remove ourselves from the global characters list so that
* the current character can be garbage collected.
*/
void closeCharacter()
{
CharacterManager.getCharacters().removeListListener(this);
}
/**
* Removes a character from the companion framework.
* This will replace the specified character with a dummy
* CompanionFacade.
* This should be called after the specified character has been closed
* or is closing.
* If this method is not called after closing a companion character
* the underlying CharacterFacade would not be able to be garbage collected.
* @param character the character to unlink
*/
private void unlinkCompanion(CharacterFacade character)
{
for (CompanionFacadeDelegate delegate : companionList)
{
File file = delegate.getFileRef().get();
if (file.equals(character.getFileRef().get()))
{
CompanionFacade comp =
new CompanionNotLoaded(character.getNameRef()
.get(), character.getFileRef()
.get(), character.getRaceRef()
.get(), delegate.getCompanionType());
delegate.setCompanionFacade(comp);
return;
}
}
}
@Override
public ListFacade<CompanionStubFacade> getAvailableCompanions()
{
return availCompList;
}
@Override
public MapFacade<String, Integer> getMaxCompanionsMap()
{
return maxCompanionsMap;
}
@Override
public ListFacade<? extends CompanionFacade> getCompanions()
{
return companionList;
}
@Override
public void elementAdded(ListEvent<CharacterFacade> e)
{
linkCompanion(e.getElement());
}
@Override
public void elementRemoved(ListEvent<CharacterFacade> e)
{
unlinkCompanion(e.getElement());
}
@Override
public void elementsChanged(ListEvent<CharacterFacade> e)
{
PartyFacade characters = CharacterManager.getCharacters();
for (CharacterFacade characterFacade : characters)
{
linkCompanion(characterFacade);
}
// TODO: Unlink characters no longer open
}
@Override
public void elementModified(ListEvent<CharacterFacade> e)
{
// Ignored.
}
/**
* The Class <code>DelegateFileListener</code> tracks the file name of a companion and
* keeps the associated Follower record up to date.
*/
private class DelegateFileListener implements ReferenceListener<File>
{
private final Follower follower;
public DelegateFileListener(Follower followerIn)
{
this.follower = followerIn;
}
@Override
public void referenceChanged(ReferenceEvent<File> e)
{
follower.setFileName(e.getNewReference().getAbsolutePath());
}
}
/**
* The Class <code>DelegateNameListener</code> tracks the name of a companion and
* keeps the associated Follower record up to date.
*/
private class DelegateNameListener implements ReferenceListener<String>
{
private final Follower follower;
public DelegateNameListener(Follower followerIn)
{
this.follower = followerIn;
}
@Override
public void referenceChanged(ReferenceEvent<String> e)
{
follower.setName(e.getNewReference());
}
}
}