This commit is contained in:
YuTian 2024-07-23 14:32:15 +08:00
commit 420c5c4ac8
38 changed files with 10072 additions and 0 deletions

40
.gitignore vendored Normal file
View File

@ -0,0 +1,40 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
/.idea/
/out/

51
pom.xml Normal file
View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.io.yutian</groupId>
<artifactId>PluginSettingTool</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.formdev/flatlaf -->
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.30</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!-- bundled with Minecraft, should be kept in sync -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
<scope>compile</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jetbrains/annotations -->
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>24.1.0</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,13 @@
package com.io.yutian.pluginsettingtool;
import com.formdev.flatlaf.themes.FlatMacLightLaf;
import com.io.yutian.pluginsettingtool.frame.MainFrame;
public class Main {
public static void main(String[] args) {
FlatMacLightLaf.setup();
javax.swing.SwingUtilities.invokeLater(() -> new MainFrame());
}
}

View File

@ -0,0 +1,38 @@
package com.io.yutian.pluginsettingtool.data;
public class GiftData {
private String event;
private String win;
private String sound;
public GiftData(String event, String win, String sound) {
this.event = event;
this.win = win;
this.sound = sound;
}
public void setEvent(String event) {
this.event = event;
}
public void setWin(String win) {
this.win = win;
}
public void setSound(String sound) {
this.sound = sound;
}
public String getEvent() {
return event;
}
public String getWin() {
return win;
}
public String getSound() {
return sound;
}
}

View File

@ -0,0 +1,339 @@
package com.io.yutian.pluginsettingtool.frame;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.*;
import javax.swing.event.*;
import com.io.yutian.pluginsettingtool.data.GiftData;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.yaml.snakeyaml.Yaml;
import java.awt.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.*;
import javax.swing.filechooser.FileFilter;
public class MainFrame extends JFrame {
private File file;
private FileConfiguration config;
private Map<String, GiftData> giftDatas = new HashMap<>();
public MainFrame() {
initComponents();
init(false);
}
private void init(boolean visible) {
label2.setVisible(visible);
label3.setVisible(visible);
label4.setVisible(visible);
comboBox1.setVisible(visible);
comboBox2.setVisible(visible);
comboBox3.setVisible(visible);
button1.setVisible(visible);
}
private void load(File file) {
if (!file.getName().endsWith(".yml")) {
JOptionPane.showMessageDialog(this, "非法的文件类型", "", JOptionPane.WARNING_MESSAGE);
return;
}
this.file = file;
config = YamlConfiguration.loadConfiguration(file);
giftDatas = new HashMap<>();
if (config.contains("GiftData") && config.isConfigurationSection("GiftData")) {
ConfigurationSection section = config.getConfigurationSection("GiftData");
for (String key : section.getKeys(false)) {
String event = section.getString(key+".event");
String win = section.getString(key+".win");
String sound = section.getString(key+".sound");
giftDatas.put(key, new GiftData(event, win, sound));
}
}
DefaultListModel<String> listModel = new DefaultListModel<>();
List<String> keyList = new ArrayList<>(giftDatas.keySet());
for (int i = keyList.size() - 1; i >= 0; i--) {
listModel.addElement(keyList.get(i));
}
list1.setModel(listModel);
init(true);
}
private void save(File file) {
ConfigurationSection section = (config.contains("GiftData") && config.isConfigurationSection("GiftData")) ? config.getConfigurationSection("GiftData") : config.createSection("GiftData");
for (Map.Entry<String, GiftData> entry : giftDatas.entrySet()) {
String key = entry.getKey();
GiftData giftData = entry.getValue();
section.set(key+".event", giftData.getEvent());
section.set(key+".win", giftData.getWin());
section.set(key+".sound", giftData.getSound());
}
try {
config.save(file);
} catch (IOException e) {
e.printStackTrace();
}
JOptionPane.showMessageDialog(this, "保存成功", "", JOptionPane.INFORMATION_MESSAGE);
}
private void reloadGiftData() {
String value = (String) list1.getSelectedValue();
if (value == null) {
return;
}
GiftData giftData = giftDatas.get(value);
if (giftData == null) {
return;
}
comboBox1.setSelectedItem(giftData.getEvent());
comboBox3.setSelectedItem(giftData.getSound());
}
private void list1ValueChanged(ListSelectionEvent e) {
reloadGiftData();
}
private void button1(ActionEvent e) {
save(file);
}
private void comboBox1ItemStateChanged(ItemEvent e) {
String value = (String) list1.getSelectedValue();
if (value == null) {
return;
}
GiftData giftData = giftDatas.get(value);
if (giftData == null) {
return;
}
String v = (String) e.getItem();
giftData.setEvent(v);
giftDatas.put(value, giftData);
}
private void comboBox2ItemStateChanged(ItemEvent e) {
String value = (String) list1.getSelectedValue();
if (value == null) {
return;
}
GiftData giftData = giftDatas.get(value);
if (giftData == null) {
return;
}
String v = (String) e.getItem();
giftData.setWin(v);
giftDatas.put(value, giftData);
}
private void comboBox3ItemStateChanged(ItemEvent e) {
String value = (String) list1.getSelectedValue();
if (value == null) {
return;
}
GiftData giftData = giftDatas.get(value);
if (giftData == null) {
return;
}
String v = (String) e.getItem();
giftData.setSound(v);
giftDatas.put(value, giftData);
}
private void initComponents() {
fileChooser = new JFileChooser();
fileChooser.setAcceptAllFileFilterUsed(false);
fileChooser.setMultiSelectionEnabled(false);
fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
fileChooser.addChoosableFileFilter(new FileFilter() {
@Override
public boolean accept(File f) {
return true;
}
@Override
public String getDescription() {
return "*.yml";
}
});
DropTarget dropTarget = new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, new DropTargetAdapter() {
@Override
public void drop(DropTargetDropEvent dtde) {
try {
if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
java.util.List<File> list = (List<File>) dtde.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
if (list.size() > 0) {
File file = list.get(0);
load(file);
dtde.dropComplete(true);
} else {
dtde.dropComplete(false);
}
} else {
dtde.rejectDrop();
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off
panel1 = new JPanel();
scrollPane1 = new JScrollPane();
list1 = new JList();
label1 = new JLabel();
label2 = new JLabel();
label3 = new JLabel();
label4 = new JLabel();
comboBox1 = new JComboBox<>();
comboBox2 = new JComboBox<>();
comboBox3 = new JComboBox<>();
button1 = new JButton();
//======== this ========
setTitle("\u63d2\u4ef6\u914d\u7f6e\u5de5\u5177");
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setVisible(true);
setResizable(false);
Container contentPane = getContentPane();
contentPane.setLayout(null);
//======== panel1 ========
{
panel1.setLayout(null);
//======== scrollPane1 ========
{
//---- list1 ----
list1.addListSelectionListener(e -> list1ValueChanged(e));
scrollPane1.setViewportView(list1);
}
panel1.add(scrollPane1);
scrollPane1.setBounds(35, 30, 180, 375);
//---- label1 ----
label1.setText("\u793c\u7269");
label1.setFont(label1.getFont().deriveFont(label1.getFont().getSize() + 3f));
panel1.add(label1);
label1.setBounds(new Rectangle(new Point(35, 5), label1.getPreferredSize()));
//---- label2 ----
label2.setText("\u6548\u679c");
label2.setFont(label2.getFont().deriveFont(label2.getFont().getSize() + 3f));
panel1.add(label2);
label2.setBounds(270, 30, label2.getPreferredSize().width, 21);
//---- label3 ----
label3.setText("\u80dc\u5229");
label3.setFont(label3.getFont().deriveFont(label3.getFont().getSize() + 3f));
panel1.add(label3);
label3.setBounds(270, 90, 30, 21);
//---- label4 ----
label4.setText("\u58f0\u97f3");
label4.setFont(label4.getFont().deriveFont(label4.getFont().getSize() + 3f));
panel1.add(label4);
label4.setBounds(270, 150, 30, 21);
//---- comboBox1 ----
comboBox1.setModel(new DefaultComboBoxModel<>(new String[] {
"\u6548\u679c1",
"\u6548\u679c2",
"\u6548\u679c3"
}));
comboBox1.addItemListener(e -> comboBox1ItemStateChanged(e));
panel1.add(comboBox1);
comboBox1.setBounds(new Rectangle(new Point(305, 25), comboBox1.getPreferredSize()));
//---- comboBox2 ----
comboBox2.setModel(new DefaultComboBoxModel<>(new String[] {
"\u6548\u679c1",
"\u6548\u679c2",
"\u6548\u679c3"
}));
comboBox2.addItemListener(e -> comboBox2ItemStateChanged(e));
panel1.add(comboBox2);
comboBox2.setBounds(305, 85, 75, 28);
//---- comboBox3 ----
comboBox3.setModel(new DefaultComboBoxModel<>(new String[] {
"\u6548\u679c1",
"\u6548\u679c2",
"\u6548\u679c3"
}));
comboBox3.addItemListener(e -> comboBox3ItemStateChanged(e));
panel1.add(comboBox3);
comboBox3.setBounds(305, 145, 75, 28);
//---- button1 ----
button1.setText("\u4fdd\u5b58");
button1.setFont(new Font("Noto Sans", Font.BOLD, 14));
button1.addActionListener(e -> button1(e));
panel1.add(button1);
button1.setBounds(500, 365, 80, 37);
{
// compute preferred size
Dimension preferredSize = new Dimension();
for(int i = 0; i < panel1.getComponentCount(); i++) {
Rectangle bounds = panel1.getComponent(i).getBounds();
preferredSize.width = Math.max(bounds.x + bounds.width, preferredSize.width);
preferredSize.height = Math.max(bounds.y + bounds.height, preferredSize.height);
}
Insets insets = panel1.getInsets();
preferredSize.width += insets.right;
preferredSize.height += insets.bottom;
panel1.setMinimumSize(preferredSize);
panel1.setPreferredSize(preferredSize);
}
}
contentPane.add(panel1);
panel1.setBounds(0, 0, 630, 420);
{
// compute preferred size
Dimension preferredSize = new Dimension();
for(int i = 0; i < contentPane.getComponentCount(); i++) {
Rectangle bounds = contentPane.getComponent(i).getBounds();
preferredSize.width = Math.max(bounds.x + bounds.width, preferredSize.width);
preferredSize.height = Math.max(bounds.y + bounds.height, preferredSize.height);
}
Insets insets = contentPane.getInsets();
preferredSize.width += insets.right;
preferredSize.height += insets.bottom;
contentPane.setMinimumSize(preferredSize);
contentPane.setPreferredSize(preferredSize);
}
pack();
setLocationRelativeTo(getOwner());
// JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on
}
private JFileChooser fileChooser;
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off
private JPanel panel1;
private JScrollPane scrollPane1;
private JList list1;
private JLabel label1;
private JLabel label2;
private JLabel label3;
private JLabel label4;
private JComboBox<String> comboBox1;
private JComboBox<String> comboBox2;
private JComboBox<String> comboBox3;
private JButton button1;
// JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on
}

View File

@ -0,0 +1,128 @@
JFDML JFormDesigner: "8.2.3.0.386" Java: "17.0.11" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
root: new FormRoot {
add( new FormWindow( "javax.swing.JFrame", new FormLayoutManager( class com.jformdesigner.runtime.NullLayout ) ) {
name: "this"
"title": "插件配置工具"
"defaultCloseOperation": 2
"visible": true
"resizable": false
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class com.jformdesigner.runtime.NullLayout ) ) {
name: "panel1"
add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
name: "scrollPane1"
add( new FormComponent( "javax.swing.JList" ) {
name: "list1"
addEvent( new FormEvent( "javax.swing.event.ListSelectionListener", "valueChanged", "list1ValueChanged", true ) )
} )
}, new FormLayoutConstraints( class com.jformdesigner.runtime.NullConstraints ) {
"x": 35
"y": 30
"width": 180
"height": 375
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "label1"
"text": "礼物"
"font": new com.jformdesigner.model.SwingDerivedFont( null, 0, 3, false )
}, new FormLayoutConstraints( class com.jformdesigner.runtime.NullConstraints ) {
"x": 35
"y": 5
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "label2"
"text": "效果"
"font": &SwingDerivedFont0 new com.jformdesigner.model.SwingDerivedFont( null, 0, 3, false )
}, new FormLayoutConstraints( class com.jformdesigner.runtime.NullConstraints ) {
"x": 270
"y": 30
"height": 21
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "label3"
"text": "胜利"
"font": #SwingDerivedFont0
}, new FormLayoutConstraints( class com.jformdesigner.runtime.NullConstraints ) {
"width": 30
"height": 21
"x": 270
"y": 90
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "label4"
"text": "声音"
"font": #SwingDerivedFont0
}, new FormLayoutConstraints( class com.jformdesigner.runtime.NullConstraints ) {
"width": 30
"height": 21
"x": 270
"y": 150
} )
add( new FormComponent( "javax.swing.JComboBox" ) {
name: "comboBox1"
"model": new javax.swing.DefaultComboBoxModel {
selectedItem: "效果1"
addElement( "效果1" )
addElement( "效果2" )
addElement( "效果3" )
}
addEvent( new FormEvent( "java.awt.event.ItemListener", "itemStateChanged", "comboBox1ItemStateChanged", true ) )
}, new FormLayoutConstraints( class com.jformdesigner.runtime.NullConstraints ) {
"x": 305
"y": 25
} )
add( new FormComponent( "javax.swing.JComboBox" ) {
name: "comboBox2"
"model": new javax.swing.DefaultComboBoxModel {
selectedItem: "效果1"
addElement( "效果1" )
addElement( "效果2" )
addElement( "效果3" )
}
addEvent( new FormEvent( "java.awt.event.ItemListener", "itemStateChanged", "comboBox2ItemStateChanged", true ) )
}, new FormLayoutConstraints( class com.jformdesigner.runtime.NullConstraints ) {
"width": 75
"height": 28
"x": 305
"y": 85
} )
add( new FormComponent( "javax.swing.JComboBox" ) {
name: "comboBox3"
"model": new javax.swing.DefaultComboBoxModel {
selectedItem: "效果1"
addElement( "效果1" )
addElement( "效果2" )
addElement( "效果3" )
}
addEvent( new FormEvent( "java.awt.event.ItemListener", "itemStateChanged", "comboBox3ItemStateChanged", true ) )
}, new FormLayoutConstraints( class com.jformdesigner.runtime.NullConstraints ) {
"width": 75
"height": 28
"x": 305
"y": 145
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "button1"
"text": "保存"
"font": new java.awt.Font( "Noto Sans", 1, 14 )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "button1", true ) )
}, new FormLayoutConstraints( class com.jformdesigner.runtime.NullConstraints ) {
"x": 500
"y": 365
"width": 80
"height": 37
} )
}, new FormLayoutConstraints( class com.jformdesigner.runtime.NullConstraints ) {
"width": 630
"height": 420
"x": 0
"y": 0
} )
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 630, 450 )
} )
}
}

View File

