Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers

This commit is contained in:
DerFrZocker 2021-12-21 15:52:32 +01:00
commit cf1d2005bb
No known key found for this signature in database
GPG key ID: 713F71FFFE1DDF91
19 changed files with 1214 additions and 259 deletions

20
pom.xml
View file

@ -5,7 +5,7 @@
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.18-R0.1-SNAPSHOT</version>
<version>1.18.1-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Bukkit</name>
@ -54,40 +54,40 @@
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.28</version>
<version>1.30</version>
<scope>compile</scope>
</dependency>
<!-- not part of the API proper -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-resolver-provider</artifactId>
<version>3.8.1</version>
<version>3.8.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-connector-basic</artifactId>
<version>1.7.0</version>
<version>1.7.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-transport-http</artifactId>
<version>1.7.0</version>
<version>1.7.2</version>
<scope>provided</scope>
</dependency>
<!-- annotations -->
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations-java5</artifactId>
<version>21.0.1</version>
<version>23.0.0</version>
<scope>provided</scope>
</dependency>
<!-- testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
@ -142,7 +142,7 @@
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>ecj</artifactId>
<version>3.27.0</version>
<version>3.28.0</version>
</dependency>
</dependencies>
</plugin>
@ -193,8 +193,8 @@
<configuration>
<links>
<link>https://guava.dev/releases/31.0.1-jre/api/docs/</link>
<link>https://javadoc.io/doc/org.yaml/snakeyaml/1.28/</link>
<link>https://javadoc.io/doc/org.jetbrains/annotations-java5/21.0.1/</link>
<link>https://javadoc.io/doc/org.yaml/snakeyaml/1.30/</link>
<link>https://javadoc.io/doc/org.jetbrains/annotations-java5/23.0.0/</link>
</links>
</configuration>
</plugin>

View file

@ -245,6 +245,49 @@ public final class Bukkit {
return server.getAllowNether();
}
/**
* Gets the server resource pack uri, or empty string if not specified.
*
* @return the server resource pack uri, otherwise empty string
*/
@NotNull
public static String getResourcePack() {
return server.getResourcePack();
}
/**
* Gets the SHA-1 digest of the server resource pack, or empty string if
* not specified.
*
* @return the SHA-1 digest of the server resource pack, otherwise empty
* string
*/
@NotNull
public static String getResourcePackHash() {
return server.getResourcePackHash();
}
/**
* Gets the custom prompt message to be shown when the server resource
* pack is required, or empty string if not specified.
*
* @return the custom prompt message to be shown when the server resource,
* otherwise empty string
*/
@NotNull
public static String getResourcePackPrompt() {
return server.getResourcePackPrompt();
}
/**
* Gets whether the server resource pack is enforced.
*
* @return whether the server resource pack is enforced
*/
public static boolean isResourcePackRequired() {
return server.isResourcePackRequired();
}
/**
* Gets whether this server has a whitelist or not.
*

View file

@ -202,6 +202,41 @@ public interface Server extends PluginMessageRecipient {
*/
public boolean getAllowNether();
/**
* Gets the server resource pack uri, or empty string if not specified.
*
* @return the server resource pack uri, otherwise empty string
*/
@NotNull
public String getResourcePack();
/**
* Gets the SHA-1 digest of the server resource pack, or empty string if
* not specified.
*
* @return the SHA-1 digest of the server resource pack, otherwise empty
* string
*/
@NotNull
public String getResourcePackHash();
/**
* Gets the custom prompt message to be shown when the server resource
* pack is required, or empty string if not specified.
*
* @return the custom prompt message to be shown when the server resource,
* otherwise empty string
*/
@NotNull
public String getResourcePackPrompt();
/**
* Gets whether the server resource pack is enforced.
*
* @return whether the server resource pack is enforced
*/
public boolean isResourcePackRequired();
/**
* Gets whether this server has a whitelist or not.
*

View file

@ -996,4 +996,66 @@ public interface ConfigurationSection {
* @throws IllegalArgumentException Thrown if path is null.
*/
public void addDefault(@NotNull String path, @Nullable Object value);
/**
* Gets the requested comment list by path.
* <p>
* If no comments exist, an empty list will be returned. A null entry
* represents an empty line and an empty String represents an empty comment
* line.
*
* @param path Path of the comments to get.
* @return A unmodifiable list of the requested comments, every entry
* represents one line.
*/
@NotNull
public List<String> getComments(@NotNull String path);
/**
* Gets the requested inline comment list by path.
* <p>
* If no comments exist, an empty list will be returned. A null entry
* represents an empty line and an empty String represents an empty comment
* line.
*
* @param path Path of the comments to get.
* @return A unmodifiable list of the requested comments, every entry
* represents one line.
*/
@NotNull
public List<String> getInlineComments(@NotNull String path);
/**
* Sets the comment list at the specified path.
* <p>
* If value is null, the comments will be removed. A null entry is an empty
* line and an empty String entry is an empty comment line. If the path does
* not exist, no comments will be set. Any existing comments will be
* replaced, regardless of what the new comments are.
* <p>
* Some implementations may have limitations on what persists. See their
* individual javadocs for details.
*
* @param path Path of the comments to set.
* @param comments New comments to set at the path, every entry represents
* one line.
*/
public void setComments(@NotNull String path, @Nullable List<String> comments);
/**
* Sets the inline comment list at the specified path.
* <p>
* If value is null, the comments will be removed. A null entry is an empty
* line and an empty String entry is an empty comment line. If the path does
* not exist, no comment will be set. Any existing comments will be
* replaced, regardless of what the new comments are.
* <p>
* Some implementations may have limitations on what persists. See their
* individual javadocs for details.
*
* @param path Path of the comments to set.
* @param comments New comments to set at the path, every entry represents
* one line.
*/
public void setInlineComments(@NotNull String path, @Nullable List<String> comments);
}

View file

@ -54,7 +54,11 @@ public class MemoryConfiguration extends MemorySection implements Configuration
public void addDefaults(@NotNull Configuration defaults) {
Validate.notNull(defaults, "Defaults may not be null");
addDefaults(defaults.getValues(true));
for (String key : defaults.getKeys(true)) {
if (!defaults.isConfigurationSection(key)) {
addDefault(key, defaults.get(key));
}
}
}
@Override

View file

