/* * Copyright 2007, 2008 (C) Tom Parker <thpr@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 */ package pcgen.cdom.base; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import pcgen.base.formula.Formula; import pcgen.cdom.enumeration.AssociationListKey; import pcgen.core.PlayerCharacter; import pcgen.core.chooser.CDOMChooserFacadeImpl; import pcgen.facade.core.ChooserFacade.ChooserTreeViewType; import pcgen.util.StringPClassUtil; import pcgen.util.chooser.ChooserFactory; /** * This is a transitional class from PCGen 5.15+ to the final CDOM core. It is * provided as convenience to hold a set of choices and the number of choices * allowed, prior to final implementation of the new choice system * * @param <T> * The type of object that will be chosen when this TransitionChoice * is used */ public class ConcreteTransitionChoice<T> implements TransitionChoice<T> { /** * The underlying SelectableSet used to determine the choices available when * selections are to be made in this TransitionChoice. */ private final SelectableSet<? extends T> choices; /** * The Formula indicating the number of choices to be made when selections * are made in this TransitionChoice. */ private final Formula choiceCount; /** * IDentifies if this TransitionChoice selection is required - if it is * required, then the user cannot dismiss the dialog box without making a * choice (or the dialog box reappears, etc.) */ private boolean required = true; /** * The ChoiceActor (optional) which will act upon any choices made from this * TransitionChoice. */ private ChoiceActor<T> choiceActor; /** * Identifies if this TransitionChoice allows stacking of the same object. * * This is typically done with Abilities, which has the STACKS: token in * order to identify stackable Abilities. Note that this field only allows * stacking, it does not enable stacking of objects which are not generally * stackable. */ private boolean allowStack = false; /** * Identifies any limit to stacking in this TransitionChoice. This is only * enabled if allowStack is true, and limits the number of times a single * object may be stacked in this selection. */ private Integer stackLimit = null; /** * Constructs a new TransitionChoice with the given SelectableSet (of * possible choices) and Formula (indicating the number of choices that may * be taken) * * @param set * The SelectableSet indicating the choices available in this * TransitionChoice. * @param count * The Formula indicating the number of choices that may be * selected when selections are made in this TransitionChoice. */ public ConcreteTransitionChoice(SelectableSet<? extends T> set, Formula count) { choices = set; choiceCount = count; } /** * Returns the SelectableSet for this TransitionChoice. * * TODO Should determine if this should be exposed. It seems this is * primarily used to get access to getLSTformat and getChoiceClass, so * perhaps the TransitionChoice should delegate those instead in order to * protect the SelectableSet? * * @return The SelectableSet for this TransitionChoice. */ @Override public SelectableSet<? extends T> getChoices() { return choices; } /** * Returns the Formula indicating the number of selections available when * selections are made in this TransitionChoice. * * @return The Formula indicating the number of selections available */ @Override public Formula getCount() { return choiceCount; } /** * Returns true if the given Object is a TransitionChoice and has identical * underlying choices and choiceCount * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (obj instanceof ConcreteTransitionChoice) { ConcreteTransitionChoice<?> other = (ConcreteTransitionChoice<?>) obj; if (choiceCount == other.choiceCount || (choiceCount != null && choiceCount .equals(other.choiceCount))) { return choices.equals(other.choices); } } return false; } /** * Returns a consistent-with-equals hashCode for this TransitionChoice. * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return choices.hashCode() + 29 * (choiceCount == null ? -1 : choiceCount.hashCode()); } /** * Drives a selection for this TransitionChoice on the given * PlayerCharacter. * * @param pc * The PlayerCharacter for which this TransitionChoice should * drive a choice. * @return A Collection of objects of the type that this TransitionChoice * selects. */ @Override public Collection<? extends T> driveChoice(PlayerCharacter pc) { int numChoices = choiceCount.resolve(pc, "").intValue(); boolean pickall = (numChoices == Integer.MAX_VALUE); String title = choices.getTitle(); if (title == null) { title = "Choose a " + StringPClassUtil.getStringFor(choices.getChoiceClass()); } Collection<? extends T> set = choices.getSet(pc); Set<T> allowed = new LinkedHashSet<>(); List<Object> assocList = pc.getAssocList(this, AssociationListKey.ADD); for (T item : set) { if (choiceActor == null || choiceActor.allow(item, pc, allowStack)) { if (assocList != null && stackLimit != null && stackLimit.intValue() > 0) { int takenCount = 0; for (Object choice : assocList) { if (choice.equals(item)) { takenCount++; } } if (stackLimit.intValue() <= takenCount) { continue; } } allowed.add(item); } } //TODO: What about allowing duplicates? if (pickall || numChoices == set.size()) { return allowed; } else { CDOMChooserFacadeImpl<T> chooserFacade = new CDOMChooserFacadeImpl<>( title, new ArrayList<>(allowed), new ArrayList<>(), numChoices); chooserFacade.setAllowsDups(allowStack); chooserFacade.setRequireCompleteSelection(required); chooserFacade.setDefaultView(ChooserTreeViewType.NAME); ChooserFactory.getDelegate().showGeneralChooser(chooserFacade); //TODO: What about cancel? Should it be allowed? return chooserFacade.getFinalSelected(); } } /** * Sets whether a selection from this TransitionChoice is required. If * required, a TransitionChoice will not exit the driveChoice method until * the user has made a selection. * * @param isRequired * true if a selection from this TransitionChoice should be * required. */ @Override public void setRequired(boolean isRequired) { required = isRequired; } /** * Sets the (optional) ChoiceActor for this TransitionChoice. The * ChoiceActor will be called when the act method of TransitionChoice is * called. If the ChoiceActor is not set, then the set method may not be * used without triggering an exception. * * @param actor * The ChoiceActor for this TransitionChoice. */ @Override public void setChoiceActor(ChoiceActor<T> actor) { choiceActor = actor; } /** * Acts upon choices made in this TransitionChoice. * * @param choicesMade * The choices on which this TransitionChoice should act. * @param owner * The owning object for this TransitionChoice * @param apc * The PlayerCharacter to which the choices should be applied. */ @Override public void act(Collection<? extends T> choicesMade, CDOMObject owner, PlayerCharacter apc) { if (choiceActor == null) { throw new IllegalStateException( "Cannot act without a defined ChoiceActor"); } for (T choice : choicesMade) { choiceActor.applyChoice(owner, choice, apc); apc.addAssoc(this, AssociationListKey.ADD, choice); } } /** * Casts an object to the (Generic) Type of this TransitionChoice. * * @param item * The incoming object * @return The incoming object, cast to the (Generic) type of this * TransitionChoice. */ @SuppressWarnings("unchecked") public T castChoice(Object item) { return (T) item; } /** * Sets whether this TransitionChoice should allow stacking. * * @param allow * true if this TransitionChoice should allow stacking; false * otherwise. */ @Override public void allowStack(boolean allow) { allowStack = allow; } /** * Sets the stacking limit of this TransitionChoice. This is only enabled if * allowStack is set to true. This limits the number of times an individual * item can stack in a given TransitionChoice. * * @param limit * The limit (number of times a stackable item may be selected in * this TransitionChoice) */ @Override public void setStackLimit(int limit) { stackLimit = limit; } /** * Identifies if this TransitionChoice allows stacking. * * @return true if this TransitionChoice should allow stacking; false * otherwise. */ @Override public boolean allowsStacking() { return allowStack; } /** * Returns the Stacking Limit of this TransitionChoice. This is only enabled * if allowStack is set to true. This limits the number of times an * individual item can stack in a given TransitionChoice. * * @return The limit (number of times a stackable item may be selected in * this TransitionChoice) */ @Override public Integer getStackLimit() { return stackLimit; } /** * Returns the ChoiceActor for this TransitionChoice. * * CONSIDER Should look at another method to get rid of this - do the users * of this method require their own sub-class to TransitionChoice? * * @return The ChoiceActor for this TransitionChoice. */ @Override public ChoiceActor<T> getChoiceActor() { return choiceActor; } }