package net.aufdemrand.denizen.objects; import net.aufdemrand.denizen.nms.NMSHandler; import net.aufdemrand.denizen.objects.notable.NotableManager; import net.aufdemrand.denizen.objects.properties.item.*; import net.aufdemrand.denizen.scripts.containers.core.BookScriptContainer; import net.aufdemrand.denizen.scripts.containers.core.ItemScriptContainer; import net.aufdemrand.denizen.scripts.containers.core.ItemScriptHelper; import net.aufdemrand.denizen.tags.BukkitTagContext; import net.aufdemrand.denizen.utilities.debugging.dB; import net.aufdemrand.denizencore.objects.*; import net.aufdemrand.denizencore.objects.notable.Notable; import net.aufdemrand.denizencore.objects.notable.Note; import net.aufdemrand.denizencore.objects.properties.Property; import net.aufdemrand.denizencore.objects.properties.PropertyParser; import net.aufdemrand.denizencore.scripts.ScriptRegistry; import net.aufdemrand.denizencore.tags.Attribute; import net.aufdemrand.denizencore.tags.TagContext; import net.aufdemrand.denizencore.utilities.CoreUtilities; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Item; import org.bukkit.inventory.ItemStack; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public class dItem implements dObject, Notable, Adjustable { final static Pattern ITEM_PATTERN = Pattern.compile("(?:item:)?([\\w ]+)[:,]?(\\d+)?\\[?(\\d+)?\\]?", // TODO: Wot. Pattern.CASE_INSENSITIVE); final static Pattern item_by_saved = Pattern.compile("(i@)?(.+)\\[?(\\d+)?\\]?"); // TODO: Wot. final public static String itemscriptIdentifier = "ยง0id:"; ////////////////// // OBJECT FETCHER //////////////// public static dItem valueOf(String string) { return valueOf(string, null); } @Fetchable("i") public static dItem valueOf(String string, TagContext context) { if (context == null) { return valueOf(string, null, null); } else { return valueOf(string, ((BukkitTagContext) context).player, ((BukkitTagContext) context).npc); } } /** * Gets a Item Object from a string form. * * @param string The string or dScript argument String * @param player The dPlayer to be used for player contexts * where applicable. * @param npc The dNPC to be used for NPC contexts * where applicable. * @return an Item, or null if incorrectly formatted */ public static dItem valueOf(String string, dPlayer player, dNPC npc) { if (string == null || string.equals("")) { return null; } Matcher m; dItem stack = null; /////// // Handle objects with properties through the object fetcher m = ObjectFetcher.DESCRIBED_PATTERN.matcher(string); if (m.matches()) { return ObjectFetcher.getObjectFrom(dItem.class, string, new BukkitTagContext(player, npc, false, null, true, null)); } //////// // Match @object format for saved dItems m = item_by_saved.matcher(string); if (m.matches() && NotableManager.isSaved(m.group(2)) && NotableManager.isType(m.group(2), dItem.class)) { stack = (dItem) NotableManager.getSavedObject(m.group(2)); if (m.group(3) != null) { stack.setAmount(Integer.valueOf(m.group(3))); } return stack; } string = string.replace("i@", ""); m = ITEM_PATTERN.matcher(string); if (m.matches()) { try { /////// // Match item and book script custom items if (ScriptRegistry.containsScript(m.group(1), ItemScriptContainer.class)) { // Get item from script stack = ScriptRegistry.getScriptContainerAs (m.group(1), ItemScriptContainer.class).getItemFrom(player, npc); } else if (ScriptRegistry.containsScript(m.group(1), BookScriptContainer.class)) { // Get book from script stack = ScriptRegistry.getScriptContainerAs (m.group(1), BookScriptContainer.class).getBookFrom(player, npc); } if (stack != null) { if (m.group(3) != null) { stack.setAmount(Integer.valueOf(m.group(3))); } return stack; } } catch (Exception e) { // Just a catch, might be a regular item... } /////// // Match Bukkit/Minecraft standard items format try { String material = m.group(1).toUpperCase(); if (aH.matchesInteger(material)) { stack = new dItem(Integer.valueOf(material)); } else { dMaterial mat = dMaterial.valueOf(material); stack = new dItem(mat.getMaterial()); if (mat.hasData()) { stack.setDurability(mat.getData()); } } if (m.group(2) != null) { stack.setDurability(Short.valueOf(m.group(2))); } if (m.group(3) != null) { stack.setAmount(Integer.valueOf(m.group(3))); } return stack; } catch (Exception e) { if (!string.equalsIgnoreCase("none") && !nope) { dB.log("Does not match a valid item ID or material: " + string); } } } if (!nope) { dB.log("valueOf dItem returning null: " + string); } // No match! Return null. return null; } // :( boolean for technicality, can be fixed // by making matches() method better. public static boolean nope = false; public static boolean matches(String arg) { if (arg == null) { return false; } // All dObjects should 'match' if there is a proper // ObjectFetcher identifier if (CoreUtilities.toLowerCase(arg).startsWith("i@")) { return true; } // Try a quick and simple item/book script match if (ScriptRegistry.containsScript(arg, ItemScriptContainer.class)) { return true; } else if (ScriptRegistry.containsScript(arg, BookScriptContainer.class)) { return true; } // TODO: Make this better. Probably creating some unnecessary // objects by doing this :( nope = true; if (valueOf(arg) != null) { nope = false; return true; } nope = false; return false; } /////////////// // Constructors ///////////// public dItem(Material material) { this(new ItemStack(material)); } public dItem(int itemId) { this(new ItemStack(itemId)); } public dItem(Material material, int qty) { this(new ItemStack(material, qty)); } public dItem(dMaterial material, int qty) { this(new ItemStack(material.getMaterial(), qty, (short) 0, material.getData())); } public dItem(int type, int qty) { this(new ItemStack(type, qty)); } public dItem(ItemStack item) { if (item == null) { this.item = new ItemStack(Material.AIR, 0); } else { this.item = item; } } public dItem(Item item) { this(item.getItemStack()); } ///////////////////// // INSTANCE FIELDS/METHODS ///////////////// // Bukkit itemstack associated private ItemStack item = null; public ItemStack getItemStack() { return item; } public void setItemStack(ItemStack item) { this.item = item; } // Compare item to item. // -1 indicates it is not a match // 0 indicates it is a perfect match // 1 indicates the item being matched against // was probably originally alike, but may have been // modified or enhanced. public int comparesTo(dItem item) { return comparesTo(item.getItemStack()); } public int comparesTo(ItemStack item) { if (item == null) { return -1; } int determination = 0; ItemStack compared = getItemStack(); ItemStack compared_to = item; // Will return -1 if these are not the same // Material IDs if (compared.getTypeId() != compared_to.getTypeId()) { return -1; } // If compared_to has item meta, and compared does not, return -1 if (compared_to.hasItemMeta()) { if (!compared.hasItemMeta()) { return -1; } // If compared_to has a display name, and compared does not, return -1 if (compared_to.getItemMeta().hasDisplayName()) { if (!compared.getItemMeta().hasDisplayName()) { return -1; } // If compared_to's display name does not at least start with compared's item name, // return -1. if (compared_to.getItemMeta().getDisplayName().toUpperCase() .startsWith(compared.getItemMeta().getDisplayName().toUpperCase())) { // If the compared item has a longer display name than compared_to, // it is similar, but modified. Perhaps 'engraved' or something? if (compared.getItemMeta().getDisplayName().length() > compared_to.getItemMeta().getDisplayName().length()) { determination++; } } else { return -1; } } // If compared_to has lore, and compared does not, return -1 if (compared_to.getItemMeta().hasLore()) { if (!compared.getItemMeta().hasLore()) { return -1; } // If compared doesn't have a piece of lore contained in compared_to, return -1 for (String lore : compared_to.getItemMeta().getLore()) { if (!compared.getItemMeta().getLore().contains(lore)) { return -1; } } // If the compared item has more lore than compared to, it is similar, but modified. // Still qualifies for a match, but it seems the item may be a 'better' item, so increase // the determination. if (compared.getItemMeta().getLore().size() > compared_to.getItemMeta().getLore().size()) { determination++; } } if (!compared_to.getItemMeta().getEnchants().isEmpty()) { if (compared.getItemMeta().getEnchants().isEmpty()) { return -1; } for (Map.Entry<Enchantment, Integer> enchant : compared_to.getItemMeta().getEnchants().entrySet()) { if (!compared.getItemMeta().getEnchants().containsKey(enchant.getKey()) || compared.getItemMeta().getEnchants().get(enchant.getKey()) < enchant.getValue()) { return -1; } } if (compared.getItemMeta().getEnchants().size() > compared_to.getItemMeta().getEnchants().size()) { determination++; } } } if (isRepairable()) { if (compared.getDurability() < compared_to.getDurability()) { determination++; } } else // Check data if (getItemStack().getData().getData() != item.getData().getData()) { return -1; } return determination; } // Additional helper methods public void setStackSize(int size) { getItemStack().setAmount(size); } /** * Check whether this item contains a lore that starts * with a certain prefix. * * @param prefix The prefix * @return True if it does, otherwise false */ public boolean containsLore(String prefix) { if (getItemStack().hasItemMeta() && getItemStack().getItemMeta().hasLore()) { for (String itemLore : getItemStack().getItemMeta().getLore()) { if (itemLore.startsWith(prefix)) { return true; } } } return false; } /** * Get the lore from this item that starts with a * certain prefix. * * @param prefix The prefix * @return String The lore */ public String getLore(String prefix) { for (String itemLore : getItemStack().getItemMeta().getLore()) { if (itemLore.startsWith(prefix)) { return itemLore.substring(prefix.length()); } } return ""; } /** * Check whether this item contains the lore specific * to item scripts. * * @return True if it does, otherwise false */ public boolean isItemscript() { return ItemScriptHelper.isItemscript(item); } public String getScriptName() { ItemScriptContainer cont = ItemScriptHelper.getItemScriptContainer(item); if (cont != null) { return cont.getName(); } else { return null; } } public dMaterial getMaterial() { return dMaterial.getMaterialFrom(getItemStack().getType(), getItemStack().getData().getData()); } public String getMaterialName() { return CoreUtilities.toLowerCase(getItemStack().getType().name()); } public void setAmount(int value) { if (item != null) { item.setAmount(value); } } public int getMaxStackSize() { return item.getMaxStackSize(); } public int getAmount() { if (item != null) { return item.getAmount(); } else { return 0; } } public void setDurability(short value) { if (item != null) { item.setDurability(value); } } public void setData(byte value) { if (item != null) { item.getData().setData(value); } } public boolean isRepairable() { return item.getType().getMaxDurability() > 0; } ////////////////////////////// // DSCRIPT ARGUMENT METHODS ///////////////////////// private String prefix = getObjectType(); @Override public String getPrefix() { return prefix; } @Override public dItem setPrefix(String prefix) { this.prefix = prefix; return this; } @Override public String getObjectType() { return "Item"; } @Override public String debug() { return "<G>" + prefix + "='<Y>" + identify() + "<G>' "; } @Override public String identify() { if (item == null) { return "i@air"; } if (item.getTypeId() != 0) { // If saved item, return that if (isUnique()) { return "i@" + NotableManager.getSavedId(this) + PropertyParser.getPropertiesString(this); } // If not a saved item, but is a custom item, return the script id else if (isItemscript()) { return "i@" + getScriptName() + PropertyParser.getPropertiesString(this); } } // Else, return the material name if ((item.getDurability() >= 16 || item.getDurability() < 0) && item.getType() != Material.AIR) { return "i@" + getMaterial().realName() + "," + item.getDurability() + PropertyParser.getPropertiesString(this); } return "i@" + getMaterial().identify().replace("m@", "") + PropertyParser.getPropertiesString(this); } @Override public String identifySimple() { if (item == null) { return "null"; } if (item.getTypeId() != 0) { // If saved item, return that if (isUnique()) { return "i@" + NotableManager.getSavedId(this); } // If not a saved item, but is a custom item, return the script id else if (isItemscript()) { return "i@" + getScriptName(); } } // Else, return the material name return "i@" + identifyMaterial().replace("m@", ""); } // Special-case that essentially fetches the material of the items and uses its 'identify()' method public String identifyMaterial() { return dMaterial.getMaterialFrom(item.getType(), item.getData().getData()).identifySimple(); } // Special-case that essentially fetches the material of the items and uses its 'identify()' method public String identifyMaterialNoIdentifier() { return dMaterial.getMaterialFrom(item.getType(), item.getData().getData()).identifySimpleNoIdentifier(); } public String identifyNoIdentifier() { if (item == null) { return "null"; } if (item.getTypeId() != 0) { // If saved item, return that if (isUnique()) { return NotableManager.getSavedId(this) + (item.getAmount() == 1 ? "" : "[quantity=" + item.getAmount() + "]"); } // If not a saved item, but is a custom item, return the script id else if (isItemscript()) { return getScriptName() + (item.getAmount() == 1 ? "" : "[quantity=" + item.getAmount() + "]"); } } // Else, return the material name if (item.getDurability() >= 16 || item.getDurability() < 0) { return getMaterial().realName() + "," + item.getDurability() + PropertyParser.getPropertiesString(this); } return getMaterial().identifyNoIdentifier() + PropertyParser.getPropertiesString(this); } public String identifySimpleNoIdentifier() { if (item == null) { return "null"; } if (item.getTypeId() != 0) { // If saved item, return that if (isUnique()) { return NotableManager.getSavedId(this); } // If not a saved item, but is a custom item, return the script id else if (isItemscript()) { return getScriptName(); } } // Else, return the material name return identifyMaterialNoIdentifier(); } public String getFullString() { return "i@" + (isItemscript() ? getScriptName() : getMaterial().realName()) + "," + item.getDurability() + PropertyParser.getPropertiesString(this); } @Override public String toString() { return identify(); } @Override public boolean isUnique() { return NotableManager.isSaved(this); } @Override @Note("Items") public String getSaveObject() { return getFullString(); } @Override public void makeUnique(String id) { NotableManager.saveAs(this, id); } @Override public void forget() { NotableManager.remove(this); } public static void registerTags() { // <--[tag] // @attribute <i@item.id> // @returns Element(Number) // @group deprecated info // @description // Returns the item ID number of the item. // EG, a stone item will return 1. // Note that ID numbers are considered deprecated - you should use the names instead! // --> registerTag("id", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(((dItem) object).getItemStack().getTypeId()) .getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <i@item.data> // @returns Element(Number) // @group deprecated info // @description // Returns the data value of the material of the item. // EG, white wool will return 0, while red wool will return 14. // Note that data values are considered deprecated - you should use the names instead! // --> registerTag("data", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(((dItem) object).getItemStack().getData().getData()) .getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <i@item.repairable> // @returns Element(Boolean) // @group properties // @description // Returns whether the item can be repaired. // If this returns true, it will enable access to: // <@link mechanism dItem.durability>, <@link tag i@item.max_durability>, // and <@link tag i@item.durability> // --> registerTag("repairable", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(ItemDurability.describes(object)) .getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <i@item.is_crop> // @returns Element(Boolean) // @group properties // @description // Returns whether the item is a growable crop. // If this returns true, it will enable access to: // <@link mechanism dItem.plant_growth> and <@link tag i@item.plant_growth> // --> registerTag("is_crop", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(ItemPlantgrowth.describes(object)) .getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <i@item.is_book> // @returns Element(Boolean) // @group properties // @description // Returns whether the item is considered an editable book. // If this returns true, it will enable access to: // <@link mechanism dItem.book>, <@link tag i@item.book>, // <@link tag i@item.book.author>, <@link tag i@item.book.title>, // <@link tag i@item.book.page_count>, <@link tag i@item.book.get_page[<#>]>, // and <@link tag i@item.book.pages> // --> registerTag("is_book", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(ItemBook.describes(object)) .getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <i@item.is_dyeable> // @returns Element(Boolean) // @group properties // Returns whether the item can have a dye. // If this returns true, it will enable access to: // <@link mechanism dItem.dye>, and <@link tag i@item.dye_color> // --> registerTag("is_dyeable", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(ItemDye.describes(object)) .getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <i@item.is_firework> // @returns Element(Boolean) // @group properties // Returns whether the item is a firework. // If this returns true, it will enable access to: // <@link mechanism dItem.firework>, and <@link tag i@item.firework> // --> registerTag("is_firework", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(ItemFirework.describes(object)) .getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <i@item.material> // @returns dMaterial // @group conversion // @description // Returns the dMaterial that is the basis of the item. // EG, a stone with lore and a display name, etc. will return only "m@stone". // --> registerTag("material", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return ((dItem) object).getMaterial().getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <i@item.json> // @returns Element // @group conversion // @description // Returns the item converted to a raw JSON object with one layer of escaping for network transmission. // EG, via /tellraw. // EXAMPLE USAGE: execute as_server 'tellraw <player.name> // {"text":"","extra":[{"text":"This is the item in your hand ","color":"white"}, // {"text":"Item","color":"white","hoverEvent":{"action":"show_item","value":"{<player.item_in_hand.json>}"}}]}' // --> registerTag("json", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(NMSHandler.getInstance().getItemHelper().getJsonString(((dItem) object).item)) .getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <i@item.full> // @returns Element // @group conversion // @description // Returns a full reusable item identification for this item, with extra, generally useless data. // --> registerTag("full", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(((dItem) object).getFullString()).getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <i@item.simple> // @returns Element // @group conversion // @description // Returns a simple reusable item identification for this item, with minimal extra data. // --> registerTag("simple", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(((dItem) object).identifySimple()).getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <i@item.notable_name> // @returns Element // @description // Gets the name of a Notable dItem. If the item isn't noted, // this is null. // --> registerTag("notable_name", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { String notname = NotableManager.getSavedId((dItem) object); if (notname == null) { return null; } return new Element(notname).getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <i@item.scriptname> // @returns Element // @group scripts // @description // Returns the script name of the item if it was created by an item script. // --> registerTag("scriptname", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { if (((dItem) object).isItemscript()) { return new Element(((dItem) object).getScriptName()) .getAttribute(attribute.fulfill(1)); } return null; } }); // <--[tag] // @attribute <i@item.script> // @returns dScript // @group scripts // @description // Returns the script of the item if it was created by an item script. // --> registerTag("script", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { if (((dItem) object).isItemscript()) { return new dScript(((dItem) object).getScriptName()) .getAttribute(attribute.fulfill(1)); } return null; } }); registerTag("prefix", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(((dItem) object).prefix) .getAttribute(attribute.fulfill(1)); } }); registerTag("debug.log", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { dB.log(object.debug()); return new Element(Boolean.TRUE.toString()) .getAttribute(attribute.fulfill(2)); } }); registerTag("debug.no_color", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(ChatColor.stripColor(object.debug())) .getAttribute(attribute.fulfill(2)); } }); registerTag("debug", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element(object.debug()) .getAttribute(attribute.fulfill(1)); } }); // <--[tag] // @attribute <i@item.type> // @returns Element // @description // Always returns 'Item' for dItem objects. All objects fetchable by the Object Fetcher will return the // type of object that is fulfilling this attribute. // --> registerTag("type", new TagRunnable() { @Override public String run(Attribute attribute, dObject object) { return new Element("Item").getAttribute(attribute.fulfill(1)); } }); } public static HashMap<String, TagRunnable> registeredTags = new HashMap<String, TagRunnable>(); public static void registerTag(String name, TagRunnable runnable) { if (runnable.name == null) { runnable.name = name; } registeredTags.put(name, runnable); } ///////////////// // ATTRIBUTES ///////// @Override public String getAttribute(Attribute attribute) { if (attribute == null) { return null; } // TODO: Scrap getAttribute, make this functionality a core system String attrLow = CoreUtilities.toLowerCase(attribute.getAttributeWithoutContext(1)); TagRunnable tr = registeredTags.get(attrLow); if (tr != null) { if (!tr.name.equals(attrLow)) { net.aufdemrand.denizencore.utilities.debugging.dB.echoError(attribute.getScriptEntry() != null ? attribute.getScriptEntry().getResidingQueue() : null, "Using deprecated form of tag '" + tr.name + "': '" + attrLow + "'."); } return tr.run(attribute, this); } // // TODO: Replace the next 2 with something saner in the tag section // // <--[tag] // @attribute <i@item.formatted> // @returns Element // @group formatting // @description // Returns the formatted material name of the item to be used in a sentence. // Correctly uses singular and plural forms of item names, among other things. // --> if (attribute.startsWith("material.formatted")) { attribute.fulfill(1); } if (attribute.startsWith("formatted")) { String id = CoreUtilities.toLowerCase(getMaterial().realName()).replace('_', ' '); if (id.equals("air")) { return new Element("nothing") .getAttribute(attribute.fulfill(1)); } if (id.equals("ice") || id.equals("dirt")) { return new Element(id) .getAttribute(attribute.fulfill(1)); } if (getItemStack().getAmount() > 1) { if (id.equals("cactus")) { return new Element("cactuses") .getAttribute(attribute.fulfill(1)); } if (id.endsWith(" off")) { id = new String(id.substring(0, id.length() - 4)); } if (id.endsWith(" on")) { id = new String(id.substring(0, id.length() - 3)); } if (id.equals("rotten flesh") || id.equals("cooked fish") || id.equals("raw fish") || id.endsWith("s")) { return new Element(id) .getAttribute(attribute.fulfill(1)); } if (id.endsWith("y")) { return new Element(id.substring(0, id.length() - 1) + "ies") .getAttribute(attribute.fulfill(1)); // ex: lily -> lilies } if (id.endsWith("sh") || id.endsWith("ch")) { return new Element(id + "es") .getAttribute(attribute.fulfill(1)); } // else return new Element(id + "s") .getAttribute(attribute.fulfill(1)); // iron sword -> iron swords } else { if (id.equals("cactus")) { return new Element("a cactus").getAttribute(attribute.fulfill(1)); } if (id.endsWith("s")) { return new Element(id).getAttribute(attribute.fulfill(1)); } if (id.endsWith(" off")) { return new Element("a " + id.substring(0, id.length() - 4)) .getAttribute(attribute.fulfill(1)); } if (id.endsWith(" on")) { return new Element("a " + id.substring(0, id.length() - 3)) .getAttribute(attribute.fulfill(1)); } if (id.startsWith("a") || id.startsWith("e") || id.startsWith("i") || id.startsWith("o") || id.startsWith("u")) { return new Element("an " + id) .getAttribute(attribute.fulfill(1));// ex: emerald -> an emerald } // else return new Element("a " + id) .getAttribute(attribute.fulfill(1));// ex: diamond -> a diamond } } // Iterate through this object's properties' attributes for (Property property : PropertyParser.getProperties(this)) { String returned = property.getAttribute(attribute); if (returned != null) { return returned; } } return new Element(identify()).getAttribute(attribute); } public void applyProperty(Mechanism mechanism) { adjust(mechanism); } @Override public void adjust(Mechanism mechanism) { // Iterate through this object's properties' mechanisms for (Property property : PropertyParser.getProperties(this)) { property.adjust(mechanism); if (mechanism.fulfilled()) { break; } } if (!mechanism.fulfilled()) { mechanism.reportInvalid(); } } }