package org.shininet.bukkit.itemrenamer.configuration;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import javax.annotation.Nonnull;
import org.shininet.bukkit.itemrenamer.utils.CollectionsUtil;
import org.shininet.bukkit.itemrenamer.wrappers.LeveledEnchantment;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* Represents a new immutable name and lore for a given item.
*
* @author Kristian
*/
public class RenameRule {
/**
* Represents a rename rule that makes no modifications to a given item stack (identity rule).
*/
public static final RenameRule IDENTITY = new RenameRule();
private final String name;
private final boolean skipCustomNamed;
private final ImmutableList<String> loreSections;
// Enchantments to add or remove
private final ImmutableSet<LeveledEnchantment> enchantments;
private final ImmutableSet<LeveledEnchantment> dechantments;
/**
* Construct the identity rule.
*/
private RenameRule() {
this(new Builder());
}
/**
* Construct a new rename rule from a builder.
* @param builder - the builder to construct from.
*/
private RenameRule(Builder builder) {
this.name = builder.name;
this.skipCustomNamed = builder.skipCustomNamed;
this.loreSections = safeList(builder.loreSections);
this.enchantments = safeSet(builder.enchantments);
this.dechantments = safeSet(builder.dechantments);
}
/**
* Construct an immutable copy of a given list.
* @param list - the list to copy.
* @return The copied immutable list.
*/
private static <T> ImmutableList<T> safeList(Collection<T> list) {
if (list != null)
return ImmutableList.copyOf(list);
else
return ImmutableList.of();
}
/**
* Construct an immutable copy of a given set.
* @param list - the set to copy.
* @return The copied immutable set.
*/
private static <T> ImmutableSet<T> safeSet(Collection<T> set) {
if (set != null)
return ImmutableSet.copyOf(set);
else
return ImmutableSet.of();
}
/**
* Represents a rename rule builder.
* @author Kristian
*/
public static class Builder {
private String name;
private boolean skipCustomNamed;
private Collection<String> loreSections;
private Set<LeveledEnchantment> enchantments;
private Set<LeveledEnchantment> dechantments;
private Builder() {
// Initialize to nothing
}
private Builder(RenameRule template) {
// Note that the enchantments themselves are never NULL
name(template.name);
skipCustomNamed(template.skipCustomNamed);
loreSections(template.loreSections);
enchantments(template.enchantments);
dechantments(template.dechantments);
}
/**
* Set the new name of items that are applied to this rule.
* @param name - new name, or NULL to keep the old.
* @return This builder, for chaining.
*/
public Builder name(String name) {
this.name = name;
return this;
}
/**
* Set whether or not to skip items that already have a custom name or lore lines.
* <p>
* This is implicitly set by the damage lookup (TRUE) and explicit lookup (FALSE), and will
* not persist through serialization.
* @param skipNamed - TRUE to skip items with custom name or lore, FALSE otherwise.
* @return This builder, for chaining.
*/
public Builder skipCustomNamed(boolean skipNamed) {
this.skipCustomNamed = skipNamed;
return this;
}
/**
* Set the list of lore sections to add to the items that are applied.
* @param loreSections - list of new lore sections, or NULL for none at all.
* @return This builder, for chaining.
*/
public Builder loreSections(Collection<String> loreSections) {
this.loreSections = loreSections != null ? Lists.newArrayList(loreSections) : null;
return this;
}
/**
* Merge in the given lore sections.
* @param loreSections - the lore sections to merge.
* @return This builder, for chaining.
*/
public Builder mergeLoreSections(@Nonnull Collection<String> loreSections) {
Preconditions.checkNotNull(loreSections, "loreSections cannot be NULL.");
// Construct a new list if needed
if (this.loreSections != null)
this.loreSections.addAll(loreSections);
else
loreSections(loreSections);
return this;
}
/**
* Set which enchantments to add to the item stack.
* @param enchantments - collection of new lore sections.
* @return This builder, for chaining.
*/
public Builder enchantments(Collection<LeveledEnchantment> enchantments) {
this.enchantments = enchantments != null ? Sets.newHashSet(enchantments) : null;
return this;
}
/**
* Merge in the given enchantments.
* @param enchantments - the enchantments to merge.
* @return This builder, for chaining.
*/
public Builder mergeEnchantments(@Nonnull Collection<LeveledEnchantment> enchantments) {
Preconditions.checkNotNull(enchantments, "enchantments cannot be NULL.");
if (this.enchantments != null) {
for (LeveledEnchantment enchantment : enchantments)
removeByType(this.enchantments, enchantment);
this.enchantments.addAll(enchantments);
} else {
enchantments(enchantments);
}
return this;
}
/**
* Set which dechantments to remove from the item stack.
* @param dechantments - enchantments to remove from the stack.
* @return This builder, for chaining.
*/
public Builder dechantments(Collection<LeveledEnchantment> dechantments) {
this.dechantments = dechantments != null ? Sets.newHashSet(dechantments) : null;
return this;
}
/**
* Merge in the given dechantments.
* @param dechantments - the enchantments to merge.
* @return This builder, for chaining.
*/
public Builder mergeDechantments(@Nonnull Collection<LeveledEnchantment> dechantments) {
Preconditions.checkNotNull(dechantments, "dechantments cannot be NULL.");
if (this.dechantments != null) {
for (LeveledEnchantment dechantment : dechantments)
removeByType(this.dechantments, dechantment);
this.dechantments.addAll(dechantments);
} else {
dechantments(dechantments);
}
return this;
}
/**
* Remove all enchantments of the same type (custom or a specific vanilla enchantments).
* @param source - the source set.
* @param other - the other enchantment.
*/
private void removeByType(Set<LeveledEnchantment> source, LeveledEnchantment other) {
for (Iterator<LeveledEnchantment> it = source.iterator(); it.hasNext(); ) {
if (other.sameType(it.next())) {
it.remove();
}
}
}
/**
* Remove enchantments and dechantments that cancel each other.
*/
private void simplify() {
// No cancellation is possible
if (enchantments == null || dechantments == null)
return;
Set<LeveledEnchantment> intersection = Sets.intersection(enchantments, dechantments);
if (intersection.size() > 0) {
intersection = Sets.newHashSet(intersection);
enchantments.removeAll(intersection);
dechantments.removeAll(intersection);
}
}
/**
* Construct a new rename rule based on the current parameters.
* @return The new rename rule.
*/
public RenameRule build() {
simplify();
return new RenameRule(this);
}
}
/**
* Construct a new rename rule builder that is initialized for identity rules.
* @return Rename rule builder.
*/
public static Builder newBuilder() {
return new Builder();
}
/**
* Construct a new rename rule builder initialized to the given template.
* <p>
* Note that NULL is treated as the IDENTITY template.
* @param template - rule template.
* @return Rename rule builder.
*/
public static Builder newBuilder(RenameRule template) {
// Handle NULL too
if (template != null)
return new Builder(template);
else
return newBuilder();
}
/**
* Retrieve the new name of a given item.
* @return The new name, or NULL if not set.
*/
public String getName() {
return name;
}
/**
* Retrieve a list of all the new lore sections to add.
* @return New lore sections.
*/
@Nonnull
public ImmutableList<String> getLoreSections() {
return loreSections;
}
/**
* Retrieve a list of every enchantment that will be added to the item stack.
* @return Every enchantment to be added.
*/
@Nonnull
public ImmutableSet<LeveledEnchantment> getEnchantments() {
return enchantments;
}
/**
* Retrieve a list of every enchantment (or dechantment) that will be removed from the item stack.
* @return Every enchantment to be removed.
*/
public ImmutableSet<LeveledEnchantment> getDechantments() {
return dechantments;
}
/**
* Determine if this rename rule will skip items with custom name or lore.
* @return TRUE it the rule will skip those items, FALSE otherwise.
*/
public boolean isSkippingCustomNamed() {
return skipCustomNamed;
}
/**
* Retrieve a rename rule with the given skip rule.
* @param skipCustomNamed - whether or not to skip names that have a custom name or lore.
* @return The same rename rule with this skip rule.
*/
public RenameRule withSkipRule(boolean skipCustomNamed) {
if (this.skipCustomNamed == skipCustomNamed)
return this;
else
return newBuilder(this).skipCustomNamed(skipCustomNamed).build();
}
@Override
public int hashCode() {
return Objects.hashCode(name, skipCustomNamed, loreSections, enchantments, dechantments);
}
@Override
public boolean equals(Object obj) {
// Shortcut
if (this == obj)
return true;
if (obj instanceof RenameRule) {
RenameRule other = (RenameRule) obj;
if (!Objects.equal(name, other.getName()) || skipCustomNamed != other.isSkippingCustomNamed())
return false;
return CollectionsUtil.equalsMany(loreSections, other.getLoreSections())
&& CollectionsUtil.equalsMany(enchantments, other.getEnchantments())
&& CollectionsUtil.equalsMany(dechantments, other.getDechantments());
}
return false;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
// For constructing the extra sections
Collection<?>[] collections = new Collection<?>[] { loreSections, enchantments, dechantments };
String[] names = new String[] { "lore", "ench", "dech" };
// The extra sections
for (int i = 0; i < collections.length; i++) {
if (!CollectionsUtil.isEmpty(collections[i])) {
result.append(", ");
result.append(names[i] + ": " + collections[i]);
}
}
return "[name: " + name + result.toString() + "]";
}
/**
* Retrieve a builder initialized to this rename rule.
* @return A builder that will build copies of this rename rule.
*/
public Builder toBuilder() {
return new Builder(this);
}
/**
* Determine if this is an identity rule.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isIdentity() {
return this == IDENTITY ||
(name == null && CollectionsUtil.isEmpty(loreSections)
&& CollectionsUtil.isEmpty(enchantments)
&& CollectionsUtil.isEmpty(dechantments));
}
/**
* Merge two item rename rules such that the priority rule overrides the fallback in name.
* <p>
* The lore is combined from both. Priority is last.
* <p>
* Also note that null names will not override set names.
* @param priority - priority rule.
* @param fallback - fallback role.
* @return Merged rename rule, or NULL if both are NULL.
*/
public static RenameRule merge(RenameRule priority, RenameRule fallback) {
if (fallback == null || priority == null) {
return priority != null ? priority : fallback;
} else {
// Priority takes, well, priority
Builder builder = fallback.toBuilder().
mergeLoreSections(priority.getLoreSections()).
mergeEnchantments(priority.getEnchantments()).
mergeDechantments(priority.getDechantments());
// Don't merge nulls
if (priority.getName() != null) {
builder.name(priority.getName());
}
return builder.build();
}
}
/**
* Determine if a given rename rule is the identity rule.
* <p>
* That is, it preserves both the name and lore section of any item stack given to it.
* @param rule - the rule to check.
* @return TRUE if it is, FALSE otherwise.
*/
public static boolean isIdentity(RenameRule rule) {
return rule == null || rule.isIdentity();
}
}