/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.internal.providers.bankitems; import com.jcwhatever.nucleus.internal.NucMsg; import com.jcwhatever.nucleus.providers.bankitems.IBankItem; import com.jcwhatever.nucleus.providers.bankitems.IBankItemsAccount; import com.jcwhatever.nucleus.providers.bankitems.IBankItemsBank; import com.jcwhatever.nucleus.providers.bankitems.InsufficientItemsException; import com.jcwhatever.nucleus.storage.IDataNode; import com.jcwhatever.nucleus.utils.CollectionUtils; import com.jcwhatever.nucleus.utils.PreCon; import com.jcwhatever.nucleus.utils.items.ItemStackBuilder; import com.jcwhatever.nucleus.utils.items.ItemStackMatcher; import com.jcwhatever.nucleus.utils.items.MatchableItem; import com.jcwhatever.nucleus.utils.text.TextUtils; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.material.MaterialData; import javax.annotation.Nullable; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; /** * Nucleus implementation of {@link IBankItemsAccount}. */ class BankItemsAccount implements IBankItemsAccount { private final UUID _ownerId; private final BankItemsBank _bank; private final IDataNode _dataNode; private final Map<MatchableItem, BankItem> _items = new HashMap<>(10); private final Object _sync = new Object(); private final Date _created; volatile long _lastAccess; /** * Constructor. * * @param ownerId The ID of the account player owner. * @param bank Optional owning bank. * @param dataNode The data node. */ BankItemsAccount(UUID ownerId, @Nullable BankItemsBank bank, IDataNode dataNode) { PreCon.notNull(ownerId); PreCon.notNull(dataNode); _ownerId = ownerId; _bank = bank; _dataNode = dataNode; long created = dataNode.getLong("created", System.currentTimeMillis()); _created = new Date(created); _lastAccess = dataNode.getLong("last-access", created); load(); } @Override public UUID getOwnerId() { return _ownerId; } @Override public Date getCreatedDate() { return _created; } @Override public Date getLastAccess() { return new Date(_lastAccess); } @Nullable @Override public IBankItemsBank getBank() { return _bank; } @Override public int getBalance() { int balance = 0; updateLastAccess(); synchronized (_sync) { for (BankItem item : _items.values()) balance += item.getAmount(); } return balance; } @Override public int getBalance(Material material) { PreCon.notNull(material); ItemStack itemStack = new ItemStack(material); return getBalance(itemStack); } @Override public int getBalance(MaterialData materialData) { PreCon.notNull(materialData); ItemStack itemStack = new ItemStackBuilder(materialData).build(); return getBalance(itemStack); } @Override public int getBalance(ItemStack matchingStack) { PreCon.notNull(matchingStack); updateLastAccess(); synchronized (_sync) { BankItem item = _items.get( new MatchableItem(matchingStack.clone(), ItemStackMatcher.getTypeMetaDurability())); return item != null ? item.getAmount() : 0; } } @Override public int deposit(Material material, int amount) { PreCon.notNull(material); ItemStack itemStack = new ItemStack(material); return deposit(itemStack, amount); } @Override public int deposit(MaterialData materialData, int amount) { PreCon.notNull(materialData); ItemStack itemStack = new ItemStackBuilder(materialData).build(); return deposit(itemStack, amount); } @Override public int deposit(ItemStack itemStack) { return deposit(itemStack, itemStack.getAmount()); } @Override public int deposit(ItemStack itemStack, int amount) { PreCon.notNull(itemStack); PreCon.positiveNumber(amount); checkDisposed(); updateLastAccess(); MatchableItem wrapper = new MatchableItem(itemStack, ItemStackMatcher.getTypeMetaDurability()); synchronized (_sync) { BankItem item = _items.get(wrapper); if (amount == 0) return item != null ? item.getAmount() : 0; if (item == null) { UUID id = UUID.randomUUID(); IDataNode itemNode = _dataNode.getNode(id.toString()); itemNode.set("item", itemStack); item = new BankItem(id, itemStack, 0, itemNode); _items.put(wrapper, item); } item.deposit(amount); return item.getAmount(); } } @Override public List<ItemStack> withdraw() { checkDisposed(); updateLastAccess(); synchronized (_sync) { List<ItemStack> result = new ArrayList<>(_items.size() * 10); for (BankItem item : _items.values()) { for (IBankItem stack : item.getItems()) { result.add(stack.toItemStack(stack.getAmount())); } } _items.clear(); return result; } } @Override public List<ItemStack> withdraw(Material material) throws InsufficientItemsException { PreCon.notNull(material); ItemStack itemStack = new ItemStack(material); synchronized (_sync) { int balance = getBalance(itemStack); return withdraw(itemStack, balance); } } @Override public List<ItemStack> withdraw(Material material, int amount) throws InsufficientItemsException { PreCon.notNull(material); ItemStack itemStack = new ItemStack(material); return withdraw(itemStack, amount); } @Override public List<ItemStack> withdraw(MaterialData materialData) throws InsufficientItemsException { PreCon.notNull(materialData); ItemStack itemStack = new ItemStackBuilder(materialData).build(); synchronized (_sync) { int balance = getBalance(itemStack); return withdraw(itemStack, balance); } } @Override public List<ItemStack> withdraw(MaterialData materialData, int amount) throws InsufficientItemsException { PreCon.notNull(materialData); ItemStack itemStack = new ItemStackBuilder(materialData).build(); return withdraw(itemStack, amount); } @Override public List<ItemStack> withdraw(ItemStack matchingStack) throws InsufficientItemsException { return withdraw(matchingStack, matchingStack.getAmount()); } @Override public List<ItemStack> withdraw(ItemStack matchingStack, int amount) throws InsufficientItemsException { PreCon.notNull(matchingStack); PreCon.positiveNumber(amount); checkDisposed(); updateLastAccess(); if (amount == 0) return CollectionUtils.unmodifiableList(); MatchableItem wrapper = new MatchableItem(matchingStack, ItemStackMatcher.getTypeMetaDurability()); synchronized (_sync) { BankItem item = _items.get(wrapper); if (item == null || amount > item.getAmount()) throw new InsufficientItemsException(); item.withdraw(amount); int stackSize = matchingStack.getType().getMaxStackSize(); int totalStacks = (int) Math.ceil((double) amount / stackSize); int lastStackSize = amount % stackSize; List<ItemStack> result = new ArrayList<>(totalStacks); for (int i = 0; i < totalStacks; i++) { int size = i < totalStacks - 1 || lastStackSize == 0 ? stackSize : lastStackSize; result.add(item.toItemStack(size)); } return Collections.unmodifiableList(result); } } @Nullable @Override public IBankItem getItem(ItemStack matchingStack) { PreCon.notNull(matchingStack); updateLastAccess(); MatchableItem wrapper = new MatchableItem(matchingStack, ItemStackMatcher.getTypeMetaDurability()); synchronized (_sync) { return _items.get(wrapper); } } @Override public List<IBankItem> getItems() { updateLastAccess(); Deque<IBankItem> result = new ArrayDeque<>(10); synchronized (_sync) { for (BankItem item : _items.values()) { result.addAll(item.getItems()); } return new ArrayList<>(result); } } void checkDisposed() { if (_bank == null) return; if (_bank.isDisposed()) throw new RuntimeException("Cannot use an account from a disposed item bank."); } private void load() { for (IDataNode itemNode : _dataNode) { UUID id = TextUtils.parseUUID(itemNode.getName()); if (id == null) { NucMsg.debug("An account bank item's data node has " + "an invalid name (should be a UUID): {0}", itemNode.getName()); continue; } ItemStack[] items = itemNode.getItemStacks("item"); if (items == null || items.length == 0) { NucMsg.debug("An account bank item's data node has no item specified."); continue; } int amount = itemNode.getInteger("amount"); if (amount <= 0) { NucMsg.debug("An account bank item's data node has an amount less " + "than or equal to zero: {0}", amount); continue; } BankItem item = new BankItem(id, items[0], amount, itemNode); _items.put(new MatchableItem(items[0]), item); } } private void updateLastAccess() { _lastAccess = System.currentTimeMillis(); _dataNode.set("last-access", _lastAccess); if (_bank != null) { _bank.updateLastAccess(); } } }