@ -0,0 +1,89 @@
package org.bukkit.configuration;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a source of configurable options and settings
*/
public interface Configuration extends ConfigurationSection {
/**
* Sets the default value of the given path as provided.
* <p>
* If no source {@link Configuration} was provided as a default
* collection, then a new {@link MemoryConfiguration} will be created to
* hold the new default value.
* <p>
* If value is null, the value will be removed from the default
* Configuration source.
*
* @param path Path of the value to set.
* @param value Value to set the default to.
* @throws IllegalArgumentException Thrown if path is null.
*/
@Override
public void addDefault(@NotNull String path, @Nullable Object value);
/**
* Sets the default values of the given paths as provided.
* <p>
* If no source {@link Configuration} was provided as a default
* collection, then a new {@link MemoryConfiguration} will be created to
* hold the new default values.
*
* @param defaults A map of Path{@literal ->}Values to add to defaults.
* @throws IllegalArgumentException Thrown if defaults is null.
*/
public void addDefaults(@NotNull Map<String, Object> defaults);
/**
* Sets the default values of the given paths as provided.
* <p>
* If no source {@link Configuration} was provided as a default
* collection, then a new {@link MemoryConfiguration} will be created to
* hold the new default value.
* <p>
* This method will not hold a reference to the specified Configuration,
* nor will it automatically update if that Configuration ever changes. If
* you require this, you should set the default source with {@link
* #setDefaults(org.bukkit.configuration.Configuration)}.
*
* @param defaults A configuration holding a list of defaults to copy.
* @throws IllegalArgumentException Thrown if defaults is null or this.
*/
public void addDefaults(@NotNull Configuration defaults);
/**
* Sets the source of all default values for this {@link Configuration}.
* <p>
* If a previous source was set, or previous default values were defined,
* then they will not be copied to the new source.
*
* @param defaults New source of default values for this configuration.
* @throws IllegalArgumentException Thrown if defaults is null or this.
*/
public void setDefaults(@NotNull Configuration defaults);
/**
* Gets the source {@link Configuration} for this configuration.
* <p>
* If no configuration source was set, but default values were added, then
* a {@link MemoryConfiguration} will be returned. If no source was set
* and no defaults were set, then this method will return null.
*
* @return Configuration source for default values, or null if none exist.
*/
@Nullable
public Configuration getDefaults();
/**
* Gets the {@link ConfigurationOptions} for this {@link Configuration}.
* <p>
* All setters through this method are chainable.
*
* @return Options for this configuration
*/
@NotNull
public ConfigurationOptions options();
}

View File

@ -0,0 +1,95 @@
package org.bukkit.configuration;
import org.jetbrains.annotations.NotNull;
/**
* Various settings for controlling the input and output of a {@link
* Configuration}
*/
public class ConfigurationOptions {
private char pathSeparator = '.';
private boolean copyDefaults = false;
private final Configuration configuration;
protected ConfigurationOptions(@NotNull Configuration configuration) {
this.configuration = configuration;
}
/**
* Returns the {@link Configuration} that this object is responsible for.
*
* @return Parent configuration
*/
@NotNull
public Configuration configuration() {
return configuration;
}
/**
* Gets the char that will be used to separate {@link
* ConfigurationSection}s
* <p>
* This value does not affect how the {@link Configuration} is stored,
* only in how you access the data. The default value is '.'.
*
* @return Path separator
*/
public char pathSeparator() {
return pathSeparator;
}
/**
* Sets the char that will be used to separate {@link
* ConfigurationSection}s
* <p>
* This value does not affect how the {@link Configuration} is stored,
* only in how you access the data. The default value is '.'.
*
* @param value Path separator
* @return This object, for chaining
*/
@NotNull
public ConfigurationOptions pathSeparator(char value) {
this.pathSeparator = value;
return this;
}
/**
* Checks if the {@link Configuration} should copy values from its default
* {@link Configuration} directly.
* <p>
* If this is true, all values in the default Configuration will be
* directly copied, making it impossible to distinguish between values
* that were set and values that are provided by default. As a result,
* {@link ConfigurationSection#contains(java.lang.String)} will always
* return the same value as {@link
* ConfigurationSection#isSet(java.lang.String)}. The default value is
* false.
*
* @return Whether or not defaults are directly copied
*/
public boolean copyDefaults() {
return copyDefaults;
}
/**
* Sets if the {@link Configuration} should copy values from its default
* {@link Configuration} directly.
* <p>
* If this is true, all values in the default Configuration will be
* directly copied, making it impossible to distinguish between values
* that were set and values that are provided by default. As a result,
* {@link ConfigurationSection#contains(java.lang.String)} will always
* return the same value as {@link
* ConfigurationSection#isSet(java.lang.String)}. The default value is
* false.
*
* @param value Whether or not defaults are directly copied
* @return This object, for chaining
*/
@NotNull
public ConfigurationOptions copyDefaults(boolean value) {
this.copyDefaults = value;
return this;
}
}

View File

@ -0,0 +1,829 @@
package org.bukkit.configuration;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a section of a {@link Configuration}
*/
public interface ConfigurationSection {
/**
* Gets a set containing all keys in this section.
* <p>
* If deep is set to true, then this will contain all the keys within any
* child {@link ConfigurationSection}s (and their children, etc). These
* will be in a valid path notation for you to use.
* <p>
* If deep is set to false, then this will contain only the keys of any
* direct children, and not their own children.
*
* @param deep Whether or not to get a deep list, as opposed to a shallow
* list.
* @return Set of keys contained within this ConfigurationSection.
*/
@NotNull
public Set<String> getKeys(boolean deep);
/**
* Gets a Map containing all keys and their values for this section.
* <p>
* If deep is set to true, then this will contain all the keys and values
* within any child {@link ConfigurationSection}s (and their children,
* etc). These keys will be in a valid path notation for you to use.
* <p>
* If deep is set to false, then this will contain only the keys and
* values of any direct children, and not their own children.
*
* @param deep Whether or not to get a deep list, as opposed to a shallow
* list.
* @return Map of keys and values of this section.
*/
@NotNull
public Map<String, Object> getValues(boolean deep);
/**
* Checks if this {@link ConfigurationSection} contains the given path.
* <p>
* If the value for the requested path does not exist but a default value
* has been specified, this will return true.
*
* @param path Path to check for existence.
* @return True if this section contains the requested path, either via
* default or being set.
* @throws IllegalArgumentException Thrown when path is null.
*/
public boolean contains(@NotNull String path);
/**
* Checks if this {@link ConfigurationSection} contains the given path.
* <p>
* If the value for the requested path does not exist, the boolean parameter
* of true has been specified, a default value for the path exists, this
* will return true.
* <p>
* If a boolean parameter of false has been specified, true will only be
* returned if there is a set value for the specified path.
*
* @param path Path to check for existence.
* @param ignoreDefault Whether or not to ignore if a default value for the
* specified path exists.
* @return True if this section contains the requested path, or if a default
* value exist and the boolean parameter for this method is true.
* @throws IllegalArgumentException Thrown when path is null.
*/
public boolean contains(@NotNull String path, boolean ignoreDefault);
/**
* Checks if this {@link ConfigurationSection} has a value set for the
* given path.
* <p>
* If the value for the requested path does not exist but a default value
* has been specified, this will still return false.
*
* @param path Path to check for existence.
* @return True if this section contains the requested path, regardless of
* having a default.
* @throws IllegalArgumentException Thrown when path is null.
*/
public boolean isSet(@NotNull String path);
/**
* Gets the path of this {@link ConfigurationSection} from its root {@link
* Configuration}
* <p>
* For any {@link Configuration} themselves, this will return an empty
* string.
* <p>
* If the section is no longer contained within its root for any reason,
* such as being replaced with a different value, this may return null.
* <p>
* To retrieve the single name of this section, that is, the final part of
* the path returned by this method, you may use {@link #getName()}.
*
* @return Path of this section relative to its root
*/
@Nullable
public String getCurrentPath();
/**
* Gets the name of this individual {@link ConfigurationSection}, in the
* path.
* <p>
* This will always be the final part of {@link #getCurrentPath()}, unless
* the section is orphaned.
*
* @return Name of this section
*/
@NotNull
public String getName();
/**
* Gets the root {@link Configuration} that contains this {@link
* ConfigurationSection}
* <p>
* For any {@link Configuration} themselves, this will return its own
* object.
* <p>
* If the section is no longer contained within its root for any reason,
* such as being replaced with a different value, this may return null.
*
* @return Root configuration containing this section.
*/
@Nullable
public Configuration getRoot();
/**
* Gets the parent {@link ConfigurationSection} that directly contains
* this {@link ConfigurationSection}.
* <p>
* For any {@link Configuration} themselves, this will return null.
* <p>
* If the section is no longer contained within its parent for any reason,
* such as being replaced with a different value, this may return null.
*
* @return Parent section containing this section.
*/
@Nullable
public ConfigurationSection getParent();
/**
* Gets the requested Object by path.
* <p>
* If the Object does not exist but a default value has been specified,
* this will return the default value. If the Object does not exist and no
* default value was specified, this will return null.
*
* @param path Path of the Object to get.
* @return Requested Object.
*/
@Nullable
public Object get(@NotNull String path);
/**
* Gets the requested Object by path, returning a default value if not
* found.
* <p>
* If the Object does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link Configuration}.
*
* @param path Path of the Object to get.
* @param def The default value to return if the path is not found.
* @return Requested Object.
*/
@Contract("_, !null -> !null")
@Nullable
public Object get(@NotNull String path, @Nullable Object def);
/**
* Sets the specified path to the given value.
* <p>
* If value is null, the entry will be removed. Any existing entry will be
* replaced, regardless of what the new value is.
* <p>
* Some implementations may have limitations on what you may store. See
* their individual javadocs for details. No implementations should allow
* you to store {@link Configuration}s or {@link ConfigurationSection}s,
* please use {@link #createSection(java.lang.String)} for that.
*
* @param path Path of the object to set.
* @param value New value to set the path to.
*/
public void set(@NotNull String path, @Nullable Object value);
/**
* Creates an empty {@link ConfigurationSection} at the specified path.
* <p>
* Any value that was previously set at this path will be overwritten. If
* the previous value was itself a {@link ConfigurationSection}, it will
* be orphaned.
*
* @param path Path to create the section at.
* @return Newly created section
*/
@NotNull
public ConfigurationSection createSection(@NotNull String path);
/**
* Creates a {@link ConfigurationSection} at the specified path, with
* specified values.
* <p>
* Any value that was previously set at this path will be overwritten. If
* the previous value was itself a {@link ConfigurationSection}, it will
* be orphaned.
*
* @param path Path to create the section at.
* @param map The values to used.
* @return Newly created section
*/
@NotNull
public ConfigurationSection createSection(@NotNull String path, @NotNull Map<?, ?> map);
// Primitives
/**
* Gets the requested String by path.
* <p>
* If the String does not exist but a default value has been specified,
* this will return the default value. If the String does not exist and no
* default value was specified, this will return null.
*
* @param path Path of the String to get.
* @return Requested String.
*/
@Nullable
public String getString(@NotNull String path);
/**
* Gets the requested String by path, returning a default value if not
* found.
* <p>
* If the String does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link Configuration}.
*
* @param path Path of the String to get.
* @param def The default value to return if the path is not found or is
* not a String.
* @return Requested String.
*/
@Contract("_, !null -> !null")
@Nullable
public String getString(@NotNull String path, @Nullable String def);
/**
* Checks if the specified path is a String.
* <p>
* If the path exists but is not a String, this will return false. If the
* path does not exist, this will return false. If the path does not exist
* but a default value has been specified, this will check if that default
* value is a String and return appropriately.
*
* @param path Path of the String to check.
* @return Whether or not the specified path is a String.
*/
public boolean isString(@NotNull String path);
/**
* Gets the requested int by path.
* <p>
* If the int does not exist but a default value has been specified, this
* will return the default value. If the int does not exist and no default
* value was specified, this will return 0.
*
* @param path Path of the int to get.
* @return Requested int.
*/
public int getInt(@NotNull String path);
/**
* Gets the requested int by path, returning a default value if not found.
* <p>
* If the int does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link Configuration}.
*
* @param path Path of the int to get.
* @param def The default value to return if the path is not found or is
* not an int.
* @return Requested int.
*/
public int getInt(@NotNull String path, int def);
/**
* Checks if the specified path is an int.
* <p>
* If the path exists but is not a int, this will return false. If the
* path does not exist, this will return false. If the path does not exist
* but a default value has been specified, this will check if that default
* value is a int and return appropriately.
*
* @param path Path of the int to check.
* @return Whether or not the specified path is an int.
*/
public boolean isInt(@NotNull String path);
/**
* Gets the requested boolean by path.
* <p>
* If the boolean does not exist but a default value has been specified,
* this will return the default value. If the boolean does not exist and
* no default value was specified, this will return false.
*
* @param path Path of the boolean to get.
* @return Requested boolean.
*/
public boolean getBoolean(@NotNull String path);
/**
* Gets the requested boolean by path, returning a default value if not
* found.
* <p>
* If the boolean does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link Configuration}.
*
* @param path Path of the boolean to get.
* @param def The default value to return if the path is not found or is
* not a boolean.
* @return Requested boolean.
*/
public boolean getBoolean(@NotNull String path, boolean def);
/**
* Checks if the specified path is a boolean.
* <p>
* If the path exists but is not a boolean, this will return false. If the
* path does not exist, this will return false. If the path does not exist
* but a default value has been specified, this will check if that default
* value is a boolean and return appropriately.
*
* @param path Path of the boolean to check.
* @return Whether or not the specified path is a boolean.
*/
public boolean isBoolean(@NotNull String path);
/**
* Gets the requested double by path.
* <p>
* If the double does not exist but a default value has been specified,
* this will return the default value. If the double does not exist and no
* default value was specified, this will return 0.
*
* @param path Path of the double to get.
* @return Requested double.
*/
public double getDouble(@NotNull String path);
/**
* Gets the requested double by path, returning a default value if not
* found.
* <p>
* If the double does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link Configuration}.
*
* @param path Path of the double to get.
* @param def The default value to return if the path is not found or is
* not a double.
* @return Requested double.
*/
public double getDouble(@NotNull String path, double def);
/**
* Checks if the specified path is a double.
* <p>
* If the path exists but is not a double, this will return false. If the
* path does not exist, this will return false. If the path does not exist
* but a default value has been specified, this will check if that default
* value is a double and return appropriately.
*
* @param path Path of the double to check.
* @return Whether or not the specified path is a double.
*/
public boolean isDouble(@NotNull String path);
/**
* Gets the requested long by path.
* <p>
* If the long does not exist but a default value has been specified, this
* will return the default value. If the long does not exist and no
* default value was specified, this will return 0.
*
* @param path Path of the long to get.
* @return Requested long.
*/
public long getLong(@NotNull String path);
/**
* Gets the requested long by path, returning a default value if not
* found.
* <p>
* If the long does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link Configuration}.
*
* @param path Path of the long to get.
* @param def The default value to return if the path is not found or is
* not a long.
* @return Requested long.
*/
public long getLong(@NotNull String path, long def);
/**
* Checks if the specified path is a long.
* <p>
* If the path exists but is not a long, this will return false. If the
* path does not exist, this will return false. If the path does not exist
* but a default value has been specified, this will check if that default
* value is a long and return appropriately.
*
* @param path Path of the long to check.
* @return Whether or not the specified path is a long.
*/
public boolean isLong(@NotNull String path);
// Java
/**
* Gets the requested List by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return null.
*
* @param path Path of the List to get.
* @return Requested List.
*/
@Nullable
public List<?> getList(@NotNull String path);
/**
* Gets the requested List by path, returning a default value if not
* found.
* <p>
* If the List does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link Configuration}.
*
* @param path Path of the List to get.
* @param def The default value to return if the path is not found or is
* not a List.
* @return Requested List.
*/
@Contract("_, !null -> !null")
@Nullable
public List<?> getList(@NotNull String path, @Nullable List<?> def);
/**
* Checks if the specified path is a List.
* <p>
* If the path exists but is not a List, this will return false. If the
* path does not exist, this will return false. If the path does not exist
* but a default value has been specified, this will check if that default
* value is a List and return appropriately.
*
* @param path Path of the List to check.
* @return Whether or not the specified path is a List.
*/
public boolean isList(@NotNull String path);
/**
* Gets the requested List of String by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a String if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of String.
*/
@NotNull
public List<String> getStringList(@NotNull String path);
/**
* Gets the requested List of Integer by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Integer if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Integer.
*/
@NotNull
public List<Integer> getIntegerList(@NotNull String path);
/**
* Gets the requested List of Boolean by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Boolean if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Boolean.
*/
@NotNull
public List<Boolean> getBooleanList(@NotNull String path);
/**
* Gets the requested List of Double by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Double if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Double.
*/
@NotNull
public List<Double> getDoubleList(@NotNull String path);
/**
* Gets the requested List of Float by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Float if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Float.
*/
@NotNull
public List<Float> getFloatList(@NotNull String path);
/**
* Gets the requested List of Long by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Long if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Long.
*/
@NotNull
public List<Long> getLongList(@NotNull String path);
/**
* Gets the requested List of Byte by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Byte if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Byte.
*/
@NotNull
public List<Byte> getByteList(@NotNull String path);
/**
* Gets the requested List of Character by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Character if
* possible, but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Character.
*/
@NotNull
public List<Character> getCharacterList(@NotNull String path);
/**
* Gets the requested List of Short by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Short if possible,
* but may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Short.
*/
@NotNull
public List<Short> getShortList(@NotNull String path);
/**
* Gets the requested List of Maps by path.
* <p>
* If the List does not exist but a default value has been specified, this
* will return the default value. If the List does not exist and no
* default value was specified, this will return an empty List.
* <p>
* This method will attempt to cast any values into a Map if possible, but
* may miss any values out if they are not compatible.
*
* @param path Path of the List to get.
* @return Requested List of Maps.
*/
@NotNull
public List<Map<?, ?>> getMapList(@NotNull String path);
// Bukkit
/**
* Gets the requested object at the given path.
*
* If the Object does not exist but a default value has been specified, this
* will return the default value. If the Object does not exist and no
* default value was specified, this will return null.
*
* <b>Note:</b> For example #getObject(path, String.class) is <b>not</b>
* equivalent to {@link #getString(String) #getString(path)} because
* {@link #getString(String) #getString(path)} converts internally all
* Objects to Strings. However, #getObject(path, Boolean.class) is
* equivalent to {@link #getBoolean(String) #getBoolean(path)} for example.
*
* @param <T> the type of the requested object
* @param path the path to the object.
* @param clazz the type of the requested object
* @return Requested object
*/
@Nullable
public <T extends Object> T getObject(@NotNull String path, @NotNull Class<T> clazz);
/**
* Gets the requested object at the given path, returning a default value if
* not found
*
* If the Object does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link Configuration}.
*
* <b>Note:</b> For example #getObject(path, String.class, def) is
* <b>not</b> equivalent to
* {@link #getString(String, String) #getString(path, def)} because
* {@link #getString(String, String) #getString(path, def)} converts
* internally all Objects to Strings. However, #getObject(path,
* Boolean.class, def) is equivalent to {@link #getBoolean(String, boolean) #getBoolean(path,
* def)} for example.
*
* @param <T> the type of the requested object
* @param path the path to the object.
* @param clazz the type of the requested object
* @param def the default object to return if the object is not present at
* the path
* @return Requested object
*/
@Contract("_, _, !null -> !null")
@Nullable
public <T extends Object> T getObject(@NotNull String path, @NotNull Class<T> clazz, @Nullable T def);
/**
* Gets the requested {@link ConfigurationSerializable} object at the given
* path.
*
* If the Object does not exist but a default value has been specified, this
* will return the default value. If the Object does not exist and no
* default value was specified, this will return null.
*
* @param <T> the type of {@link ConfigurationSerializable}
* @param path the path to the object.
* @param clazz the type of {@link ConfigurationSerializable}
* @return Requested {@link ConfigurationSerializable} object
*/
@Nullable
public <T extends ConfigurationSerializable> T getSerializable(@NotNull String path, @NotNull Class<T> clazz);
/**
* Gets the requested {@link ConfigurationSerializable} object at the given
* path, returning a default value if not found
*
* If the Object does not exist then the specified default value will
* returned regardless of if a default has been identified in the root
* {@link Configuration}.
*
* @param <T> the type of {@link ConfigurationSerializable}
* @param path the path to the object.
* @param clazz the type of {@link ConfigurationSerializable}
* @param def the default object to return if the object is not present at
* the path
* @return Requested {@link ConfigurationSerializable} object
*/
@Contract("_, _, !null -> !null")
@Nullable
public <T extends ConfigurationSerializable> T getSerializable(@NotNull String path, @NotNull Class<T> clazz, @Nullable T def);
@Nullable
public ConfigurationSection getConfigurationSection(@NotNull String path);
/**
* Checks if the specified path is a ConfigurationSection.
* <p>
* If the path exists but is not a ConfigurationSection, this will return
* false. If the path does not exist, this will return false. If the path
* does not exist but a default value has been specified, this will check
* if that default value is a ConfigurationSection and return
* appropriately.
*
* @param path Path of the ConfigurationSection to check.
* @return Whether or not the specified path is a ConfigurationSection.
*/
public boolean isConfigurationSection(@NotNull String path);
/**
* Gets the equivalent {@link ConfigurationSection} from the default
* {@link Configuration} defined in {@link #getRoot()}.
* <p>
* If the root contains no defaults, or the defaults doesn't contain a
* value for this path, or the value at this path is not a {@link
* ConfigurationSection} then this will return null.
*
* @return Equivalent section in root configuration
*/
@Nullable
public ConfigurationSection getDefaultSection();
/**
* Sets the default value in the root at the given path as provided.
* <p>
* If no source {@link Configuration} was provided as a default
* collection, then a new {@link MemoryConfiguration} will be created to
* hold the new default value.
* <p>
* If value is null, the value will be removed from the default
* Configuration source.
* <p>
* If the value as returned by {@link #getDefaultSection()} is null, then
* this will create a new section at the path, replacing anything that may
* have existed there previously.
*
* @param path Path of the value to set.
* @param value Value to set the default to.
* @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

@ -0,0 +1,45 @@
package org.bukkit.configuration;
/**
* Exception thrown when attempting to load an invalid {@link Configuration}
*/
@SuppressWarnings("serial")
public class InvalidConfigurationException extends Exception {
/**
* Creates a new instance of InvalidConfigurationException without a
* message or cause.
*/
public InvalidConfigurationException() {}
/**
* Constructs an instance of InvalidConfigurationException with the
* specified message.
*
* @param msg The details of the exception.
*/
public InvalidConfigurationException(String msg) {
super(msg);
}
/**
* Constructs an instance of InvalidConfigurationException with the
* specified cause.
*
* @param cause The cause of the exception.
*/
public InvalidConfigurationException(Throwable cause) {
super(cause);
}
/**
* Constructs an instance of InvalidConfigurationException with the
* specified message and cause.
*
* @param cause The cause of the exception.
* @param msg The details of the exception.
*/
public InvalidConfigurationException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@ -0,0 +1,92 @@
package org.bukkit.configuration;
import java.util.Map;
import org.apache.commons.lang.Validate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This is a {@link Configuration} implementation that does not save or load
* from any source, and stores all values in memory only.
* This is useful for temporary Configurations for providing defaults.
*/
public class MemoryConfiguration extends MemorySection implements Configuration {
protected Configuration defaults;
protected MemoryConfigurationOptions options;
/**
* Creates an empty {@link MemoryConfiguration} with no default values.
*/
public MemoryConfiguration() {}
/**
* Creates an empty {@link MemoryConfiguration} using the specified {@link
* Configuration} as a source for all default values.
*
* @param defaults Default value provider
* @throws IllegalArgumentException Thrown if defaults is null
*/
public MemoryConfiguration(@Nullable Configuration defaults) {
this.defaults = defaults;
}
@Override
public void addDefault(@NotNull String path, @Nullable Object value) {
Validate.notNull(path, "Path may not be null");
if (defaults == null) {
defaults = new MemoryConfiguration();
}
defaults.set(path, value);
}
@Override
public void addDefaults(@NotNull Map<String, Object> defaults) {
Validate.notNull(defaults, "Defaults may not be null");
for (Map.Entry<String, Object> entry : defaults.entrySet()) {
addDefault(entry.getKey(), entry.getValue());
}
}
@Override
public void addDefaults(@NotNull Configuration defaults) {
Validate.notNull(defaults, "Defaults may not be null");
for (String key : defaults.getKeys(true)) {
if (!defaults.isConfigurationSection(key)) {
addDefault(key, defaults.get(key));
}
}
}
@Override
public void setDefaults(@NotNull Configuration defaults) {
Validate.notNull(defaults, "Defaults may not be null");
this.defaults = defaults;
}
@Override
@Nullable
public Configuration getDefaults() {
return defaults;
}
@Nullable
@Override
public ConfigurationSection getParent() {
return null;
}
@Override
@NotNull
public MemoryConfigurationOptions options() {
if (options == null) {
options = new MemoryConfigurationOptions(this);
}
return options;
}
}

View File

@ -0,0 +1,33 @@
package org.bukkit.configuration;
import org.jetbrains.annotations.NotNull;
/**
* Various settings for controlling the input and output of a {@link
* MemoryConfiguration}
*/
public class MemoryConfigurationOptions extends ConfigurationOptions {
protected MemoryConfigurationOptions(@NotNull MemoryConfiguration configuration) {
super(configuration);
}
@NotNull
@Override
public MemoryConfiguration configuration() {
return (MemoryConfiguration) super.configuration();
}
@NotNull
@Override
public MemoryConfigurationOptions copyDefaults(boolean value) {
super.copyDefaults(value);
return this;
}
@NotNull
@Override
public MemoryConfigurationOptions pathSeparator(char value) {
super.pathSeparator(value);
return this;
}
}

View File

@ -0,0 +1,949 @@
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;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A type of {@link ConfigurationSection} that is stored in memory.
*/
public class MemorySection implements ConfigurationSection {
protected final Map<String, SectionPathData> map = new LinkedHashMap<String, SectionPathData>();
private final Configuration root;
private final ConfigurationSection parent;
private final String path;
private final String fullPath;
/**
* Creates an empty MemorySection for use as a root {@link Configuration}
* section.
* <p>
* Note that calling this without being yourself a {@link Configuration}
* will throw an exception!
*
* @throws IllegalStateException Thrown if this is not a {@link
* Configuration} root.
*/
protected MemorySection() {
if (!(this instanceof Configuration)) {
throw new IllegalStateException("Cannot construct a root MemorySection when not a Configuration");
}
this.path = "";
this.fullPath = "";
this.parent = null;
this.root = (Configuration) this;
}
/**
* Creates an empty MemorySection with the specified parent and path.
*
* @param parent Parent section that contains this own section.
* @param path Path that you may access this section from via the root
* {@link Configuration}.
* @throws IllegalArgumentException Thrown is parent or path is null, or
* if parent contains no root Configuration.
*/
protected MemorySection(@NotNull ConfigurationSection parent, @NotNull String path) {
Validate.notNull(parent, "Parent cannot be null");
Validate.notNull(path, "Path cannot be null");
this.path = path;
this.parent = parent;
this.root = parent.getRoot();
Validate.notNull(root, "Path cannot be orphaned");
this.fullPath = createPath(parent, path);
}
@Override
@NotNull
public Set<String> getKeys(boolean deep) {
Set<String> result = new LinkedHashSet<String>();
Configuration root = getRoot();
if (root != null && root.options().copyDefaults()) {
ConfigurationSection defaults = getDefaultSection();
if (defaults != null) {
result.addAll(defaults.getKeys(deep));
}
}
mapChildrenKeys(result, this, deep);
return result;
}
@Override
@NotNull
public Map<String, Object> getValues(boolean deep) {
Map<String, Object> result = new LinkedHashMap<String, Object>();
Configuration root = getRoot();
if (root != null && root.options().copyDefaults()) {
ConfigurationSection defaults = getDefaultSection();
if (defaults != null) {
result.putAll(defaults.getValues(deep));
}
}
mapChildrenValues(result, this, deep);
return result;
}
@Override
public boolean contains(@NotNull String path) {
return contains(path, false);
}
@Override
public boolean contains(@NotNull String path, boolean ignoreDefault) {
return ((ignoreDefault) ? get(path, null) : get(path)) != null;
}
@Override
public boolean isSet(@NotNull String path) {
Configuration root = getRoot();
if (root == null) {
return false;
}
if (root.options().copyDefaults()) {
return contains(path);
}
return get(path, null) != null;
}
@Override
@NotNull
public String getCurrentPath() {
return fullPath;
}
@Override
@NotNull
public String getName() {
return path;
}
@Override
@Nullable
public Configuration getRoot() {
return root;
}
@Override
@Nullable
public ConfigurationSection getParent() {
return parent;
}
@Override
public void addDefault(@NotNull String path, @Nullable Object value) {
Validate.notNull(path, "Path cannot be null");
Configuration root = getRoot();
if (root == null) {
throw new IllegalStateException("Cannot add default without root");
}
if (root == this) {
throw new UnsupportedOperationException("Unsupported addDefault(String, Object) implementation");
}
root.addDefault(createPath(this, path), value);
}
@Override
@Nullable
public ConfigurationSection getDefaultSection() {
Configuration root = getRoot();
Configuration defaults = root == null ? null : root.getDefaults();
if (defaults != null) {
if (defaults.isConfigurationSection(getCurrentPath())) {
return defaults.getConfigurationSection(getCurrentPath());
}
}
return null;
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
Validate.notEmpty(path, "Cannot set to an empty path");
Configuration root = getRoot();
if (root == null) {
throw new IllegalStateException("Cannot use 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) {
String node = path.substring(i2, i1);
ConfigurationSection subSection = section.getConfigurationSection(node);
if (subSection == null) {
if (value == null) {
// no need to create missing sub-sections if we want to remove the value:
return;
}
section = section.createSection(node);
} else {
section = subSection;
}
}
String key = path.substring(i2);
if (section == this) {
if (value == null) {
map.remove(key);
} else {
SectionPathData entry = map.get(key);
if (entry == null) {
map.put(key, new SectionPathData(value));
} else {
entry.setData(value);
}
}
} else {
section.set(key, value);
}
}
@Override
@Nullable
public Object get(@NotNull String path) {
return get(path, getDefault(path));
}
@Override
@Contract("_, !null -> !null")
@Nullable
public Object get(@NotNull String path, @Nullable Object def) {
Validate.notNull(path, "Path cannot be null");
if (path.length() == 0) {
return this;
}
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) {
final String currentPath = path.substring(i2, i1);
if (!section.contains(currentPath, true)) {
return def;
}
section = section.getConfigurationSection(currentPath);
if (section == null) {
return def;
}
}
String key = path.substring(i2);
if (section == this) {
SectionPathData result = map.get(key);
return (result == null) ? def : result.getData();
}
return section.get(key, def);
}
@Override
@NotNull
public ConfigurationSection createSection(@NotNull String path) {
Validate.notEmpty(path, "Cannot create section at empty path");
Configuration root = getRoot();
if (root == null) {
throw new IllegalStateException("Cannot create 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) {
String node = path.substring(i2, i1);
ConfigurationSection subSection = section.getConfigurationSection(node);
if (subSection == null) {
section = section.createSection(node);
} else {
section = subSection;
}
}
String key = path.substring(i2);
if (section == this) {
ConfigurationSection result = new MemorySection(this, key);
map.put(key, new SectionPathData(result));
return result;
}
return section.createSection(key);
}
@Override
@NotNull
public ConfigurationSection createSection(@NotNull String path, @NotNull Map<?, ?> map) {
ConfigurationSection section = createSection(path);
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (entry.getValue() instanceof Map) {
section.createSection(entry.getKey().toString(), (Map<?, ?>) entry.getValue());
} else {
section.set(entry.getKey().toString(), entry.getValue());
}
}
return section;
}
// Primitives
@Override
@Nullable
public String getString(@NotNull String path) {
Object def = getDefault(path);
return getString(path, def != null ? def.toString() : null);
}
@Override
@Contract("_, !null -> !null")
@Nullable
public String getString(@NotNull String path, @Nullable String def) {
Object val = get(path, def);
return (val != null) ? val.toString() : def;
}
@Override
public boolean isString(@NotNull String path) {
Object val = get(path);
return val instanceof String;
}
@Override
public int getInt(@NotNull String path) {
Object def = getDefault(path);
return getInt(path, (def instanceof Number) ? toInt(def) : 0);
}
@Override
public int getInt(@NotNull String path, int def) {
Object val = get(path, def);
return (val instanceof Number) ? toInt(val) : def;
}
@Override
public boolean isInt(@NotNull String path) {
Object val = get(path);
return val instanceof Integer;
}
@Override
public boolean getBoolean(@NotNull String path) {
Object def = getDefault(path);
return getBoolean(path, (def instanceof Boolean) ? (Boolean) def : false);
}
@Override
public boolean getBoolean(@NotNull String path, boolean def) {
Object val = get(path, def);
return (val instanceof Boolean) ? (Boolean) val : def;
}
@Override
public boolean isBoolean(@NotNull String path) {
Object val = get(path);
return val instanceof Boolean;
}
@Override
public double getDouble(@NotNull String path) {
Object def = getDefault(path);
return getDouble(path, (def instanceof Number) ? toDouble(def) : 0);
}
@Override
public double getDouble(@NotNull String path, double def) {
Object val = get(path, def);
return (val instanceof Number) ? toDouble(val) : def;
}
@Override
public boolean isDouble(@NotNull String path) {
Object val = get(path);
return val instanceof Double;
}
@Override
public long getLong(@NotNull String path) {
Object def = getDefault(path);
return getLong(path, (def instanceof Number) ? toLong(def) : 0);
}
@Override
public long getLong(@NotNull String path, long def) {
Object val = get(path, def);
return (val instanceof Number) ? toLong(val) : def;
}
@Override
public boolean isLong(@NotNull String path) {
Object val = get(path);
return val instanceof Long;
}
// Java
@Override
@Nullable
public List<?> getList(@NotNull String path) {
Object def = getDefault(path);
return getList(path, (def instanceof List) ? (List<?>) def : null);
}
@Override
@Contract("_, !null -> !null")
@Nullable
public List<?> getList(@NotNull String path, @Nullable List<?> def) {
Object val = get(path, def);
return (List<?>) ((val instanceof List) ? val : def);
}
@Override
public boolean isList(@NotNull String path) {
Object val = get(path);
return val instanceof List;
}
@Override
@NotNull
public List<String> getStringList(@NotNull String path) {
List<?> list = getList(path);
if (list == null) {
return new ArrayList<String>(0);
}
List<String> result = new ArrayList<String>();
for (Object object : list) {
if ((object instanceof String) || (isPrimitiveWrapper(object))) {
result.add(String.valueOf(object));
}
}
return result;
}
@Override
@NotNull
public List<Integer> getIntegerList(@NotNull String path) {
List<?> list = getList(path);
if (list == null) {
return new ArrayList<Integer>(0);
}
List<Integer> result = new ArrayList<Integer>();
for (Object object : list) {
if (object instanceof Integer) {
result.add((Integer) object);
} else if (object instanceof String) {
try {
result.add(Integer.valueOf((String) object));
} catch (Exception ex) {
}
} else if (object instanceof Character) {
result.add((int) ((Character) object).charValue());
} else if (object instanceof Number) {
result.add(((Number) object).intValue());
}
}
return result;
}
@Override
@NotNull
public List<Boolean> getBooleanList(@NotNull String path) {
List<?> list = getList(path);
if (list == null) {
return new ArrayList<Boolean>(0);
}
List<Boolean> result = new ArrayList<Boolean>();
for (Object object : list) {
if (object instanceof Boolean) {
result.add((Boolean) object);
} else if (object instanceof String) {
if (Boolean.TRUE.toString().equals(object)) {
result.add(true);
} else if (Boolean.FALSE.toString().equals(object)) {
result.add(false);
}
}
}
return result;
}
@Override
@NotNull
public List<Double> getDoubleList(@NotNull String path) {
List<?> list = getList(path);
if (list == null) {
return new ArrayList<Double>(0);
}
List<Double> result = new ArrayList<Double>();
for (Object object : list) {
if (object instanceof Double) {
result.add((Double) object);
} else if (object instanceof String) {
try {
result.add(Double.valueOf((String) object));
} catch (Exception ex) {
}
} else if (object instanceof Character) {
result.add((double) ((Character) object).charValue());
} else if (object instanceof Number) {
result.add(((Number) object).doubleValue());
}
}
return result;
}
@Override
@NotNull
public List<Float> getFloatList(@NotNull String path) {
List<?> list = getList(path);
if (list == null) {
return new ArrayList<Float>(0);
}
List<Float> result = new ArrayList<Float>();
for (Object object : list) {
if (object instanceof Float) {
result.add((Float) object);
} else if (object instanceof String) {
try {
result.add(Float.valueOf((String) object));
} catch (Exception ex) {
}
} else if (object instanceof Character) {
result.add((float) ((Character) object).charValue());
} else if (object instanceof Number) {
result.add(((Number) object).floatValue());
}
}
return result;
}
@Override
@NotNull
public List<Long> getLongList(@NotNull String path) {
List<?> list = getList(path);
if (list == null) {
return new ArrayList<Long>(0);
}
List<Long> result = new ArrayList<Long>();
for (Object object : list) {
if (object instanceof Long) {
result.add((Long) object);
} else if (object instanceof String) {
try {
result.add(Long.valueOf((String) object));
} catch (Exception ex) {
}
} else if (object instanceof Character) {
result.add((long) ((Character) object).charValue());
} else if (object instanceof Number) {
result.add(((Number) object).longValue());
}
}
return result;
}
@Override
@NotNull
public List<Byte> getByteList(@NotNull String path) {
List<?> list = getList(path);
if (list == null) {
return new ArrayList<Byte>(0);
}
List<Byte> result = new ArrayList<Byte>();
for (Object object : list) {
if (object instanceof Byte) {
result.add((Byte) object);
} else if (object instanceof String) {
try {
result.add(Byte.valueOf((String) object));
} catch (Exception ex) {
}
} else if (object instanceof Character) {
result.add((byte) ((Character) object).charValue());
} else if (object instanceof Number) {
result.add(((Number) object).byteValue());
}
}
return result;
}
@Override
@NotNull
public List<Character> getCharacterList(@NotNull String path) {
List<?> list = getList(path);
if (list == null) {
return new ArrayList<Character>(0);
}
List<Character> result = new ArrayList<Character>();
for (Object object : list) {
if (object instanceof Character) {
result.add((Character) object);
} else if (object instanceof String) {
String str = (String) object;
if (str.length() == 1) {
result.add(str.charAt(0));
}
} else if (object instanceof Number) {
result.add((char) ((Number) object).intValue());
}
}
return result;
}
@Override
@NotNull
public List<Short> getShortList(@NotNull String path) {
List<?> list = getList(path);
if (list == null) {
return new ArrayList<Short>(0);
}
List<Short> result = new ArrayList<Short>();
for (Object object : list) {
if (object instanceof Short) {
result.add((Short) object);
} else if (object instanceof String) {
try {
result.add(Short.valueOf((String) object));
} catch (Exception ex) {
}
} else if (object instanceof Character) {
result.add((short) ((Character) object).charValue());
} else if (object instanceof Number) {
result.add(((Number) object).shortValue());
}
}
return result;
}
@Override
@NotNull
public List<Map<?, ?>> getMapList(@NotNull String path) {
List<?> list = getList(path);
List<Map<?, ?>> result = new ArrayList<Map<?, ?>>();
if (list == null) {
return result;
}
for (Object object : list) {
if (object instanceof Map) {
result.add((Map<?, ?>) object);
}
}
return result;
}
// Bukkit
@Nullable
@Override
public <T extends Object> T getObject(@NotNull String path, @NotNull Class<T> clazz) {
Validate.notNull(clazz, "Class cannot be null");
Object def = getDefault(path);
return getObject(path, clazz, (def != null && clazz.isInstance(def)) ? clazz.cast(def) : null);
}
@Contract("_, _, !null -> !null")
@Nullable
@Override
public <T extends Object> T getObject(@NotNull String path, @NotNull Class<T> clazz, @Nullable T def) {
Validate.notNull(clazz, "Class cannot be null");
Object val = get(path, def);
return (val != null && clazz.isInstance(val)) ? clazz.cast(val) : def;
}
@Nullable
@Override
public <T extends ConfigurationSerializable> T getSerializable(@NotNull String path, @NotNull Class<T> clazz) {
return getObject(path, clazz);
}
@Contract("_, _, !null -> !null")
@Nullable
@Override
public <T extends ConfigurationSerializable> T getSerializable(@NotNull String path, @NotNull Class<T> clazz, @Nullable T def) {
return getObject(path, clazz, def);
}
@Override
@Nullable
public ConfigurationSection getConfigurationSection(@NotNull String path) {
Object val = get(path, null);
if (val != null) {
return (val instanceof ConfigurationSection) ? (ConfigurationSection) val : null;
}
val = get(path, getDefault(path));
return (val instanceof ConfigurationSection) ? createSection(path) : null;
}
@Override
public boolean isConfigurationSection(@NotNull String path) {
Object val = get(path);
return val instanceof ConfigurationSection;
}
protected boolean isPrimitiveWrapper(@Nullable Object input) {
return input instanceof Integer || input instanceof Boolean
|| input instanceof Character || input instanceof Byte
|| input instanceof Short || input instanceof Double
|| input instanceof Long || input instanceof Float;
}
@Nullable
protected Object getDefault(@NotNull String path) {
Validate.notNull(path, "Path cannot be null");
Configuration root = getRoot();
Configuration defaults = root == null ? null : root.getDefaults();
return (defaults == null) ? null : defaults.get(createPath(this, path));
}
protected void mapChildrenKeys(@NotNull Set<String> output, @NotNull ConfigurationSection section, boolean deep) {
if (section instanceof MemorySection) {
MemorySection sec = (MemorySection) section;
for (Map.Entry<String, SectionPathData> entry : sec.map.entrySet()) {
output.add(createPath(section, entry.getKey(), this));
if ((deep) && (entry.getValue().getData() instanceof ConfigurationSection)) {
ConfigurationSection subsection = (ConfigurationSection) entry.getValue().getData();
mapChildrenKeys(output, subsection, deep);
}
}
} else {
Set<String> keys = section.getKeys(deep);
for (String key : keys) {
output.add(createPath(section, key, this));
}
}
}
protected void mapChildrenValues(@NotNull Map<String, Object> output, @NotNull ConfigurationSection section, boolean deep) {
if (section instanceof MemorySection) {
MemorySection sec = (MemorySection) section;
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().getData());
if (entry.getValue().getData() instanceof ConfigurationSection) {
if (deep) {
mapChildrenValues(output, (ConfigurationSection) entry.getValue().getData(), deep);
}
}
}
} else {
Map<String, Object> values = section.getValues(deep);
for (Map.Entry<String, Object> entry : values.entrySet()) {
output.put(createPath(section, entry.getKey(), this), entry.getValue());
}
}
}
/**
* Creates a full path to the given {@link ConfigurationSection} from its
* root {@link Configuration}.
* <p>
* You may use this method for any given {@link ConfigurationSection}, not
* only {@link MemorySection}.
*
* @param section Section to create a path for.
* @param key Name of the specified section.
* @return Full path of the section from its root.
*/
@NotNull
public static String createPath(@NotNull ConfigurationSection section, @Nullable String key) {
return createPath(section, key, (section == null) ? null : section.getRoot());
}
/**
* Creates a relative path to the given {@link ConfigurationSection} from
* the given relative section.
* <p>
* You may use this method for any given {@link ConfigurationSection}, not
* only {@link MemorySection}.
*
* @param section Section to create a path for.
* @param key Name of the specified section.
* @param relativeTo Section to create the path relative to.
* @return Full path of the section from its root.
*/
@NotNull
public static String createPath(@NotNull ConfigurationSection section, @Nullable String key, @Nullable ConfigurationSection relativeTo) {
Validate.notNull(section, "Cannot create path without a section");
Configuration root = section.getRoot();
if (root == null) {
throw new IllegalStateException("Cannot create path without a root");
}
char separator = root.options().pathSeparator();
StringBuilder builder = new StringBuilder();
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) {
builder.append(separator);
}
builder.append(key);
}
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();
return new StringBuilder()
.append(getClass().getSimpleName())
.append("[path='")
.append(getCurrentPath())
.append("', root='")
.append(root == null ? null : root.getClass().getSimpleName())
.append("']")
.toString();
}
}

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

@ -0,0 +1,74 @@
package org.bukkit.configuration.file;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.Queue;
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.CommentEventsCollector;
import org.yaml.snakeyaml.comments.CommentType;
import org.yaml.snakeyaml.constructor.BaseConstructor;
import org.yaml.snakeyaml.emitter.Emitter;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.events.Event;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.representer.Representer;
import org.yaml.snakeyaml.serializer.Serializer;
final class BukkitYaml extends Yaml {
private static final Field events;
private static final Field blockCommentsCollector;
private static final Field inlineCommentsCollector;
private static Field getEmitterField(String name) {
Field field = null;
try {
field = Emitter.class.getDeclaredField(name);
field.setAccessible(true);
} catch (ReflectiveOperationException ex) {
// Ignore as a fail-safe fallback
}
return field;
}
static {
events = getEmitterField("events");
blockCommentsCollector = getEmitterField("blockCommentsCollector");
inlineCommentsCollector = getEmitterField("inlineCommentsCollector");
}
public BukkitYaml(@NotNull BaseConstructor constructor, @NotNull Representer representer, @NotNull DumperOptions dumperOptions, @NotNull LoaderOptions loadingConfig) {
super(constructor, representer, dumperOptions, loadingConfig);
}
@Override
public void serialize(@NotNull Node node, @NotNull Writer output) {
Emitter emitter = new Emitter(output, dumperOptions);
if (events != null && blockCommentsCollector != null && inlineCommentsCollector != null) {
Queue<Event> newEvents = new ArrayDeque<>(100);
try {
events.set(emitter, newEvents);
blockCommentsCollector.set(emitter, new CommentEventsCollector(newEvents, CommentType.BLANK_LINE, CommentType.BLOCK));
inlineCommentsCollector.set(emitter, new CommentEventsCollector(newEvents, CommentType.IN_LINE));
} catch (ReflectiveOperationException ex) {
// Don't ignore this as we could be in an inconsistent state
throw new RuntimeException("Could not update Yaml event queue", ex);
}
}
Serializer serializer = new Serializer(emitter, resolver, dumperOptions, null);
try {
serializer.open();
serializer.serialize(node);
serializer.close();
} catch (IOException ex) {
throw new YAMLException(ex);
}
}
}

