From 1eddb4eb81bf1b2defece406cf0192245017aaf2 Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 15 Nov 2018 18:02:09 +0200 Subject: [PATCH] Initial --- build.xml | 73 + config.txt | 49 + nbproject/build-impl.xml | 1640 ++++++++++++++++ nbproject/genfiles.properties | 8 + nbproject/private/config.properties | 0 nbproject/private/private.properties | 6 + nbproject/private/private.xml | 12 + nbproject/project.properties | 86 + nbproject/project.xml | 13 + .../vkdialogsaver/AlphanumComparator.java | 113 ++ src/com/annimon/vkdialogsaver/Config.java | 120 ++ src/com/annimon/vkdialogsaver/Emoji.java | 60 + src/com/annimon/vkdialogsaver/Main.java | 24 + .../annimon/vkdialogsaver/MessageParser.java | 161 ++ src/com/annimon/vkdialogsaver/VkApi.java | 127 ++ src/com/annimon/vkdialogsaver/VkDialog.java | 52 + src/com/vk/sdk/api/model/Identifiable.java | 42 + src/com/vk/sdk/api/model/ParseUtils.java | 251 +++ src/com/vk/sdk/api/model/TextUtils.java | 26 + .../api/model/VKApiApplicationContent.java | 82 + src/com/vk/sdk/api/model/VKApiArray.java | 85 + src/com/vk/sdk/api/model/VKApiAudio.java | 170 ++ src/com/vk/sdk/api/model/VKApiDocument.java | 165 ++ src/com/vk/sdk/api/model/VKApiLink.java | 113 ++ src/com/vk/sdk/api/model/VKApiMessage.java | 124 ++ src/com/vk/sdk/api/model/VKApiModel.java | 85 + src/com/vk/sdk/api/model/VKApiNote.java | 110 ++ src/com/vk/sdk/api/model/VKApiPhoto.java | 227 +++ src/com/vk/sdk/api/model/VKApiPhotoAlbum.java | 170 ++ src/com/vk/sdk/api/model/VKApiPhotoSize.java | 322 ++++ src/com/vk/sdk/api/model/VKApiPoll.java | 149 ++ src/com/vk/sdk/api/model/VKApiPost.java | 209 ++ .../vk/sdk/api/model/VKApiPostedPhoto.java | 59 + src/com/vk/sdk/api/model/VKApiVideo.java | 280 +++ src/com/vk/sdk/api/model/VKAttachments.java | 209 ++ src/com/vk/sdk/api/model/VKList.java | 417 ++++ src/com/vk/sdk/api/model/VKPhotoArray.java | 36 + src/com/vk/sdk/api/model/VKPhotoSizes.java | 208 ++ src/com/vk/sdk/api/model/VKPostArray.java | 37 + src/com/vk/sdk/api/model/VKPrivacy.java | 87 + src/com/vk/sdk/api/model/VKScopes.java | 159 ++ src/org/json/CDL.java | 279 +++ src/org/json/Cookie.java | 169 ++ src/org/json/CookieList.java | 89 + src/org/json/HTTP.java | 163 ++ src/org/json/HTTPTokener.java | 77 + src/org/json/JSONArray.java | 977 ++++++++++ src/org/json/JSONException.java | 43 + src/org/json/JSONML.java | 467 +++++ src/org/json/JSONObject.java | 1683 +++++++++++++++++ src/org/json/JSONString.java | 18 + src/org/json/JSONStringer.java | 78 + src/org/json/JSONTokener.java | 446 +++++ src/org/json/JSONWriter.java | 327 ++++ src/org/json/Kim.java | 372 ++++ src/org/json/Property.java | 72 + src/org/json/XML.java | 490 +++++ src/org/json/XMLTokener.java | 365 ++++ tmp/styles/main.css | 43 + 59 files changed, 12524 insertions(+) create mode 100644 build.xml create mode 100644 config.txt create mode 100644 nbproject/build-impl.xml create mode 100644 nbproject/genfiles.properties create mode 100644 nbproject/private/config.properties create mode 100644 nbproject/private/private.properties create mode 100644 nbproject/private/private.xml create mode 100644 nbproject/project.properties create mode 100644 nbproject/project.xml create mode 100644 src/com/annimon/vkdialogsaver/AlphanumComparator.java create mode 100644 src/com/annimon/vkdialogsaver/Config.java create mode 100644 src/com/annimon/vkdialogsaver/Emoji.java create mode 100644 src/com/annimon/vkdialogsaver/Main.java create mode 100644 src/com/annimon/vkdialogsaver/MessageParser.java create mode 100644 src/com/annimon/vkdialogsaver/VkApi.java create mode 100644 src/com/annimon/vkdialogsaver/VkDialog.java create mode 100644 src/com/vk/sdk/api/model/Identifiable.java create mode 100644 src/com/vk/sdk/api/model/ParseUtils.java create mode 100644 src/com/vk/sdk/api/model/TextUtils.java create mode 100644 src/com/vk/sdk/api/model/VKApiApplicationContent.java create mode 100644 src/com/vk/sdk/api/model/VKApiArray.java create mode 100644 src/com/vk/sdk/api/model/VKApiAudio.java create mode 100644 src/com/vk/sdk/api/model/VKApiDocument.java create mode 100644 src/com/vk/sdk/api/model/VKApiLink.java create mode 100644 src/com/vk/sdk/api/model/VKApiMessage.java create mode 100644 src/com/vk/sdk/api/model/VKApiModel.java create mode 100644 src/com/vk/sdk/api/model/VKApiNote.java create mode 100644 src/com/vk/sdk/api/model/VKApiPhoto.java create mode 100644 src/com/vk/sdk/api/model/VKApiPhotoAlbum.java create mode 100644 src/com/vk/sdk/api/model/VKApiPhotoSize.java create mode 100644 src/com/vk/sdk/api/model/VKApiPoll.java create mode 100644 src/com/vk/sdk/api/model/VKApiPost.java create mode 100644 src/com/vk/sdk/api/model/VKApiPostedPhoto.java create mode 100644 src/com/vk/sdk/api/model/VKApiVideo.java create mode 100644 src/com/vk/sdk/api/model/VKAttachments.java create mode 100644 src/com/vk/sdk/api/model/VKList.java create mode 100644 src/com/vk/sdk/api/model/VKPhotoArray.java create mode 100644 src/com/vk/sdk/api/model/VKPhotoSizes.java create mode 100644 src/com/vk/sdk/api/model/VKPostArray.java create mode 100644 src/com/vk/sdk/api/model/VKPrivacy.java create mode 100644 src/com/vk/sdk/api/model/VKScopes.java create mode 100644 src/org/json/CDL.java create mode 100644 src/org/json/Cookie.java create mode 100644 src/org/json/CookieList.java create mode 100644 src/org/json/HTTP.java create mode 100644 src/org/json/HTTPTokener.java create mode 100644 src/org/json/JSONArray.java create mode 100644 src/org/json/JSONException.java create mode 100644 src/org/json/JSONML.java create mode 100644 src/org/json/JSONObject.java create mode 100644 src/org/json/JSONString.java create mode 100644 src/org/json/JSONStringer.java create mode 100644 src/org/json/JSONTokener.java create mode 100644 src/org/json/JSONWriter.java create mode 100644 src/org/json/Kim.java create mode 100644 src/org/json/Property.java create mode 100644 src/org/json/XML.java create mode 100644 src/org/json/XMLTokener.java create mode 100644 tmp/styles/main.css diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..6191d06 --- /dev/null +++ b/build.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Builds, tests, and runs the project VkDialogSaver. + + + diff --git a/config.txt b/config.txt new file mode 100644 index 0000000..4b63adb --- /dev/null +++ b/config.txt @@ -0,0 +1,49 @@ +################### +# общие настройки # +################### + +# рабочая директория, куда будут положены файлы сообщений +# и откуда они будут читаться при генерировании html +workDir = tmp/ + +# id Вашего приложения вк +appId = + +# токен доступа. Если оставить пустым, будет +# открыта страница для его получения +accessToken = + +# режим: +# get - получение диалогов +# generate - генерирование html +mode = get + + +############################ +# настройки для режима get # +############################ + +## id собеседника +dialogId = 1 + +## с какой по какую позиции получать сообщения +fromOffset = 0 +toOffset = 3000 + + +################################# +# настройки для режима generate # +################################# + +## с какого по какой год генерировать +## html-страницы из полученных ранее сообщений +fromYear = 2015 +toYear = 2017 + +## имя отправителя, то есть Ваше имя +outName = Я + +## список имён пользователей и их id +1 = Паша Дуров +yy = Имя Отчество +zz = Кто-то неизвестный \ No newline at end of file diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml new file mode 100644 index 0000000..3e7243f --- /dev/null +++ b/nbproject/build-impl.xml @@ -0,0 +1,1640 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set 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..67bccc3 --- /dev/null +++ b/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=1f1ee2ea +build.xml.script.CRC32=77c87077 +build.xml.stylesheet.CRC32=8064a381@1.75.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=1f1ee2ea +nbproject/build-impl.xml.script.CRC32=0106952b +nbproject/build-impl.xml.stylesheet.CRC32=bd492785@1.85.0.48 diff --git a/nbproject/private/config.properties b/nbproject/private/config.properties new file mode 100644 index 0000000..e69de29 diff --git a/nbproject/private/private.properties b/nbproject/private/private.properties new file mode 100644 index 0000000..fbbfe24 --- /dev/null +++ b/nbproject/private/private.properties @@ -0,0 +1,6 @@ +compile.on.save=true +do.depend=false +do.jar=true +javac.debug=true +javadoc.preview=true +user.properties.file=C:\\Users\\aNNiMON\\AppData\\Roaming\\NetBeans\\dev\\build.properties diff --git a/nbproject/private/private.xml b/nbproject/private/private.xml new file mode 100644 index 0000000..a5b05e5 --- /dev/null +++ b/nbproject/private/private.xml @@ -0,0 +1,12 @@ + + + + + + file:/C:/Users/aNNiMON/Documents/NetBeansProjects/_Java%20SE/VkDialogSaver/src/com/annimon/vkdialogsaver/Config.java + file:/C:/Users/aNNiMON/Documents/NetBeansProjects/_Java%20SE/VkDialogSaver/src/com/annimon/vkdialogsaver/MessageParser.java + file:/C:/Users/aNNiMON/Documents/NetBeansProjects/_Java%20SE/VkDialogSaver/src/com/annimon/vkdialogsaver/Main.java + file:/C:/Users/aNNiMON/Documents/NetBeansProjects/_Java%20SE/VkDialogSaver/src/com/annimon/vkdialogsaver/VkDialog.java + + + diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 0000000..63c6cb6 --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,86 @@ +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=VkDialogSaver +application.vendor=aNNiMON +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.modulepath=\ + ${run.modulepath} +debug.test.classpath=\ + ${run.test.classpath} +debug.test.modulepath=\ + ${run.test.modulepath} +# Files in build.classes.dir which should be excluded from distribution jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/VkDialogSaver.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.modulepath= +javac.processorpath=\ + ${javac.classpath} +javac.source=1.8 +javac.target=1.8 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.modulepath=\ + ${javac.modulepath} +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=com.annimon.vkdialogsaver.Main +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.modulepath=\ + ${javac.modulepath}:\ + ${build.classes.dir} +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +run.test.modulepath=\ + ${javac.test.modulepath} +source.encoding=UTF-8 +src.dir=src diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 0000000..eabe0be --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,13 @@ + + + org.netbeans.modules.java.j2seproject + + + VkDialogSaver + + + + + + + diff --git a/src/com/annimon/vkdialogsaver/AlphanumComparator.java b/src/com/annimon/vkdialogsaver/AlphanumComparator.java new file mode 100644 index 0000000..f420370 --- /dev/null +++ b/src/com/annimon/vkdialogsaver/AlphanumComparator.java @@ -0,0 +1,113 @@ +package com.annimon.vkdialogsaver; + +/* + * The Alphanum Algorithm is an improved sorting algorithm for strings + * containing numbers. Instead of sorting numbers in ASCII order like + * a standard sort, this algorithm sorts numbers in numeric order. + * + * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +import java.nio.file.Path; +import java.util.Comparator; + +/** + * This is an updated version with enhancements made by Daniel Migowski, + * Andre Bogus, and David Koelle + * + * To convert to use Templates (Java 1.5+): + * - Change "implements Comparator" to "implements Comparator" + * - Change "compare(Object o1, Object o2)" to "compare(String s1, String s2)" + * - Remove the type checking and casting in compare(). + * + * To use this class: + * Use the static "sort" method from the java.util.Collections class: + * Collections.sort(your list, new AlphanumComparator()); + */ +public class AlphanumComparator implements Comparator { + + /** + * Length of string is passed in for improved efficiency (only need to calculate it once) * + */ + private String getChunk(String s, int slength, int marker) { + StringBuilder chunk = new StringBuilder(); + char c = s.charAt(marker); + chunk.append(c); + marker++; + if (Character.isDigit(c)) { + while (marker < slength) { + c = s.charAt(marker); + if (!Character.isDigit(c)) + break; + chunk.append(c); + marker++; + } + } else { + while (marker < slength) { + c = s.charAt(marker); + if (Character.isDigit(c)) + break; + chunk.append(c); + marker++; + } + } + return chunk.toString(); + } + + @Override + public int compare(Path p1, Path p2) { + String s1 = p1.getFileName().toString(); + String s2 = p2.getFileName().toString(); + int thisMarker = 0; + int thatMarker = 0; + int s1Length = s1.length(); + int s2Length = s2.length(); + + while (thisMarker < s1Length && thatMarker < s2Length) { + String thisChunk = getChunk(s1, s1Length, thisMarker); + thisMarker += thisChunk.length(); + + String thatChunk = getChunk(s2, s2Length, thatMarker); + thatMarker += thatChunk.length(); + + // If both chunks contain numeric characters, sort them numerically + int result = 0; + if (Character.isDigit(thisChunk.charAt(0)) && Character.isDigit(thatChunk.charAt(0))) { + // Simple chunk comparison by length. + int thisChunkLength = thisChunk.length(); + result = thisChunkLength - thatChunk.length(); + // If equal, the first different number counts + if (result == 0) { + for (int i = 0; i < thisChunkLength; i++) { + result = thisChunk.charAt(i) - thatChunk.charAt(i); + if (result != 0) { + return result; + } + } + } + } else { + result = thisChunk.compareTo(thatChunk); + } + + if (result != 0) + return result; + } + + return s1Length - s2Length; + } +} diff --git a/src/com/annimon/vkdialogsaver/Config.java b/src/com/annimon/vkdialogsaver/Config.java new file mode 100644 index 0000000..9859745 --- /dev/null +++ b/src/com/annimon/vkdialogsaver/Config.java @@ -0,0 +1,120 @@ +package com.annimon.vkdialogsaver; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public final class Config { + + public static Config load() throws IOException { + final Map data = Files.lines(Paths.get("config.txt")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .filter(s -> !s.startsWith("#")) + .map(s -> s.split("=")) + .map(arr -> new String[] { + arr[0].trim().toLowerCase(), + arr.length > 1 ? arr[1].trim() : "" + }) + .collect(Collectors.toMap(arr -> arr[0], arr -> arr[1])); + + final Base base = new Base(); + base.appId = data.getOrDefault("appid", ""); // 4422363 + base.accessToken = data.getOrDefault("accesstoken", ""); + base.workDir = data.getOrDefault("workdir", ""); + base.mode = data.getOrDefault("mode", ""); + + final Get get = new Get(); + get.dialogId = data.getOrDefault("dialogid", ""); + get.fromOffset = Integer.parseInt(data.getOrDefault("fromoffset", "0")); + get.toOffset = Integer.parseInt(data.getOrDefault("tooffset", "300")); + + final Generate gen = new Generate(); + gen.fromYear = Integer.parseInt(data.getOrDefault("fromyear", "2013")); + gen.toYear = Integer.parseInt(data.getOrDefault("toyear", "2017")); + gen.outName = data.getOrDefault("outname", "Я"); + + final Map users = new LinkedHashMap<>(); + for (Map.Entry e : data.entrySet()) { + try { + int id = Integer.parseInt(e.getKey().trim()); + users.put(id, e.getValue()); + } catch (NumberFormatException nfe) {} + } + return new Config(base, get, gen, users); + } + + private static class Base { + String workDir, appId, accessToken; + String mode; + } + + private static class Get { + String dialogId; + int fromOffset, toOffset; + } + + private static class Generate { + int fromYear, toYear; + String outName; + } + + private final Base base; + private final Get get; + private final Generate gen; + private final Map users; + + private Config(Base base, Get get, Generate gen, Map users) { + this.base = base; + this.get = get; + this.gen = gen; + this.users = users; + } + + public String getWorkDir() { + return base.workDir; + } + + public String getAppId() { + return base.appId; + } + + public String getAccessToken() { + return base.accessToken; + } + + public String getMode() { + return base.mode; + } + + public String getDialogId() { + return get.dialogId; + } + + public int getFromOffset() { + return get.fromOffset; + } + + public int getToOffset() { + return get.toOffset; + } + + public int getFromYear() { + return gen.fromYear; + } + + public int getToYear() { + return gen.toYear; + } + + public String getOutName() { + return gen.outName; + } + + public Map getUsers() { + return users; + } +} diff --git a/src/com/annimon/vkdialogsaver/Emoji.java b/src/com/annimon/vkdialogsaver/Emoji.java new file mode 100644 index 0000000..9770c7a --- /dev/null +++ b/src/com/annimon/vkdialogsaver/Emoji.java @@ -0,0 +1,60 @@ +package com.annimon.vkdialogsaver; + +import java.util.Arrays; + +/** + * Класс вывода сообщений со смайлами. + */ +public final class Emoji { + + private static final char[] emojiChars = { + '\u203c', '\u2049', '\u2139', '\u2194', '\u2195', '\u2196', '\u2197', '\u2198', '\u2199', '\u21a9', '\u21aa', + '\u231a', '\u231b', '\u23e9', '\u23ea', '\u23eb', '\u23ec', '\u23f0', '\u23f3', '\u24c2', '\u25aa', '\u25ab', + '\u25b6', '\u25c0', '\u25fb', '\u25fc', '\u25fd', '\u25fe', '\u2600', '\u2601', '\u260e', '\u2611', '\u2614', + '\u2615', '\u261d', '\u263a', '\u2648', '\u2649', '\u264a', '\u264b', '\u264c', '\u264d', '\u264e', '\u264f', + '\u2650', '\u2651', '\u2652', '\u2653', '\u2660', '\u2663', '\u2665', '\u2666', '\u2668', '\u267b', '\u267f', + '\u2693', '\u26a0', '\u26a1', '\u26aa', '\u26ab', '\u26bd', '\u26be', '\u26c4', '\u26c5', '\u26ce', '\u26d4', + '\u26ea', '\u26f2', '\u26f3', '\u26f5', '\u26fa', '\u26fd', '\u2702', '\u2705', '\u2708', '\u2709', '\u270a', + '\u270b', '\u270c', '\u270f', '\u2712', '\u2714', '\u2716', '\u2728', '\u2733', '\u2734', '\u2744', '\u2747', + '\u274c', '\u274e', '\u2753', '\u2754', '\u2755', '\u2757', '\u2764', '\u2795', '\u2796', '\u2797', '\u27a1', + '\u27b0', '\u27bf', '\u2934', '\u2935', '\u2b05', '\u2b06', '\u2b07', '\u2b1b', '\u2b1c', '\u2b50', '\u2b55', + '\u3030', '\u303d', '\u3297', '\u3299' + }; + + public static CharSequence printEmoji(final CharSequence message) { + if (message == null || message.length() == 0) { + return message; + } + + long n = 0; + for (int i = 0; i < message.length(); ++i) { + final char ch = message.charAt(i); + if (ch == '\ud83c' || ch == '\ud83d' || (n != 0 && (0xFFFFFFFF00000000L & n) == 0 && ch >= '\udde6' && ch <= '\uddfa')) { + n = (n << 16 | ch); + } else if (n > 0 && ('\uf000' & ch) == '\ud000') { + Emoji.printImage(n << 16 | ch); + n = 0; + } else if (ch == '\u20e3') { + if (i > 0) { + // Смайлы с цифрами 0..9 и #, на сайте для них нет картинок. + final char prev = message.charAt(i - 1); + if ((prev >= '0' && prev <= '9') || prev == '#') { + System.out.print("<"+prev+">"); + n = 0; + } + } + } else if (Arrays.binarySearch(emojiChars, ch) != -1) { + Emoji.printImage((long)ch); + } else { + System.out.print(ch); + } + } + + return message; + } + + private static void printImage(long val) { + String emoji = String.format("%8X", val).trim(); + System.out.print(" "); + } +} diff --git a/src/com/annimon/vkdialogsaver/Main.java b/src/com/annimon/vkdialogsaver/Main.java new file mode 100644 index 0000000..ff264c9 --- /dev/null +++ b/src/com/annimon/vkdialogsaver/Main.java @@ -0,0 +1,24 @@ +package com.annimon.vkdialogsaver; + +import java.io.IOException; + +public class Main { + + public static void main(String[] args) throws IOException, InterruptedException { + final Config config = Config.load(); + switch (config.getMode()) { + case "get": + VkDialog.setConfig(config); + VkDialog.save(config.getDialogId(), config.getFromOffset(), config.getToOffset()); + return; + + case "generate": + MessageParser.setConfig(config); + MessageParser.parse(); + return; + + default: + System.out.println("Ошибка! Неверный режим."); + } + } +} diff --git a/src/com/annimon/vkdialogsaver/MessageParser.java b/src/com/annimon/vkdialogsaver/MessageParser.java new file mode 100644 index 0000000..3e6c675 --- /dev/null +++ b/src/com/annimon/vkdialogsaver/MessageParser.java @@ -0,0 +1,161 @@ +package com.annimon.vkdialogsaver; + +import com.vk.sdk.api.model.VKApiMessage; +import com.vk.sdk.api.model.VKAttachments; +import com.vk.sdk.api.model.VKList; +import java.awt.Desktop; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import org.json.JSONObject; + +/** + * + * @author aNNiMON + */ +public final class MessageParser { + + private static Config config; + + public static void setConfig(Config config) { + MessageParser.config = config; + } + + /** + * Пакетный парсинг файлов в папке + * @throws IOException + */ + public static void parse() throws IOException { + VKList messages = new VKList<>(); + Files.list(Paths.get(config.getWorkDir())) + .filter(p -> p.toString().endsWith(".txt")) + .sorted(new AlphanumComparator()) + .forEach(p -> messages.addAll(readMessages(p))); + + for (int year = config.getFromYear(); year <= config.getToYear(); year++) { + LocalDateTime from = LocalDateTime.of(year, Month.JANUARY, 1, 0, 0); + LocalDateTime to = from.plusYears(1); + VKList query = new VKList<>(messages.stream() + .filter(msg -> getDateTime(msg.date).isAfter(from) + && getDateTime(msg.date).isBefore(to)) + .collect(Collectors.toList())); + generate(query, String.valueOf(year)); + } + /*VKList query = new VKList<>(messages.stream() + .filter(msg -> hasAttachment(msg.attachments, VKAttachments.TYPE_AUDIO)) + .collect(Collectors.toList())); + generate(query, "audio_");*/ + } + + private static boolean hasAttachment(VKAttachments attachments, String type) { + return attachments.stream().anyMatch(a -> a.getType().equals(type)); + } + + /** + * Парсинг одного конкретного файла + * @param filename + * @throws IOException + */ + public static void parse(String filename) throws IOException { + final String fullPath = config.getWorkDir() + filename; + VKList messages = readMessages(Paths.get(fullPath)); + generate(messages, filename); + } + + private static VKList readMessages(Path fullPath) { + try { + final String content = new String(Files.readAllBytes(fullPath), "UTF-8"); + + JSONObject jsonObject = new JSONObject(content); + return new VKList<>(jsonObject, VKApiMessage.class); + } catch (IOException ex) { + ex.printStackTrace(); + } + return new VKList<>(); + } + + private static void generate(VKList messages, String outFilename) throws IOException { + final String outPath = config.getWorkDir() + outFilename + ".html"; + final File file = new File(outPath); + System.setOut(new PrintStream(file)); + generate(messages); + Desktop.getDesktop().browse(file.toURI()); + } + + private static void generate(VKList messages) throws IOException { + System.out.println(""); + System.out.println(""); + messages.stream().forEach(new MessageConsumer()); + System.out.println(""); + } + + private static class MessageConsumer implements Consumer { + + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd MMMM uuuu", Locale.getDefault()); + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm", Locale.getDefault()); + + @Override + public void accept(VKApiMessage message) { + println(String.format("%s", getName(message.user_id, message.out))); + final LocalDateTime dateTime = getDateTime(message.date); + String date = dateTime.format(DATE_FORMATTER) + "   " + dateTime.format(TIME_FORMATTER); + println(String.format("%s", date)); + if (!message.body.isEmpty()) { + message.body = replaceHtml(message.body); + if (message.emoji) { + Emoji.printEmoji(message.body); + println(); + } else println(message.body); + } + if (!message.fwd_messages.isEmpty()) { + System.out.println("
"); + message.fwd_messages.forEach(new MessageConsumer()); + System.out.println("
"); + } + if (!message.attachments.isEmpty()) { + System.out.println("
"); + message.attachments.forEach((attachment) -> { + println(attachment.toHtml()); + }); + System.out.println("
"); + } + println(); + } + + private String getName(int id, boolean out) { + if (out) return config.getOutName(); + return config.getUsers().getOrDefault(id, Integer.toString(id)); + } + } + + private static String replaceHtml(String in) { + return in.replace("<", "<") + .replace(">", ">") + .replace("\n", "
"); + } + + private static LocalDateTime getDateTime(long timestamp) { + return LocalDateTime.ofEpochSecond(timestamp, 0, ZoneOffset.ofHours(+3)); + } + + private static void println(Object text) { + System.out.print(text); + System.out.println("
"); + } + + private static void println() { + System.out.println("
"); + } +} diff --git a/src/com/annimon/vkdialogsaver/VkApi.java b/src/com/annimon/vkdialogsaver/VkApi.java new file mode 100644 index 0000000..2c7bc08 --- /dev/null +++ b/src/com/annimon/vkdialogsaver/VkApi.java @@ -0,0 +1,127 @@ +package com.annimon.vkdialogsaver; + +import java.awt.Desktop; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.HashMap; + +/** + * VK API sample implementation for Java 8. + * @see https://github.com/liosha2007/vkontakte-api + * @author liosha2007 + * @author aNNiMON + */ +public final class VkApi { + + private static final String API_VERSION = "5.21"; + + private static final String AUTH_URL = "https://oauth.vk.com/authorize" + + "?client_id={APP_ID}" + + "&scope={PERMISSIONS}" + + "&redirect_uri={REDIRECT_URI}" + + "&display={DISPLAY}" + + "&v={API_VERSION}" + + "&response_type=token"; + + private static final String API_REQUEST = "https://api.vk.com/method/{METHOD_NAME}" + + "?{PARAMETERS}" + + "&access_token={ACCESS_TOKEN}" + + "&v=" + API_VERSION; + + public static VkApi with(String appId, String accessToken) throws IOException { + return new VkApi(appId, accessToken); + } + + private final String accessToken; + + private VkApi(String appId, String accessToken) throws IOException { + this.accessToken = accessToken; + if (accessToken == null || accessToken.isEmpty()) { + auth(appId); + throw new Error("Need access token"); + } + } + + private void auth(String appId) throws IOException { + String reqUrl = AUTH_URL + .replace("{APP_ID}", appId) + .replace("{PERMISSIONS}", "photos,messages") + .replace("{REDIRECT_URI}", "https://oauth.vk.com/blank.html") + .replace("{DISPLAY}", "page") + .replace("{API_VERSION}", API_VERSION); + try { + Desktop.getDesktop().browse(new URL(reqUrl).toURI()); + } catch (URISyntaxException ex) { + throw new IOException(ex); + } + } + + public String getDialogs() throws IOException { + return invokeApi("messages.getDialogs", null); + } + + public String getHistory(String userId, int offset, int count, boolean rev) throws IOException { + return invokeApi("messages.getHistory", Params.create() + .add("user_id", userId) + .add("offset", String.valueOf(offset)) + .add("count", String.valueOf(count)) + .add("rev", rev ? "1" : "0")); + } + + public String getAlbums(String userId) throws IOException { + return invokeApi("photos.getAlbums", Params.create() + .add("owner_id", userId) + .add("photo_sizes", "1") + .add("thumb_src", "1")); + } + + private String invokeApi(String method, Params params) throws IOException { + final String parameters = (params == null) ? "" : params.build(); + String reqUrl = API_REQUEST + .replace("{METHOD_NAME}", method) + .replace("{ACCESS_TOKEN}", accessToken) + .replace("{PARAMETERS}&", parameters); + return invokeApi(reqUrl); + } + + private static String invokeApi(String requestUrl) throws IOException { + final StringBuilder result = new StringBuilder(); + final URL url = new URL(requestUrl); + try (InputStream is = url.openStream()) { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + reader.lines().forEach(result::append); + } + return result.toString(); + } + + private static class Params { + + public static Params create() { + return new Params(); + } + + private final HashMap params; + + private Params() { + params = new HashMap<>(); + } + + public Params add(String key, String value) { + params.put(key, value); + return this; + } + + public String build() { + if (params.isEmpty()) return ""; + final StringBuilder result = new StringBuilder(); + params.keySet().stream().forEach(key -> { + result.append(key).append('=').append(params.get(key)).append('&'); + }); + return result.toString(); + } + } +} diff --git a/src/com/annimon/vkdialogsaver/VkDialog.java b/src/com/annimon/vkdialogsaver/VkDialog.java new file mode 100644 index 0000000..71d4106 --- /dev/null +++ b/src/com/annimon/vkdialogsaver/VkDialog.java @@ -0,0 +1,52 @@ +package com.annimon.vkdialogsaver; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; + +/** + * Бекап сообщений Вконтакте. + * @author aNNiMON + */ +public final class VkDialog { + + private static Config config; + + public static void setConfig(Config config) { + VkDialog.config = config; + } + + public static void save(String userId, int from, int to) throws IOException { + VkApi vkApi = VkApi.with(config.getAppId(), config.getAccessToken()); + + final int count = 200; + int offset = from; + while (offset < to) { + System.out.println(offset); + String text; + while (true) { + text = vkApi.getHistory(userId, offset, count, true); + if (!text.contains("Too many requests per second")) break; + System.out.println("Wait 1 second"); + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + write(offset, text); + offset += count; + } + } + + private static void write(int offset, String text) throws IOException { + File file = new File(config.getWorkDir() + offset + ".txt"); + try (OutputStream os = new FileOutputStream(file)) { + OutputStreamWriter writer = new OutputStreamWriter(os, "UTF-8"); + writer.write(text); + writer.flush(); + } + } +} diff --git a/src/com/vk/sdk/api/model/Identifiable.java b/src/com/vk/sdk/api/model/Identifiable.java new file mode 100644 index 0000000..405199f --- /dev/null +++ b/src/com/vk/sdk/api/model/Identifiable.java @@ -0,0 +1,42 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +/** + * identifiable.java + * vk-android-sdk + * + * Created by Babichev Vitaly on 06.01.14. + * Copyright (c) 2014 VK. All rights reserved. + */ +package com.vk.sdk.api.model; + +/** + * Describes objects that contains an "id" field. + */ +@SuppressWarnings("unused") +public interface Identifiable { + + /** + * Returns unique identifier of this object(usually it's value of JSON field "id"). + */ + int getId(); + +} \ No newline at end of file diff --git a/src/com/vk/sdk/api/model/ParseUtils.java b/src/com/vk/sdk/api/model/ParseUtils.java new file mode 100644 index 0000000..82f30f9 --- /dev/null +++ b/src/com/vk/sdk/api/model/ParseUtils.java @@ -0,0 +1,251 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +package com.vk.sdk.api.model; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.*; + +/** + * Collection of helpers to parse server responses. + */ +class ParseUtils { + + private ParseUtils() { + } + + /** + * Parse boolean from server response. + * + * @param from server response like this format: {@code response: 1} + * @throws JSONException if server response is not valid + */ + public static boolean parseBoolean(String from) throws JSONException { + return new JSONObject(from).optInt("response", 0) == 1; + } + + /** + * Parse boolean from JSONObject with given name. + * + * @param from server response like this format: {@code field: 1} + * @param name name of field to read + */ + public static boolean parseBoolean(JSONObject from, String name) { + return from != null && from.optInt(name, 0) == 1; + } + + /** + * Parse int from JSONObject with given name. + * + * @param from server response like this format: {@code field: 34} + * @param name name of field to read + */ + public static int parseInt(JSONObject from, String name) { + if (from == null) return 0; + return from.optInt(name, 0); + } + + /** + * Parse int from server response. + * + * @param from server response like this format: {@code response: 34} + * @throws JSONException if server response is not valid + */ + public static int parseInt(String from) throws JSONException { + if (from == null) return 0; + return new JSONObject(from).optInt("response"); + } + + /** + * Parse long from JSONObject with given name. + * + * @param from server response like this format: {@code field: 34} + * @param name name of field to read + */ + public static long parseLong(JSONObject from, String name) { + if (from == null) return 0; + return from.optLong(name, 0); + } + + /** + * Parse int array from JSONObject with given name. + * + * @param from int JSON array like this one {@code {11, 34, 42}} + */ + public static int[] parseIntArray(JSONArray from) { + int[] result = new int[from.length()]; + for (int i = 0; i < result.length; i++) { + result[i] = from.optInt(i); + } + return result; + } + + /** + * Returns root JSONObject from server response + * + * @param source standart VK server response + * @throws JSONException if source is not valid + */ + public static JSONObject rootJSONObject(String source) throws JSONException { + return new JSONObject(source).getJSONObject("response"); + } + + /** + * Returns root JSONArray from server response + * + * @param source standart VK server response + * @throws JSONException if source is not valid + */ + public static JSONArray rootJSONArray(String source) throws JSONException { + return new JSONObject(source).getJSONArray("response"); + } + + /** + * Parses object with follow rules: + * + * 1. All fields should had a public access. + * 2. The name of the filed should be fully equal to name of JSONObject key. + * 3. Supports parse of all Java primitives, all {@link java.lang.String}, + * arrays of primitive types, {@link java.lang.String}s and {@link com.vk.sdk.api.model.VKApiModel}s, + * list implementation line {@link com.vk.sdk.api.model.VKList}, {@link com.vk.sdk.api.model.VKAttachments.VKAttachment} or {@link com.vk.sdk.api.model.VKPhotoSizes}, + * {@link com.vk.sdk.api.model.VKApiModel}s. + * + * 4. Boolean fields defines by vk_int == 1 expression. + * + * @param object object to initialize + * @param source data to read values + * @param type of result + * @return initialized according with given data object + * @throws JSONException if source object structure is invalid + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public static T parseViaReflection(T object, JSONObject source) throws JSONException { + if (source.has("response")) { + source = source.optJSONObject("response"); + } + if (source == null) { + return object; + } + for (Field field : object.getClass().getFields()) { + field.setAccessible(true); + String fieldName = field.getName(); + Class fieldType = field.getType(); + + Object value = source.opt(fieldName); + if (value == null) { + continue; + } + try { + if (fieldType.isPrimitive() && value instanceof Number) { + Number number = (Number) value; + if (fieldType.equals(int.class)) { + field.setInt(object, number.intValue()); + } else if (fieldType.equals(long.class)) { + field.setLong(object, number.longValue()); + } else if (fieldType.equals(float.class)) { + field.setFloat(object, number.floatValue()); + } else if (fieldType.equals(double.class)) { + field.setDouble(object, number.doubleValue()); + } else if (fieldType.equals(boolean.class)) { + field.setBoolean(object, number.intValue() == 1); + } else if (fieldType.equals(short.class)) { + field.setShort(object, number.shortValue()); + } else if (fieldType.equals(byte.class)) { + field.setByte(object, number.byteValue()); + } + } else { + Object result = field.get(object); + if (value.getClass().equals(fieldType)) { + result = value; + } else if (fieldType.isArray() && value instanceof JSONArray) { + result = parseArrayViaReflection((JSONArray) value, fieldType); + } else if(VKPhotoSizes.class.isAssignableFrom(fieldType) && value instanceof JSONArray) { + Constructor constructor = fieldType.getConstructor(JSONArray.class); + result = constructor.newInstance((JSONArray) value); + } else if(VKAttachments.class.isAssignableFrom(fieldType) && value instanceof JSONArray) { + Constructor constructor = fieldType.getConstructor(JSONArray.class); + result = constructor.newInstance((JSONArray) value); + } else if(VKList.class.equals(fieldType)) { + ParameterizedType genericTypes = (ParameterizedType) field.getGenericType(); + Class genericType = (Class) genericTypes.getActualTypeArguments()[0]; + if(VKApiModel.class.isAssignableFrom(genericType) && Identifiable.class.isAssignableFrom(genericType)) { + if(value instanceof JSONArray) { + result = new VKList((JSONArray) value, genericType); + } else if(value instanceof JSONObject) { + result = new VKList((JSONObject) value, genericType); + } + } + } else if (VKApiModel.class.isAssignableFrom(fieldType) && value instanceof JSONObject) { + result = ((VKApiModel) fieldType.newInstance()).parse((JSONObject) value); + } + field.set(object, result); + } + } catch (InstantiationException e) { + throw new JSONException(e.getMessage()); + } catch (IllegalAccessException e) { + throw new JSONException(e.getMessage()); + } catch (NoSuchMethodException e) { + throw new JSONException(e.getMessage()); + } catch (InvocationTargetException e) { + throw new JSONException(e.getMessage()); + } catch (NoSuchMethodError e) { + // Примечание Виталия: + // Вы не поверите, но у некоторых вендоров getFields() вызывает ВОТ ЭТО. + // Иногда я всерьез задумываюсь, правильно ли я поступил, выбрав Android в качестве платформы разработки. + throw new JSONException(e.getMessage()); + } + } + return object; + } + + /** + * Parses array from given JSONArray. + * Supports parsing of primitive types and {@link com.vk.sdk.api.model.VKApiModel} instances. + * @param array JSONArray to parse + * @param arrayClass type of array field in class. + * @return object to set to array field in class + * @throws JSONException if given array have incompatible type with given field. + */ + private static Object parseArrayViaReflection(JSONArray array, Class arrayClass) throws JSONException { + Object result = Array.newInstance(arrayClass.getComponentType(), array.length()); + Class subType = arrayClass.getComponentType(); + for (int i = 0; i < array.length(); i++) { + try { + Object item = array.opt(i); + if(VKApiModel.class.isAssignableFrom(subType) && item instanceof JSONObject) { + VKApiModel model = (VKApiModel) subType.newInstance(); + item = model.parse((JSONObject) item); + } + Array.set(result, i, item); + } catch (InstantiationException e) { + throw new JSONException(e.getMessage()); + } catch (IllegalAccessException e) { + throw new JSONException(e.getMessage()); + } catch (IllegalArgumentException e) { + throw new JSONException(e.getMessage()); + } + } + return result; + } +} diff --git a/src/com/vk/sdk/api/model/TextUtils.java b/src/com/vk/sdk/api/model/TextUtils.java new file mode 100644 index 0000000..ddddf3e --- /dev/null +++ b/src/com/vk/sdk/api/model/TextUtils.java @@ -0,0 +1,26 @@ +package com.vk.sdk.api.model; + +/** + * + * @author aNNiMON + */ +class TextUtils { + + static boolean isEmpty(CharSequence text) { + return (text == null) || (text.length() == 0); + } + + static String formatDuration(int duration) { + final StringBuilder sb = new StringBuilder(); + final int hours = (duration / 3600); + if (hours != 0) sb.append(formatNumber(hours)).append(':'); + final int minutes = duration % 3600 / 60; + final int seconds = duration % 60; + sb.append(formatNumber(minutes)).append(':').append(formatNumber(seconds)); + return sb.toString(); + } + + private static String formatNumber(int number) { + return String.format("%02d", number); + } +} diff --git a/src/com/vk/sdk/api/model/VKApiApplicationContent.java b/src/com/vk/sdk/api/model/VKApiApplicationContent.java new file mode 100644 index 0000000..166e467 --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiApplicationContent.java @@ -0,0 +1,82 @@ +/** + * AppInfo.java + * vk-android-sdk + * + * Created by Babichev Vitaly on 19.01.14. + * Copyright (c) 2014 VK. All rights reserved. + */ +package com.vk.sdk.api.model; + +import org.json.JSONObject; +import static com.vk.sdk.api.model.VKAttachments.*; + +/** + * Describes information about application in the post. + */ +@SuppressWarnings("unused") +public class VKApiApplicationContent extends VKApiAttachment { + + /** + * ID of the application that posted on the wall; + */ + public int id; + + /** + * Application name + */ + public String name; + + /** + * Image URL for preview with maximum width in 130px + */ + public String photo_130; + + /** + * Image URL for preview with maximum width in 130px + */ + public String photo_604; + + /** + * Image URL for preview; + */ + public VKPhotoSizes photo = new VKPhotoSizes(); + + /** + * Fills an ApplicationContent instance from JSONObject. + */ + public VKApiApplicationContent parse(JSONObject source) { + id = source.optInt("id"); + name = source.optString("name"); + photo_130 = source.optString("photo_130"); + if(!TextUtils.isEmpty(photo_130)) { + photo.add(VKApiPhotoSize.create(photo_130, 130)); + } + photo_604 = source.optString("photo_604"); + if(!TextUtils.isEmpty(photo_604)) { + photo.add(VKApiPhotoSize.create(photo_604, 604)); + } + return this; + } + + /** + * Creates empty ApplicationContent instance. + */ + public VKApiApplicationContent() { + + } + + @Override + public CharSequence toAttachmentString() { + throw new UnsupportedOperationException("Attaching app info is not supported by VK.com API"); + } + + @Override + public String getType() { + return TYPE_APP; + } + + @Override + public int getId() { + return id; + } +} diff --git a/src/com/vk/sdk/api/model/VKApiArray.java b/src/com/vk/sdk/api/model/VKApiArray.java new file mode 100644 index 0000000..785be3a --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiArray.java @@ -0,0 +1,85 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +package com.vk.sdk.api.model; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +/** + * Base class for VK API arrays + */ +public abstract class VKApiArray extends VKApiModel { + private List items; + private int count; + + @Override + public VKApiModel parse(JSONObject object) { + try { + JSONArray jsonArray; + if ((jsonArray = object.optJSONArray("response")) == null) + { + object = object.getJSONObject("response"); + count = object.getInt("count"); + jsonArray = object.getJSONArray("items"); + } + parse(jsonArray); + + } catch (JSONException e) { + } + fields = object; + return this; + } + public void parse(JSONArray jsonArray) { + items = new ArrayList(jsonArray.length()); + for (int i = 0; i < jsonArray.length(); i++) { + try { + items.add(parseNextObject(jsonArray.getJSONObject(i))); + } catch (JSONException e) { + } + } + if (count == 0) + count = items.size(); + } + + protected T parseNextObject(JSONObject object) { + try { + T model = createObject(); + model.parse(object); + return model; + } catch (Exception ignored) + { + return null; + } + } + protected abstract T createObject(); + + public T get(int index) { + if (items == null) return null; + return items.get(index); + } + + public int size() { return count; } +} diff --git a/src/com/vk/sdk/api/model/VKApiAudio.java b/src/com/vk/sdk/api/model/VKApiAudio.java new file mode 100644 index 0000000..68dee5d --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiAudio.java @@ -0,0 +1,170 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +/** + * Audio.java + * vk-android-sdk + * + * Created by Babichev Vitaly on 19.01.14. + * Copyright (c) 2014 VK. All rights reserved. + */ +package com.vk.sdk.api.model; + +import org.json.JSONObject; +import static com.vk.sdk.api.model.VKAttachments.*; + +/** + * An audio object describes an audio file and contains the following fields. + */ +@SuppressWarnings("unused") +public class VKApiAudio extends VKApiAttachment implements Identifiable { + + /** + * Audio ID. + */ + public int id; + + /** + * Audio owner ID. + */ + public int owner_id; + + /** + * Artist name. + */ + public String artist; + + /** + * Audio file title. + */ + public String title; + + /** + * Duration (in seconds). + */ + public int duration; + + /** + * Link to mp3. + */ + public String url; + + /** + * ID of the lyrics (if available) of the audio file. + */ + public int lyrics_id; + + /** + * ID of the album containing the audio file (if assigned). + */ + public int album_id; + + /** + * Genre ID. See the list of audio genres. + */ + public int genre; + + /** + * An access key using for get information about hidden objects. + */ + public String access_key; + + /** + * Fills an Audio instance from JSONObject. + */ + public VKApiAudio parse(JSONObject from) { + id = from.optInt("id"); + owner_id = from.optInt("owner_id"); + artist = from.optString("artist"); + title = from.optString("title"); + duration = from.optInt("duration"); + url = from.optString("url"); + lyrics_id = from.optInt("lyrics_id"); + album_id = from.optInt("album_id"); + genre = from.optInt("genre_id"); + access_key = from.optString("access_key"); + return this; + } + + /** + * Creates empty Audio instance. + */ + public VKApiAudio() { + + } + + @Override + public int getId() { + return id; + } + + @Override + public CharSequence toAttachmentString() { + StringBuilder result = new StringBuilder(TYPE_AUDIO).append(owner_id).append('_').append(id); + if(!TextUtils.isEmpty(access_key)) { + result.append('_'); + result.append(access_key); + } + return result; + } + + @Override + public CharSequence toHtml() { + String d = TextUtils.formatDuration(duration); + return String.format("
%s - %s   (%s)

