mirror of
https://hub.spigotmc.org/stash/scm/spigot/bukkit.git
synced 2025-08-21 05:44:17 +00:00
Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers
This commit is contained in:
commit
cf1d2005bb
19 changed files with 1214 additions and 259 deletions
20
pom.xml
20
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
81
src/main/java/org/bukkit/configuration/SectionPathData.java
Normal file
81
src/main/java/org/bukkit/configuration/SectionPathData.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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()} -> {@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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue