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