", artist, title, d); + } + + @Override + public String getType() { + return TYPE_AUDIO; + } + + /** + * Audio object genres. + */ + public final static class Genre { + + private Genre() {} + + public final static int ROCK = 1; + public final static int POP = 2; + public final static int RAP_AND_HIPHOP = 3; + public final static int EASY_LISTENING = 4; + public final static int DANCE_AND_HOUSE = 5; + public final static int INSTRUMENTAL = 6; + public final static int METAL = 7; + public final static int DUBSTEP = 8; + public final static int JAZZ_AND_BLUES = 9; + public final static int DRUM_AND_BASS = 10; + public final static int TRANCE = 11; + public final static int CHANSON = 12; + public final static int ETHNIC = 13; + public final static int ACOUSTIC_AND_VOCAL = 14; + public final static int REGGAE = 15; + public final static int CLASSICAL = 16; + public final static int INDIE_POP = 17; + public final static int OTHER = 18; + public final static int SPEECH = 19; + public final static int ALTERNATIVE = 21; + public final static int ELECTROPOP_AND_DISCO = 22; + } + +} diff --git a/src/com/vk/sdk/api/model/VKApiDocument.java b/src/com/vk/sdk/api/model/VKApiDocument.java new file mode 100644 index 0000000..b6038a5 --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiDocument.java @@ -0,0 +1,165 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +package com.vk.sdk.api.model; + +import org.json.JSONObject; +import static com.vk.sdk.api.model.VKAttachments.*; + +/** + * A document object describes a document file. + */ +@SuppressWarnings("unused") +public class VKApiDocument extends VKApiAttachment implements Identifiable { + + /** + * Document ID. + */ + public int id; + + /** + * ID of the user or group who uploaded the document. + */ + public int owner_id; + + /** + * Document title. + */ + public String title; + + /** + * Document size (in bytes). + */ + public long size; + + /** + * Document extension. + */ + public String ext; + + /** + * Document URL for downloading. + */ + public String url; + + /** + * URL of the 100x75px image (if the file is graphical). + */ + public String photo_100; + + /** + * URL of the 130x100px image (if the file is graphical). + */ + public String photo_130; + + /** + * Array of all photos. + */ + public VKPhotoSizes photo = new VKPhotoSizes(); + + /** + * An access key using for get information about hidden objects. + */ + public String access_key; + + private boolean mIsGif; + private boolean mIsImage; + + /** + * Fills a Doc instance from JSONObject. + */ + public VKApiDocument parse(JSONObject jo) { + id = jo.optInt("id"); + owner_id = jo.optInt("owner_id"); + title = jo.optString("title"); + size = jo.optLong("size"); + ext = jo.optString("ext"); + url = jo.optString("url"); + access_key = jo.optString("access_key"); + + photo_100 = jo.optString("photo_100"); + if(!TextUtils.isEmpty(photo_100)) { + photo.add(VKApiPhotoSize.create(photo_100, 100, 75)); + } + photo_130 = jo.optString("photo_130"); + if(!TextUtils.isEmpty(photo_130)) { + photo.add(VKApiPhotoSize.create(photo_130, 130, 100)); + } + photo.sort(); + return this; + } + + /** + * Creates empty Doc instance. + */ + public VKApiDocument() { + + } + + public boolean isImage() { + mIsImage = mIsImage || + "jpg".equals(ext) || + "jpeg".equals(ext) || + "png".equals(ext) || + "bmp".equals(ext); + return mIsImage; + } + + public boolean isGif() { + mIsGif = mIsGif || "gif".equals(ext); + return mIsGif; + } + + @Override + public int getId() { + return id; + } + + @Override + public String toString() { + return title; + } + + @Override + public CharSequence toAttachmentString() { + StringBuilder result = new StringBuilder(TYPE_DOC).append(owner_id).append('_').append(id); + if(!TextUtils.isEmpty(access_key)) { + result.append('_'); + result.append(access_key); + } + return result; + } + + @Override + public CharSequence toHtml() { + if (isGif() || isImage()) { + String preview = photo.get(photo.size() - 1).src; + return String.format("", url, preview); + } else { + return String.format("%s", url, title); + } + } + + @Override + public String getType() { + return TYPE_DOC; + } +} diff --git a/src/com/vk/sdk/api/model/VKApiLink.java b/src/com/vk/sdk/api/model/VKApiLink.java new file mode 100644 index 0000000..eed5c55 --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiLink.java @@ -0,0 +1,113 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +/** + * Link.java + * vk-android-sdk + * + * Created by Babichev Vitaly on 19.01.14. + * Copyright (c) 2014 VK. All rights reserved. + */ +package com.vk.sdk.api.model; + +import org.json.JSONObject; +import static com.vk.sdk.api.model.VKAttachments.*; + +/** + * A link object describes a link attachment + */ +@SuppressWarnings("unused") +public class VKApiLink extends VKAttachments.VKApiAttachment { + + /** + * Link URL + */ + public String url; + + /** + * Link title + */ + public String title; + + /** + * Link description; + */ + public String description; + + /** + * Image preview URL for the link (if any). + */ + public String image_src; + + /** + * ID wiki page with content for the preview of the page contents + * ID is returned as "ownerid_pageid". + */ + public String preview_page; + + /** + * Creates link attachment to attach it to the post + * @param url full URL of link + */ + public VKApiLink(String url) { + this.url = url; + } + + /** + * Fills a Link instance from JSONObject. + */ + public VKApiLink parse(JSONObject source) { + url = source.optString("url"); + title = source.optString("title"); + description = source.optString("description"); + image_src = source.optString("image_src"); + preview_page = source.optString("preview_page"); + return this; + } + + /** + * Creates empty Link instance. + */ + public VKApiLink() { + + } + + @Override + public CharSequence toAttachmentString() { + return url; + } + + @Override + public CharSequence toHtml() { + String preview = (TextUtils.isEmpty(image_src)) ? String.format("
", image_src) : ""; + return String.format("%s %s", url, title, preview); + } + + @Override + public String getType() { + return TYPE_LINK; + } + + @Override + public int getId() { + return 0; + } +} diff --git a/src/com/vk/sdk/api/model/VKApiMessage.java b/src/com/vk/sdk/api/model/VKApiMessage.java new file mode 100644 index 0000000..818b8cc --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiMessage.java @@ -0,0 +1,124 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +/** + * Message.java + * vk-android-sdk + * + * Created by Babichev Vitaly on 19.01.14. + * Copyright (c) 2014 VK. All rights reserved. + */ +package com.vk.sdk.api.model; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * A message object describes a private message + */ +@SuppressWarnings("unused") +public class VKApiMessage extends VKApiModel implements Identifiable { + + /** + * Message ID. (Not returned for forwarded messages), positive number + */ + public int id; + + /** + * For an incoming message, the user ID of the author. For an outgoing message, the user ID of the receiver. + */ + public int user_id; + + /** + * Date (in Unix time) when the message was sent. + */ + public long date; + + /** + * Message status (false — not read, true — read). (Not returned for forwarded messages.) + */ + public boolean read_state; + + /** + * Message type (false — received, true — sent). (Not returned for forwarded messages.) + */ + public boolean out; + + /** + * Title of message or chat. + */ + public String title; + + /** + * Body of the message. + */ + public String body; + + /** + * List of media-attachments; + */ + public VKAttachments attachments = new VKAttachments(); + + /** + * Array of forwarded messages (if any). + */ + public VKList fwd_messages; + + /** + * Whether the message contains smiles (false — no, true — yes). + */ + public boolean emoji; + + /** + * Whether the message is deleted (false — no, true — yes). + */ + public boolean deleted; + + /** + * Fills a Message instance from JSONObject. + */ + public VKApiMessage parse(JSONObject source) throws JSONException { + id = source.optInt("id"); + user_id = source.optInt("user_id"); + date = source.optLong("date"); + read_state = ParseUtils.parseBoolean(source, "read_state"); + out = ParseUtils.parseBoolean(source, "out"); + title = source.optString("title"); + body = source.optString("body"); + attachments .fill(source.optJSONArray("attachments")); + fwd_messages = new VKList(source.optJSONArray("fwd_messages"), VKApiMessage.class); + emoji = ParseUtils.parseBoolean(source, "emoji"); + deleted = ParseUtils.parseBoolean(source, "deleted"); + return this; + } + + /** + * Creates empty Country instance. + */ + public VKApiMessage() { + + } + + @Override + public int getId() { + return id; + } +} diff --git a/src/com/vk/sdk/api/model/VKApiModel.java b/src/com/vk/sdk/api/model/VKApiModel.java new file mode 100644 index 0000000..cde51f1 --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiModel.java @@ -0,0 +1,85 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +/** + * BaseModel.java + * vk-android-sdk + * + * Created by Babichev Vitaly on 06.01.14. + * Copyright (c) 2014 VK. All rights reserved. + */ +package com.vk.sdk.api.model; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Root class for all VK models. + * Any VK model supports Parcelable interface so you can pass it as extra. + * + * VK model is also allows you to store some object inside as a tag. + * These objects are retained by hard links, + * and never will be saved during parcelization. + */ +@SuppressWarnings("unused") +public abstract class VKApiModel { + + public JSONObject fields; + /** + * The model's tag. + */ + private Object mTag; + + /** + * Returns this model's tag. + * + * @return the Object stored in this model as a tag + * + * @see #setTag(Object) + * @see #getTag(int) + */ + public Object getTag() { + return mTag; + } + + /** + * Sets the tag associated with this model. A tag can be used to store + * data within a model without resorting to another data structure. + * + * @param tag an Object to tag the model with + * + * @see #getTag() + * @see #setTag(int, Object) + */ + public void setTag(Object tag) { + mTag = tag; + } + + /** + * Parses object from source. + * @param response server API object. + * @return this object. + * @throws JSONException if any critical error occurred while parsing. + */ + public VKApiModel parse(JSONObject response) throws JSONException { + return ParseUtils.parseViaReflection(this, response); + } +} diff --git a/src/com/vk/sdk/api/model/VKApiNote.java b/src/com/vk/sdk/api/model/VKApiNote.java new file mode 100644 index 0000000..f5b7c8e --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiNote.java @@ -0,0 +1,110 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +/** + * Note.java + * vk-android-sdk + * + * Created by Babichev Vitaly on 19.01.14. + * Copyright (c) 2014 VK. All rights reserved. + */ +package com.vk.sdk.api.model; + +import org.json.JSONObject; +import static com.vk.sdk.api.model.VKAttachments.*; + +/** + * A note object describes a note. + */ +@SuppressWarnings("unused") +public class VKApiNote extends VKAttachments.VKApiAttachment implements Identifiable { + + /** + * Note ID, positive number + */ + public int id; + + /** + * Note owner ID. + */ + public int user_id; + + /** + * Note title. + */ + public String title; + + /** + * Note text. + */ + public String text; + + /** + * Date (in Unix time) when the note was created. + */ + public long date; + + /** + * Number of comments. + */ + public int comments; + + /** + * Number of read comments (only if owner_id is the current user). + */ + public int read_comments; + + /** + * Fills a Note instance from JSONObject. + */ + public VKApiNote parse(JSONObject source) { + id = source.optInt("id"); + user_id = source.optInt("user_id"); + title = source.optString("title"); + text = source.optString("text"); + date = source.optLong("date"); + comments = source.optInt("comments"); + read_comments = source.optInt("read_comments"); + return this; + } + + /** + * Creates empty Note instance. + */ + public VKApiNote() { + + } + + @Override + public int getId() { + return id; + } + + @Override + public CharSequence toAttachmentString() { + return new StringBuilder(TYPE_NOTE).append(user_id).append('_').append(id); + } + + @Override + public String getType() { + return TYPE_NOTE; + } +} diff --git a/src/com/vk/sdk/api/model/VKApiPhoto.java b/src/com/vk/sdk/api/model/VKApiPhoto.java new file mode 100644 index 0000000..3bf620e --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiPhoto.java @@ -0,0 +1,227 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +package com.vk.sdk.api.model; + +import org.json.JSONArray; +import org.json.JSONObject; + +import static com.vk.sdk.api.model.ParseUtils.parseBoolean; +import static com.vk.sdk.api.model.ParseUtils.parseInt; +import static com.vk.sdk.api.model.VKAttachments.*; + +/** + * Describes a photo object from VK. + */ +public class VKApiPhoto extends VKAttachments.VKApiAttachment implements Identifiable { + + /** + * Photo ID, positive number + */ + public int id; + + /** + * Photo album ID. + */ + public int album_id; + + /** + * ID of the user or community that owns the photo. + */ + public int owner_id; + + /** + * Width (in pixels) of the original photo. + */ + public int width; + + /** + * Height (in pixels) of the original photo. + */ + public int height; + + /** + * Text describing the photo. + */ + public String text; + + /** + * Date (in Unix time) the photo was added. + */ + public long date; + + /** + * URL of image with maximum size 75x75px. + */ + public String photo_75; + + /** + * URL of image with maximum size 130x130px. + */ + public String photo_130; + + /** + * URL of image with maximum size 604x604px. + */ + public String photo_604; + + /** + * URL of image with maximum size 807x807px. + */ + public String photo_807; + + /** + * URL of image with maximum size 1280x1024px. + */ + public String photo_1280; + + /** + * URL of image with maximum size 2560x2048px. + */ + public String photo_2560; + + /** + * All photo thumbs in photo sizes. + * It has data even if server returned them without {@code PhotoSizes} format. + */ + public VKPhotoSizes src = new VKPhotoSizes(); + + /** + * Information whether the current user liked the photo. + */ + public boolean user_likes; + + /** + * Whether the current user can comment on the photo + */ + public boolean can_comment; + + /** + * Number of likes on the photo. + */ + public int likes; + + /** + * Number of comments on the photo. + */ + public int comments; + + /** + * Number of tags on the photo. + */ + public int tags; + + /** + * An access key using for get information about hidden objects. + */ + public String access_key; + + /** + * Fills a Photo instance from JSONObject. + */ + public VKApiPhoto parse(JSONObject from) { + album_id = from.optInt("album_id"); + date = from.optLong("date"); + height = from.optInt("height"); + width = from.optInt("width"); + owner_id = from.optInt("owner_id"); + id = from.optInt("id"); + text = from.optString("text"); + access_key = from.optString("access_key"); + + photo_75 = from.optString("photo_75"); + photo_130 = from.optString("photo_130"); + photo_604 = from.optString("photo_604"); + photo_807 = from.optString("photo_807"); + photo_1280 = from.optString("photo_1280"); + photo_2560 = from.optString("photo_2560"); + + JSONObject likes = from.optJSONObject("likes"); + this.likes = ParseUtils.parseInt(likes, "count"); + this.user_likes = ParseUtils.parseBoolean(likes, "user_likes"); + comments = parseInt(from.optJSONObject("comments"), "count"); + tags = parseInt(from.optJSONObject("tags"), "count"); + can_comment = parseBoolean(from, "can_comment"); + + src.setOriginalDimension(width, height); + JSONArray photo_sizes = from.optJSONArray("sizes"); + if(photo_sizes != null) { + src.fill(photo_sizes); + } else { + if(!TextUtils.isEmpty(photo_75)) { + src.add(VKApiPhotoSize.create(photo_75, VKApiPhotoSize.S, width, height)); + } + if(!TextUtils.isEmpty(photo_130)) { + src.add(VKApiPhotoSize.create(photo_130, VKApiPhotoSize.M, width, height)); + } + if(!TextUtils.isEmpty(photo_604)) { + src.add(VKApiPhotoSize.create(photo_604, VKApiPhotoSize.X, width, height)); + } + if(!TextUtils.isEmpty(photo_807)) { + src.add(VKApiPhotoSize.create(photo_807, VKApiPhotoSize.Y, width, height)); + } + if(!TextUtils.isEmpty(photo_1280)) { + src.add(VKApiPhotoSize.create(photo_1280, VKApiPhotoSize.Z, width, height)); + } + if(!TextUtils.isEmpty(photo_2560)) { + src.add(VKApiPhotoSize.create(photo_2560, VKApiPhotoSize.W, width, height)); + } + src.sort(); + } + return this; + } + + /** + * Creates empty Photo instance. + */ + public VKApiPhoto() { + + } + + @Override + public int getId() { + return id; + } + + @Override + public CharSequence toAttachmentString() { + StringBuilder result = new StringBuilder(TYPE_PHOTO).append(owner_id).append('_').append(id); + if(!TextUtils.isEmpty(access_key)) { + result.append('_'); + result.append(access_key); + } + return result; + } + + @Override + public CharSequence toHtml() { + final int size = src.size(); + VKApiPhotoSize small = src.get(0); + if (size > 1) small = src.get(1); + VKApiPhotoSize big = src.get(size - 1); + return String.format("", big.src, small.src); + } + + @Override + public String getType() { + return TYPE_PHOTO; + } +} diff --git a/src/com/vk/sdk/api/model/VKApiPhotoAlbum.java b/src/com/vk/sdk/api/model/VKApiPhotoAlbum.java new file mode 100644 index 0000000..fa891a5 --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiPhotoAlbum.java @@ -0,0 +1,170 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +package com.vk.sdk.api.model; + +import org.json.JSONArray; +import org.json.JSONObject; +import static com.vk.sdk.api.model.VKAttachments.*; + +/** + * Describes a photo album + */ +@SuppressWarnings("unused") +public class VKApiPhotoAlbum extends VKAttachments.VKApiAttachment implements Identifiable { + + /** + * URL for empty album cover with max width at 75px + */ + public final static String COVER_S = "http://vk.com/images/s_noalbum.png"; + + /** + * URL of empty album cover with max width at 130px + */ + public final static String COVER_M = "http://vk.com/images/m_noalbum.png"; + + /** + * URL of empty album cover with max width at 604px + */ + public final static String COVER_X = "http://vk.com/images/x_noalbum.png"; + + /** + * Album ID. + */ + public int id; + + /** + * Album title. + */ + public String title; + + /** + * Number of photos in the album. + */ + public int size; + + /** + * Privacy settings for the album. + */ + public int privacy; + + /** + * Album description. + */ + public String description; + + /** + * ID of the user or community that owns the album. + */ + public int owner_id; + + /** + * Whether a user can upload photos to this album(false — cannot, true — can). + */ + public boolean can_upload; + + /** + * Date (in Unix time) the album was last updated. + */ + public long updated; + + /** + * Album creation date (in Unix time). + */ + public long created; + + /** + * ID of the photo which is the cover. + */ + public int thumb_id; + + /** + * Link to album cover photo. + */ + public String thumb_src; + + /** + * Links to to cover photo. + */ + public VKPhotoSizes photo = new VKPhotoSizes(); + + /** + * Creates a PhotoAlbum instance from JSONObject. + */ + public VKApiPhotoAlbum parse(JSONObject from) { + id = from.optInt("id"); + thumb_id = from.optInt("thumb_id"); + owner_id = from.optInt("owner_id"); + title = from.optString("title"); + description = from.optString("description"); + created = from.optLong("created"); + updated = from.optLong("updated"); + size = from.optInt("size"); + can_upload = ParseUtils.parseBoolean(from, "can_upload"); + thumb_src = from.optString("thumb_src"); + if(from.has("privacy")) { + privacy = from.optInt("privacy"); + } else { + privacy = VKPrivacy.parsePrivacy(from.optJSONObject("privacy_view")); + } + JSONArray sizes = from.optJSONArray("sizes"); + if(sizes != null) { + photo.fill(sizes); + } else { + photo.add(VKApiPhotoSize.create(COVER_S, 75, 55)); + photo.add(VKApiPhotoSize.create(COVER_M, 130, 97)); + photo.add(VKApiPhotoSize.create(COVER_X, 432, 249)); + photo.sort(); + } + return this; + } + + /** + * Creates empty PhotoAlbum instance. + */ + public VKApiPhotoAlbum() { + + } + + public boolean isClosed() { + return privacy != VKPrivacy.PRIVACY_ALL; + } + + @Override + public int getId() { + return id; + } + + @Override + public String toString() { + return title; + } + + @Override + public CharSequence toAttachmentString() { + return new StringBuilder(TYPE_ALBUM).append(owner_id).append('_').append(id); + } + + @Override + public String getType() { + return TYPE_ALBUM; + } +} diff --git a/src/com/vk/sdk/api/model/VKApiPhotoSize.java b/src/com/vk/sdk/api/model/VKApiPhotoSize.java new file mode 100644 index 0000000..4edc88d --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiPhotoSize.java @@ -0,0 +1,322 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +/** + * PhotoSize.java + * VK Dev + * + * Created by Babichev Vitaly on 03.10.13. + * Copyright (c) 2013 VK. All rights reserved. + */ +package com.vk.sdk.api.model; + +import org.json.JSONObject; + +/** + * Describes an photo info in special format. + * + * Some methods returns information about copies of the original image in different sizes, + * Represented as an array of sizes, containing a description of the objects of this class. + * + * Sizes value example: + * + * Original photo — https://pp.vk.me/c323930/v323930021/53fb/1VrEC2eSkZQ.jpg,1280x856px, + * "width/height" ratio is 1.495327102803738 + * + * + sizes: [{ + src: http://cs323930.vk.me/v323930021/53f7/OwV0l2YFJ7s.jpg + width: 75, + height: 50, + type: 's' + }, { + src: http://cs323930.vk.me/v323930021/53f8/qX8MRNyUPqg.jpg, + width: 130, + height: 87, + type: 'm' + }, { + src: http://cs323930.vk.me/v323930021/53f9/7fBJyr9OHMA.jpg, + width: 604, + height: 404, + type: 'x' + }, { + src: http://cs323930.vk.me/v323930021/53fa/bskHpsuH6sM.jpg, + width: 807, + height: 540, + type: 'y' + }, { + src: http://cs323930.vk.me/v323930021/53fb/1VrEC2eSkZQ.jpg, + width: 1280, + height: 856, + type: 'z' + }, { + src: http://cs323930.vk.me/v323930021/53fc/iAl-TIHfRDY.jpg, + width: 130, + height: 87, + type: 'o' + }, { + src: http://cs323930.vk.me/v323930021/53fd/qjD0fbHkgmI.jpg, + width: 200, + height: 134, + type: 'p' + }, { + src: http://cs323930.vk.me/v323930021/53fe/3d2nCvvKQfw.jpg, + width: 320, + height: 214, + type: 'q' + }, { + src: http://cs323930.vk.me/v323930021/53ff/uK_Nj34SIY8.jpg, + width: 510, + height: 341, + type: 'r' + }] + * + * + */ +public class VKApiPhotoSize extends VKApiModel implements Comparable, Identifiable { + + /** + * Proportional copy with 75px max width + */ + public final static char S = 's'; + + /** + * Proportional copy with 130px max width + */ + public final static char M = 'm'; + + /** + * Proportional copy with 604px max width + */ + public final static char X = 'x'; + + /** + * Proportional copy with 807px max width + */ + public final static char Y = 'y'; + + /** + * If original image's "width/height" ratio is less or equal to 3:2, then proportional + * copy with 130px max width. If original image's "width/height" ratio is more than 3:2, + * then copy of cropped by left side image with 130px max width and 3:2 sides ratio. + */ + public final static char O = 'o'; + + /** + * If original image's "width/height" ratio is less or equal to 3:2, then proportional + * copy with 200px max width. If original image's "width/height" ratio is more than 3:2, + * then copy of cropped by left side image with 200px max width and 3:2 sides ratio. + */ + public final static char P = 'p'; + + /** + * If original image's "width/height" ratio is less or equal to 3:2, then proportional + * copy with 320px max width. If original image's "width/height" ratio is more than 3:2, + * then copy of cropped by left side image with 320px max width and 3:2 sides ratio. + */ + public final static char Q = 'q'; + + /** + * Proportional copy with 1280x1024px max size + */ + public final static char Z = 'z'; + + /** + * Proportional copy with 2560x2048px max size + */ + public final static char W = 'w'; + + /** + * Url of image + */ + public String src; + + /** + * Width of image in pixels + */ + public int width; + + /** + * Height of image in pixels + */ + public int height; + + /** + * Designation of size and proportions copy, @see {{@link #S}, {@link #M}, {@link #X}, {@link #O}, {@link #P}, {@link #Q}, {@link #Y}, {@link #Z}, {@link #W}} + */ + public char type; + + private VKApiPhotoSize() { + + } + + @Override + public int compareTo(VKApiPhotoSize another) { + // Так как основной превалирующий элемент в фотографиях именно ширина и все фотографии пропорциональны, + // то сравниваем именно по ней + return this.width < another.width ? -1 : (this.width == another.width ? 0 : 1); + } + + @Override + public int getId() { + return 0; + } + + /** + * Creates dimension from {@code source}. Used in parsing. + * If size is not specified copies calculates them based on internal algorithms. + * @param source object in format, returned VK API, which is generated from the dimension + * @param originalWidth original image width in pixels + * @param originalHeight original image height in pixels + */ + public static VKApiPhotoSize parse(JSONObject source, int originalWidth, int originalHeight) { + VKApiPhotoSize result = new VKApiPhotoSize(); + result.src = source.optString("src"); + result.width = source.optInt("width"); + result.height = source.optInt("height"); + String type = source.optString("type"); + if(!TextUtils.isEmpty(type)) { + result.type = type.charAt(0); + } + // Казалось бы, теперь можно с чистой советью закончить метод. + // Но нет, оказывается, width и height не просчитывается на некоторых серверах ВК. + // Приходится гадать на кофейной гуще. + if(result.width == 0 || result.height == 0) { + fillDimensions(result, originalWidth, originalHeight); + } + return result; + } + + /* + * Устанавливает размерность исходя из размеров оригинала и типа изображения. + */ + private static void fillDimensions(VKApiPhotoSize result, int originalWidth, int originalHeight) { + float ratio = (float) originalWidth / originalHeight; + switch (result.type) { + case S: { + fillDimensionSMXY(result, ratio, Math.min(originalWidth, 75)); + } break; + case M: { + fillDimensionSMXY(result, ratio, Math.min(originalWidth, 130)); + } break; + case X: { + fillDimensionSMXY(result, ratio, Math.min(originalWidth, 604)); + } break; + case Y: { + fillDimensionSMXY(result, ratio, Math.min(originalWidth, 807)); + } break; + case O: { + fillDimensionOPQ(result, ratio, Math.min(originalWidth, 130)); + } break; + case P: { + fillDimensionOPQ(result, ratio, Math.min(originalWidth, 200)); + } break; + case Q: { + fillDimensionOPQ(result, ratio, Math.min(originalWidth, 320)); + } break; + case Z: { + fillDimensionZW(result, ratio, Math.min(originalWidth, 1280), Math.min(originalHeight, 1024)); + } break; + case W: { + fillDimensionZW(result, ratio, Math.min(originalWidth, 2560), Math.min(originalHeight, 2048)); + } break; + } + } + + /* + * Про S, M, X, Y известно, про копия обязательно пропорциональна, а ширина не должна превышать заданную. + * Это значит, что для всех случаев(кроме тех, когда ширина картинки меньше указанной) соотношения + * сторон картинка впишется пропорционально по ширине. + */ + private static void fillDimensionSMXY(VKApiPhotoSize result, float ratio, int width) { + result.width = width; + result.height = (int) Math.ceil(result.width / ratio); + } + + /* + * Пропорциональная ширина. В принципе, все, что было сказано к предыдущему, верно и здесь, + * за исключением того, что высота здесь не может превышать ширину * 1,5f + */ + private static void fillDimensionOPQ(VKApiPhotoSize result, float ratio, int width) { + fillDimensionSMXY(result, Math.min(1.5f, ratio), width); + } + + /* + * А здесь просто берем одну сторону за фактическую и исходя из нее вычисляем другую. + */ + private static void fillDimensionZW(VKApiPhotoSize result, float ratio, int allowedWidth, int allowedHeight) { + if(ratio > 1) { // ширина больше высоты + result.width = allowedWidth; + result.height = (int) (result.width / ratio); + } else { + result.height = allowedHeight; + result.width = (int) (result.height * ratio); + } + } + + /** + * Creates a dimension with explicit dimensions. + * Can be helpful if the dimensions are exactly known. + */ + public static VKApiPhotoSize create(String url, int width, int height) { + VKApiPhotoSize result = new VKApiPhotoSize(); + result.src = url; + result.width = width; + result.height = height; + float ratio = width / (float) height ; + if(width <= 75) { + result.type = S; + } else if(width <= 130) { + result.type = ratio <= 1.5f ? O : M; + } else if(width <= 200 && ratio <= 1.5f) { + result.type = P; + } else if(width <= 320 && ratio <= 1.5f) { + result.type = Q; + } else if(width <= 604 ) { + result.type = X; + } else if(width <= 807) { + result.type = Y; + } else if(width <= 1280 && height <= 1024) { + result.type = Z; + } else if(width <= 2560 && height <= 2048) { + result.type = W; + } + return result; + } + + /** + * Creates a dimension type and size of the original. + */ + public static VKApiPhotoSize create(String url, char type, int originalWidth, int originalHeight) { + VKApiPhotoSize result = new VKApiPhotoSize(); + result.src = url; + result.type = type; + fillDimensions(result, originalWidth, originalHeight); + return result; + } + + /** + * Creates a square dimension type and size of the original. + */ + public static VKApiPhotoSize create(String url, int dimension) { + return create(url, dimension, dimension); + } +} \ No newline at end of file diff --git a/src/com/vk/sdk/api/model/VKApiPoll.java b/src/com/vk/sdk/api/model/VKApiPoll.java new file mode 100644 index 0000000..27908b5 --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiPoll.java @@ -0,0 +1,149 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +/** + * PollAttachment.java + * vk-android-sdk + * + * Created by Babichev Vitaly on 19.01.14. + * Copyright (c) 2014 VK. All rights reserved. + */ +package com.vk.sdk.api.model; + +import org.json.JSONObject; +import static com.vk.sdk.api.model.VKAttachments.*; + +/** + * Describes poll on the wall on board. + */ +@SuppressWarnings("unused") +public class VKApiPoll extends VKAttachments.VKApiAttachment { + + /** + * Poll ID to get information about it using polls.getById method; + */ + public int id; + + /** + * ID of the user or community that owns this poll. + */ + public int owner_id; + + /** + * Date (in Unix time) the poll was created. + */ + public long created; + + /** + * Question in the poll. + */ + public String question; + + /** + * The total number of users answered. + */ + public int votes; + + /** + * Response ID of the current user(if the current user has not yet posted in this poll, it contains 0) + */ + public int answer_id; + + /** + * Array of answers for this question. + */ + public VKList answers; + + /** + * Fills a Poll instance from JSONObject. + */ + public VKApiPoll parse(JSONObject source) { + id = source.optInt("id"); + owner_id = source.optInt("owner_id"); + created = source.optLong("created"); + question = source.optString("question"); + votes = source.optInt("votes"); + answer_id = source.optInt("answer_id"); + answers = new VKList(source.optJSONArray("answers"), Answer.class); + return this; + } + + /** + * Creates empty Country instance. + */ + public VKApiPoll() { + + } + + @Override + public CharSequence toAttachmentString() { + return null; + } + + @Override + public String getType() { + return TYPE_POLL; + } + + @Override + public int getId() { + return id; + } + + /** + * Represents answer for the poll + */ + public final static class Answer extends VKApiModel implements Identifiable { + + /** + * ID of the answer for the question + */ + public int id; + + /** + * Text of the answer + */ + public String text; + + /** + * Number of users that voted for this answer + */ + public int votes; + + /** + * Rate of this answer in percent + */ + public double rate; + + public Answer parse(JSONObject source) { + id = source.optInt("id"); + text = source.optString("text"); + votes = source.optInt("votes"); + rate = source.optDouble("rate"); + return this; + } + + @Override + public int getId() { + return id; + } + } +} diff --git a/src/com/vk/sdk/api/model/VKApiPost.java b/src/com/vk/sdk/api/model/VKApiPost.java new file mode 100644 index 0000000..e1bdfb5 --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiPost.java @@ -0,0 +1,209 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +/** + * Post.java + * vk-android-sdk + * + * Created by Babichev Vitaly on 19.01.14. + * Copyright (c) 2014 VK. All rights reserved. + */ +package com.vk.sdk.api.model; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * A post object describes a wall post. + */ +@SuppressWarnings("unused") +public class VKApiPost extends VKAttachments.VKApiAttachment implements Identifiable { + + /** + * Post ID on the wall, positive number + */ + public int id; + + /** + * Wall owner ID. + */ + public int to_id; + + /** + * ID of the user who posted. + */ + public int from_id; + + /** + * Date (in Unix time) the post was added. + */ + public long date; + + /** + * Text of the post. + */ + public String text; + + /** + * ID of the wall owner the post to which the reply is addressed (if the post is a reply to another wall post). + */ + public int reply_owner_id; + + /** + * ID of the wall post to which the reply is addressed (if the post is a reply to another wall post). + */ + public int reply_post_id; + + /** + * True, if the post was created with "Friends only" option. + */ + public boolean friends_only; + + /** + * Number of comments. + */ + public int comments_count; + + /** + * Whether the current user can leave comments to the post (false — cannot, true — can) + */ + public boolean can_post_comment; + + /** + * Number of users who liked the post. + */ + public int likes_count; + + /** + * Whether the user liked the post (false — not liked, true — liked) + */ + public boolean user_likes; + + /** + * Whether the user can like the post (false — cannot, true — can). + */ + public boolean can_like; + + /** + * Whether the user can repost (false — cannot, true — can). + */ + public boolean can_publish; + + /** + * Number of users who copied the post. + */ + public int reposts_count; + + /** + * Whether the user reposted the post (false — not reposted, true — reposted). + */ + public boolean user_reposted; + + /** + * Type of the post, can be: post, copy, reply, postpone, suggest. + */ + public String post_type; + + /** + * Information about attachments to the post (photos, links, etc.), if any; + */ + public VKAttachments attachments = new VKAttachments(); + + /** + * ID of the author (if the post was published by a community and signed by a user). + */ + public int signer_id; + + /** + * List of history of the reposts. + */ + public VKList copy_history; + + /** + * Fills a Post instance from JSONObject. + */ + public VKApiPost parse(JSONObject source) throws JSONException { + id = source.optInt("id"); + to_id = source.optInt("to_id"); + from_id = source.optInt("from_id"); + date = source.optLong("date"); + text = source.optString("text"); + reply_owner_id = source.optInt("reply_owner_id"); + reply_post_id = source.optInt("reply_post_id"); + friends_only = ParseUtils.parseBoolean(source, "friends_only"); + JSONObject comments = source.optJSONObject("comments"); + if(comments != null) { + comments_count = comments.optInt("count"); + can_post_comment = ParseUtils.parseBoolean(comments, "can_post"); + } + JSONObject likes = source.optJSONObject("likes"); + if(likes != null) { + likes_count = likes.optInt("count"); + user_likes = ParseUtils.parseBoolean(likes, "user_likes"); + can_like = ParseUtils.parseBoolean(likes, "can_like"); + can_publish = ParseUtils.parseBoolean(likes, "can_publish"); + } + JSONObject reposts = source.optJSONObject("reposts"); + if(reposts != null) { + reposts_count = reposts.optInt("count"); + user_reposted = ParseUtils.parseBoolean(reposts, "user_reposted"); + } + post_type = source.optString("post_type"); + attachments.fill(source.optJSONArray("attachments")); + signer_id = source.optInt("signer_id"); + copy_history = new VKList(source.optJSONArray("copy_history"), VKApiPost.class); + return this; + } + + /** + * Creates empty Post instance. + */ + public VKApiPost() { + + } + + @Override + public int getId() { + return id; + } + + @Override + public CharSequence toAttachmentString() { + return new StringBuilder(VKAttachments.TYPE_POST).append(to_id).append('_').append(id); + } + + @Override + public CharSequence toHtml() { + String url = "http://vk.com/" + VKAttachments.TYPE_POST + to_id + "_" + id; + StringBuilder att = new StringBuilder(); + att.append("
"); + attachments.forEach(p -> att.append(p.toHtml())); + att.append("
"); + return String.format("
", url, text, att); + } + + @Override + public String getType() { + return VKAttachments.TYPE_POST; + } +} diff --git a/src/com/vk/sdk/api/model/VKApiPostedPhoto.java b/src/com/vk/sdk/api/model/VKApiPostedPhoto.java new file mode 100644 index 0000000..4344b7b --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiPostedPhoto.java @@ -0,0 +1,59 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +/** + * PostdPhoto.java + * vk-android-sdk + * + * Created by Babichev Vitaly on 19.01.14. + * Copyright (c) 2014 VK. All rights reserved. + */ +package com.vk.sdk.api.model; + +import org.json.JSONObject; +import static com.vk.sdk.api.model.VKAttachments.*; + +/** + * Subclass to directly uploaded wall photo. + */ +@SuppressWarnings("unused") +public class VKApiPostedPhoto extends VKApiPhoto { + + /** + * Fills a PostedPhoto instance from JSONObject. + */ + public VKApiPostedPhoto parse(JSONObject from) { + super.parse(from); + return this; + } + + /** + * Creates empty PostedPhoto instance. + */ + public VKApiPostedPhoto() { + + } + + @Override + public String getType() { + return TYPE_POSTED_PHOTO; + } +} diff --git a/src/com/vk/sdk/api/model/VKApiVideo.java b/src/com/vk/sdk/api/model/VKApiVideo.java new file mode 100644 index 0000000..25129e2 --- /dev/null +++ b/src/com/vk/sdk/api/model/VKApiVideo.java @@ -0,0 +1,280 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +/** + * Video.java + * VK Dev + * + * Created by Babichev Vitaly on 29.09.13. + * Copyright (c) 2013 VK. All rights reserved. + */ +package com.vk.sdk.api.model; + +import org.json.JSONObject; + +import static com.vk.sdk.api.model.ParseUtils.parseBoolean; +import static com.vk.sdk.api.model.VKAttachments.*; + +/** + * A video object describes an video file. + */ +@SuppressWarnings("unused") +public class VKApiVideo extends VKAttachments.VKApiAttachment implements Identifiable { + + /** + * Video ID. + */ + public int id; + + /** + * Video owner ID. + */ + public int owner_id; + + /** + * Video album ID. + */ + public int album_id; + + /** + * Video title. + */ + public String title; + + /** + * Text describing video. + */ + public String description; + + /** + * Duration of the video in seconds. + */ + public int duration; + + /** + * String with video+vid key. + */ + public String link; + + /** + * Date when the video was added, as unixtime. + */ + public long date; + + /** + * Number of views of the video. + */ + public int views; + + /** + * URL of the page with a player that can be used to play a video in the browser. + * Flash and HTML5 video players are supported; the player is always zoomed to fit + * the window size. + */ + public String player; + + /** + * URL of the video cover image with the size of 130x98px. + */ + public String photo_130; + + /** + * URL of the video cover image with the size of 320x240px. + */ + public String photo_320; + + /** + * URL of the video cover image with the size of 640x480px (if available). + */ + public String photo_640; + + /** + * Array of all photos. + */ + public VKPhotoSizes photo = new VKPhotoSizes(); + + /** + * An access key using for get information about hidden objects. + */ + public String access_key; + + /** + * Number of comments on the video. + */ + public int comments; + + /** + * Whether the current user can comment on the video + */ + public boolean can_comment; + + /** + * Whether the current user can repost this video + */ + public boolean can_repost; + + /** + * Information whether the current user liked the video. + */ + public boolean user_likes; + + /** + * Information whether the the video should be repeated. + */ + public boolean repeat; + + /** + * Number of likes on the video. + */ + public int likes; + + /** + * Privacy to view of this video. + */ + public int privacy_view; + + /** + * Privacy to comment of this video. + */ + public int privacy_comment; + + /** + * URL of video with height of 240 pixels. Returns only if you use direct auth. + */ + public String mp4_240; + + /** + * URL of video with height of 360 pixels. Returns only if you use direct auth. + */ + public String mp4_360; + + /** + * URL of video with height of 480 pixels. Returns only if you use direct auth. + */ + public String mp4_480; + + /** + * URL of video with height of 720 pixels. Returns only if you use direct auth. + */ + public String mp4_720; + + /** + * URL of the external video link. + */ + public String external; + + /** + * Fills a Video instance from JSONObject. + */ + public VKApiVideo parse(JSONObject from) { + id = from.optInt("id"); + owner_id = from.optInt("owner_id"); + title = from.optString("title"); + description = from.optString("description"); + duration = from.optInt("duration"); + link = from.optString("link"); + date = from.optLong("date"); + views = from.optInt("views"); + comments = from.optInt("comments"); + player = from.optString("player"); + access_key = from.optString("access_key"); + album_id = from.optInt("album_id"); + + JSONObject likes = from.optJSONObject("likes"); + if(likes != null) { + this.likes = likes.optInt("count"); + user_likes = parseBoolean(likes, "user_likes"); + } + can_comment = parseBoolean(from, "can_comment"); + can_repost = parseBoolean(from, "can_repost"); + repeat = parseBoolean(from, "repeat"); + + privacy_view = VKPrivacy.parsePrivacy(from.optJSONObject("privacy_view")); + privacy_comment = VKPrivacy.parsePrivacy(from.optJSONObject("privacy_comment")); + + JSONObject files = from.optJSONObject("files"); + if(files != null) { + mp4_240 = files.optString("mp4_240"); + mp4_360 = files.optString("mp4_360"); + mp4_480 = files.optString("mp4_480"); + mp4_720 = files.optString("mp4_720"); + external = files.optString("external"); + } + + photo_130 = from.optString("photo_130"); + if(!TextUtils.isEmpty(photo_130)) { + photo.add(VKApiPhotoSize.create(photo_130, 130)); + } + + photo_320 = from.optString("photo_320"); + if(!TextUtils.isEmpty(photo_320)) { + photo.add(VKApiPhotoSize.create(photo_320, 320)); + } + + photo_640 = from.optString("photo_640"); + if(!TextUtils.isEmpty(photo_640)) { + photo.add(VKApiPhotoSize.create(photo_640, 640)); + } + + photo.sort(); + return this; + } + + /** + * Creates empty Video instance. + */ + public VKApiVideo() { + + } + + @Override + public int getId() { + return id; + } + + @Override + public CharSequence toAttachmentString() { + StringBuilder result = new StringBuilder(TYPE_VIDEO).append(owner_id).append('_').append(id); + if(!TextUtils.isEmpty(access_key)) { + result.append('_'); + result.append(access_key); + } + return result; + } + + @Override + public CharSequence toHtml() { + String preview = photo.get(0).src; + String url = "http://vk.com/" + TYPE_VIDEO + owner_id + "_" + id; + String d = TextUtils.formatDuration(duration); + return String.format("
%s (%s)
", url, preview, title, d); + } + + @Override + public String getType() { + return TYPE_VIDEO; + } + + @Override + public String toString() { + return title; + } +} diff --git a/src/com/vk/sdk/api/model/VKAttachments.java b/src/com/vk/sdk/api/model/VKAttachments.java new file mode 100644 index 0000000..788ab7b --- /dev/null +++ b/src/com/vk/sdk/api/model/VKAttachments.java @@ -0,0 +1,209 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +/** + * VKAttachments.java + * vk-android-sdk + * + * Created by Babichev Vitaly on 01.02.14. + * Copyright (c) 2014 VK. All rights reserved. + */ +package com.vk.sdk.api.model; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A list of attachments in {@link VKApiComment}, {@link VKApiPost}, {@link VKApiMessage} + */ +public class VKAttachments extends VKList { + + /** + * Attachment is a photo. + * @see {@link VKApiPhoto} + */ + public static final String TYPE_PHOTO = "photo"; + + /** + * Attachment is a video. + * @see {@link VKApiVideo} + */ + public static final String TYPE_VIDEO = "video"; + + /** + * Attachment is an audio. + * @see {@link VKApiAudio} + */ + public static final String TYPE_AUDIO = "audio"; + + /** + * Attachment is a document. + * @see {@link VKApiDocument} + */ + public static final String TYPE_DOC = "doc"; + + /** + * Attachment is a wall post. + * @see {@link VKApiPost} + */ + public static final String TYPE_POST = "wall"; + + /** + * Attachment is a posted photo. + * @see {@link VKApiPostedPhoto} + */ + public static final String TYPE_POSTED_PHOTO = "posted_photo"; + + /** + * Attachment is a link + * @see {@link VKApiLink} + */ + public static final String TYPE_LINK = "link"; + + /** + * Attachment is a note + * @see {@link VKApiNote} + */ + public static final String TYPE_NOTE = "note"; + + /** + * Attachment is an application content + * @see {@link VKApiApplicationContent} + */ + public static final String TYPE_APP = "app"; + + /** + * Attachment is a poll + * @see {@link VKApiPoll} + */ + public static final String TYPE_POLL = "poll"; + + /** + * Attachment is a WikiPage + * @see {@link VKApiWikiPage} + */ + public static final String TYPE_WIKI_PAGE = "page"; + + /** + * Attachment is a PhotoAlbum + * @see {@link VKApiPhotoAlbum} + */ + public static final String TYPE_ALBUM = "album"; + + + public VKAttachments() { + super(); + } + + public VKAttachments(VKApiAttachment... data) { + super(Arrays.asList(data)); + } + + public VKAttachments(List data) { + super(data); + } + + public VKAttachments(JSONObject from) { + super(); + fill(from); + } + + public VKAttachments(JSONArray from) { + super(); + fill(from); + } + + public void fill(JSONObject from) { + super.fill(from, parser); + } + + public void fill(JSONArray from) { + super.fill(from, parser); + } + + public String toAttachmentsString() { + ArrayList attachments = new ArrayList<>(); + this.stream().forEach((attach) -> { + attachments.add(attach.toAttachmentString()); + }); + return String.join(",", attachments); + } + /** + * Parser that's used for parsing photo sizes. + */ + private final Parser parser = new Parser() { + @Override + public VKApiAttachment parseObject(JSONObject attachment) throws Exception { + String type = attachment.optString("type"); + if(null != type) switch (type) { + case TYPE_PHOTO: + return new VKApiPhoto().parse(attachment.getJSONObject(TYPE_PHOTO)); + case TYPE_VIDEO: + return new VKApiVideo().parse(attachment.getJSONObject(TYPE_VIDEO)); + case TYPE_AUDIO: + return new VKApiAudio().parse(attachment.getJSONObject(TYPE_AUDIO)); + case TYPE_DOC: + return new VKApiDocument().parse(attachment.getJSONObject(TYPE_DOC)); + case TYPE_POST: + return new VKApiPost().parse(attachment.getJSONObject(TYPE_POST)); + case TYPE_POSTED_PHOTO: + return new VKApiPostedPhoto().parse(attachment.getJSONObject(TYPE_POSTED_PHOTO)); + case TYPE_LINK: + return new VKApiLink().parse(attachment.getJSONObject(TYPE_LINK)); + case TYPE_NOTE: + return new VKApiNote().parse(attachment.getJSONObject(TYPE_NOTE)); + case TYPE_APP: + return new VKApiApplicationContent().parse(attachment.getJSONObject(TYPE_APP)); + case TYPE_POLL: + return new VKApiPoll().parse(attachment.getJSONObject(TYPE_POLL)); + case TYPE_ALBUM: + return new VKApiPhotoAlbum().parse(attachment.getJSONObject(TYPE_ALBUM)); + } + return null; + } + }; + + + /** + * An abstract class for all attachments + */ + @SuppressWarnings("unused") + public abstract static class VKApiAttachment extends VKApiModel implements Identifiable { + + /** + * Convert attachment to special string to attach it to the post, message or comment. + */ + public abstract CharSequence toAttachmentString(); + + public CharSequence toHtml() { + return toAttachmentString(); + } + + /** + * @return type of this attachment + */ + public abstract String getType(); + } +} diff --git a/src/com/vk/sdk/api/model/VKList.java b/src/com/vk/sdk/api/model/VKList.java new file mode 100644 index 0000000..c260296 --- /dev/null +++ b/src/com/vk/sdk/api/model/VKList.java @@ -0,0 +1,417 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +package com.vk.sdk.api.model; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.regex.Pattern; + +/** + * Universal data list for VK API. + * This class is not thread-safe. + * @param type of stored values. + * @see http://vk.com/dev/list + */ +@SuppressWarnings({"unchecked", "UnusedDeclaration"}) +public class VKList extends VKApiModel implements java.util.List { + + /** + * The server did not return the count field. + */ + private final static int NO_COUNT = -1; + + /** + * Decorated list + */ + private ArrayList items = new ArrayList<>(); + + /** + * Field {@code count} which returned by server. + */ + private int count = NO_COUNT; + + /** + * Creates empty list. + */ + public VKList() { + + } + + /** + * Creates list and fills it according with given data. + */ + public VKList(java.util.List data) { + assert data != null; + items = new ArrayList(data); + } + + /** + * Creates list and fills it according with data in {@code from}. + * @param from an object that represents a list adopted in accordance with VK API format. You can use null. + * @param clazz class represents a model that has a public constructor with {@link org.json.JSONObject} argument. + */ + public VKList(JSONObject from, Class clazz) { + fill(from, clazz); + } + + /** + * Creates list and fills it according with data in {@code from}. + * @param from an array of items in the list. You can use null. + * @param clazz class represents a model that has a public constructor with {@link org.json.JSONObject} argument. + */ + public VKList(JSONArray from, Class clazz) { + fill(from, clazz); + } + + /** + * Creates list and fills it according with data in {@code from}. + * @param from an object that represents a list adopted in accordance with VK API format. You can use null. + * @param creator interface implementation to parse objects. + */ + public VKList(JSONObject from, Parser creator) { + + fill(from, creator); + } + + /** + * Creates list and fills it according with data in {@code from}. + * @param from an array of items in the list. You can use null. + * @param creator interface implementation to parse objects. + */ + public VKList(JSONArray from, Parser creator) { + + fill(from, creator); + } + + /** + * Fills list according with data in {@code from}. + * @param from an object that represents a list adopted in accordance with VK API format. You can use null. + * @param clazz class represents a model that has a public constructor with {@link org.json.JSONObject} argument. + */ + public void fill(JSONObject from, Class clazz) { + if (from.has("response")) { + JSONArray array = from.optJSONArray("response"); + if (array != null) { + fill(array, clazz); + } + else { + fill(from.optJSONObject("response"), clazz); + } + } else { + fill(from, new ReflectParser(clazz)); + } + } + + /** + * Creates list and fills it according with data in {@code from}. + * @param from an array of items in the list. You can use null. + * @param clazz class represents a model that has a public constructor with {@link org.json.JSONObject} argument. + */ + public void fill(JSONArray from, Class clazz) { + fill(from, new ReflectParser(clazz)); + } + + /** + * Fills list according with data in {@code from}. + * @param from an object that represents a list adopted in accordance with VK API format. You can use null. + * @param creator interface implementation to parse objects. + */ + public void fill(JSONObject from, Parser creator) { + if(from != null) { + fill(from.optJSONArray("items"), creator); + count = from.optInt("count", count); + } + } + + /** + * Fills list according with data in {@code from}. + * @param from an array of items in the list. You can use null. + * @param creator interface implementation to parse objects. + */ + public void fill(JSONArray from, Parser creator) { + if(from != null) { + for(int i = 0; i < from.length(); i++) { + try { + T object = creator.parseObject(from.getJSONObject(i)); + if(object != null) { + items.add(object); + } + } catch (Exception e) { + } + } + } + } + + /** + * Adds the element before the element with the specified id. + * If an element with the specified id is not found, adds an element to the end of the list. + * @param id element identifier to add element before it. + * @param data element to add + */ + public void addBefore(int id, T data) { + int size = size(); + for(int i = 0; i < size; i++) { + if(get(i).getId() > id || i == size - 1) { + add(i, data); + break; + } + } + } + + /** + * Adds the element after the element with the specified id. + * If an element with the specified id is not found, adds an element to the end of the list. + * @param id element identifier to add element after it. + * @param data element to add + */ + public void addAfter(int id, T data) { + int size = size(); + for(int i = 0; i < size; i++) { + if(get(i).getId() > id || i == size - 1) { + add(i + 1, data); + break; + } + } + } + + /** + * Returns element according with id. + * If nothing found, returns null. + */ + public T getById(int id) { + for(T item: this) { + if(item.getId() == id) { + return item; + } + } + return null; + } + + /** + * Searches through the list of available items.
+ *
+ * The search will be carried out not by the content of characters per line, and the content of them in separate words.
+ *
+ * Search is not case sensitive.
+ *
+ * To support search class {@code T} must have overridden method {@link #toString()}, + * search will be carried out exactly according to the result of calling this method.
+ *
+ *
+ * Suppose there are elements in the list of contents: + *
+     * - Hello world
+     * - Hello test
+     * 
+ * In this case, the matches will be on search phrases {@code 'Hel'}, {@code 'Hello'}, {@code 'test'}, but not on {@code 'llo'}, {@code 'llo world'} + * + * @param query search query can not be equal to {@code null}, but can be an empty string. + * @return created based on the search results new list. If no matches are found, the list will be empty. + */ + public VKList search(String query) { + VKList result = new VKList(); + final Pattern pattern = Pattern.compile("(?i).*\\b" + query + ".*"); + for (T item : this) { + if (pattern.matcher(item.toString()).find()) { + result.add(item); + } + } + return result; + } + + /** + * Returns the return value of the field VK API {@code count}, if it has been returned, and the size of the list, if not. + */ + public int getCount() { + return count != NO_COUNT ? count : size(); + } + + @Override + public void add(int location, T object) { + items.add(location, object); + } + + @Override + public boolean add(T object) { + return items.add(object); + } + + @Override + public boolean addAll(int location, Collection collection) { + return items.addAll(location, collection); + } + + @Override + public boolean addAll(Collection collection) { + return items.addAll(collection); + } + + @Override + public void clear() { + items.clear(); + } + + @Override + public boolean contains(Object object) { + return items.contains(object); + } + + @Override + public boolean containsAll(Collection collection) { + assert collection != null; + return items.containsAll(collection); + } + + @Override + public boolean equals(Object object) { + return ((Object) this).getClass().equals(object.getClass()) && items.equals(object); + } + + @Override + public T get(int location) { + return items.get(location); + } + + @Override + public int indexOf(Object object) { + return items.indexOf(object); + } + + @Override + public boolean isEmpty() { + return items.isEmpty(); + } + + @Override + public Iterator iterator() { + return items.iterator(); + } + + @Override + public int lastIndexOf(Object object) { + return items.lastIndexOf(object); + } + + + @Override + public ListIterator listIterator() { + return items.listIterator(); + } + + @Override + public ListIterator listIterator(int location) { + return items.listIterator(location); + } + + @Override + public T remove(int location) { + return items.remove(location); + } + + @Override + public boolean remove(Object object) { + return items.remove(object); + } + + @Override + public boolean removeAll(Collection collection) { + assert collection != null; + return items.removeAll(collection); + } + + @Override + public boolean retainAll(Collection collection) { + return items.retainAll(collection); + } + + @Override + public T set(int location, T object) { + return items.set(location, object); + } + + @Override + public int size() { + return items.size(); + } + + @Override + public java.util.List subList(int start, int end) { + return items.subList(start, end); + } + + @Override + public Object[] toArray() { + return items.toArray(); + } + + @Override + public T1[] toArray(T1[] array) { + assert array != null; + return items.toArray(array); + } + + /** + * Used when parsing the list objects as interator created from {@link org.json.JSONArray} a instances of items of the list. + * @param list item type. + */ + public static interface Parser { + + /** + * Creates a list item of its representation return VK API from {@link org.json.JSONArray} + * @param source representation of the object in the format returned by VK API. + * @return created element to add to the list. + * @throws Exception if the exception is thrown, the element iterated this method will not be added to the list. + */ + D parseObject(JSONObject source) throws Exception; + } + + /** + * Parser list items using reflection mechanism. + * To use an object class must have a public constructor that accepts {@link org.json.JSONObject}. + * If, during the creation of the object constructor will throw any exception, the element will not be added to the list. + * @param list item type. + */ + public final static class ReflectParser implements Parser { + + private final Class clazz; + + public ReflectParser(Class clazz) { + this.clazz = clazz; + } + + @Override + public D parseObject(JSONObject source) throws Exception { + return (D) clazz.newInstance().parse(source); + } + } + + @Override + public VKApiModel parse(JSONObject response) throws JSONException { + throw new JSONException("Operation is not supported while class is generic"); + } +} \ No newline at end of file diff --git a/src/com/vk/sdk/api/model/VKPhotoArray.java b/src/com/vk/sdk/api/model/VKPhotoArray.java new file mode 100644 index 0000000..c44669b --- /dev/null +++ b/src/com/vk/sdk/api/model/VKPhotoArray.java @@ -0,0 +1,36 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +package com.vk.sdk.api.model; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Array of API photos objects + */ +public class VKPhotoArray extends VKList { + @Override + public VKApiModel parse(JSONObject response) throws JSONException { + fill(response, VKApiPhoto.class); + return this; + } +} diff --git a/src/com/vk/sdk/api/model/VKPhotoSizes.java b/src/com/vk/sdk/api/model/VKPhotoSizes.java new file mode 100644 index 0000000..1dc169e --- /dev/null +++ b/src/com/vk/sdk/api/model/VKPhotoSizes.java @@ -0,0 +1,208 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +package com.vk.sdk.api.model; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.Collections; + +/** + * Model to parse a list of photo with photo sizes format. + * + * When {@code photo_sizes=1} parameter is used in methods, + * response contains an info about original photo + * copies with different sizes in sizes array with objects, + * each of them contains following fields:
+ *
    + *
  • src — url of image copy;
  • + *
  • width — copy width, px;
  • + *
  • height — copy height, px;
  • + *
  • type — notation for copy size and ratio.
  • + *
+ */ +@SuppressWarnings("unused") +public class VKPhotoSizes extends VKList { + + private static float sQuality = 1.0f; + + /** + * Sets the quality modifier for sampling algorithm of image resolution. + * @param quality positive number between 0.0f and 1.0f. + */ + public static void setQuality(float quality) { + sQuality = quality; + } + + /** + * Original width of photo in pixels. + */ + private int mOriginalWidth = 1; + + /** + * Original height of photo in pixels. + */ + private int mOriginalHeight = 1; + + /** + * URL of last image thumb for width sampling algorithm. + */ + private String mWidthThumb; + /** + * URL of last image thumb for height sampling algorithm. + */ + private String mHeightThumb; + + /** + * Width of last image thumb for width sampling algorithm. + */ + private int mLastWidth; + + /** + * Height of last image thumb for width sampling algorithm. + */ + private int mLastHeight; + + /** + * Parser that's used for parsing photo sizes. + */ + private final Parser parser = new Parser() { + @Override + public VKApiPhotoSize parseObject(JSONObject source) throws Exception { + return VKApiPhotoSize.parse(source, mOriginalWidth, mOriginalHeight); + } + }; + + /** + * Creates empty list of photo sizes. + */ + public VKPhotoSizes() { + super(); + } + + /** + * Creates and fills list of photo sizes. + */ + public VKPhotoSizes(JSONArray from) { + super(); + fill(from); + } + + /** + * Creates list of photo sizes which fill with according data. + * @param from array of photo sizes returned by VK. + * @param width original photo width in pixels. + * @param height original photo height in pixels. + */ + public void fill(JSONArray from, int width, int height) { + setOriginalDimension(width, height); + fill(from); + } + + /** + * Fill list according with given data. + * @param from array of photo sizes returned by VK. + */ + public void fill(JSONArray from) { + fill(from, parser); + sort(); + } + + /** + * Return image according with given type of thumb. + * @return URL of image thumb, or null if image with this thumb is not found in the list. + */ + public String getByType(char type) { + for(VKApiPhotoSize size: this) { + if(size.type == type) { + return size.src; + } + } + return null; + } + + /** + * Sets original image dimensions. + * @param width original photo width in pixels. + * @param height original photo height in pixels. + */ + public void setOriginalDimension(int width, int height) { + if(width != 0) { + this.mOriginalWidth = width; + } + if(height != 0) { + this.mOriginalHeight = height; + } + } + + /** + * Sorts thumbs according to their width. + */ + public void sort() { + Collections.sort(this); + } + + /** + * Finds an image that fits perfectly into the specified dimensions. + * Method is uses a cache of last thumbs for better performance. + * @param width required minimum width of image in pixels. + * @param height required minimum height of image in pixels. + * @return URL of selected thumb or null if image with what parameters is not found. + */ + public String getImageForDimension(int width, int height) { + return width >= height ? getImageForWidth(width) : getImageForHeight(height); + } + + private String getImageForWidth(int width) { + if((mWidthThumb != null && mLastWidth != width) || isEmpty()) { + return mWidthThumb; + } + mLastWidth = width; + mWidthThumb = null; + width = (int) (width * sQuality); + + for(VKApiPhotoSize size : this) { + if(size.width >= width) { + mWidthThumb = size.src; + break; + } + } + return mWidthThumb; + } + + private String getImageForHeight(int height) { + if((mHeightThumb != null && mLastHeight != height) || isEmpty()) { + return mHeightThumb; + } + mLastHeight = height; + mHeightThumb = null; + height = (int) (height * sQuality); + + for(VKApiPhotoSize size : this) { + if(size.height >= height) { + mHeightThumb = size.src; + break; + } + } + return mHeightThumb; + } +} diff --git a/src/com/vk/sdk/api/model/VKPostArray.java b/src/com/vk/sdk/api/model/VKPostArray.java new file mode 100644 index 0000000..f62d778 --- /dev/null +++ b/src/com/vk/sdk/api/model/VKPostArray.java @@ -0,0 +1,37 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +package com.vk.sdk.api.model; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Array of Post + * Created by alex_xpert on 29.01.14. + */ +public class VKPostArray extends VKList { + @Override + public VKApiModel parse(JSONObject response) throws JSONException { + fill(response, VKApiPost.class); + return this; + } +} diff --git a/src/com/vk/sdk/api/model/VKPrivacy.java b/src/com/vk/sdk/api/model/VKPrivacy.java new file mode 100644 index 0000000..6562819 --- /dev/null +++ b/src/com/vk/sdk/api/model/VKPrivacy.java @@ -0,0 +1,87 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +/** + * Privacy.java + * VK Dev + * + * Created by Babichev Vitaly on 29.09.13. + * Copyright (c) 2013 VK. All rights reserved. + */ +package com.vk.sdk.api.model; + +import org.json.JSONObject; + +/** + * Describes privacy of some VK object + */ +public class VKPrivacy { + + VKPrivacy() {} + + /** + * Access for all users + */ + public final static int PRIVACY_ALL = 0; + + /** + * Access only for friends. + */ + public final static int PRIVACY_FRIENDS = 1; + + /** + * Access only for friends and friend of friends. + */ + public final static int PRIVACY_FRIENDS_OF_FRIENDS = 2; + + /** + * Access only for logged user. + */ + public final static int PRIVACY_NOBODY = 3; + + /** + * Unknown privacy format + */ + public final static int PRIVACY_UNKNOWN = 4; + + /** + * Parses privacy in int format from privacy_view format. + * @see
http://vk.com/dev/privacy_setting + */ + public static int parsePrivacy(JSONObject privacyView) { + int privacy = 0; + if(privacyView != null) { + String type = privacyView.optString("type"); + if("all".equals(type)) { + privacy = PRIVACY_ALL; + } else if("friends".equals(type)) { + privacy = PRIVACY_FRIENDS; + } else if("friends_of_friends".equals(type)) { + privacy = PRIVACY_FRIENDS_OF_FRIENDS; + } else if("nobody".equals(type)) { + privacy = PRIVACY_NOBODY; + } else { + privacy = PRIVACY_UNKNOWN; + } + } + return privacy; + } +} \ No newline at end of file diff --git a/src/com/vk/sdk/api/model/VKScopes.java b/src/com/vk/sdk/api/model/VKScopes.java new file mode 100644 index 0000000..37bc8fb --- /dev/null +++ b/src/com/vk/sdk/api/model/VKScopes.java @@ -0,0 +1,159 @@ +// +// Copyright (c) 2014 VK.com +// +// 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 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. +// + +package com.vk.sdk.api.model; + +import java.util.ArrayList; + +/** + * Application Access Permissions + * @see http://vk.com/dev/permissions + */ +@SuppressWarnings("unused") +public class VKScopes { + + private VKScopes() {} + + /** + * User allowed to send notifications to him/her. + */ + public final static String NOTIFY = "notify"; + + /** + * Access to friends. + */ + public final static String FRIENDS = "friends"; + + /** + * Access to photos. + */ + public final static String PHOTOS = "photos"; + + /** + * Access to audios. + */ + public final static String AUDIO = "audio"; + + /** + * Access to videos. + */ + public final static String VIDEO = "video"; + + /** + * Access to documents. + */ + public final static String DOCS = "docs"; + + /** + * Access to user notes. + */ + public final static String NOTES = "notes"; + + /** + * Access to wiki pages. + */ + public final static String PAGES = "pages"; + + /** + * Access to user status. + */ + public final static String STATUS = "status"; + + /** + * Access to offers (obsolete methods). + */ + @Deprecated + public final static String OFFERS = "offers"; + + /** + * Access to questions (obsolete methods). + */ + @Deprecated + public final static String QUESTIONS = "questions"; + + /** + * Access to standard and advanced methods for the wall. + */ + public final static String WALL = "wall"; + + /** + * Access to user groups. + */ + public final static String GROUPS = "groups"; + + /** + * Access to advanced methods for messaging. + */ + public final static String MESSAGES = "messages"; + + /** + * Access to notifications about answers to the user. + */ + public final static String NOTIFICATIONS = "notifications"; + + /** + * Access to statistics of user groups and applications where he/she is an administrator. + */ + public final static String STATS = "stats"; + + /** + * Access to advanced methods for Ads API. + */ + public final static String ADS = "ads"; + + /** + * Access to API at any time from a third party server. + */ + public final static String OFFLINE = "offline"; + + /** + * Possibility to make API requests without HTTPS.
+ * Note that this functionality is under testing and can be changed. + */ + public final static String NOHTTPS = "nohttps"; + + /** + * Converts integer value of permissions into arraylist of constants + * @param permissions integer permissions value + * @return ArrayList contains string constants of permissions (scope) + */ + public static ArrayList parse(int permissions) { + ArrayList result = new ArrayList(); + if ((permissions & 1) > 0) result.add(NOTIFY); + if ((permissions & 2) > 0) result.add(FRIENDS); + if ((permissions & 4) > 0) result.add(PHOTOS); + if ((permissions & 8) > 0) result.add(AUDIO); + if ((permissions & 16) > 0) result.add(VIDEO); + if ((permissions & 128) > 0) result.add(PAGES); + if ((permissions & 1024) > 0) result.add(STATUS); + if ((permissions & 2048) > 0) result.add(NOTES); + if ((permissions & 4096) > 0) result.add(MESSAGES); + if ((permissions & 8192) > 0) result.add(WALL); + if ((permissions & 32768) > 0) result.add(ADS); + if ((permissions & 65536) > 0) result.add(OFFLINE); + if ((permissions & 131072) > 0) result.add(DOCS); + if ((permissions & 262144) > 0) result.add(GROUPS); + if ((permissions & 524288) > 0) result.add(NOTIFICATIONS); + if ((permissions & 1048576) > 0) result.add(STATS); + return result; + } + +} diff --git a/src/org/json/CDL.java b/src/org/json/CDL.java new file mode 100644 index 0000000..995b1d4 --- /dev/null +++ b/src/org/json/CDL.java @@ -0,0 +1,279 @@ +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. +*/ + +/** + * This provides static methods to convert comma delimited text into a + * JSONArray, and to covert a JSONArray into comma delimited text. Comma + * delimited text is a very popular format for data interchange. It is + * understood by most database, spreadsheet, and organizer programs. + *

+ * Each row of text represents a row in a table or a data record. Each row + * ends with a NEWLINE character. Each row contains one or more values. + * Values are separated by commas. A value can contain any character except + * for comma, unless is is wrapped in single quotes or double quotes. + *

+ * The first row usually contains the names of the columns. + *

+ * A comma delimited list can be converted into a JSONArray of JSONObjects. + * The names for the elements in the JSONObjects can be taken from the names + * in the first row. + * @author JSON.org + * @version 2014-05-03 + */ +public class CDL { + + /** + * Get the next value. The value can be wrapped in quotes. The value can + * be empty. + * @param x A JSONTokener of the source text. + * @return The value string, or null if empty. + * @throws JSONException if the quoted string is badly formed. + */ + private static String getValue(JSONTokener x) throws JSONException { + char c; + char q; + StringBuffer sb; + do { + c = x.next(); + } while (c == ' ' || c == '\t'); + switch (c) { + case 0: + return null; + case '"': + case '\'': + q = c; + sb = new StringBuffer(); + for (;;) { + c = x.next(); + if (c == q) { + break; + } + if (c == 0 || c == '\n' || c == '\r') { + throw x.syntaxError("Missing close quote '" + q + "'."); + } + sb.append(c); + } + return sb.toString(); + case ',': + x.back(); + return ""; + default: + x.back(); + return x.nextTo(','); + } + } + + /** + * Produce a JSONArray of strings from a row of comma delimited values. + * @param x A JSONTokener of the source text. + * @return A JSONArray of strings. + * @throws JSONException + */ + public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException { + JSONArray ja = new JSONArray(); + for (;;) { + String value = getValue(x); + char c = x.next(); + if (value == null || + (ja.length() == 0 && value.length() == 0 && c != ',')) { + return null; + } + ja.put(value); + for (;;) { + if (c == ',') { + break; + } + if (c != ' ') { + if (c == '\n' || c == '\r' || c == 0) { + return ja; + } + throw x.syntaxError("Bad character '" + c + "' (" + + (int)c + ")."); + } + c = x.next(); + } + } + } + + /** + * Produce a JSONObject from a row of comma delimited text, using a + * parallel JSONArray of strings to provides the names of the elements. + * @param names A JSONArray of names. This is commonly obtained from the + * first row of a comma delimited text file using the rowToJSONArray + * method. + * @param x A JSONTokener of the source text. + * @return A JSONObject combining the names and values. + * @throws JSONException + */ + public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) + throws JSONException { + JSONArray ja = rowToJSONArray(x); + return ja != null ? ja.toJSONObject(names) : null; + } + + /** + * Produce a comma delimited text row from a JSONArray. Values containing + * the comma character will be quoted. Troublesome characters may be + * removed. + * @param ja A JSONArray of strings. + * @return A string ending in NEWLINE. + */ + public static String rowToString(JSONArray ja) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < ja.length(); i += 1) { + if (i > 0) { + sb.append(','); + } + Object object = ja.opt(i); + if (object != null) { + String string = object.toString(); + if (string.length() > 0 && (string.indexOf(',') >= 0 || + string.indexOf('\n') >= 0 || string.indexOf('\r') >= 0 || + string.indexOf(0) >= 0 || string.charAt(0) == '"')) { + sb.append('"'); + int length = string.length(); + for (int j = 0; j < length; j += 1) { + char c = string.charAt(j); + if (c >= ' ' && c != '"') { + sb.append(c); + } + } + sb.append('"'); + } else { + sb.append(string); + } + } + } + sb.append('\n'); + return sb.toString(); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string, + * using the first row as a source of names. + * @param string The comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(String string) throws JSONException { + return toJSONArray(new JSONTokener(string)); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string, + * using the first row as a source of names. + * @param x The JSONTokener containing the comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONTokener x) throws JSONException { + return toJSONArray(rowToJSONArray(x), x); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string + * using a supplied JSONArray as the source of element names. + * @param names A JSONArray of strings. + * @param string The comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONArray names, String string) + throws JSONException { + return toJSONArray(names, new JSONTokener(string)); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string + * using a supplied JSONArray as the source of element names. + * @param names A JSONArray of strings. + * @param x A JSONTokener of the source text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONArray names, JSONTokener x) + throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + JSONArray ja = new JSONArray(); + for (;;) { + JSONObject jo = rowToJSONObject(names, x); + if (jo == null) { + break; + } + ja.put(jo); + } + if (ja.length() == 0) { + return null; + } + return ja; + } + + + /** + * Produce a comma delimited text from a JSONArray of JSONObjects. The + * first row will be a list of names obtained by inspecting the first + * JSONObject. + * @param ja A JSONArray of JSONObjects. + * @return A comma delimited text. + * @throws JSONException + */ + public static String toString(JSONArray ja) throws JSONException { + JSONObject jo = ja.optJSONObject(0); + if (jo != null) { + JSONArray names = jo.names(); + if (names != null) { + return rowToString(names) + toString(names, ja); + } + } + return null; + } + + /** + * Produce a comma delimited text from a JSONArray of JSONObjects using + * a provided list of names. The list of names is not included in the + * output. + * @param names A JSONArray of strings. + * @param ja A JSONArray of JSONObjects. + * @return A comma delimited text. + * @throws JSONException + */ + public static String toString(JSONArray names, JSONArray ja) + throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < ja.length(); i += 1) { + JSONObject jo = ja.optJSONObject(i); + if (jo != null) { + sb.append(rowToString(jo.toJSONArray(names))); + } + } + return sb.toString(); + } +} diff --git a/src/org/json/Cookie.java b/src/org/json/Cookie.java new file mode 100644 index 0000000..1867dbd --- /dev/null +++ b/src/org/json/Cookie.java @@ -0,0 +1,169 @@ +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. +*/ + +/** + * Convert a web browser cookie specification to a JSONObject and back. + * JSON and Cookies are both notations for name/value pairs. + * @author JSON.org + * @version 2014-05-03 + */ +public class Cookie { + + /** + * Produce a copy of a string in which the characters '+', '%', '=', ';' + * and control characters are replaced with "%hh". This is a gentle form + * of URL encoding, attempting to cause as little distortion to the + * string as possible. The characters '=' and ';' are meta characters in + * cookies. By convention, they are escaped using the URL-encoding. This is + * only a convention, not a standard. Often, cookies are expected to have + * encoded values. We encode '=' and ';' because we must. We encode '%' and + * '+' because they are meta characters in URL encoding. + * @param string The source string. + * @return The escaped result. + */ + public static String escape(String string) { + char c; + String s = string.trim(); + int length = s.length(); + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i += 1) { + c = s.charAt(i); + if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') { + sb.append('%'); + sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16)); + sb.append(Character.forDigit((char)(c & 0x0f), 16)); + } else { + sb.append(c); + } + } + return sb.toString(); + } + + + /** + * Convert a cookie specification string into a JSONObject. The string + * will contain a name value pair separated by '='. The name and the value + * will be unescaped, possibly converting '+' and '%' sequences. The + * cookie properties may follow, separated by ';', also represented as + * name=value (except the secure property, which does not have a value). + * The name will be stored under the key "name", and the value will be + * stored under the key "value". This method does not do checking or + * validation of the parameters. It only converts the cookie string into + * a JSONObject. + * @param string The cookie specification string. + * @return A JSONObject containing "name", "value", and possibly other + * members. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + String name; + JSONObject jo = new JSONObject(); + Object value; + JSONTokener x = new JSONTokener(string); + jo.put("name", x.nextTo('=')); + x.next('='); + jo.put("value", x.nextTo(';')); + x.next(); + while (x.more()) { + name = unescape(x.nextTo("=;")); + if (x.next() != '=') { + if (name.equals("secure")) { + value = Boolean.TRUE; + } else { + throw x.syntaxError("Missing '=' in cookie parameter."); + } + } else { + value = unescape(x.nextTo(';')); + x.next(); + } + jo.put(name, value); + } + return jo; + } + + + /** + * Convert a JSONObject into a cookie specification string. The JSONObject + * must contain "name" and "value" members. + * If the JSONObject contains "expires", "domain", "path", or "secure" + * members, they will be appended to the cookie specification string. + * All other members are ignored. + * @param jo A JSONObject + * @return A cookie specification string + * @throws JSONException + */ + public static String toString(JSONObject jo) throws JSONException { + StringBuilder sb = new StringBuilder(); + + sb.append(escape(jo.getString("name"))); + sb.append("="); + sb.append(escape(jo.getString("value"))); + if (jo.has("expires")) { + sb.append(";expires="); + sb.append(jo.getString("expires")); + } + if (jo.has("domain")) { + sb.append(";domain="); + sb.append(escape(jo.getString("domain"))); + } + if (jo.has("path")) { + sb.append(";path="); + sb.append(escape(jo.getString("path"))); + } + if (jo.optBoolean("secure")) { + sb.append(";secure"); + } + return sb.toString(); + } + + /** + * Convert %hh sequences to single characters, and + * convert plus to space. + * @param string A string that may contain + * + (plus) and + * %hh sequences. + * @return The unescaped string. + */ + public static String unescape(String string) { + int length = string.length(); + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; ++i) { + char c = string.charAt(i); + if (c == '+') { + c = ' '; + } else if (c == '%' && i + 2 < length) { + int d = JSONTokener.dehexchar(string.charAt(i + 1)); + int e = JSONTokener.dehexchar(string.charAt(i + 2)); + if (d >= 0 && e >= 0) { + c = (char)(d * 16 + e); + i += 2; + } + } + sb.append(c); + } + return sb.toString(); + } +} diff --git a/src/org/json/CookieList.java b/src/org/json/CookieList.java new file mode 100644 index 0000000..b716fd7 --- /dev/null +++ b/src/org/json/CookieList.java @@ -0,0 +1,89 @@ +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.util.Iterator; + +/** + * Convert a web browser cookie list string to a JSONObject and back. + * @author JSON.org + * @version 2014-05-03 + */ +public class CookieList { + + /** + * Convert a cookie list into a JSONObject. A cookie list is a sequence + * of name/value pairs. The names are separated from the values by '='. + * The pairs are separated by ';'. The names and the values + * will be unescaped, possibly converting '+' and '%' sequences. + * + * To add a cookie to a cooklist, + * cookielistJSONObject.put(cookieJSONObject.getString("name"), + * cookieJSONObject.getString("value")); + * @param string A cookie list string + * @return A JSONObject + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + JSONObject jo = new JSONObject(); + JSONTokener x = new JSONTokener(string); + while (x.more()) { + String name = Cookie.unescape(x.nextTo('=')); + x.next('='); + jo.put(name, Cookie.unescape(x.nextTo(';'))); + x.next(); + } + return jo; + } + + /** + * Convert a JSONObject into a cookie list. A cookie list is a sequence + * of name/value pairs. The names are separated from the values by '='. + * The pairs are separated by ';'. The characters '%', '+', '=', and ';' + * in the names and values are replaced by "%hh". + * @param jo A JSONObject + * @return A cookie list string + * @throws JSONException + */ + public static String toString(JSONObject jo) throws JSONException { + boolean b = false; + Iterator keys = jo.keys(); + String string; + StringBuilder sb = new StringBuilder(); + while (keys.hasNext()) { + string = keys.next(); + if (!jo.isNull(string)) { + if (b) { + sb.append(';'); + } + sb.append(Cookie.escape(string)); + sb.append("="); + sb.append(Cookie.escape(jo.getString(string))); + b = true; + } + } + return sb.toString(); + } +} diff --git a/src/org/json/HTTP.java b/src/org/json/HTTP.java new file mode 100644 index 0000000..bd1d6fb --- /dev/null +++ b/src/org/json/HTTP.java @@ -0,0 +1,163 @@ +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.util.Iterator; + +/** + * Convert an HTTP header to a JSONObject and back. + * @author JSON.org + * @version 2014-05-03 + */ +public class HTTP { + + /** Carriage return/line feed. */ + public static final String CRLF = "\r\n"; + + /** + * Convert an HTTP header string into a JSONObject. It can be a request + * header or a response header. A request header will contain + *

{
+     *    Method: "POST" (for example),
+     *    "Request-URI": "/" (for example),
+     *    "HTTP-Version": "HTTP/1.1" (for example)
+     * }
+ * A response header will contain + *
{
+     *    "HTTP-Version": "HTTP/1.1" (for example),
+     *    "Status-Code": "200" (for example),
+     *    "Reason-Phrase": "OK" (for example)
+     * }
+ * In addition, the other parameters in the header will be captured, using + * the HTTP field names as JSON names, so that
+     *    Date: Sun, 26 May 2002 18:06:04 GMT
+     *    Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s
+     *    Cache-Control: no-cache
+ * become + *
{...
+     *    Date: "Sun, 26 May 2002 18:06:04 GMT",
+     *    Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s",
+     *    "Cache-Control": "no-cache",
+     * ...}
+ * It does no further checking or conversion. It does not parse dates. + * It does not do '%' transforms on URLs. + * @param string An HTTP header string. + * @return A JSONObject containing the elements and attributes + * of the XML string. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + JSONObject jo = new JSONObject(); + HTTPTokener x = new HTTPTokener(string); + String token; + + token = x.nextToken(); + if (token.toUpperCase().startsWith("HTTP")) { + +// Response + + jo.put("HTTP-Version", token); + jo.put("Status-Code", x.nextToken()); + jo.put("Reason-Phrase", x.nextTo('\0')); + x.next(); + + } else { + +// Request + + jo.put("Method", token); + jo.put("Request-URI", x.nextToken()); + jo.put("HTTP-Version", x.nextToken()); + } + +// Fields + + while (x.more()) { + String name = x.nextTo(':'); + x.next(':'); + jo.put(name, x.nextTo('\0')); + x.next(); + } + return jo; + } + + + /** + * Convert a JSONObject into an HTTP header. A request header must contain + *
{
+     *    Method: "POST" (for example),
+     *    "Request-URI": "/" (for example),
+     *    "HTTP-Version": "HTTP/1.1" (for example)
+     * }
+ * A response header must contain + *
{
+     *    "HTTP-Version": "HTTP/1.1" (for example),
+     *    "Status-Code": "200" (for example),
+     *    "Reason-Phrase": "OK" (for example)
+     * }
+ * Any other members of the JSONObject will be output as HTTP fields. + * The result will end with two CRLF pairs. + * @param jo A JSONObject + * @return An HTTP header string. + * @throws JSONException if the object does not contain enough + * information. + */ + public static String toString(JSONObject jo) throws JSONException { + Iterator keys = jo.keys(); + String string; + StringBuilder sb = new StringBuilder(); + if (jo.has("Status-Code") && jo.has("Reason-Phrase")) { + sb.append(jo.getString("HTTP-Version")); + sb.append(' '); + sb.append(jo.getString("Status-Code")); + sb.append(' '); + sb.append(jo.getString("Reason-Phrase")); + } else if (jo.has("Method") && jo.has("Request-URI")) { + sb.append(jo.getString("Method")); + sb.append(' '); + sb.append('"'); + sb.append(jo.getString("Request-URI")); + sb.append('"'); + sb.append(' '); + sb.append(jo.getString("HTTP-Version")); + } else { + throw new JSONException("Not enough material for an HTTP header."); + } + sb.append(CRLF); + while (keys.hasNext()) { + string = keys.next(); + if (!"HTTP-Version".equals(string) && !"Status-Code".equals(string) && + !"Reason-Phrase".equals(string) && !"Method".equals(string) && + !"Request-URI".equals(string) && !jo.isNull(string)) { + sb.append(string); + sb.append(": "); + sb.append(jo.getString(string)); + sb.append(CRLF); + } + } + sb.append(CRLF); + return sb.toString(); + } +} diff --git a/src/org/json/HTTPTokener.java b/src/org/json/HTTPTokener.java new file mode 100644 index 0000000..b2489b6 --- /dev/null +++ b/src/org/json/HTTPTokener.java @@ -0,0 +1,77 @@ +package org.json; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/** + * The HTTPTokener extends the JSONTokener to provide additional methods + * for the parsing of HTTP headers. + * @author JSON.org + * @version 2014-05-03 + */ +public class HTTPTokener extends JSONTokener { + + /** + * Construct an HTTPTokener from a string. + * @param string A source string. + */ + public HTTPTokener(String string) { + super(string); + } + + + /** + * Get the next token or string. This is used in parsing HTTP headers. + * @throws JSONException + * @return A String. + */ + public String nextToken() throws JSONException { + char c; + char q; + StringBuilder sb = new StringBuilder(); + do { + c = next(); + } while (Character.isWhitespace(c)); + if (c == '"' || c == '\'') { + q = c; + for (;;) { + c = next(); + if (c < ' ') { + throw syntaxError("Unterminated string."); + } + if (c == q) { + return sb.toString(); + } + sb.append(c); + } + } + for (;;) { + if (c == 0 || Character.isWhitespace(c)) { + return sb.toString(); + } + sb.append(c); + c = next(); + } + } +} diff --git a/src/org/json/JSONArray.java b/src/org/json/JSONArray.java new file mode 100644 index 0000000..3f05548 --- /dev/null +++ b/src/org/json/JSONArray.java @@ -0,0 +1,977 @@ +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.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 2014-05-03 + */ +public class JSONArray { + + /** + * 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."); + } + } + + /** + * 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 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 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/JSONML.java b/src/org/json/JSONML.java new file mode 100644 index 0000000..20e0be5 --- /dev/null +++ b/src/org/json/JSONML.java @@ -0,0 +1,467 @@ +package org.json; + +/* +Copyright (c) 2008 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.util.Iterator; + + +/** + * This provides static methods to convert an XML text into a JSONArray or + * JSONObject, and to covert a JSONArray or JSONObject into an XML text using + * the JsonML transform. + * + * @author JSON.org + * @version 2014-05-03 + */ +public class JSONML { + + /** + * Parse XML values and store them in a JSONArray. + * @param x The XMLTokener containing the source string. + * @param arrayForm true if array form, false if object form. + * @param ja The JSONArray that is containing the current tag or null + * if we are at the outermost level. + * @return A JSONArray if the value is the outermost tag, otherwise null. + * @throws JSONException + */ + private static Object parse( + XMLTokener x, + boolean arrayForm, + JSONArray ja + ) throws JSONException { + String attribute; + char c; + String closeTag = null; + int i; + JSONArray newja = null; + JSONObject newjo = null; + Object token; + String tagName = null; + +// Test for and skip past these forms: +// +// +// +// + + while (true) { + if (!x.more()) { + throw x.syntaxError("Bad XML"); + } + token = x.nextContent(); + if (token == XML.LT) { + token = x.nextToken(); + if (token instanceof Character) { + if (token == XML.SLASH) { + +// Close tag "); + } else { + x.back(); + } + } else if (c == '[') { + token = x.nextToken(); + if (token.equals("CDATA") && x.next() == '[') { + if (ja != null) { + ja.put(x.nextCDATA()); + } + } else { + throw x.syntaxError("Expected 'CDATA['"); + } + } else { + i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + } + } else if (token == XML.QUEST) { + +// "); + } else { + throw x.syntaxError("Misshaped tag"); + } + +// Open tag < + + } else { + if (!(token instanceof String)) { + throw x.syntaxError("Bad tagName '" + token + "'."); + } + tagName = (String)token; + newja = new JSONArray(); + newjo = new JSONObject(); + if (arrayForm) { + newja.put(tagName); + if (ja != null) { + ja.put(newja); + } + } else { + newjo.put("tagName", tagName); + if (ja != null) { + ja.put(newjo); + } + } + token = null; + for (;;) { + if (token == null) { + token = x.nextToken(); + } + if (token == null) { + throw x.syntaxError("Misshaped tag"); + } + if (!(token instanceof String)) { + break; + } + +// attribute = value + + attribute = (String)token; + if (!arrayForm && ("tagName".equals(attribute) || "childNode".equals(attribute))) { + throw x.syntaxError("Reserved attribute."); + } + token = x.nextToken(); + if (token == XML.EQ) { + token = x.nextToken(); + if (!(token instanceof String)) { + throw x.syntaxError("Missing value"); + } + newjo.accumulate(attribute, XML.stringToValue((String)token)); + token = null; + } else { + newjo.accumulate(attribute, ""); + } + } + if (arrayForm && newjo.length() > 0) { + newja.put(newjo); + } + +// Empty tag <.../> + + if (token == XML.SLASH) { + if (x.nextToken() != XML.GT) { + throw x.syntaxError("Misshaped tag"); + } + if (ja == null) { + if (arrayForm) { + return newja; + } else { + return newjo; + } + } + +// Content, between <...> and + + } else { + if (token != XML.GT) { + throw x.syntaxError("Misshaped tag"); + } + closeTag = (String)parse(x, arrayForm, newja); + if (closeTag != null) { + if (!closeTag.equals(tagName)) { + throw x.syntaxError("Mismatched '" + tagName + + "' and '" + closeTag + "'"); + } + tagName = null; + if (!arrayForm && newja.length() > 0) { + newjo.put("childNodes", newja); + } + if (ja == null) { + if (arrayForm) { + return newja; + } else { + return newjo; + } + } + } + } + } + } else { + if (ja != null) { + ja.put(token instanceof String + ? XML.stringToValue((String)token) + : token); + } + } + } + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONArray using the JsonML transform. Each XML tag is represented as + * a JSONArray in which the first element is the tag name. If the tag has + * attributes, then the second element will be JSONObject containing the + * name/value pairs. If the tag contains children, then strings and + * JSONArrays will represent the child tags. + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param string The source string. + * @return A JSONArray containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONArray toJSONArray(String string) throws JSONException { + return toJSONArray(new XMLTokener(string)); + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONArray using the JsonML transform. Each XML tag is represented as + * a JSONArray in which the first element is the tag name. If the tag has + * attributes, then the second element will be JSONObject containing the + * name/value pairs. If the tag contains children, then strings and + * JSONArrays will represent the child content and tags. + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param x An XMLTokener. + * @return A JSONArray containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONArray toJSONArray(XMLTokener x) throws JSONException { + return (JSONArray)parse(x, true, null); + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONObject using the JsonML transform. Each XML tag is represented as + * a JSONObject with a "tagName" property. If the tag has attributes, then + * the attributes will be in the JSONObject as properties. If the tag + * contains children, the object will have a "childNodes" property which + * will be an array of strings and JsonML JSONObjects. + + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param x An XMLTokener of the XML source text. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONObject toJSONObject(XMLTokener x) throws JSONException { + return (JSONObject)parse(x, false, null); + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONObject using the JsonML transform. Each XML tag is represented as + * a JSONObject with a "tagName" property. If the tag has attributes, then + * the attributes will be in the JSONObject as properties. If the tag + * contains children, the object will have a "childNodes" property which + * will be an array of strings and JsonML JSONObjects. + + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param string The XML source text. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + return toJSONObject(new XMLTokener(string)); + } + + + /** + * Reverse the JSONML transformation, making an XML text from a JSONArray. + * @param ja A JSONArray. + * @return An XML string. + * @throws JSONException + */ + public static String toString(JSONArray ja) throws JSONException { + int i; + JSONObject jo; + String key; + Iterator keys; + int length; + Object object; + StringBuilder sb = new StringBuilder(); + String tagName; + String value; + +// Emit = length) { + sb.append('/'); + sb.append('>'); + } else { + sb.append('>'); + do { + object = ja.get(i); + i += 1; + if (object != null) { + if (object instanceof String) { + sb.append(XML.escape(object.toString())); + } else if (object instanceof JSONObject) { + sb.append(toString((JSONObject)object)); + } else if (object instanceof JSONArray) { + sb.append(toString((JSONArray)object)); + } + } + } while (i < length); + sb.append('<'); + sb.append('/'); + sb.append(tagName); + sb.append('>'); + } + return sb.toString(); + } + + /** + * Reverse the JSONML transformation, making an XML text from a JSONObject. + * The JSONObject must contain a "tagName" property. If it has children, + * then it must have a "childNodes" property containing an array of objects. + * The other properties are attributes with string values. + * @param jo A JSONObject. + * @return An XML string. + * @throws JSONException + */ + public static String toString(JSONObject jo) throws JSONException { + StringBuilder sb = new StringBuilder(); + int i; + JSONArray ja; + String key; + Iterator keys; + int length; + Object object; + String tagName; + String value; + +//Emit '); + } else { + sb.append('>'); + length = ja.length(); + for (i = 0; i < length; i += 1) { + object = ja.get(i); + if (object != null) { + if (object instanceof String) { + sb.append(XML.escape(object.toString())); + } else if (object instanceof JSONObject) { + sb.append(toString((JSONObject)object)); + } else if (object instanceof JSONArray) { + sb.append(toString((JSONArray)object)); + } else { + sb.append(object.toString()); + } + } + } + sb.append('<'); + sb.append('/'); + sb.append(tagName); + sb.append('>'); + } + return sb.toString(); + } +} diff --git a/src/org/json/JSONObject.java b/src/org/json/JSONObject.java new file mode 100644 index 0000000..d666231 --- /dev/null +++ b/src/org/json/JSONObject.java @@ -0,0 +1,1683 @@ +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.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 2014-05-03 + */ +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 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 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 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) { + return new JSONObject((Map)value).toString(); + } + if (value instanceof Collection) { + return new JSONArray((Collection) value).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) { + return object; + } + + if (object instanceof Collection) { + return new JSONArray((Collection) object); + } + if (object.getClass().isArray()) { + return new JSONArray(object); + } + if (object instanceof Map) { + return new JSONObject((Map) object); + } + 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) { + new JSONObject((Map) value).write(writer, indentFactor, indent); + } else if (value instanceof Collection) { + new JSONArray((Collection) value).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)); + } +} diff --git a/src/org/json/Kim.java b/src/org/json/Kim.java new file mode 100644 index 0000000..9f7af92 --- /dev/null +++ b/src/org/json/Kim.java @@ -0,0 +1,372 @@ +package org.json; + + +/* + Copyright (c) 2013 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. + */ + +/** + * Kim makes immutable eight bit Unicode strings. If the MSB of a byte is set, + * then the next byte is a continuation byte. The last byte of a character + * never has the MSB reset. Every byte that is not the last byte has the MSB + * set. Kim stands for "Keep it minimal". A Unicode character is never longer + * than 3 bytes. Every byte contributes 7 bits to the character. ASCII is + * unmodified. + * + * Kim UTF-8 + * one byte U+007F U+007F + * two bytes U+3FFF U+07FF + * three bytes U+10FFF U+FFFF + * four bytes U+10FFFF + * + * Characters in the ranges U+0800..U+3FFF and U+10000..U+10FFFF will be one + * byte smaller when encoded in Kim compared to UTF-8. + * + * Kim is beneficial when using scripts such as Old South Arabian, Aramaic, + * Avestan, Balinese, Batak, Bopomofo, Buginese, Buhid, Carian, Cherokee, + * Coptic, Cyrillic, Deseret, Egyptian Hieroglyphs, Ethiopic, Georgian, + * Glagolitic, Gothic, Hangul Jamo, Hanunoo, Hiragana, Kanbun, Kaithi, + * Kannada, Katakana, Kharoshthi, Khmer, Lao, Lepcha, Limbu, Lycian, Lydian, + * Malayalam, Mandaic, Meroitic, Miao, Mongolian, Myanmar, New Tai Lue, + * Ol Chiki, Old Turkic, Oriya, Osmanya, Pahlavi, Parthian, Phags-Pa, + * Phoenician, Samaritan, Sharada, Sinhala, Sora Sompeng, Tagalog, Tagbanwa, + * Takri, Tai Le, Tai Tham, Tamil, Telugu, Thai, Tibetan, Tifinagh, UCAS. + * + * A kim object can be constructed from an ordinary UTF-16 string, or from a + * byte array. A kim object can produce a UTF-16 string. + * + * As with UTF-8, it is possible to detect character boundaries within a byte + * sequence. UTF-8 is one of the world's great inventions. While Kim is more + * efficient, it is not clear that it is worth the expense of transition. + * + * @version 2013-04-18 + */ +public class Kim { + + /** + * The byte array containing the kim's content. + */ + private byte[] bytes = null; + + /** + * The kim's hashcode, conforming to Java's hashcode conventions. + */ + private int hashcode = 0; + + /** + * The number of bytes in the kim. The number of bytes can be as much as + * three times the number of characters. + */ + public int length = 0; + + /** + * The memoization of toString(). + */ + private String string = null; + + /** + * Make a kim from a portion of a byte array. + * + * @param bytes + * A byte array. + * @param from + * The index of the first byte. + * @param thru + * The index of the last byte plus one. + */ + public Kim(byte[] bytes, int from, int thru) { + +// As the bytes are copied into the new kim, a hashcode is computed using a +// modified Fletcher code. + + int sum = 1; + int value; + this.hashcode = 0; + this.length = thru - from; + if (this.length > 0) { + this.bytes = new byte[this.length]; + for (int at = 0; at < this.length; at += 1) { + value = (int) bytes[at + from] & 0xFF; + sum += value; + this.hashcode += sum; + this.bytes[at] = (byte) value; + } + this.hashcode += sum << 16; + } + } + + /** + * Make a kim from a byte array. + * + * @param bytes + * The byte array. + * @param length + * The number of bytes. + */ + public Kim(byte[] bytes, int length) { + this(bytes, 0, length); + } + + /** + * Make a new kim from a substring of an existing kim. The coordinates are + * in byte units, not character units. + * + * @param kim + * The source of bytes. + * @param from + * The point at which to take bytes. + * @param thru + * The point at which to stop taking bytes. + */ + public Kim(Kim kim, int from, int thru) { + this(kim.bytes, from, thru); + } + + /** + * Make a kim from a string. + * + * @param string + * The string. + * @throws JSONException + * if surrogate pair mismatch. + */ + public Kim(String string) throws JSONException { + int stringLength = string.length(); + this.hashcode = 0; + this.length = 0; + +// First pass: Determine the length of the kim, allowing for the UTF-16 +// to UTF-32 conversion, and then the UTF-32 to Kim conversion. + + if (stringLength > 0) { + for (int i = 0; i < stringLength; i += 1) { + int c = string.charAt(i); + if (c <= 0x7F) { + this.length += 1; + } else if (c <= 0x3FFF) { + this.length += 2; + } else { + if (c >= 0xD800 && c <= 0xDFFF) { + i += 1; + int d = string.charAt(i); + if (c > 0xDBFF || d < 0xDC00 || d > 0xDFFF) { + throw new JSONException("Bad UTF16"); + } + } + this.length += 3; + } + } + +// Second pass: Allocate a byte array and fill that array with the conversion +// while computing the hashcode. + + this.bytes = new byte[length]; + int at = 0; + int b; + int sum = 1; + for (int i = 0; i < stringLength; i += 1) { + int character = string.charAt(i); + if (character <= 0x7F) { + bytes[at] = (byte) character; + sum += character; + this.hashcode += sum; + at += 1; + } else if (character <= 0x3FFF) { + b = 0x80 | (character >>> 7); + bytes[at] = (byte) b; + sum += b; + this.hashcode += sum; + at += 1; + b = character & 0x7F; + bytes[at] = (byte) b; + sum += b; + this.hashcode += sum; + at += 1; + } else { + if (character >= 0xD800 && character <= 0xDBFF) { + i += 1; + character = (((character & 0x3FF) << 10) | (string + .charAt(i) & 0x3FF)) + 65536; + } + b = 0x80 | (character >>> 14); + bytes[at] = (byte) b; + sum += b; + this.hashcode += sum; + at += 1; + b = 0x80 | ((character >>> 7) & 0xFF); + bytes[at] = (byte) b; + sum += b; + this.hashcode += sum; + at += 1; + b = character & 0x7F; + bytes[at] = (byte) b; + sum += b; + this.hashcode += sum; + at += 1; + } + } + this.hashcode += sum << 16; + } + } + + /** + * Returns the character at the specified index. The index refers to byte + * values and ranges from 0 to length - 1. The index of the next character + * is at index + Kim.characterSize(kim.characterAt(index)). + * + * @param at + * the index of the char value. The first character is at 0. + * @returns a Unicode character between 0 and 0x10FFFF. + * @throws JSONException + * if at does not point to a valid character. + */ + public int characterAt(int at) throws JSONException { + int c = get(at); + if ((c & 0x80) == 0) { + return c; + } + int character; + int c1 = get(at + 1); + if ((c1 & 0x80) == 0) { + character = ((c & 0x7F) << 7) | c1; + if (character > 0x7F) { + return character; + } + } else { + int c2 = get(at + 2); + character = ((c & 0x7F) << 14) | ((c1 & 0x7F) << 7) | c2; + if ((c2 & 0x80) == 0 && character > 0x3FFF && character <= 0x10FFFF + && (character < 0xD800 || character > 0xDFFF)) { + return character; + } + } + throw new JSONException("Bad character at " + at); + } + + /** + * Returns the number of bytes needed to contain the character in Kim + * format. + * + * @param character + * a Unicode character between 0 and 0x10FFFF. + * @return 1, 2, or 3 + * @throws JSONException + * if the character is not representable in a kim. + */ + public static int characterSize(int character) throws JSONException { + if (character < 0 || character > 0x10FFFF) { + throw new JSONException("Bad character " + character); + } + return character <= 0x7F ? 1 : character <= 0x3FFF ? 2 : 3; + } + + /** + * Copy the contents of this kim to a byte array. + * + * @param bytes + * A byte array of sufficient size. + * @param at + * The position within the byte array to take the byes. + * @return The position immediately after the copy. + */ + public int copy(byte[] bytes, int at) { + System.arraycopy(this.bytes, 0, bytes, at, this.length); + return at + this.length; + } + + /** + * Two kim objects containing exactly the same bytes in the same order are + * equal to each other. + * + * @param obj + * the other kim with which to compare. + * @returns true if this and obj are both kim objects containing identical + * byte sequences. + */ + public boolean equals(Object obj) { + if (!(obj instanceof Kim)) { + return false; + } + Kim that = (Kim) obj; + if (this == that) { + return true; + } + if (this.hashcode != that.hashcode) { + return false; + } + return java.util.Arrays.equals(this.bytes, that.bytes); + } + + /** + * Get a byte from a kim. + * @param at + * The position of the byte. The first byte is at 0. + * @return The byte. + * @throws JSONException + * if there is no byte at that position. + */ + public int get(int at) throws JSONException { + if (at < 0 || at > this.length) { + throw new JSONException("Bad character at " + at); + } + return ((int) this.bytes[at]) & 0xFF; + } + + /** + * Returns a hash code value for the kim. + */ + public int hashCode() { + return this.hashcode; + } + + /** + * Produce a UTF-16 String from this kim. The number of codepoints in the + * string will not be greater than the number of bytes in the kim, although + * it could be less. + * + * @return The string. A kim memoizes its string representation. + * @throws JSONException + * if the kim is not valid. + */ + public String toString() throws JSONException { + if (this.string == null) { + int c; + int length = 0; + char chars[] = new char[this.length]; + for (int at = 0; at < this.length; at += characterSize(c)) { + c = this.characterAt(at); + if (c < 0x10000) { + chars[length] = (char) c; + length += 1; + } else { + chars[length] = (char) (0xD800 | ((c - 0x10000) >>> 10)); + length += 1; + chars[length] = (char) (0xDC00 | (c & 0x03FF)); + length += 1; + } + } + this.string = new String(chars, 0, length); + } + return this.string; + } +} diff --git a/src/org/json/Property.java b/src/org/json/Property.java new file mode 100644 index 0000000..8122241 --- /dev/null +++ b/src/org/json/Property.java @@ -0,0 +1,72 @@ +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.util.Enumeration; +import java.util.Iterator; +import java.util.Properties; + +/** + * Converts a Property file data into JSONObject and back. + * @author JSON.org + * @version 2014-05-03 + */ +public class Property { + /** + * Converts a property file object into a JSONObject. The property file object is a table of name value pairs. + * @param properties java.util.Properties + * @return JSONObject + * @throws JSONException + */ + public static JSONObject toJSONObject(java.util.Properties properties) throws JSONException { + JSONObject jo = new JSONObject(); + if (properties != null && !properties.isEmpty()) { + Enumeration enumProperties = properties.propertyNames(); + while(enumProperties.hasMoreElements()) { + String name = (String)enumProperties.nextElement(); + jo.put(name, properties.getProperty(name)); + } + } + return jo; + } + + /** + * Converts the JSONObject into a property file object. + * @param jo JSONObject + * @return java.util.Properties + * @throws JSONException + */ + public static Properties toProperties(JSONObject jo) throws JSONException { + Properties properties = new Properties(); + if (jo != null) { + Iterator keys = jo.keys(); + while (keys.hasNext()) { + String name = keys.next(); + properties.put(name, jo.getString(name)); + } + } + return properties; + } +} diff --git a/src/org/json/XML.java b/src/org/json/XML.java new file mode 100644 index 0000000..07090ab --- /dev/null +++ b/src/org/json/XML.java @@ -0,0 +1,490 @@ +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.util.Iterator; + +/** + * This provides static methods to convert an XML text into a JSONObject, + * and to covert a JSONObject into an XML text. + * @author JSON.org + * @version 2014-05-03 + */ +public class XML { + + /** The Character '&'. */ + public static final Character AMP = '&'; + + /** The Character '''. */ + public static final Character APOS = '\''; + + /** The Character '!'. */ + public static final Character BANG = '!'; + + /** The Character '='. */ + public static final Character EQ = '='; + + /** The Character '>'. */ + public static final Character GT = '>'; + + /** The Character '<'. */ + public static final Character LT = '<'; + + /** The Character '?'. */ + public static final Character QUEST = '?'; + + /** The Character '"'. */ + public static final Character QUOT = '"'; + + /** The Character '/'. */ + public static final Character SLASH = '/'; + + /** + * Replace special characters with XML escapes: + *

+     * & (ampersand) is replaced by &amp;
+     * < (less than) is replaced by &lt;
+     * > (greater than) is replaced by &gt;
+     * " (double quote) is replaced by &quot;
+     * 
+ * @param string The string to be escaped. + * @return The escaped string. + */ + public static String escape(String string) { + StringBuilder sb = new StringBuilder(string.length()); + for (int i = 0, length = string.length(); i < length; i++) { + char c = string.charAt(i); + switch (c) { + case '&': + sb.append("&"); + break; + case '<': + sb.append("<"); + break; + case '>': + sb.append(">"); + break; + case '"': + sb.append("""); + break; + case '\'': + sb.append("'"); + break; + default: + sb.append(c); + } + } + return sb.toString(); + } + + /** + * Throw an exception if the string contains whitespace. + * Whitespace is not allowed in tagNames and attributes. + * @param string A string. + * @throws JSONException + */ + public static void noSpace(String string) throws JSONException { + int i, length = string.length(); + if (length == 0) { + throw new JSONException("Empty string."); + } + for (i = 0; i < length; i += 1) { + if (Character.isWhitespace(string.charAt(i))) { + throw new JSONException("'" + string + + "' contains a space character."); + } + } + } + + /** + * Scan the content following the named tag, attaching it to the context. + * @param x The XMLTokener containing the source string. + * @param context The JSONObject that will include the new material. + * @param name The tag name. + * @return true if the close tag is processed. + * @throws JSONException + */ + private static boolean parse(XMLTokener x, JSONObject context, + String name) throws JSONException { + char c; + int i; + JSONObject jsonobject = null; + String string; + String tagName; + Object token; + +// Test for and skip past these forms: +// +// +// +// +// Report errors for these forms: +// <> +// <= +// << + + token = x.nextToken(); + +// "); + return false; + } + x.back(); + } else if (c == '[') { + token = x.nextToken(); + if ("CDATA".equals(token)) { + if (x.next() == '[') { + string = x.nextCDATA(); + if (string.length() > 0) { + context.accumulate("content", string); + } + return false; + } + } + throw x.syntaxError("Expected 'CDATA['"); + } + i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + return false; + } else if (token == QUEST) { + +// "); + return false; + } else if (token == SLASH) { + +// Close tag + + } else if (token == SLASH) { + if (x.nextToken() != GT) { + throw x.syntaxError("Misshaped tag"); + } + if (jsonobject.length() > 0) { + context.accumulate(tagName, jsonobject); + } else { + context.accumulate(tagName, ""); + } + return false; + +// Content, between <...> and + + } else if (token == GT) { + for (;;) { + token = x.nextContent(); + if (token == null) { + if (tagName != null) { + throw x.syntaxError("Unclosed tag " + tagName); + } + return false; + } else if (token instanceof String) { + string = (String)token; + if (string.length() > 0) { + jsonobject.accumulate("content", + XML.stringToValue(string)); + } + +// Nested element + + } else if (token == LT) { + if (parse(x, jsonobject, tagName)) { + if (jsonobject.length() == 0) { + context.accumulate(tagName, ""); + } else if (jsonobject.length() == 1 && + jsonobject.opt("content") != null) { + context.accumulate(tagName, + jsonobject.opt("content")); + } else { + context.accumulate(tagName, jsonobject); + } + return false; + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); + } + } + } + } + + + /** + * Try to convert a string into a number, boolean, or null. If the string + * can't be converted, return the string. This is much less ambitious than + * JSONObject.stringToValue, especially because it does not attempt to + * convert plus forms, octal forms, hex forms, or E forms lacking decimal + * points. + * @param string A String. + * @return A simple JSON value. + */ + public static Object stringToValue(String string) { + if ("true".equalsIgnoreCase(string)) { + return Boolean.TRUE; + } + if ("false".equalsIgnoreCase(string)) { + return Boolean.FALSE; + } + if ("null".equalsIgnoreCase(string)) { + return JSONObject.NULL; + } + +// If it might be a number, try converting it, first as a Long, and then as a +// Double. If that doesn't work, return the string. + + try { + char initial = string.charAt(0); + if (initial == '-' || (initial >= '0' && initial <= '9')) { + Long value = new Long(string); + if (value.toString().equals(string)) { + return value; + } + } + } catch (Exception ignore) { + try { + Double value = new Double(string); + if (value.toString().equals(string)) { + return value; + } + } catch (Exception ignoreAlso) { + } + } + return string; + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONObject. Some information may be lost in this transformation + * because JSON is a data format and XML is a document format. XML uses + * elements, attributes, and content text, while JSON uses unordered + * collections of name/value pairs and arrays of values. JSON does not + * does not like to distinguish between elements and attributes. + * Sequences of similar elements are represented as JSONArrays. Content + * text may be placed in a "content" member. Comments, prologs, DTDs, and + * <[ [ ]]> are ignored. + * @param string The source string. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + JSONObject jo = new JSONObject(); + XMLTokener x = new XMLTokener(string); + while (x.more() && x.skipPast("<")) { + parse(x, jo, null); + } + return jo; + } + + + /** + * Convert a JSONObject into a well-formed, element-normal XML string. + * @param object A JSONObject. + * @return A string. + * @throws JSONException + */ + public static String toString(Object object) throws JSONException { + return toString(object, null); + } + + + /** + * Convert a JSONObject into a well-formed, element-normal XML string. + * @param object A JSONObject. + * @param tagName The optional name of the enclosing tag. + * @return A string. + * @throws JSONException + */ + public static String toString(Object object, String tagName) + throws JSONException { + StringBuilder sb = new StringBuilder(); + int i; + JSONArray ja; + JSONObject jo; + String key; + Iterator keys; + int length; + String string; + Object value; + if (object instanceof JSONObject) { + +// Emit + + if (tagName != null) { + sb.append('<'); + sb.append(tagName); + sb.append('>'); + } + +// Loop thru the keys. + + jo = (JSONObject)object; + keys = jo.keys(); + while (keys.hasNext()) { + key = keys.next(); + value = jo.opt(key); + if (value == null) { + value = ""; + } + string = value instanceof String ? (String)value : null; + +// Emit content in body + + if ("content".equals(key)) { + if (value instanceof JSONArray) { + ja = (JSONArray)value; + length = ja.length(); + for (i = 0; i < length; i += 1) { + if (i > 0) { + sb.append('\n'); + } + sb.append(escape(ja.get(i).toString())); + } + } else { + sb.append(escape(value.toString())); + } + +// Emit an array of similar keys + + } else if (value instanceof JSONArray) { + ja = (JSONArray)value; + length = ja.length(); + for (i = 0; i < length; i += 1) { + value = ja.get(i); + if (value instanceof JSONArray) { + sb.append('<'); + sb.append(key); + sb.append('>'); + sb.append(toString(value)); + sb.append("'); + } else { + sb.append(toString(value, key)); + } + } + } else if ("".equals(value)) { + sb.append('<'); + sb.append(key); + sb.append("/>"); + +// Emit a new tag + + } else { + sb.append(toString(value, key)); + } + } + if (tagName != null) { + +// Emit the close tag + + sb.append("'); + } + return sb.toString(); + +// XML does not have good support for arrays. If an array appears in a place +// where XML is lacking, synthesize an element. + + } else { + if (object.getClass().isArray()) { + object = new JSONArray(object); + } + if (object instanceof JSONArray) { + ja = (JSONArray)object; + length = ja.length(); + for (i = 0; i < length; i += 1) { + sb.append(toString(ja.opt(i), tagName == null ? "array" : tagName)); + } + return sb.toString(); + } else { + string = (object == null) ? "null" : escape(object.toString()); + return (tagName == null) ? "\"" + string + "\"" : + (string.length() == 0) ? "<" + tagName + "/>" : + "<" + tagName + ">" + string + ""; + } + } + } +} diff --git a/src/org/json/XMLTokener.java b/src/org/json/XMLTokener.java new file mode 100644 index 0000000..d319765 --- /dev/null +++ b/src/org/json/XMLTokener.java @@ -0,0 +1,365 @@ +package org.json; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/** + * The XMLTokener extends the JSONTokener to provide additional methods + * for the parsing of XML texts. + * @author JSON.org + * @version 2014-05-03 + */ +public class XMLTokener extends JSONTokener { + + + /** The table of entity values. It initially contains Character values for + * amp, apos, gt, lt, quot. + */ + public static final java.util.HashMap entity; + + static { + entity = new java.util.HashMap(8); + entity.put("amp", XML.AMP); + entity.put("apos", XML.APOS); + entity.put("gt", XML.GT); + entity.put("lt", XML.LT); + entity.put("quot", XML.QUOT); + } + + /** + * Construct an XMLTokener from a string. + * @param s A source string. + */ + public XMLTokener(String s) { + super(s); + } + + /** + * Get the text in the CDATA block. + * @return The string up to the ]]>. + * @throws JSONException If the ]]> is not found. + */ + public String nextCDATA() throws JSONException { + char c; + int i; + StringBuilder sb = new StringBuilder(); + for (;;) { + c = next(); + if (end()) { + throw syntaxError("Unclosed CDATA"); + } + sb.append(c); + i = sb.length() - 3; + if (i >= 0 && sb.charAt(i) == ']' && + sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') { + sb.setLength(i); + return sb.toString(); + } + } + } + + + /** + * Get the next XML outer token, trimming whitespace. There are two kinds + * of tokens: the '<' character which begins a markup tag, and the content + * text between markup tags. + * + * @return A string, or a '<' Character, or null if there is no more + * source text. + * @throws JSONException + */ + public Object nextContent() throws JSONException { + char c; + StringBuilder sb; + do { + c = next(); + } while (Character.isWhitespace(c)); + if (c == 0) { + return null; + } + if (c == '<') { + return XML.LT; + } + sb = new StringBuilder(); + for (;;) { + if (c == '<' || c == 0) { + back(); + return sb.toString().trim(); + } + if (c == '&') { + sb.append(nextEntity(c)); + } else { + sb.append(c); + } + c = next(); + } + } + + + /** + * Return the next entity. These entities are translated to Characters: + * & ' > < ". + * @param ampersand An ampersand character. + * @return A Character or an entity String if the entity is not recognized. + * @throws JSONException If missing ';' in XML entity. + */ + public Object nextEntity(char ampersand) throws JSONException { + StringBuilder sb = new StringBuilder(); + for (;;) { + char c = next(); + if (Character.isLetterOrDigit(c) || c == '#') { + sb.append(Character.toLowerCase(c)); + } else if (c == ';') { + break; + } else { + throw syntaxError("Missing ';' in XML entity: &" + sb); + } + } + String string = sb.toString(); + Object object = entity.get(string); + return object != null ? object : ampersand + string + ";"; + } + + + /** + * Returns the next XML meta token. This is used for skipping over + * and structures. + * @return Syntax characters (< > / = ! ?) are returned as + * Character, and strings and names are returned as Boolean. We don't care + * what the values actually are. + * @throws JSONException If a string is not properly closed or if the XML + * is badly structured. + */ + public Object nextMeta() throws JSONException { + char c; + char q; + do { + c = next(); + } while (Character.isWhitespace(c)); + switch (c) { + case 0: + throw syntaxError("Misshaped meta tag"); + case '<': + return XML.LT; + case '>': + return XML.GT; + case '/': + return XML.SLASH; + case '=': + return XML.EQ; + case '!': + return XML.BANG; + case '?': + return XML.QUEST; + case '"': + case '\'': + q = c; + for (;;) { + c = next(); + if (c == 0) { + throw syntaxError("Unterminated string"); + } + if (c == q) { + return Boolean.TRUE; + } + } + default: + for (;;) { + c = next(); + if (Character.isWhitespace(c)) { + return Boolean.TRUE; + } + switch (c) { + case 0: + case '<': + case '>': + case '/': + case '=': + case '!': + case '?': + case '"': + case '\'': + back(); + return Boolean.TRUE; + } + } + } + } + + + /** + * Get the next XML Token. These tokens are found inside of angle + * brackets. It may be one of these characters: / > = ! ? or it + * may be a string wrapped in single quotes or double quotes, or it may be a + * name. + * @return a String or a Character. + * @throws JSONException If the XML is not well formed. + */ + public Object nextToken() throws JSONException { + char c; + char q; + StringBuilder sb; + do { + c = next(); + } while (Character.isWhitespace(c)); + switch (c) { + case 0: + throw syntaxError("Misshaped element"); + case '<': + throw syntaxError("Misplaced '<'"); + case '>': + return XML.GT; + case '/': + return XML.SLASH; + case '=': + return XML.EQ; + case '!': + return XML.BANG; + case '?': + return XML.QUEST; + +// Quoted string + + case '"': + case '\'': + q = c; + sb = new StringBuilder(); + for (;;) { + c = next(); + if (c == 0) { + throw syntaxError("Unterminated string"); + } + if (c == q) { + return sb.toString(); + } + if (c == '&') { + sb.append(nextEntity(c)); + } else { + sb.append(c); + } + } + default: + +// Name + + sb = new StringBuilder(); + for (;;) { + sb.append(c); + c = next(); + if (Character.isWhitespace(c)) { + return sb.toString(); + } + switch (c) { + case 0: + return sb.toString(); + case '>': + case '/': + case '=': + case '!': + case '?': + case '[': + case ']': + back(); + return sb.toString(); + case '<': + case '"': + case '\'': + throw syntaxError("Bad character in a name"); + } + } + } + } + + + /** + * Skip characters until past the requested string. + * If it is not found, we are left at the end of the source with a result of false. + * @param to A string to skip past. + * @throws JSONException + */ + public boolean skipPast(String to) throws JSONException { + boolean b; + char c; + int i; + int j; + int offset = 0; + int length = to.length(); + char[] circle = new char[length]; + + /* + * First fill the circle buffer with as many characters as are in the + * to string. If we reach an early end, bail. + */ + + for (i = 0; i < length; i += 1) { + c = next(); + if (c == 0) { + return false; + } + circle[i] = c; + } + + /* We will loop, possibly for all of the remaining characters. */ + + for (;;) { + j = offset; + b = true; + + /* Compare the circle buffer with the to string. */ + + for (i = 0; i < length; i += 1) { + if (circle[j] != to.charAt(i)) { + b = false; + break; + } + j += 1; + if (j >= length) { + j -= length; + } + } + + /* If we exit the loop with b intact, then victory is ours. */ + + if (b) { + return true; + } + + /* Get the next character. If there isn't one, then defeat is ours. */ + + c = next(); + if (c == 0) { + return false; + } + /* + * Shove the character in the circle buffer and advance the + * circle offset. The offset is mod n. + */ + circle[offset] = c; + offset += 1; + if (offset >= length) { + offset -= length; + } + } + } +} diff --git a/tmp/styles/main.css b/tmp/styles/main.css new file mode 100644 index 0000000..226c68d --- /dev/null +++ b/tmp/styles/main.css @@ -0,0 +1,43 @@ +body { + background: #fff; + color: #000; + margin: 0px auto; + padding: 0px; + max-width: 900px; + font-size: 12px; + font-family: tahoma; + line-height: 1.182; +} + +.im_date_link { + color: #999999; +} + +.hr { padding: 5px 0 3px 15px; } + +.im_log_author { + text-align: right; + padding-right: 10px !important; + width: 32px; + color: #2B587A; + font-weight: bold; +} + + +.audio { + padding-left: 5px; + padding-top: 2px; + float: left; +} +.play_new { + width: 16px; + height: 16px; + background: url(http://st0.vk.me/images/playpause.gif) no-repeat 0px 0px; + float: left; +} + +.wall { + border-left: 2px solid #C3D1E0; + padding-left: 10px; + margin: 10px 0 5px 3px; +} \ No newline at end of file