1.0
This commit is contained in:
commit
420c5c4ac8
40
.gitignore
vendored
Normal file
40
.gitignore
vendored
Normal 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
51
pom.xml
Normal 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>
|
13
src/main/java/com/io/yutian/pluginsettingtool/Main.java
Normal file
13
src/main/java/com/io/yutian/pluginsettingtool/Main.java
Normal 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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 )
|
||||||
|
} )
|
||||||
|
}
|
||||||
|
}
|
89
src/main/java/org/bukkit/configuration/Configuration.java
Normal file
89
src/main/java/org/bukkit/configuration/Configuration.java
Normal 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();
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
829
src/main/java/org/bukkit/configuration/ConfigurationSection.java
Normal file
829
src/main/java/org/bukkit/configuration/ConfigurationSection.java
Normal 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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
949
src/main/java/org/bukkit/configuration/MemorySection.java
Normal file
949
src/main/java/org/bukkit/configuration/MemorySection.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
81
src/main/java/org/bukkit/configuration/SectionPathData.java
Normal file
81
src/main/java/org/bukkit/configuration/SectionPathData.java
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package org.bukkit.configuration;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
final class SectionPathData {
|
||||||
|
|
||||||
|
private Object data;
|
||||||
|
private List<String> comments;
|
||||||
|
private List<String> inlineComments;
|
||||||
|
|
||||||
|
public SectionPathData(@Nullable Object data) {
|
||||||
|
this.data = data;
|
||||||
|
comments = Collections.emptyList();
|
||||||
|
inlineComments = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Object getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(@Nullable final Object data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If no comments exist, an empty list will be returned. A null entry in the
|
||||||
|
* list represents an empty line and an empty String represents an empty
|
||||||
|
* comment line.
|
||||||
|
*
|
||||||
|
* @return A unmodifiable list of the requested comments, every entry
|
||||||
|
* represents one line.
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public List<String> getComments() {
|
||||||
|
return comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the comments on a {@link ConfigurationSection} entry.
|
||||||
|
*
|
||||||
|
* A null entry in the List is an empty line and an empty String entry is an
|
||||||
|
* empty comment line. Any existing comments will be replaced, regardless of
|
||||||
|
* what the new comments are.
|
||||||
|
*
|
||||||
|
* @param comments New comments to set every entry represents one line.
|
||||||
|
*/
|
||||||
|
public void setComments(@Nullable final List<String> comments) {
|
||||||
|
this.comments = (comments == null) ? Collections.emptyList() : Collections.unmodifiableList(comments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If no comments exist, an empty list will be returned. A null entry in the
|
||||||
|
* list represents an empty line and an empty String represents an empty
|
||||||
|
* comment line.
|
||||||
|
*
|
||||||
|
* @return A unmodifiable list of the requested comments, every entry
|
||||||
|
* represents one line.
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public List<String> getInlineComments() {
|
||||||
|
return inlineComments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the comments on a {@link ConfigurationSection} entry.
|
||||||
|
*
|
||||||
|
* A null entry in the List is an empty line and an empty String entry is an
|
||||||
|
* empty comment line. Any existing comments will be replaced, regardless of
|
||||||
|
* what the new comments are.
|
||||||
|
*
|
||||||
|
* @param inlineComments New comments to set every entry represents one
|
||||||
|
* line.
|
||||||
|
*/
|
||||||
|
public void setInlineComments(@Nullable final List<String> inlineComments) {
|
||||||
|
this.inlineComments = (inlineComments == null) ? Collections.emptyList() : Collections.unmodifiableList(inlineComments);
|
||||||
|
}
|
||||||
|
}
|
74
src/main/java/org/bukkit/configuration/file/BukkitYaml.java
Normal file
74
src/main/java/org/bukkit/configuration/file/BukkitYaml.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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}<
|
||||||
|
* {@link String}, {@link Object}> and returns the class.</li>
|
||||||
|
* <li>A static method "valueOf" that accepts a single {@link Map}<{@link
|
||||||
|
* String}, {@link Object}> and returns the class.</li>
|
||||||
|
* <li>A constructor that accepts a single {@link Map}<{@link String},
|
||||||
|
* {@link Object}>.</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();
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
124
src/main/java/org/bukkit/util/NumberConversions.java
Normal file
124
src/main/java/org/bukkit/util/NumberConversions.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1517
src/main/java/org/json/JSONArray.java
Normal file
1517
src/main/java/org/json/JSONArray.java
Normal file
File diff suppressed because it is too large
Load Diff
45
src/main/java/org/json/JSONException.java
Normal file
45
src/main/java/org/json/JSONException.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
2647
src/main/java/org/json/JSONObject.java
Normal file
2647
src/main/java/org/json/JSONObject.java
Normal file
File diff suppressed because it is too large
Load Diff
293
src/main/java/org/json/JSONPointer.java
Normal file
293
src/main/java/org/json/JSONPointer.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
45
src/main/java/org/json/JSONPointerException.java
Normal file
45
src/main/java/org/json/JSONPointerException.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
43
src/main/java/org/json/JSONPropertyIgnore.java
Normal file
43
src/main/java/org/json/JSONPropertyIgnore.java
Normal 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 -> 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 { }
|
47
src/main/java/org/json/JSONPropertyName.java
Normal file
47
src/main/java/org/json/JSONPropertyName.java
Normal 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 -> 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();
|
||||||
|
}
|
18
src/main/java/org/json/JSONString.java
Normal file
18
src/main/java/org/json/JSONString.java
Normal 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();
|
||||||
|
}
|
79
src/main/java/org/json/JSONStringer.java
Normal file
79
src/main/java/org/json/JSONStringer.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
526
src/main/java/org/json/JSONTokener.java
Normal file
526
src/main/java/org/json/JSONTokener.java
Normal 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> <small>(double quote)</small> or
|
||||||
|
* <code>'</code> <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 + "]";
|
||||||
|
}
|
||||||
|
}
|
413
src/main/java/org/json/JSONWriter.java
Normal file
413
src/main/java/org/json/JSONWriter.java
Normal 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> <small>(left
|
||||||
|
* brace)</small> and ending with <code>}</code> <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));
|
||||||
|
}
|
||||||
|
}
|
3
src/main/resources/META-INF/MANIFEST.MF
Normal file
3
src/main/resources/META-INF/MANIFEST.MF
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Manifest-Version: 1.0
|
||||||
|
Main-Class: com.io.yutian.pluginsettingtool.Main
|
||||||
|
|
Loading…
Reference in New Issue
Block a user