View File

@ -0,0 +1,226 @@
package org.bukkit.configuration.file;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.MemoryConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This is a base class for all File based implementations of {@link
* Configuration}
*/
public abstract class FileConfiguration extends MemoryConfiguration {
/**
* Creates an empty {@link FileConfiguration} with no default values.
*/
public FileConfiguration() {
super();
}
/**
* Creates an empty {@link FileConfiguration} using the specified {@link
* Configuration} as a source for all default values.
*
* @param defaults Default value provider
*/
public FileConfiguration(@Nullable Configuration defaults) {
super(defaults);
}
/**
* Saves this {@link FileConfiguration} to the specified location.
* <p>
* If the file does not exist, it will be created. If already exists, it
* will be overwritten. If it cannot be overwritten or created, an
* exception will be thrown.
* <p>
* This method will save using the system default encoding, or possibly
* using UTF8.
*
* @param file File to save to.
* @throws IOException Thrown when the given file cannot be written to for
* any reason.
* @throws IllegalArgumentException Thrown when file is null.
*/
public void save(@NotNull File file) throws IOException {
Validate.notNull(file, "File cannot be null");
Files.createParentDirs(file);
String data = saveToString();
Writer writer = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8);
try {
writer.write(data);
} finally {
writer.close();
}
}
/**
* Saves this {@link FileConfiguration} to the specified location.
* <p>
* If the file does not exist, it will be created. If already exists, it
* will be overwritten. If it cannot be overwritten or created, an
* exception will be thrown.
* <p>
* This method will save using the system default encoding, or possibly
* using UTF8.
*
* @param file File to save to.
* @throws IOException Thrown when the given file cannot be written to for
* any reason.
* @throws IllegalArgumentException Thrown when file is null.
*/
public void save(@NotNull String file) throws IOException {
Validate.notNull(file, "File cannot be null");
save(new File(file));
}
/**
* Saves this {@link FileConfiguration} to a string, and returns it.
*
* @return String containing this configuration.
*/
@NotNull
public abstract String saveToString();
/**
* Loads this {@link FileConfiguration} from the specified location.
* <p>
* All the values contained within this configuration will be removed,
* leaving only settings and defaults, and the new values will be loaded
* from the given file.
* <p>
* If the file cannot be loaded for any reason, an exception will be
* thrown.
*
* @param file File to load from.
* @throws FileNotFoundException Thrown when the given file cannot be
* opened.
* @throws IOException Thrown when the given file cannot be read.
* @throws InvalidConfigurationException Thrown when the given file is not
* a valid Configuration.
* @throws IllegalArgumentException Thrown when file is null.
*/
public void load(@NotNull File file) throws FileNotFoundException, IOException, InvalidConfigurationException {
Validate.notNull(file, "File cannot be null");
final FileInputStream stream = new FileInputStream(file);
load(new InputStreamReader(stream, Charsets.UTF_8));
}
/**
* Loads this {@link FileConfiguration} from the specified reader.
* <p>
* All the values contained within this configuration will be removed,
* leaving only settings and defaults, and the new values will be loaded
* from the given stream.
*
* @param reader the reader to load from
* @throws IOException thrown when underlying reader throws an IOException
* @throws InvalidConfigurationException thrown when the reader does not
* represent a valid Configuration
* @throws IllegalArgumentException thrown when reader is null
*/
public void load(@NotNull Reader reader) throws IOException, InvalidConfigurationException {
BufferedReader input = reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader);
StringBuilder builder = new StringBuilder();
try {
String line;
while ((line = input.readLine()) != null) {
builder.append(line);
builder.append('\n');
}
} finally {
input.close();
}
loadFromString(builder.toString());
}
/**
* Loads this {@link FileConfiguration} from the specified location.
* <p>
* All the values contained within this configuration will be removed,
* leaving only settings and defaults, and the new values will be loaded
* from the given file.
* <p>
* If the file cannot be loaded for any reason, an exception will be
* thrown.
*
* @param file File to load from.
* @throws FileNotFoundException Thrown when the given file cannot be
* opened.
* @throws IOException Thrown when the given file cannot be read.
* @throws InvalidConfigurationException Thrown when the given file is not
* a valid Configuration.
* @throws IllegalArgumentException Thrown when file is null.
*/
public void load(@NotNull String file) throws FileNotFoundException, IOException, InvalidConfigurationException {
Validate.notNull(file, "File cannot be null");
load(new File(file));
}
/**
* Loads this {@link FileConfiguration} from the specified string, as
* opposed to from file.
* <p>
* All the values contained within this configuration will be removed,
* leaving only settings and defaults, and the new values will be loaded
* from the given string.
* <p>
* If the string is invalid in any way, an exception will be thrown.
*
* @param contents Contents of a Configuration to load.
* @throws InvalidConfigurationException Thrown if the specified string is
* invalid.
* @throws IllegalArgumentException Thrown if contents is null.
*/
public abstract void loadFromString(@NotNull String contents) throws InvalidConfigurationException;
/**
* @return empty string
*
* @deprecated This method only exists for backwards compatibility. It will
* do nothing and should not be used! Please use
* {@link FileConfigurationOptions#getHeader()} instead.
*/
@NotNull
@Deprecated
protected String buildHeader() {
return "";
}
@NotNull
@Override
public FileConfigurationOptions options() {
if (options == null) {
options = new FileConfigurationOptions(this);
}
return (FileConfigurationOptions) options;
}
}

View File

@ -0,0 +1,203 @@
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;
import org.jetbrains.annotations.Nullable;
/**
* Various settings for controlling the input and output of a {@link
* FileConfiguration}
*/
public class FileConfigurationOptions extends MemoryConfigurationOptions {
private List<String> header = Collections.emptyList();
private List<String> footer = Collections.emptyList();
private boolean parseComments = true;
protected FileConfigurationOptions(@NotNull MemoryConfiguration configuration) {
super(configuration);
}
@NotNull
@Override
public FileConfiguration configuration() {
return (FileConfiguration) super.configuration();
}
@NotNull
@Override
public FileConfigurationOptions copyDefaults(boolean value) {
super.copyDefaults(value);
return this;
}
@NotNull
@Override
public FileConfigurationOptions pathSeparator(char value) {
super.pathSeparator(value);
return this;
}
/**
* Gets the header that will be applied to the top of the saved output.
* <p>
* This header will be commented out and applied directly at the top of
* the generated output of the {@link FileConfiguration}. It is not
* required to include a newline at the end of the header 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.
*
* @return Unmodifiable header, every entry represents one line.
*/
@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>
* This header will be commented out and applied directly at the top of
* the generated output of the {@link FileConfiguration}. It is not
* required to include a newline at the end of the header 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 header, every entry represents one line.
* @return This object, for chaining
*/
@NotNull
public FileConfigurationOptions setHeader(@Nullable List<String> value) {
this.header = (value == null) ? Collections.emptyList() : Collections.unmodifiableList(value);
return this;
}
/**
* @param value The string header.
* @return This object, for chaining.
*
* @deprecated use setHeader() instead
*/
@NotNull
@Deprecated
public FileConfigurationOptions header(@Nullable String value) {
this.header = (value == null) ? Collections.emptyList() : Collections.unmodifiableList(Arrays.asList(value.split("\\n")));
return this;
}
/**
* Gets 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.
*
* @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 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

@ -0,0 +1,341 @@
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.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.apache.commons.lang.Validate;
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.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 {
/**
* @deprecated unused, not intended to be API
*/
@Deprecated
protected static final String COMMENT_PREFIX = "# ";
/**
* @deprecated unused, not intended to be API
*/
@Deprecated
protected static final String BLANK_CONFIG = "{}\n";
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 BukkitYaml(constructor, representer, yamlDumperOptions, yamlLoaderOptions);
}
@NotNull
@Override
public String saveToString() {
yamlDumperOptions.setIndent(options().indent());
yamlDumperOptions.setWidth(options().width());
yamlDumperOptions.setProcessComments(options().parseComments());
MappingNode node = toNodeTree(this);
node.setBlockComments(getCommentLines(saveHeader(options().getHeader()), CommentType.BLOCK));
node.setEndComments(getCommentLines(options().getFooter(), CommentType.BLOCK));
StringWriter writer = new StringWriter();
if (node.getBlockComments().isEmpty() && node.getEndComments().isEmpty() && node.getValue().isEmpty()) {
writer.write("");
} else {
if (node.getValue().isEmpty()) {
node.setFlowStyle(DumperOptions.FlowStyle.FLOW);
}
yaml.serialize(node, writer);
}
return writer.toString();
}
@Override
public void loadFromString(@NotNull String contents) throws InvalidConfigurationException {
Validate.notNull(contents, "String cannot be null");
yamlLoaderOptions.setProcessComments(options().parseComments());
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.");
}
this.map.clear();
if (node != null) {
adjustNodeComments(node);
options().setHeader(loadHeader(getCommentLines(node.getBlockComments())));
options().setFooter(getCommentLines(node.getEndComments()));
fromNodeTree(node, this);
}
}
/**
* 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()));
}
}
}
}
private void fromNodeTree(@NotNull MappingNode input, @NotNull ConfigurationSection section) {
constructor.flattenMapping(input);
for (NodeTuple nodeTuple : input.getValue()) {
Node key = nodeTuple.getKeyNode();
String keyString = String.valueOf(constructor.construct(key));
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(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()));
}
}
}
private boolean hasSerializedTypeKey(MappingNode node) {
for (NodeTuple nodeTuple : node.getValue()) {
Node keyNode = nodeTuple.getKeyNode();
if (!(keyNode instanceof ScalarNode)) continue;
String key = ((ScalarNode) keyNode).getValue();
if (key.equals(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) {
return true;
}
}
return false;
}
private MappingNode toNodeTree(@NotNull ConfigurationSection section) {
List<NodeTuple> nodeTuples = new ArrayList<>();
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
Node key = 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));
}
nodeTuples.add(new NodeTuple(key, value));
}
return new MappingNode(Tag.MAP, nodeTuples, DumperOptions.FlowStyle.BLOCK);
}
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 {
String line = comment.getValue();
line = line.startsWith(" ") ? line.substring(1) : line;
lines.add(line);
}
}
}
return lines;
}
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 {
String line = comment;
line = line.isEmpty() ? line : " " + line;
lines.add(new CommentLine(null, null, line, commentType));
}
}
return lines;
}
/**
* Removes the empty line at the end of the header that separates the header
* from further comments. Also removes all empty header starts (backwards
* compat).
*
* @param header The list of heading comments
* @return The modified list
*/
private List<String> loadHeader(List<String> header) {
LinkedList<String> list = new LinkedList<>(header);
if (!list.isEmpty()) {
list.removeLast();
}
while (!list.isEmpty() && list.peek() == null) {
list.remove();
}
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) {
LinkedList<String> list = new LinkedList<>(header);
if (!list.isEmpty()) {
list.add(null);
}
return list;
}
@NotNull
@Override
public YamlConfigurationOptions options() {
if (options == null) {
options = new YamlConfigurationOptions(this);
}
return (YamlConfigurationOptions) options;
}
/**
* Creates a new {@link YamlConfiguration}, loading from the given file.
* <p>
* Any errors loading the Configuration will be logged and then ignored.
* If the specified input is not a valid config, a blank config will be
* returned.
* <p>
* The encoding used may follow the system dependent default.
*
* @param file Input file
* @return Resulting configuration
* @throws IllegalArgumentException Thrown if file is null
*/
@NotNull
public static YamlConfiguration loadConfiguration(@NotNull File file) {
Validate.notNull(file, "File cannot be null");
YamlConfiguration config = new YamlConfiguration();
try {
config.load(file);
} catch (FileNotFoundException ex) {
} catch (IOException ex) {
ex.printStackTrace();
} catch (InvalidConfigurationException ex) {
ex.printStackTrace();
}
return config;
}
/**
* Creates a new {@link YamlConfiguration}, loading from the given reader.
* <p>
* Any errors loading the Configuration will be logged and then ignored.
* If the specified input is not a valid config, a blank config will be
* returned.
*
* @param reader input
* @return resulting configuration
* @throws IllegalArgumentException Thrown if stream is null
*/
@NotNull
public static YamlConfiguration loadConfiguration(@NotNull Reader reader) {
Validate.notNull(reader, "Stream cannot be null");
YamlConfiguration config = new YamlConfiguration();
try {
config.load(reader);
} catch (IOException ex) {
ex.printStackTrace();
} catch (InvalidConfigurationException ex) {
ex.printStackTrace();
}
return config;
}
}

View File

@ -0,0 +1,125 @@
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;
/**
* Various settings for controlling the input and output of a {@link
* YamlConfiguration}
*/
public class YamlConfigurationOptions extends FileConfigurationOptions {
private int indent = 2;
private int width = 80;
protected YamlConfigurationOptions(@NotNull YamlConfiguration configuration) {
super(configuration);
}
@NotNull
@Override
public YamlConfiguration configuration() {
return (YamlConfiguration) super.configuration();
}
@NotNull
@Override
public YamlConfigurationOptions copyDefaults(boolean value) {
super.copyDefaults(value);
return this;
}
@NotNull
@Override
public YamlConfigurationOptions pathSeparator(char value) {
super.pathSeparator(value);
return this;
}
@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;
}
@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;
}
/**
* Gets how much spaces should be used to indent each line.
* <p>
* The minimum value this may be is 2, and the maximum is 9.
*
* @return How much to indent by
*/
public int indent() {
return indent;
}
/**
* Sets how much spaces should be used to indent each line.
* <p>
* The minimum value this may be is 2, and the maximum is 9.
*
* @param value New indent
* @return This object, for chaining
*/
@NotNull
public YamlConfigurationOptions indent(int value) {
Validate.isTrue(value >= 2, "Indent must be at least 2 characters");
Validate.isTrue(value <= 9, "Indent cannot be greater than 9 characters");
this.indent = value;
return this;
}
/**
* Gets how long a line can be, before it gets split.
*
* @return How the max line width
*/
public int width() {
return width;
}
/**
* Sets how long a line can be, before it gets split.
*
* @param value New width
* @return This object, for chaining
*/
@NotNull
public YamlConfigurationOptions width(int value) {
this.width = value;
return this;
}
}

View File