@ -2,6 +2,7 @@ package org.bukkit.configuration;
import static org.bukkit.util.NumberConversions.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@ -22,7 +23,7 @@ import org.jetbrains.annotations.Nullable;
* A type of {@link ConfigurationSection} that is stored in memory.
*/
public class MemorySection implements ConfigurationSection {
protected final Map<String, Object> map = new LinkedHashMap<String, Object>();
protected final Map<String, SectionPathData> map = new LinkedHashMap<String, SectionPathData>();
private final Configuration root;
private final ConfigurationSection parent;
private final String path;
@ -217,7 +218,12 @@ public class MemorySection implements ConfigurationSection {
if (value == null) {
map.remove(key);
} else {
map.put(key, value);
SectionPathData entry = map.get(key);
if (entry == null) {
map.put(key, new SectionPathData(value));
} else {
entry.setData(value);
}
}
} else {
section.set(key, value);
@ -251,7 +257,11 @@ public class MemorySection implements ConfigurationSection {
int i1 = -1, i2;
ConfigurationSection section = this;
while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) {
section = section.getConfigurationSection(path.substring(i2, i1));
final String currentPath = path.substring(i2, i1);
if (!section.contains(currentPath, true)) {
return def;
}
section = section.getConfigurationSection(currentPath);
if (section == null) {
return def;
}
@ -259,8 +269,8 @@ public class MemorySection implements ConfigurationSection {
String key = path.substring(i2);
if (section == this) {
Object result = map.get(key);
return (result == null) ? def : result;
SectionPathData result = map.get(key);
return (result == null) ? def : result.getData();
}
return section.get(key, def);
}
@ -292,7 +302,7 @@ public class MemorySection implements ConfigurationSection {
String key = path.substring(i2);
if (section == this) {
ConfigurationSection result = new MemorySection(this, key);
map.put(key, result);
map.put(key, new SectionPathData(result));
return result;
}
return section.createSection(key);
@ -856,11 +866,11 @@ public class MemorySection implements ConfigurationSection {
if (section instanceof MemorySection) {
MemorySection sec = (MemorySection) section;
for (Map.Entry<String, Object> entry : sec.map.entrySet()) {
for (Map.Entry<String, SectionPathData> entry : sec.map.entrySet()) {
output.add(createPath(section, entry.getKey(), this));
if ((deep) && (entry.getValue() instanceof ConfigurationSection)) {
ConfigurationSection subsection = (ConfigurationSection) entry.getValue();
if ((deep) && (entry.getValue().getData() instanceof ConfigurationSection)) {
ConfigurationSection subsection = (ConfigurationSection) entry.getValue().getData();
mapChildrenKeys(output, subsection, deep);
}
}
@ -877,17 +887,17 @@ public class MemorySection implements ConfigurationSection {
if (section instanceof MemorySection) {
MemorySection sec = (MemorySection) section;
for (Map.Entry<String, Object> entry : sec.map.entrySet()) {
for (Map.Entry<String, SectionPathData> entry : sec.map.entrySet()) {
// Because of the copyDefaults call potentially copying out of order, we must remove and then add in our saved order
// This means that default values we haven't set end up getting placed first
// See SPIGOT-4558 for an example using spigot.yml - watch subsections move around to default order
String childPath = createPath(section, entry.getKey(), this);
output.remove(childPath);
output.put(childPath, entry.getValue());
output.put(childPath, entry.getValue().getData());
if (entry.getValue() instanceof ConfigurationSection) {
if (entry.getValue().getData() instanceof ConfigurationSection) {
if (deep) {
mapChildrenValues(output, (ConfigurationSection) entry.getValue(), deep);
mapChildrenValues(output, (ConfigurationSection) entry.getValue().getData(), deep);
}
}
}
@ -938,15 +948,12 @@ public class MemorySection implements ConfigurationSection {
char separator = root.options().pathSeparator();
StringBuilder builder = new StringBuilder();
if (section != null) {
for (ConfigurationSection parent = section; (parent != null) && (parent != relativeTo); parent = parent.getParent()) {
if (builder.length() > 0) {
builder.insert(0, separator);
}
builder.insert(0, parent.getName());
}
}
if ((key != null) && (key.length() > 0)) {
if (builder.length() > 0) {
@ -959,6 +966,69 @@ public class MemorySection implements ConfigurationSection {
return builder.toString();
}
@Override
@NotNull
public List<String> getComments(@NotNull final String path) {
final SectionPathData pathData = getSectionPathData(path);
return pathData == null ? Collections.emptyList() : pathData.getComments();
}
@Override
@NotNull
public List<String> getInlineComments(@NotNull final String path) {
final SectionPathData pathData = getSectionPathData(path);
return pathData == null ? Collections.emptyList() : pathData.getInlineComments();
}
@Override
public void setComments(@NotNull final String path, @Nullable final List<String> comments) {
final SectionPathData pathData = getSectionPathData(path);
if (pathData != null) {
pathData.setComments(comments);
}
}
@Override
public void setInlineComments(@NotNull final String path, @Nullable final List<String> comments) {
final SectionPathData pathData = getSectionPathData(path);
if (pathData != null) {
pathData.setInlineComments(comments);
}
}
@Nullable
private SectionPathData getSectionPathData(@NotNull String path) {
Validate.notNull(path, "Path cannot be null");
Configuration root = getRoot();
if (root == null) {
throw new IllegalStateException("Cannot access section without a root");
}
final char separator = root.options().pathSeparator();
// i1 is the leading (higher) index
// i2 is the trailing (lower) index
int i1 = -1, i2;
ConfigurationSection section = this;
while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) {
section = section.getConfigurationSection(path.substring(i2, i1));
if (section == null) {
return null;
}
}
String key = path.substring(i2);
if (section == this) {
SectionPathData entry = map.get(key);
if (entry != null) {
return entry;
}
} else if (section instanceof MemorySection) {
return ((MemorySection) section).getSectionPathData(key);
}
return null;
}
@Override
public String toString() {
Configuration root = getRoot();

View file

@ -0,0 +1,81 @@
package org.bukkit.configuration;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
final class SectionPathData {
private Object data;
private List<String> comments;
private List<String> inlineComments;
public SectionPathData(@Nullable Object data) {
this.data = data;
comments = Collections.emptyList();
inlineComments = Collections.emptyList();
}
@Nullable
public Object getData() {
return data;
}
public void setData(@Nullable final Object data) {
this.data = data;
}
/**
* If no comments exist, an empty list will be returned. A null entry in the
* list represents an empty line and an empty String represents an empty
* comment line.
*
* @return A unmodifiable list of the requested comments, every entry
* represents one line.
*/
@NotNull
public List<String> getComments() {
return comments;
}
/**
* Represents the comments on a {@link ConfigurationSection} entry.
*
* A null entry in the List is an empty line and an empty String entry is an
* empty comment line. Any existing comments will be replaced, regardless of
* what the new comments are.
*
* @param comments New comments to set every entry represents one line.
*/
public void setComments(@Nullable final List<String> comments) {
this.comments = (comments == null) ? Collections.emptyList() : Collections.unmodifiableList(comments);
}
/**
* If no comments exist, an empty list will be returned. A null entry in the
* list represents an empty line and an empty String represents an empty
* comment line.
*
* @return A unmodifiable list of the requested comments, every entry
* represents one line.
*/
@NotNull
public List<String> getInlineComments() {
return inlineComments;
}
/**
* Represents the comments on a {@link ConfigurationSection} entry.
*
* A null entry in the List is an empty line and an empty String entry is an
* empty comment line. Any existing comments will be replaced, regardless of
* what the new comments are.
*
* @param inlineComments New comments to set every entry represents one
* line.
*/
public void setInlineComments(@Nullable final List<String> inlineComments) {
this.inlineComments = (inlineComments == null) ? Collections.emptyList() : Collections.unmodifiableList(inlineComments);
}
}

View file

@ -202,17 +202,17 @@ public abstract class FileConfiguration extends MemoryConfiguration {
public abstract void loadFromString(@NotNull String contents) throws InvalidConfigurationException;
/**
* Compiles the header for this {@link FileConfiguration} and returns the
* result.
* <p>
* This will use the header from {@link #options()} -&gt; {@link
* FileConfigurationOptions#header()}, respecting the rules of {@link
* FileConfigurationOptions#copyHeader()} if set.
* @return empty string
*
* @return Compiled header
* @deprecated This method only exists for backwards compatibility. It will
* do nothing and should not be used! Please use
* {@link FileConfigurationOptions#getHeader()} instead.
*/
@NotNull
protected abstract String buildHeader();
@Deprecated
protected String buildHeader() {
return "";
}
@NotNull
@Override

View file

@ -1,5 +1,8 @@
package org.bukkit.configuration.file;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.bukkit.configuration.MemoryConfiguration;
import org.bukkit.configuration.MemoryConfigurationOptions;
import org.jetbrains.annotations.NotNull;
@ -10,8 +13,9 @@ import org.jetbrains.annotations.Nullable;
* FileConfiguration}
*/
public class FileConfigurationOptions extends MemoryConfigurationOptions {
private String header = null;
private boolean copyHeader = true;
private List<String> header = Collections.emptyList();
private List<String> footer = Collections.emptyList();
private boolean parseComments = true;
protected FileConfigurationOptions(@NotNull MemoryConfiguration configuration) {
super(configuration);
@ -46,16 +50,32 @@ public class FileConfigurationOptions extends MemoryConfigurationOptions {
* automatically be applied, but you may include one if you wish for extra
* spacing.
* <p>
* Null is a valid value which will indicate that no header is to be
* applied. The default value is null.
* If no comments exist, an empty list will be returned. A null entry
* represents an empty line and an empty String represents an empty comment
* line.
*
* @return Header
* @return Unmodifiable header, every entry represents one line.
*/
@Nullable
public String header() {
@NotNull
public List<String> getHeader() {
return header;
}
/**
* @return The string header.
*
* @deprecated use getHeader() instead.
*/
@NotNull
@Deprecated
public String header() {
StringBuilder stringHeader = new StringBuilder();
for (String line : header) {
stringHeader.append(line == null ? "\n" : line + "\n");
}
return stringHeader.toString();
}
/**
* Sets the header that will be applied to the top of the saved output.
* <p>
@ -65,63 +85,119 @@ public class FileConfigurationOptions extends MemoryConfigurationOptions {
* automatically be applied, but you may include one if you wish for extra
* spacing.
* <p>
* Null is a valid value which will indicate that no header is to be
* applied.
* If no comments exist, an empty list will be returned. A null entry
* represents an empty line and an empty String represents an empty comment
* line.
*
* @param value New header
* @param value New header, every entry represents one line.
* @return This object, for chaining
*/
@NotNull
public FileConfigurationOptions header(@Nullable String value) {
this.header = value;
public FileConfigurationOptions setHeader(@Nullable List<String> value) {
this.header = (value == null) ? Collections.emptyList() : Collections.unmodifiableList(value);
return this;
}
/**
* Gets whether or not the header should be copied from a default source.
* <p>
* If this is true, if a default {@link FileConfiguration} is passed to
* {@link
* FileConfiguration#setDefaults(org.bukkit.configuration.Configuration)}
* then upon saving it will use the header from that config, instead of
* the one provided here.
* <p>
* If no default is set on the configuration, or the default is not of
* type FileConfiguration, or that config has no header ({@link #header()}
* returns null) then the header specified in this configuration will be
* used.
* <p>
* Defaults to true.
* @param value The string header.
* @return This object, for chaining.
*
* @return Whether or not to copy the header
* @deprecated use setHeader() instead
*/
public boolean copyHeader() {
return copyHeader;
@NotNull
@Deprecated
public FileConfigurationOptions header(@Nullable String value) {
this.header = (value == null) ? Collections.emptyList() : Collections.unmodifiableList(Arrays.asList(value.split("\\n")));
return this;
}
/**
* Sets whether or not the header should be copied from a default source.
* Gets the footer that will be applied to the bottom of the saved output.
* <p>
* If this is true, if a default {@link FileConfiguration} is passed to
* {@link
* FileConfiguration#setDefaults(org.bukkit.configuration.Configuration)}
* then upon saving it will use the header from that config, instead of
* the one provided here.
* This footer will be commented out and applied directly at the bottom of
* the generated output of the {@link FileConfiguration}. It is not required
* to include a newline at the beginning of the footer as it will
* automatically be applied, but you may include one if you wish for extra
* spacing.
* <p>
* If no default is set on the configuration, or the default is not of
* type FileConfiguration, or that config has no header ({@link #header()}
* returns null) then the header specified in this configuration will be
* used.
* <p>
* Defaults to true.
* If no comments exist, an empty list will be returned. A null entry
* represents an empty line and an empty String represents an empty comment
* line.
*
* @param value Whether or not to copy the header
* @return Unmodifiable footer, every entry represents one line.
*/
@NotNull
public List<String> getFooter() {
return footer;
}
/**
* Sets the footer that will be applied to the bottom of the saved output.
* <p>
* This footer will be commented out and applied directly at the bottom of
* the generated output of the {@link FileConfiguration}. It is not required
* to include a newline at the beginning of the footer as it will
* automatically be applied, but you may include one if you wish for extra
* spacing.
* <p>
* If no comments exist, an empty list will be returned. A null entry
* represents an empty line and an empty String represents an empty comment
* line.
*
* @param value New footer, every entry represents one line.
* @return This object, for chaining
*/
@NotNull
public FileConfigurationOptions copyHeader(boolean value) {
copyHeader = value;
public FileConfigurationOptions setFooter(@Nullable List<String> value) {
this.footer = (value == null) ? Collections.emptyList() : Collections.unmodifiableList(value);
return this;
}
/**
* Gets whether or not comments should be loaded and saved.
* <p>
* Defaults to true.
*
* @return Whether or not comments are parsed.
*/
public boolean parseComments() {
return parseComments;
}
/**
* Sets whether or not comments should be loaded and saved.
* <p>
* Defaults to true.
*
* @param value Whether or not comments are parsed.
* @return This object, for chaining
*/
@NotNull
public MemoryConfigurationOptions parseComments(boolean value) {
parseComments = value;
return this;
}
/**
* @return Whether or not comments are parsed.
*
* @deprecated Call {@link #parseComments()} instead.
*/
@Deprecated
public boolean copyHeader() {
return parseComments;
}
/**
* @param value Should comments be parsed.
* @return This object, for chaining
*
* @deprecated Call {@link #parseComments(boolean)} instead.
*/
@NotNull
@Deprecated
public FileConfigurationOptions copyHeader(boolean value) {
parseComments = value;
return this;
}
}

View file

@ -1,9 +1,14 @@
package org.bukkit.configuration.file;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.apache.commons.lang.Validate;
@ -11,148 +16,231 @@ import org.bukkit.Bukkit;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.jetbrains.annotations.NotNull;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.comments.CommentLine;
import org.yaml.snakeyaml.comments.CommentType;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.representer.Representer;
import org.yaml.snakeyaml.nodes.AnchorNode;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.SequenceNode;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.reader.UnicodeReader;
/**
* An implementation of {@link Configuration} which saves all files in Yaml.
* Note that this implementation is not synchronized.
*/
public class YamlConfiguration extends FileConfiguration {
protected static final String COMMENT_PREFIX = "# ";
protected static final String BLANK_CONFIG = "{}\n";
private final DumperOptions yamlOptions = new DumperOptions();
private final LoaderOptions loaderOptions = new LoaderOptions();
private final Representer yamlRepresenter = new YamlRepresenter();
private final Yaml yaml = new Yaml(new YamlConstructor(), yamlRepresenter, yamlOptions, loaderOptions);
private final DumperOptions yamlDumperOptions;
private final LoaderOptions yamlLoaderOptions;
private final YamlConstructor constructor;
private final YamlRepresenter representer;
private final Yaml yaml;
public YamlConfiguration() {
constructor = new YamlConstructor();
representer = new YamlRepresenter();
representer.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
yamlDumperOptions = new DumperOptions();
yamlDumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
yamlLoaderOptions = new LoaderOptions();
yamlLoaderOptions.setMaxAliasesForCollections(Integer.MAX_VALUE); // SPIGOT-5881: Not ideal, but was default pre SnakeYAML 1.26
yaml = new Yaml(constructor, representer, yamlDumperOptions, yamlLoaderOptions);
}
@NotNull
@Override
public String saveToString() {
yamlOptions.setIndent(options().indent());
yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
yamlDumperOptions.setIndent(options().indent());
yamlDumperOptions.setProcessComments(options().parseComments());
String header = buildHeader();
String dump = yaml.dump(getValues(false));
MappingNode node = toNodeTree(this);
if (dump.equals(BLANK_CONFIG)) {
dump = "";
node.setBlockComments(getCommentLines(saveHeader(options().getHeader()), CommentType.BLOCK));
node.setEndComments(getCommentLines(options().getFooter(), CommentType.BLOCK));
StringWriter writer = new StringWriter();
if (node.getEndComments().isEmpty() && node.getEndComments().isEmpty() && node.getValue().isEmpty()) {
writer.write("");
} else {
if (node.getValue().isEmpty()) {
node.setFlowStyle(DumperOptions.FlowStyle.FLOW);
}
return header + dump;
yaml.serialize(node, writer);
}
return writer.toString();
}
@Override
public void loadFromString(@NotNull String contents) throws InvalidConfigurationException {
Validate.notNull(contents, "Contents cannot be null");
Validate.notNull(contents, "String cannot be null");
yamlLoaderOptions.setProcessComments(options().parseComments());
Map<?, ?> input;
try {
loaderOptions.setMaxAliasesForCollections(Integer.MAX_VALUE); // SPIGOT-5881: Not ideal, but was default pre SnakeYAML 1.26
input = (Map<?, ?>) yaml.load(contents);
} catch (YAMLException e) {
MappingNode node;
try (Reader reader = new UnicodeReader(new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)))) {
node = (MappingNode) yaml.compose(reader);
} catch (YAMLException | IOException e) {
throw new InvalidConfigurationException(e);
} catch (ClassCastException e) {
throw new InvalidConfigurationException("Top level is not a Map.");
}
String header = parseHeader(contents);
if (header.length() > 0) {
options().header(header);
}
this.map.clear();
if (input != null) {
convertMapsToSections(input, this);
if (node != null) {
adjustNodeComments(node);
options().setHeader(loadHeader(getCommentLines(node.getBlockComments())));
options().setFooter(getCommentLines(node.getEndComments()));
fromNodeTree(node, this);
}
}
protected void convertMapsToSections(@NotNull Map<?, ?> input, @NotNull ConfigurationSection section) {
for (Map.Entry<?, ?> entry : input.entrySet()) {
String key = entry.getKey().toString();
Object value = entry.getValue();
/**
* This method splits the header on the last empty line, and sets the
* comments below this line as comments for the first key on the map object.
*
* @param node The root node of the yaml object
*/
private void adjustNodeComments(final MappingNode node) {
if (node.getBlockComments() == null && !node.getValue().isEmpty()) {
Node firstNode = node.getValue().get(0).getKeyNode();
List<CommentLine> lines = firstNode.getBlockComments();
if (lines != null) {
int index = -1;
for (int i = 0; i < lines.size(); i++) {
if (lines.get(i).getCommentType() == CommentType.BLANK_LINE) {
index = i;
}
}
if (index != -1) {
node.setBlockComments(lines.subList(0, index + 1));
firstNode.setBlockComments(lines.subList(index + 1, lines.size()));
}
}
}
}
if (value instanceof Map) {
convertMapsToSections((Map<?, ?>) value, section.createSection(key));
protected void fromNodeTree(@NotNull MappingNode input, @NotNull ConfigurationSection section) {
for (NodeTuple nodeTuple : input.getValue()) {
ScalarNode key = (ScalarNode) nodeTuple.getKeyNode();
String keyString = key.getValue();
Node value = nodeTuple.getValueNode();
while (value instanceof AnchorNode) {
value = ((AnchorNode) value).getRealNode();
}
if (value instanceof MappingNode && !hasSerializedTypeKey((MappingNode) value)) {
fromNodeTree((MappingNode) value, section.createSection(keyString));
} else {
section.set(key, value);
section.set(keyString, constructor.construct(value));
}
section.setComments(keyString, getCommentLines(key.getBlockComments()));
if (value instanceof MappingNode || value instanceof SequenceNode) {
section.setInlineComments(keyString, getCommentLines(key.getInLineComments()));
} else {
section.setInlineComments(keyString, getCommentLines(value.getInLineComments()));
}
}
}
@NotNull
protected String parseHeader(@NotNull String input) {
String[] lines = input.split("\r?\n", -1);
StringBuilder result = new StringBuilder();
boolean readingHeader = true;
boolean foundHeader = false;
for (int i = 0; (i < lines.length) && (readingHeader); i++) {
String line = lines[i];
if (line.startsWith(COMMENT_PREFIX)) {
if (i > 0) {
result.append("\n");
}
if (line.length() > COMMENT_PREFIX.length()) {
result.append(line.substring(COMMENT_PREFIX.length()));
}
foundHeader = true;
} else if ((foundHeader) && (line.length() == 0)) {
result.append("\n");
} else if (foundHeader) {
readingHeader = false;
private boolean hasSerializedTypeKey(MappingNode node) {
for (NodeTuple nodeTuple : node.getValue()) {
String key = ((ScalarNode) nodeTuple.getKeyNode()).getValue();
if (key.equals(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) {
return true;
}
}
return result.toString();
return false;
}
@NotNull
@Override
protected String buildHeader() {
String header = options().header();
private MappingNode toNodeTree(@NotNull ConfigurationSection section) {
List<NodeTuple> nodeTuples = new ArrayList<>();
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
ScalarNode key = (ScalarNode) representer.represent(entry.getKey());
Node value;
if (entry.getValue() instanceof ConfigurationSection) {
value = toNodeTree((ConfigurationSection) entry.getValue());
} else {
value = representer.represent(entry.getValue());
}
key.setBlockComments(getCommentLines(section.getComments(entry.getKey()), CommentType.BLOCK));
if (value instanceof MappingNode || value instanceof SequenceNode) {
key.setInLineComments(getCommentLines(section.getInlineComments(entry.getKey()), CommentType.IN_LINE));
} else {
value.setInLineComments(getCommentLines(section.getInlineComments(entry.getKey()), CommentType.IN_LINE));
}
if (options().copyHeader()) {
Configuration def = getDefaults();
nodeTuples.add(new NodeTuple(key, value));
}
if ((def != null) && (def instanceof FileConfiguration)) {
FileConfiguration filedefaults = (FileConfiguration) def;
String defaultsHeader = filedefaults.buildHeader();
return new MappingNode(Tag.MAP, nodeTuples, DumperOptions.FlowStyle.BLOCK);
}
if ((defaultsHeader != null) && (defaultsHeader.length() > 0)) {
return defaultsHeader;
private List<String> getCommentLines(List<CommentLine> comments) {
List<String> lines = new ArrayList<>();
if (comments != null) {
for (CommentLine comment : comments) {
if (comment.getCommentType() == CommentType.BLANK_LINE) {
lines.add(null);
} else {
lines.add(comment.getValue());
}
}
}
if (header == null) {
return "";
return lines;
}
StringBuilder builder = new StringBuilder();
String[] lines = header.split("\r?\n", -1);
boolean startedHeader = false;
for (int i = lines.length - 1; i >= 0; i--) {
builder.insert(0, "\n");
if ((startedHeader) || (lines[i].length() != 0)) {
builder.insert(0, lines[i]);
builder.insert(0, COMMENT_PREFIX);
startedHeader = true;
private List<CommentLine> getCommentLines(List<String> comments, CommentType commentType) {
List<CommentLine> lines = new ArrayList<CommentLine>();
for (String comment : comments) {
if (comment == null) {
lines.add(new CommentLine(null, null, "", CommentType.BLANK_LINE));
} else {
lines.add(new CommentLine(null, null, comment, commentType));
}
}
return lines;
}
return builder.toString();
/**
* Removes the empty line at the end of the header that separates the header
* from further comments.
*
* @param header The list of heading comments
* @return The modified list
*/
private List<String> loadHeader(List<String> header) {
ArrayList<String> list = new ArrayList<String>(header);
if (list.size() != 0) {
list.remove(list.size() - 1);
}
return list;
}
/**
* Adds the empty line at the end of the header that separates the header
* from further comments.
*
* @param header The list of heading comments
* @return The modified list
*/
private List<String> saveHeader(List<String> header) {
ArrayList<String> list = new ArrayList<String>(header);
if (list.size() != 0) {
list.add(null);
}
return list;
}
@NotNull

View file

@ -1,5 +1,6 @@
package org.bukkit.configuration.file;
import java.util.List;
import org.apache.commons.lang.Validate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -37,6 +38,14 @@ public class YamlConfigurationOptions extends FileConfigurationOptions {
@NotNull
@Override
public YamlConfigurationOptions setHeader(@Nullable List<String> value) {
super.setHeader(value);
return this;
}
@NotNull
@Override
@Deprecated
public YamlConfigurationOptions header(@Nullable String value) {
super.header(value);
return this;
@ -44,6 +53,21 @@ public class YamlConfigurationOptions extends FileConfigurationOptions {
@NotNull
@Override
public YamlConfigurationOptions setFooter(@Nullable List<String> value) {
super.setFooter(value);
return this;
}
@NotNull
@Override
public YamlConfigurationOptions parseComments(boolean value) {
super.parseComments(value);
return this;
}
@NotNull
@Override
@Deprecated
public YamlConfigurationOptions copyHeader(boolean value) {
super.copyHeader(value);
return this;

View file

@ -16,6 +16,11 @@ public class YamlConstructor extends SafeConstructor {
this.yamlConstructors.put(Tag.MAP, new ConstructCustomObject());
}
@NotNull
public Object construct(@NotNull Node node) {
return constructObject(node);
}
private class ConstructCustomObject extends ConstructYamlMap {
@Nullable

View file

@ -2,7 +2,6 @@ package org.bukkit.configuration.file;
import java.util.LinkedHashMap;
import java.util.Map;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.jetbrains.annotations.NotNull;
@ -12,22 +11,12 @@ import org.yaml.snakeyaml.representer.Representer;
public class YamlRepresenter extends Representer {
public YamlRepresenter() {
this.multiRepresenters.put(ConfigurationSection.class, new RepresentConfigurationSection());
this.multiRepresenters.put(ConfigurationSerializable.class, new RepresentConfigurationSerializable());
// SPIGOT-6234: We could just switch YamlConstructor to extend Constructor rather than SafeConstructor, however there is a very small risk of issues with plugins treating config as untrusted input
// So instead we will just allow future plugins to have their enums extend ConfigurationSerializable
this.multiRepresenters.remove(Enum.class);
}
private class RepresentConfigurationSection extends RepresentMap {
@NotNull
@Override
public Node representData(@NotNull Object data) {
return super.representData(((ConfigurationSection) data).getValues(false));
}
}
private class RepresentConfigurationSerializable extends RepresentMap {
@NotNull

View file

@ -16,6 +16,7 @@ import org.bukkit.WeatherType;
import org.bukkit.advancement.Advancement;
import org.bukkit.advancement.AdvancementProgress;
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
import org.bukkit.block.data.BlockData;
import org.bukkit.conversations.Conversable;
import org.bukkit.event.block.BlockBreakEvent;
@ -908,7 +909,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
* <li>There is no concept of resetting resource packs back to default
* within Minecraft, so players will have to relog to do so or you
* have to send an empty pack.
* <li>The request is send with "null" as the hash. This might result
* <li>The request is send with empty string as the hash. This might result
* in newer versions not loading the pack correctly.
* </ul>
*
@ -928,9 +929,13 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
* in the background, and will automatically switch to it once the
* download is complete. If the client has downloaded and cached a
* resource pack with the same hash in the past it will not download but
* directly apply the cached pack. When this request is sent for the very
* first time from a given server, the client will first display a
* confirmation GUI to the player before proceeding with the download.
* directly apply the cached pack. If the hash is null and the client has
* downloaded and cached the same resource pack in the past, it will
* perform a file size check against the response content to determine if
* the resource pack has changed and needs to be downloaded again. When
* this request is sent for the very first time from a given server, the
* client will first display a confirmation GUI to the player before
* proceeding with the download.
* <p>
* Notes:
* <ul>
@ -941,6 +946,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
* <li>There is no concept of resetting resource packs back to default
* within Minecraft, so players will have to relog to do so or you
* have to send an empty pack.
* <li>The request is sent with empty string as the hash when the hash is
* not provided. This might result in newer versions not loading the
* pack correctly.
* </ul>
*
* @param url The URL from which the client will download the resource
@ -952,11 +960,145 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
* @throws IllegalArgumentException Thrown if the URL is null.
* @throws IllegalArgumentException Thrown if the URL is too long. The
* length restriction is an implementation specific arbitrary value.
* @throws IllegalArgumentException Thrown if the hash is null.
* @throws IllegalArgumentException Thrown if the hash is not 20 bytes
* long.
*/
public void setResourcePack(@NotNull String url, @NotNull byte[] hash);
public void setResourcePack(@NotNull String url, @Nullable byte[] hash);
/**
* Request that the player's client download and switch resource packs.
* <p>
* The player's client will download the new resource pack asynchronously
* in the background, and will automatically switch to it once the
* download is complete. If the client has downloaded and cached a
* resource pack with the same hash in the past it will not download but
* directly apply the cached pack. If the hash is null and the client has
* downloaded and cached the same resource pack in the past, it will
* perform a file size check against the response content to determine if
* the resource pack has changed and needs to be downloaded again. When
* this request is sent for the very first time from a given server, the
* client will first display a confirmation GUI to the player before
* proceeding with the download.
* <p>
* Notes:
* <ul>
* <li>Players can disable server resources on their client, in which
* case this method will have no affect on them. Use the
* {@link PlayerResourcePackStatusEvent} to figure out whether or not
* the player loaded the pack!
* <li>There is no concept of resetting resource packs back to default
* within Minecraft, so players will have to relog to do so or you
* have to send an empty pack.
* <li>The request is sent with empty string as the hash when the hash is
* not provided. This might result in newer versions not loading the
* pack correctly.
* </ul>
*
* @param url The URL from which the client will download the resource
* pack. The string must contain only US-ASCII characters and should
* be encoded as per RFC 1738.
* @param hash The sha1 hash sum of the resource pack file which is used
* to apply a cached version of the pack directly without downloading
* if it is available. Hast to be 20 bytes long!
* @param prompt The optional custom prompt message to be shown to client.
* @throws IllegalArgumentException Thrown if the URL is null.
* @throws IllegalArgumentException Thrown if the URL is too long. The
* length restriction is an implementation specific arbitrary value.
* @throws IllegalArgumentException Thrown if the hash is not 20 bytes
* long.
*/
public void setResourcePack(@NotNull String url, @Nullable byte[] hash, @Nullable String prompt);
/**
* Request that the player's client download and switch resource packs.
* <p>
* The player's client will download the new resource pack asynchronously
* in the background, and will automatically switch to it once the
* download is complete. If the client has downloaded and cached a
* resource pack with the same hash in the past it will not download but
* directly apply the cached pack. If the hash is null and the client has
* downloaded and cached the same resource pack in the past, it will
* perform a file size check against the response content to determine if
* the resource pack has changed and needs to be downloaded again. When
* this request is sent for the very first time from a given server, the
* client will first display a confirmation GUI to the player before
* proceeding with the download.
* <p>
* Notes:
* <ul>
* <li>Players can disable server resources on their client, in which
* case this method will have no affect on them. Use the
* {@link PlayerResourcePackStatusEvent} to figure out whether or not
* the player loaded the pack!
* <li>There is no concept of resetting resource packs back to default
* within Minecraft, so players will have to relog to do so or you
* have to send an empty pack.
* <li>The request is sent with empty string as the hash when the hash is
* not provided. This might result in newer versions not loading the
* pack correctly.
* </ul>
*
* @param url The URL from which the client will download the resource
* pack. The string must contain only US-ASCII characters and should
* be encoded as per RFC 1738.
* @param hash The sha1 hash sum of the resource pack file which is used
* to apply a cached version of the pack directly without downloading
* if it is available. Hast to be 20 bytes long!
* @param force If true, the client will be disconnected from the server
* when it declines to use the resource pack.
* @throws IllegalArgumentException Thrown if the URL is null.
* @throws IllegalArgumentException Thrown if the URL is too long. The
* length restriction is an implementation specific arbitrary value.
* @throws IllegalArgumentException Thrown if the hash is not 20 bytes
* long.
*/
public void setResourcePack(@NotNull String url, @Nullable byte[] hash, boolean force);
/**
* Request that the player's client download and switch resource packs.
* <p>
* The player's client will download the new resource pack asynchronously
* in the background, and will automatically switch to it once the
* download is complete. If the client has downloaded and cached a
* resource pack with the same hash in the past it will not download but
* directly apply the cached pack. If the hash is null and the client has
* downloaded and cached the same resource pack in the past, it will
* perform a file size check against the response content to determine if
* the resource pack has changed and needs to be downloaded again. When
* this request is sent for the very first time from a given server, the
* client will first display a confirmation GUI to the player before
* proceeding with the download.
* <p>
* Notes:
* <ul>
* <li>Players can disable server resources on their client, in which
* case this method will have no affect on them. Use the
* {@link PlayerResourcePackStatusEvent} to figure out whether or not
* the player loaded the pack!
* <li>There is no concept of resetting resource packs back to default
* within Minecraft, so players will have to relog to do so or you
* have to send an empty pack.
* <li>The request is sent with empty string as the hash when the hash is
* not provided. This might result in newer versions not loading the
* pack correctly.
* </ul>
*
* @param url The URL from which the client will download the resource
* pack. The string must contain only US-ASCII characters and should
* be encoded as per RFC 1738.
* @param hash The sha1 hash sum of the resource pack file which is used
* to apply a cached version of the pack directly without downloading
* if it is available. Hast to be 20 bytes long!
* @param prompt The optional custom prompt message to be shown to client.
* @param force If true, the client will be disconnected from the server
* when it declines to use the resource pack.
* @throws IllegalArgumentException Thrown if the URL is null.
* @throws IllegalArgumentException Thrown if the URL is too long. The
* length restriction is an implementation specific arbitrary value.
* @throws IllegalArgumentException Thrown if the hash is not 20 bytes
* long.
*/
public void setResourcePack(@NotNull String url, @Nullable byte[] hash, @Nullable String prompt, boolean force);
/**
* Gets the Scoreboard displayed to this player
@ -1341,6 +1483,15 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
*/
public void openBook(@NotNull ItemStack book);
/**
* Open a Sign for editing by the Player.
*
* The Sign must be placed in the same world as the player.
*
* @param sign The sign to edit
*/
public void openSign(@NotNull Sign sign);
/**
* Shows the demo screen to the player, this screen is normally only seen in
* the demo version of the game.

View file

@ -3,7 +3,12 @@ package org.bukkit.inventory;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.event.entity.VillagerReplenishTradeEvent;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.NumberConversions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a merchant's trade.
@ -16,6 +21,30 @@ import org.jetbrains.annotations.NotNull;
* uses to increase.
* <br>
* A trade may or may not reward experience for being completed.
* <br>
* During trades, the {@link MerchantRecipe} dynamically adjusts the amount of
* its first ingredient based on the following criteria:
* <ul>
* <li>{@link #getDemand() Demand}: This value is periodically updated by the
* villager that owns this merchant recipe based on how often the recipe has
* been used since it has been last restocked in relation to its
* {@link #getMaxUses maximum uses}. The amount by which the demand influences
* the amount of the first ingredient is scaled by the recipe's
* {@link #getPriceMultiplier price multiplier}, and can never be below zero.
* <li>{@link #getSpecialPrice() Special price}: This value is dynamically
* updated whenever a player starts and stops trading with a villager that owns
* this merchant recipe. It is based on the player's individual reputation with
* the villager, and the player's currently active status effects (see
* {@link PotionEffectType#HERO_OF_THE_VILLAGE}). The influence of the player's
* reputation on the special price is scaled by the recipe's
* {@link #getPriceMultiplier price multiplier}.
* </ul>
* The adjusted amount of the first ingredient is calculated by adding up the
* original amount of the first ingredient, the demand scaled by the recipe's
* {@link #getPriceMultiplier price multiplier} and truncated to the next lowest
* integer value greater than or equal to 0, and the special price, and then
* constraining the resulting value between <code>1</code> and the item stack's
* {@link ItemStack#getMaxStackSize() maximum stack size}.
*
* @see org.bukkit.event.entity.VillagerReplenishTradeEvent
*/
@ -26,6 +55,8 @@ public class MerchantRecipe implements Recipe {
private int uses;
private int maxUses;
private boolean experienceReward;
private int specialPrice;
private int demand;
private int villagerExperience;
private float priceMultiplier;
@ -34,16 +65,22 @@ public class MerchantRecipe implements Recipe {
}
public MerchantRecipe(@NotNull ItemStack result, int uses, int maxUses, boolean experienceReward) {
this(result, uses, maxUses, experienceReward, 0, 0.0F);
this(result, uses, maxUses, experienceReward, 0, 0.0F, 0, 0);
}
public MerchantRecipe(@NotNull ItemStack result, int uses, int maxUses, boolean experienceReward, int villagerExperience, float priceMultiplier) {
this(result, uses, maxUses, experienceReward, villagerExperience, priceMultiplier, 0, 0);
}
public MerchantRecipe(@NotNull ItemStack result, int uses, int maxUses, boolean experienceReward, int villagerExperience, float priceMultiplier, int demand, int specialPrice) {
this.result = result;
this.uses = uses;
this.maxUses = maxUses;
this.experienceReward = experienceReward;
this.villagerExperience = villagerExperience;
this.priceMultiplier = priceMultiplier;
this.demand = demand;
this.specialPrice = specialPrice;
}
@NotNull
@ -78,6 +115,95 @@ public class MerchantRecipe implements Recipe {
return copy;
}
/**
* Gets the {@link #adjust(ItemStack) adjusted} first ingredient.
*
* @return the adjusted first ingredient, or <code>null</code> if this
* recipe has no ingredients
* @see #adjust(ItemStack)
*/
@Nullable
public ItemStack getAdjustedIngredient1() {
if (this.ingredients.isEmpty()) {
return null;
}
ItemStack firstIngredient = this.ingredients.get(0).clone();
adjust(firstIngredient);
return firstIngredient;
}
/**
* Modifies the amount of the given {@link ItemStack} in the same way as
* MerchantRecipe dynamically adjusts the amount of the first ingredient
* during trading.
* <br>
* This is calculated by adding up the original amount of the item, the
* demand scaled by the recipe's
* {@link #getPriceMultiplier price multiplier} and truncated to the next
* lowest integer value greater than or equal to 0, and the special price,
* and then constraining the resulting value between <code>1</code> and the
* {@link ItemStack}'s {@link ItemStack#getMaxStackSize()
* maximum stack size}.
*
* @param itemStack the item to adjust
*/
public void adjust(@Nullable ItemStack itemStack) {
if (itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() <= 0) {
return;
}
int amount = itemStack.getAmount();
int demandAdjustment = Math.max(0, NumberConversions.floor((float) (amount * getDemand()) * getPriceMultiplier()));
itemStack.setAmount(Math.max(1, Math.min(itemStack.getMaxStackSize(), amount + demandAdjustment + getSpecialPrice())));
}
/**
* Get the value of the demand for the item in {@link #getResult()}.
*
* @return the demand for the item
*/
public int getDemand() {
return demand;
}
/**
* Set the value of the demand for the item in {@link #getResult()}.
* <br>
* <b>Note: </b> This value is updated when the item is purchase
*
* @param demand demand value
*/
public void setDemand(int demand) {
this.demand = demand;
}
/**
* Get the special price for this trade.
* <br>
* <b>Note: </b> This value can be updated by
* {@link VillagerReplenishTradeEvent#getBonus()} or by
* {@link PotionEffectType#HERO_OF_THE_VILLAGE}
*
* @return special price value
*/
public int getSpecialPrice() {
return specialPrice;
}
/**
* Set the special value for this trade.
* <br>
* <b>Note: </b> This value can be updated by
* {@link VillagerReplenishTradeEvent#getBonus()} or by
* {@link PotionEffectType#HERO_OF_THE_VILLAGE}
*
* @param specialPrice special price value
*/
public void setSpecialPrice(int specialPrice) {
this.specialPrice = specialPrice;
}
/**
* Get the number of times this trade has been used.
*

View file

@ -111,6 +111,16 @@ public abstract class ConfigurationSectionTest extends AbstractTestingBase {
assertTrue(section.contains("doenst-exist-two", false));
}
@Test
public void testContainsDoesNotCreateSection() {
ConfigurationSection section = getConfigurationSection();
section.addDefault("notExistingSection.Value", "Test String");
assertFalse(section.contains("notExistingSection", true));
assertFalse(section.contains("notExistingSection.Value", true));
assertFalse(section.contains("notExistingSection", true));
}
@Test
public void testIsSet() {
ConfigurationSection section = getConfigurationSection();

View file

@ -103,6 +103,43 @@ public abstract class ConfigurationTest {
}
}
/**
* Test of addDefaults method, of class Configuration but with existing
* defaults in a child section.
*/
@Test
public void testAddDefaults_Configuration_WithExisting() {
Configuration config = getConfig();
Map<String, Object> values = getTestValues();
values.put("default-section.string", "String Value");
Configuration defaults = getConfig();
Configuration defaultsAdditional = getConfig();
for (Map.Entry<String, Object> entry : values.entrySet()) {
defaults.set(entry.getKey(), entry.getValue());
}
config.addDefaults(defaults);
Map<String, Object> additionalValues = new HashMap<>();
additionalValues.put("default-section.additionalString", "Additional String");
additionalValues.put("default-section.additionalInt", 42);
for (Map.Entry<String, Object> entry : additionalValues.entrySet()) {
defaultsAdditional.set(entry.getKey(), entry.getValue());
}
config.addDefaults(defaultsAdditional);
values.putAll(additionalValues);
for (Map.Entry<String, Object> entry : values.entrySet()) {
String path = entry.getKey();
Object object = entry.getValue();
assertEquals(object, config.get(path));
assertTrue(config.contains(path));
assertFalse(config.isSet(path));
assertTrue(config.getDefaults().isSet(path));
}
}
/**
* Test of setDefaults method, of class Configuration.
*/

View file

@ -4,6 +4,8 @@ import static org.junit.Assert.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.bukkit.configuration.MemoryConfigurationTest;
import org.junit.Rule;
@ -19,9 +21,17 @@ public abstract class FileConfigurationTest extends MemoryConfigurationTest {
public abstract String getTestValuesString();
public abstract String getTestHeaderInput();
public abstract List<String> getTestCommentInput();
public abstract String getTestHeaderResult();
public abstract String getTestCommentResult();
public abstract List<String> getTestHeaderComments();
public abstract String getTestHeaderCommentsResult();
public abstract List<String> getTestKeyComments();
public abstract String getTestHeaderKeyCommentResult();
@Test
public void testSave_File() throws Exception {
@ -127,69 +137,6 @@ public abstract class FileConfigurationTest extends MemoryConfigurationTest {
assertEquals(saved, config.saveToString());
}
@Test
public void testSaveToStringWithHeader() {
FileConfiguration config = getConfig();
config.options().header(getTestHeaderInput());
for (Map.Entry<String, Object> entry : getTestValues().entrySet()) {
config.set(entry.getKey(), entry.getValue());
}
String result = config.saveToString();
String expected = getTestHeaderResult() + "\n" + getTestValuesString();
assertEquals(expected, result);
}
@Test
public void testParseHeader() throws Exception {
FileConfiguration config = getConfig();
Map<String, Object> values = getTestValues();
String saved = getTestValuesString();
String header = getTestHeaderResult();
String expected = getTestHeaderInput();
config.loadFromString(header + "\n" + saved);
assertEquals(expected, config.options().header());
for (Map.Entry<String, Object> entry : values.entrySet()) {
assertEquals(entry.getValue(), config.get(entry.getKey()));
}
assertEquals(values.keySet(), config.getKeys(true));
assertEquals(header + "\n" + saved, config.saveToString());
}
@Test
public void testCopyHeader() throws Exception {
FileConfiguration config = getConfig();
FileConfiguration defaults = getConfig();
Map<String, Object> values = getTestValues();
String saved = getTestValuesString();
String header = getTestHeaderResult();
String expected = getTestHeaderInput();
defaults.loadFromString(header);
config.loadFromString(saved);
config.setDefaults(defaults);
assertNull(config.options().header());
assertEquals(expected, defaults.options().header());
for (Map.Entry<String, Object> entry : values.entrySet()) {
assertEquals(entry.getValue(), config.get(entry.getKey()));
}
assertEquals(values.keySet(), config.getKeys(true));
assertEquals(header + "\n" + saved, config.saveToString());
config = getConfig();
config.loadFromString(getTestHeaderResult() + saved);
assertEquals(getTestHeaderResult() + saved, config.saveToString());
}
@Test
public void testReloadEmptyConfig() throws Exception {
FileConfiguration config = getConfig();
@ -271,4 +218,178 @@ public abstract class FileConfigurationTest extends MemoryConfigurationTest {
assertFalse(config.contains("test"));
assertFalse(config.getBoolean("test"));
}
@Test
public void testSaveWithComments() {
FileConfiguration config = getConfig();
config.options().parseComments(true);
for (Map.Entry<String, Object> entry : getTestValues().entrySet()) {
config.set(entry.getKey(), entry.getValue());
}
String key = getTestValues().keySet().iterator().next();
config.setComments(key, getTestCommentInput());
String result = config.saveToString();
String expected = getTestCommentResult() + "\n" + getTestValuesString();
assertEquals(expected, result);
}
@Test
public void testSaveWithoutComments() {
FileConfiguration config = getConfig();
config.options().parseComments(false);
for (Map.Entry<String, Object> entry : getTestValues().entrySet()) {
config.set(entry.getKey(), entry.getValue());
}
String key = getTestValues().keySet().iterator().next();
config.setComments(key, getTestCommentInput());
String result = config.saveToString();
String expected = getTestValuesString();
assertEquals(expected, result);
}
@Test
public void testLoadWithComments() throws Exception {
FileConfiguration config = getConfig();
Map<String, Object> values = getTestValues();
String saved = getTestValuesString();
String comments = getTestCommentResult();
config.options().parseComments(true);
config.loadFromString(comments + "\n" + saved);
for (Map.Entry<String, Object> entry : values.entrySet()) {
assertEquals(entry.getValue(), config.get(entry.getKey()));
}
assertEquals(values.keySet(), config.getKeys(true));
assertEquals(comments + "\n" + saved, config.saveToString());
}
@Test
public void testLoadWithoutComments() throws Exception {
FileConfiguration config = getConfig();
Map<String, Object> values = getTestValues();
String saved = getTestValuesString();
String comments = getTestCommentResult();
config.options().parseComments(false);
config.loadFromString(comments + "\n" + saved);
config.options().parseComments(true);
for (Map.Entry<String, Object> entry : values.entrySet()) {
assertEquals(entry.getValue(), config.get(entry.getKey()));
}
assertEquals(values.keySet(), config.getKeys(true));
assertEquals(saved, config.saveToString());
}
@Test
public void testSaveWithCommentsHeader() {
FileConfiguration config = getConfig();
config.options().parseComments(true);
for (Map.Entry<String, Object> entry : getTestValues().entrySet()) {
config.set(entry.getKey(), entry.getValue());
}
String key = getTestValues().keySet().iterator().next();
config.options().setHeader(getTestHeaderComments());
config.setComments(key, getTestKeyComments());
String result = config.saveToString();
String expected = getTestHeaderKeyCommentResult() + getTestValuesString();
assertEquals(expected, result);
}
@Test
public void testLoadWithCommentsHeader() throws Exception {
FileConfiguration config = getConfig();
Map<String, Object> values = getTestValues();
String saved = getTestValuesString();
String comments = getTestHeaderKeyCommentResult();
config.options().parseComments(true);
config.loadFromString(comments + saved);
for (Map.Entry<String, Object> entry : values.entrySet()) {
assertEquals(entry.getValue(), config.get(entry.getKey()));
}
String key = getTestValues().keySet().iterator().next();
assertEquals(getTestHeaderComments(), config.options().getHeader());
assertEquals(getTestKeyComments(), config.getComments(key));
assertEquals(values.keySet(), config.getKeys(true));
assertEquals(comments + saved, config.saveToString());
}
@Test
public void testSaveWithCommentsFooter() {
FileConfiguration config = getConfig();
config.options().parseComments(true);
for (Map.Entry<String, Object> entry : getTestValues().entrySet()) {
config.set(entry.getKey(), entry.getValue());
}
config.options().setFooter(getTestHeaderComments());
String result = config.saveToString();
String expected = getTestValuesString() + getTestHeaderCommentsResult();
assertEquals(expected, result);
}
@Test
public void testLoadWithCommentsFooter() throws Exception {
FileConfiguration config = getConfig();
Map<String, Object> values = getTestValues();
String saved = getTestValuesString();
String comments = getTestHeaderCommentsResult();
config.options().parseComments(true);
config.loadFromString(saved + comments);
for (Map.Entry<String, Object> entry : values.entrySet()) {
assertEquals(entry.getValue(), config.get(entry.getKey()));
}
assertEquals(getTestHeaderComments(), config.options().getFooter());
assertEquals(values.keySet(), config.getKeys(true));
assertEquals(saved + comments, config.saveToString());
}
@Test
public void testLoadWithCommentsInline() throws Exception {
FileConfiguration config = getConfig();
config.options().parseComments(true);
config.loadFromString("key1: value1\nkey2: value2 # Test inline\nkey3: value3");
assertEquals(Arrays.asList(" Test inline"), config.getInlineComments("key2"));
}
@Test
public void testSaveWithCommentsInline() {
FileConfiguration config = getConfig();
config.options().parseComments(true);
config.set("key1", "value1");
config.set("key2", "value2");
config.set("key3", "value3");
config.setInlineComments("key2", Arrays.asList(" Test inline"));
String result = config.saveToString();
String expected = "key1: value1\nkey2: value2 # Test inline\nkey3: value3\n";
assertEquals(expected, result);
}
}

View file

@ -1,6 +1,9 @@
package org.bukkit.configuration.file;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
public class YamlConfigurationTest extends FileConfigurationTest {
@ -11,13 +14,43 @@ public class YamlConfigurationTest extends FileConfigurationTest {
}
@Override
public String getTestHeaderInput() {
return "This is a sample\nheader.\n\nNewline above should be commented.\n\n";
public List<String> getTestCommentInput() {
List<String> comments = new ArrayList<>();
comments.add(" This is a sample");
comments.add(" header.");
comments.add(" Newline above should be commented.");
comments.add("");
comments.add("");
comments.add(null);
comments.add(null);
comments.add(" Comment of first Key");
comments.add(" and a second line.");
return comments;
}
@Override
public String getTestHeaderResult() {
return "# This is a sample\n# header.\n# \n# Newline above should be commented.\n\n";
public String getTestCommentResult() {
return "# This is a sample\n# header.\n# Newline above should be commented.\n#\n#\n\n\n# Comment of first Key\n# and a second line.";
}
@Override
public List<String> getTestHeaderComments() {
return Arrays.asList(" Header", " Second Line");
}
@Override
public String getTestHeaderCommentsResult() {
return "# Header\n# Second Line\n";
}
@Override
public List<String> getTestKeyComments() {
return Arrays.asList(" First key Comment", " Second Line");
}
@Override
public String getTestHeaderKeyCommentResult() {
return "# Header\n# Second Line\n\n# First key Comment\n# Second Line\n";
}
@Override