commit 27e2312eed927d0908fe4ac9566536b2187d26b4 Author: Victor Date: Sat Aug 22 21:25:26 2015 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d3bae3d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/build/ +/dist/ +/nbproject/private/ \ No newline at end of file diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..cb480db --- /dev/null +++ b/build.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Builds, tests, and runs the project SamobotVK. + + + diff --git a/manifest.mf b/manifest.mf new file mode 100644 index 0000000..328e8e5 --- /dev/null +++ b/manifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +X-COMMENT: Main-Class will be added automatically by build + diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml new file mode 100644 index 0000000..928963f --- /dev/null +++ b/nbproject/build-impl.xml @@ -0,0 +1,1419 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties new file mode 100644 index 0000000..20e044c --- /dev/null +++ b/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=85dd5378 +build.xml.script.CRC32=e9039a67 +build.xml.stylesheet.CRC32=8064a381@1.79.0.48 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=85dd5378 +nbproject/build-impl.xml.script.CRC32=5f7e4b67 +nbproject/build-impl.xml.stylesheet.CRC32=05530350@1.79.0.48 diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 0000000..427cdb4 --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,76 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=SamobotVK +application.vendor=dns1 +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# \u0424\u0430\u0439\u043b\u044b \u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0435 build.classes.dir, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0438\u0441\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0438\u0437 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u044f\u0435\u043c\u043e\u0433\u043e \u0430\u0440\u0445\u0438\u0432\u0430 jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/SamobotVK.jar +dist.javadoc.dir=${dist.dir}/javadoc +endorsed.classpath= +excludes= +includes=** +jar.compress=false +javac.classpath= +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.external.vm=false +javac.processorpath=\ + ${javac.classpath} +javac.source=1.8 +javac.target=1.8 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +main.class=holdfast.samobot.ControlFrame +manifest.file=manifest.mf +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=false +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=UTF-8 +src.dir=src +test.src.dir=test diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 0000000..30e9d8b --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,15 @@ + + + org.netbeans.modules.java.j2seproject + + + SamobotVK + + + + + + + + + diff --git a/src/commands.txt b/src/commands.txt new file mode 100644 index 0000000..e248c86 --- /dev/null +++ b/src/commands.txt @@ -0,0 +1,18 @@ +Лена-бот + +Команды: + - выбери: выбирает из списка случайное значение (выбери пункт1, пункт2, ..., пунктN) + - курс / валюта: текущий курс валют + - ответ: поиск определения или расчёт выражений в гугле (ответ 10+50/15) + - фото: поиск картинок (фото Miami Green Diamond) + - текст: текст песни (текст Ария - Беспечный Ангел) + - boobs / сиськи / сиси: показывает сиськи из oboobs.ru + - butts / попа: показывает попы из obutts.ru + - пони / неко / пеппа / аниме: спечиализированный поиск картинок в вк + - пирожки: выводит стихи-пирожки с сайта perashki.ru + - инфа: подсчитывает вероятность чего-либо (инфа пойти спать) + - аудио / музыка / песню: поиск песен в вк (музыка Slipknot / аудио Disturbed - Inside The Fire) + - вкфото: поиск картинок в вк (вкфото авто) + - видео / видос: поиск видео в вк (видос концерт Parkway Drive) + - стата / статистика: выводит статистику о работе бота + - когда: определяет дату того или иного события (когда я пойду спать?) \ No newline at end of file diff --git a/src/holdfast/samobot/Config.java b/src/holdfast/samobot/Config.java new file mode 100644 index 0000000..7306823 --- /dev/null +++ b/src/holdfast/samobot/Config.java @@ -0,0 +1,16 @@ +package holdfast.samobot; + +/** + * + * @author HoldFast + */ +public class Config { + + // https://oauth.vk.com/authorize?client_id=APP_ID&scope=photos,messages,notifications,wall,audio,video&redirect_uri=https://oauth.vk.com/blank.html&display=page&v=5.34&response_type=token + + public static final String access_token = ""; + + public static final String BOT_NAMES = "лена|леночка"; // обращение + public static final String ANSWER_PREFIX = "";//"[Вадос] "; // префикс перед выводом сообщения бота + +} diff --git a/src/holdfast/samobot/ControlFrame.java b/src/holdfast/samobot/ControlFrame.java new file mode 100644 index 0000000..2694703 --- /dev/null +++ b/src/holdfast/samobot/ControlFrame.java @@ -0,0 +1,108 @@ +package holdfast.samobot; + +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.Font; +import java.io.IOException; +import javax.swing.*; + +/** + * + * @author aNNiMON + */ +public final class ControlFrame extends JFrame { + + private static final String TITLE = "Лена-бот"; + + public static void main(String[] args) throws Exception { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + EventQueue.invokeLater(() -> new ControlFrame().setVisible(true)); + } + + public ControlFrame() { + super(TITLE); + + final JMenuBar menu = new JMenuBar(); + final JMenu botMenu = new JMenu("Бот"); + final JMenuItem launchMenuItem = new JMenuItem("Запустить"); + final JMenuItem exitMenuItem = new JMenuItem("Выход"); + botMenu.add(launchMenuItem); + botMenu.add(exitMenuItem); + menu.add(botMenu); + final JMenu helpMenu = new JMenu("Помощь"); + final JMenuItem commandsMenuItem = new JMenuItem("Команды"); + helpMenu.add(commandsMenuItem); + menu.add(helpMenu); + setJMenuBar(menu); + + setLocationByPlatform(true); + setDefaultCloseOperation(EXIT_ON_CLOSE); + setPreferredSize(new Dimension(450, 320)); + + final JTabbedPane tabbedPane = new JTabbedPane(); + final JPanel controlPanel = new JPanel(); + controlPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); + controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.PAGE_AXIS)); + controlPanel.add(new JLabel("Текст сообщения:")); + final JTextArea messageArea = new JTextArea(5, 20); + messageArea.setFont(messageArea.getFont().deriveFont(14)); + controlPanel.add(new JScrollPane(messageArea)); + controlPanel.add(new JLabel("ID беседы:")); + final JTextField chatIdField = new JTextField("2"); + chatIdField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30)); + controlPanel.add(chatIdField); + final JButton sendButton = new JButton("Отправить"); + controlPanel.add(sendButton); + tabbedPane.addTab("Отправить сообщение", controlPanel); + + final JTextPane chatLog = new JTextPane(); + chatLog.setFont(new Font("Monospaced", Font.PLAIN, 12)); + tabbedPane.addTab("Лог чата", new JScrollPane(chatLog)); + + final JTextPane queryLog = new JTextPane(); + queryLog.setFont(chatLog.getFont()); + tabbedPane.addTab("Лог запросов", new JScrollPane(queryLog)); + + final JTextPane errorsLog = new JTextPane(); + errorsLog.setFont(new Font("Monospaced", Font.PLAIN, 10)); + tabbedPane.addTab("Лог ошибок", new JScrollPane(errorsLog)); + + add(tabbedPane); + pack(); + + Log.init(chatLog, queryLog, errorsLog); + + // Actions + launchMenuItem.addActionListener((e) -> { + if (!MainThread.run) { + StatisticsProcessor.init(); + new Thread(new MainThread(true)).start(); + launchMenuItem.setText("Остановить"); + setTitle(TITLE + " - Работает"); + } else { + MainThread.run = false; + launchMenuItem.setText("Запустить"); + setTitle(TITLE); + } + }); + exitMenuItem.addActionListener((e) -> System.exit(0)); + commandsMenuItem.addActionListener((e) -> { + try { + JOptionPane.showMessageDialog(this, + IOUtil.readResponse(getClass().getResourceAsStream("/commands.txt")), + TITLE, JOptionPane.INFORMATION_MESSAGE); + } catch (IOException ex) { + Log.error(ex); + } + }); + + sendButton.addActionListener((e) -> { + final String message = messageArea.getText(); + final int chatId = Integer.parseInt(chatIdField.getText()); + new Thread( () -> { + VK.sendMessage(message, null, chatId, 0); + EventQueue.invokeLater(() -> messageArea.setText("")); + }).start(); + }); + } +} diff --git a/src/holdfast/samobot/IOUtil.java b/src/holdfast/samobot/IOUtil.java new file mode 100644 index 0000000..deeccb1 --- /dev/null +++ b/src/holdfast/samobot/IOUtil.java @@ -0,0 +1,117 @@ +package holdfast.samobot; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import javax.net.ssl.HttpsURLConnection; + +/** + * + * @author aNNiMON + */ +public final class IOUtil { + + public static String get(String link) { + try { + final URL url = new URL(link); + return readResponse(url.openStream()); + } catch (IOException ex) { + return ""; + } + } + + public static String getWithUserAgent(String address) throws IOException { + URL urls = new URL(address); + HttpURLConnection http = (HttpURLConnection) urls.openConnection(); + http.setRequestProperty("User-Agent", "Mozilla/5.0"); + http.setRequestProperty("Charset", "UTF-8"); + return readResponse(http.getInputStream()); + + } + + public static String upload(String link, String params, byte[] data, String ext) { + try { + final URL url = new URL(link); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setDoOutput(true); + final String boundary = Long.toHexString(System.currentTimeMillis()); + con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); + + try (DataOutputStream writer = new DataOutputStream(con.getOutputStream())) { + writer.write(params.getBytes()); + writer.write( ("\r\n--" + boundary + "\r\n").getBytes() ); + writer.write( ("Content-Disposition: form-data; name=\"file\"; filename=\"file." + ext + "\"\r\n").getBytes() ); + writer.write( ("Content-Type: image/" + ext + "\r\n").getBytes()); + writer.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes()); + writer.write(data); + writer.write( ("\r\n--" + boundary + "--\r\n").getBytes() ); + writer.flush(); + } + return readResponse(con.getInputStream()); + } catch (IOException ex) { + return ""; + } + } + + public static byte[] download(String link) { + ByteArrayOutputStream uploadParams = new ByteArrayOutputStream(); + try { + final URL url = new URL(link); + try (InputStream is = url.openStream()) { + byte[] byteChunk = new byte[4096]; + int n; + while ((n = is.read(byteChunk)) > 0) { + uploadParams.write(byteChunk, 0, n); + } + } + } catch (IOException ioe) { } + return uploadParams.toByteArray(); + } + + public static String post(String link, byte[] data) { + try { + final URL url = new URL(link); + HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setDoOutput(true); + try (DataOutputStream wr = new DataOutputStream(con.getOutputStream())) { + wr.write(data); + wr.flush(); + } + return readResponse(con.getInputStream()); + } catch (IOException ex) { + return ""; + } + } + + public static String readResponse(InputStream is) throws IOException { + StringBuilder response = new StringBuilder(); + try (BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { + String inputLine; + while ((inputLine = in.readLine()) != null) { + response.append(inputLine).append(Util.NEW_LINE); + } + } + return response.toString(); + } + + public static String stackTraceToString(Throwable t) { + try ( + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + ) { + t.printStackTrace(pw); + return sw.toString(); + } catch (IOException ex) { + return ""; + } + } +} diff --git a/src/holdfast/samobot/Log.java b/src/holdfast/samobot/Log.java new file mode 100644 index 0000000..b744c1a --- /dev/null +++ b/src/holdfast/samobot/Log.java @@ -0,0 +1,97 @@ +package holdfast.samobot; + +import java.awt.Color; +import javax.swing.JTextPane; +import javax.swing.text.BadLocationException; +import javax.swing.text.Style; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; + +/** + * + * @author aNNiMON + */ +public final class Log { + + public static final String NORMAL = "normal"; + public static final String SMALL = "small"; + public static final String RED = "red"; + public static final String GREEN = "green"; + public static final String BLUE = "blue"; + + private static StyledDocument chat, query, errors; + + public static void init(JTextPane chatPane, JTextPane queryPane, JTextPane errorsPane) { + Log.chat = chatPane.getStyledDocument(); + Log.query = queryPane.getStyledDocument(); + Log.errors = errorsPane.getStyledDocument(); + addStyles(chatPane); + addStyles(queryPane); + addStyles(errorsPane); + } + + private static void addStyles(JTextPane pane) { + Style normal = pane.addStyle(NORMAL, null); + + StyleConstants.setFontSize(pane.addStyle(SMALL, normal), 8); + StyleConstants.setForeground(pane.addStyle(RED, normal), Color.RED); + StyleConstants.setForeground(pane.addStyle(GREEN, normal), Color.GREEN); + StyleConstants.setForeground(pane.addStyle(BLUE, normal), new Color(0xFF2E217B)); + } + + public static void error(Throwable throwable) { + insertTo(errors, IOUtil.stackTraceToString(throwable) + "\n\n", RED); + } + + public static void chatln(String text) { + chat(text + "\n"); + } + + public static void chatln(String text, String style) { + chat(text + "\n", style); + } + + public static void chat(String text) { + chat(text, NORMAL); + } + + public static void chat(String text, String style) { + insertTo(chat, text, style); + } + + + public static void queryln(String text) { + query(text + "\n"); + } + + public static void queryln(String text, String style) { + query(text + "\n", style); + } + + public static void query(String text) { + query(text, NORMAL); + } + + public static void query(String text, String style) { + insertTo(query, text, style); + } + + public static void insertTo(StyledDocument doc, String text, String style) { + try { + doc.insertString(doc.getLength(), text, doc.getStyle(style)); + } catch (BadLocationException ex) { } + } + + private static final boolean LOG = true; + + public static void println(int value) { + if (LOG) { + System.out.println(value); + } + } + public static void println(String text) { + if (LOG) { + System.out.println(text); + } + } +} diff --git a/src/holdfast/samobot/MainThread.java b/src/holdfast/samobot/MainThread.java new file mode 100644 index 0000000..399b119 --- /dev/null +++ b/src/holdfast/samobot/MainThread.java @@ -0,0 +1,123 @@ +package holdfast.samobot; + +import holdfast.samobot.commands.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * + * @author HoldFast + */ +public class MainThread implements Runnable { + + private static final String[] MESSAGES = { + "Ору с вас!", "Проигрываю", "Славка, го осу", "Олег, рисуй!", "Эд, го кодить", + "Листочек, потри мне сосочек", "Пойду вскроюсь", "Не за надпись" + }; + + private static final int ACCOUNT_ID = 258220262; + public static boolean run = false; + + private final Command[] commands; + private int lastChatId = 2; + + public MainThread(boolean run) { + MainThread.run = run; + commands = new Command[] { + new PhotoSearch(), + new VkPhotoSearch(), + new VkAudioSearch(), + new VkVideoSearch(), + new LyricSearch(), + new GoogleAnswer(), + new GooglePhotoSearch(), + new Oboobs(), + new Obutts(), + new Chooser(), + new Possibility(), + new SendMessage(), + new Pirozhki(), + new Currency(), + new Stats(), + new When(), + + new AnnimonResponse() + }; + } + + @Override + public void run() { + try { + int countEmptyUnread = 0; + int pts = VK.getLastPTS(); + while (run) { + Thread.sleep(5000); + + String obj = VK.getUnread(pts); + if (obj.isEmpty()) { + countEmptyUnread++; + if (countEmptyUnread >= 4) { + pts = VK.getLastPTS(); + countEmptyUnread = 0; + } + continue; + } + + JSONObject response = new JSONObject(obj).getJSONObject("response"); + pts = response.getInt("new_pts"); + JSONArray messages = response.getJSONArray("messages"); + JSONArray profiles = response.getJSONArray("profiles"); + final int messagesLength = messages.length(); + if (messagesLength == 1 && Util.random(80) == 5) { + String message; + if (Util.random(6) == 2) { + message = MESSAGES[Util.random(MESSAGES.length)]; + } else { + message = AnnimonResponse.getResponse("jhgjh"); + } + VK.sendMessage(message, null, lastChatId, 0); + } else { + for (int i = 1; i < messagesLength; i++) { + final JSONObject profile = profiles.getJSONObject(i - 1); + if (profile.getInt("uid") != ACCOUNT_ID) { + final JSONObject currentMessage = messages.getJSONObject(i); + answer(currentMessage, profile); + } + } + } + } + } catch (Exception ex) { + Log.error(ex); + new Thread(this).start(); + } + } + + private void answer(JSONObject currentMessage, JSONObject profile) throws Exception { + String message = currentMessage.getString("body"); + final int forward = currentMessage.getInt("mid"); + final int chatId = lastChatId = currentMessage.optInt("chat_id", currentMessage.getInt("uid")); + final boolean toUser = !currentMessage.has("chat_id"); + final String userName = profile.getString("first_name"); + + + Matcher match = Pattern.compile("^("+Config.BOT_NAMES + "),? ?(.+)", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE).matcher(message); + if (!match.matches()) return; + + message = match.group(2).trim(); + final String[] words = message.split("\\s+"); + if (words.length == 0) return; + + final String cmd = words[0].toLowerCase(); + for (Command command : commands) { + if (!command.match(cmd)) continue; + if (command.execute(chatId, toUser, forward, userName, words, message)) { + Log.chatln(String.format("(%s): %s", userName, message)); + StatisticsProcessor.updateUsername(userName); + StatisticsProcessor.updateAnswersCounter(); + return; + } + } + } +} diff --git a/src/holdfast/samobot/StatisticsProcessor.java b/src/holdfast/samobot/StatisticsProcessor.java new file mode 100644 index 0000000..b374bdb --- /dev/null +++ b/src/holdfast/samobot/StatisticsProcessor.java @@ -0,0 +1,74 @@ +package holdfast.samobot; + +import java.time.Duration; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * + * @author aNNiMON + */ +public final class StatisticsProcessor { + + private static Instant startTime; + private static long answers, photosUploaded; + private static Map usersActivity; + + public static void init() { + startTime = Instant.now(); + usersActivity = new HashMap<>(); + } + + public static Instant getStartTime() { + return startTime; + } + + + public static void updateAnswersCounter() { + answers++; + } + + public static long getAnswers() { + return answers; + } + + public static void updatePhotosUploadedCounter() { + photosUploaded++; + } + + public static long getPhotosUploaded() { + return photosUploaded; + } + + public static void updateUsername(String name) { + usersActivity.put(name, usersActivity.getOrDefault(name, 0) + 1); + } + + public static String asString() { + final Duration duration = Duration.between(startTime, Instant.now()); + final StringBuilder sb = new StringBuilder(); + sb.append("Время работы: "); + long time; + time = duration.toHours(); + if (time != 0) sb.append(time).append(" ч. "); + time = duration.toMinutes() % 60; + if (time != 0) sb.append(time).append(" мин. "); + time = duration.getSeconds() % 60; + if (time != 0) sb.append(time).append(" сек. "); + sb.append('\n'); + + sb.append("Отправлено сообщений: ").append(answers).append('\n'); + sb.append("Загружено фотографий: ").append(photosUploaded).append('\n'); + + sb.append("Активные пользователи:\n"); + sb.append(usersActivity.entrySet().stream() + .sorted((a, b) -> Integer.compare(b.getValue(), a.getValue())) + .limit(5) + .map(e -> String.format("%s: %d сообщений\n", e.getKey(), e.getValue())) + .collect(Collectors.joining())); + + return sb.toString(); + } +} diff --git a/src/holdfast/samobot/Util.java b/src/holdfast/samobot/Util.java new file mode 100644 index 0000000..f815c22 --- /dev/null +++ b/src/holdfast/samobot/Util.java @@ -0,0 +1,49 @@ +package holdfast.samobot; + +import java.time.LocalDate; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.Random; + +/** + * + * @author aNNiMON + */ +public final class Util { + + public static final String NEW_LINE = System.lineSeparator(); + + private static final String[] MONTH = { + "января", "февраля", "марта", "апреля", "мая", "июня", + "июля", "августа", "сентября", "октября", "ноября", "декабря" + }; + + public static Random rnd = new Random(); + + public static int random(int max) { + return rnd.nextInt(max); + } + + public static int random(int min, int max) { + return min + rnd.nextInt(max - min); + } + + public static String dateToString(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return String.format(Locale.getDefault(), "%02d %s %d", + cal.get(Calendar.DAY_OF_MONTH), + MONTH[cal.get(Calendar.MONTH)], + cal.get(Calendar.YEAR)); + + } + + public static String dateToString(LocalDate date) { + return String.format(Locale.getDefault(), "%02d %s %d", + date.getDayOfMonth(), + MONTH[date.getMonthValue() - 1], + date.getYear()); + + } +} diff --git a/src/holdfast/samobot/VK.java b/src/holdfast/samobot/VK.java new file mode 100644 index 0000000..d585b6a --- /dev/null +++ b/src/holdfast/samobot/VK.java @@ -0,0 +1,157 @@ +package holdfast.samobot; + +import java.io.IOException; +import java.net.URLEncoder; +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * + * @author HoldFast + */ +public class VK { + + public static String url = "https://api.vk.com/method/"; + + public static void sendMessage(String text, String attachment, int chatId, int forward) { + sendMessage(text, attachment, chatId, (chatId > 10000), forward); + } + + public static void sendMessage(String text, String attachment, int chatId, boolean toUser, int forward) { + Log.chatln("(Лена): " + text, Log.BLUE); + StringBuilder query = new StringBuilder(); + query.append(toUser ? "user_id=" : "chat_id=").append(chatId); + try { + query.append("&message=").append(URLEncoder.encode(Config.ANSWER_PREFIX + text, "UTF-8")); + } catch (IOException ioe) { } + if (attachment != null) { + query.append("&attachment=").append(attachment); + } + if (forward != 0) { + query.append("&forward_messages=").append(forward); + } + VK.query("messages.send", query.toString()); + } + + public static String query(String method, String params) { + params = "access_token=" + Config.access_token + "&" + params; + Log.queryln(" > " + url + method + "?" + params, Log.BLUE); + String res = IOUtil.post(url + method, params.getBytes()); + Log.query(" < "); + Log.queryln(res, Log.SMALL); + return res; + } + + public static int getLastPTS() { + String query = query("messages.getLongPollServer", "v=5.34&use_ssl=0&need_pts=1"); + Log.queryln(" > get last pts"); + Log.query(" < get last pts: "); + Log.queryln(query, Log.SMALL); + return (new JSONObject(query).getJSONObject("response")).getInt("pts"); + } + + public static String getUploadServer() { + return (new JSONObject(query("photos.getMessagesUploadServer", "v=5.37")).getJSONObject("response")).getString("upload_url"); + } + + public static String getUnread(int pts) { + String result = VK.query("messages.getLongPollHistory", "pts=" + pts); + Log.queryln(" > get unread " + pts, Log.GREEN); + Log.query(" < get unread: "); + Log.queryln(result, Log.SMALL); + return result; + } + + public static String getRandomPhoto(String query, int count, int offset) { + try { + String obj = VK.query("photos.search", "q=" + URLEncoder.encode(query.trim(), "UTF-8") + "&count=" + count + "&offset=" + offset); + Log.queryln(" > get photo " + query, Log.GREEN); + Log.query(" < get photo: "); + Log.queryln(obj, Log.SMALL); + JSONObject json = new JSONObject(obj); + JSONArray response = json.getJSONArray("response"); + if (response.length() <= 1) return null; + + JSONObject photo = response.getJSONObject(1); + return "photo" + photo.get("owner_id") + "_" + photo.get("pid"); + } catch (Exception ex) { + Log.error(ex); + } + return null; + } + + public static String getRandomAudio(String query, int count) { + try { + String obj = VK.query("audio.search", "q=" + URLEncoder.encode(query.trim(), "UTF-8") + "&auto_complete=1&count=" + count); + Log.queryln(" > get audio " + query, Log.GREEN); + Log.query(" < get audio: "); + Log.queryln(obj, Log.SMALL); + JSONObject json = new JSONObject(obj); + JSONArray response = json.getJSONArray("response"); + if (response.length() < 1) return null; + + StringBuilder sb = new StringBuilder(); + for (int i = 1; i < response.length(); i++) { + JSONObject audio = response.getJSONObject(i); + sb.append("audio").append(audio.get("owner_id")).append('_').append(audio.get("aid")); + sb.append(','); + } + + return sb.substring(0, sb.length() - 1); + } catch (Exception ex) { + Log.error(ex); + } + return null; + } + + public static String getRandomVideo(String query, int count) { + try { + String obj = VK.query("video.search", "q=" + URLEncoder.encode(query.trim(), "UTF-8") + "&adult=0&count=" + count); + Log.queryln(" > get video " + query, Log.GREEN); + Log.query(" < get video: "); + Log.queryln(obj, Log.SMALL); + JSONObject json = new JSONObject(obj); + JSONArray response = json.getJSONArray("response"); + if (response.length() < 1) return null; + + StringBuilder sb = new StringBuilder(); + for (int i = 1; i < response.length(); i++) { + JSONObject video = response.getJSONObject(i); + sb.append("video").append(video.get("owner_id")).append('_').append(video.get("id")); + sb.append(','); + } + + return sb.substring(0, sb.length() - 1); + } catch (Exception ex) { + Log.error(ex); + } + return null; + } + + public static String uploadPhoto(String photoUrl) { + String uploadServer = getUploadServer(); + Log.queryln(" > upload photo to " + uploadServer); + + String uploadResultRaw = IOUtil.upload(uploadServer, "photo=", IOUtil.download(photoUrl), getExtension(photoUrl)); + Log.query(" < upload photo result: "); + Log.queryln(uploadResultRaw, Log.SMALL); + JSONObject uploadResult = new JSONObject(uploadResultRaw); + + StringBuilder sb = new StringBuilder(); + sb.append("server=").append(uploadResult.get("server")); + sb.append("&photo=").append(uploadResult.get("photo")); + sb.append("&hash=").append(uploadResult.get("hash")); + + String photoRaw = VK.query("photos.saveMessagesPhoto", sb.toString()); + Log.query(" < saveMessagesPhoto result: "); + Log.queryln(photoRaw, Log.SMALL); + JSONObject photo = new JSONObject(photoRaw).getJSONArray("response").getJSONObject(0); + StatisticsProcessor.updatePhotosUploadedCounter(); + return "photo" + photo.get("owner_id") + "_" + photo.get("pid"); + } + + private static String getExtension(String link) { + if (link.endsWith(".png")) return "png"; + return "jpg"; + } +} diff --git a/src/holdfast/samobot/commands/AnnimonResponse.java b/src/holdfast/samobot/commands/AnnimonResponse.java new file mode 100644 index 0000000..f3dee39 --- /dev/null +++ b/src/holdfast/samobot/commands/AnnimonResponse.java @@ -0,0 +1,52 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.IOUtil; +import holdfast.samobot.Log; +import java.io.IOException; +import java.net.URLEncoder; +import org.json.JSONObject; + +/** + * + * @author aNNiMON + */ +public final class AnnimonResponse extends Command { + + @Override + protected String[] command() { + return new String[0]; + } + + @Override + public boolean match(String cmd) { + return true; + } + + @Override + protected boolean execute(String message, String userName) throws IOException { + String response = getResponse(message); + if (response == null) return false; + + send(userName + ", " + response); + return true; + } + + public static String getResponse(String query) { + try { + String response = IOUtil.get("https://query.yahooapis.com/v1/public/yql?q=" + + "select%20*%20from%20htmlpost%20where%20url%3D" + + "\"http%3A%2F%2Fannimon.com%2Fjson%2Fbot_vk.php\"" + + "%20and%20postdata%3D\"%26text%3D" + URLEncoder.encode(query, "UTF-8") + "%26\"" + + "%20and%20xpath%3D\"*\"&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys"); + return new JSONObject(response) + .getJSONObject("query") + .getJSONObject("results") + .getJSONObject("postresult") + .getJSONObject("html") + .getString("body"); + } catch (IOException ex) { + Log.error(ex); + return null; + } + } +} diff --git a/src/holdfast/samobot/commands/Chooser.java b/src/holdfast/samobot/commands/Chooser.java new file mode 100644 index 0000000..57aaf12 --- /dev/null +++ b/src/holdfast/samobot/commands/Chooser.java @@ -0,0 +1,27 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.Util; +import java.io.IOException; + +/** + * + * @author aNNiMON + */ +public final class Chooser extends Command { + + @Override + protected String[] command() { + return new String[] { "выбери" }; + } + + @Override + protected boolean execute(String message, String userName) throws IOException { + message = message.substring(words[0].length()); + + final String[] args = message.split(",\\s?"); + final int index = Util.random(args.length); + + send(userName + ", я выбираю " + args[index]); + return true; + } +} diff --git a/src/holdfast/samobot/commands/Command.java b/src/holdfast/samobot/commands/Command.java new file mode 100644 index 0000000..63df627 --- /dev/null +++ b/src/holdfast/samobot/commands/Command.java @@ -0,0 +1,55 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.Log; +import holdfast.samobot.VK; +import java.io.IOException; +import java.net.URLEncoder; + +/** + * + * @author aNNiMON + */ +public abstract class Command { + + protected String[] words; + protected boolean toUser; + protected int chatId; + protected int forward; + + public boolean match(String cmd) { + for (String command : command()) { + if (cmd.equalsIgnoreCase(command)) { + return true; + } + } + return false; + } + + public boolean execute(int chatId, boolean toUser, int forward, String userName, String[] words, String message) { + this.chatId = chatId; + this.toUser = toUser; + this.words = words; + this.forward = forward; + try { + return execute(message, userName); + } catch (IOException ioe) { + return false; + } + } + + protected abstract String[] command(); + + protected abstract boolean execute(String message, String userName) throws IOException; + + protected void send(String text) throws IOException { + send(text, null, forward); + } + + protected void send(String text, String attachment) throws IOException { + send(text, attachment, forward); + } + + protected void send(String text, String attachment, int forward) throws IOException { + VK.sendMessage(text, attachment, chatId, toUser, forward); + } +} diff --git a/src/holdfast/samobot/commands/Currency.java b/src/holdfast/samobot/commands/Currency.java new file mode 100644 index 0000000..610b727 --- /dev/null +++ b/src/holdfast/samobot/commands/Currency.java @@ -0,0 +1,71 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.IOUtil; +import static holdfast.samobot.Util.NEW_LINE; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * + * @author aNNiMON + */ +public class Currency extends Command { + + private static final String API_URL = "https://query.yahooapis.com/v1/public/yql?q=select+*+from+yahoo.finance.xchange+where+pair+=+%22USDRUB,EURRUB,USDUAH,EURUAH,UAHRUB%22&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback="; + + @Override + protected String[] command() { + return new String[] { "курс", "валюта" }; + } + + @Override + protected boolean execute(String message, String userName) throws IOException { + String raw = IOUtil.get(API_URL); + JSONArray array = new JSONObject(raw).getJSONObject("query").getJSONObject("results").getJSONArray("rate"); + + StringBuilder sb = new StringBuilder(); + sb.append("Курс валют:"); + for (int i = 0; i < array.length(); i++) { + Rate rate = new Rate(array.optJSONObject(i)); + sb.append(NEW_LINE).append(rate.getReadableString()); + } + + send(sb.toString()); + return true; + } + + + private static class Rate { + + private static final Map readableName; + + static { + readableName = new HashMap<>(); + readableName.put("USDRUB", "Доллар: %f рублей"); + readableName.put("EURRUB", "Евро: %f рублей"); + readableName.put("USDUAH", "Доллар: %f гривен"); + readableName.put("EURUAH", "Евро: %f гривен"); + readableName.put("UAHRUB", "Гривна: %f рублей"); + } + + String id; + double rate; + + public Rate(String id, double rate) { + this.id = id; + this.rate = rate; + } + + public Rate(JSONObject json) { + this.id = json.getString("id"); + this.rate = json.getDouble("Rate"); + } + + public String getReadableString() { + return String.format(readableName.get(id), rate); + } + } +} diff --git a/src/holdfast/samobot/commands/GoogleAnswer.java b/src/holdfast/samobot/commands/GoogleAnswer.java new file mode 100644 index 0000000..7ec018d --- /dev/null +++ b/src/holdfast/samobot/commands/GoogleAnswer.java @@ -0,0 +1,83 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.IOUtil; +import holdfast.samobot.Log; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Arrays; + +/** + * + * @author aNNiMON + */ +public final class GoogleAnswer extends Command { + + private static String GOOGLE_URL = "https://www.google.ru/search?hl=ru&ie=UTF-8&q="; + private static final String[] SKIP_RESULTS = { + "Похожие запросы", "Показаны результаты", "Поиск рядом" + }; + + @Override + protected String[] command() { + return new String[] { "ответ" }; + } + + @Override + protected boolean execute(String message, String userName) throws IOException { + message = message.substring(words[0].length()); + + String fact = getFact(message); + if (fact == null) return false; + + send(fact); + return true; + } + + + public static String getFact(String query) { + try { + URL urls = new URL(GOOGLE_URL + URLEncoder.encode(query, "UTF-8")); + HttpURLConnection http = (HttpURLConnection) urls.openConnection(); + http.setRequestProperty("User-Agent", "Opera/9.80 (J2ME/MIDP; Opera Mini/4.4.29476/27.1573; U; id) Presto/2.8.119 Version/11.10"); + http.setRequestProperty("Charset", "UTF-8"); + + String fact = IOUtil.readResponse(http.getInputStream()); + + if (fact.contains("
")) { + String left = fact.substring(fact.indexOf("
") + 52); + String right = left.substring(0, left.indexOf("
")); + right = right.replaceAll("\\<[^>]*>", "\n").replaceAll("\n{2,}", "\n").replace(" ", "").trim(); + if (!right.isEmpty()) { + return right; + } + } else { + int index = 0; + while (true) { + index = fact.indexOf("onebox_result\">", index + 1); + if (index == -1) return null; + + String left = fact.substring(index + 15); + String right = left.substring(0, left.indexOf("
")); + final String block = right + .replace("", "^") + .replace("", "") + .replaceAll("\\<[^>]*>", "\n") + .replaceAll("\n{2,}", "\n") + .replace(" ", "").trim(); + if (block.isEmpty()) continue; + + if (Arrays.stream(SKIP_RESULTS) + .noneMatch(s -> block.startsWith(s))) { + return block; + } + } + } + + } catch (Exception ex) { + Log.error(ex); + } + return null; + } +} diff --git a/src/holdfast/samobot/commands/GooglePhotoSearch.java b/src/holdfast/samobot/commands/GooglePhotoSearch.java new file mode 100644 index 0000000..013bd5f --- /dev/null +++ b/src/holdfast/samobot/commands/GooglePhotoSearch.java @@ -0,0 +1,45 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.IOUtil; +import holdfast.samobot.Util; +import holdfast.samobot.VK; +import java.io.IOException; +import java.net.URLEncoder; +import org.json.JSONObject; + +/** + * + * @author aNNiMON + */ +public class GooglePhotoSearch extends Command { + + private static final String API_URL = "https://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8&q="; + + @Override + protected String[] command() { + return new String[] { "фото", "покажи" }; + } + + @Override + public boolean execute(String message, String userName) throws IOException { + final String query = message.substring(words[0].length()); + + String json = IOUtil.get(API_URL + URLEncoder.encode(query, "UTF-8")); + String photoUrl = new JSONObject(json).getJSONObject("responseData").getJSONArray("results").getJSONObject(Util.random(8)).getString("url"); + + String photo; + try { + photo = VK.uploadPhoto(photoUrl); + } catch (Exception e) { + photo = null; + } + + if (photo == null) { + send(userName + ", ничего не найдено :("); + } else { + send(userName, photo); + } + return true; + } + +} diff --git a/src/holdfast/samobot/commands/LyricSearch.java b/src/holdfast/samobot/commands/LyricSearch.java new file mode 100644 index 0000000..4c5c885 --- /dev/null +++ b/src/holdfast/samobot/commands/LyricSearch.java @@ -0,0 +1,126 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.IOUtil; +import java.io.IOException; +import java.net.URLEncoder; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * + * @author aNNiMON + */ +public class LyricSearch extends Command { + + @Override + protected String[] command() { + return new String[] { "текст" }; + } + + @Override + protected boolean execute(String message, String userName) throws IOException { + message = message.substring(words[0].length()); + String[] title = message.split(" - | — "); + if (title.length > 1) { + send(userName + ", \n" + getText(title[0].trim(), title[1].trim())); + return true; + } + return false; + } + + private static String ucwords(String str) { + if (str == null || str.isEmpty()) return ""; + + str = str.trim(); + if (str.contains(" ")) { + final StringBuilder sb = new StringBuilder(); + while (str.contains(" ")) { + String tmp = str.substring(0, str.indexOf(" ")); + if (!tmp.trim().isEmpty()) { + sb.append(Character.toUpperCase(tmp.charAt(0))).append(tmp.substring(1, tmp.length()).toLowerCase()).append(' '); + } + str = str.substring(str.indexOf(" ") + 1, str.length()); + } + sb.append(Character.toUpperCase(str.charAt(0))).append(str.substring(1, str.length()).toLowerCase()); + str = sb.toString().trim(); + } else { + str = Character.toUpperCase(str.charAt(0)) + str.substring(1, str.length()).toLowerCase(); + } + return str; + } + + private static String getTextWithCorrectArtistName(String artist, String title) throws JSONException, IOException { + try { + String data = IOUtil.getWithUserAgent("https://lyrics.wikia.com/api.php?action=query&prop=revisions&rvprop=content&format=json&titles=" + URLEncoder.encode(artist, "UTF-8")); + json = new JSONObject(data); + String name = json.getJSONObject("query").getJSONObject("pages").names().get(0).toString(); + JSONObject r = new JSONObject(json.getJSONObject("query").getJSONObject("pages").getJSONObject(name).getJSONArray("revisions").get(0).toString()); + artist = r.get("*").toString().trim(); + + if (artist.toUpperCase().contains("#REDIRECT")) { + artist = artist.substring(artist.indexOf("[[") + 2, artist.indexOf("]]")); + } + System.out.println(artist); + } catch (IOException ex) { + return ""; + } + + return giveText(artist, title); + + } + + private static String getRedirect(String redirect) throws JSONException { + json = new JSONObject(redirect); + String name = json.getJSONObject("query").getJSONObject("pages").names().get(0).toString(); + JSONObject r = new JSONObject(json.getJSONObject("query").getJSONObject("pages").getJSONObject(name).getJSONArray("revisions").get(0).toString()); + name = r.get("*").toString().trim(); + if (!name.equals("")) { + name = name.substring(name.indexOf("[[") + 2, name.indexOf("]]")); + try { + return IOUtil.getWithUserAgent("https://lyrics.wikia.com/api.php?action=query&prop=revisions&rvprop=content&format=json&titles=" + URLEncoder.encode(name, "UTF-8")); + } catch (Exception ex) { + return ""; + } + } else { + return redirect; + } + } + + private static String extractText(String data) throws JSONException { + if (!data.contains("")) return ""; + json = new JSONObject(data); + String name = json.getJSONObject("query").getJSONObject("pages").names().get(0).toString(); + JSONObject r = new JSONObject(json.getJSONObject("query").getJSONObject("pages").getJSONObject(name).getJSONArray("revisions").get(0).toString()); + data = r.get("*").toString().trim(); + data = data.substring(data.indexOf("") + 8, data.indexOf("")); + return data.trim(); + + } + + private static String giveText(String artist, String title) throws IOException, JSONException { + String data = IOUtil.getWithUserAgent("https://lyrics.wikia.com/api.php?action=query&prop=revisions&rvprop=content&format=json&titles=" + URLEncoder.encode(artist, "UTF-8") + ':' + URLEncoder.encode(title, "UTF-8")); + String text = extractText(data); + if (data.contains("#REDIRECT [[")) { + text = getRedirect(data); + text = extractText(text); + } + return text; + } + + public static String getText(String artist, String title) { + try { + artist = ucwords(artist); + title = ucwords(title); + String text = giveText(artist, title); + + if (text.isEmpty()) { + text = getTextWithCorrectArtistName(artist, title); + } + return (text.trim().equals("")) ? "я таких песен не знаю" : text; + } catch (Exception jex) { + return "я таких песен не знаю"; + } + + } + private static JSONObject json = null; +} diff --git a/src/holdfast/samobot/commands/Oboobs.java b/src/holdfast/samobot/commands/Oboobs.java new file mode 100644 index 0000000..395e50a --- /dev/null +++ b/src/holdfast/samobot/commands/Oboobs.java @@ -0,0 +1,37 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.IOUtil; +import holdfast.samobot.VK; +import java.io.IOException; +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * + * @author aNNiMON + */ +public class Oboobs extends Command { + + private static final String API_URL = "http://api.oboobs.ru/noise"; + private static final String URL = "http://media.oboobs.ru/"; + + @Override + protected String[] command() { + return new String[] { "boobs", "сиськи", "сиси" }; + } + + @Override + protected boolean execute(String message, String userName) throws IOException { + String raw = IOUtil.get(API_URL); + JSONObject obj = new JSONArray(raw).getJSONObject(0); + + String photo = VK.uploadPhoto(URL + obj.getString("preview")); + if (photo == null) { + send(userName + ", не смогла :("); + } else { + send(userName, photo); + } + return true; + } + +} diff --git a/src/holdfast/samobot/commands/Obutts.java b/src/holdfast/samobot/commands/Obutts.java new file mode 100644 index 0000000..3a16bc4 --- /dev/null +++ b/src/holdfast/samobot/commands/Obutts.java @@ -0,0 +1,37 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.IOUtil; +import holdfast.samobot.VK; +import java.io.IOException; +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * + * @author aNNiMON + */ +public class Obutts extends Command { + + private static final String API_URL = "http://api.obutts.ru/noise"; + private static final String URL = "http://media.obutts.ru/"; + + @Override + protected String[] command() { + return new String[] { "butts", "попа" }; + } + + @Override + protected boolean execute(String message, String userName) throws IOException { + String raw = IOUtil.get(API_URL); + JSONObject obj = new JSONArray(raw).getJSONObject(0); + + String photo = VK.uploadPhoto(URL + obj.getString("preview")); + if (photo == null) { + send(userName + ", не смогла :("); + } else { + send(userName, photo); + } + return true; + } + +} diff --git a/src/holdfast/samobot/commands/PhotoSearch.java b/src/holdfast/samobot/commands/PhotoSearch.java new file mode 100644 index 0000000..d9a8824 --- /dev/null +++ b/src/holdfast/samobot/commands/PhotoSearch.java @@ -0,0 +1,41 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.Util; +import holdfast.samobot.VK; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author aNNiMON + */ +public class PhotoSearch extends Command { + + private static final Map SEARCH_KEYWORDS; + + static { + SEARCH_KEYWORDS = new HashMap<>(); + SEARCH_KEYWORDS.put("пони", "MLP art"); + SEARCH_KEYWORDS.put("неко", "неко кошкодевочки"); + SEARCH_KEYWORDS.put("пеппа", "peppa pig"); + SEARCH_KEYWORDS.put("аниме", "anime"); + } + + @Override + protected String[] command() { + return new String[] { "пони", "неко", "пеппа", "аниме" }; + } + + @Override + public boolean execute(String message, String userName) throws IOException { + System.out.println("Photo: " + message); + String photo = VK.getRandomPhoto(SEARCH_KEYWORDS.get(message), 1, Util.random(2900)); + if (photo == null) { + send(userName + ", ничего не найдено :("); + } else { + send(userName, photo); + } + return true; + } +} diff --git a/src/holdfast/samobot/commands/Pirozhki.java b/src/holdfast/samobot/commands/Pirozhki.java new file mode 100644 index 0000000..9f2b113 --- /dev/null +++ b/src/holdfast/samobot/commands/Pirozhki.java @@ -0,0 +1,34 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.IOUtil; +import java.io.IOException; + +/** + * + * @author aNNiMON + */ +public class Pirozhki extends Command { + + private static final String URL = "http://perashki.ru/Piro/Random/"; + private static final String TAG_BEGIN = "
"; + private static final String TAG_END = "
"; + + @Override + protected String[] command() { + return new String[] { "пирожки" }; + } + + @Override + protected boolean execute(String message, String userName) throws IOException { + String rawHtml = IOUtil.get(URL); + + int startIndex = rawHtml.indexOf(TAG_BEGIN) + TAG_BEGIN.length(); + int endIndex = rawHtml.indexOf(TAG_END, startIndex); + if (endIndex <= startIndex || endIndex <= 0) return false; + + String text = rawHtml.substring(startIndex, endIndex); + send(text); + return true; + } + +} diff --git a/src/holdfast/samobot/commands/Possibility.java b/src/holdfast/samobot/commands/Possibility.java new file mode 100644 index 0000000..66b2e2d --- /dev/null +++ b/src/holdfast/samobot/commands/Possibility.java @@ -0,0 +1,28 @@ +package holdfast.samobot.commands; + +import java.io.IOException; +import java.util.Calendar; + +/** + * + * @author aNNiMON + */ +public final class Possibility extends Command { + + @Override + protected String[] command() { + return new String[] { "инфа" }; + } + + @Override + protected boolean execute(String message, String userName) throws IOException { + message = message.substring(words[0].length()); + + int salt = Calendar.getInstance().get(Calendar.DAY_OF_MONTH) + + Calendar.getInstance().get(Calendar.HOUR); + int percentage = (message.toLowerCase() + salt).hashCode() % 100; + + send(userName + ", инфа " + Math.abs(percentage) + "%"); + return true; + } +} diff --git a/src/holdfast/samobot/commands/SendMessage.java b/src/holdfast/samobot/commands/SendMessage.java new file mode 100644 index 0000000..a78ccf7 --- /dev/null +++ b/src/holdfast/samobot/commands/SendMessage.java @@ -0,0 +1,72 @@ +package holdfast.samobot.commands; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author aNNiMON + */ +public class SendMessage extends Command { + + private static final int ADMIN_ID = 18800138; + private static final Map namesId; + + static { + namesId = new HashMap<>(); + namesId.put("эд", 152102072); + namesId.put("эдуард", 152102072); + namesId.put("слава", 312584128); + namesId.put("славка", 312584128); + namesId.put("анна", 203160951); + namesId.put("азат", 203160951); + namesId.put("кальтер", 203160951); + namesId.put("федя", 139948561); + namesId.put("витя", 18800138); + namesId.put("виктор", 18800138); + namesId.put("сергей", 296528453); + namesId.put("листок", 296528453); + namesId.put("листочек", 296528453); + namesId.put("виталя", 186862489); + namesId.put("игорь", 28420405); + namesId.put("ксакеп", 28420405); + namesId.put("кса", 28420405); + namesId.put("конфа", 1); + namesId.put("ботоконфа", 2); + } + + @Override + protected String[] command() { + return new String[] { "отправь" }; + } + + @Override + public boolean execute(String message, String userName) throws IOException { +// if (toUser && chatId != 18800138) return false; + + message = message.substring(words[0].length()); // cut command + final String lastWord = words[words.length - 1].trim(); + message = message.substring(0, message.length() - lastWord.length() - 1); // cut id + + int id; + try { + id = Integer.parseInt(lastWord); + } catch (Exception ex) { + if (!namesId.containsKey(lastWord.toLowerCase())) return false; + id = namesId.get(lastWord.toLowerCase()); + } + + // Разрешаем отправлять только заданным в namesId аккаунтам + // Исключение - админ + if ( (chatId != ADMIN_ID) && !namesId.containsValue(id)) return false; + + toUser = (id > 10000); + chatId = id; + forward = 0; + + send(message); + return true; + } + +} diff --git a/src/holdfast/samobot/commands/Stats.java b/src/holdfast/samobot/commands/Stats.java new file mode 100644 index 0000000..fbfdec8 --- /dev/null +++ b/src/holdfast/samobot/commands/Stats.java @@ -0,0 +1,23 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.StatisticsProcessor; +import holdfast.samobot.Util; +import java.io.IOException; + +/** + * + * @author aNNiMON + */ +public final class Stats extends Command { + + @Override + protected String[] command() { + return new String[] { "стата", "статистика" }; + } + + @Override + protected boolean execute(String message, String userName) throws IOException { + send(StatisticsProcessor.asString()); + return true; + } +} diff --git a/src/holdfast/samobot/commands/VkAudioSearch.java b/src/holdfast/samobot/commands/VkAudioSearch.java new file mode 100644 index 0000000..d8a68b4 --- /dev/null +++ b/src/holdfast/samobot/commands/VkAudioSearch.java @@ -0,0 +1,32 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.Util; +import holdfast.samobot.VK; +import java.io.IOException; + +/** + * + * @author aNNiMON + */ +public class VkAudioSearch extends Command { + + + @Override + protected String[] command() { + return new String[] { "аудио", "музыка", "песню" }; + } + + @Override + public boolean execute(String message, String userName) throws IOException { + final String query = message.substring(words[0].length()); + + String audio = VK.getRandomAudio(query, 10); + if (audio == null) { + send(userName + ", ничего не найдено :("); + } else { + send(userName, audio); + } + return true; + } + +} diff --git a/src/holdfast/samobot/commands/VkPhotoSearch.java b/src/holdfast/samobot/commands/VkPhotoSearch.java new file mode 100644 index 0000000..b6952ac --- /dev/null +++ b/src/holdfast/samobot/commands/VkPhotoSearch.java @@ -0,0 +1,31 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.Util; +import holdfast.samobot.VK; +import java.io.IOException; + +/** + * + * @author aNNiMON + */ +public class VkPhotoSearch extends Command { + + @Override + protected String[] command() { + return new String[] { "вкфото" }; + } + + @Override + public boolean execute(String message, String userName) throws IOException { + final String query = message.substring(words[0].length()); + + String photo = VK.getRandomPhoto(query, 1, Util.random(100)); + if (photo == null) { + send(userName + ", ничего не найдено :("); + } else { + send(userName, photo); + } + return true; + } + +} diff --git a/src/holdfast/samobot/commands/VkVideoSearch.java b/src/holdfast/samobot/commands/VkVideoSearch.java new file mode 100644 index 0000000..3916ab6 --- /dev/null +++ b/src/holdfast/samobot/commands/VkVideoSearch.java @@ -0,0 +1,31 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.VK; +import java.io.IOException; + +/** + * + * @author aNNiMON + */ +public class VkVideoSearch extends Command { + + + @Override + protected String[] command() { + return new String[] { "видео", "видос" }; + } + + @Override + public boolean execute(String message, String userName) throws IOException { + final String query = message.substring(words[0].length()); + + String video = VK.getRandomVideo(query, 10); + if (video == null) { + send(userName + ", ничего не найдено :("); + } else { + send(userName, video); + } + return true; + } + +} diff --git a/src/holdfast/samobot/commands/When.java b/src/holdfast/samobot/commands/When.java new file mode 100644 index 0000000..379493c --- /dev/null +++ b/src/holdfast/samobot/commands/When.java @@ -0,0 +1,55 @@ +package holdfast.samobot.commands; + +import holdfast.samobot.Util; +import java.io.IOException; +import java.time.LocalDate; +import java.util.Calendar; +import java.util.Random; + +/** + * + * @author aNNiMON + */ +public final class When extends Command { + + @Override + protected String[] command() { + return new String[] { "когда" }; + } + + @Override + protected boolean execute(String message, String userName) throws IOException { + message = message.substring(words[0].length()); + if (!message.trim().endsWith("?")) return false; + + int salt = Calendar.getInstance().get(Calendar.DAY_OF_MONTH) + + Calendar.getInstance().get(Calendar.HOUR); + int seed = (message.toLowerCase() + salt).hashCode(); + + send(userName + ", " + getDateMessage(seed)); + return true; + } + + private String getDateMessage(int seed) { + final Random random = new Random(seed); + switch (random.nextInt(5)) { + case 0: + LocalDate date = LocalDate.now(); + date = date.minusDays(random.nextInt(2000)); + return String.format("Это событие уже было %s года.", Util.dateToString(date)); + case 1: + return "Через " + (4+random.nextInt(15)) + " часов."; + case 2: + return "Через " + (1+random.nextInt(40)) + " дней."; + case 3: + return "Через " + (1+random.nextInt(12)) + " месяцев."; + case 4: + date = LocalDate.now(); + date = date.plusDays(random.nextInt(2000)); + return String.format("Это событие произойдет %s года.", Util.dateToString(date)); + default: + return "Этого никогда не будет."; + } + } + +} diff --git a/src/org/json/JSONArray.java b/src/org/json/JSONArray.java new file mode 100644 index 0000000..b1334db --- /dev/null +++ b/src/org/json/JSONArray.java @@ -0,0 +1,1061 @@ +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. + */ + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Array; +import java.math.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +/** + * A JSONArray is an ordered sequence of values. Its external text form is a + * string wrapped in square brackets with commas separating the values. The + * internal form is an object having get and opt + * methods for accessing the values by index, and put methods for + * adding or replacing values. The values can be any of these types: + * Boolean, JSONArray, JSONObject, + * Number, String, or the + * JSONObject.NULL object. + *

+ * The constructor can convert a JSON text into a Java object. The + * toString method converts to JSON text. + *

+ * A get method returns a value if one can be found, and throws an + * exception if one cannot be found. An opt method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. + *

+ * The generic get() and opt() methods return an + * object which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type + * coercion for you. + *

+ * The texts produced by the toString methods strictly conform to + * JSON syntax rules. The constructors are more forgiving in the texts they will + * accept: + *

    + *
  • An extra , (comma) may appear just + * before the closing bracket.
  • + *
  • The null value will be inserted when there is , + *  (comma) elision.
  • + *
  • Strings may be quoted with ' (single + * quote).
  • + *
  • Strings do not need to be quoted at all if they do not begin with a quote + * or single quote, and if they do not contain leading or trailing spaces, and + * if they do not contain any of these characters: + * { } [ ] / \ : , # and if they do not look like numbers and + * if they are not the reserved words true, false, or + * null.
  • + *
+ * + * @author JSON.org + * @version 2015-07-06 + */ +public class JSONArray implements Iterable { + + /** + * The arrayList where the JSONArray's properties are kept. + */ + private final ArrayList myArrayList; + + /** + * Construct an empty JSONArray. + */ + public JSONArray() { + this.myArrayList = new ArrayList(); + } + + /** + * Construct a JSONArray from a JSONTokener. + * + * @param x + * A JSONTokener + * @throws JSONException + * If there is a syntax error. + */ + public JSONArray(JSONTokener x) throws JSONException { + this(); + if (x.nextClean() != '[') { + throw x.syntaxError("A JSONArray text must start with '['"); + } + if (x.nextClean() != ']') { + x.back(); + for (;;) { + if (x.nextClean() == ',') { + x.back(); + this.myArrayList.add(JSONObject.NULL); + } else { + x.back(); + this.myArrayList.add(x.nextValue()); + } + switch (x.nextClean()) { + case ',': + if (x.nextClean() == ']') { + return; + } + x.back(); + break; + case ']': + return; + default: + throw x.syntaxError("Expected a ',' or ']'"); + } + } + } + } + + /** + * Construct a JSONArray from a source JSON text. + * + * @param source + * A string that begins with [ (left + * bracket) and ends with ] + *  (right bracket). + * @throws JSONException + * If there is a syntax error. + */ + public JSONArray(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONArray from a Collection. + * + * @param collection + * A Collection. + */ + public JSONArray(Collection collection) { + this.myArrayList = new ArrayList(); + if (collection != null) { + Iterator iter = collection.iterator(); + while (iter.hasNext()) { + this.myArrayList.add(JSONObject.wrap(iter.next())); + } + } + } + + /** + * Construct a JSONArray from an array + * + * @throws JSONException + * If not an array. + */ + public JSONArray(Object array) throws JSONException { + this(); + if (array.getClass().isArray()) { + int length = Array.getLength(array); + for (int i = 0; i < length; i += 1) { + this.put(JSONObject.wrap(Array.get(array, i))); + } + } else { + throw new JSONException( + "JSONArray initial value should be a string or collection or array."); + } + } + + @Override + public Iterator iterator() { + return myArrayList.iterator(); + } + + /** + * Get the object value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return An object value. + * @throws JSONException + * If there is no value for the index. + */ + public Object get(int index) throws JSONException { + Object object = this.opt(index); + if (object == null) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with an index. The string values "true" + * and "false" are converted to boolean. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The truth. + * @throws JSONException + * If there is no value for the index or if the value is not + * convertible to boolean. + */ + public boolean getBoolean(int index) throws JSONException { + Object object = this.get(index); + if (object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONArray[" + index + "] is not a boolean."); + } + + /** + * Get the double value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value cannot be converted + * to a number. + */ + public double getDouble(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).doubleValue() + : Double.parseDouble((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the BigDecimal value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value cannot be converted + * to a BigDecimal. + */ + public BigDecimal getBigDecimal (int index) throws JSONException { + Object object = this.get(index); + try { + return new BigDecimal(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + + "] could not convert to BigDecimal."); + } + } + + /** + * Get the BigInteger value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value cannot be converted + * to a BigInteger. + */ + public BigInteger getBigInteger (int index) throws JSONException { + Object object = this.get(index); + try { + return new BigInteger(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + + "] could not convert to BigInteger."); + } + } + + /** + * Get the int value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value is not a number. + */ + public int getInt(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the JSONArray associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A JSONArray value. + * @throws JSONException + * If there is no value for the index. or if the value is not a + * JSONArray + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONArray."); + } + + /** + * Get the JSONObject associated with an index. + * + * @param index + * subscript + * @return A JSONObject value. + * @throws JSONException + * If there is no value for the index or if the value is not a + * JSONObject + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONObject."); + } + + /** + * Get the long value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value cannot be converted + * to a number. + */ + public long getLong(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the string associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A string value. + * @throws JSONException + * If there is no string value for the index. + */ + public String getString(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONArray[" + index + "] not a string."); + } + + /** + * Determine if the value is null. + * + * @param index + * The index must be between 0 and length() - 1. + * @return true if the value at the index is null, or if there is no value. + */ + public boolean isNull(int index) { + return JSONObject.NULL.equals(this.opt(index)); + } + + /** + * Make a string from the contents of this JSONArray. The + * separator string is inserted between each element. Warning: + * This method assumes that the data structure is acyclical. + * + * @param separator + * A string that will be inserted between the elements. + * @return a string. + * @throws JSONException + * If the array contains an invalid number. + */ + public String join(String separator) throws JSONException { + int len = this.length(); + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < len; i += 1) { + if (i > 0) { + sb.append(separator); + } + sb.append(JSONObject.valueToString(this.myArrayList.get(i))); + } + return sb.toString(); + } + + /** + * Get the number of elements in the JSONArray, included nulls. + * + * @return The length (or size). + */ + public int length() { + return this.myArrayList.size(); + } + + /** + * Get the optional object value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return An object value, or null if there is no object at that index. + */ + public Object opt(int index) { + return (index < 0 || index >= this.length()) ? null : this.myArrayList + .get(index); + } + + /** + * Get the optional boolean value associated with an index. It returns false + * if there is no value at that index, or if the value is not Boolean.TRUE + * or the String "true". + * + * @param index + * The index must be between 0 and length() - 1. + * @return The truth. + */ + public boolean optBoolean(int index) { + return this.optBoolean(index, false); + } + + /** + * Get the optional boolean value associated with an index. It returns the + * defaultValue if there is no value at that index or if it is not a Boolean + * or the String "true" or "false" (case insensitive). + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * A boolean default. + * @return The truth. + */ + public boolean optBoolean(int index, boolean defaultValue) { + try { + return this.getBoolean(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional double value associated with an index. NaN is returned + * if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public double optDouble(int index) { + return this.optDouble(index, Double.NaN); + } + + /** + * Get the optional double value associated with an index. The defaultValue + * is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * subscript + * @param defaultValue + * The default value. + * @return The value. + */ + public double optDouble(int index, double defaultValue) { + try { + return this.getDouble(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional int value associated with an index. Zero is returned if + * there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public int optInt(int index) { + return this.optInt(index, 0); + } + + /** + * Get the optional int value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public int optInt(int index, int defaultValue) { + try { + return this.getInt(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional BigInteger value associated with an index. The + * defaultValue is returned if there is no value for the index, or if the + * value is not a number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public BigInteger optBigInteger(int index, BigInteger defaultValue) { + try { + return this.getBigInteger(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional BigDecimal value associated with an index. The + * defaultValue is returned if there is no value for the index, or if the + * value is not a number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public BigDecimal optBigDecimal(int index, BigDecimal defaultValue) { + try { + return this.getBigDecimal(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional JSONArray associated with an index. + * + * @param index + * subscript + * @return A JSONArray value, or null if the index has no value, or if the + * value is not a JSONArray. + */ + public JSONArray optJSONArray(int index) { + Object o = this.opt(index); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get the optional JSONObject associated with an index. Null is returned if + * the key is not found, or null if the index has no value, or if the value + * is not a JSONObject. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A JSONObject value. + */ + public JSONObject optJSONObject(int index) { + Object o = this.opt(index); + return o instanceof JSONObject ? (JSONObject) o : null; + } + + /** + * Get the optional long value associated with an index. Zero is returned if + * there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public long optLong(int index) { + return this.optLong(index, 0); + } + + /** + * Get the optional long value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public long optLong(int index, long defaultValue) { + try { + return this.getLong(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional string value associated with an index. It returns an + * empty string if there is no value at that index. If the value is not a + * string and is not null, then it is coverted to a string. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A String value. + */ + public String optString(int index) { + return this.optString(index, ""); + } + + /** + * Get the optional string associated with an index. The defaultValue is + * returned if the key is not found. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return A String value. + */ + public String optString(int index, String defaultValue) { + Object object = this.opt(index); + return JSONObject.NULL.equals(object) ? defaultValue : object + .toString(); + } + + /** + * Append a boolean value. This increases the array's length by one. + * + * @param value + * A boolean value. + * @return this. + */ + public JSONArray put(boolean value) { + this.put(value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param value + * A Collection value. + * @return this. + */ + public JSONArray put(Collection value) { + this.put(new JSONArray(value)); + return this; + } + + /** + * Append a double value. This increases the array's length by one. + * + * @param value + * A double value. + * @throws JSONException + * if the value is not finite. + * @return this. + */ + public JSONArray put(double value) throws JSONException { + Double d = new Double(value); + JSONObject.testValidity(d); + this.put(d); + return this; + } + + /** + * Append an int value. This increases the array's length by one. + * + * @param value + * An int value. + * @return this. + */ + public JSONArray put(int value) { + this.put(new Integer(value)); + return this; + } + + /** + * Append an long value. This increases the array's length by one. + * + * @param value + * A long value. + * @return this. + */ + public JSONArray put(long value) { + this.put(new Long(value)); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject which + * is produced from a Map. + * + * @param value + * A Map value. + * @return this. + */ + public JSONArray put(Map value) { + this.put(new JSONObject(value)); + return this; + } + + /** + * Append an object value. This increases the array's length by one. + * + * @param value + * An object value. The value should be a Boolean, Double, + * Integer, JSONArray, JSONObject, Long, or String, or the + * JSONObject.NULL object. + * @return this. + */ + public JSONArray put(Object value) { + this.myArrayList.add(value); + return this; + } + + /** + * Put or replace a boolean value in the JSONArray. If the index is greater + * than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * + * @param index + * The subscript. + * @param value + * A boolean value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, boolean value) throws JSONException { + this.put(index, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param index + * The subscript. + * @param value + * A Collection value. + * @return this. + * @throws JSONException + * If the index is negative or if the value is not finite. + */ + public JSONArray put(int index, Collection value) throws JSONException { + this.put(index, new JSONArray(value)); + return this; + } + + /** + * Put or replace a double value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index + * The subscript. + * @param value + * A double value. + * @return this. + * @throws JSONException + * If the index is negative or if the value is not finite. + */ + public JSONArray put(int index, double value) throws JSONException { + this.put(index, new Double(value)); + return this; + } + + /** + * Put or replace an int value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index + * The subscript. + * @param value + * An int value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, int value) throws JSONException { + this.put(index, new Integer(value)); + return this; + } + + /** + * Put or replace a long value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index + * The subscript. + * @param value + * A long value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, long value) throws JSONException { + this.put(index, new Long(value)); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject that + * is produced from a Map. + * + * @param index + * The subscript. + * @param value + * The Map value. + * @return this. + * @throws JSONException + * If the index is negative or if the the value is an invalid + * number. + */ + public JSONArray put(int index, Map value) throws JSONException { + this.put(index, new JSONObject(value)); + return this; + } + + /** + * Put or replace an object value in the JSONArray. If the index is greater + * than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * + * @param index + * The subscript. + * @param value + * The value to put into the array. The value should be a + * Boolean, Double, Integer, JSONArray, JSONObject, Long, or + * String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the index is negative or if the the value is an invalid + * number. + */ + public JSONArray put(int index, Object value) throws JSONException { + JSONObject.testValidity(value); + if (index < 0) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + if (index < this.length()) { + this.myArrayList.set(index, value); + } else { + while (index != this.length()) { + this.put(JSONObject.NULL); + } + this.put(value); + } + return this; + } + + /** + * Remove an index and close the hole. + * + * @param index + * The index of the element to be removed. + * @return The value that was associated with the index, or null if there + * was no value. + */ + public Object remove(int index) { + return index >= 0 && index < this.length() + ? this.myArrayList.remove(index) + : null; + } + + /** + * Determine if two JSONArrays are similar. + * They must contain similar sequences. + * + * @param other The other JSONArray + * @return true if they are equal + */ + public boolean similar(Object other) { + if (!(other instanceof JSONArray)) { + return false; + } + int len = this.length(); + if (len != ((JSONArray)other).length()) { + return false; + } + for (int i = 0; i < len; i += 1) { + Object valueThis = this.get(i); + Object valueOther = ((JSONArray)other).get(i); + if (valueThis instanceof JSONObject) { + if (!((JSONObject)valueThis).similar(valueOther)) { + return false; + } + } else if (valueThis instanceof JSONArray) { + if (!((JSONArray)valueThis).similar(valueOther)) { + return false; + } + } else if (!valueThis.equals(valueOther)) { + return false; + } + } + return true; + } + + /** + * Produce a JSONObject by combining a JSONArray of names with the values of + * this JSONArray. + * + * @param names + * A JSONArray containing a list of key strings. These will be + * paired with the values. + * @return A JSONObject, or null if there are no names or if this JSONArray + * has no values. + * @throws JSONException + * If any of the names are null. + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + if (names == null || names.length() == 0 || this.length() == 0) { + return null; + } + JSONObject jo = new JSONObject(); + for (int i = 0; i < names.length(); i += 1) { + jo.put(names.getString(i), this.opt(i)); + } + return jo; + } + + /** + * Make a JSON text of this JSONArray. For compactness, no unnecessary + * whitespace is added. If it is not possible to produce a syntactically + * correct JSON text then null will be returned instead. This could occur if + * the array contains an invalid number. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, transmittable representation of the + * array. + */ + public String toString() { + try { + return this.toString(0); + } catch (Exception e) { + return null; + } + } + + /** + * Make a prettyprinted JSON text of this JSONArray. Warning: This method + * assumes that the data structure is acyclical. + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @return a printable, displayable, transmittable representation of the + * object, beginning with [ (left + * bracket) and ending with ] + *  (right bracket). + * @throws JSONException + */ + public String toString(int indentFactor) throws JSONException { + StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + return this.write(sw, indentFactor, 0).toString(); + } + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @param indent + * The indention of the top level. + * @return The writer. + * @throws JSONException + */ + Writer write(Writer writer, int indentFactor, int indent) + throws JSONException { + try { + boolean commanate = false; + int length = this.length(); + writer.write('['); + + if (length == 1) { + JSONObject.writeValue(writer, this.myArrayList.get(0), + indentFactor, indent); + } else if (length != 0) { + final int newindent = indent + indentFactor; + + for (int i = 0; i < length; i += 1) { + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, newindent); + JSONObject.writeValue(writer, this.myArrayList.get(i), + indentFactor, newindent); + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, indent); + } + writer.write(']'); + return writer; + } catch (IOException e) { + throw new JSONException(e); + } + } +} diff --git a/src/org/json/JSONException.java b/src/org/json/JSONException.java new file mode 100644 index 0000000..6fef519 --- /dev/null +++ b/src/org/json/JSONException.java @@ -0,0 +1,43 @@ +package org.json; + +/** + * The JSONException is thrown by the JSON.org classes when things are amiss. + * + * @author JSON.org + * @version 2014-05-03 + */ +public class JSONException extends RuntimeException { + private static final long serialVersionUID = 0; + private Throwable cause; + + /** + * Constructs a JSONException with an explanatory message. + * + * @param message + * Detail about the reason for the exception. + */ + public JSONException(String message) { + super(message); + } + + /** + * Constructs a new JSONException with the specified cause. + * @param cause The cause. + */ + public JSONException(Throwable cause) { + super(cause.getMessage()); + this.cause = cause; + } + + /** + * Returns the cause of this exception or null if the cause is nonexistent + * or unknown. + * + * @return the cause of this exception or null if the cause is nonexistent + * or unknown. + */ + @Override + public Throwable getCause() { + return this.cause; + } +} diff --git a/src/org/json/JSONObject.java b/src/org/json/JSONObject.java new file mode 100644 index 0000000..34d5c6c --- /dev/null +++ b/src/org/json/JSONObject.java @@ -0,0 +1,1779 @@ +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. + */ + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.math.*; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.ResourceBundle; +import java.util.Set; + +/** + * A JSONObject is an unordered collection of name/value pairs. Its external + * form is a string wrapped in curly braces with colons between the names and + * values, and commas between the values and names. The internal form is an + * object having get and opt methods for accessing + * the values by name, and put methods for adding or replacing + * values by name. The values can be any of these types: Boolean, + * JSONArray, JSONObject, Number, + * String, or the JSONObject.NULL object. A + * JSONObject constructor can be used to convert an external form JSON text + * into an internal form whose values can be retrieved with the + * get and opt methods, or to convert values into a + * JSON text using the put and toString methods. A + * get method returns a value if one can be found, and throws an + * exception if one cannot be found. An opt method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. + *

+ * The generic get() and opt() methods return an + * object, which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type + * coercion for you. The opt methods differ from the get methods in that they + * do not throw. Instead, they return a specified value, such as null. + *

+ * The put methods add or replace values in an object. For + * example, + * + *

+ * myString = new JSONObject()
+ *         .put("JSON", "Hello, World!").toString();
+ * 
+ * + * produces the string {"JSON": "Hello, World"}. + *

+ * The texts produced by the toString methods strictly conform to + * the JSON syntax rules. The constructors are more forgiving in the texts they + * will accept: + *

    + *
  • An extra , (comma) may appear just + * before the closing brace.
  • + *
  • Strings may be quoted with ' (single + * quote).
  • + *
  • Strings do not need to be quoted at all if they do not begin with a + * quote or single quote, and if they do not contain leading or trailing + * spaces, and if they do not contain any of these characters: + * { } [ ] / \ : , # and if they do not look like numbers and + * if they are not the reserved words true, false, + * or null.
  • + *
+ * + * @author JSON.org + * @version 2015-07-06 + */ +public class JSONObject { + /** + * JSONObject.NULL is equivalent to the value that JavaScript calls null, + * whilst Java's null is equivalent to the value that JavaScript calls + * undefined. + */ + private static final class Null { + + /** + * There is only intended to be a single instance of the NULL object, + * so the clone method returns itself. + * + * @return NULL. + */ + @Override + protected final Object clone() { + return this; + } + + /** + * A Null object is equal to the null value and to itself. + * + * @param object + * An object to test for nullness. + * @return true if the object parameter is the JSONObject.NULL object or + * null. + */ + @Override + public boolean equals(Object object) { + return object == null || object == this; + } + + /** + * Get the "null" string value. + * + * @return The string "null". + */ + public String toString() { + return "null"; + } + } + + /** + * The map where the JSONObject's properties are kept. + */ + private final Map map; + + /** + * It is sometimes more convenient and less ambiguous to have a + * NULL object than to use Java's null value. + * JSONObject.NULL.equals(null) returns true. + * JSONObject.NULL.toString() returns "null". + */ + public static final Object NULL = new Null(); + + /** + * Construct an empty JSONObject. + */ + public JSONObject() { + this.map = new HashMap(); + } + + /** + * Construct a JSONObject from a subset of another JSONObject. An array of + * strings is used to identify the keys that should be copied. Missing keys + * are ignored. + * + * @param jo + * A JSONObject. + * @param names + * An array of strings. + * @throws JSONException + * @exception JSONException + * If a value is a non-finite number or if a name is + * duplicated. + */ + public JSONObject(JSONObject jo, String[] names) { + this(); + for (int i = 0; i < names.length; i += 1) { + try { + this.putOnce(names[i], jo.opt(names[i])); + } catch (Exception ignore) { + } + } + } + + /** + * Construct a JSONObject from a JSONTokener. + * + * @param x + * A JSONTokener object containing the source string. + * @throws JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(JSONTokener x) throws JSONException { + this(); + char c; + String key; + + if (x.nextClean() != '{') { + throw x.syntaxError("A JSONObject text must begin with '{'"); + } + for (;;) { + c = x.nextClean(); + switch (c) { + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); + } + +// The key is followed by ':'. + + c = x.nextClean(); + if (c != ':') { + throw x.syntaxError("Expected a ':' after a key"); + } + this.putOnce(key, x.nextValue()); + +// Pairs are separated by ','. + + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + x.back(); + break; + case '}': + return; + default: + throw x.syntaxError("Expected a ',' or '}'"); + } + } + } + + /** + * Construct a JSONObject from a Map. + * + * @param map + * A map object that can be used to initialize the contents of + * the JSONObject. + * @throws JSONException + */ + public JSONObject(Map map) { + this.map = new HashMap(); + if (map != null) { + Iterator> i = map.entrySet().iterator(); + while (i.hasNext()) { + Entry entry = i.next(); + Object value = entry.getValue(); + if (value != null) { + this.map.put(entry.getKey(), wrap(value)); + } + } + } + } + + /** + * Construct a JSONObject from an Object using bean getters. It reflects on + * all of the public methods of the object. For each of the methods with no + * parameters and a name starting with "get" or + * "is" followed by an uppercase letter, the method is invoked, + * and a key and the value returned from the getter method are put into the + * new JSONObject. + * + * The key is formed by removing the "get" or "is" + * prefix. If the second remaining character is not upper case, then the + * first character is converted to lower case. + * + * For example, if an object has a method named "getName", and + * if the result of calling object.getName() is + * "Larry Fine", then the JSONObject will contain + * "name": "Larry Fine". + * + * @param bean + * An object that has getter methods that should be used to make + * a JSONObject. + */ + public JSONObject(Object bean) { + this(); + this.populateMap(bean); + } + + /** + * Construct a JSONObject from an Object, using reflection to find the + * public members. The resulting JSONObject's keys will be the strings from + * the names array, and the values will be the field values associated with + * those keys in the object. If a key is not found or not visible, then it + * will not be copied into the new JSONObject. + * + * @param object + * An object that has fields that should be used to make a + * JSONObject. + * @param names + * An array of strings, the names of the fields to be obtained + * from the object. + */ + public JSONObject(Object object, String names[]) { + this(); + Class c = object.getClass(); + for (int i = 0; i < names.length; i += 1) { + String name = names[i]; + try { + this.putOpt(name, c.getField(name).get(object)); + } catch (Exception ignore) { + } + } + } + + /** + * Construct a JSONObject from a source JSON text string. This is the most + * commonly used JSONObject constructor. + * + * @param source + * A string beginning with { (left + * brace) and ending with } + *  (right brace). + * @exception JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONObject from a ResourceBundle. + * + * @param baseName + * The ResourceBundle base name. + * @param locale + * The Locale to load the ResourceBundle for. + * @throws JSONException + * If any JSONExceptions are detected. + */ + public JSONObject(String baseName, Locale locale) throws JSONException { + this(); + ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale, + Thread.currentThread().getContextClassLoader()); + +// Iterate through the keys in the bundle. + + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + Object key = keys.nextElement(); + if (key != null) { + +// Go through the path, ensuring that there is a nested JSONObject for each +// segment except the last. Add the value using the last segment's name into +// the deepest nested JSONObject. + + String[] path = ((String) key).split("\\."); + int last = path.length - 1; + JSONObject target = this; + for (int i = 0; i < last; i += 1) { + String segment = path[i]; + JSONObject nextTarget = target.optJSONObject(segment); + if (nextTarget == null) { + nextTarget = new JSONObject(); + target.put(segment, nextTarget); + } + target = nextTarget; + } + target.put(path[last], bundle.getString((String) key)); + } + } + } + + /** + * Accumulate values under a key. It is similar to the put method except + * that if there is already an object stored under the key then a JSONArray + * is stored under the key to hold all of the accumulated values. If there + * is already a JSONArray, then the new value is appended to it. In + * contrast, the put method replaces the previous value. + * + * If only one value is accumulated that is not a JSONArray, then the result + * will be the same as using put. But if multiple values are accumulated, + * then the result will be like append. + * + * @param key + * A key string. + * @param value + * An object to be accumulated under the key. + * @return this. + * @throws JSONException + * If the value is an invalid number or if the key is null. + */ + public JSONObject accumulate(String key, Object value) throws JSONException { + testValidity(value); + Object object = this.opt(key); + if (object == null) { + this.put(key, + value instanceof JSONArray ? new JSONArray().put(value) + : value); + } else if (object instanceof JSONArray) { + ((JSONArray) object).put(value); + } else { + this.put(key, new JSONArray().put(object).put(value)); + } + return this; + } + + /** + * Append values to the array under a key. If the key does not exist in the + * JSONObject, then the key is put in the JSONObject with its value being a + * JSONArray containing the value parameter. If the key was already + * associated with a JSONArray, then the value parameter is appended to it. + * + * @param key + * A key string. + * @param value + * An object to be accumulated under the key. + * @return this. + * @throws JSONException + * If the key is null or if the current value associated with + * the key is not a JSONArray. + */ + public JSONObject append(String key, Object value) throws JSONException { + testValidity(value); + Object object = this.opt(key); + if (object == null) { + this.put(key, new JSONArray().put(value)); + } else if (object instanceof JSONArray) { + this.put(key, ((JSONArray) object).put(value)); + } else { + throw new JSONException("JSONObject[" + key + + "] is not a JSONArray."); + } + return this; + } + + /** + * Produce a string from a double. The string "null" will be returned if the + * number is not finite. + * + * @param d + * A double. + * @return A String. + */ + public static String doubleToString(double d) { + if (Double.isInfinite(d) || Double.isNaN(d)) { + return "null"; + } + +// Shave off trailing zeros and decimal point, if possible. + + String string = Double.toString(d); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 + && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get the value object associated with a key. + * + * @param key + * A key string. + * @return The object associated with the key. + * @throws JSONException + * if the key is not found. + */ + public Object get(String key) throws JSONException { + if (key == null) { + throw new JSONException("Null key."); + } + Object object = this.opt(key); + if (object == null) { + throw new JSONException("JSONObject[" + quote(key) + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with a key. + * + * @param key + * A key string. + * @return The truth. + * @throws JSONException + * if the value is not a Boolean or the String "true" or + * "false". + */ + public boolean getBoolean(String key) throws JSONException { + Object object = this.get(key); + if (object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a Boolean."); + } + + /** + * Get the BigInteger value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value cannot + * be converted to BigInteger. + */ + public BigInteger getBigInteger(String key) throws JSONException { + Object object = this.get(key); + try { + return new BigInteger(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] could not be converted to BigInteger."); + } + } + + /** + * Get the BigDecimal value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value + * cannot be converted to BigDecimal. + */ + public BigDecimal getBigDecimal(String key) throws JSONException { + Object object = this.get(key); + try { + return new BigDecimal(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] could not be converted to BigDecimal."); + } + } + + /** + * Get the double value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public double getDouble(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).doubleValue() + : Double.parseDouble((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a number."); + } + } + + /** + * Get the int value associated with a key. + * + * @param key + * A key string. + * @return The integer value. + * @throws JSONException + * if the key is not found or if the value cannot be converted + * to an integer. + */ + public int getInt(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not an int."); + } + } + + /** + * Get the JSONArray value associated with a key. + * + * @param key + * A key string. + * @return A JSONArray which is the value. + * @throws JSONException + * if the key is not found or if the value is not a JSONArray. + */ + public JSONArray getJSONArray(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a JSONArray."); + } + + /** + * Get the JSONObject value associated with a key. + * + * @param key + * A key string. + * @return A JSONObject which is the value. + * @throws JSONException + * if the key is not found or if the value is not a JSONObject. + */ + public JSONObject getJSONObject(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a JSONObject."); + } + + /** + * Get the long value associated with a key. + * + * @param key + * A key string. + * @return The long value. + * @throws JSONException + * if the key is not found or if the value cannot be converted + * to a long. + */ + public long getLong(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a long."); + } + } + + /** + * Get an array of field names from a JSONObject. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(JSONObject jo) { + int length = jo.length(); + if (length == 0) { + return null; + } + Iterator iterator = jo.keys(); + String[] names = new String[length]; + int i = 0; + while (iterator.hasNext()) { + names[i] = iterator.next(); + i += 1; + } + return names; + } + + /** + * Get an array of field names from an Object. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(Object object) { + if (object == null) { + return null; + } + Class klass = object.getClass(); + Field[] fields = klass.getFields(); + int length = fields.length; + if (length == 0) { + return null; + } + String[] names = new String[length]; + for (int i = 0; i < length; i += 1) { + names[i] = fields[i].getName(); + } + return names; + } + + /** + * Get the string associated with a key. + * + * @param key + * A key string. + * @return A string which is the value. + * @throws JSONException + * if there is no string value for the key. + */ + public String getString(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] not a string."); + } + + /** + * Determine if the JSONObject contains a specific key. + * + * @param key + * A key string. + * @return true if the key exists in the JSONObject. + */ + public boolean has(String key) { + return this.map.containsKey(key); + } + + /** + * Increment a property of a JSONObject. If there is no such property, + * create one with a value of 1. If there is such a property, and if it is + * an Integer, Long, Double, or Float, then add one to it. + * + * @param key + * A key string. + * @return this. + * @throws JSONException + * If there is already a property with this name that is not an + * Integer, Long, Double, or Float. + */ + public JSONObject increment(String key) throws JSONException { + Object value = this.opt(key); + if (value == null) { + this.put(key, 1); + } else if (value instanceof BigInteger) { + this.put(key, ((BigInteger)value).add(BigInteger.ONE)); + } else if (value instanceof BigDecimal) { + this.put(key, ((BigDecimal)value).add(BigDecimal.ONE)); + } else if (value instanceof Integer) { + this.put(key, (Integer) value + 1); + } else if (value instanceof Long) { + this.put(key, (Long) value + 1); + } else if (value instanceof Double) { + this.put(key, (Double) value + 1); + } else if (value instanceof Float) { + this.put(key, (Float) value + 1); + } else { + throw new JSONException("Unable to increment [" + quote(key) + "]."); + } + return this; + } + + /** + * Determine if the value associated with the key is null or if there is no + * value. + * + * @param key + * A key string. + * @return true if there is no value associated with the key or if the value + * is the JSONObject.NULL object. + */ + public boolean isNull(String key) { + return JSONObject.NULL.equals(this.opt(key)); + } + + /** + * Get an enumeration of the keys of the JSONObject. + * + * @return An iterator of the keys. + */ + public Iterator keys() { + return this.keySet().iterator(); + } + + /** + * Get a set of keys of the JSONObject. + * + * @return A keySet. + */ + public Set keySet() { + return this.map.keySet(); + } + + /** + * Get the number of keys stored in the JSONObject. + * + * @return The number of keys in the JSONObject. + */ + public int length() { + return this.map.size(); + } + + /** + * Produce a JSONArray containing the names of the elements of this + * JSONObject. + * + * @return A JSONArray containing the key strings, or null if the JSONObject + * is empty. + */ + public JSONArray names() { + JSONArray ja = new JSONArray(); + Iterator keys = this.keys(); + while (keys.hasNext()) { + ja.put(keys.next()); + } + return ja.length() == 0 ? null : ja; + } + + /** + * Produce a string from a Number. + * + * @param number + * A Number + * @return A String. + * @throws JSONException + * If n is a non-finite number. + */ + public static String numberToString(Number number) throws JSONException { + if (number == null) { + throw new JSONException("Null pointer"); + } + testValidity(number); + +// Shave off trailing zeros and decimal point, if possible. + + String string = number.toString(); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 + && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get an optional value associated with a key. + * + * @param key + * A key string. + * @return An object which is the value, or null if there is no value. + */ + public Object opt(String key) { + return key == null ? null : this.map.get(key); + } + + /** + * Get an optional boolean associated with a key. It returns false if there + * is no such key, or if the value is not Boolean.TRUE or the String "true". + * + * @param key + * A key string. + * @return The truth. + */ + public boolean optBoolean(String key) { + return this.optBoolean(key, false); + } + + /** + * Get an optional boolean associated with a key. It returns the + * defaultValue if there is no such key, or if it is not a Boolean or the + * String "true" or "false" (case insensitive). + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return The truth. + */ + public boolean optBoolean(String key, boolean defaultValue) { + try { + return this.getBoolean(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional double associated with a key, or NaN if there is no such + * key or if its value is not a number. If the value is a string, an attempt + * will be made to evaluate it as a number. + * + * @param key + * A string which is the key. + * @return An object which is the value. + */ + public double optDouble(String key) { + return this.optDouble(key, Double.NaN); + } + + /** + * Get an optional BigInteger associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public BigInteger optBigInteger(String key, BigInteger defaultValue) { + try { + return this.getBigInteger(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional BigDecimal associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) { + try { + return this.getBigDecimal(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional double associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public double optDouble(String key, double defaultValue) { + try { + return this.getDouble(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional int value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public int optInt(String key) { + return this.optInt(key, 0); + } + + /** + * Get an optional int value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public int optInt(String key, int defaultValue) { + try { + return this.getInt(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional JSONArray associated with a key. It returns null if there + * is no such key, or if its value is not a JSONArray. + * + * @param key + * A key string. + * @return A JSONArray which is the value. + */ + public JSONArray optJSONArray(String key) { + Object o = this.opt(key); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get an optional JSONObject associated with a key. It returns null if + * there is no such key, or if its value is not a JSONObject. + * + * @param key + * A key string. + * @return A JSONObject which is the value. + */ + public JSONObject optJSONObject(String key) { + Object object = this.opt(key); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Get an optional long value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public long optLong(String key) { + return this.optLong(key, 0); + } + + /** + * Get an optional long value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public long optLong(String key, long defaultValue) { + try { + return this.getLong(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional string associated with a key. It returns an empty string + * if there is no such key. If the value is not a string and is not null, + * then it is converted to a string. + * + * @param key + * A key string. + * @return A string which is the value. + */ + public String optString(String key) { + return this.optString(key, ""); + } + + /** + * Get an optional string associated with a key. It returns the defaultValue + * if there is no such key. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return A string which is the value. + */ + public String optString(String key, String defaultValue) { + Object object = this.opt(key); + return NULL.equals(object) ? defaultValue : object.toString(); + } + + private void populateMap(Object bean) { + Class klass = bean.getClass(); + +// If klass is a System class then set includeSuperClass to false. + + boolean includeSuperClass = klass.getClassLoader() != null; + + Method[] methods = includeSuperClass ? klass.getMethods() : klass + .getDeclaredMethods(); + for (int i = 0; i < methods.length; i += 1) { + try { + Method method = methods[i]; + if (Modifier.isPublic(method.getModifiers())) { + String name = method.getName(); + String key = ""; + if (name.startsWith("get")) { + if ("getClass".equals(name) + || "getDeclaringClass".equals(name)) { + key = ""; + } else { + key = name.substring(3); + } + } else if (name.startsWith("is")) { + key = name.substring(2); + } + if (key.length() > 0 + && Character.isUpperCase(key.charAt(0)) + && method.getParameterTypes().length == 0) { + if (key.length() == 1) { + key = key.toLowerCase(); + } else if (!Character.isUpperCase(key.charAt(1))) { + key = key.substring(0, 1).toLowerCase() + + key.substring(1); + } + + Object result = method.invoke(bean, (Object[]) null); + if (result != null) { + this.map.put(key, wrap(result)); + } + } + } + } catch (Exception ignore) { + } + } + } + + /** + * Put a key/boolean pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A boolean which is the value. + * @return this. + * @throws JSONException + * If the key is null. + */ + public JSONObject put(String key, boolean value) throws JSONException { + this.put(key, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONArray which is produced from a Collection. + * + * @param key + * A key string. + * @param value + * A Collection value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Collection value) throws JSONException { + this.put(key, new JSONArray(value)); + return this; + } + + /** + * Put a key/double pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A double which is the value. + * @return this. + * @throws JSONException + * If the key is null or if the number is invalid. + */ + public JSONObject put(String key, double value) throws JSONException { + this.put(key, new Double(value)); + return this; + } + + /** + * Put a key/int pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * An int which is the value. + * @return this. + * @throws JSONException + * If the key is null. + */ + public JSONObject put(String key, int value) throws JSONException { + this.put(key, new Integer(value)); + return this; + } + + /** + * Put a key/long pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A long which is the value. + * @return this. + * @throws JSONException + * If the key is null. + */ + public JSONObject put(String key, long value) throws JSONException { + this.put(key, new Long(value)); + return this; + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONObject which is produced from a Map. + * + * @param key + * A key string. + * @param value + * A Map value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Map value) throws JSONException { + this.put(key, new JSONObject(value)); + return this; + } + + /** + * Put a key/value pair in the JSONObject. If the value is null, then the + * key will be removed from the JSONObject if it is present. + * + * @param key + * A key string. + * @param value + * An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, + * String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the value is non-finite number or if the key is null. + */ + public JSONObject put(String key, Object value) throws JSONException { + if (key == null) { + throw new NullPointerException("Null key."); + } + if (value != null) { + testValidity(value); + this.map.put(key, value); + } else { + this.remove(key); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null, and only if there is not already a member with that + * name. + * + * @param key string + * @param value object + * @return this. + * @throws JSONException + * if the key is a duplicate + */ + public JSONObject putOnce(String key, Object value) throws JSONException { + if (key != null && value != null) { + if (this.opt(key) != null) { + throw new JSONException("Duplicate key \"" + key + "\""); + } + this.put(key, value); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null. + * + * @param key + * A key string. + * @param value + * An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, + * String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the value is a non-finite number. + */ + public JSONObject putOpt(String key, Object value) throws JSONException { + if (key != null && value != null) { + this.put(key, value); + } + return this; + } + + /** + * Produce a string in double quotes with backslash sequences in all the + * right places. A backslash will be inserted within = '\u0080' && c < '\u00a0') + || (c >= '\u2000' && c < '\u2100')) { + w.write("\\u"); + hhhh = Integer.toHexString(c); + w.write("0000", 0, 4 - hhhh.length()); + w.write(hhhh); + } else { + w.write(c); + } + } + } + w.write('"'); + return w; + } + + /** + * Remove a name and its value, if present. + * + * @param key + * The name to be removed. + * @return The value that was associated with the name, or null if there was + * no value. + */ + public Object remove(String key) { + return this.map.remove(key); + } + + /** + * Determine if two JSONObjects are similar. + * They must contain the same set of names which must be associated with + * similar values. + * + * @param other The other JSONObject + * @return true if they are equal + */ + public boolean similar(Object other) { + try { + if (!(other instanceof JSONObject)) { + return false; + } + Set set = this.keySet(); + if (!set.equals(((JSONObject)other).keySet())) { + return false; + } + Iterator iterator = set.iterator(); + while (iterator.hasNext()) { + String name = iterator.next(); + Object valueThis = this.get(name); + Object valueOther = ((JSONObject)other).get(name); + if (valueThis instanceof JSONObject) { + if (!((JSONObject)valueThis).similar(valueOther)) { + return false; + } + } else if (valueThis instanceof JSONArray) { + if (!((JSONArray)valueThis).similar(valueOther)) { + return false; + } + } else if (!valueThis.equals(valueOther)) { + return false; + } + } + return true; + } catch (Throwable exception) { + return false; + } + } + + /** + * Try to convert a string into a number, boolean, or null. If the string + * can't be converted, return the string. + * + * @param string + * A String. + * @return A simple JSON value. + */ + public static Object stringToValue(String string) { + Double d; + if (string.equals("")) { + return string; + } + if (string.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (string.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + if (string.equalsIgnoreCase("null")) { + return JSONObject.NULL; + } + + /* + * If it might be a number, try converting it. If a number cannot be + * produced, then the value will just be a string. + */ + + char b = string.charAt(0); + if ((b >= '0' && b <= '9') || b == '-') { + try { + if (string.indexOf('.') > -1 || string.indexOf('e') > -1 + || string.indexOf('E') > -1) { + d = Double.valueOf(string); + if (!d.isInfinite() && !d.isNaN()) { + return d; + } + } else { + Long myLong = new Long(string); + if (string.equals(myLong.toString())) { + if (myLong == myLong.intValue()) { + return myLong.intValue(); + } else { + return myLong; + } + } + } + } catch (Exception ignore) { + } + } + return string; + } + + /** + * Throw an exception if the object is a NaN or infinite number. + * + * @param o + * The object to test. + * @throws JSONException + * If o is a non-finite number. + */ + public static void testValidity(Object o) throws JSONException { + if (o != null) { + if (o instanceof Double) { + if (((Double) o).isInfinite() || ((Double) o).isNaN()) { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } else if (o instanceof Float) { + if (((Float) o).isInfinite() || ((Float) o).isNaN()) { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } + } + } + + /** + * Produce a JSONArray containing the values of the members of this + * JSONObject. + * + * @param names + * A JSONArray containing a list of key strings. This determines + * the sequence of the values in the result. + * @return A JSONArray of values. + * @throws JSONException + * If any of the values are non-finite numbers. + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + JSONArray ja = new JSONArray(); + for (int i = 0; i < names.length(); i += 1) { + ja.put(this.opt(names.getString(i))); + } + return ja; + } + + /** + * Make a JSON text of this JSONObject. For compactness, no whitespace is + * added. If this would not result in a syntactically correct JSON text, + * then null will be returned instead. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with { (left + * brace) and ending with } (right + * brace). + */ + public String toString() { + try { + return this.toString(0); + } catch (Exception e) { + return null; + } + } + + /** + * Make a prettyprinted JSON text of this JSONObject. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with { (left + * brace) and ending with } (right + * brace). + * @throws JSONException + * If the object contains an invalid number. + */ + public String toString(int indentFactor) throws JSONException { + StringWriter w = new StringWriter(); + synchronized (w.getBuffer()) { + return this.write(w, indentFactor, 0).toString(); + } + } + + /** + * 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. + * + *

+ * 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 { (left + * brace) and ending with } (right + * brace). + * @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) { + Object object; + try { + object = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + if (object instanceof String) { + return (String) object; + } + throw new JSONException("Bad value from toJSONString: " + object); + } + if (value instanceof Number) { + return numberToString((Number) value); + } + if (value instanceof Boolean || value instanceof JSONObject + || value instanceof JSONArray) { + return value.toString(); + } + if (value instanceof Map) { + @SuppressWarnings("unchecked") + Map map = (Map) value; + return new JSONObject(map).toString(); + } + if (value instanceof Collection) { + @SuppressWarnings("unchecked") + Collection coll = (Collection) value; + return new JSONArray(coll).toString(); + } + if (value.getClass().isArray()) { + return new JSONArray(value).toString(); + } + return quote(value.toString()); + } + + /** + * Wrap an object, if necessary. If the object is null, return the NULL + * object. If it is an array or collection, wrap it in a JSONArray. If it is + * a map, wrap it in a JSONObject. If it is a standard property (Double, + * String, et al) then it is already wrapped. Otherwise, if it comes from + * one of the java packages, turn it into a string. And if it doesn't, try + * to wrap it in a JSONObject. If the wrapping fails, then null is returned. + * + * @param object + * The object to wrap + * @return The wrapped value + */ + public static Object wrap(Object object) { + try { + if (object == null) { + return NULL; + } + if (object instanceof JSONObject || object instanceof JSONArray + || NULL.equals(object) || object instanceof JSONString + || object instanceof Byte || object instanceof Character + || object instanceof Short || object instanceof Integer + || object instanceof Long || object instanceof Boolean + || object instanceof Float || object instanceof Double + || object instanceof String || object instanceof BigInteger + || object instanceof BigDecimal) { + return object; + } + + if (object instanceof Collection) { + @SuppressWarnings("unchecked") + Collection coll = (Collection) object; + return new JSONArray(coll); + } + if (object.getClass().isArray()) { + return new JSONArray(object); + } + if (object instanceof Map) { + @SuppressWarnings("unchecked") + Map map = (Map) object; + return new JSONObject(map); + } + Package objectPackage = object.getClass().getPackage(); + String objectPackageName = objectPackage != null ? objectPackage + .getName() : ""; + if (objectPackageName.startsWith("java.") + || objectPackageName.startsWith("javax.") + || object.getClass().getClassLoader() == null) { + return object.toString(); + } + return new JSONObject(object); + } catch (Exception exception) { + return null; + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + static final Writer writeValue(Writer writer, Object value, + int indentFactor, int indent) throws JSONException, IOException { + if (value == null || value.equals(null)) { + writer.write("null"); + } else if (value instanceof JSONObject) { + ((JSONObject) value).write(writer, indentFactor, indent); + } else if (value instanceof JSONArray) { + ((JSONArray) value).write(writer, indentFactor, indent); + } else if (value instanceof Map) { + @SuppressWarnings("unchecked") + Map map = (Map) value; + new JSONObject(map).write(writer, indentFactor, indent); + } else if (value instanceof Collection) { + @SuppressWarnings("unchecked") + Collection coll = (Collection) value; + new JSONArray(coll).write(writer, indentFactor, + indent); + } else if (value.getClass().isArray()) { + new JSONArray(value).write(writer, indentFactor, indent); + } else if (value instanceof Number) { + writer.write(numberToString((Number) value)); + } else if (value instanceof Boolean) { + writer.write(value.toString()); + } else if (value instanceof JSONString) { + Object o; + try { + o = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + writer.write(o != null ? o.toString() : quote(value.toString())); + } else { + quote(value.toString(), writer); + } + return writer; + } + + static final void indent(Writer writer, int indent) throws IOException { + for (int i = 0; i < indent; i += 1) { + writer.write(' '); + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + Writer write(Writer writer, int indentFactor, int indent) + throws JSONException { + try { + boolean commanate = false; + final int length = this.length(); + Iterator keys = this.keys(); + writer.write('{'); + + if (length == 1) { + Object key = keys.next(); + writer.write(quote(key.toString())); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + writeValue(writer, this.map.get(key), indentFactor, indent); + } else if (length != 0) { + final int newindent = indent + indentFactor; + while (keys.hasNext()) { + Object key = keys.next(); + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, newindent); + writer.write(quote(key.toString())); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + writeValue(writer, this.map.get(key), indentFactor, newindent); + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, indent); + } + writer.write('}'); + return writer; + } catch (IOException exception) { + throw new JSONException(exception); + } + } +} diff --git a/src/org/json/JSONString.java b/src/org/json/JSONString.java new file mode 100644 index 0000000..1f2d77d --- /dev/null +++ b/src/org/json/JSONString.java @@ -0,0 +1,18 @@ +package org.json; +/** + * The JSONString interface allows a toJSONString() + * method so that a class can change the behavior of + * JSONObject.toString(), JSONArray.toString(), + * and JSONWriter.value(Object). The + * toJSONString method will be used instead of the default behavior + * of using the Object's toString() method and quoting the result. + */ +public interface JSONString { + /** + * The toJSONString method allows a class to produce its own JSON + * serialization. + * + * @return A strictly syntactically correct JSON text. + */ + public String toJSONString(); +} diff --git a/src/org/json/JSONStringer.java b/src/org/json/JSONStringer.java new file mode 100644 index 0000000..32c9f7f --- /dev/null +++ b/src/org/json/JSONStringer.java @@ -0,0 +1,78 @@ +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. + *

+ * A JSONStringer instance provides a value method for appending + * values to the + * text, and a key + * method for adding keys before values in objects. There are array + * and endArray methods that make and bound array values, and + * object and endObject methods which make and bound + * object values. All of these methods return the JSONWriter instance, + * permitting cascade style. For example,

+ * myString = new JSONStringer()
+ *     .object()
+ *         .key("JSON")
+ *         .value("Hello, World!")
+ *     .endObject()
+ *     .toString();
which produces the string
+ * {"JSON":"Hello, World!"}
+ *

+ * The first method called must be array or object. + * 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. + *

+ * This can sometimes be easier than using a JSONObject to build a string. + * @author JSON.org + * @version 2008-09-18 + */ +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 null if there was a + * problem in the construction of the JSON text (such as the calls to + * array were not properly balanced with calls to + * endArray). + * @return The JSON text. + */ + public String toString() { + return this.mode == 'd' ? this.writer.toString() : null; + } +} diff --git a/src/org/json/JSONTokener.java b/src/org/json/JSONTokener.java new file mode 100644 index 0000000..32548ed --- /dev/null +++ b/src/org/json/JSONTokener.java @@ -0,0 +1,446 @@ +package org.json; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; + +/* +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 { + + private long character; + private boolean eof; + private long index; + private long line; + private char previous; + private Reader reader; + private boolean usePrevious; + + + /** + * Construct a JSONTokener from a 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.line = 1; + } + + + /** + * Construct a JSONTokener from an InputStream. + * @param inputStream The source. + */ + public JSONTokener(InputStream inputStream) throws JSONException { + 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. + */ + public void back() throws JSONException { + if (this.usePrevious || this.index <= 0) { + throw new JSONException("Stepping back two steps is not supported"); + } + this.index -= 1; + this.character -= 1; + this.usePrevious = true; + this.eof = false; + } + + + /** + * 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; + } + + 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. + */ + public boolean more() throws JSONException { + this.next(); + if (this.end()) { + return false; + } + this.back(); + return true; + } + + + /** + * Get the next character in the source string. + * + * @return The next character, or 0 if past the end of 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; + c = 0; + } + } + this.index += 1; + if (this.previous == '\r') { + this.line += 1; + this.character = c == '\n' ? 0 : 1; + } else if (c == '\n') { + this.line += 1; + this.character = 0; + } else { + this.character += 1; + } + this.previous = (char) c; + return this.previous; + } + + + /** + * 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) { + throw this.syntaxError("Expected '" + c + "' and instead saw '" + + n + "'"); + } + 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 + * @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 + * " (double quote) or + * ' (single quote). + * @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': + sb.append((char)Integer.parseInt(this.next(4), 16)); + 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. + */ + 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. + */ + 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(); + } + 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. + */ + 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) { + this.reader.reset(); + this.index = startIndex; + this.character = startCharacter; + this.line = startLine; + return c; + } + } while (c != to); + } 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 printable string of this JSONTokener. + * + * @return " at {index} [character {character} line {line}]" + */ + public String toString() { + return " at " + this.index + " [character " + this.character + " line " + + this.line + "]"; + } +} diff --git a/src/org/json/JSONWriter.java b/src/org/json/JSONWriter.java new file mode 100644 index 0000000..8c69caf --- /dev/null +++ b/src/org/json/JSONWriter.java @@ -0,0 +1,327 @@ +package org.json; + +import java.io.IOException; +import java.io.Writer; + +/* +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. + *

+ * A JSONWriter instance provides a value method for appending + * values to the + * text, and a key + * method for adding keys before values in objects. There are array + * and endArray methods that make and bound array values, and + * object and endObject methods which make and bound + * object values. All of these methods return the JSONWriter instance, + * permitting a cascade style. For example,

+ * new JSONWriter(myWriter)
+ *     .object()
+ *         .key("JSON")
+ *         .value("Hello, World!")
+ *     .endObject();
which writes
+ * {"JSON":"Hello, World!"}
+ *

+ * The first method called must be array or object. + * There are no methods for adding commas or colons. JSONWriter adds them for + * you. Objects and arrays can be nested up to 20 levels deep. + *

+ * This can sometimes be easier than using a JSONObject to build a string. + * @author JSON.org + * @version 2011-11-24 + */ +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 Writer writer; + + /** + * Make a fresh JSONWriter. It can be used to build one JSON text. + */ + public JSONWriter(Writer 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.write(','); + } + this.writer.write(string); + } catch (IOException e) { + 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 + * endArray will be appended to this array. The + * endArray 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 mode Mode + * @param c Closing character + * @return this + * @throws JSONException If unbalanced. + */ + private JSONWriter end(char mode, char c) throws JSONException { + if (this.mode != mode) { + throw new JSONException(mode == 'a' + ? "Misplaced endArray." + : "Misplaced endObject."); + } + this.pop(mode); + try { + this.writer.write(c); + } catch (IOException e) { + throw new JSONException(e); + } + this.comma = true; + return this; + } + + /** + * End an array. This method most be called to balance calls to + * array. + * @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 + * object. + * @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 { + this.stack[this.top - 1].putOnce(string, Boolean.TRUE); + if (this.comma) { + this.writer.write(','); + } + this.writer.write(JSONObject.quote(string)); + this.writer.write(':'); + this.comma = false; + this.mode = 'o'; + return this; + } catch (IOException e) { + throw new JSONException(e); + } + } + throw new JSONException("Misplaced key."); + } + + + /** + * Begin appending a new object. All keys and values until the balancing + * endObject will be appended to this object. The + * endObject 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; + } + + + /** + * Append either the value true or the value + * false. + * @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(new Double(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(JSONObject.valueToString(object)); + } +}