@ -0,0 +1,62 @@
package org.bukkit.configuration.file;
import java.util.LinkedHashMap;
import java.util.Map;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.Tag;
public class YamlConstructor extends SafeConstructor {
public YamlConstructor() {
this.yamlConstructors.put(Tag.MAP, new ConstructCustomObject());
}
@Override
public void flattenMapping(@NotNull final MappingNode node) {
super.flattenMapping(node);
}
@Nullable
public Object construct(@NotNull Node node) {
return constructObject(node);
}
private class ConstructCustomObject extends ConstructYamlMap {
@Nullable
@Override
public Object construct(@NotNull Node node) {
if (node.isTwoStepsConstruction()) {
throw new YAMLException("Unexpected referential mapping structure. Node: " + node);
}
Map<?, ?> raw = (Map<?, ?>) super.construct(node);
if (raw.containsKey(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) {
Map<String, Object> typed = new LinkedHashMap<String, Object>(raw.size());
for (Map.Entry<?, ?> entry : raw.entrySet()) {
typed.put(entry.getKey().toString(), entry.getValue());
}
try {
return ConfigurationSerialization.deserializeObject(typed);
} catch (IllegalArgumentException ex) {
throw new YAMLException("Could not deserialize object", ex);
}
}
return raw;
}
@Override
public void construct2ndStep(@NotNull Node node, @NotNull Object object) {
throw new YAMLException("Unexpected referential mapping structure. Node: " + node);
}
}
}

View File

@ -0,0 +1,46 @@
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;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.representer.Representer;
public class YamlRepresenter extends Representer {
public YamlRepresenter() {
super();
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);
}
// SPIGOT-6949: Used by configuration sections that are nested within lists or maps.
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
@Override
public Node representData(@NotNull Object data) {
ConfigurationSerializable serializable = (ConfigurationSerializable) data;
Map<String, Object> values = new LinkedHashMap<String, Object>();
values.put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, ConfigurationSerialization.getAlias(serializable.getClass()));
values.putAll(serializable.serialize());
return super.representData(values);
}
}
}

View File

@ -0,0 +1,37 @@
package org.bukkit.configuration.serialization;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
/**
* Represents an object that may be serialized.
* <p>
* These objects MUST implement one of the following, in addition to the
* methods as defined by this interface:
* <ul>
* <li>A static method "deserialize" that accepts a single {@link Map}&lt;
* {@link String}, {@link Object}&gt; and returns the class.</li>
* <li>A static method "valueOf" that accepts a single {@link Map}&lt;{@link
* String}, {@link Object}&gt; and returns the class.</li>
* <li>A constructor that accepts a single {@link Map}&lt;{@link String},
* {@link Object}&gt;.</li>
* </ul>
* In addition to implementing this interface, you must register the class
* with {@link ConfigurationSerialization#registerClass(Class)}.
*
* @see DelegateDeserialization
* @see SerializableAs
*/
public interface ConfigurationSerializable {
/**
* Creates a Map representation of this class.
* <p>
* This class must provide a method to restore this class, as defined in
* the {@link ConfigurationSerializable} interface javadocs.
*
* @return Map containing the current state of this class
*/
@NotNull
public Map<String, Object> serialize();
}

View File

@ -0,0 +1,276 @@
package org.bukkit.configuration.serialization;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.Configuration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Utility class for storing and retrieving classes for {@link Configuration}.
*/
public class ConfigurationSerialization {
public static final String SERIALIZED_TYPE_KEY = "==";
private final Class<? extends ConfigurationSerializable> clazz;
private static Map<String, Class<? extends ConfigurationSerializable>> aliases = new HashMap<String, Class<? extends ConfigurationSerializable>>();
protected ConfigurationSerialization(@NotNull Class<? extends ConfigurationSerializable> clazz) {
this.clazz = clazz;
}
@Nullable
protected Method getMethod(@NotNull String name, boolean isStatic) {
try {
Method method = clazz.getDeclaredMethod(name, Map.class);
if (!ConfigurationSerializable.class.isAssignableFrom(method.getReturnType())) {
return null;
}
if (Modifier.isStatic(method.getModifiers()) != isStatic) {
return null;
}
return method;
} catch (NoSuchMethodException ex) {
return null;
} catch (SecurityException ex) {
return null;
}
}
@Nullable
protected Constructor<? extends ConfigurationSerializable> getConstructor() {
try {
return clazz.getConstructor(Map.class);
} catch (NoSuchMethodException ex) {
return null;
} catch (SecurityException ex) {
return null;
}
}
@Nullable
protected ConfigurationSerializable deserializeViaMethod(@NotNull Method method, @NotNull Map<String, ?> args) {
try {
ConfigurationSerializable result = (ConfigurationSerializable) method.invoke(null, args);
if (result == null) {
Logger.getLogger(ConfigurationSerialization.class.getName()).log(Level.SEVERE, "Could not call method '" + method.toString() + "' of " + clazz + " for deserialization: method returned null");
} else {
return result;
}
} catch (Throwable ex) {
Logger.getLogger(ConfigurationSerialization.class.getName()).log(
Level.SEVERE,
"Could not call method '" + method.toString() + "' of " + clazz + " for deserialization",
ex instanceof InvocationTargetException ? ex.getCause() : ex);
}
return null;
}
@Nullable
protected ConfigurationSerializable deserializeViaCtor(@NotNull Constructor<? extends ConfigurationSerializable> ctor, @NotNull Map<String, ?> args) {
try {
return ctor.newInstance(args);
} catch (Throwable ex) {
Logger.getLogger(ConfigurationSerialization.class.getName()).log(
Level.SEVERE,
"Could not call constructor '" + ctor.toString() + "' of " + clazz + " for deserialization",
ex instanceof InvocationTargetException ? ex.getCause() : ex);
}
return null;
}
@Nullable
public ConfigurationSerializable deserialize(@NotNull Map<String, ?> args) {
Validate.notNull(args, "Args must not be null");
ConfigurationSerializable result = null;
Method method = null;
if (result == null) {
method = getMethod("deserialize", true);
if (method != null) {
result = deserializeViaMethod(method, args);
}
}
if (result == null) {
method = getMethod("valueOf", true);
if (method != null) {
result = deserializeViaMethod(method, args);
}
}
if (result == null) {
Constructor<? extends ConfigurationSerializable> constructor = getConstructor();
if (constructor != null) {
result = deserializeViaCtor(constructor, args);
}
}
return result;
}
/**
* Attempts to deserialize the given arguments into a new instance of the
* given class.
* <p>
* The class must implement {@link ConfigurationSerializable}, including
* the extra methods as specified in the javadoc of
* ConfigurationSerializable.
* <p>
* If a new instance could not be made, an example being the class not
* fully implementing the interface, null will be returned.
*
* @param args Arguments for deserialization
* @param clazz Class to deserialize into
* @return New instance of the specified class
*/
@Nullable
public static ConfigurationSerializable deserializeObject(@NotNull Map<String, ?> args, @NotNull Class<? extends ConfigurationSerializable> clazz) {
return new ConfigurationSerialization(clazz).deserialize(args);
}
/**
* Attempts to deserialize the given arguments into a new instance of the
* given class.
* <p>
* The class must implement {@link ConfigurationSerializable}, including
* the extra methods as specified in the javadoc of
* ConfigurationSerializable.
* <p>
* If a new instance could not be made, an example being the class not
* fully implementing the interface, null will be returned.
*
* @param args Arguments for deserialization
* @return New instance of the specified class
*/
@Nullable
public static ConfigurationSerializable deserializeObject(@NotNull Map<String, ?> args) {
Class<? extends ConfigurationSerializable> clazz = null;
if (args.containsKey(SERIALIZED_TYPE_KEY)) {
try {
String alias = (String) args.get(SERIALIZED_TYPE_KEY);
if (alias == null) {
throw new IllegalArgumentException("Cannot have null alias");
}
clazz = getClassByAlias(alias);
if (clazz == null) {
throw new IllegalArgumentException("Specified class does not exist ('" + alias + "')");
}
} catch (ClassCastException ex) {
ex.fillInStackTrace();
throw ex;
}
} else {
throw new IllegalArgumentException("Args doesn't contain type key ('" + SERIALIZED_TYPE_KEY + "')");
}
return new ConfigurationSerialization(clazz).deserialize(args);
}
/**
* Registers the given {@link ConfigurationSerializable} class by its
* alias
*
* @param clazz Class to register
*/
public static void registerClass(@NotNull Class<? extends ConfigurationSerializable> clazz) {
DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class);
if (delegate == null) {
registerClass(clazz, getAlias(clazz));
registerClass(clazz, clazz.getName());
}
}
/**
* Registers the given alias to the specified {@link
* ConfigurationSerializable} class
*
* @param clazz Class to register
* @param alias Alias to register as
* @see SerializableAs
*/
public static void registerClass(@NotNull Class<? extends ConfigurationSerializable> clazz, @NotNull String alias) {
aliases.put(alias, clazz);
}
/**
* Unregisters the specified alias to a {@link ConfigurationSerializable}
*
* @param alias Alias to unregister
*/
public static void unregisterClass(@NotNull String alias) {
aliases.remove(alias);
}
/**
* Unregisters any aliases for the specified {@link
* ConfigurationSerializable} class
*
* @param clazz Class to unregister
*/
public static void unregisterClass(@NotNull Class<? extends ConfigurationSerializable> clazz) {
while (aliases.values().remove(clazz)) {
;
}
}
/**
* Attempts to get a registered {@link ConfigurationSerializable} class by
* its alias
*
* @param alias Alias of the serializable
* @return Registered class, or null if not found
*/
@Nullable
public static Class<? extends ConfigurationSerializable> getClassByAlias(@NotNull String alias) {
return aliases.get(alias);
}
/**
* Gets the correct alias for the given {@link ConfigurationSerializable}
* class
*
* @param clazz Class to get alias for
* @return Alias to use for the class
*/
@NotNull
public static String getAlias(@NotNull Class<? extends ConfigurationSerializable> clazz) {
DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class);
if (delegate != null) {
if ((delegate.value() == null) || (delegate.value() == clazz)) {
delegate = null;
} else {
return getAlias(delegate.value());
}
}
if (delegate == null) {
SerializableAs alias = clazz.getAnnotation(SerializableAs.class);
if ((alias != null) && (alias.value() != null)) {
return alias.value();
}
}
return clazz.getName();
}
}

View File

@ -0,0 +1,24 @@
package org.bukkit.configuration.serialization;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.jetbrains.annotations.NotNull;
/**
* Applies to a {@link ConfigurationSerializable} that will delegate all
* deserialization to another {@link ConfigurationSerializable}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DelegateDeserialization {
/**
* Which class should be used as a delegate for this classes
* deserialization
*
* @return Delegate class
*/
@NotNull
public Class<? extends ConfigurationSerializable> value();
}

View File

@ -0,0 +1,36 @@
package org.bukkit.configuration.serialization;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.jetbrains.annotations.NotNull;
/**
* Represents an "alias" that a {@link ConfigurationSerializable} may be
* stored as.
* If this is not present on a {@link ConfigurationSerializable} class, it
* will use the fully qualified name of the class.
* <p>
* This value will be stored in the configuration so that the configuration
* deserialization can determine what type it is.
* <p>
* Using this annotation on any other class than a {@link
* ConfigurationSerializable} will have no effect.
*
* @see ConfigurationSerialization#registerClass(Class, String)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SerializableAs {
/**
* This is the name your class will be stored and retrieved as.
* <p>
* This name MUST be unique. We recommend using names such as
* "MyPluginThing" instead of "Thing".
*
* @return Name to serialize the class as.
*/
@NotNull
public String value();
}

View File

