package forge.card.spellability; import forge.AllZone; import forge.Card; import forge.Counters; import forge.card.mana.ManaCost; import java.util.regex.Pattern; /** * <p>Cost class.</p> * * @author Forge * @version $Id: $ */ public class Cost { private boolean isAbility = true; private boolean sacCost = false; /** * <p>Getter for the field <code>sacCost</code>.</p> * * @return a boolean. */ public boolean getSacCost() { return sacCost; } private String sacType = ""; // <type> or CARDNAME /** * <p>Getter for the field <code>sacType</code>.</p> * * @return a {@link java.lang.String} object. */ public String getSacType() { return sacType; } private boolean sacThis = false; /** * <p>Getter for the field <code>sacThis</code>.</p> * * @return a boolean. */ public boolean getSacThis() { return sacThis; } private int sacAmount = 0; /** * <p>Getter for the field <code>sacAmount</code>.</p> * * @return a int. */ public int getSacAmount() { return sacAmount; } private boolean sacX = false; /** * <p>isSacX.</p> * * @return a boolean. */ public boolean isSacX() { return sacX; } private boolean sacAll = false; /** * <p>isSacAll.</p> * * @return a boolean. */ public boolean isSacAll() { return sacAll; } private boolean exileCost = false; /** * <p>Getter for the field <code>exileCost</code>.</p> * * @return a boolean. */ public boolean getExileCost() { return exileCost; } private String exileType = ""; // <type> or CARDNAME /** * <p>Getter for the field <code>exileType</code>.</p> * * @return a {@link java.lang.String} object. */ public String getExileType() { return exileType; } private boolean exileThis = false; /** * <p>Getter for the field <code>exileThis</code>.</p> * * @return a boolean. */ public boolean getExileThis() { return exileThis; } private int exileAmount = 0; /** * <p>Getter for the field <code>exileAmount</code>.</p> * * @return a int. */ public int getExileAmount() { return exileAmount; } private boolean exileFromHandCost = false; /** * <p>Getter for the field <code>exileFromHandCost</code>.</p> * * @return a boolean. */ public boolean getExileFromHandCost() { return exileFromHandCost; } private String exileFromHandType = ""; // <type> or CARDNAME /** * <p>Getter for the field <code>exileFromHandType</code>.</p> * * @return a {@link java.lang.String} object. */ public String getExileFromHandType() { return exileFromHandType; } private boolean exileFromHandThis = false; /** * <p>Getter for the field <code>exileFromHandThis</code>.</p> * * @return a boolean. */ public boolean getExileFromHandThis() { return exileFromHandThis; } private int exileFromHandAmount = 0; /** * <p>Getter for the field <code>exileFromHandAmount</code>.</p> * * @return a int. */ public int getExileFromHandAmount() { return exileFromHandAmount; } private boolean exileFromGraveCost = false; /** * <p>Getter for the field <code>exileFromGraveCost</code>.</p> * * @return a boolean. */ public boolean getExileFromGraveCost() { return exileFromGraveCost; } private String exileFromGraveType = ""; // <type> or CARDNAME /** * <p>Getter for the field <code>exileFromGraveType</code>.</p> * * @return a {@link java.lang.String} object. */ public String getExileFromGraveType() { return exileFromGraveType; } private boolean exileFromGraveThis = false; /** * <p>Getter for the field <code>exileFromGraveThis</code>.</p> * * @return a boolean. */ public boolean getExileFromGraveThis() { return exileFromGraveThis; } private int exileFromGraveAmount = 0; /** * <p>Getter for the field <code>exileFromGraveAmount</code>.</p> * * @return a int. */ public int getExileFromGraveAmount() { return exileFromGraveAmount; } private boolean exileFromTopCost = false; /** * <p>Getter for the field <code>exileFromTopCost</code>.</p> * * @return a boolean. */ public boolean getExileFromTopCost() { return exileFromTopCost; } private String exileFromTopType = ""; // <type> or CARDNAME /** * <p>Getter for the field <code>exileFromTopType</code>.</p> * * @return a {@link java.lang.String} object. */ public String getExileFromTopType() { return exileFromTopType; } private boolean exileFromTopThis = false; /** * <p>Getter for the field <code>exileFromTopThis</code>.</p> * * @return a boolean. */ public boolean getExileFromTopThis() { return exileFromTopThis; } private int exileFromTopAmount = 0; /** * <p>Getter for the field <code>exileFromTopAmount</code>.</p> * * @return a int. */ public int getExileFromTopAmount() { return exileFromTopAmount; } private boolean tapCost = false; /** * <p>getTap.</p> * * @return a boolean. */ public boolean getTap() { return tapCost; } // future expansion of Ability_Cost class: tap untapped type private boolean tapXTypeCost = false; /** * <p>Getter for the field <code>tapXTypeCost</code>.</p> * * @return a boolean. */ public boolean getTapXTypeCost() { return tapXTypeCost; } private int tapXTypeAmount = 0; /** * <p>Getter for the field <code>tapXTypeAmount</code>.</p> * * @return a int. */ public int getTapXTypeAmount() { return tapXTypeAmount; } private String tapXType = ""; /** * <p>Getter for the field <code>tapXType</code>.</p> * * @return a {@link java.lang.String} object. */ public String getTapXType() { return tapXType; } private boolean untapCost = false; /** * <p>getUntap.</p> * * @return a boolean. */ public boolean getUntap() { return untapCost; } private boolean subtractCounterCost = false; /** * <p>getSubCounter.</p> * * @return a boolean. */ public boolean getSubCounter() { return subtractCounterCost; } private boolean addCounterCost = false; /** * <p>getAddCounter.</p> * * @return a boolean. */ public boolean getAddCounter() { return addCounterCost; } private int counterAmount = 0; /** * <p>getCounterNum.</p> * * @return a int. */ public int getCounterNum() { return counterAmount; } private Counters counterType; /** * <p>Getter for the field <code>counterType</code>.</p> * * @return a {@link forge.Counters} object. */ public Counters getCounterType() { return counterType; } private boolean lifeCost = false; /** * <p>Getter for the field <code>lifeCost</code>.</p> * * @return a boolean. */ public boolean getLifeCost() { return lifeCost; } private int lifeAmount = 0; /** * <p>Getter for the field <code>lifeAmount</code>.</p> * * @return a int. */ public int getLifeAmount() { return lifeAmount; } private boolean discardCost = false; /** * <p>Getter for the field <code>discardCost</code>.</p> * * @return a boolean. */ public boolean getDiscardCost() { return discardCost; } private int discardAmount = 0; /** * <p>Getter for the field <code>discardAmount</code>.</p> * * @return a int. */ public int getDiscardAmount() { return discardAmount; } private String discardType = ""; /** * <p>Getter for the field <code>discardType</code>.</p> * * @return a {@link java.lang.String} object. */ public String getDiscardType() { return discardType; } private boolean discardThis = false; /** * <p>Getter for the field <code>discardThis</code>.</p> * * @return a boolean. */ public boolean getDiscardThis() { return discardThis; } private boolean returnCost = false; // Return something to owner's hand /** * <p>Getter for the field <code>returnCost</code>.</p> * * @return a boolean. */ public boolean getReturnCost() { return returnCost; } private String returnType = ""; // <type> or CARDNAME /** * <p>Getter for the field <code>returnType</code>.</p> * * @return a {@link java.lang.String} object. */ public String getReturnType() { return returnType; } private boolean returnThis = false; /** * <p>Getter for the field <code>returnThis</code>.</p> * * @return a boolean. */ public boolean getReturnThis() { return returnThis; } private int returnAmount = 0; /** * <p>Getter for the field <code>returnAmount</code>.</p> * * @return a int. */ public int getReturnAmount() { return returnAmount; } /** * <p>hasNoManaCost.</p> * * @return a boolean. */ public boolean hasNoManaCost() { return manaCost.equals("") || manaCost.equals("0"); } private String manaCost = ""; /** * <p>getMana.</p> * * @return a {@link java.lang.String} object. */ public String getMana() { return manaCost; } // Only used for Human to pay for non-X cost first /** * <p>setMana.</p> * * @param sCost a {@link java.lang.String} object. */ public void setMana(String sCost) { manaCost = sCost; } /** * <p>hasNoXManaCost.</p> * * @return a boolean. */ public boolean hasNoXManaCost() { return manaXCost == 0; } private int manaXCost = 0; /** * <p>getXMana.</p> * * @return a int. */ public int getXMana() { return manaXCost; } /** * <p>setXMana.</p> * * @param xCost a int. */ public void setXMana(int xCost) { manaXCost = xCost; } /** * <p>isOnlyManaCost.</p> * * @return a boolean. */ public boolean isOnlyManaCost() { return !sacCost && !exileCost && !exileFromHandCost && !exileFromGraveCost && !exileFromTopCost && !tapCost && !tapXTypeCost && !untapCost && !subtractCounterCost && !addCounterCost && !lifeCost && !discardCost && !returnCost; } /** * <p>getTotalMana.</p> * * @return a {@link java.lang.String} object. */ public String getTotalMana() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < manaXCost; i++) sb.append("X "); if (!hasNoManaCost()) sb.append(manaCost); if (sb.toString().equals("")) return "0"; return sb.toString().trim(); } private String name; /** * <p>Constructor for Cost.</p> * * @param parse a {@link java.lang.String} object. * @param cardName a {@link java.lang.String} object. * @param bAbility a boolean. */ public Cost(String parse, String cardName, boolean bAbility) { isAbility = bAbility; // when adding new costs for cost string, place them here name = cardName; String tapXStr = "tapXType<"; if (parse.contains(tapXStr)) { tapXTypeCost = true; String[] splitStr = abCostParse(parse, tapXStr, 2); parse = abUpdateParse(parse, tapXStr); tapXTypeAmount = Integer.parseInt(splitStr[0]); tapXType = splitStr[1]; } String subStr = "SubCounter<"; if (parse.contains(subStr)) { // SubCounter<NumCounters/CounterType> subtractCounterCost = true; String[] splitStr = abCostParse(parse, subStr, 2); parse = abUpdateParse(parse, subStr); counterAmount = Integer.parseInt(splitStr[0]); counterType = Counters.valueOf(splitStr[1]); } String addStr = "AddCounter<"; if (parse.contains(addStr)) { // AddCounter<NumCounters/CounterType> addCounterCost = true; String[] splitStr = abCostParse(parse, addStr, 2); parse = abUpdateParse(parse, addStr); counterAmount = Integer.parseInt(splitStr[0]); counterType = Counters.valueOf(splitStr[1]); } String lifeStr = "PayLife<"; if (parse.contains(lifeStr)) { // PayLife<LifeCost> lifeCost = true; String[] splitStr = abCostParse(parse, lifeStr, 1); parse = abUpdateParse(parse, lifeStr); lifeAmount = Integer.parseInt(splitStr[0]); } String discStr = "Discard<"; if (parse.contains(discStr)) { // Discard<NumCards/DiscardType> discardCost = true; String[] splitStr = abCostParse(parse, discStr, 2); parse = abUpdateParse(parse, discStr); discardAmount = Integer.parseInt(splitStr[0]); discardType = splitStr[1]; discardThis = (discardType.equals("CARDNAME")); } String sacStr = "Sac<"; if (parse.contains(sacStr)) { // TODO: maybe separate SacThis from SacType? not sure if any card would use both sacCost = true; String[] splitStr = abCostParse(parse, sacStr, 2); parse = abUpdateParse(parse, sacStr); if (splitStr[0].equals("X")) sacX = true; else if (splitStr[0].equals("All")) sacAll = true; else sacAmount = Integer.parseInt(splitStr[0]); sacType = splitStr[1]; sacThis = (sacType.equals("CARDNAME")); } String exileStr = "Exile<"; if (parse.contains(exileStr)) { exileCost = true; String[] splitStr = abCostParse(parse, exileStr, 2); parse = abUpdateParse(parse, exileStr); exileAmount = Integer.parseInt(splitStr[0]); exileType = splitStr[1]; exileThis = (exileType.equals("CARDNAME")); } String exileFromHandStr = "ExileFromHand<"; if (parse.contains(exileFromHandStr)) { exileFromHandCost = true; String[] splitStr = abCostParse(parse, exileFromHandStr, 2); parse = abUpdateParse(parse, exileFromHandStr); exileFromHandAmount = Integer.parseInt(splitStr[0]); exileFromHandType = splitStr[1]; exileFromHandThis = (exileFromHandType.equals("CARDNAME")); } String exileFromGraveStr = "ExileFromGrave<"; if (parse.contains(exileFromGraveStr)) { exileFromGraveCost = true; String[] splitStr = abCostParse(parse, exileFromGraveStr, 2); parse = abUpdateParse(parse, exileFromGraveStr); exileFromGraveAmount = Integer.parseInt(splitStr[0]); exileFromGraveType = splitStr[1]; exileFromGraveThis = (exileFromGraveType.equals("CARDNAME")); } String exileFromTopStr = "ExileFromTop<"; if (parse.contains(exileFromTopStr)) { exileFromTopCost = true; String[] splitStr = abCostParse(parse, exileFromTopStr, 2); parse = abUpdateParse(parse, exileFromTopStr); exileFromTopAmount = Integer.parseInt(splitStr[0]); exileFromTopType = splitStr[1]; exileFromTopThis = false; } String returnStr = "Return<"; if (parse.contains(returnStr)) { returnCost = true; String[] splitStr = abCostParse(parse, returnStr, 2); parse = abUpdateParse(parse, returnStr); returnAmount = Integer.parseInt(splitStr[0]); returnType = splitStr[1]; returnThis = (returnType.equals("CARDNAME")); } if (parse.contains("Untap")) { untapCost = true; parse = parse.replace("Untap", "").trim(); } if (parse.contains("Q")) { untapCost = true; parse = parse.replace("Q", "").trim(); } if (parse.contains("T")) { tapCost = true; parse = parse.replace("T", ""); parse = parse.trim(); } String stripXCost = parse.replaceAll("X", ""); manaXCost = parse.length() - stripXCost.length(); manaCost = stripXCost.trim(); if (manaCost.equals("")) manaCost = "0"; } /** * <p>abCostParse.</p> * * @param parse a {@link java.lang.String} object. * @param subkey a {@link java.lang.String} object. * @param numParse a int. * @return an array of {@link java.lang.String} objects. */ String[] abCostParse(String parse, String subkey, int numParse) { int startPos = parse.indexOf(subkey); int endPos = parse.indexOf(">", startPos); String str = parse.substring(startPos, endPos); str = str.replace(subkey, ""); String[] splitStr = str.split("/", numParse); return splitStr; } /** * <p>abUpdateParse.</p> * * @param parse a {@link java.lang.String} object. * @param subkey a {@link java.lang.String} object. * @return a {@link java.lang.String} object. */ String abUpdateParse(String parse, String subkey) { int startPos = parse.indexOf(subkey); int endPos = parse.indexOf(">", startPos); String str = parse.substring(startPos, endPos + 1); return parse.replace(str, "").trim(); } /** * <p>changeCost.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. */ public void changeCost(SpellAbility sa) { if (getTotalMana() != "0") { // 11/15/10 use getTotalMana() to account for X reduction String mana = getTotalMana(); manaCost = AllZone.getGameAction().getSpellCostChange(sa, new ManaCost(mana)).toString(); } } /** * <p>refundPaidCost.</p> * * @param source a {@link forge.Card} object. */ public void refundPaidCost(Card source) { // prereq: isUndoable is called first if (tapCost) source.untap(); else if (untapCost) source.tap(); if (subtractCounterCost) source.addCounterFromNonEffect(counterType, counterAmount); else if (addCounterCost) source.subtractCounter(counterType, counterAmount); // refund chained mana abilities? } /** * <p>isUndoable.</p> * * @return a boolean. */ public boolean isUndoable() { return !(sacCost || exileCost || exileFromHandCost || exileFromGraveCost || tapXTypeCost || discardCost || returnCost || lifeCost || exileFromTopCost) && hasNoXManaCost() && hasNoManaCost(); } /** * <p>isReusuableResource.</p> * * @return a boolean. */ public boolean isReusuableResource() { return !(sacCost || exileCost || exileFromHandCost || tapXTypeCost || discardCost || returnCost || lifeCost) && isAbility; // TODO: add/sub counter? Maybe check if it's we're adding a positive counter, or removing a negative counter } /** * <p>toString.</p> * * @return a {@link java.lang.String} object. */ public String toString() { if (isAbility) return abilityToString(); else return spellToString(true); } // maybe add a conversion method that turns the amounts into words 1=a(n), 2=two etc. /** * <p>toStringAlt.</p> * * @return a {@link java.lang.String} object. */ public String toStringAlt() { return spellToString(false); } /** * <p>spellToString.</p> * * @param bFlag a boolean. * @return a {@link java.lang.String} object. */ private String spellToString(boolean bFlag) { StringBuilder cost = new StringBuilder(); if (bFlag) cost.append("As an additional cost to cast ").append(name).append(", "); boolean first = true; if (!bFlag) { // usually no additional mana cost for spells // only three Alliances cards have additional mana costs, but they are basically kicker/multikicker if (!getTotalMana().equals("0")) { cost.append("pay ").append(getTotalMana()); first = false; } } if (tapCost || untapCost) { // tap cost for spells will not be in this form. } if (subtractCounterCost || addCounterCost) { // add counterCost only appears in this form, which is currently on supported: // put a -1/-1 counter on a creature you control. // subtractCounter for spells will not be in this form } if (lifeCost) { if (first) cost.append("pay "); else cost.append("and pay "); cost.append(lifeAmount); cost.append(" Life"); first = false; } if (discardCost) { cost.append(discardString(first)); first = false; } if (sacCost) { cost.append(sacString(first)); first = false; } if (exileCost) { cost.append(exileString(first)); first = false; } if (exileFromHandCost) { cost.append(exileFromHandString(first)); first = false; } if (exileFromGraveCost) { cost.append(exileFromGraveString(first)); first = false; } if (exileFromTopCost) { cost.append(exileFromTopString(first)); first = false; } if (returnCost) { cost.append(returnString(first)); first = false; } if (first) return ""; if (bFlag) cost.append(".").append("\n"); return cost.toString(); } /** * <p>abilityToString.</p> * * @return a {@link java.lang.String} object. */ private String abilityToString() { StringBuilder cost = new StringBuilder(); boolean first = true; if (manaXCost > 0) { for (int i = 0; i < manaXCost; i++) { cost.append("X").append(" "); } first = false; } if (!(manaCost.equals("0") || manaCost.equals(""))) { cost.append(manaCost); first = false; } if (tapCost) { if (first) cost.append("Tap"); else cost.append(", tap"); first = false; } if (untapCost) { if (first) cost.append("Untap "); else cost.append(", untap"); first = false; } if (tapXTypeCost) { if (first) cost.append("Tap "); else cost.append(", tap "); cost.append(convertIntAndTypeToWords(tapXTypeAmount, "untapped " + tapXType)); cost.append(" you control"); // cost.append(tapXType); // needs IsValid String converter first = false; } if (subtractCounterCost) { if (counterType.getName().equals("Loyalty")) cost.append("-").append(counterAmount); else { if (first) cost.append("Remove "); else cost.append(", remove "); cost.append(convertIntAndTypeToWords(counterAmount, counterType.getName() + " counter")); cost.append(" from "); cost.append(name); } first = false; } if (addCounterCost) { if (counterType.getName().equals("Loyalty")) cost.append("+").append(counterAmount); else { if (first) cost.append("Put "); else cost.append(", put "); cost.append(convertIntAndTypeToWords(counterAmount, counterType.getName() + " counter")); cost.append(" on "); cost.append(name); } first = false; } if (lifeCost) { if (first) cost.append("Pay "); else cost.append(", Pay "); cost.append(lifeAmount); cost.append(" Life"); first = false; } if (discardCost) { cost.append(discardString(first)); first = false; } if (sacCost) { cost.append(sacString(first)); first = false; } if (exileCost) { cost.append(exileString(first)); first = false; } if (exileFromHandCost) { cost.append(exileFromHandString(first)); first = false; } if (exileFromGraveCost) { cost.append(exileFromGraveString(first)); first = false; } if (exileFromTopCost) { cost.append(exileFromTopString(first)); first = false; } if (returnCost) { cost.append(returnString(first)); first = false; } if (first) // No costs, append 0 cost.append("0"); cost.append(": "); return cost.toString(); } /** * <p>discardString.</p> * * @param first a boolean. * @return a {@link java.lang.String} object. */ public String discardString(boolean first) { StringBuilder cost = new StringBuilder(); if (first) { if (isAbility) cost.append("Discard "); else cost.append("discard "); } else { if (isAbility) cost.append(", discard "); else cost.append("and discard "); } if (discardThis) { cost.append(name); } else if (discardType.equals("Hand")) { cost.append("your hand"); } else if (discardType.equals("LastDrawn")) { cost.append("last drawn card"); } else { if (!discardType.equals("Any") && !discardType.equals("Card") && !discardType.equals("Random")) { cost.append(convertIntAndTypeToWords(discardAmount, discardType + " card")); } else cost.append(convertIntAndTypeToWords(discardAmount, "card")); if (discardType.equals("Random")) cost.append(" at random"); } return cost.toString(); } /** * <p>sacString.</p> * * @param first a boolean. * @return a {@link java.lang.String} object. */ public String sacString(boolean first) { StringBuilder cost = new StringBuilder(); if (first) { if (isAbility) cost.append("Sacrifice "); else cost.append("sacrifice "); } else { cost.append(", sacrifice "); } if (sacType.equals("CARDNAME")) cost.append(name); else cost.append(convertIntAndTypeToWords(sacAmount, sacType)); return cost.toString(); } /** * <p>exileString.</p> * * @param first a boolean. * @return a {@link java.lang.String} object. */ public String exileString(boolean first) { StringBuilder cost = new StringBuilder(); if (first) { if (isAbility) cost.append("Exile "); else cost.append("exile "); } else { cost.append(", exile "); } if (exileType.equals("CARDNAME")) cost.append(name); else cost.append(convertIntAndTypeToWords(exileAmount, exileType)); return cost.toString(); } /** * <p>exileFromHandString.</p> * * @param first a boolean. * @return a {@link java.lang.String} object. */ public String exileFromHandString(boolean first) { StringBuilder cost = new StringBuilder(); if (first) { if (isAbility) cost.append("Exile "); else cost.append("exile "); } else { cost.append(", exile "); } if (exileType.equals("CARDNAME")) cost.append(name); else { cost.append(convertIntAndTypeToWords(exileFromHandAmount, exileFromHandType)); cost.append(" from your hand"); } return cost.toString(); } /** * <p>exileFromGraveString.</p> * * @param first a boolean. * @return a {@link java.lang.String} object. */ public String exileFromGraveString(boolean first) { StringBuilder cost = new StringBuilder(); if (first) { if (isAbility) cost.append("Exile "); else cost.append("exile "); } else { cost.append(", exile "); } if (exileType.equals("CARDNAME")) cost.append(name); else { cost.append(convertIntAndTypeToWords(exileFromGraveAmount, exileFromGraveType)); cost.append(" from your graveyard"); } return cost.toString(); } /** * <p>exileFromTopString.</p> * * @param first a boolean. * @return a {@link java.lang.String} object. */ public String exileFromTopString(boolean first) { StringBuilder cost = new StringBuilder(); if (first) { if (isAbility) cost.append("Exile "); else cost.append("exile "); } else { cost.append(", Exile "); } if (exileType.equals("CARDNAME")) cost.append(name).append(" from the top of you library"); else { cost.append("the top "); cost.append(convertIntAndTypeToWords(exileFromTopAmount, exileFromTopType)); cost.append(" of your library"); } return cost.toString(); } /** * <p>returnString.</p> * * @param first a boolean. * @return a {@link java.lang.String} object. */ public String returnString(boolean first) { StringBuilder cost = new StringBuilder(); if (first) { if (isAbility) cost.append("Return "); else cost.append("return "); } else { cost.append(", return "); } String pronoun = "its"; if (returnType.equals("CARDNAME")) cost.append(name); else { cost.append(convertIntAndTypeToWords(returnAmount, returnType)); if (returnAmount > 1) { pronoun = "their"; } cost.append(" you control"); } cost.append(" to ").append(pronoun).append(" owner's hand"); return cost.toString(); } // TODO: If an Ability_Cost needs to pay more than 10 of something, fill this array as appropriate /** Constant <code>numNames="{zero, a, two, three, four, five, six, "{trunked}</code> */ private static final String[] numNames = {"zero", "a", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"}; /** Constant <code>vowelPattern</code> */ private static final Pattern vowelPattern = Pattern.compile("^[aeiou]", Pattern.CASE_INSENSITIVE); /** * <p>convertIntAndTypeToWords.</p> * * @param i a int. * @param type a {@link java.lang.String} object. * @return a {@link java.lang.String} object. */ private String convertIntAndTypeToWords(int i, String type) { StringBuilder sb = new StringBuilder(); if (i >= numNames.length) { sb.append(i); } else if (1 == i && vowelPattern.matcher(type).find()) sb.append("an"); else sb.append(numNames[i]); sb.append(" "); sb.append(type); if (1 != i) sb.append("s"); return sb.toString(); } }