001    package org.bukkit.inventory;
002    
003    import java.util.HashMap;
004    import java.util.Map;
005    
006    import org.apache.commons.lang.Validate;
007    
008    import org.bukkit.Material;
009    import org.bukkit.material.MaterialData;
010    
011    /**
012     * Represents a shaped (ie normal) crafting recipe.
013     */
014    public class ShapedRecipe implements Recipe {
015        private ItemStack output;
016        private String[] rows;
017        private Map<Character, ItemStack> ingredients = new HashMap<Character, ItemStack>();
018    
019        /**
020         * Create a shaped recipe to craft the specified ItemStack. The
021         * constructor merely determines the result and type; to set the actual
022         * recipe, you'll need to call the appropriate methods.
023         *
024         * @param result The item you want the recipe to create.
025         * @see ShapedRecipe#shape(String...)
026         * @see ShapedRecipe#setIngredient(char, Material)
027         * @see ShapedRecipe#setIngredient(char, Material, int)
028         * @see ShapedRecipe#setIngredient(char, MaterialData)
029         */
030        public ShapedRecipe(ItemStack result) {
031            this.output = new ItemStack(result);
032        }
033    
034        /**
035         * Set the shape of this recipe to the specified rows. Each character
036         * represents a different ingredient; exactly what each character
037         * represents is set separately. The first row supplied corresponds with
038         * the upper most part of the recipe on the workbench e.g. if all three
039         * rows are supplies the first string represents the top row on the
040         * workbench.
041         *
042         * @param shape The rows of the recipe (up to 3 rows).
043         * @return The changed recipe, so you can chain calls.
044         */
045        public ShapedRecipe shape(final String... shape) {
046            Validate.notNull(shape, "Must provide a shape");
047            Validate.isTrue(shape.length > 0 && shape.length < 4, "Crafting recipes should be 1, 2, 3 rows, not ", shape.length);
048    
049            for (String row : shape) {
050                Validate.notNull(row, "Shape cannot have null rows");
051                Validate.isTrue(row.length() > 0 && row.length() < 4, "Crafting rows should be 1, 2, or 3 characters, not ", row.length());
052            }
053            this.rows = new String[shape.length];
054            for (int i = 0; i < shape.length; i++) {
055                this.rows[i] = shape[i];
056            }
057    
058            // Remove character mappings for characters that no longer exist in the shape
059            HashMap<Character, ItemStack> newIngredients = new HashMap<Character, ItemStack>();
060            for (String row : shape) {
061                for (Character c : row.toCharArray()) {
062                    newIngredients.put(c, ingredients.get(c));
063                }
064            }
065            this.ingredients = newIngredients;
066    
067            return this;
068        }
069    
070        /**
071         * Sets the material that a character in the recipe shape refers to.
072         *
073         * @param key The character that represents the ingredient in the shape.
074         * @param ingredient The ingredient.
075         * @return The changed recipe, so you can chain calls.
076         */
077        public ShapedRecipe setIngredient(char key, MaterialData ingredient) {
078            return setIngredient(key, ingredient.getItemType(), ingredient.getData());
079        }
080    
081        /**
082         * Sets the material that a character in the recipe shape refers to.
083         *
084         * @param key The character that represents the ingredient in the shape.
085         * @param ingredient The ingredient.
086         * @return The changed recipe, so you can chain calls.
087         */
088        public ShapedRecipe setIngredient(char key, Material ingredient) {
089            return setIngredient(key, ingredient, 0);
090        }
091    
092        /**
093         * Sets the material that a character in the recipe shape refers to.
094         *
095         * @param key The character that represents the ingredient in the shape.
096         * @param ingredient The ingredient.
097         * @param raw The raw material data as an integer.
098         * @return The changed recipe, so you can chain calls.
099         * @deprecated Magic value
100         */
101        @Deprecated
102        public ShapedRecipe setIngredient(char key, Material ingredient, int raw) {
103            Validate.isTrue(ingredients.containsKey(key), "Symbol does not appear in the shape:", key);
104    
105            // -1 is the old wildcard, map to Short.MAX_VALUE as the new one
106            if (raw == -1) {
107                raw = Short.MAX_VALUE;
108            }
109    
110            ingredients.put(key, new ItemStack(ingredient, 1, (short) raw));
111            return this;
112        }
113    
114        /**
115         * Get a copy of the ingredients map.
116         *
117         * @return The mapping of character to ingredients.
118         */
119        public Map<Character, ItemStack> getIngredientMap() {
120            HashMap<Character, ItemStack> result = new HashMap<Character, ItemStack>();
121            for (Map.Entry<Character, ItemStack> ingredient : ingredients.entrySet()) {
122                if (ingredient.getValue() == null) {
123                    result.put(ingredient.getKey(), null);
124                } else {
125                    result.put(ingredient.getKey(), ingredient.getValue().clone());
126                }
127            }
128            return result;
129        }
130    
131        /**
132         * Get the shape.
133         *
134         * @return The recipe's shape.
135         */
136        public String[] getShape() {
137            return rows.clone();
138        }
139    
140        /**
141         * Get the result.
142         *
143         * @return The result stack.
144         */
145        public ItemStack getResult() {
146            return output.clone();
147        }
148    }