@ -0,0 +1,124 @@
package org.bukkit.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class NumberConversions {
private NumberConversions() {}
public static int floor(double num) {
final int floor = (int) num;
return floor == num ? floor : floor - (int) (Double.doubleToRawLongBits(num) >>> 63);
}
public static int ceil(final double num) {
final int floor = (int) num;
return floor == num ? floor : floor + (int) (~Double.doubleToRawLongBits(num) >>> 63);
}
public static int round(double num) {
return floor(num + 0.5d);
}
public static double square(double num) {
return num * num;
}
public static int toInt(@Nullable Object object) {
if (object instanceof Number) {
return ((Number) object).intValue();
}
try {
return Integer.parseInt(object.toString());
} catch (NumberFormatException e) {
} catch (NullPointerException e) {
}
return 0;
}
public static float toFloat(@Nullable Object object) {
if (object instanceof Number) {
return ((Number) object).floatValue();
}
try {
return Float.parseFloat(object.toString());
} catch (NumberFormatException e) {
} catch (NullPointerException e) {
}
return 0;
}
public static double toDouble(@Nullable Object object) {
if (object instanceof Number) {
return ((Number) object).doubleValue();
}
try {
return Double.parseDouble(object.toString());
} catch (NumberFormatException e) {
} catch (NullPointerException e) {
}
return 0;
}
public static long toLong(@Nullable Object object) {
if (object instanceof Number) {
return ((Number) object).longValue();
}
try {
return Long.parseLong(object.toString());
} catch (NumberFormatException e) {
} catch (NullPointerException e) {
}
return 0;
}
public static short toShort(@Nullable Object object) {
if (object instanceof Number) {
return ((Number) object).shortValue();
}
try {
return Short.parseShort(object.toString());
} catch (NumberFormatException e) {
} catch (NullPointerException e) {
}
return 0;
}
public static byte toByte(@Nullable Object object) {
if (object instanceof Number) {
return ((Number) object).byteValue();
}
try {
return Byte.parseByte(object.toString());
} catch (NumberFormatException e) {
} catch (NullPointerException e) {
}
return 0;
}
public static boolean isFinite(double d) {
return Math.abs(d) <= Double.MAX_VALUE;
}
public static boolean isFinite(float f) {
return Math.abs(f) <= Float.MAX_VALUE;
}
public static void checkFinite(double d, @NotNull String message) {
if (!isFinite(d)) {
throw new IllegalArgumentException(message);
}
}
public static void checkFinite(float d, @NotNull String message) {
if (!isFinite(d)) {
throw new IllegalArgumentException(message);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
package org.json;
/**
* The JSONException is thrown by the JSON.org classes when things are amiss.
*
* @author JSON.org
* @version 2015-12-09
*/
public class JSONException extends RuntimeException {
/** Serialization ID */
private static final long serialVersionUID = 0;
/**
* Constructs a JSONException with an explanatory message.
*
* @param message
* Detail about the reason for the exception.
*/
public JSONException(final String message) {
super(message);
}
/**
* Constructs a JSONException with an explanatory message and cause.
*
* @param message
* Detail about the reason for the exception.
* @param cause
* The cause.
*/
public JSONException(final String message, final Throwable cause) {
super(message, cause);
}
/**
* Constructs a new JSONException with the specified cause.
*
* @param cause
* The cause.
*/
public JSONException(final Throwable cause) {
super(cause.getMessage(), cause);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,293 @@
package org.json;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static java.lang.String.format;
/*
Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
* A JSON Pointer is a simple query language defined for JSON documents by
* <a href="https://tools.ietf.org/html/rfc6901">RFC 6901</a>.
*
* In a nutshell, JSONPointer allows the user to navigate into a JSON document
* using strings, and retrieve targeted objects, like a simple form of XPATH.
* Path segments are separated by the '/' char, which signifies the root of
* the document when it appears as the first char of the string. Array
* elements are navigated using ordinals, counting from 0. JSONPointer strings
* may be extended to any arbitrary number of segments. If the navigation
* is successful, the matched item is returned. A matched item may be a
* JSONObject, a JSONArray, or a JSON value. If the JSONPointer string building
* fails, an appropriate exception is thrown. If the navigation fails to find
* a match, a JSONPointerException is thrown.
*
* @author JSON.org
* @version 2016-05-14
*/
public class JSONPointer {
// used for URL encoding and decoding
private static final String ENCODING = "utf-8";
/**
* This class allows the user to build a JSONPointer in steps, using
* exactly one segment in each step.
*/
public static class Builder {
// Segments for the eventual JSONPointer string
private final List<String> refTokens = new ArrayList<String>();
/**
* Creates a {@code JSONPointer} instance using the tokens previously set using the
* {@link #append(String)} method calls.
*/
public JSONPointer build() {
return new JSONPointer(this.refTokens);
}
/**
* Adds an arbitrary token to the list of reference tokens. It can be any non-null value.
*
* Unlike in the case of JSON string or URI fragment representation of JSON pointers, the
* argument of this method MUST NOT be escaped. If you want to query the property called
* {@code "a~b"} then you should simply pass the {@code "a~b"} string as-is, there is no
* need to escape it as {@code "a~0b"}.
*
* @param token the new token to be appended to the list
* @return {@code this}
* @throws NullPointerException if {@code token} is null
*/
public Builder append(String token) {
if (token == null) {
throw new NullPointerException("token cannot be null");
}
this.refTokens.add(token);
return this;
}
/**
* Adds an integer to the reference token list. Although not necessarily, mostly this token will
* denote an array index.
*
* @param arrayIndex the array index to be added to the token list
* @return {@code this}
*/
public Builder append(int arrayIndex) {
this.refTokens.add(String.valueOf(arrayIndex));
return this;
}
}
/**
* Static factory method for {@link Builder}. Example usage:
*
* <pre><code>
* JSONPointer pointer = JSONPointer.builder()
* .append("obj")
* .append("other~key").append("another/key")
* .append("\"")
* .append(0)
* .build();
* </code></pre>
*
* @return a builder instance which can be used to construct a {@code JSONPointer} instance by chained
* {@link Builder#append(String)} calls.
*/
public static Builder builder() {
return new Builder();
}
// Segments for the JSONPointer string
private final List<String> refTokens;
/**
* Pre-parses and initializes a new {@code JSONPointer} instance. If you want to
* evaluate the same JSON Pointer on different JSON documents then it is recommended
* to keep the {@code JSONPointer} instances due to performance considerations.
*
* @param pointer the JSON String or URI Fragment representation of the JSON pointer.
* @throws IllegalArgumentException if {@code pointer} is not a valid JSON pointer
*/
public JSONPointer(final String pointer) {
if (pointer == null) {
throw new NullPointerException("pointer cannot be null");
}
if (pointer.isEmpty() || pointer.equals("#")) {
this.refTokens = Collections.emptyList();
return;
}
String refs;
if (pointer.startsWith("#/")) {
refs = pointer.substring(2);
try {
refs = URLDecoder.decode(refs, ENCODING);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
} else if (pointer.startsWith("/")) {
refs = pointer.substring(1);
} else {
throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'");
}
this.refTokens = new ArrayList<String>();
int slashIdx = -1;
int prevSlashIdx = 0;
do {
prevSlashIdx = slashIdx + 1;
slashIdx = refs.indexOf('/', prevSlashIdx);
if(prevSlashIdx == slashIdx || prevSlashIdx == refs.length()) {
// found 2 slashes in a row ( obj//next )
// or single slash at the end of a string ( obj/test/ )
this.refTokens.add("");
} else if (slashIdx >= 0) {
final String token = refs.substring(prevSlashIdx, slashIdx);
this.refTokens.add(unescape(token));
} else {
// last item after separator, or no separator at all.
final String token = refs.substring(prevSlashIdx);
this.refTokens.add(unescape(token));
}
} while (slashIdx >= 0);
// using split does not take into account consecutive separators or "ending nulls"
//for (String token : refs.split("/")) {
// this.refTokens.add(unescape(token));
//}
}
public JSONPointer(List<String> refTokens) {
this.refTokens = new ArrayList<String>(refTokens);
}
private static String unescape(String token) {
return token.replace("~1", "/").replace("~0", "~")
.replace("\\\"", "\"")
.replace("\\\\", "\\");
}
/**
* Evaluates this JSON Pointer on the given {@code document}. The {@code document}
* is usually a {@link JSONObject} or a {@link JSONArray} instance, but the empty
* JSON Pointer ({@code ""}) can be evaluated on any JSON values and in such case the
* returned value will be {@code document} itself.
*
* @param document the JSON document which should be the subject of querying.
* @return the result of the evaluation
* @throws JSONPointerException if an error occurs during evaluation
*/
public Object queryFrom(Object document) throws JSONPointerException {
if (this.refTokens.isEmpty()) {
return document;
}
Object current = document;
for (String token : this.refTokens) {
if (current instanceof JSONObject) {
current = ((JSONObject) current).opt(unescape(token));
} else if (current instanceof JSONArray) {
current = readByIndexToken(current, token);
} else {
throw new JSONPointerException(format(
"value [%s] is not an array or object therefore its key %s cannot be resolved", current,
token));
}
}
return current;
}
/**
* Matches a JSONArray element by ordinal position
* @param current the JSONArray to be evaluated
* @param indexToken the array index in string form
* @return the matched object. If no matching item is found a
* @throws JSONPointerException is thrown if the index is out of bounds
*/
private static Object readByIndexToken(Object current, String indexToken) throws JSONPointerException {
try {
int index = Integer.parseInt(indexToken);
JSONArray currentArr = (JSONArray) current;
if (index >= currentArr.length()) {
throw new JSONPointerException(format("index %s is out of bounds - the array has %d elements", indexToken,
Integer.valueOf(currentArr.length())));
}
try {
return currentArr.get(index);
} catch (JSONException e) {
throw new JSONPointerException("Error reading value at index position " + index, e);
}
} catch (NumberFormatException e) {
throw new JSONPointerException(format("%s is not an array index", indexToken), e);
}
}
/**
* Returns a string representing the JSONPointer path value using string
* representation
*/
@Override
public String toString() {
StringBuilder rval = new StringBuilder("");
for (String token: this.refTokens) {
rval.append('/').append(escape(token));
}
return rval.toString();
}
/**
* Escapes path segment values to an unambiguous form.
* The escape char to be inserted is '~'. The chars to be escaped
* are ~, which maps to ~0, and /, which maps to ~1. Backslashes
* and double quote chars are also escaped.
* @param token the JSONPointer segment value to be escaped
* @return the escaped value for the token
*/
private static String escape(String token) {
return token.replace("~", "~0")
.replace("/", "~1")
.replace("\\", "\\\\")
.replace("\"", "\\\"");
}
/**
* Returns a string representing the JSONPointer path value using URI
* fragment identifier representation
*/
public String toURIFragment() {
try {
StringBuilder rval = new StringBuilder("#");
for (String token : this.refTokens) {
rval.append('/').append(URLEncoder.encode(token, ENCODING));
}
return rval.toString();
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,45 @@
package org.json;
/*
Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
* The JSONPointerException is thrown by {@link JSONPointer} if an error occurs
* during evaluating a pointer.
*
* @author JSON.org
* @version 2016-05-13
*/
public class JSONPointerException extends JSONException {
private static final long serialVersionUID = 8872944667561856751L;
public JSONPointerException(String message) {
super(message);
}
public JSONPointerException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,43 @@
package org.json;
/*
Copyright (c) 2018 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Retention(RUNTIME)
@Target({METHOD})
/**
* Use this annotation on a getter method to override the Bean name
* parser for Bean -&gt; JSONObject mapping. If this annotation is
* present at any level in the class hierarchy, then the method will
* not be serialized from the bean into the JSONObject.
*/
public @interface JSONPropertyIgnore { }

View File

@ -0,0 +1,47 @@
package org.json;
/*
Copyright (c) 2018 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Retention(RUNTIME)
@Target({METHOD})
/**
* Use this annotation on a getter method to override the Bean name
* parser for Bean -&gt; JSONObject mapping. A value set to empty string <code>""</code>
* will have the Bean parser fall back to the default field name processing.
*/
public @interface JSONPropertyName {
/**
* @return The name of the property as to be used in the JSON Object.
*/
String value();
}

View File

@ -0,0 +1,18 @@
package org.json;
/**
* The <code>JSONString</code> interface allows a <code>toJSONString()</code>
* method so that a class can change the behavior of
* <code>JSONObject.toString()</code>, <code>JSONArray.toString()</code>,
* and <code>JSONWriter.value(</code>Object<code>)</code>. The
* <code>toJSONString</code> method will be used instead of the default behavior
* of using the Object's <code>toString()</code> method and quoting the result.
*/
public interface JSONString {
/**
* The <code>toJSONString</code> method allows a class to produce its own JSON
* serialization.
*
* @return A strictly syntactically correct JSON text.
*/
public String toJSONString();
}

View File

@ -0,0 +1,79 @@
package org.json;
/*
Copyright (c) 2006 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import java.io.StringWriter;
/**
* JSONStringer provides a quick and convenient way of producing JSON text.
* The texts produced strictly conform to JSON syntax rules. No whitespace is
* added, so the results are ready for transmission or storage. Each instance of
* JSONStringer can produce one JSON text.
* <p>
* A JSONStringer instance provides a <code>value</code> method for appending
* values to the
* text, and a <code>key</code>
* method for adding keys before values in objects. There are <code>array</code>
* and <code>endArray</code> methods that make and bound array values, and
* <code>object</code> and <code>endObject</code> methods which make and bound
* object values. All of these methods return the JSONWriter instance,
* permitting cascade style. For example, <pre>
* myString = new JSONStringer()
* .object()
* .key("JSON")
* .value("Hello, World!")
* .endObject()
* .toString();</pre> which produces the string <pre>
* {"JSON":"Hello, World!"}</pre>
* <p>
* The first method called must be <code>array</code> or <code>object</code>.
* There are no methods for adding commas or colons. JSONStringer adds them for
* you. Objects and arrays can be nested up to 20 levels deep.
* <p>
* This can sometimes be easier than using a JSONObject to build a string.
* @author JSON.org
* @version 2015-12-09
*/
public class JSONStringer extends JSONWriter {
/**
* Make a fresh JSONStringer. It can be used to build one JSON text.
*/
public JSONStringer() {
super(new StringWriter());
}
/**
* Return the JSON text. This method is used to obtain the product of the
* JSONStringer instance. It will return <code>null</code> if there was a
* problem in the construction of the JSON text (such as the calls to
* <code>array</code> were not properly balanced with calls to
* <code>endArray</code>).
* @return The JSON text.
*/
@Override
public String toString() {
return this.mode == 'd' ? this.writer.toString() : null;
}
}

View File

@ -0,0 +1,526 @@
package org.json;
import java.io.*;
/*
Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
* A JSONTokener takes a source string and extracts characters and tokens from
* it. It is used by the JSONObject and JSONArray constructors to parse
* JSON source strings.
* @author JSON.org
* @version 2014-05-03
*/
public class JSONTokener {
/** current read character position on the current line. */
private long character;
/** flag to indicate if the end of the input has been found. */
private boolean eof;
/** current read index of the input. */
private long index;
/** current line of the input. */
private long line;
/** previous character read from the input. */
private char previous;
/** Reader for the input. */
private final Reader reader;
/** flag to indicate that a previous character was requested. */
private boolean usePrevious;
/** the number of characters read in the previous line. */
private long characterPreviousLine;
/**
* Construct a JSONTokener from a Reader. The caller must close the Reader.
*
* @param reader A reader.
*/
public JSONTokener(Reader reader) {
this.reader = reader.markSupported()
? reader
: new BufferedReader(reader);
this.eof = false;
this.usePrevious = false;
this.previous = 0;
this.index = 0;
this.character = 1;
this.characterPreviousLine = 0;
this.line = 1;
}
/**
* Construct a JSONTokener from an InputStream. The caller must close the input stream.
* @param inputStream The source.
*/
public JSONTokener(InputStream inputStream) {
this(new InputStreamReader(inputStream));
}
/**
* Construct a JSONTokener from a string.
*
* @param s A source string.
*/
public JSONTokener(String s) {
this(new StringReader(s));
}
/**
* Back up one character. This provides a sort of lookahead capability,
* so that you can test for a digit or letter before attempting to parse
* the next number or identifier.
* @throws JSONException Thrown if trying to step back more than 1 step
* or if already at the start of the string
*/
public void back() throws JSONException {
if (this.usePrevious || this.index <= 0) {
throw new JSONException("Stepping back two steps is not supported");
}
this.decrementIndexes();
this.usePrevious = true;
this.eof = false;
}
/**
* Decrements the indexes for the {@link #back()} method based on the previous character read.
*/
private void decrementIndexes() {
this.index--;
if(this.previous=='\r' || this.previous == '\n') {
this.line--;
this.character=this.characterPreviousLine ;
} else if(this.character > 0){
this.character--;
}
}
/**
* Get the hex value of a character (base16).
* @param c A character between '0' and '9' or between 'A' and 'F' or
* between 'a' and 'f'.
* @return An int between 0 and 15, or -1 if c was not a hex digit.
*/
public static int dehexchar(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'A' && c <= 'F') {
return c - ('A' - 10);
}
if (c >= 'a' && c <= 'f') {
return c - ('a' - 10);
}
return -1;
}
/**
* Checks if the end of the input has been reached.
*
* @return true if at the end of the file and we didn't step back
*/
public boolean end() {
return this.eof && !this.usePrevious;
}
/**
* Determine if the source string still contains characters that next()
* can consume.
* @return true if not yet at the end of the source.
* @throws JSONException thrown if there is an error stepping forward
* or backward while checking for more data.
*/
public boolean more() throws JSONException {
if(this.usePrevious) {
return true;
}
try {
this.reader.mark(1);
} catch (IOException e) {
throw new JSONException("Unable to preserve stream position", e);
}
try {
// -1 is EOF, but next() can not consume the null character '\0'
if(this.reader.read() <= 0) {
this.eof = true;
return false;
}
this.reader.reset();
} catch (IOException e) {
throw new JSONException("Unable to read the next character from the stream", e);
}
return true;
}
/**
* Get the next character in the source string.
*
* @return The next character, or 0 if past the end of the source string.
* @throws JSONException Thrown if there is an error reading the source string.
*/
public char next() throws JSONException {
int c;
if (this.usePrevious) {
this.usePrevious = false;
c = this.previous;
} else {
try {
c = this.reader.read();
} catch (IOException exception) {
throw new JSONException(exception);
}
}
if (c <= 0) { // End of stream
this.eof = true;
return 0;
}
this.incrementIndexes(c);
this.previous = (char) c;
return this.previous;
}
/**
* Increments the internal indexes according to the previous character
* read and the character passed as the current character.
* @param c the current character read.
*/
private void incrementIndexes(int c) {
if(c > 0) {
this.index++;
if(c=='\r') {
this.line++;
this.characterPreviousLine = this.character;
this.character=0;
}else if (c=='\n') {
if(this.previous != '\r') {
this.line++;
this.characterPreviousLine = this.character;
}
this.character=0;
} else {
this.character++;
}
}
}
/**
* Consume the next character, and check that it matches a specified
* character.
* @param c The character to match.
* @return The character.
* @throws JSONException if the character does not match.
*/
public char next(char c) throws JSONException {
char n = this.next();
if (n != c) {
if(n > 0) {
throw this.syntaxError("Expected '" + c + "' and instead saw '" +
n + "'");
}
throw this.syntaxError("Expected '" + c + "' and instead saw ''");
}
return n;
}
/**
* Get the next n characters.
*
* @param n The number of characters to take.
* @return A string of n characters.
* @throws JSONException
* Substring bounds error if there are not
* n characters remaining in the source string.
*/
public String next(int n) throws JSONException {
if (n == 0) {
return "";
}
char[] chars = new char[n];
int pos = 0;
while (pos < n) {
chars[pos] = this.next();
if (this.end()) {
throw this.syntaxError("Substring bounds error");
}
pos += 1;
}
return new String(chars);
}
/**
* Get the next char in the string, skipping whitespace.
* @throws JSONException Thrown if there is an error reading the source string.
* @return A character, or 0 if there are no more characters.
*/
public char nextClean() throws JSONException {
for (;;) {
char c = this.next();
if (c == 0 || c > ' ') {
return c;
}
}
}
/**
* Return the characters up to the next close quote character.
* Backslash processing is done. The formal JSON format does not
* allow strings in single quotes, but an implementation is allowed to
* accept them.
* @param quote The quoting character, either
* <code>"</code>&nbsp;<small>(double quote)</small> or
* <code>'</code>&nbsp;<small>(single quote)</small>.
* @return A String.
* @throws JSONException Unterminated string.
*/
public String nextString(char quote) throws JSONException {
char c;
StringBuilder sb = new StringBuilder();
for (;;) {
c = this.next();
switch (c) {
case 0:
case '\n':
case '\r':
throw this.syntaxError("Unterminated string");
case '\\':
c = this.next();
switch (c) {
case 'b':
sb.append('\b');
break;
case 't':
sb.append('\t');
break;
case 'n':
sb.append('\n');
break;
case 'f':
sb.append('\f');
break;
case 'r':
sb.append('\r');
break;
case 'u':
try {
sb.append((char)Integer.parseInt(this.next(4), 16));
} catch (NumberFormatException e) {
throw this.syntaxError("Illegal escape.", e);
}
break;
case '"':
case '\'':
case '\\':
case '/':
sb.append(c);
break;
default:
throw this.syntaxError("Illegal escape.");
}
break;
default:
if (c == quote) {
return sb.toString();
}
sb.append(c);
}
}
}
/**
* Get the text up but not including the specified character or the
* end of line, whichever comes first.
* @param delimiter A delimiter character.
* @return A string.
* @throws JSONException Thrown if there is an error while searching
* for the delimiter
*/
public String nextTo(char delimiter) throws JSONException {
StringBuilder sb = new StringBuilder();
for (;;) {
char c = this.next();
if (c == delimiter || c == 0 || c == '\n' || c == '\r') {
if (c != 0) {
this.back();
}
return sb.toString().trim();
}
sb.append(c);
}
}
/**
* Get the text up but not including one of the specified delimiter
* characters or the end of line, whichever comes first.
* @param delimiters A set of delimiter characters.
* @return A string, trimmed.
* @throws JSONException Thrown if there is an error while searching
* for the delimiter
*/
public String nextTo(String delimiters) throws JSONException {
char c;
StringBuilder sb = new StringBuilder();
for (;;) {
c = this.next();
if (delimiters.indexOf(c) >= 0 || c == 0 ||
c == '\n' || c == '\r') {
if (c != 0) {
this.back();
}
return sb.toString().trim();
}
sb.append(c);
}
}
/**
* Get the next value. The value can be a Boolean, Double, Integer,
* JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
* @throws JSONException If syntax error.
*
* @return An object.
*/
public Object nextValue() throws JSONException {
char c = this.nextClean();
String string;
switch (c) {
case '"':
case '\'':
return this.nextString(c);
case '{':
this.back();
return new JSONObject(this);
case '[':
this.back();
return new JSONArray(this);
}
/*
* Handle unquoted text. This could be the values true, false, or
* null, or it can be a number. An implementation (such as this one)
* is allowed to also accept non-standard forms.
*
* Accumulate characters until we reach the end of the text or a
* formatting character.
*/
StringBuilder sb = new StringBuilder();
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
sb.append(c);
c = this.next();
}
if (!this.eof) {
this.back();
}
string = sb.toString().trim();
if ("".equals(string)) {
throw this.syntaxError("Missing value");
}
return JSONObject.stringToValue(string);
}
/**
* Skip characters until the next character is the requested character.
* If the requested character is not found, no characters are skipped.
* @param to A character to skip to.
* @return The requested character, or zero if the requested character
* is not found.
* @throws JSONException Thrown if there is an error while searching
* for the to character
*/
public char skipTo(char to) throws JSONException {
char c;
try {
long startIndex = this.index;
long startCharacter = this.character;
long startLine = this.line;
this.reader.mark(1000000);
do {
c = this.next();
if (c == 0) {
// in some readers, reset() may throw an exception if
// the remaining portion of the input is greater than
// the mark size (1,000,000 above).
this.reader.reset();
this.index = startIndex;
this.character = startCharacter;
this.line = startLine;
return 0;
}
} while (c != to);
this.reader.mark(1);
} catch (IOException exception) {
throw new JSONException(exception);
}
this.back();
return c;
}
/**
* Make a JSONException to signal a syntax error.
*
* @param message The error message.
* @return A JSONException object, suitable for throwing
*/
public JSONException syntaxError(String message) {
return new JSONException(message + this.toString());
}
/**
* Make a JSONException to signal a syntax error.
*
* @param message The error message.
* @param causedBy The throwable that caused the error.
* @return A JSONException object, suitable for throwing
*/
public JSONException syntaxError(String message, Throwable causedBy) {
return new JSONException(message + this.toString(), causedBy);
}
/**
* Make a printable string of this JSONTokener.
*
* @return " at {index} [character {character} line {line}]"
*/
@Override
public String toString() {
return " at " + this.index + " [character " + this.character + " line " +
this.line + "]";
}
}

View File

@ -0,0 +1,413 @@
package org.json;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
/*
Copyright (c) 2006 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
* JSONWriter provides a quick and convenient way of producing JSON text.
* The texts produced strictly conform to JSON syntax rules. No whitespace is
* added, so the results are ready for transmission or storage. Each instance of
* JSONWriter can produce one JSON text.
* <p>
* A JSONWriter instance provides a <code>value</code> method for appending
* values to the
* text, and a <code>key</code>
* method for adding keys before values in objects. There are <code>array</code>
* and <code>endArray</code> methods that make and bound array values, and
* <code>object</code> and <code>endObject</code> methods which make and bound
* object values. All of these methods return the JSONWriter instance,
* permitting a cascade style. For example, <pre>
* new JSONWriter(myWriter)
* .object()
* .key("JSON")
* .value("Hello, World!")
* .endObject();</pre> which writes <pre>
* {"JSON":"Hello, World!"}</pre>
* <p>
* The first method called must be <code>array</code> or <code>object</code>.
* There are no methods for adding commas or colons. JSONWriter adds them for
* you. Objects and arrays can be nested up to 200 levels deep.
* <p>
* This can sometimes be easier than using a JSONObject to build a string.
* @author JSON.org
* @version 2016-08-08
*/
public class JSONWriter {
private static final int maxdepth = 200;
/**
* The comma flag determines if a comma should be output before the next
* value.
*/
private boolean comma;
/**
* The current mode. Values:
* 'a' (array),
* 'd' (done),
* 'i' (initial),
* 'k' (key),
* 'o' (object).
*/
protected char mode;
/**
* The object/array stack.
*/
private final JSONObject stack[];
/**
* The stack top index. A value of 0 indicates that the stack is empty.
*/
private int top;
/**
* The writer that will receive the output.
*/
protected Appendable writer;
/**
* Make a fresh JSONWriter. It can be used to build one JSON text.
*/
public JSONWriter(Appendable w) {
this.comma = false;
this.mode = 'i';
this.stack = new JSONObject[maxdepth];
this.top = 0;
this.writer = w;
}
/**
* Append a value.
* @param string A string value.
* @return this
* @throws JSONException If the value is out of sequence.
*/
private JSONWriter append(String string) throws JSONException {
if (string == null) {
throw new JSONException("Null pointer");
}
if (this.mode == 'o' || this.mode == 'a') {
try {
if (this.comma && this.mode == 'a') {
this.writer.append(',');
}
this.writer.append(string);
} catch (IOException e) {
// Android as of API 25 does not support this exception constructor
// however we won't worry about it. If an exception is happening here
// it will just throw a "Method not found" exception instead.
throw new JSONException(e);
}
if (this.mode == 'o') {
this.mode = 'k';
}
this.comma = true;
return this;
}
throw new JSONException("Value out of sequence.");
}
/**
* Begin appending a new array. All values until the balancing
* <code>endArray</code> will be appended to this array. The
* <code>endArray</code> method must be called to mark the array's end.
* @return this
* @throws JSONException If the nesting is too deep, or if the object is
* started in the wrong place (for example as a key or after the end of the
* outermost array or object).
*/
public JSONWriter array() throws JSONException {
if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') {
this.push(null);
this.append("[");
this.comma = false;
return this;
}
throw new JSONException("Misplaced array.");
}
/**
* End something.
* @param m Mode
* @param c Closing character
* @return this
* @throws JSONException If unbalanced.
*/
private JSONWriter end(char m, char c) throws JSONException {
if (this.mode != m) {
throw new JSONException(m == 'a'
? "Misplaced endArray."
: "Misplaced endObject.");
}
this.pop(m);
try {
this.writer.append(c);
} catch (IOException e) {
// Android as of API 25 does not support this exception constructor
// however we won't worry about it. If an exception is happening here
// it will just throw a "Method not found" exception instead.
throw new JSONException(e);
}
this.comma = true;
return this;
}
/**
* End an array. This method most be called to balance calls to
* <code>array</code>.
* @return this
* @throws JSONException If incorrectly nested.
*/
public JSONWriter endArray() throws JSONException {
return this.end('a', ']');
}
/**
* End an object. This method most be called to balance calls to
* <code>object</code>.
* @return this
* @throws JSONException If incorrectly nested.
*/
public JSONWriter endObject() throws JSONException {
return this.end('k', '}');
}
/**
* Append a key. The key will be associated with the next value. In an
* object, every value must be preceded by a key.
* @param string A key string.
* @return this
* @throws JSONException If the key is out of place. For example, keys
* do not belong in arrays or if the key is null.
*/
public JSONWriter key(String string) throws JSONException {
if (string == null) {
throw new JSONException("Null key.");
}
if (this.mode == 'k') {
try {
JSONObject topObject = this.stack[this.top - 1];
// don't use the built in putOnce method to maintain Android support
if(topObject.has(string)) {
throw new JSONException("Duplicate key \"" + string + "\"");
}
topObject.put(string, true);
if (this.comma) {
this.writer.append(',');
}
this.writer.append(JSONObject.quote(string));
this.writer.append(':');
this.comma = false;
this.mode = 'o';
return this;
} catch (IOException e) {
// Android as of API 25 does not support this exception constructor
// however we won't worry about it. If an exception is happening here
// it will just throw a "Method not found" exception instead.
throw new JSONException(e);
}
}
throw new JSONException("Misplaced key.");
}
/**
* Begin appending a new object. All keys and values until the balancing
* <code>endObject</code> will be appended to this object. The
* <code>endObject</code> method must be called to mark the object's end.
* @return this
* @throws JSONException If the nesting is too deep, or if the object is
* started in the wrong place (for example as a key or after the end of the
* outermost array or object).
*/
public JSONWriter object() throws JSONException {
if (this.mode == 'i') {
this.mode = 'o';
}
if (this.mode == 'o' || this.mode == 'a') {
this.append("{");
this.push(new JSONObject());
this.comma = false;
return this;
}
throw new JSONException("Misplaced object.");
}
/**
* Pop an array or object scope.
* @param c The scope to close.
* @throws JSONException If nesting is wrong.
*/
private void pop(char c) throws JSONException {
if (this.top <= 0) {
throw new JSONException("Nesting error.");
}
char m = this.stack[this.top - 1] == null ? 'a' : 'k';
if (m != c) {
throw new JSONException("Nesting error.");
}
this.top -= 1;
this.mode = this.top == 0
? 'd'
: this.stack[this.top - 1] == null
? 'a'
: 'k';
}
/**
* Push an array or object scope.
* @param jo The scope to open.
* @throws JSONException If nesting is too deep.
*/
private void push(JSONObject jo) throws JSONException {
if (this.top >= maxdepth) {
throw new JSONException("Nesting too deep.");
}
this.stack[this.top] = jo;
this.mode = jo == null ? 'a' : 'k';
this.top += 1;
}
/**
* Make a JSON text of an Object value. If the object has an
* value.toJSONString() method, then that method will be used to produce the
* JSON text. The method is required to produce a strictly conforming text.
* If the object does not contain a toJSONString method (which is the most
* common case), then a text will be produced by other means. If the value
* is an array or Collection, then a JSONArray will be made from it and its
* toJSONString method will be called. If the value is a MAP, then a
* JSONObject will be made from it and its toJSONString method will be
* called. Otherwise, the value's toString method will be called, and the
* result will be quoted.
*
* <p>
* Warning: This method assumes that the data structure is acyclical.
*
* @param value
* The value to be serialized.
* @return a printable, displayable, transmittable representation of the
* object, beginning with <code>{</code>&nbsp;<small>(left
* brace)</small> and ending with <code>}</code>&nbsp;<small>(right
* brace)</small>.
* @throws JSONException
* If the value is or contains an invalid number.
*/
public static String valueToString(Object value) throws JSONException {
if (value == null || value.equals(null)) {
return "null";
}
if (value instanceof JSONString) {
String object;
try {
object = ((JSONString) value).toJSONString();
} catch (Exception e) {
throw new JSONException(e);
}
if (object != null) {
return object;
}
throw new JSONException("Bad value from toJSONString: " + object);
}
if (value instanceof Number) {
// not all Numbers may match actual JSON Numbers. i.e. Fractions or Complex
final String numberAsString = JSONObject.numberToString((Number) value);
if(JSONObject.NUMBER_PATTERN.matcher(numberAsString).matches()) {
// Close enough to a JSON number that we will return it unquoted
return numberAsString;
}
// The Number value is not a valid JSON number.
// Instead we will quote it as a string
return JSONObject.quote(numberAsString);
}
if (value instanceof Boolean || value instanceof JSONObject
|| value instanceof JSONArray) {
return value.toString();
}
if (value instanceof Map) {
Map<?, ?> map = (Map<?, ?>) value;
return new JSONObject(map).toString();
}
if (value instanceof Collection) {
Collection<?> coll = (Collection<?>) value;
return new JSONArray(coll).toString();
}
if (value.getClass().isArray()) {
return new JSONArray(value).toString();
}
if(value instanceof Enum<?>){
return JSONObject.quote(((Enum<?>)value).name());
}
return JSONObject.quote(value.toString());
}
/**
* Append either the value <code>true</code> or the value
* <code>false</code>.
* @param b A boolean.
* @return this
* @throws JSONException
*/
public JSONWriter value(boolean b) throws JSONException {
return this.append(b ? "true" : "false");
}
/**
* Append a double value.
* @param d A double.
* @return this
* @throws JSONException If the number is not finite.
*/
public JSONWriter value(double d) throws JSONException {
return this.value(Double.valueOf(d));
}
/**
* Append a long value.
* @param l A long.
* @return this
* @throws JSONException
*/
public JSONWriter value(long l) throws JSONException {
return this.append(Long.toString(l));
}
/**
* Append an object value.
* @param object The object to append. It can be null, or a Boolean, Number,
* String, JSONObject, or JSONArray, or an object that implements JSONString.
* @return this
* @throws JSONException If the value is out of sequence.
*/
public JSONWriter value(Object object) throws JSONException {
return this.append(valueToString(object));
}
}

View File

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: com.io.yutian.pluginsettingtool.Main