Alohalytics statistics library.
This commit is contained in:
parent
03aebe7331
commit
82df548633
142 changed files with 27114 additions and 0 deletions
3
3party/Alohalytics/.clang-format
Normal file
3
3party/Alohalytics/.clang-format
Normal file
|
@ -0,0 +1,3 @@
|
|||
BasedOnStyle: Google
|
||||
ColumnLimit: 112 # Calibrated by Dima&Alex for GitHub-based code reviews in 2014.
|
||||
BinPackParameters: false # Allow either all parameters on one line or one parameter per line, no packing.
|
2
3party/Alohalytics/.gitignore
vendored
Normal file
2
3party/Alohalytics/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
.DS_Store
|
||||
build
|
22
3party/Alohalytics/LICENSE
Normal file
22
3party/Alohalytics/LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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.
|
||||
|
18
3party/Alohalytics/alohalytics.pro
Normal file
18
3party/Alohalytics/alohalytics.pro
Normal file
|
@ -0,0 +1,18 @@
|
|||
TEMPLATE = app
|
||||
CONFIG *= console c++11
|
||||
CONFIG -= app_bundle qt
|
||||
|
||||
SOURCES += examples/cpp/example.cc \
|
||||
src/alohalytics.cc \
|
||||
|
||||
HEADERS += src/alohalytics.h \
|
||||
src/http_client.h \
|
||||
src/gzip_wrapper.h \
|
||||
|
||||
QMAKE_LFLAGS *= -lz
|
||||
|
||||
macx-* {
|
||||
OBJECTIVE_SOURCES += src/http_client_apple.mm
|
||||
QMAKE_OBJECTIVE_CFLAGS *= -fobjc-arc
|
||||
QMAKE_LFLAGS *= -framework Foundation
|
||||
}
|
6
3party/Alohalytics/examples/android/.gitignore
vendored
Normal file
6
3party/Alohalytics/examples/android/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
.gradle
|
||||
/local.properties
|
||||
/.idea
|
||||
.DS_Store
|
||||
/build
|
||||
*.iml
|
4
3party/Alohalytics/examples/android/README.md
Normal file
4
3party/Alohalytics/examples/android/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
Create local.properties file here with paths to NDK and SDK:
|
||||
|
||||
sdk.dir=/path/to/android/sdk
|
||||
ndk.dir=/path/to/android/ndk
|
51
3party/Alohalytics/examples/android/build.gradle
Normal file
51
3party/Alohalytics/examples/android/build.gradle
Normal file
|
@ -0,0 +1,51 @@
|
|||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:+'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
dependencies {
|
||||
// This one is needed to get Google Play advertising ID if Google Play Services are available on the device.
|
||||
compile 'com.google.android.gms:play-services-location:+'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 21
|
||||
buildToolsVersion '21.1.1'
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'org.alohalytics.demoapp'
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 21
|
||||
versionCode 1
|
||||
versionName '1.0'
|
||||
|
||||
ndk {
|
||||
moduleName 'alohalytics'
|
||||
stl 'c++_static'
|
||||
cFlags '-frtti -fexceptions'
|
||||
ldLibs 'log', 'atomic'
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
java.srcDirs = ['src/main/java', '../../src/android/java']
|
||||
jni.srcDirs = ['../../src/cpp', '../../src/android/jni']
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
}
|
BIN
3party/Alohalytics/examples/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
3party/Alohalytics/examples/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
3party/Alohalytics/examples/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
3party/Alohalytics/examples/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
#Tue Dec 02 17:29:40 HST 2014
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
|
164
3party/Alohalytics/examples/android/gradlew
vendored
Executable file
164
3party/Alohalytics/examples/android/gradlew
vendored
Executable file
|
@ -0,0 +1,164 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
90
3party/Alohalytics/examples/android/gradlew.bat
vendored
Normal file
90
3party/Alohalytics/examples/android/gradlew.bat
vendored
Normal file
|
@ -0,0 +1,90 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
|
@ -0,0 +1,246 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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 org.alohalytics.test;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import org.alohalytics.HttpTransport;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
// <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
public class HttpTransportTest extends InstrumentationTestCase {
|
||||
|
||||
static {
|
||||
// For faster unit testing on the real-world servers.
|
||||
HttpTransport.TIMEOUT_IN_MILLISECONDS = 3000;
|
||||
}
|
||||
|
||||
private String getFullWritablePathForFile(String fileName) {
|
||||
return getInstrumentation().getContext().getCacheDir().getAbsolutePath() + "/" + fileName;
|
||||
}
|
||||
|
||||
public void testGetIntoMemory() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/drip?numbytes=7");
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(200, r.httpResponseCode);
|
||||
assertEquals(p.url, r.receivedUrl);
|
||||
assertEquals("*******", new String(r.data));
|
||||
}
|
||||
|
||||
public void testGetIntoFile() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/drip?numbytes=5");
|
||||
try {
|
||||
p.outputFilePath = getFullWritablePathForFile("some_test_file_for_http_get");
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(200, r.httpResponseCode);
|
||||
assertNull(r.data);
|
||||
assertEquals("*****", Util.ReadFileAsUtf8String(p.outputFilePath));
|
||||
} finally {
|
||||
(new File(p.outputFilePath)).delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void testPostFromMemoryIntoMemory() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/post");
|
||||
final String postBody = "Hello, World!";
|
||||
p.data = postBody.getBytes();
|
||||
p.contentType = "application/octet-stream";
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(200, r.httpResponseCode);
|
||||
final String receivedBody = new String(r.data);
|
||||
// Server mirrors our content which we have gzipped.
|
||||
assertTrue(receivedBody, receivedBody.contains("\"Content-Encoding\": \"gzip\""));
|
||||
assertTrue(receivedBody, receivedBody.contains("data:application/octet-stream;base64,H4sIAAAAAAAAAPNIzcnJ11EIzy/KSVEEANDDSuwNAAAA"));
|
||||
}
|
||||
|
||||
public void testPostMissingContentType() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/post");
|
||||
p.data = "Hello, World!".getBytes();
|
||||
// here is missing p.contentType = "application/octet-stream";
|
||||
boolean caughtException = false;
|
||||
try {
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertFalse(true);
|
||||
} catch (NullPointerException ex) {
|
||||
caughtException = true;
|
||||
}
|
||||
assertTrue(caughtException);
|
||||
}
|
||||
|
||||
public void testPostFromInvalidFile() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/post");
|
||||
p.inputFilePath = getFullWritablePathForFile("this_file_should_not_exist");
|
||||
p.contentType = "text/plain";
|
||||
boolean caughtException = false;
|
||||
try {
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertFalse(true);
|
||||
} catch (FileNotFoundException ex) {
|
||||
caughtException = true;
|
||||
}
|
||||
assertTrue(caughtException);
|
||||
}
|
||||
|
||||
public void testPostFromFileIntoMemory() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/post");
|
||||
p.inputFilePath = getFullWritablePathForFile("some_input_test_file_for_http_post");
|
||||
p.contentType = "text/plain";
|
||||
try {
|
||||
// Use file name as a test string for the post body.
|
||||
Util.WriteStringToFile(p.inputFilePath, p.inputFilePath);
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(200, r.httpResponseCode);
|
||||
final String receivedBody = new String(p.data);
|
||||
assertTrue(receivedBody, receivedBody.contains(p.inputFilePath));
|
||||
} finally {
|
||||
(new File(p.inputFilePath)).delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void testPostFromMemoryIntoFile() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/post");
|
||||
p.outputFilePath = getFullWritablePathForFile("some_output_test_file_for_http_post");
|
||||
p.data = p.url.getBytes(); // Use server url as a test string for the post body.
|
||||
p.contentType = "text/plain";
|
||||
try {
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(200, r.httpResponseCode);
|
||||
// TODO(AlexZ): Think about using data field in the future for error pages (404 etc)
|
||||
//assertNull(r.data);
|
||||
final String receivedBody = Util.ReadFileAsUtf8String(p.outputFilePath);
|
||||
assertTrue(receivedBody, receivedBody.contains("\"Content-Encoding\": \"gzip\""));
|
||||
assertTrue(receivedBody, receivedBody.contains("data:application/octet-stream;base64,H4sIAAAAAAAAAMsoKSmw0tfPAFJJmXl6+UXp+gX5xSUAYum2hhcAAAA="));
|
||||
} finally {
|
||||
(new File(p.outputFilePath)).delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void testPostFromFileIntoFile() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/post");
|
||||
p.inputFilePath = getFullWritablePathForFile("some_complex_input_test_file_for_http_post");
|
||||
p.outputFilePath = getFullWritablePathForFile("some_complex_output_test_file_for_http_post");
|
||||
p.contentType = "text/plain";
|
||||
final String postBodyToSend = "Aloha, this text should pass from one file to another. Mahalo!";
|
||||
try {
|
||||
Util.WriteStringToFile(postBodyToSend, p.inputFilePath);
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(200, r.httpResponseCode);
|
||||
final String receivedBody = Util.ReadFileAsUtf8String(p.outputFilePath);
|
||||
assertTrue(receivedBody, receivedBody.contains(postBodyToSend));
|
||||
} finally {
|
||||
(new File(p.inputFilePath)).delete();
|
||||
(new File(p.outputFilePath)).delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void testErrorCodes() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/status/403");
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(403, r.httpResponseCode);
|
||||
}
|
||||
|
||||
public void testHttps() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("https://httpbin.org/get?Aloha=Mahalo");
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(200, r.httpResponseCode);
|
||||
assertEquals(r.url, r.receivedUrl);
|
||||
final String receivedBody = new String(r.data);
|
||||
assertTrue(receivedBody.contains("\"Aloha\": \"Mahalo\""));
|
||||
}
|
||||
|
||||
public void testHttpRedirect302() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/redirect-to?url=/get");
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(200, r.httpResponseCode);
|
||||
assertEquals(r.receivedUrl, "http://httpbin.org/get");
|
||||
assertTrue(!r.url.equals(r.receivedUrl));
|
||||
}
|
||||
|
||||
public void testUserAgent() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/user-agent");
|
||||
p.userAgent = "Aloha User Agent";
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(200, r.httpResponseCode);
|
||||
final String receivedBody = new String(r.data);
|
||||
assertTrue(receivedBody.contains(p.userAgent));
|
||||
}
|
||||
|
||||
// Default HTTPUrlConnection implementation doesn't automatically follow http <==> https redirects
|
||||
public void disabled_testHttpRedirect301FromHttpToHttps() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://github.com");
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(200, r.httpResponseCode);
|
||||
assertEquals(r.receivedUrl, "https://github.com/");
|
||||
assertTrue(r.url.equals(r.receivedUrl));
|
||||
}
|
||||
|
||||
public void testHttpRedirect301() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://maps.me");
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(200, r.httpResponseCode);
|
||||
assertEquals(r.receivedUrl, "http://maps.me/en/home");
|
||||
assertTrue(!r.url.equals(r.receivedUrl));
|
||||
}
|
||||
|
||||
public void testInvalidHost() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://very.bad.host");
|
||||
boolean caughtException = false;
|
||||
try {
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(-1, r.httpResponseCode);
|
||||
assertFalse(true);
|
||||
} catch (java.net.UnknownHostException ex) {
|
||||
caughtException = true;
|
||||
}
|
||||
assertTrue(caughtException);
|
||||
}
|
||||
|
||||
public void testPostFromEmptyFileIntoMemory() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/post");
|
||||
p.inputFilePath = getFullWritablePathForFile("empty_input_test_file_for_http_post");
|
||||
p.contentType = "text/plain";
|
||||
try {
|
||||
Util.WriteStringToFile("", p.inputFilePath);
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(200, r.httpResponseCode);
|
||||
final String receivedBody = new String(p.data);
|
||||
assertTrue(receivedBody, receivedBody.contains("\"data\": \"\""));
|
||||
assertTrue(receivedBody, receivedBody.contains("\"form\": {}"));
|
||||
} finally {
|
||||
(new File(p.inputFilePath)).delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void testResponseContentType() throws Exception {
|
||||
final HttpTransport.Params p = new HttpTransport.Params("http://httpbin.org/get");
|
||||
final HttpTransport.Params r = HttpTransport.run(p);
|
||||
assertEquals(200, r.httpResponseCode);
|
||||
assertEquals(p.url, r.receivedUrl);
|
||||
assertEquals("application/json", r.contentType);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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 org.alohalytics.test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class Util {
|
||||
|
||||
public static String ReadFileAsUtf8String(String filePath) throws IOException {
|
||||
final File file = new File(filePath);
|
||||
final byte[] buffer = new byte[(int) file.length()];
|
||||
final FileInputStream istream = new FileInputStream(file);
|
||||
try {
|
||||
istream.read(buffer);
|
||||
} finally {
|
||||
istream.close();
|
||||
}
|
||||
return new String(buffer, "UTF-8");
|
||||
}
|
||||
|
||||
public static void WriteStringToFile(String toWrite, String filePath) throws IOException {
|
||||
final FileOutputStream ostream = new FileOutputStream(filePath);
|
||||
try {
|
||||
ostream.write(toWrite.getBytes());
|
||||
} finally {
|
||||
ostream.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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 org.alohalytics.test;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
public class UtilTest extends InstrumentationTestCase {
|
||||
|
||||
private String getFullWritablePathForFile(String fileName) {
|
||||
return getInstrumentation().getContext().getCacheDir().getAbsolutePath() + "/" + fileName;
|
||||
}
|
||||
|
||||
public void testReadAndWriteStringToFile() throws Exception {
|
||||
final String testFileName = getFullWritablePathForFile("test_write_string_to_file");
|
||||
try {
|
||||
Util.WriteStringToFile(testFileName, testFileName);
|
||||
assertEquals(testFileName, Util.ReadFileAsUtf8String(testFileName));
|
||||
} finally {
|
||||
new File(testFileName).delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void testFailedWriteStringToFile() throws Exception {
|
||||
final String invalidFileName = getFullWritablePathForFile("invalid?path\\with/slashes");
|
||||
boolean caughtException = false;
|
||||
try {
|
||||
Util.WriteStringToFile(invalidFileName, invalidFileName);
|
||||
assertFalse(true);
|
||||
} catch (FileNotFoundException ex) {
|
||||
caughtException = true;
|
||||
} finally {
|
||||
new File(invalidFileName).delete();
|
||||
}
|
||||
assertTrue(caughtException);
|
||||
}
|
||||
|
||||
public void testFailedReadFileAsString() throws Exception {
|
||||
final String notExistingFileName = getFullWritablePathForFile("this_file_should_not_exist_for_the_test");
|
||||
boolean caughtException = false;
|
||||
try {
|
||||
final String s = Util.ReadFileAsUtf8String(notExistingFileName);
|
||||
assertFalse(true);
|
||||
} catch (FileNotFoundException ex) {
|
||||
caughtException = true;
|
||||
}
|
||||
assertTrue(caughtException);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.alohalytics.demoapp" >
|
||||
|
||||
<!-- This permission is needed if you want to access IMEI -->
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
|
||||
<!-- This one is needed to upload statistics to the server -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
|
||||
<activity
|
||||
android:name="org.alohalytics.demoapp.MainActivity"
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Used to automatically upload statistics using WiFi. -->
|
||||
<receiver
|
||||
android:name="org.alohalytics.ConnectivityChangedReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,111 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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 org.alohalytics.demoapp;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.alohalytics.Statistics;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class MainActivity extends Activity {
|
||||
|
||||
static {
|
||||
System.loadLibrary("alohalytics");
|
||||
}
|
||||
|
||||
private static final String STATISTICS_SERVER_URL = "http://localhost:8080/";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Statistics.setup(STATISTICS_SERVER_URL, this);
|
||||
// Optionally enable debug mode for easier integration (but don't forget to remove it in production!).
|
||||
Statistics.setDebugMode(true);
|
||||
|
||||
// To handle Enter key for convenience testing on emulator
|
||||
findViewById(R.id.eventNameEditor).setOnKeyListener(new View.OnKeyListener() {
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
// If the event is a key-down event on the "enter" button
|
||||
if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
|
||||
onSendButtonClicked(null);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// Very simple event.
|
||||
Statistics.logEvent("simple_event");
|
||||
|
||||
// Event with parameter (key=value)
|
||||
Statistics.logEvent("device_manufacturer", Build.MANUFACTURER);
|
||||
|
||||
final HashMap<String, String> kv = new HashMap<String, String>();
|
||||
kv.put("brand", Build.BRAND);
|
||||
kv.put("device", Build.DEVICE);
|
||||
kv.put("model", Build.MODEL);
|
||||
// Event with a key=value pairs.
|
||||
Statistics.logEvent("device", kv);
|
||||
|
||||
final String packageName = getPackageName();
|
||||
// Last version null value will be replaced below.
|
||||
final String[] arr = {"package", packageName, "demo_null_value", null, "version", null};
|
||||
try {
|
||||
arr[arr.length - 1] = getPackageManager().getPackageInfo(packageName, 0).versionName;
|
||||
} catch (PackageManager.NameNotFoundException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
// Event with a key=value pairs but passed as an array.
|
||||
Statistics.logEvent("app", arr);
|
||||
|
||||
// Example event to track user activity.
|
||||
Statistics.logEvent("$onResume", getLocalClassName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
Statistics.logEvent("$onPause", getLocalClassName());
|
||||
}
|
||||
|
||||
public void onSendButtonClicked(View v) {
|
||||
final String eventValue = ((TextView) findViewById(R.id.eventNameEditor)).getText().toString();
|
||||
Statistics.logEvent("send_button_clicked", eventValue);
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
|
@ -0,0 +1,42 @@
|
|||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context=".MainActivity"
|
||||
android:id="@+id/activity_main">
|
||||
|
||||
<EditText
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/eventNameEditor"
|
||||
android:enabled="true"
|
||||
android:focusable="true"
|
||||
android:hint="@string/test_event_hint"
|
||||
android:inputType="textUri"
|
||||
android:layout_toLeftOf="@+id/button"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/logView"
|
||||
android:layout_below="@+id/eventNameEditor"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/send"
|
||||
android:id="@+id/button"
|
||||
android:onClick="onSendButtonClicked"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"/>
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,5 @@
|
|||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Aloha Stats</string>
|
||||
<string name="send">Send</string>
|
||||
<string name="test_event_hint">Test Event Name</string>
|
||||
|
||||
</resources>
|
321
3party/Alohalytics/examples/cpp/dflags.h
Normal file
321
3party/Alohalytics/examples/cpp/dflags.h
Normal file
|
@ -0,0 +1,321 @@
|
|||
/*****************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Dmitry ("Dima") Korolev
|
||||
|
||||
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.
|
||||
******************************************************************************/
|
||||
|
||||
// Header-only command line flags parsing library for C++11. Inspired by Google's gflags. Synopsis:
|
||||
|
||||
/*
|
||||
|
||||
DEFINE_int32(answer, 42, "Human-readable flag description.");
|
||||
DEFINE_string(question, "six by nine", "Another human-readable flag description.");
|
||||
|
||||
void example() {
|
||||
std::cout << FLAGS_question.length() << ' ' << FLAGS_answer * FLAGS_answer << std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ParseDFlags(&argc, &argv);
|
||||
// Alternatively, `google::ParseCommandLineFlags(&argc, &argv);` works for compatibility reasons.
|
||||
example();
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
// Supports `string` as `std::string`, int32, uint32, int64, uint64, float, double and bool.
|
||||
// Booleans accept 0/1 and lowercase or capitalized true/false/yes/no.
|
||||
//
|
||||
// Flags can be passed in as "-flag=value", "--flag=value", "-flag value" or "--flag value" parameters.
|
||||
//
|
||||
// Undefined flag triggers an error message dumped into stderr followed by exit(-1).
|
||||
// Same happens if `ParseDFlags()` was not called or was attempted to be called more than once.
|
||||
//
|
||||
// Non-flag parameters are kept; ParseDFlags() replaces argc/argv with the new,
|
||||
// updated values, eliminating the ones holding the parsed flags.
|
||||
// In other words ./main foo --flag_bar=bar baz results in argc=2, new argv == { argv[0], "foo", "baz" }.
|
||||
//
|
||||
// Passing --help will cause ParseDFlags() to print all registered flags with their descriptions and exit(0).
|
||||
|
||||
#ifndef DFLAGS_H
|
||||
#define DFLAGS_H
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace dflags {
|
||||
|
||||
inline void TerminateExecution(const int code, const std::string& message) {
|
||||
std::cerr << message << std::endl;
|
||||
std::exit(code);
|
||||
}
|
||||
|
||||
class FlagRegistererBase {
|
||||
public:
|
||||
virtual ~FlagRegistererBase() {}
|
||||
virtual void ParseValue(const std::string& name, const std::string& value) const = 0;
|
||||
virtual std::string TypeAsString() const = 0;
|
||||
virtual std::string DefaultValueAsString() const = 0;
|
||||
virtual std::string DescriptionAsString() const = 0;
|
||||
};
|
||||
|
||||
class FlagsRegistererSingleton {
|
||||
public:
|
||||
virtual ~FlagsRegistererSingleton() {}
|
||||
virtual void RegisterFlag(const std::string& name, FlagRegistererBase* impl) = 0;
|
||||
virtual void ParseFlags(int& argc, char**& argv) = 0;
|
||||
virtual void PrintHelpAndExit(const std::map<std::string, FlagRegistererBase*>& flags) const {
|
||||
PrintHelp(flags, HelpPrinterOStream());
|
||||
std::exit(HelpPrinterReturnCode());
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void PrintHelp(const std::map<std::string, FlagRegistererBase*>& flags, std::ostream& os) const {
|
||||
os << flags.size() << " flags registered.\n";
|
||||
for (const auto cit : flags) {
|
||||
os << "\t--" << cit.first << " , " << cit.second->TypeAsString() << "\n\t\t"
|
||||
<< cit.second->DescriptionAsString() << "\n\t\t"
|
||||
<< "Default value: " << cit.second->DefaultValueAsString() << '\n';
|
||||
}
|
||||
}
|
||||
// LCOV_EXCL_START -- exclude the following lines from unit test line coverage report.
|
||||
virtual std::ostream& HelpPrinterOStream() const { return std::cout; }
|
||||
virtual int HelpPrinterReturnCode() const { return 0; }
|
||||
// LCOV_EXCL_STOP -- exclude the above lines from unit test line coverage report.
|
||||
};
|
||||
|
||||
class FlagsManager {
|
||||
public:
|
||||
class DefaultRegisterer : public FlagsRegistererSingleton {
|
||||
public:
|
||||
void RegisterFlag(const std::string& name, FlagRegistererBase* impl) override { flags_[name] = impl; }
|
||||
|
||||
void ParseFlags(int& argc, char**& argv) override {
|
||||
if (parse_flags_called_) {
|
||||
TerminateExecution(-1, "ParseDFlags() is called more than once.");
|
||||
}
|
||||
parse_flags_called_ = true;
|
||||
argv_.push_back(argv[0]);
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (argv[i][0] == '-') {
|
||||
const char* flag = argv[i];
|
||||
size_t dashes_count = 0;
|
||||
while (*flag == '-') {
|
||||
++flag;
|
||||
++dashes_count;
|
||||
if (dashes_count > 2) {
|
||||
TerminateExecution(-1,
|
||||
std::string() + "Parameter: '" + argv[i] + "' has too many dashes in front.");
|
||||
}
|
||||
}
|
||||
const char* equals_sign = strstr(flag, "=");
|
||||
const std::pair<std::string, const char*> key_value =
|
||||
equals_sign ? std::make_pair(std::string(flag, equals_sign), equals_sign + 1)
|
||||
: (++i, std::make_pair(flag, argv[i]));
|
||||
if (key_value.first == "help") {
|
||||
UserRequestedHelp();
|
||||
} else {
|
||||
if (i == argc) {
|
||||
TerminateExecution(
|
||||
-1, std::string() + "Flag: '" + key_value.first + "' is not provided with the value.");
|
||||
}
|
||||
const auto cit = flags_.find(key_value.first);
|
||||
if (cit == flags_.end()) {
|
||||
TerminateExecution(-1, std::string() + "Undefined flag: '" + key_value.first + "'.");
|
||||
} else {
|
||||
cit->second->ParseValue(cit->first, key_value.second);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
argv_.push_back(argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
argc = argv_.size();
|
||||
argv = &argv_[0];
|
||||
}
|
||||
|
||||
private:
|
||||
void UserRequestedHelp() {
|
||||
Singleton().PrintHelpAndExit(flags_);
|
||||
} // LCOV_EXCL_LINE -- exclude this line from unit test line coverage report.
|
||||
|
||||
std::map<std::string, FlagRegistererBase*> flags_;
|
||||
bool parse_flags_called_ = false;
|
||||
std::vector<char*> argv_;
|
||||
};
|
||||
|
||||
class ScopedSingletonInjector {
|
||||
public:
|
||||
explicit ScopedSingletonInjector(FlagsRegistererSingleton* ptr)
|
||||
: current_ptr_(MockableSingletonGetterAndSetter()) {
|
||||
MockableSingletonGetterAndSetter(ptr);
|
||||
}
|
||||
~ScopedSingletonInjector() { MockableSingletonGetterAndSetter(current_ptr_); }
|
||||
explicit ScopedSingletonInjector(FlagsRegistererSingleton& ref) : ScopedSingletonInjector(&ref) {}
|
||||
|
||||
private:
|
||||
FlagsRegistererSingleton* current_ptr_;
|
||||
};
|
||||
|
||||
static FlagsRegistererSingleton* MockableSingletonGetterAndSetter(
|
||||
FlagsRegistererSingleton* inject_ptr = nullptr) {
|
||||
static DefaultRegisterer singleton;
|
||||
static FlagsRegistererSingleton* ptr = &singleton;
|
||||
if (inject_ptr) {
|
||||
ptr = inject_ptr;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static FlagsRegistererSingleton& Singleton() { return *MockableSingletonGetterAndSetter(); }
|
||||
|
||||
static void RegisterFlag(const std::string& name, FlagRegistererBase* impl) {
|
||||
Singleton().RegisterFlag(name, impl);
|
||||
}
|
||||
|
||||
static void ParseFlags(int& argc, char**& argv) { Singleton().ParseFlags(argc, argv); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
bool FromStringSupportingStringAndBool(const std::string& from, T& to) {
|
||||
std::istringstream is(from);
|
||||
// Workaronud for a bug in `clang++ -std=c++11` on Mac, clang++ --version `LLVM version 6.0 (clang-600.0.56)`.
|
||||
// See: http://www.quora.com/Does-Macs-clang++-have-a-bug-with-return-type-of-templated-functions
|
||||
return static_cast<bool>(is >> to);
|
||||
}
|
||||
|
||||
template <>
|
||||
bool FromStringSupportingStringAndBool(const std::string& from, std::string& to) {
|
||||
to = from;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool FromStringSupportingStringAndBool(const std::string& from, bool& to) {
|
||||
if (from == "0" || from == "false" || from == "False" || from == "no" || from == "No") {
|
||||
to = false;
|
||||
return true;
|
||||
} else if (from == "1" || from == "true" || from == "True" || from == "yes" || from == "Yes") {
|
||||
to = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::string ToStringSupportingStringAndBool(T x) {
|
||||
return std::to_string(x);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string ToStringSupportingStringAndBool(std::string s) {
|
||||
return std::string("'") + s + "'";
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string ToStringSupportingStringAndBool(bool b) {
|
||||
return b ? "True" : "False";
|
||||
}
|
||||
|
||||
template <typename FLAG_TYPE>
|
||||
class FlagRegisterer : public FlagRegistererBase {
|
||||
public:
|
||||
FlagRegisterer(FLAG_TYPE& ref,
|
||||
const std::string& name,
|
||||
const std::string& type,
|
||||
const FLAG_TYPE default_value,
|
||||
const std::string& description)
|
||||
: ref_(ref), name_(name), type_(type), default_value_(default_value), description_(description) {
|
||||
FlagsManager::RegisterFlag(name, this);
|
||||
}
|
||||
|
||||
virtual void ParseValue(const std::string& name, const std::string& value) const override {
|
||||
if (!FromStringSupportingStringAndBool(value, ref_)) {
|
||||
TerminateExecution(-1, std::string("Can not parse '") + value + "' for flag '" + name + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
virtual std::string TypeAsString() const override { return type_; }
|
||||
|
||||
virtual std::string DefaultValueAsString() const override {
|
||||
return ToStringSupportingStringAndBool(default_value_);
|
||||
}
|
||||
|
||||
virtual std::string DescriptionAsString() const override { return description_; }
|
||||
|
||||
private:
|
||||
FLAG_TYPE& ref_;
|
||||
const std::string name_;
|
||||
const std::string type_;
|
||||
const FLAG_TYPE default_value_;
|
||||
const std::string description_;
|
||||
};
|
||||
|
||||
#define DEFINE_flag(type, name, default_value, description) \
|
||||
type FLAGS_##name = default_value; \
|
||||
::dflags::FlagRegisterer<type> REGISTERER_##name( \
|
||||
std::ref(FLAGS_##name), #name, #type, default_value, description)
|
||||
|
||||
#define DEFINE_int8(name, default_value, description) DEFINE_flag(int8_t, name, default_value, description)
|
||||
#define DEFINE_uint8(name, default_value, description) DEFINE_flag(uint8_t, name, default_value, description)
|
||||
#define DEFINE_int16(name, default_value, description) DEFINE_flag(int16_t, name, default_value, description)
|
||||
#define DEFINE_uint16(name, default_value, description) DEFINE_flag(uint16_t, name, default_value, description)
|
||||
#define DEFINE_int32(name, default_value, description) DEFINE_flag(int32_t, name, default_value, description)
|
||||
#define DEFINE_uint32(name, default_value, description) DEFINE_flag(uint32_t, name, default_value, description)
|
||||
#define DEFINE_int64(name, default_value, description) DEFINE_flag(int64_t, name, default_value, description)
|
||||
#define DEFINE_uint64(name, default_value, description) DEFINE_flag(uint64_t, name, default_value, description)
|
||||
#define DEFINE_float(name, default_value, description) DEFINE_flag(float, name, default_value, description)
|
||||
#define DEFINE_double(name, default_value, description) DEFINE_flag(double, name, default_value, description)
|
||||
#define DEFINE_string(name, default_value, description) \
|
||||
DEFINE_flag(std::string, name, default_value, description)
|
||||
#define DEFINE_bool(name, default_value, description) DEFINE_flag(bool, name, default_value, description)
|
||||
|
||||
} // namespace dflags
|
||||
|
||||
inline void ParseDFlags(int* argc, char*** argv) { ::dflags::FlagsManager::ParseFlags(*argc, *argv); }
|
||||
|
||||
namespace fake_google {
|
||||
struct UnambiguousGoogleFriendlyIntPointerWrapper {
|
||||
int* p;
|
||||
inline UnambiguousGoogleFriendlyIntPointerWrapper(int* p) : p(p) {}
|
||||
inline operator int*() { return p; }
|
||||
};
|
||||
inline bool ParseCommandLineFlags(UnambiguousGoogleFriendlyIntPointerWrapper argc, char*** argv, bool = true) {
|
||||
ParseDFlags(argc, argv);
|
||||
return true;
|
||||
}
|
||||
} // namespace fake_google.
|
||||
|
||||
using fake_google::ParseCommandLineFlags;
|
||||
|
||||
namespace google {
|
||||
using fake_google::ParseCommandLineFlags;
|
||||
}
|
||||
|
||||
#endif // DFLAGS_H
|
113
3party/Alohalytics/examples/cpp/example.cc
Normal file
113
3party/Alohalytics/examples/cpp/example.cc
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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.
|
||||
*******************************************************************************/
|
||||
|
||||
// Core statistics engine.
|
||||
#include "../../src/alohalytics.h"
|
||||
// dflags is optional and is used here just for command line parameters parsing.
|
||||
#include "dflags.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
DEFINE_string(server_url, "", "Statistics server url.");
|
||||
DEFINE_string(event, "TestEvent", "Records given event.");
|
||||
DEFINE_string(values,
|
||||
"",
|
||||
"Records event with single value (--values singleValue) or value pairs (--values "
|
||||
"key1=value1,key2=value2).");
|
||||
DEFINE_string(storage,
|
||||
"",
|
||||
"Path to directory (with a slash at the end) to store recorded events before they are sent.");
|
||||
DEFINE_bool(debug, true, "Enables debug mode for statistics engine.");
|
||||
DEFINE_bool(upload, false, "If true, triggers event to forcebly upload all statistics to the server.");
|
||||
DEFINE_double(sleep, 1, "The number of seconds to sleep before terminating.");
|
||||
DEFINE_string(id, "0xDEADBABA", "Unique client id.");
|
||||
|
||||
using namespace std;
|
||||
using alohalytics::Stats;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ParseDFlags(&argc, &argv);
|
||||
|
||||
Stats& stats = Stats::Instance();
|
||||
|
||||
if (FLAGS_debug) {
|
||||
stats.SetDebugMode(true);
|
||||
}
|
||||
|
||||
if (!FLAGS_server_url.empty()) {
|
||||
stats.SetServerUrl(FLAGS_server_url);
|
||||
}
|
||||
|
||||
if (!FLAGS_storage.empty()) {
|
||||
stats.SetStoragePath(FLAGS_storage);
|
||||
}
|
||||
|
||||
if (!FLAGS_id.empty()) {
|
||||
stats.SetClientId(FLAGS_id);
|
||||
}
|
||||
|
||||
if (!FLAGS_event.empty()) {
|
||||
if (!FLAGS_values.empty()) {
|
||||
string values = FLAGS_values;
|
||||
for (auto& c : values) {
|
||||
if (c == '=' || c == ',') {
|
||||
c = ' ';
|
||||
}
|
||||
}
|
||||
string key;
|
||||
alohalytics::TStringMap map;
|
||||
istringstream is(values);
|
||||
string it;
|
||||
while (is >> it) {
|
||||
if (key.empty()) {
|
||||
key = it;
|
||||
map[key] = "";
|
||||
} else {
|
||||
map[key] = it;
|
||||
key.clear();
|
||||
}
|
||||
}
|
||||
if (map.size() == 1 && map.begin()->second == "") {
|
||||
// Event with one value.
|
||||
stats.LogEvent(FLAGS_event, map.begin()->first);
|
||||
} else {
|
||||
// Event with many key=value pairs.
|
||||
stats.LogEvent(FLAGS_event, map);
|
||||
}
|
||||
} else {
|
||||
// Simple event.
|
||||
stats.LogEvent(FLAGS_event);
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAGS_upload) {
|
||||
stats.Upload();
|
||||
}
|
||||
|
||||
this_thread::sleep_for(chrono::milliseconds(static_cast<uint32_t>(1e3 * FLAGS_sleep)));
|
||||
|
||||
return 0;
|
||||
}
|
7
3party/Alohalytics/examples/cpp/test.sh
Executable file
7
3party/Alohalytics/examples/cpp/test.sh
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
SERVER=${ALOHA_SERVER:-http://localhost/upload}
|
||||
make clean all
|
||||
./build/example --server_url=$SERVER --event=Event1
|
||||
./build/example --server_url=$SERVER --values=Value1
|
||||
./build/example --server_url=$SERVER --event=Event2 --values=Value2
|
||||
./build/example --server_url=$SERVER --event=Event3 --values=Foo=foo,Bar=bar
|
3
3party/Alohalytics/examples/ios/README.md
Normal file
3
3party/Alohalytics/examples/ios/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
How to integrate AlohaStats into your code:
|
||||
|
||||
TODO(AlexZ)
|
|
@ -0,0 +1,429 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
6BDEF0571A3CF2D100054FAC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BDEF0561A3CF2D100054FAC /* main.m */; };
|
||||
6BDEF05A1A3CF2D100054FAC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BDEF0591A3CF2D100054FAC /* AppDelegate.m */; };
|
||||
6BDEF05D1A3CF2D100054FAC /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BDEF05C1A3CF2D100054FAC /* ViewController.m */; };
|
||||
6BDEF0601A3CF2D100054FAC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6BDEF05E1A3CF2D100054FAC /* Main.storyboard */; };
|
||||
6BDEF0621A3CF2D100054FAC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6BDEF0611A3CF2D100054FAC /* Images.xcassets */; };
|
||||
6BDEF0651A3CF2D100054FAC /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6BDEF0631A3CF2D100054FAC /* LaunchScreen.xib */; };
|
||||
6BDEF0711A3CF2D100054FAC /* SampleClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BDEF0701A3CF2D100054FAC /* SampleClientTests.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
6BDEF06B1A3CF2D100054FAC /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6BDEF0491A3CF2D100054FAC /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 6BDEF0501A3CF2D100054FAC;
|
||||
remoteInfo = SampleClient;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
6BDEF0511A3CF2D100054FAC /* SampleClient.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleClient.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6BDEF0551A3CF2D100054FAC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
6BDEF0561A3CF2D100054FAC /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
6BDEF0581A3CF2D100054FAC /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
6BDEF0591A3CF2D100054FAC /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||
6BDEF05B1A3CF2D100054FAC /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
|
||||
6BDEF05C1A3CF2D100054FAC /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
|
||||
6BDEF05F1A3CF2D100054FAC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
6BDEF0611A3CF2D100054FAC /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
6BDEF0641A3CF2D100054FAC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
||||
6BDEF06A1A3CF2D100054FAC /* SampleClientTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleClientTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6BDEF06F1A3CF2D100054FAC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
6BDEF0701A3CF2D100054FAC /* SampleClientTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleClientTests.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
6BDEF04E1A3CF2D100054FAC /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6BDEF0671A3CF2D100054FAC /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
6BDEF0481A3CF2D100054FAC = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6BDEF0531A3CF2D100054FAC /* SampleClient */,
|
||||
6BDEF06D1A3CF2D100054FAC /* SampleClientTests */,
|
||||
6BDEF0521A3CF2D100054FAC /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6BDEF0521A3CF2D100054FAC /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6BDEF0511A3CF2D100054FAC /* SampleClient.app */,
|
||||
6BDEF06A1A3CF2D100054FAC /* SampleClientTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6BDEF0531A3CF2D100054FAC /* SampleClient */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6BDEF0581A3CF2D100054FAC /* AppDelegate.h */,
|
||||
6BDEF0591A3CF2D100054FAC /* AppDelegate.m */,
|
||||
6BDEF05B1A3CF2D100054FAC /* ViewController.h */,
|
||||
6BDEF05C1A3CF2D100054FAC /* ViewController.m */,
|
||||
6BDEF05E1A3CF2D100054FAC /* Main.storyboard */,
|
||||
6BDEF0611A3CF2D100054FAC /* Images.xcassets */,
|
||||
6BDEF0631A3CF2D100054FAC /* LaunchScreen.xib */,
|
||||
6BDEF0541A3CF2D100054FAC /* Supporting Files */,
|
||||
);
|
||||
path = SampleClient;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6BDEF0541A3CF2D100054FAC /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6BDEF0551A3CF2D100054FAC /* Info.plist */,
|
||||
6BDEF0561A3CF2D100054FAC /* main.m */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6BDEF06D1A3CF2D100054FAC /* SampleClientTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6BDEF0701A3CF2D100054FAC /* SampleClientTests.m */,
|
||||
6BDEF06E1A3CF2D100054FAC /* Supporting Files */,
|
||||
);
|
||||
path = SampleClientTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6BDEF06E1A3CF2D100054FAC /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6BDEF06F1A3CF2D100054FAC /* Info.plist */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
6BDEF0501A3CF2D100054FAC /* SampleClient */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 6BDEF0741A3CF2D100054FAC /* Build configuration list for PBXNativeTarget "SampleClient" */;
|
||||
buildPhases = (
|
||||
6BDEF04D1A3CF2D100054FAC /* Sources */,
|
||||
6BDEF04E1A3CF2D100054FAC /* Frameworks */,
|
||||
6BDEF04F1A3CF2D100054FAC /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = SampleClient;
|
||||
productName = SampleClient;
|
||||
productReference = 6BDEF0511A3CF2D100054FAC /* SampleClient.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
6BDEF0691A3CF2D100054FAC /* SampleClientTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 6BDEF0771A3CF2D100054FAC /* Build configuration list for PBXNativeTarget "SampleClientTests" */;
|
||||
buildPhases = (
|
||||
6BDEF0661A3CF2D100054FAC /* Sources */,
|
||||
6BDEF0671A3CF2D100054FAC /* Frameworks */,
|
||||
6BDEF0681A3CF2D100054FAC /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
6BDEF06C1A3CF2D100054FAC /* PBXTargetDependency */,
|
||||
);
|
||||
name = SampleClientTests;
|
||||
productName = SampleClientTests;
|
||||
productReference = 6BDEF06A1A3CF2D100054FAC /* SampleClientTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
6BDEF0491A3CF2D100054FAC /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0610;
|
||||
ORGANIZATIONNAME = "Alexander Zolotarev";
|
||||
TargetAttributes = {
|
||||
6BDEF0501A3CF2D100054FAC = {
|
||||
CreatedOnToolsVersion = 6.1.1;
|
||||
};
|
||||
6BDEF0691A3CF2D100054FAC = {
|
||||
CreatedOnToolsVersion = 6.1.1;
|
||||
TestTargetID = 6BDEF0501A3CF2D100054FAC;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 6BDEF04C1A3CF2D100054FAC /* Build configuration list for PBXProject "SampleClient" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 6BDEF0481A3CF2D100054FAC;
|
||||
productRefGroup = 6BDEF0521A3CF2D100054FAC /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
6BDEF0501A3CF2D100054FAC /* SampleClient */,
|
||||
6BDEF0691A3CF2D100054FAC /* SampleClientTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
6BDEF04F1A3CF2D100054FAC /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6BDEF0601A3CF2D100054FAC /* Main.storyboard in Resources */,
|
||||
6BDEF0651A3CF2D100054FAC /* LaunchScreen.xib in Resources */,
|
||||
6BDEF0621A3CF2D100054FAC /* Images.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6BDEF0681A3CF2D100054FAC /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
6BDEF04D1A3CF2D100054FAC /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6BDEF05D1A3CF2D100054FAC /* ViewController.m in Sources */,
|
||||
6BDEF05A1A3CF2D100054FAC /* AppDelegate.m in Sources */,
|
||||
6BDEF0571A3CF2D100054FAC /* main.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6BDEF0661A3CF2D100054FAC /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6BDEF0711A3CF2D100054FAC /* SampleClientTests.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
6BDEF06C1A3CF2D100054FAC /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 6BDEF0501A3CF2D100054FAC /* SampleClient */;
|
||||
targetProxy = 6BDEF06B1A3CF2D100054FAC /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
6BDEF05E1A3CF2D100054FAC /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
6BDEF05F1A3CF2D100054FAC /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6BDEF0631A3CF2D100054FAC /* LaunchScreen.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
6BDEF0641A3CF2D100054FAC /* Base */,
|
||||
);
|
||||
name = LaunchScreen.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
6BDEF0721A3CF2D100054FAC /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
6BDEF0731A3CF2D100054FAC /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
6BDEF0751A3CF2D100054FAC /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
INFOPLIST_FILE = SampleClient/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
6BDEF0761A3CF2D100054FAC /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
INFOPLIST_FILE = SampleClient/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
6BDEF0781A3CF2D100054FAC /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = SampleClientTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SampleClient.app/SampleClient";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
6BDEF0791A3CF2D100054FAC /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = SampleClientTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SampleClient.app/SampleClient";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
6BDEF04C1A3CF2D100054FAC /* Build configuration list for PBXProject "SampleClient" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
6BDEF0721A3CF2D100054FAC /* Debug */,
|
||||
6BDEF0731A3CF2D100054FAC /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
6BDEF0741A3CF2D100054FAC /* Build configuration list for PBXNativeTarget "SampleClient" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
6BDEF0751A3CF2D100054FAC /* Debug */,
|
||||
6BDEF0761A3CF2D100054FAC /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
};
|
||||
6BDEF0771A3CF2D100054FAC /* Build configuration list for PBXNativeTarget "SampleClientTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
6BDEF0781A3CF2D100054FAC /* Debug */,
|
||||
6BDEF0791A3CF2D100054FAC /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 6BDEF0491A3CF2D100054FAC /* Project object */;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:SampleClient.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDESourceControlProjectFavoriteDictionaryKey</key>
|
||||
<false/>
|
||||
<key>IDESourceControlProjectIdentifier</key>
|
||||
<string>DC49AAAD-79F8-48DF-87B1-7B0856F93DB4</string>
|
||||
<key>IDESourceControlProjectName</key>
|
||||
<string>SampleClient</string>
|
||||
<key>IDESourceControlProjectOriginsDictionary</key>
|
||||
<dict>
|
||||
<key>B29F426D1BF5D0F0A418F392D1F363C5DC680EA5</key>
|
||||
<string>github.com:KnowSheet/AlohaStatsClient.git</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectPath</key>
|
||||
<string>examples/ios/SampleClient.xcodeproj</string>
|
||||
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
|
||||
<dict>
|
||||
<key>B29F426D1BF5D0F0A418F392D1F363C5DC680EA5</key>
|
||||
<string>../../../..</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectURL</key>
|
||||
<string>github.com:KnowSheet/AlohaStatsClient.git</string>
|
||||
<key>IDESourceControlProjectVersion</key>
|
||||
<integer>111</integer>
|
||||
<key>IDESourceControlProjectWCCIdentifier</key>
|
||||
<string>B29F426D1BF5D0F0A418F392D1F363C5DC680EA5</string>
|
||||
<key>IDESourceControlProjectWCConfigurations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>B29F426D1BF5D0F0A418F392D1F363C5DC680EA5</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>AlohaStatsClient</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
Binary file not shown.
|
@ -0,0 +1,110 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0610"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6BDEF0501A3CF2D100054FAC"
|
||||
BuildableName = "SampleClient.app"
|
||||
BlueprintName = "SampleClient"
|
||||
ReferencedContainer = "container:SampleClient.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6BDEF0691A3CF2D100054FAC"
|
||||
BuildableName = "SampleClientTests.xctest"
|
||||
BlueprintName = "SampleClientTests"
|
||||
ReferencedContainer = "container:SampleClient.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6BDEF0691A3CF2D100054FAC"
|
||||
BuildableName = "SampleClientTests.xctest"
|
||||
BlueprintName = "SampleClientTests"
|
||||
ReferencedContainer = "container:SampleClient.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6BDEF0501A3CF2D100054FAC"
|
||||
BuildableName = "SampleClient.app"
|
||||
BlueprintName = "SampleClient"
|
||||
ReferencedContainer = "container:SampleClient.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6BDEF0501A3CF2D100054FAC"
|
||||
BuildableName = "SampleClient.app"
|
||||
BlueprintName = "SampleClient"
|
||||
ReferencedContainer = "container:SampleClient.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6BDEF0501A3CF2D100054FAC"
|
||||
BuildableName = "SampleClient.app"
|
||||
BlueprintName = "SampleClient"
|
||||
ReferencedContainer = "container:SampleClient.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>SampleClient.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict>
|
||||
<key>6BDEF0501A3CF2D100054FAC</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>6BDEF0691A3CF2D100054FAC</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
33
3party/Alohalytics/examples/ios/SampleClient/AppDelegate.h
Normal file
33
3party/Alohalytics/examples/ios/SampleClient/AppDelegate.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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.
|
||||
*******************************************************************************/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
|
||||
|
||||
@end
|
||||
|
61
3party/Alohalytics/examples/ios/SampleClient/AppDelegate.m
Normal file
61
3party/Alohalytics/examples/ios/SampleClient/AppDelegate.m
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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.
|
||||
*******************************************************************************/
|
||||
|
||||
#import "AppDelegate.h"
|
||||
|
||||
@interface AppDelegate ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
// Override point for customization after application launch.
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6214" systemVersion="14A314h" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6207"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2014 Alexander Zolotarev. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SampleClient" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="140" width="441" height="43"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
|
||||
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
|
||||
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="548" y="455"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
47
3party/Alohalytics/examples/ios/SampleClient/Info.plist
Normal file
47
3party/Alohalytics/examples/ios/SampleClient/Info.plist
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.alohaclient.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,31 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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.
|
||||
*******************************************************************************/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface ViewController : UIViewController
|
||||
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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.
|
||||
*******************************************************************************/
|
||||
|
||||
#import "ViewController.h"
|
||||
|
||||
@interface ViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation ViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
[super didReceiveMemoryWarning];
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
@end
|
32
3party/Alohalytics/examples/ios/SampleClient/main.m
Normal file
32
3party/Alohalytics/examples/ios/SampleClient/main.m
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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.
|
||||
*******************************************************************************/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
||||
}
|
||||
}
|
24
3party/Alohalytics/examples/ios/SampleClientTests/Info.plist
Normal file
24
3party/Alohalytics/examples/ios/SampleClientTests/Info.plist
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.alohaclient.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,56 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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.
|
||||
*******************************************************************************/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
@interface SampleClientTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation SampleClientTests
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testExample {
|
||||
// This is an example of a functional test case.
|
||||
XCTAssert(YES, @"Pass");
|
||||
}
|
||||
|
||||
- (void)testPerformanceExample {
|
||||
// This is an example of a performance test case.
|
||||
[self measureBlock:^{
|
||||
// Put the code you want to measure the time of here.
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
14
3party/Alohalytics/src/Bricks/.clang-format
Normal file
14
3party/Alohalytics/src/Bricks/.clang-format
Normal file
|
@ -0,0 +1,14 @@
|
|||
BasedOnStyle: Google
|
||||
|
||||
ColumnLimit: 112
|
||||
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
BinPackParameters: false # Use multiple lines for parameters.
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true # One per line.
|
||||
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
|
||||
Cpp11BracedListStyle: true
|
||||
|
||||
IndentCaseLabels: true
|
10
3party/Alohalytics/src/Bricks/.gitignore
vendored
Normal file
10
3party/Alohalytics/src/Bricks/.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
build
|
||||
.tmp
|
||||
core
|
||||
*.h.gch
|
||||
.exrc
|
||||
|
||||
coverage.info
|
||||
*.gcov
|
||||
*.gcda
|
||||
*.gcno
|
6
3party/Alohalytics/src/Bricks/README.md
Normal file
6
3party/Alohalytics/src/Bricks/README.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
Bricks
|
||||
======
|
||||
|
||||

|
||||
|
||||
Our own core pieces to reuse.
|
13
3party/Alohalytics/src/Bricks/exception.h
Normal file
13
3party/Alohalytics/src/Bricks/exception.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#ifndef BRICKS_EXCEPTIONS_H
|
||||
#define BRICKS_EXCEPTIONS_H
|
||||
|
||||
#include <exception>
|
||||
|
||||
namespace bricks {
|
||||
|
||||
// TODO(dkorolev): Add string descriptions.
|
||||
struct Exception : std::exception {};
|
||||
|
||||
} // namespace bricks
|
||||
|
||||
#endif // BRICKS_EXCEPTIONS_H
|
13
3party/Alohalytics/src/Bricks/file/exceptions.h
Normal file
13
3party/Alohalytics/src/Bricks/file/exceptions.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#ifndef BRICKS_FILE_EXCEPTIONS_H
|
||||
#define BRICKS_FILE_EXCEPTIONS_H
|
||||
|
||||
#include "../exception.h"
|
||||
|
||||
namespace bricks {
|
||||
|
||||
// TODO(dkorolev): Add more detailed exceptions for Read/Write/etc.
|
||||
struct FileException : Exception {};
|
||||
|
||||
} // namespace bricks
|
||||
|
||||
#endif // BRICKS_FILE_EXCEPTIONS_H
|
189
3party/Alohalytics/src/Bricks/file/file.h
Normal file
189
3party/Alohalytics/src/Bricks/file/file.h
Normal file
|
@ -0,0 +1,189 @@
|
|||
// TODO(dkorolev): Add unit tests.
|
||||
// TODO(dkorolev): Move everything under bricks::file::FileSystem and have all the tests pass.
|
||||
|
||||
#ifndef BRICKS_FILE_FILE_H
|
||||
#define BRICKS_FILE_FILE_H
|
||||
|
||||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
(c) 2014 Dmitry "Dima" Korolev <dmitry.korolev@gmail.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.
|
||||
*******************************************************************************/
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "exceptions.h"
|
||||
#include "../../logger.h"
|
||||
|
||||
namespace bricks {
|
||||
|
||||
inline std::string ReadFileAsString(std::string const& file_name) {
|
||||
try {
|
||||
std::ifstream fi;
|
||||
fi.exceptions(std::ifstream::failbit | std::ifstream::badbit);
|
||||
fi.open(file_name, std::ifstream::binary);
|
||||
fi.seekg(0, std::ios::end);
|
||||
const size_t size = fi.tellg();
|
||||
std::string buffer(size, '\0');
|
||||
fi.seekg(0);
|
||||
if (fi.read(&buffer[0], size).good()) {
|
||||
return buffer;
|
||||
} else {
|
||||
// TODO(dkorolev): Ask Alex whether there's a better way than what I have here with two exceptions.
|
||||
throw FileException();
|
||||
}
|
||||
} catch (const std::ifstream::failure&) {
|
||||
throw FileException();
|
||||
} catch (FileException()) {
|
||||
throw FileException();
|
||||
}
|
||||
}
|
||||
|
||||
inline void WriteStringToFile(const std::string& file_name, const std::string& contents, bool append = false) {
|
||||
try {
|
||||
std::ofstream fo;
|
||||
fo.exceptions(std::ofstream::failbit | std::ofstream::badbit);
|
||||
fo.open(file_name, (append ? std::ofstream::app : std::ofstream::trunc) | std::ofstream::binary);
|
||||
fo << contents;
|
||||
} catch (const std::ofstream::failure&) {
|
||||
throw FileException();
|
||||
}
|
||||
}
|
||||
|
||||
enum class RemoveFileParameters { ThrowExceptionOnError, Silent };
|
||||
inline void RemoveFile(const std::string& file_name,
|
||||
RemoveFileParameters parameters = RemoveFileParameters::ThrowExceptionOnError) {
|
||||
if (::remove(file_name.c_str())) {
|
||||
if (parameters == RemoveFileParameters::ThrowExceptionOnError) {
|
||||
throw FileException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ScopedRemoveFile final {
|
||||
public:
|
||||
explicit ScopedRemoveFile(const std::string& file_name, bool remove_now_as_well = true)
|
||||
: file_name_(file_name) {
|
||||
if (remove_now_as_well) {
|
||||
RemoveFile(file_name_, RemoveFileParameters::Silent);
|
||||
}
|
||||
}
|
||||
~ScopedRemoveFile() {
|
||||
RemoveFile(file_name_, RemoveFileParameters::Silent);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string file_name_;
|
||||
};
|
||||
|
||||
class ScopedCloseDir final {
|
||||
DIR* dir_;
|
||||
|
||||
public:
|
||||
explicit ScopedCloseDir(DIR* dir) : dir_(dir) {
|
||||
}
|
||||
~ScopedCloseDir() {
|
||||
::closedir(dir_);
|
||||
}
|
||||
};
|
||||
|
||||
// Platform-indepenent, injection-friendly filesystem wrapper.
|
||||
struct FileSystem {
|
||||
typedef std::ofstream OutputFile;
|
||||
|
||||
static inline std::string ReadFileAsString(std::string const& file_name) {
|
||||
return bricks::ReadFileAsString(file_name);
|
||||
}
|
||||
|
||||
static inline void WriteStringToFile(const std::string& file_name,
|
||||
const std::string& contents,
|
||||
bool append = false) {
|
||||
bricks::WriteStringToFile(file_name, contents, append);
|
||||
}
|
||||
|
||||
static inline std::string JoinPath(const std::string& path_name, const std::string& base_name) {
|
||||
if (path_name.empty()) {
|
||||
return base_name;
|
||||
} else if (path_name.back() == '/') {
|
||||
return path_name + base_name;
|
||||
} else {
|
||||
return path_name + '/' + base_name;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void RenameFile(const std::string& old_name, const std::string& new_name) {
|
||||
if (0 != ::rename(old_name.c_str(), new_name.c_str())) {
|
||||
ALOG("Error", errno, "renaming file", old_name, "to", new_name);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void RemoveFile(const std::string& file_name,
|
||||
RemoveFileParameters parameters = RemoveFileParameters::ThrowExceptionOnError) {
|
||||
bricks::RemoveFile(file_name, parameters);
|
||||
}
|
||||
|
||||
static inline void ScanDirUntil(const std::string& directory,
|
||||
std::function<bool(const std::string&)> lambda) {
|
||||
DIR* dir = ::opendir(directory.c_str());
|
||||
const ScopedCloseDir dir_closer(dir);
|
||||
if (dir) {
|
||||
while (struct dirent* entry = ::readdir(dir)) {
|
||||
if (*entry->d_name && ::strcmp(entry->d_name, ".") && ::strcmp(entry->d_name, "..")) {
|
||||
if (!lambda(entry->d_name)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ScanDir(const std::string& directory, std::function<void(const std::string&)> lambda) {
|
||||
ScanDirUntil(directory, [lambda](const std::string& filename) {
|
||||
lambda(filename);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static inline uint64_t GetFileSize(const std::string& file_name) {
|
||||
struct stat info;
|
||||
if (stat(file_name.c_str(), &info)) {
|
||||
// TODO(dkorolev): Throw an exception and analyze errno.
|
||||
return 0;
|
||||
} else {
|
||||
return static_cast<uint64_t>(info.st_size);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void CreateDirectory(const std::string& directory) {
|
||||
// Hard-code default permissions to avoid cross-platform compatibility issues.
|
||||
::mkdir(directory.c_str(), 0755);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace bricks
|
||||
|
||||
#endif // BRICKS_FILE_FILE_H
|
|
@ -0,0 +1,50 @@
|
|||
// Fixed-sized, zero-padded serialization and de-serialization for unsigned types of two or more bytes.
|
||||
//
|
||||
// Ported into Bricks from TailProduce.
|
||||
|
||||
#ifndef BRICKS_STRINGS_FIXED_SIZE_SERIALIZER_H
|
||||
#define BRICKS_STRINGS_FIXED_SIZE_SERIALIZER_H
|
||||
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace bricks {
|
||||
namespace strings {
|
||||
|
||||
struct FixedSizeSerializerEnabler {};
|
||||
template <typename T>
|
||||
struct FixedSizeSerializer
|
||||
: std::enable_if<std::is_unsigned<T>::value && std::is_integral<T>::value&&(sizeof(T) > 1),
|
||||
FixedSizeSerializerEnabler>::type {
|
||||
static constexpr size_t size_in_bytes = std::numeric_limits<T>::digits10 + 1;
|
||||
static std::string PackToString(T x) {
|
||||
std::ostringstream os;
|
||||
os << std::setfill('0') << std::setw(size_in_bytes) << x;
|
||||
return os.str();
|
||||
}
|
||||
static T UnpackFromString(std::string const& s) {
|
||||
T x;
|
||||
std::istringstream is(s);
|
||||
is >> x;
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
// To allow implicit type specialization wherever possible.
|
||||
template <typename T>
|
||||
inline std::string PackToString(T x) {
|
||||
return FixedSizeSerializer<T>::PackToString(x);
|
||||
}
|
||||
template <typename T>
|
||||
inline const T& UnpackFromString(std::string const& s, T& x) {
|
||||
x = FixedSizeSerializer<T>::UnpackFromString(s);
|
||||
return x;
|
||||
}
|
||||
|
||||
} // namespace string
|
||||
} // namespace bricks
|
||||
|
||||
#endif // BRICKS_STRINGS_FIXED_SIZE_SERIALIZER_H
|
23
3party/Alohalytics/src/Bricks/strings/printf.h
Normal file
23
3party/Alohalytics/src/Bricks/strings/printf.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#ifndef BRICKS_STRINGS_PRINTF_H
|
||||
#define BRICKS_STRINGS_PRINTF_H
|
||||
|
||||
#include <string>
|
||||
#include <cstdarg>
|
||||
|
||||
namespace bricks {
|
||||
namespace strings {
|
||||
|
||||
std::string Printf(const char *fmt, ...) {
|
||||
const int max_formatted_output_length = 1024 * 1024;
|
||||
static char buf[max_formatted_output_length + 1];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, max_formatted_output_length, fmt, ap);
|
||||
va_end(ap);
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // namespace string
|
||||
} // namespace bricks
|
||||
|
||||
#endif // BRICKS_STRINGS_PRINTF_H
|
56
3party/Alohalytics/src/Bricks/strings/test.cc
Normal file
56
3party/Alohalytics/src/Bricks/strings/test.cc
Normal file
|
@ -0,0 +1,56 @@
|
|||
#include "printf.h"
|
||||
#include "fixed_size_serializer.h"
|
||||
|
||||
#include "../3party/gtest/gtest.h"
|
||||
#include "../3party/gtest/gtest-main.h"
|
||||
|
||||
using bricks::strings::Printf;
|
||||
using bricks::strings::FixedSizeSerializer;
|
||||
using bricks::strings::PackToString;
|
||||
using bricks::strings::UnpackFromString;
|
||||
|
||||
TEST(StringPrintf, SmokeTest) {
|
||||
EXPECT_EQ("Test: 42, 'Hello', 0000ABBA", Printf("Test: %d, '%s', %08X", 42, "Hello", 0xabba));
|
||||
}
|
||||
|
||||
TEST(FixedSizeSerializer, UInt16) {
|
||||
EXPECT_EQ(5, FixedSizeSerializer<uint16_t>::size_in_bytes);
|
||||
// Does not fit signed 16-bit, requires unsigned.
|
||||
EXPECT_EQ("54321", FixedSizeSerializer<uint16_t>::PackToString(54321));
|
||||
EXPECT_EQ(54321, FixedSizeSerializer<uint16_t>::UnpackFromString("54321"));
|
||||
}
|
||||
|
||||
TEST(FixedSizeSerializer, UInt32) {
|
||||
EXPECT_EQ(10, FixedSizeSerializer<uint32_t>::size_in_bytes);
|
||||
// Does not fit signed 32-bit, requires unsigned.
|
||||
EXPECT_EQ("3987654321", FixedSizeSerializer<uint32_t>::PackToString(3987654321));
|
||||
EXPECT_EQ(3987654321, FixedSizeSerializer<uint32_t>::UnpackFromString("3987654321"));
|
||||
}
|
||||
|
||||
TEST(FixedSizeSerializer, UInt64) {
|
||||
EXPECT_EQ(20, FixedSizeSerializer<uint64_t>::size_in_bytes);
|
||||
uint64_t magic = 1e19;
|
||||
magic += 42;
|
||||
// Does not fit signed 64-bit.
|
||||
EXPECT_EQ("10000000000000000042", FixedSizeSerializer<uint64_t>::PackToString(magic));
|
||||
EXPECT_EQ(magic, FixedSizeSerializer<uint64_t>::UnpackFromString("10000000000000000042"));
|
||||
}
|
||||
|
||||
TEST(FixedSizeSerializer, ImplicitSyntax) {
|
||||
{
|
||||
uint32_t x;
|
||||
EXPECT_EQ(42u, UnpackFromString("42", x));
|
||||
}
|
||||
{
|
||||
uint16_t x;
|
||||
EXPECT_EQ(10000u, UnpackFromString("10000", x));
|
||||
}
|
||||
{
|
||||
uint16_t x = 42;
|
||||
EXPECT_EQ("00042", PackToString(x));
|
||||
}
|
||||
{
|
||||
uint64_t x = static_cast<int64_t>(1e18);
|
||||
EXPECT_EQ("01000000000000000000", PackToString(x));
|
||||
}
|
||||
}
|
106
3party/Alohalytics/src/Bricks/time/chrono.h
Normal file
106
3party/Alohalytics/src/Bricks/time/chrono.h
Normal file
|
@ -0,0 +1,106 @@
|
|||
// This file can not be named `time.h`, since it would interfere with C/C++ standard header.
|
||||
|
||||
#ifndef BRICKS_TIME_CHRONO_H
|
||||
#define BRICKS_TIME_CHRONO_H
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "../strings/fixed_size_serializer.h"
|
||||
|
||||
// TODO(dkorolev): Add platform-dependent tests comparing Bricks time to UNIX time.
|
||||
|
||||
namespace bricks {
|
||||
|
||||
namespace time {
|
||||
|
||||
// enum class EPOCH_MILLISECONDS : uint64_t {};
|
||||
typedef uint64_t EPOCH_MILLISECONDS;
|
||||
// enum class MILLISECONDS_INTERVAL : uint64_t {};
|
||||
typedef uint64_t MILLISECONDS_INTERVAL;
|
||||
|
||||
#ifndef BRICKS_ANDROID
|
||||
|
||||
// Since chrono::system_clock is not monotonic, and chrono::steady_clock is not guaranteed to be Epoch,
|
||||
// use a simple wrapper around chrono::system_clock to make it non-decreasing.
|
||||
struct EpochClockGuaranteeingMonotonicity {
|
||||
struct Impl {
|
||||
mutable uint64_t monotonic_now = 0ull;
|
||||
inline uint64_t Now() const {
|
||||
const uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
monotonic_now = std::max(monotonic_now, now);
|
||||
return monotonic_now;
|
||||
}
|
||||
};
|
||||
static const Impl& Singleton() {
|
||||
static Impl singleton;
|
||||
return singleton;
|
||||
}
|
||||
};
|
||||
|
||||
inline EPOCH_MILLISECONDS Now() {
|
||||
return static_cast<EPOCH_MILLISECONDS>(EpochClockGuaranteeingMonotonicity::Singleton().Now());
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// Android C++ compiler doesn't support `thread_local`, fall back to the naive implementation.
|
||||
inline EPOCH_MILLISECONDS Now() {
|
||||
return static_cast<EPOCH_MILLISECONDS>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace time
|
||||
|
||||
namespace strings {
|
||||
|
||||
template <>
|
||||
struct FixedSizeSerializer<bricks::time::EPOCH_MILLISECONDS> {
|
||||
enum { size_in_bytes = std::numeric_limits<uint64_t>::digits10 + 1 };
|
||||
static std::string PackToString(bricks::time::EPOCH_MILLISECONDS x) {
|
||||
std::ostringstream os;
|
||||
os << std::setfill('0') << std::setw(size_in_bytes) << static_cast<uint64_t>(x);
|
||||
return os.str();
|
||||
}
|
||||
static bricks::time::EPOCH_MILLISECONDS UnpackFromString(std::string const& s) {
|
||||
uint64_t x;
|
||||
std::istringstream is(s);
|
||||
is >> x;
|
||||
return static_cast<bricks::time::EPOCH_MILLISECONDS>(x);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace strings
|
||||
|
||||
} // namespace bricks
|
||||
|
||||
// inline bricks::time::MILLISECONDS_INTERVAL operator-(bricks::time::EPOCH_MILLISECONDS lhs,
|
||||
// bricks::time::EPOCH_MILLISECONDS rhs) {
|
||||
// return static_cast<bricks::time::MILLISECONDS_INTERVAL>(static_cast<int64_t>(lhs) -
|
||||
// static_cast<int64_t>(rhs));
|
||||
//}
|
||||
|
||||
// inline bricks::time::MILLISECONDS_INTERVAL operator-(bricks::time::EPOCH_MILLISECONDS x) {
|
||||
// return static_cast<bricks::time::MILLISECONDS_INTERVAL>(-static_cast<int64_t>(x));
|
||||
//}
|
||||
|
||||
// inline bricks::time::EPOCH_MILLISECONDS operator+(bricks::time::EPOCH_MILLISECONDS lhs,
|
||||
// bricks::time::MILLISECONDS_INTERVAL rhs) {
|
||||
// return static_cast<bricks::time::EPOCH_MILLISECONDS>(static_cast<int64_t>(lhs) + static_cast<int64_t>(rhs));
|
||||
//}
|
||||
|
||||
// inline bricks::time::EPOCH_MILLISECONDS operator-(bricks::time::EPOCH_MILLISECONDS lhs,
|
||||
// bricks::time::MILLISECONDS_INTERVAL rhs) {
|
||||
// return static_cast<bricks::time::EPOCH_MILLISECONDS>(static_cast<int64_t>(lhs) - static_cast<int64_t>(rhs));
|
||||
//}
|
||||
|
||||
// inline bricks::time::EPOCH_MILLISECONDS operator+(bricks::time::MILLISECONDS_INTERVAL lhs,
|
||||
// bricks::time::EPOCH_MILLISECONDS rhs) {
|
||||
// return static_cast<bricks::time::EPOCH_MILLISECONDS>(static_cast<int64_t>(lhs) + static_cast<int64_t>(rhs));
|
||||
//}
|
||||
|
||||
// TODO(dkorolev): Add more arithmetic operations on milliseconds here.
|
||||
|
||||
#endif
|
61
3party/Alohalytics/src/Bricks/util/make_scope_guard.h
Normal file
61
3party/Alohalytics/src/Bricks/util/make_scope_guard.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
#ifndef BRICKS_UTIL_MAKE_SCOPED_GUARD_H
|
||||
#define BRICKS_UTIL_MAKE_SCOPED_GUARD_H
|
||||
|
||||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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.
|
||||
*******************************************************************************/
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace bricks {
|
||||
|
||||
template <typename POINTER, typename DELETER>
|
||||
std::unique_ptr<POINTER, DELETER> MakePointerScopeGuard(POINTER* x, DELETER t) {
|
||||
return std::unique_ptr<POINTER, DELETER>(x, t);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
class ScopeGuard final {
|
||||
F f_;
|
||||
ScopeGuard(const ScopeGuard&) = delete;
|
||||
void operator=(const ScopeGuard&) = delete;
|
||||
|
||||
public:
|
||||
explicit ScopeGuard(F f) : f_(f) {
|
||||
}
|
||||
ScopeGuard(ScopeGuard&& other) : f_(std::forward<F>(other.f_)) {
|
||||
}
|
||||
~ScopeGuard() {
|
||||
f_();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
ScopeGuard<F> MakeScopeGuard(F f) {
|
||||
return ScopeGuard<F>(f);
|
||||
}
|
||||
|
||||
} // namespace bricks
|
||||
|
||||
#endif // BRICKS_UTIL_MAKE_SCOPED_GUARD_H
|
16
3party/Alohalytics/src/Bricks/util/test.cc
Normal file
16
3party/Alohalytics/src/Bricks/util/test.cc
Normal file
|
@ -0,0 +1,16 @@
|
|||
// TODO(dkorolev): Test ScopeGuard and MakeScopeGuard as well.
|
||||
|
||||
#include "util.h"
|
||||
|
||||
#include "../3party/gtest/gtest.h"
|
||||
#include "../3party/gtest/gtest-main.h"
|
||||
|
||||
static const char global_string[] = "magic";
|
||||
|
||||
TEST(Util, CompileTimeStringLength) {
|
||||
const char local_string[] = "foo";
|
||||
static const char local_static_string[] = "blah";
|
||||
EXPECT_EQ(3u, bricks::CompileTimeStringLength(local_string));
|
||||
EXPECT_EQ(4u, bricks::CompileTimeStringLength(local_static_string));
|
||||
EXPECT_EQ(5u, bricks::CompileTimeStringLength(global_string));
|
||||
}
|
15
3party/Alohalytics/src/Bricks/util/util.h
Normal file
15
3party/Alohalytics/src/Bricks/util/util.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#ifndef BRICKS_UTIL_UTIL_H
|
||||
#define BRICKS_UTIL_UTIL_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace bricks {
|
||||
|
||||
template <size_t N>
|
||||
constexpr size_t CompileTimeStringLength(char const (&)[N]) {
|
||||
return N - 1;
|
||||
}
|
||||
|
||||
} // namespace bricks
|
||||
|
||||
#endif // BRICKS_UTIL_UTIL_H
|
2
3party/Alohalytics/src/FileStorageQueue/.gitignore
vendored
Normal file
2
3party/Alohalytics/src/FileStorageQueue/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
build
|
||||
core
|
21
3party/Alohalytics/src/FileStorageQueue/LICENSE
Normal file
21
3party/Alohalytics/src/FileStorageQueue/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Dmitry ("Dima") Korolev
|
||||
|
||||
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.
|
5
3party/Alohalytics/src/FileStorageQueue/README.md
Normal file
5
3party/Alohalytics/src/FileStorageQueue/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# ClientFileStorage
|
||||
|
||||
Appends messages to the current file, maintains certain file size and max. age, renames the current file to a finalized one, notifies file listener thread upon newly added finalized files available.
|
||||
|
||||
The receiving end of the events queue (see CachingMessageQueue), that collects raw events to be sent to the server.
|
53
3party/Alohalytics/src/FileStorageQueue/config.h
Normal file
53
3party/Alohalytics/src/FileStorageQueue/config.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
#ifndef FSQ_CONFIG_H
|
||||
#define FSQ_CONFIG_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../Bricks/time/chrono.h"
|
||||
|
||||
#include "strategies.h"
|
||||
#include "exponential_retry_strategy.h"
|
||||
|
||||
namespace fsq {
|
||||
|
||||
// Default strategy configuration for FSQ.
|
||||
//
|
||||
// The easiest way to create a user-specific configuration is to derive from this class
|
||||
// and alter certain fields.
|
||||
//
|
||||
// FSQ derives itself from all strategy classes except T_PROCESSOR, T_TIME_MANAGER and T_FILE_MANAGER,
|
||||
// thus allowing calling member setters for other policies directly on itself.
|
||||
|
||||
template <typename PROCESSOR>
|
||||
struct Config {
|
||||
typedef PROCESSOR T_PROCESSOR;
|
||||
typedef std::string T_MESSAGE;
|
||||
typedef strategy::JustAppendToFile T_FILE_APPEND_STRATEGY;
|
||||
typedef strategy::AlwaysResume T_FILE_RESUME_STRATEGY;
|
||||
typedef strategy::DummyFileNamingToUnblockAlexFromMinsk T_FILE_NAMING_STRATEGY;
|
||||
template <typename FILESYSTEM>
|
||||
using T_RETRY_STRATEGY = strategy::ExponentialDelayRetryStrategy<FILESYSTEM>;
|
||||
typedef bricks::FileSystem T_FILE_SYSTEM;
|
||||
typedef strategy::UseEpochMilliseconds T_TIME_MANAGER;
|
||||
typedef strategy::KeepFilesAround100KBUnlessNoBacklog T_FINALIZE_STRATEGY;
|
||||
typedef strategy::KeepUnder20MBAndUnder1KFiles T_PURGE_STRATEGY;
|
||||
|
||||
// Set to true to have FSQ detach the processing thread instead of joining it in destructor.
|
||||
inline static bool DetachProcessingThreadOnTermination() { return false; }
|
||||
|
||||
// Set to false to have PushMessage() throw an exception when attempted to push while shutting down.
|
||||
inline static bool NoThrowOnPushMessageWhileShuttingDown() { return true; }
|
||||
|
||||
// Set to true to have FSQ process all queued files in destructor before returning.
|
||||
inline static bool ProcessQueueToTheEndOnShutdown() { return false; }
|
||||
|
||||
template <typename T_FSQ_INSTANCE>
|
||||
inline static void Initialize(T_FSQ_INSTANCE&) {
|
||||
// `T_CONFIG::Initialize(*this)` is invoked from FSQ's constructor
|
||||
// User-derived Config-s can put non-static initialization code here.
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fsq
|
||||
|
||||
#endif // FSQ_CONFIG_H
|
12
3party/Alohalytics/src/FileStorageQueue/exception.h
Normal file
12
3party/Alohalytics/src/FileStorageQueue/exception.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#ifndef FSQ_EXCEPTION_H
|
||||
#define FSQ_EXCEPTION_H
|
||||
|
||||
namespace fsq {
|
||||
|
||||
struct FSQException : std::exception {
|
||||
// TODO(dkorolev): Fill this class.
|
||||
};
|
||||
|
||||
} // namespace fsq
|
||||
|
||||
#endif // FSQ_EXCEPTION_H
|
|
@ -0,0 +1,146 @@
|
|||
#ifndef FSQ_EXPONENTIAL_RETRY_STRATEGY_H
|
||||
#define FSQ_EXPONENTIAL_RETRY_STRATEGY_H
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <random>
|
||||
|
||||
#include "exception.h"
|
||||
#include "../Bricks/time/chrono.h"
|
||||
|
||||
namespace fsq {
|
||||
namespace strategy {
|
||||
|
||||
// To be used primarily by the unit test to have different rand seeds.
|
||||
// TODO(dkorolev): Move to Bricks.
|
||||
struct StatefulRandSeed {
|
||||
static int GetRandSeed() {
|
||||
static std::atomic_int seed(static_cast<int>(bricks::time::Now()));
|
||||
return ++seed;
|
||||
}
|
||||
};
|
||||
|
||||
// Exponential retry strategy for the processing of finalized files.
|
||||
// On `Success`, processes files as they arrive without any delays.
|
||||
// On `Unavaliable`, retries after an amount of time drawn from an exponential distribution
|
||||
// with the mean defaulting to 15 minutes, min defaulting to 1 minute and max defaulting to 24 hours.
|
||||
template <typename FILE_SYSTEM_FOR_RETRY_STRATEGY>
|
||||
class ExponentialDelayRetryStrategy {
|
||||
public:
|
||||
typedef FILE_SYSTEM_FOR_RETRY_STRATEGY T_FILE_SYSTEM;
|
||||
struct DistributionParams {
|
||||
double mean, min, max;
|
||||
DistributionParams(double mean, double min, double max) : mean(mean), min(min), max(max) {}
|
||||
DistributionParams() = default;
|
||||
DistributionParams(const DistributionParams&) = default;
|
||||
DistributionParams& operator=(const DistributionParams&) = default;
|
||||
};
|
||||
explicit ExponentialDelayRetryStrategy(const T_FILE_SYSTEM& file_system, const DistributionParams& params)
|
||||
: file_system_(file_system),
|
||||
last_update_time_(bricks::time::Now()),
|
||||
time_to_be_ready_to_process_(last_update_time_),
|
||||
params_(params),
|
||||
rng_(StatefulRandSeed::GetRandSeed()),
|
||||
distribution_(1.0 / params.mean) {}
|
||||
explicit ExponentialDelayRetryStrategy(const T_FILE_SYSTEM& file_system,
|
||||
const double mean = 15 * 60 * 1e3,
|
||||
const double min = 60 * 1e3,
|
||||
const double max = 24 * 60 * 60 * 1e3)
|
||||
: ExponentialDelayRetryStrategy(file_system, DistributionParams(mean, min, max)) {}
|
||||
void AttachToFile(const std::string& filename) {
|
||||
if (!filename.empty()) {
|
||||
persistence_filename_ = filename;
|
||||
// First, resume delay, is possible.
|
||||
// Then, save it to a) ensure the file exists, and b) update its timestamp.
|
||||
ResumeStateFromFile();
|
||||
SaveStateToFile();
|
||||
} else {
|
||||
throw FSQException();
|
||||
}
|
||||
}
|
||||
// OnSuccess(): Clear all retry delays, cruising at full speed.
|
||||
void OnSuccess() {
|
||||
last_update_time_ = bricks::time::Now();
|
||||
time_to_be_ready_to_process_ = last_update_time_;
|
||||
if (!persistence_filename_.empty()) {
|
||||
SaveStateToFile();
|
||||
}
|
||||
}
|
||||
// OnFailure(): Set or update all retry delays.
|
||||
void OnFailure() {
|
||||
const bricks::time::EPOCH_MILLISECONDS now = bricks::time::Now();
|
||||
double random_delay;
|
||||
do {
|
||||
random_delay = distribution_(rng_);
|
||||
} while (!(random_delay >= params_.min && random_delay <= params_.max));
|
||||
time_to_be_ready_to_process_ = std::max(
|
||||
time_to_be_ready_to_process_, now + static_cast<bricks::time::MILLISECONDS_INTERVAL>(random_delay));
|
||||
last_update_time_ = now;
|
||||
if (!persistence_filename_.empty()) {
|
||||
SaveStateToFile();
|
||||
}
|
||||
}
|
||||
bool ShouldWait(bricks::time::MILLISECONDS_INTERVAL* output_wait_ms) {
|
||||
const bricks::time::EPOCH_MILLISECONDS now = bricks::time::Now();
|
||||
if (now >= time_to_be_ready_to_process_) {
|
||||
return false;
|
||||
} else {
|
||||
*output_wait_ms = time_to_be_ready_to_process_ - now;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void ResumeStateFromFile() const {
|
||||
using typename bricks::time::EPOCH_MILLISECONDS;
|
||||
const EPOCH_MILLISECONDS now = bricks::time::Now();
|
||||
try {
|
||||
const std::string contents = std::move(file_system_.ReadFileAsString(persistence_filename_));
|
||||
constexpr size_t w = bricks::strings::FixedSizeSerializer<EPOCH_MILLISECONDS>::size_in_bytes;
|
||||
// File format is "${update_time} ${time_to_be_ready_to_process}".
|
||||
if (contents.length() == w * 2 + 1 && contents[w] == ' ') {
|
||||
EPOCH_MILLISECONDS last_update_time;
|
||||
EPOCH_MILLISECONDS time_to_be_ready_to_process;
|
||||
bricks::strings::UnpackFromString(contents.substr(0, w), last_update_time);
|
||||
bricks::strings::UnpackFromString(contents.substr(w + 1, w), time_to_be_ready_to_process);
|
||||
if (last_update_time <= now) {
|
||||
last_update_time_ = now;
|
||||
time_to_be_ready_to_process_ = std::max(time_to_be_ready_to_process_, time_to_be_ready_to_process);
|
||||
} else {
|
||||
// TODO(dkorolev): Log an error message, time skew detected, not resuming from file,
|
||||
// overwriting it with "no delay" instead.
|
||||
}
|
||||
} else {
|
||||
// TODO(dkorolev): Log an error message, file format is incorrect.
|
||||
}
|
||||
} catch (const bricks::FileException&) {
|
||||
// TODO(dkorolev): Log an error message, could not read the file.
|
||||
}
|
||||
}
|
||||
void SaveStateToFile() const {
|
||||
using bricks::strings::PackToString;
|
||||
try {
|
||||
file_system_.WriteStringToFile(
|
||||
persistence_filename_.c_str(),
|
||||
PackToString(last_update_time_) + ' ' + PackToString(time_to_be_ready_to_process_));
|
||||
} catch (const bricks::FileException&) {
|
||||
// TODO(dkorolev): Log an error message, could not read the file.
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const T_FILE_SYSTEM& file_system_;
|
||||
mutable typename bricks::time::EPOCH_MILLISECONDS last_update_time_;
|
||||
mutable typename bricks::time::EPOCH_MILLISECONDS time_to_be_ready_to_process_;
|
||||
const DistributionParams params_;
|
||||
std::string persistence_filename_;
|
||||
std::mt19937 rng_;
|
||||
std::exponential_distribution<double> distribution_;
|
||||
};
|
||||
|
||||
} // namespace strategy
|
||||
} // namespace fsq
|
||||
|
||||
#endif // FSQ_EXPONENTIAL_RETRY_STRATEGY_H
|
479
3party/Alohalytics/src/FileStorageQueue/fsq.h
Normal file
479
3party/Alohalytics/src/FileStorageQueue/fsq.h
Normal file
|
@ -0,0 +1,479 @@
|
|||
// Class FSQ manages local, filesystem-based message queue.
|
||||
//
|
||||
// A temporary append-only file is created and then written into. Once the strategy dictates so,
|
||||
// it is declared finalized and gets atomically renamed to a different name (with its 1st timestamp in it),
|
||||
// using which name it is passed to the PROCESSOR. A new new append-only file is started in the meantime.
|
||||
//
|
||||
// The processor runs in a dedicated thread. Thus, it is guaranteed to process at most one file at a time.
|
||||
// It can take as long as it needs to process the file. Files are guaranteed to be passed in the FIFO order.
|
||||
//
|
||||
// Once a file is ready, which translates to "on startup" if there are pending files,
|
||||
// the user handler in PROCESSOR::OnFileReady(file_name) is invoked.
|
||||
// When a retry strategy is active, further logic depends on the return value of this method,
|
||||
// see the description of the `FileProcessingResult` enum below for more details.
|
||||
//
|
||||
// On top of the above FSQ keeps an eye on the size it occupies on disk and purges the oldest data files
|
||||
// if the specified purge strategy dictates so.
|
||||
|
||||
#ifndef FSQ_H
|
||||
#define FSQ_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "status.h"
|
||||
#include "exception.h"
|
||||
#include "config.h"
|
||||
#include "strategies.h"
|
||||
|
||||
#include "../Bricks/file/file.h"
|
||||
#include "../Bricks/time/chrono.h"
|
||||
|
||||
namespace fsq {
|
||||
|
||||
// On `Success`, FQS deleted file that just got processed and sends the next one to as it arrives,
|
||||
// which can happen immediately, if the queue is not empty, or later, once the next file is ready.
|
||||
//
|
||||
// On `SuccessAndMoved`, FQS does the same thing as for `Success`, except for it does not attempt
|
||||
// to delete the file, assuming that it has already been deleted or otherwise taken care of by the user code.
|
||||
// Keep in mind that the user code is responsible for making sure the file is removed or renamed,
|
||||
// otherwise it will be re-processed after the application restarts, because of matching the mask.
|
||||
//
|
||||
// On `Unavailable`, automatic file processing is suspended until it is resumed externally.
|
||||
// An example of this case would be the processor being the file uploader, with the device going offline.
|
||||
// This way, no further action is required until FQS is explicitly notified that the device is back online.
|
||||
//
|
||||
// On `FailureNeedRetry`, the file is kept and will be re-attempted to be sent to the processor,
|
||||
// with respect to the retry strategy specified as the template parameter to FSQ.
|
||||
enum class FileProcessingResult { Success, SuccessAndMoved, Unavailable, FailureNeedRetry };
|
||||
|
||||
template <class CONFIG>
|
||||
class FSQ final : public CONFIG::T_FILE_NAMING_STRATEGY,
|
||||
public CONFIG::T_FINALIZE_STRATEGY,
|
||||
public CONFIG::T_PURGE_STRATEGY,
|
||||
public CONFIG::T_FILE_APPEND_STRATEGY,
|
||||
public CONFIG::template T_RETRY_STRATEGY<typename CONFIG::T_FILE_SYSTEM> {
|
||||
public:
|
||||
typedef CONFIG T_CONFIG;
|
||||
|
||||
typedef typename T_CONFIG::T_PROCESSOR T_PROCESSOR;
|
||||
typedef typename T_CONFIG::T_MESSAGE T_MESSAGE;
|
||||
typedef typename T_CONFIG::T_FILE_APPEND_STRATEGY T_FILE_APPEND_STRATEGY;
|
||||
typedef typename T_CONFIG::T_FILE_RESUME_STRATEGY T_FILE_RESUME_STRATEGY;
|
||||
typedef typename T_CONFIG::T_FILE_NAMING_STRATEGY T_FILE_NAMING_STRATEGY;
|
||||
template <typename FILE_SYSTEM>
|
||||
using T_RETRY_STRATEGY = typename T_CONFIG::template T_RETRY_STRATEGY<FILE_SYSTEM>;
|
||||
typedef T_RETRY_STRATEGY<typename CONFIG::T_FILE_SYSTEM> T_RETRY_STRATEGY_INSTANCE;
|
||||
typedef typename T_CONFIG::T_FILE_SYSTEM T_FILE_SYSTEM;
|
||||
typedef typename T_CONFIG::T_TIME_MANAGER T_TIME_MANAGER;
|
||||
typedef typename T_CONFIG::T_FINALIZE_STRATEGY T_FINALIZE_STRATEGY;
|
||||
typedef typename T_CONFIG::T_PURGE_STRATEGY T_PURGE_STRATEGY;
|
||||
|
||||
typedef typename T_TIME_MANAGER::T_TIMESTAMP T_TIMESTAMP;
|
||||
typedef typename T_TIME_MANAGER::T_TIME_SPAN T_TIME_SPAN;
|
||||
|
||||
typedef QueueFinalizedFilesStatus<T_TIMESTAMP> FinalizedFilesStatus;
|
||||
typedef QueueStatus<T_TIMESTAMP> Status;
|
||||
|
||||
// The constructor initializes all the parameters and starts the worker thread.
|
||||
FSQ(T_PROCESSOR& processor,
|
||||
const std::string& working_directory,
|
||||
const T_TIME_MANAGER& time_manager,
|
||||
const T_FILE_SYSTEM& file_system,
|
||||
const T_RETRY_STRATEGY_INSTANCE& retry_strategy)
|
||||
: T_RETRY_STRATEGY_INSTANCE(retry_strategy),
|
||||
processor_(processor),
|
||||
working_directory_(working_directory),
|
||||
time_manager_(time_manager),
|
||||
file_system_(file_system) {
|
||||
T_CONFIG::Initialize(*this);
|
||||
worker_thread_ = std::thread(&FSQ::WorkerThread, this);
|
||||
}
|
||||
FSQ(T_PROCESSOR& processor,
|
||||
const std::string& working_directory,
|
||||
const T_TIME_MANAGER& time_manager = T_TIME_MANAGER(),
|
||||
const T_FILE_SYSTEM& file_system = T_FILE_SYSTEM())
|
||||
: FSQ(processor, working_directory, time_manager, file_system, T_RETRY_STRATEGY_INSTANCE(file_system)) {}
|
||||
|
||||
// Destructor gracefully terminates worker thread and optionally joins it.
|
||||
~FSQ() {
|
||||
// Notify the worker thread that it's time to wrap up.
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(status_mutex_);
|
||||
force_worker_thread_shutdown_ = true;
|
||||
queue_status_condition_variable_.notify_all();
|
||||
}
|
||||
// Close the current file. `current_file_.reset(nullptr);` is always safe especially in destructor.
|
||||
current_file_.reset(nullptr);
|
||||
// Either wait for the processor thread to terminate or detach it, unless it's already done.
|
||||
if (worker_thread_.joinable()) {
|
||||
if (T_CONFIG::DetachProcessingThreadOnTermination()) {
|
||||
worker_thread_.detach();
|
||||
} else {
|
||||
worker_thread_.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Getters.
|
||||
const std::string& WorkingDirectory() const { return working_directory_; }
|
||||
|
||||
const Status GetQueueStatus() const {
|
||||
if (!status_ready_) {
|
||||
std::unique_lock<std::mutex> lock(status_mutex_);
|
||||
while (!status_ready_) {
|
||||
queue_status_condition_variable_.wait(lock);
|
||||
if (force_worker_thread_shutdown_) {
|
||||
throw FSQException();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Returning `status_` by const reference is not thread-safe, return a copy from a locked section.
|
||||
return status_;
|
||||
}
|
||||
|
||||
// `PushMessage()` appends data to the queue.
|
||||
void PushMessage(const T_MESSAGE& message) {
|
||||
if (!status_ready_) {
|
||||
// Need to wait for the status to be ready, otherwise current file resume might not happen.
|
||||
std::unique_lock<std::mutex> lock(status_mutex_);
|
||||
while (!status_ready_) {
|
||||
queue_status_condition_variable_.wait(lock);
|
||||
}
|
||||
}
|
||||
if (force_worker_thread_shutdown_) {
|
||||
if (T_CONFIG::NoThrowOnPushMessageWhileShuttingDown()) {
|
||||
// Silently ignoring incoming messages while in shutdown mode is the default strategy.
|
||||
return;
|
||||
} else {
|
||||
throw FSQException();
|
||||
}
|
||||
} else {
|
||||
const T_TIMESTAMP now = time_manager_.Now();
|
||||
const uint64_t message_size_in_bytes = T_FILE_APPEND_STRATEGY::MessageSizeInBytes(message);
|
||||
{
|
||||
// Take current message size into consideration when making file finalization decision.
|
||||
status_.appended_file_size += message_size_in_bytes;
|
||||
const bool should_finalize = T_FINALIZE_STRATEGY::ShouldFinalize(status_, now);
|
||||
status_.appended_file_size -= message_size_in_bytes;
|
||||
if (should_finalize) {
|
||||
FinalizeCurrentFile();
|
||||
}
|
||||
}
|
||||
EnsureCurrentFileIsOpen(now);
|
||||
if (!current_file_ || current_file_->bad()) {
|
||||
throw FSQException();
|
||||
}
|
||||
T_FILE_APPEND_STRATEGY::AppendToFile(*current_file_.get(), message);
|
||||
status_.appended_file_size += message_size_in_bytes;
|
||||
if (T_FINALIZE_STRATEGY::ShouldFinalize(status_, now)) {
|
||||
FinalizeCurrentFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `ResumeProcessing() is used when a temporary reason of unavailability is now gone.
|
||||
// A common usecase is if the processor sends files over network, and the network just became unavailable.
|
||||
// In this case, on an event of network becoming available again, `ResumeProcessing()` should be called.
|
||||
//
|
||||
// `ResumeProcessing()` respects retransmission delays strategy. While the "Unavailability" event
|
||||
// does not trigger retransmission logic, if, due to prior external factors, FSQ is in waiting mode
|
||||
// for a while,
|
||||
// `ResumeProcessing()` would not override that wait. Use `ForceProcessing()` for those forced overrides.
|
||||
void ResumeProcessing() {
|
||||
processing_suspended_ = false;
|
||||
queue_status_condition_variable_.notify_all();
|
||||
}
|
||||
|
||||
// `ForceProcessing()` initiates processing of finalized files, if any.
|
||||
//
|
||||
// THIS METHOD IS NOT SAFE, since using it based on a frequent external event, like "WiFi connected",
|
||||
// may result in file queue processing to be started and re-started multiple times,
|
||||
// skipping the retransmission delay logic.
|
||||
//
|
||||
// Only use `ForceProcessing()` when processing should start no matter what.
|
||||
// Example: App just got updated, or a large external download has just been successfully completed.
|
||||
//
|
||||
// Use `ResumeProcessing()` in other cases.
|
||||
void ForceProcessing(bool force_finalize_current_file = false) {
|
||||
std::unique_lock<std::mutex> lock(status_mutex_);
|
||||
if (force_finalize_current_file || status_.finalized.queue.empty()) {
|
||||
if (current_file_) {
|
||||
FinalizeCurrentFile(lock);
|
||||
}
|
||||
}
|
||||
processing_suspended_ = false;
|
||||
force_processing_ = true;
|
||||
queue_status_condition_variable_.notify_all();
|
||||
}
|
||||
|
||||
// `FinalizeCurrentFile()` forces the finalization of the currently appended file.
|
||||
void FinalizeCurrentFile() {
|
||||
if (current_file_) {
|
||||
std::unique_lock<std::mutex> lock(status_mutex_);
|
||||
FinalizeCurrentFile(lock);
|
||||
}
|
||||
}
|
||||
|
||||
// Removes all finalized and current files from disk.
|
||||
// Has to shut down as well, since removing files does not play well with the worker thread processing them.
|
||||
// USE CAREFULLY!
|
||||
void ShutdownAndRemoveAllFSQFiles() {
|
||||
// First, force the worker thread to terminate.
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(status_mutex_);
|
||||
force_worker_thread_shutdown_ = true;
|
||||
queue_status_condition_variable_.notify_all();
|
||||
}
|
||||
current_file_.reset(nullptr);
|
||||
worker_thread_.join();
|
||||
// Scan the directory and remove the files.
|
||||
for (const auto& file : ScanDir([this](const std::string& s, T_TIMESTAMP* t) {
|
||||
return T_FILE_NAMING_STRATEGY::finalized.ParseFileName(s, t) ||
|
||||
T_FILE_NAMING_STRATEGY::current.ParseFileName(s, t);
|
||||
})) {
|
||||
T_FILE_SYSTEM::RemoveFile(file.full_path_name);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// If the current file exists, declare it finalized, rename it under a permanent name
|
||||
// and notify the worker thread that a new file is available.
|
||||
void FinalizeCurrentFile(std::unique_lock<std::mutex>& already_acquired_status_mutex_lock) {
|
||||
if (current_file_) {
|
||||
current_file_.reset(nullptr);
|
||||
const std::string finalized_file_name =
|
||||
T_FILE_NAMING_STRATEGY::finalized.GenerateFileName(status_.appended_file_timestamp);
|
||||
FileInfo<T_TIMESTAMP> finalized_file_info(
|
||||
finalized_file_name,
|
||||
T_FILE_SYSTEM::JoinPath(working_directory_, finalized_file_name),
|
||||
status_.appended_file_timestamp,
|
||||
status_.appended_file_size);
|
||||
T_FILE_SYSTEM::RenameFile(current_file_name_, finalized_file_info.full_path_name);
|
||||
status_.finalized.queue.push_back(finalized_file_info);
|
||||
status_.finalized.total_size += status_.appended_file_size;
|
||||
status_.appended_file_size = 0;
|
||||
status_.appended_file_timestamp = T_TIMESTAMP(0);
|
||||
current_file_name_.clear();
|
||||
PurgeFilesAsNecessary(already_acquired_status_mutex_lock);
|
||||
queue_status_condition_variable_.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
// Scans the directory for the files that match certain predicate.
|
||||
// Gets their sized and and extracts timestamps from their names along the way.
|
||||
template <typename F>
|
||||
std::vector<FileInfo<T_TIMESTAMP>> ScanDir(F f) const {
|
||||
std::vector<FileInfo<T_TIMESTAMP>> matched_files_list;
|
||||
const auto& dir = working_directory_;
|
||||
T_FILE_SYSTEM::ScanDir(working_directory_,
|
||||
[this, &matched_files_list, &f, dir](const std::string& file_name) {
|
||||
// if (T_FILE_NAMING_STRATEGY::template IsFinalizedFileName<T_TIMESTAMP>(file_name)) {
|
||||
T_TIMESTAMP timestamp;
|
||||
if (f(file_name, ×tamp)) {
|
||||
matched_files_list.emplace_back(file_name,
|
||||
T_FILE_SYSTEM::JoinPath(working_directory_, file_name),
|
||||
timestamp,
|
||||
T_FILE_SYSTEM::GetFileSize(T_FILE_SYSTEM::JoinPath(dir, file_name)));
|
||||
}
|
||||
});
|
||||
std::sort(matched_files_list.begin(), matched_files_list.end());
|
||||
return matched_files_list;
|
||||
}
|
||||
|
||||
// EnsureCurrentFileIsOpen() expires the current file and/or creates the new one as necessary.
|
||||
void EnsureCurrentFileIsOpen(const T_TIMESTAMP now) {
|
||||
if (!current_file_) {
|
||||
current_file_name_ =
|
||||
T_FILE_SYSTEM::JoinPath(working_directory_, T_FILE_NAMING_STRATEGY::current.GenerateFileName(now));
|
||||
// TODO(dkorolev): This relies on OutputFile being std::ofstream. Fine for now anyway.
|
||||
current_file_.reset(new typename T_FILE_SYSTEM::OutputFile(current_file_name_,
|
||||
std::ofstream::trunc | std::ofstream::binary));
|
||||
status_.appended_file_timestamp = now;
|
||||
}
|
||||
}
|
||||
|
||||
// Purges the old files as necessary.
|
||||
void PurgeFilesAsNecessary(std::unique_lock<std::mutex>& already_acquired_status_mutex_lock) {
|
||||
static_cast<void>(already_acquired_status_mutex_lock);
|
||||
while (!status_.finalized.queue.empty() && T_PURGE_STRATEGY::ShouldPurge(status_)) {
|
||||
const std::string filename = status_.finalized.queue.front().full_path_name;
|
||||
status_.finalized.total_size -= status_.finalized.queue.front().size;
|
||||
status_.finalized.queue.pop_front();
|
||||
T_FILE_SYSTEM::RemoveFile(filename);
|
||||
}
|
||||
}
|
||||
|
||||
// The worker thread first scans the directory for present finalized and current files.
|
||||
// Present finalized files are queued up.
|
||||
// If more than one present current files is available, all but one are finalized on the spot.
|
||||
// The one remaining current file can be appended to or finalized depending on the strategy.
|
||||
void WorkerThread() {
|
||||
// Step 1/4: Get the list of finalized files.
|
||||
typedef std::vector<FileInfo<T_TIMESTAMP>> FileInfoVector;
|
||||
const FileInfoVector& finalized_files_on_disk = ScanDir([this](const std::string& s, T_TIMESTAMP* t) {
|
||||
return T_FILE_NAMING_STRATEGY::finalized.ParseFileName(s, t);
|
||||
});
|
||||
status_.finalized.queue.assign(finalized_files_on_disk.begin(), finalized_files_on_disk.end());
|
||||
status_.finalized.total_size = 0;
|
||||
for (const auto& file : finalized_files_on_disk) {
|
||||
status_.finalized.total_size += file.size;
|
||||
}
|
||||
|
||||
// Step 2/4: Get the list of current files.
|
||||
const FileInfoVector& current_files_on_disk = ScanDir([this](
|
||||
const std::string& s, T_TIMESTAMP* t) { return T_FILE_NAMING_STRATEGY::current.ParseFileName(s, t); });
|
||||
if (!current_files_on_disk.empty()) {
|
||||
const bool resume = T_FILE_RESUME_STRATEGY::ShouldResume();
|
||||
const size_t number_of_files_to_finalize = current_files_on_disk.size() - (resume ? 1u : 0u);
|
||||
for (size_t i = 0; i < number_of_files_to_finalize; ++i) {
|
||||
const FileInfo<T_TIMESTAMP>& f = current_files_on_disk[i];
|
||||
const std::string finalized_file_name = T_FILE_NAMING_STRATEGY::finalized.GenerateFileName(f.timestamp);
|
||||
FileInfo<T_TIMESTAMP> finalized_file_info(
|
||||
finalized_file_name,
|
||||
T_FILE_SYSTEM::JoinPath(working_directory_, finalized_file_name),
|
||||
f.timestamp,
|
||||
f.size);
|
||||
T_FILE_SYSTEM::RenameFile(f.full_path_name, finalized_file_info.full_path_name);
|
||||
status_.finalized.queue.push_back(finalized_file_info);
|
||||
status_.finalized.total_size += f.size;
|
||||
}
|
||||
if (resume) {
|
||||
const FileInfo<T_TIMESTAMP>& c = current_files_on_disk.back();
|
||||
status_.appended_file_timestamp = c.timestamp;
|
||||
status_.appended_file_size = c.size;
|
||||
current_file_name_ = c.full_path_name;
|
||||
// TODO(dkorolev): This relies on OutputFile being std::ofstream. Fine for now anyway.
|
||||
current_file_.reset(new typename T_FILE_SYSTEM::OutputFile(current_file_name_,
|
||||
std::ofstream::app | std::ofstream::binary));
|
||||
}
|
||||
std::unique_lock<std::mutex> lock(status_mutex_);
|
||||
PurgeFilesAsNecessary(lock);
|
||||
}
|
||||
|
||||
// Step 3/4: Signal that FSQ's status has been successfully parsed from disk and FSQ is ready to go.
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(status_mutex_);
|
||||
status_ready_ = true;
|
||||
queue_status_condition_variable_.notify_all();
|
||||
}
|
||||
|
||||
// Step 4/4: Start processing finalized files via T_PROCESSOR, respecting retry strategy.
|
||||
while (true) {
|
||||
// Wait for a newly arrived file or another event to happen.
|
||||
std::unique_ptr<FileInfo<T_TIMESTAMP>> next_file;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(status_mutex_);
|
||||
const bricks::time::EPOCH_MILLISECONDS begin_ms = bricks::time::Now();
|
||||
bricks::time::MILLISECONDS_INTERVAL wait_ms;
|
||||
const bool should_wait = T_RETRY_STRATEGY_INSTANCE::ShouldWait(&wait_ms);
|
||||
const auto predicate = [this, should_wait, begin_ms, wait_ms]() {
|
||||
if (force_worker_thread_shutdown_) {
|
||||
return true;
|
||||
} else if (force_processing_) {
|
||||
return true;
|
||||
} else if (processing_suspended_) {
|
||||
return false;
|
||||
} else if (should_wait && bricks::time::Now() - begin_ms < wait_ms) {
|
||||
return false;
|
||||
} else if (!status_.finalized.queue.empty()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if (!predicate()) {
|
||||
if (should_wait) {
|
||||
// Add one millisecond to avoid multiple runs of this loop when `wait_ms` is close to zero.
|
||||
queue_status_condition_variable_.wait_for(
|
||||
lock, std::chrono::milliseconds(static_cast<uint64_t>(wait_ms) + 1), predicate);
|
||||
} else {
|
||||
queue_status_condition_variable_.wait(lock, predicate);
|
||||
}
|
||||
}
|
||||
if (!status_.finalized.queue.empty()) {
|
||||
next_file.reset(new FileInfo<T_TIMESTAMP>(status_.finalized.queue.front()));
|
||||
}
|
||||
if (force_worker_thread_shutdown_) {
|
||||
// By default, terminate immediately.
|
||||
// However, allow the user to override this setting and have the queue
|
||||
// processed in full before returning from FSQ's destructor.
|
||||
if (!T_CONFIG::ProcessQueueToTheEndOnShutdown() || !next_file) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process the file, if available.
|
||||
if (next_file) {
|
||||
// const FileProcessingResult result = processor_.OnFileReady(*next_file.get(),
|
||||
// time_manager_.Now());
|
||||
const bool successfully_processed = processor_.OnFileReady(next_file->full_path_name, next_file->size);
|
||||
// Important to clear force_processing_, in a locked way.
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(status_mutex_);
|
||||
force_processing_ = false;
|
||||
}
|
||||
// if (result == FileProcessingResult::Success || result == FileProcessingResult::SuccessAndMoved)
|
||||
// {
|
||||
if (successfully_processed) {
|
||||
std::unique_lock<std::mutex> lock(status_mutex_);
|
||||
processing_suspended_ = false;
|
||||
if (*next_file.get() == status_.finalized.queue.front()) {
|
||||
status_.finalized.total_size -= status_.finalized.queue.front().size;
|
||||
status_.finalized.queue.pop_front();
|
||||
} else {
|
||||
// The `front()` part of the queue should only be altered by this worker thread.
|
||||
throw FSQException();
|
||||
}
|
||||
// if (result == FileProcessingResult::Success) {
|
||||
T_FILE_SYSTEM::RemoveFile(next_file->full_path_name);
|
||||
// }
|
||||
T_RETRY_STRATEGY_INSTANCE::OnSuccess();
|
||||
// } else if (result == FileProcessingResult::Unavailable) {
|
||||
// std::unique_lock<std::mutex> lock(status_mutex_);
|
||||
// processing_suspended_ = true;
|
||||
} else { // if (result == FileProcessingResult::FailureNeedRetry) {
|
||||
T_RETRY_STRATEGY_INSTANCE::OnFailure();
|
||||
} // else {
|
||||
// throw FSQException();
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status status_;
|
||||
// Appending messages is single-threaded and thus lock-free.
|
||||
// The status of the processing queue, on the other hand, should be guarded.
|
||||
mutable std::mutex status_mutex_;
|
||||
// Set to true and pings the variable once the initial directory scan is completed.
|
||||
bool status_ready_ = false;
|
||||
mutable std::condition_variable queue_status_condition_variable_;
|
||||
|
||||
T_PROCESSOR& processor_;
|
||||
std::string working_directory_;
|
||||
const T_TIME_MANAGER& time_manager_;
|
||||
const T_FILE_SYSTEM& file_system_;
|
||||
|
||||
std::unique_ptr<typename T_FILE_SYSTEM::OutputFile> current_file_;
|
||||
std::string current_file_name_;
|
||||
|
||||
std::thread worker_thread_;
|
||||
bool processing_suspended_ = false;
|
||||
bool force_processing_ = false;
|
||||
bool force_worker_thread_shutdown_ = false;
|
||||
|
||||
FSQ(const FSQ&) = delete;
|
||||
FSQ(FSQ&&) = delete;
|
||||
void operator=(const FSQ&) = delete;
|
||||
void operator=(FSQ&&) = delete;
|
||||
};
|
||||
|
||||
} // namespace fsq
|
||||
|
||||
#endif // FSQ_H
|
24
3party/Alohalytics/src/FileStorageQueue/minimalistic_demo.cc
Normal file
24
3party/Alohalytics/src/FileStorageQueue/minimalistic_demo.cc
Normal file
|
@ -0,0 +1,24 @@
|
|||
// This header file is provided here for the convenience of Alex from Minsk, Belarus, of using FSQ.
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
#include "fsq.h"
|
||||
#include "../Bricks/file/file.h"
|
||||
|
||||
struct MinimalisticProcessor {
|
||||
template <typename T_TIMESTAMP>
|
||||
fsq::FileProcessingResult OnFileReady(const fsq::FileInfo<T_TIMESTAMP>& file, T_TIMESTAMP /*now*/) {
|
||||
std::cerr << file.full_path_name << std::endl << bricks::ReadFileAsString(file.full_path_name) << std::endl;
|
||||
return fsq::FileProcessingResult::Success;
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
MinimalisticProcessor processor;
|
||||
fsq::FSQ<fsq::Config<MinimalisticProcessor>> fsq(processor, ".");
|
||||
fsq.PushMessage("Hello, World!\n");
|
||||
fsq.ForceProcessing();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
47
3party/Alohalytics/src/FileStorageQueue/status.h
Normal file
47
3party/Alohalytics/src/FileStorageQueue/status.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
// The status of FSQ's file system usage.
|
||||
|
||||
#ifndef FSQ_STATUS_H
|
||||
#define FSQ_STATUS_H
|
||||
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace fsq {
|
||||
|
||||
template <typename TIMESTAMP>
|
||||
struct FileInfo {
|
||||
typedef TIMESTAMP T_TIMESTAMP;
|
||||
std::string name = std::string("");
|
||||
std::string full_path_name = std::string("");
|
||||
T_TIMESTAMP timestamp = T_TIMESTAMP(0);
|
||||
uint64_t size = 0;
|
||||
FileInfo(const std::string& name, const std::string& full_path_name, T_TIMESTAMP timestamp, uint64_t size)
|
||||
: name(name), full_path_name(full_path_name), timestamp(timestamp), size(size) {}
|
||||
inline std::tuple<T_TIMESTAMP, std::string, std::string, uint64_t> AsTuple() const {
|
||||
return std::tie(timestamp, name, full_path_name, size);
|
||||
}
|
||||
inline bool operator==(const FileInfo& rhs) const { return AsTuple() == rhs.AsTuple(); }
|
||||
inline bool operator<(const FileInfo& rhs) const { return AsTuple() < rhs.AsTuple(); }
|
||||
};
|
||||
|
||||
// The status of all other, finalized, files combined.
|
||||
template <typename TIMESTAMP>
|
||||
struct QueueFinalizedFilesStatus {
|
||||
typedef TIMESTAMP T_TIMESTAMP;
|
||||
std::deque<FileInfo<T_TIMESTAMP>> queue; // Sorted from oldest to newest.
|
||||
uint64_t total_size = 0;
|
||||
};
|
||||
|
||||
// The status of the file that is currently being appended to.
|
||||
template <typename TIMESTAMP>
|
||||
struct QueueStatus {
|
||||
typedef TIMESTAMP T_TIMESTAMP;
|
||||
uint64_t appended_file_size = 0; // Also zero if no file is currently open.
|
||||
T_TIMESTAMP appended_file_timestamp = T_TIMESTAMP(0); // Also zero if no file is curently open.
|
||||
QueueFinalizedFilesStatus<T_TIMESTAMP> finalized;
|
||||
};
|
||||
|
||||
} // namespace fsq
|
||||
|
||||
#endif // FSQ_STATUS_H
|
159
3party/Alohalytics/src/FileStorageQueue/strategies.h
Normal file
159
3party/Alohalytics/src/FileStorageQueue/strategies.h
Normal file
|
@ -0,0 +1,159 @@
|
|||
#ifndef FSQ_STRATEGIES_H
|
||||
#define FSQ_STRATEGIES_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "status.h"
|
||||
|
||||
#include "../Bricks/util/util.h"
|
||||
#include "../Bricks/file/file.h"
|
||||
#include "../Bricks/time/chrono.h"
|
||||
#include "../Bricks/strings/fixed_size_serializer.h"
|
||||
|
||||
namespace fsq {
|
||||
namespace strategy {
|
||||
|
||||
// Default file append strategy: Appends data to files in raw format, without separators.
|
||||
struct JustAppendToFile {
|
||||
void AppendToFile(bricks::FileSystem::OutputFile& fo, const std::string& message) const {
|
||||
// TODO(dkorolev): Should we flush each record? Make it part of the strategy?
|
||||
fo << message << std::flush;
|
||||
}
|
||||
uint64_t MessageSizeInBytes(const std::string& message) const { return message.length(); }
|
||||
};
|
||||
|
||||
// Another simple file append strategy: Append messages adding a separator after each of them.
|
||||
class AppendToFileWithSeparator {
|
||||
public:
|
||||
void AppendToFile(bricks::FileSystem::OutputFile& fo, const std::string& message) const {
|
||||
// TODO(dkorolev): Should we flush each record? Make it part of the strategy?
|
||||
fo << message << separator_ << std::flush;
|
||||
}
|
||||
uint64_t MessageSizeInBytes(const std::string& message) const {
|
||||
return message.length() + separator_.length();
|
||||
}
|
||||
void SetSeparator(const std::string& separator) { separator_ = separator; }
|
||||
|
||||
private:
|
||||
std::string separator_ = "";
|
||||
};
|
||||
|
||||
// Default resume strategy: Always resume.
|
||||
struct AlwaysResume {
|
||||
inline static bool ShouldResume() { return true; }
|
||||
};
|
||||
|
||||
// A dummy retry strategy: Always process, no need to retry.
|
||||
template <class FILE_SYSTEM>
|
||||
class AlwaysProcessNoNeedToRetry {
|
||||
public:
|
||||
AlwaysProcessNoNeedToRetry(const FILE_SYSTEM&) {}
|
||||
typedef FILE_SYSTEM T_FILE_SYSTEM;
|
||||
inline void OnSuccess() {}
|
||||
inline void OnFailure() {}
|
||||
inline bool ShouldWait(bricks::time::MILLISECONDS_INTERVAL*) { return false; }
|
||||
};
|
||||
|
||||
// Default file naming strategy: Use "finalized-{timestamp}.bin" and "current-{timestamp}.bin".
|
||||
struct DummyFileNamingToUnblockAlexFromMinsk {
|
||||
struct FileNamingSchema {
|
||||
FileNamingSchema(const std::string& prefix, const std::string& suffix) : prefix_(prefix), suffix_(suffix) {}
|
||||
template <typename T_TIMESTAMP>
|
||||
inline std::string GenerateFileName(const T_TIMESTAMP timestamp) const {
|
||||
return prefix_ + bricks::strings::PackToString(timestamp) + suffix_;
|
||||
}
|
||||
template <typename T_TIMESTAMP>
|
||||
inline bool ParseFileName(const std::string& filename, T_TIMESTAMP* output_timestamp) const {
|
||||
if ((filename.length() ==
|
||||
prefix_.length() + bricks::strings::FixedSizeSerializer<T_TIMESTAMP>::size_in_bytes +
|
||||
suffix_.length()) &&
|
||||
filename.substr(0, prefix_.length()) == prefix_ &&
|
||||
filename.substr(filename.length() - suffix_.length()) == suffix_) {
|
||||
T_TIMESTAMP timestamp;
|
||||
bricks::strings::UnpackFromString(filename.substr(prefix_.length()), timestamp);
|
||||
if (GenerateFileName(timestamp) == filename) {
|
||||
*output_timestamp = timestamp;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::string prefix_;
|
||||
std::string suffix_;
|
||||
};
|
||||
FileNamingSchema current = FileNamingSchema("current-", ".bin");
|
||||
FileNamingSchema finalized = FileNamingSchema("finalized-", ".bin");
|
||||
};
|
||||
|
||||
// Default time manager strategy: Use UNIX time in milliseconds.
|
||||
struct UseEpochMilliseconds final {
|
||||
typedef bricks::time::EPOCH_MILLISECONDS T_TIMESTAMP;
|
||||
typedef bricks::time::MILLISECONDS_INTERVAL T_TIME_SPAN;
|
||||
T_TIMESTAMP Now() const { return bricks::time::Now(); }
|
||||
};
|
||||
|
||||
// Default file finalization strategy: Keeps files under 100KB, if there are files in the processing queue,
|
||||
// in case of no files waiting, keep them under 10KB. Also manage maximum age before forced finalization:
|
||||
// a maximum of 24 hours when there is backlog, a maximum of 10 minutes if there is no.
|
||||
template <typename TIMESTAMP,
|
||||
typename TIME_SPAN,
|
||||
uint64_t BACKLOG_MAX_FILE_SIZE,
|
||||
TIME_SPAN BACKLOG_MAX_FILE_AGE,
|
||||
uint64_t REALTIME_MAX_FILE_SIZE,
|
||||
TIME_SPAN REALTIME_MAX_FILE_AGE>
|
||||
struct SimpleFinalizationStrategy {
|
||||
typedef TIMESTAMP T_TIMESTAMP;
|
||||
typedef TIME_SPAN T_TIME_SPAN;
|
||||
// This default strategy only supports MILLISECONDS from bricks:time as timestamps.
|
||||
bool ShouldFinalize(const QueueStatus<T_TIMESTAMP>& status, const T_TIMESTAMP now) const {
|
||||
if (status.appended_file_size >= BACKLOG_MAX_FILE_SIZE ||
|
||||
(now - status.appended_file_timestamp) > BACKLOG_MAX_FILE_AGE) {
|
||||
// Always keep files of at most 100KB and at most 24 hours old.
|
||||
return true;
|
||||
} else if (!status.finalized.queue.empty()) {
|
||||
// The above is the only condition as long as there are queued, pending, unprocessed files.
|
||||
return false;
|
||||
} else {
|
||||
// Otherwise, there are no files pending processing no queue,
|
||||
// and the default strategy can be legitimately expected to keep finalizing files somewhat often.
|
||||
return (status.appended_file_size >= REALTIME_MAX_FILE_SIZE ||
|
||||
(now - status.appended_file_timestamp) > REALTIME_MAX_FILE_AGE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
typedef SimpleFinalizationStrategy<bricks::time::EPOCH_MILLISECONDS,
|
||||
bricks::time::MILLISECONDS_INTERVAL,
|
||||
100 * 1024,
|
||||
bricks::time::MILLISECONDS_INTERVAL(24 * 60 * 60 * 1000),
|
||||
10 * 1024,
|
||||
bricks::time::MILLISECONDS_INTERVAL(10 * 60 * 1000)>
|
||||
KeepFilesAround100KBUnlessNoBacklog;
|
||||
|
||||
// Default file purge strategy: Keeps under 1K files of under 20MB of total size.
|
||||
template <uint64_t MAX_TOTAL_SIZE, size_t MAX_FILES>
|
||||
struct SimplePurgeStrategy {
|
||||
template <typename T_TIMESTAMP>
|
||||
bool ShouldPurge(const QueueStatus<T_TIMESTAMP>& status) const {
|
||||
if (status.finalized.total_size + status.appended_file_size > MAX_TOTAL_SIZE) {
|
||||
// Purge the oldest files if the total size of data stored in the queue exceeds MAX_TOTAL_SIZE.
|
||||
return true;
|
||||
} else if (status.finalized.queue.size() > MAX_FILES) {
|
||||
// Purge the oldest files if the total number of queued files exceeds MAX_FILE.
|
||||
return true;
|
||||
} else {
|
||||
// Good to go otherwise.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
typedef SimplePurgeStrategy<20 * 1024 * 1024, 1000> KeepUnder20MBAndUnder1KFiles;
|
||||
|
||||
} // namespace strategy
|
||||
} // namespace fsq
|
||||
|
||||
#endif // FSQ_STRATEGIES_H
|
529
3party/Alohalytics/src/FileStorageQueue/test.cc
Normal file
529
3party/Alohalytics/src/FileStorageQueue/test.cc
Normal file
|
@ -0,0 +1,529 @@
|
|||
// TODO(dkorolev): Add a more purge test(s), code coverage should show which.
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "fsq.h"
|
||||
|
||||
#include "../Bricks/file/file.h"
|
||||
|
||||
#include "../Bricks/3party/gtest/gtest.h"
|
||||
#include "../Bricks/3party/gtest/gtest-main.h"
|
||||
|
||||
using std::string;
|
||||
using std::atomic_size_t;
|
||||
|
||||
const char* const kTestDir = "build/";
|
||||
|
||||
// TestOutputFilesProcessor collects the output of finalized files.
|
||||
struct TestOutputFilesProcessor {
|
||||
TestOutputFilesProcessor() : finalized_count(0) {}
|
||||
|
||||
fsq::FileProcessingResult OnFileReady(const fsq::FileInfo<uint64_t>& file_info, uint64_t now) {
|
||||
if (mimic_unavailable_) {
|
||||
return fsq::FileProcessingResult::Unavailable;
|
||||
} else if (mimic_need_retry_) {
|
||||
return fsq::FileProcessingResult::FailureNeedRetry;
|
||||
} else {
|
||||
if (!finalized_count) {
|
||||
contents = bricks::ReadFileAsString(file_info.full_path_name);
|
||||
filenames = file_info.name;
|
||||
} else {
|
||||
contents = contents + "FILE SEPARATOR\n" + bricks::ReadFileAsString(file_info.full_path_name);
|
||||
filenames = filenames + "|" + file_info.name;
|
||||
}
|
||||
timestamp = now;
|
||||
++finalized_count;
|
||||
return fsq::FileProcessingResult::Success;
|
||||
}
|
||||
}
|
||||
|
||||
void ClearStats() {
|
||||
finalized_count = 0;
|
||||
filenames = "";
|
||||
contents = "";
|
||||
timestamp = 0;
|
||||
}
|
||||
|
||||
void SetMimicUnavailable(bool mimic_unavailable = true) { mimic_unavailable_ = mimic_unavailable; }
|
||||
|
||||
void SetMimicNeedRetry(bool mimic_need_retry = true) { mimic_need_retry_ = mimic_need_retry; }
|
||||
|
||||
atomic_size_t finalized_count;
|
||||
string filenames = "";
|
||||
string contents = "";
|
||||
uint64_t timestamp = 0;
|
||||
|
||||
bool mimic_unavailable_ = false;
|
||||
bool mimic_need_retry_ = false;
|
||||
};
|
||||
|
||||
struct MockTime {
|
||||
typedef uint64_t T_TIMESTAMP;
|
||||
typedef int64_t T_TIME_SPAN;
|
||||
uint64_t now = 0;
|
||||
T_TIMESTAMP Now() const { return now; }
|
||||
};
|
||||
|
||||
struct MockConfig : fsq::Config<TestOutputFilesProcessor> {
|
||||
// Mock time.
|
||||
typedef MockTime T_TIME_MANAGER;
|
||||
// Append using newlines.
|
||||
typedef fsq::strategy::AppendToFileWithSeparator T_FILE_APPEND_STRATEGY;
|
||||
// No backlog: 20 bytes 10 seconds old files max, with backlog: 100 bytes 60 seconds old files max.
|
||||
typedef fsq::strategy::SimpleFinalizationStrategy<MockTime::T_TIMESTAMP,
|
||||
MockTime::T_TIME_SPAN,
|
||||
20,
|
||||
MockTime::T_TIME_SPAN(10 * 1000),
|
||||
100,
|
||||
MockTime::T_TIME_SPAN(60 * 1000)> T_FINALIZE_STRATEGY;
|
||||
// Purge after 50 bytes total or after 3 files.
|
||||
typedef fsq::strategy::SimplePurgeStrategy<50, 3> T_PURGE_STRATEGY;
|
||||
|
||||
// Non-static initialization.
|
||||
template <typename T_FSQ_INSTANCE>
|
||||
static void Initialize(T_FSQ_INSTANCE& instance) {
|
||||
instance.SetSeparator("\n");
|
||||
}
|
||||
};
|
||||
|
||||
struct NoResumeMockConfig : MockConfig {
|
||||
struct T_FILE_RESUME_STRATEGY {
|
||||
inline static bool ShouldResume() { return false; }
|
||||
};
|
||||
};
|
||||
|
||||
typedef fsq::FSQ<MockConfig> FSQ;
|
||||
typedef fsq::FSQ<NoResumeMockConfig> NoResumeFSQ;
|
||||
|
||||
static void CleanupOldFiles() {
|
||||
// Initialize a temporary FSQ to remove previously created files for the tests that need it.
|
||||
TestOutputFilesProcessor processor;
|
||||
FSQ(processor, kTestDir).ShutdownAndRemoveAllFSQFiles();
|
||||
}
|
||||
|
||||
// Observe messages being processed as they exceed 20 bytes of size.
|
||||
TEST(FileSystemQueueTest, FinalizedBySize) {
|
||||
CleanupOldFiles();
|
||||
|
||||
TestOutputFilesProcessor processor;
|
||||
MockTime mock_wall_time;
|
||||
FSQ fsq(processor, kTestDir, mock_wall_time);
|
||||
|
||||
// Confirm the queue is empty.
|
||||
EXPECT_EQ(0ull, fsq.GetQueueStatus().appended_file_size);
|
||||
EXPECT_EQ(0u, fsq.GetQueueStatus().finalized.queue.size());
|
||||
EXPECT_EQ(0ul, fsq.GetQueueStatus().finalized.total_size);
|
||||
|
||||
// Add a few entries.
|
||||
mock_wall_time.now = 101;
|
||||
fsq.PushMessage("this is");
|
||||
mock_wall_time.now = 102;
|
||||
fsq.PushMessage("a test");
|
||||
mock_wall_time.now = 103;
|
||||
|
||||
// Confirm the queue is still empty.
|
||||
EXPECT_EQ(15ull, fsq.GetQueueStatus().appended_file_size); // 15 == strlen("this is\na test\n").
|
||||
EXPECT_EQ(0u, fsq.GetQueueStatus().finalized.queue.size());
|
||||
EXPECT_EQ(0ul, fsq.GetQueueStatus().finalized.total_size);
|
||||
EXPECT_EQ(0u, processor.finalized_count);
|
||||
|
||||
// Add another message, under 20 bytes itself, that would make the current file exceed 20 bytes.
|
||||
fsq.PushMessage("process now");
|
||||
while (processor.finalized_count != 1) {
|
||||
; // Spin lock.
|
||||
}
|
||||
|
||||
EXPECT_EQ(1u, processor.finalized_count);
|
||||
EXPECT_EQ("finalized-00000000000000000101.bin", processor.filenames);
|
||||
EXPECT_EQ("this is\na test\n", processor.contents);
|
||||
EXPECT_EQ(103ull, processor.timestamp);
|
||||
|
||||
// Since the new message made it to the next file, finalize it and confirm the message is there as well.
|
||||
fsq.ForceProcessing();
|
||||
while (processor.finalized_count != 2) {
|
||||
; // Spin lock.
|
||||
}
|
||||
|
||||
EXPECT_EQ(2u, processor.finalized_count);
|
||||
EXPECT_EQ("finalized-00000000000000000101.bin|finalized-00000000000000000103.bin", processor.filenames);
|
||||
EXPECT_EQ("this is\na test\nFILE SEPARATOR\nprocess now\n", processor.contents);
|
||||
EXPECT_EQ(103ull, processor.timestamp);
|
||||
}
|
||||
|
||||
// Observe messages being processed as they get older than 10 seconds.
|
||||
TEST(FileSystemQueueTest, FinalizedByAge) {
|
||||
CleanupOldFiles();
|
||||
|
||||
TestOutputFilesProcessor processor;
|
||||
MockTime mock_wall_time;
|
||||
FSQ fsq(processor, kTestDir, mock_wall_time);
|
||||
|
||||
// Confirm the queue is empty.
|
||||
EXPECT_EQ(0ull, fsq.GetQueueStatus().appended_file_size);
|
||||
EXPECT_EQ(0u, fsq.GetQueueStatus().finalized.queue.size());
|
||||
EXPECT_EQ(0ul, fsq.GetQueueStatus().finalized.total_size);
|
||||
|
||||
// Add a few entries.
|
||||
mock_wall_time.now = 10000;
|
||||
fsq.PushMessage("this too");
|
||||
mock_wall_time.now = 10001;
|
||||
fsq.PushMessage("shall");
|
||||
|
||||
// Confirm the queue is still empty.
|
||||
EXPECT_EQ(15ull, fsq.GetQueueStatus().appended_file_size); // 15 == strlen("this is\na test\n").
|
||||
EXPECT_EQ(0u, fsq.GetQueueStatus().finalized.queue.size());
|
||||
EXPECT_EQ(0ul, fsq.GetQueueStatus().finalized.total_size);
|
||||
EXPECT_EQ(0u, processor.finalized_count);
|
||||
|
||||
// Add another message and make the current file span an interval of more than 10 seconds.
|
||||
mock_wall_time.now = 21000;
|
||||
fsq.PushMessage("pass");
|
||||
|
||||
while (processor.finalized_count != 1) {
|
||||
; // Spin lock.
|
||||
}
|
||||
|
||||
EXPECT_EQ(1u, processor.finalized_count);
|
||||
EXPECT_EQ("finalized-00000000000000010000.bin", processor.filenames);
|
||||
EXPECT_EQ("this too\nshall\n", processor.contents);
|
||||
EXPECT_EQ(21000ull, processor.timestamp);
|
||||
|
||||
// Since the new message made it to the next file, finalize it and confirm the message is there as well.
|
||||
fsq.ForceProcessing();
|
||||
while (processor.finalized_count != 2) {
|
||||
; // Spin lock.
|
||||
}
|
||||
|
||||
EXPECT_EQ(2u, processor.finalized_count);
|
||||
EXPECT_EQ("finalized-00000000000000010000.bin|finalized-00000000000000021000.bin", processor.filenames);
|
||||
EXPECT_EQ("this too\nshall\nFILE SEPARATOR\npass\n", processor.contents);
|
||||
EXPECT_EQ(21000ull, processor.timestamp);
|
||||
}
|
||||
|
||||
// Pushes a few messages and force their processing.
|
||||
TEST(FileSystemQueueTest, ForceProcessing) {
|
||||
CleanupOldFiles();
|
||||
|
||||
TestOutputFilesProcessor processor;
|
||||
MockTime mock_wall_time;
|
||||
FSQ fsq(processor, kTestDir, mock_wall_time);
|
||||
|
||||
// Confirm the queue is empty.
|
||||
EXPECT_EQ(0ull, fsq.GetQueueStatus().appended_file_size);
|
||||
EXPECT_EQ(0u, fsq.GetQueueStatus().finalized.queue.size());
|
||||
EXPECT_EQ(0ul, fsq.GetQueueStatus().finalized.total_size);
|
||||
|
||||
// Add a few entries.
|
||||
mock_wall_time.now = 1001;
|
||||
fsq.PushMessage("foo");
|
||||
mock_wall_time.now = 1002;
|
||||
fsq.PushMessage("bar");
|
||||
mock_wall_time.now = 1003;
|
||||
fsq.PushMessage("baz");
|
||||
|
||||
// Confirm the queue is empty.
|
||||
EXPECT_EQ(12ull, fsq.GetQueueStatus().appended_file_size); // Three messages of (3 + '\n') bytes each.
|
||||
EXPECT_EQ(0u, fsq.GetQueueStatus().finalized.queue.size());
|
||||
EXPECT_EQ(0ul, fsq.GetQueueStatus().finalized.total_size);
|
||||
|
||||
// Force entries processing to have three freshly added ones reach our TestOutputFilesProcessor.
|
||||
fsq.ForceProcessing();
|
||||
while (!processor.finalized_count) {
|
||||
; // Spin lock.
|
||||
}
|
||||
|
||||
EXPECT_EQ(1u, processor.finalized_count);
|
||||
EXPECT_EQ("finalized-00000000000000001001.bin", processor.filenames);
|
||||
EXPECT_EQ("foo\nbar\nbaz\n", processor.contents);
|
||||
EXPECT_EQ(1003ull, processor.timestamp);
|
||||
}
|
||||
|
||||
// Confirm the existing file is resumed.
|
||||
TEST(FileSystemQueueTest, ResumesExistingFile) {
|
||||
CleanupOldFiles();
|
||||
|
||||
TestOutputFilesProcessor processor;
|
||||
MockTime mock_wall_time;
|
||||
|
||||
bricks::WriteStringToFile(bricks::FileSystem::JoinPath(kTestDir, "current-00000000000000000001.bin"),
|
||||
"meh\n");
|
||||
|
||||
FSQ fsq(processor, kTestDir, mock_wall_time);
|
||||
|
||||
mock_wall_time.now = 1;
|
||||
fsq.PushMessage("wow");
|
||||
|
||||
fsq.ForceProcessing();
|
||||
while (!processor.finalized_count) {
|
||||
; // Spin lock.
|
||||
}
|
||||
|
||||
EXPECT_EQ(1u, processor.finalized_count);
|
||||
EXPECT_EQ("finalized-00000000000000000001.bin", processor.filenames);
|
||||
EXPECT_EQ("meh\nwow\n", processor.contents);
|
||||
}
|
||||
|
||||
// Confirm only one existing file is resumed, the rest are finalized.
|
||||
TEST(FileSystemQueueTest, ResumesOnlyExistingFileAndFinalizesTheRest) {
|
||||
CleanupOldFiles();
|
||||
|
||||
TestOutputFilesProcessor processor;
|
||||
MockTime mock_wall_time;
|
||||
|
||||
bricks::WriteStringToFile(bricks::FileSystem::JoinPath(kTestDir, "current-00000000000000000001.bin"),
|
||||
"one\n");
|
||||
bricks::WriteStringToFile(bricks::FileSystem::JoinPath(kTestDir, "current-00000000000000000002.bin"),
|
||||
"two\n");
|
||||
bricks::WriteStringToFile(bricks::FileSystem::JoinPath(kTestDir, "current-00000000000000000003.bin"),
|
||||
"three\n");
|
||||
|
||||
FSQ fsq(processor, kTestDir, mock_wall_time);
|
||||
|
||||
while (processor.finalized_count != 2) {
|
||||
; // Spin lock.
|
||||
}
|
||||
|
||||
EXPECT_EQ(2u, processor.finalized_count);
|
||||
EXPECT_EQ("finalized-00000000000000000001.bin|finalized-00000000000000000002.bin", processor.filenames);
|
||||
EXPECT_EQ("one\nFILE SEPARATOR\ntwo\n", processor.contents);
|
||||
processor.ClearStats();
|
||||
|
||||
mock_wall_time.now = 4;
|
||||
fsq.PushMessage("four");
|
||||
|
||||
fsq.ForceProcessing();
|
||||
while (processor.finalized_count != 1) {
|
||||
; // Spin lock.
|
||||
}
|
||||
|
||||
EXPECT_EQ(1u, processor.finalized_count);
|
||||
EXPECT_EQ("finalized-00000000000000000003.bin", processor.filenames);
|
||||
EXPECT_EQ("three\nfour\n", processor.contents);
|
||||
}
|
||||
|
||||
// Confirm the existing file is not resumed if the strategy dictates so.
|
||||
TEST(FileSystemQueueTest, ResumeCanBeTurnedOff) {
|
||||
CleanupOldFiles();
|
||||
|
||||
TestOutputFilesProcessor processor;
|
||||
MockTime mock_wall_time;
|
||||
|
||||
bricks::WriteStringToFile(bricks::FileSystem::JoinPath(kTestDir, "current-00000000000000000000.bin"),
|
||||
"meh\n");
|
||||
|
||||
NoResumeFSQ fsq(processor, kTestDir, mock_wall_time);
|
||||
|
||||
while (processor.finalized_count != 1) {
|
||||
; // Spin lock.
|
||||
}
|
||||
|
||||
EXPECT_EQ("finalized-00000000000000000000.bin", processor.filenames);
|
||||
EXPECT_EQ("meh\n", processor.contents);
|
||||
|
||||
mock_wall_time.now = 1;
|
||||
fsq.PushMessage("wow");
|
||||
|
||||
fsq.ForceProcessing();
|
||||
while (processor.finalized_count != 2) {
|
||||
; // Spin lock.
|
||||
}
|
||||
|
||||
EXPECT_EQ(2u, processor.finalized_count);
|
||||
EXPECT_EQ("finalized-00000000000000000000.bin|finalized-00000000000000000001.bin", processor.filenames);
|
||||
EXPECT_EQ("meh\nFILE SEPARATOR\nwow\n", processor.contents);
|
||||
}
|
||||
|
||||
// Purges the oldest files so that there are at most three in the queue of the finalized ones.
|
||||
TEST(FileSystemQueueTest, PurgesByNumberOfFiles) {
|
||||
CleanupOldFiles();
|
||||
|
||||
TestOutputFilesProcessor processor;
|
||||
processor.SetMimicUnavailable();
|
||||
MockTime mock_wall_time;
|
||||
FSQ fsq(processor, kTestDir, mock_wall_time);
|
||||
|
||||
// Add a few files.
|
||||
mock_wall_time.now = 100001;
|
||||
fsq.PushMessage("one");
|
||||
fsq.FinalizeCurrentFile();
|
||||
mock_wall_time.now = 100002;
|
||||
fsq.PushMessage("two");
|
||||
fsq.FinalizeCurrentFile();
|
||||
mock_wall_time.now = 100003;
|
||||
fsq.PushMessage("three");
|
||||
fsq.FinalizeCurrentFile();
|
||||
|
||||
// Confirm the queue contains three files.
|
||||
EXPECT_EQ(3u, fsq.GetQueueStatus().finalized.queue.size());
|
||||
EXPECT_EQ(14ul, fsq.GetQueueStatus().finalized.total_size); // strlen("one\ntwo\nthree\n").
|
||||
EXPECT_EQ("finalized-00000000000000100001.bin", fsq.GetQueueStatus().finalized.queue.front().name);
|
||||
EXPECT_EQ("finalized-00000000000000100003.bin", fsq.GetQueueStatus().finalized.queue.back().name);
|
||||
|
||||
// Add the fourth file.
|
||||
mock_wall_time.now = 100004;
|
||||
fsq.PushMessage("four");
|
||||
fsq.FinalizeCurrentFile();
|
||||
|
||||
// Confirm the first one got deleted.
|
||||
EXPECT_EQ(3u, fsq.GetQueueStatus().finalized.queue.size());
|
||||
EXPECT_EQ(15ul, fsq.GetQueueStatus().finalized.total_size); // strlen("two\nthree\nfour\n").
|
||||
EXPECT_EQ("finalized-00000000000000100002.bin", fsq.GetQueueStatus().finalized.queue.front().name);
|
||||
EXPECT_EQ("finalized-00000000000000100004.bin", fsq.GetQueueStatus().finalized.queue.back().name);
|
||||
}
|
||||
|
||||
// Purges the oldest files so that the total size of the queue never exceeds 20 bytes.
|
||||
TEST(FileSystemQueueTest, PurgesByTotalSize) {
|
||||
CleanupOldFiles();
|
||||
|
||||
TestOutputFilesProcessor processor;
|
||||
processor.SetMimicUnavailable();
|
||||
MockTime mock_wall_time;
|
||||
FSQ fsq(processor, kTestDir, mock_wall_time);
|
||||
|
||||
mock_wall_time.now = 100001;
|
||||
fsq.PushMessage("one");
|
||||
mock_wall_time.now = 100002;
|
||||
fsq.PushMessage("two");
|
||||
fsq.FinalizeCurrentFile();
|
||||
mock_wall_time.now = 100003;
|
||||
fsq.PushMessage("three");
|
||||
mock_wall_time.now = 100004;
|
||||
fsq.PushMessage("four");
|
||||
fsq.FinalizeCurrentFile();
|
||||
|
||||
// Confirm the queue contains two files.
|
||||
EXPECT_EQ(2u, fsq.GetQueueStatus().finalized.queue.size());
|
||||
EXPECT_EQ(19ul, fsq.GetQueueStatus().finalized.total_size); // strlen("one\ntwo\nthree\nfour\n").
|
||||
EXPECT_EQ("finalized-00000000000000100001.bin", fsq.GetQueueStatus().finalized.queue.front().name);
|
||||
EXPECT_EQ("finalized-00000000000000100003.bin", fsq.GetQueueStatus().finalized.queue.back().name);
|
||||
|
||||
// Add another file of the size that would force the oldest one to get purged.
|
||||
mock_wall_time.now = 100010;
|
||||
fsq.PushMessage("very, very, very, very long message");
|
||||
fsq.FinalizeCurrentFile();
|
||||
|
||||
// Confirm the oldest file got deleted.
|
||||
EXPECT_EQ(2u, fsq.GetQueueStatus().finalized.queue.size());
|
||||
EXPECT_EQ(47l, fsq.GetQueueStatus().finalized.total_size);
|
||||
EXPECT_EQ("finalized-00000000000000100003.bin", fsq.GetQueueStatus().finalized.queue.front().name);
|
||||
EXPECT_EQ("finalized-00000000000000100010.bin", fsq.GetQueueStatus().finalized.queue.back().name);
|
||||
}
|
||||
|
||||
// Persists retry delay to the file.
|
||||
TEST(FileSystemQueueTest, SavesRetryDelayToFile) {
|
||||
const std::string state_file_name = std::move(bricks::FileSystem::JoinPath(kTestDir, "state"));
|
||||
|
||||
CleanupOldFiles();
|
||||
bricks::RemoveFile(state_file_name, bricks::RemoveFileParameters::Silent);
|
||||
|
||||
const uint64_t t1 = static_cast<uint64_t>(bricks::time::Now());
|
||||
|
||||
TestOutputFilesProcessor processor;
|
||||
MockTime mock_wall_time;
|
||||
typedef fsq::strategy::ExponentialDelayRetryStrategy<bricks::FileSystem> ExpRetry;
|
||||
// Wait between 1 second and 2 seconds before retrying.
|
||||
FSQ fsq(processor,
|
||||
kTestDir,
|
||||
mock_wall_time,
|
||||
bricks::FileSystem(),
|
||||
ExpRetry(bricks::FileSystem(), ExpRetry::DistributionParams(1500, 1000, 2000)));
|
||||
|
||||
// Attach to file, this will force the creation of the file.
|
||||
fsq.AttachToFile(state_file_name);
|
||||
|
||||
const uint64_t t2 = static_cast<uint64_t>(bricks::time::Now());
|
||||
|
||||
// At start, with no file to resume, update time should be equal to next processing ready time,
|
||||
// and they should both be between `t1` and `t2` retrieved above.
|
||||
std::string contents1 = bricks::ReadFileAsString(state_file_name);
|
||||
ASSERT_EQ(contents1.length(), 41); // 20 + 1 + 20.
|
||||
std::istringstream is1(contents1);
|
||||
uint64_t a1, b1;
|
||||
is1 >> a1 >> b1;
|
||||
EXPECT_GE(a1, t1);
|
||||
EXPECT_LE(a1, t2);
|
||||
EXPECT_GE(b1, t1);
|
||||
EXPECT_LE(b1, t2);
|
||||
EXPECT_EQ(a1, b1);
|
||||
|
||||
// Now, after one failure, update time whould be between `t4` and `t4`,
|
||||
// and next processing time should be between one and two seconds into the future.
|
||||
const uint64_t t3 = static_cast<uint64_t>(bricks::time::Now());
|
||||
fsq.PushMessage("blah");
|
||||
processor.SetMimicNeedRetry();
|
||||
fsq.ForceProcessing();
|
||||
const uint64_t t4 = static_cast<uint64_t>(bricks::time::Now());
|
||||
|
||||
// Give FSQ some time to apply retry policy and to update the state file.
|
||||
const uint64_t SAFETY_INTERVAL = 50; // Wait for 50ms to ensure the file is updated.
|
||||
// TODO(dkorolev): This makes the test flaky, but should be fine for Alex to go ahead and push it.
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(SAFETY_INTERVAL));
|
||||
|
||||
std::string contents2 = bricks::ReadFileAsString(state_file_name);
|
||||
ASSERT_EQ(contents2.length(), 41); // 20 + 1 + 20.
|
||||
std::istringstream is2(contents2);
|
||||
uint64_t a2, b2;
|
||||
is2 >> a2 >> b2;
|
||||
EXPECT_GE(a2, t3);
|
||||
EXPECT_LE(a2, t4 + SAFETY_INTERVAL);
|
||||
EXPECT_GE(b2, a2 + 1000);
|
||||
EXPECT_LE(b2, a2 + 2000);
|
||||
}
|
||||
|
||||
// Reads retry delay from file.
|
||||
TEST(FileSystemQueueTest, ReadsRetryDelayFromFile) {
|
||||
const std::string state_file_name = std::move(bricks::FileSystem::JoinPath(kTestDir, "state"));
|
||||
|
||||
CleanupOldFiles();
|
||||
|
||||
// Legitimately configure FSQ to wait for 5 seconds from now before further processing takes place.
|
||||
const uint64_t t1 = static_cast<uint64_t>(bricks::time::Now());
|
||||
using bricks::strings::PackToString;
|
||||
bricks::WriteStringToFile(state_file_name.c_str(), PackToString(t1) + ' ' + PackToString(t1 + 5000));
|
||||
|
||||
TestOutputFilesProcessor processor;
|
||||
MockTime mock_wall_time;
|
||||
typedef fsq::strategy::ExponentialDelayRetryStrategy<bricks::FileSystem> ExpRetry;
|
||||
FSQ fsq(processor,
|
||||
kTestDir,
|
||||
mock_wall_time,
|
||||
bricks::FileSystem(),
|
||||
ExpRetry(bricks::FileSystem(), ExpRetry::DistributionParams(1500, 1000, 2000)));
|
||||
fsq.AttachToFile(state_file_name);
|
||||
|
||||
const uint64_t t2 = static_cast<uint64_t>(bricks::time::Now());
|
||||
|
||||
// Should wait for 5 more seconds, perhaps without a few milliseconds it took to read the file, etc.
|
||||
bricks::time::MILLISECONDS_INTERVAL d;
|
||||
ASSERT_TRUE(fsq.ShouldWait(&d));
|
||||
EXPECT_GE(t2, t1);
|
||||
EXPECT_LE(t2 - t1, 50);
|
||||
EXPECT_GE(static_cast<uint64_t>(d), 4950);
|
||||
EXPECT_LE(static_cast<uint64_t>(d), 5000);
|
||||
}
|
||||
|
||||
// Ignored retry delay if it was set from the future.
|
||||
TEST(FileSystemQueueTest, IgnoredRetryDelaySetFromTheFuture) {
|
||||
const std::string state_file_name = std::move(bricks::FileSystem::JoinPath(kTestDir, "state"));
|
||||
|
||||
CleanupOldFiles();
|
||||
|
||||
// Incorrectly configure FSQ to start 5 seconds from now, set at 0.5 seconds into the future.
|
||||
const uint64_t t = static_cast<uint64_t>(bricks::time::Now());
|
||||
using bricks::strings::PackToString;
|
||||
bricks::WriteStringToFile(state_file_name.c_str(), PackToString(t + 500) + ' ' + PackToString(t + 5000));
|
||||
|
||||
TestOutputFilesProcessor processor;
|
||||
MockTime mock_wall_time;
|
||||
typedef fsq::strategy::ExponentialDelayRetryStrategy<bricks::FileSystem> ExpRetry;
|
||||
FSQ fsq(processor,
|
||||
kTestDir,
|
||||
mock_wall_time,
|
||||
bricks::FileSystem(),
|
||||
ExpRetry(bricks::FileSystem(), ExpRetry::DistributionParams(1500, 1000, 2000)));
|
||||
fsq.AttachToFile(state_file_name);
|
||||
|
||||
bricks::time::MILLISECONDS_INTERVAL interval;
|
||||
ASSERT_FALSE(fsq.ShouldWait(&interval));
|
||||
}
|
204
3party/Alohalytics/src/FileStorageQueue/test_retry_strategy.cc
Normal file
204
3party/Alohalytics/src/FileStorageQueue/test_retry_strategy.cc
Normal file
|
@ -0,0 +1,204 @@
|
|||
// Tests for FSQ's retry strategy.
|
||||
//
|
||||
// The implementation of retry logic uses condition_variable::wait_for(),
|
||||
// thus real delays on real clock are used for this test.
|
||||
//
|
||||
// The above makes the test non-deterministic in theory.
|
||||
// In practice, the thresholds are liberal enough for it to pass.
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "fsq.h"
|
||||
#include "exponential_retry_strategy.h"
|
||||
|
||||
#include "../Bricks/dflags/dflags.h"
|
||||
|
||||
#include "../Bricks/file/file.h"
|
||||
#include "../Bricks/strings/printf.h"
|
||||
|
||||
#include "../Bricks/3party/gtest/gtest.h"
|
||||
#include "../Bricks/3party/gtest/gtest-main-with-dflags.h"
|
||||
|
||||
using std::string;
|
||||
using std::atomic_size_t;
|
||||
|
||||
using bricks::time::EPOCH_MILLISECONDS;
|
||||
|
||||
DEFINE_string(tmpdir, "build/", "Directory to create temporary files in.");
|
||||
|
||||
DEFINE_int32(n, 200, "Number of FSQ-s to run. They will be run in parallel");
|
||||
DEFINE_int32(number_of_failures, 1, "The first --number_of_failures processing attempts will fail.");
|
||||
|
||||
// Wait for 0.2s on average, with exponential distribution.
|
||||
DEFINE_double(mean_ms, 200, "Retry exponential distribution parameter: Average time, in ms.");
|
||||
DEFINE_double(min_ms, 0, "Retry exponential distribution parameter: Minimum time, in ms.");
|
||||
DEFINE_double(max_ms, 5000, "Retry exponential distribution parameter: Maximum time, in ms.");
|
||||
|
||||
// At least 25% of delays should be under 0.1s and at least 25% should be over 0.25s.
|
||||
DEFINE_double(p25_max, 100, "Maximum allowed value for 25-th percentile latency, in ms.");
|
||||
DEFINE_double(p75_min, 250, "Minimum allowed value for 25-th percentile latency, in ms.");
|
||||
|
||||
DEFINE_bool(verbose, true, "Set to false to supress verbose output.");
|
||||
|
||||
#if 0
|
||||
// TODO(dkorolev): Move to Bricks.
|
||||
inline void SafeDebugOutput(const std::string& message) {
|
||||
if (FLAGS_verbose) {
|
||||
static std::mutex mutex;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
std::cerr << message << std::endl;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// LatencyMeasuringProcessor measures the time it took for the message to get processed.
|
||||
struct LatencyMeasuringProcessor final {
|
||||
LatencyMeasuringProcessor(size_t index, atomic_size_t& counter)
|
||||
: index_(index), counter_(counter), failures_remaining_(FLAGS_number_of_failures), processed_(false) {}
|
||||
|
||||
fsq::FileProcessingResult OnFileReady(const fsq::FileInfo<EPOCH_MILLISECONDS>&, EPOCH_MILLISECONDS now) {
|
||||
if (failures_remaining_ > 0) {
|
||||
--failures_remaining_;
|
||||
++counter_; // Important to only increment `counter_` after `processed_` is set, hence it's inside.
|
||||
return fsq::FileProcessingResult::FailureNeedRetry;
|
||||
} else {
|
||||
assert(failures_remaining_ == 0);
|
||||
assert(!processed_);
|
||||
processed_ = true;
|
||||
message_processed_timestamp_ = now;
|
||||
++counter_; // Important to only increment `counter_` after `processed_` is set, hence it's inside.
|
||||
return fsq::FileProcessingResult::Success;
|
||||
}
|
||||
}
|
||||
|
||||
size_t index_;
|
||||
atomic_size_t& counter_;
|
||||
int failures_remaining_;
|
||||
bool processed_ = false;
|
||||
EPOCH_MILLISECONDS message_push_timestamp_;
|
||||
EPOCH_MILLISECONDS message_processed_timestamp_;
|
||||
};
|
||||
|
||||
typedef fsq::Config<LatencyMeasuringProcessor> TestConfig;
|
||||
|
||||
template <typename T>
|
||||
double Percentile(double p, const std::vector<T>& x) {
|
||||
assert(!x.empty());
|
||||
assert(p >= 0 && p <= 1);
|
||||
const double index = 1.0 * (x.size() - 1) * p;
|
||||
const size_t i0 = static_cast<size_t>(index);
|
||||
const size_t i1 = i0 + 1;
|
||||
const double w1 = index - i0;
|
||||
const double w0 = 1.0 - w1;
|
||||
double result = w0 * x[i0];
|
||||
if (i1 < x.size()) {
|
||||
result += w1 * x[i1];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST(FileSystemQueueLatenciesTest, LatencyPercentiles) {
|
||||
struct Worker final {
|
||||
typedef fsq::strategy::ExponentialDelayRetryStrategy<bricks::FileSystem> ExpRetry;
|
||||
Worker(int index, std::atomic_size_t& counter)
|
||||
: directory_name_(GenDirNameAndCreateDir(index)),
|
||||
index_(index),
|
||||
counter_(counter),
|
||||
processor_(index, counter_),
|
||||
fsq_(processor_,
|
||||
directory_name_,
|
||||
fsq::strategy::UseEpochMilliseconds(),
|
||||
bricks::FileSystem(),
|
||||
ExpRetry(bricks::FileSystem(),
|
||||
ExpRetry::DistributionParams(FLAGS_mean_ms, FLAGS_min_ms, FLAGS_max_ms))) {}
|
||||
|
||||
~Worker() {
|
||||
// TODO(dkorolev): Remove created file(s) and the directory.
|
||||
}
|
||||
|
||||
void InvokePushMessage() {
|
||||
processor_.message_push_timestamp_ = bricks::time::Now();
|
||||
fsq_.PushMessage("foo");
|
||||
}
|
||||
|
||||
void InvokeFinalizeCurrentFile() { fsq_.FinalizeCurrentFile(); }
|
||||
|
||||
uint64_t ActualProcessingLatencyInMS() const {
|
||||
assert(processor_.processed_);
|
||||
return static_cast<uint64_t>(processor_.message_processed_timestamp_ -
|
||||
processor_.message_push_timestamp_);
|
||||
}
|
||||
|
||||
static std::string GenDirName(int index) {
|
||||
return bricks::FileSystem::JoinPath(FLAGS_tmpdir, bricks::strings::Printf("%05d", index));
|
||||
}
|
||||
|
||||
static std::string GenDirNameAndCreateDir(int index) {
|
||||
std::string directory_name = GenDirName(index);
|
||||
bricks::FileSystem::CreateDirectory(directory_name);
|
||||
return directory_name;
|
||||
}
|
||||
|
||||
std::string directory_name_;
|
||||
size_t index_;
|
||||
std::atomic_size_t& counter_;
|
||||
LatencyMeasuringProcessor processor_;
|
||||
fsq::FSQ<TestConfig> fsq_;
|
||||
};
|
||||
|
||||
// Cleanup first.
|
||||
const size_t N = static_cast<size_t>(FLAGS_n);
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
struct DummyProcessor final {
|
||||
DummyProcessor() {}
|
||||
fsq::FileProcessingResult OnFileReady(const fsq::FileInfo<EPOCH_MILLISECONDS>&, EPOCH_MILLISECONDS) {
|
||||
return fsq::FileProcessingResult::Success;
|
||||
}
|
||||
};
|
||||
DummyProcessor processor;
|
||||
fsq::FSQ<fsq::Config<DummyProcessor>>(processor, Worker::GenDirName(i + 1)).ShutdownAndRemoveAllFSQFiles();
|
||||
}
|
||||
|
||||
// Now, run the test.
|
||||
std::atomic_size_t counter(0);
|
||||
std::vector<std::unique_ptr<Worker>> workers;
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
workers.emplace_back(new Worker(i + 1, counter));
|
||||
}
|
||||
|
||||
for (auto& it : workers) {
|
||||
it->InvokePushMessage();
|
||||
}
|
||||
|
||||
for (auto& it : workers) {
|
||||
it->InvokeFinalizeCurrentFile();
|
||||
}
|
||||
|
||||
while (counter != N * (1 + FLAGS_number_of_failures)) {
|
||||
; // Spin lock;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> latencies(N);
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
latencies[i] = workers[i]->ActualProcessingLatencyInMS();
|
||||
}
|
||||
std::sort(latencies.begin(), latencies.end());
|
||||
if (FLAGS_verbose) {
|
||||
std::cerr << "Latencies, ms:";
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
std::cerr << ' ' << latencies[i];
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
}
|
||||
const double latency_p25_ms = Percentile(0.25, latencies);
|
||||
const double latency_p75_ms = Percentile(0.75, latencies);
|
||||
if (FLAGS_verbose) {
|
||||
std::cerr << "Latency average: " << std::accumulate(latencies.begin(), latencies.end(), 0.0) / N
|
||||
<< " ms\n";
|
||||
std::cerr << "Latency 25-th percentile: " << latency_p25_ms << " ms\n";
|
||||
std::cerr << "Latency 75-th percentile: " << latency_p75_ms << " ms\n";
|
||||
}
|
||||
|
||||
EXPECT_LE(latency_p25_ms, FLAGS_p25_max);
|
||||
EXPECT_GE(latency_p75_ms, FLAGS_p75_min);
|
||||
}
|
108
3party/Alohalytics/src/alohalytics.h
Normal file
108
3party/Alohalytics/src/alohalytics.h
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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.
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef ALOHALYTICS_H
|
||||
#define ALOHALYTICS_H
|
||||
|
||||
#include "message_queue.h"
|
||||
#include "FileStorageQueue/fsq.h"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
namespace alohalytics {
|
||||
|
||||
typedef std::map<std::string, std::string> TStringMap;
|
||||
|
||||
class Stats final {
|
||||
std::string upload_url_;
|
||||
// Stores already serialized and ready-to-append event with unique client id.
|
||||
// NOTE: Statistics will not be uploaded if unique client id was not set.
|
||||
std::string unique_client_id_event_;
|
||||
MessageQueue<Stats> message_queue_;
|
||||
typedef fsq::FSQ<fsq::Config<Stats>> TFileStorageQueue;
|
||||
// TODO(AlexZ): Refactor storage queue so it can store messages in memory if no file directory was set.
|
||||
std::unique_ptr<TFileStorageQueue> file_storage_queue_;
|
||||
// Used to store events if no storage path was set.
|
||||
// Flushes all data to file storage and is not used any more if storage path was set.
|
||||
typedef std::list<std::string> TMemoryContainer;
|
||||
TMemoryContainer memory_storage_;
|
||||
bool debug_mode_ = false;
|
||||
|
||||
// Use alohalytics::Stats::Instance() to access statistics engine.
|
||||
Stats();
|
||||
|
||||
static bool UploadBuffer(const std::string& url, std::string&& buffer, bool debug_mode);
|
||||
|
||||
public:
|
||||
// Processes messages passed from UI in message queue's own thread.
|
||||
// TODO(AlexZ): Refactor message queue to make this method private.
|
||||
void OnMessage(const std::string& message, size_t dropped_events);
|
||||
|
||||
// Called by file storage engine to upload file with collected data.
|
||||
// Should return true if upload has been successful.
|
||||
// TODO(AlexZ): Refactor FSQ to make this method private.
|
||||
bool OnFileReady(const std::string& full_path_to_file, uint64_t file_size);
|
||||
|
||||
static Stats& Instance();
|
||||
|
||||
// Easier integration if enabled.
|
||||
Stats& SetDebugMode(bool enable);
|
||||
|
||||
// If not set, collected data will never be uploaded.
|
||||
Stats& SetServerUrl(const std::string& url_to_upload_statistics_to);
|
||||
|
||||
// If not set, data will be stored in memory only.
|
||||
Stats& SetStoragePath(const std::string& full_path_to_storage_with_a_slash_at_the_end);
|
||||
|
||||
// If not set, data will be uploaded without any unique id.
|
||||
Stats& SetClientId(const std::string& unique_client_id);
|
||||
|
||||
void LogEvent(std::string const& event_name);
|
||||
|
||||
void LogEvent(std::string const& event_name, std::string const& event_value);
|
||||
|
||||
void LogEvent(std::string const& event_name, TStringMap const& value_pairs);
|
||||
|
||||
// Forcedly tries to upload all stored data to the server.
|
||||
void Upload();
|
||||
};
|
||||
|
||||
inline void LogEvent(std::string const& event_name) {
|
||||
Stats::Instance().LogEvent(event_name);
|
||||
}
|
||||
|
||||
inline void LogEvent(std::string const& event_name, std::string const& event_value) {
|
||||
Stats::Instance().LogEvent(event_name, event_value);
|
||||
}
|
||||
|
||||
inline void LogEvent(std::string const& event_name, TStringMap const& value_pairs) {
|
||||
Stats::Instance().LogEvent(event_name, value_pairs);
|
||||
}
|
||||
|
||||
} // namespace alohalytics
|
||||
|
||||
#endif // #ifndef ALOHALYTICS_H
|
22
3party/Alohalytics/src/android/AndroidManifest.xml
Normal file
22
3party/Alohalytics/src/android/AndroidManifest.xml
Normal file
|
@ -0,0 +1,22 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.alohalytics">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<!-- This permission is needed if you want to access device's IMEI.
|
||||
It is not enabled by default as users are very sensitive to it.
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
|
||||
-->
|
||||
|
||||
<application>
|
||||
<!-- Used to automatically upload statistics using WiFi. -->
|
||||
<receiver
|
||||
android:name="org.alohalytics.ConnectivityChangedReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
</manifest>
|
29
3party/Alohalytics/src/android/README.md
Normal file
29
3party/Alohalytics/src/android/README.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
1. Include these files in your project by adding them into the build.gradle:
|
||||
```
|
||||
android {
|
||||
sourceSets.main {
|
||||
java.srcDirs = ['your_java_sources', 'Alohalytics/src/android/java']
|
||||
jni.srcDirs = ['Alohalytics/src', 'Alohalytics/src/android/jni']
|
||||
}
|
||||
defaultConfig {
|
||||
ndk {
|
||||
moduleName 'alohalytics'
|
||||
stl 'c++_static'
|
||||
cFlags '-frtti -fexceptions'
|
||||
ldLibs 'log', 'atomic'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
But if you use custom Android.mk file for your sources, add these lines there (instead of jni.srcDirs and ndk block above):
|
||||
```
|
||||
LOCAL_SRC_FILES += Alohalytics/src/android/jni/jni_alohalytics.cc \
|
||||
Alohalytics/src/cpp/alohalytics.cc
|
||||
```
|
||||
2. Modify your AndroidManifest as in Alohalytics/src/android/AndroidManifest.xml
|
||||
3. Insert ```System.loadLibrary("alohalytics");``` call if you don't load your own jni library. You can put it into your main activity:
|
||||
```
|
||||
static {
|
||||
System.loadLibrary("alohalytics");
|
||||
}
|
||||
```
|
|
@ -0,0 +1,59 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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 org.alohalytics;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
|
||||
/** To upload statistics on WiFi connected events, you should add to your <application> in AndroidManifest.xml:
|
||||
<receiver
|
||||
android:name="org.alohalytics.ConnectivityChangedReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
**/
|
||||
public class ConnectivityChangedReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final NetworkInfo networkInfo = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
|
||||
if (networkInfo != null
|
||||
&& networkInfo.getType() == ConnectivityManager.TYPE_WIFI
|
||||
&& networkInfo.isConnected()) {
|
||||
onWiFiConnected();
|
||||
}
|
||||
}
|
||||
|
||||
public void onWiFiConnected() {
|
||||
// TODO(AlexZ): Make it optional.
|
||||
org.alohalytics.Statistics.logEvent("$onWiFi");
|
||||
org.alohalytics.Statistics.forceUpload();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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 org.alohalytics;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
public class HttpTransport {
|
||||
|
||||
// TODO(AlexZ): tune for larger files
|
||||
private final static int STREAM_BUFFER_SIZE = 1024 * 64;
|
||||
private final static String TAG = "Alohalytics-HttpTransport";
|
||||
// Globally accessible for faster unit-testing
|
||||
public static int TIMEOUT_IN_MILLISECONDS = 30000;
|
||||
|
||||
public static Params run(final Params p) throws IOException, NullPointerException {
|
||||
HttpURLConnection connection = null;
|
||||
if (p.debugMode)
|
||||
Log.d(TAG, "Connecting to " + p.url);
|
||||
try {
|
||||
connection = (HttpURLConnection) new URL(p.url).openConnection(); // NullPointerException, MalformedUrlException, IOException
|
||||
// TODO(AlexZ): Customize redirects following in the future implementation for safer transfers.
|
||||
connection.setInstanceFollowRedirects(true);
|
||||
connection.setConnectTimeout(TIMEOUT_IN_MILLISECONDS);
|
||||
connection.setReadTimeout(TIMEOUT_IN_MILLISECONDS);
|
||||
connection.setUseCaches(false);
|
||||
if (p.userAgent != null) {
|
||||
connection.setRequestProperty("User-Agent", p.userAgent);
|
||||
}
|
||||
if (p.inputFilePath != null || p.data != null) {
|
||||
// POST data to the server.
|
||||
if (p.contentType == null) {
|
||||
throw new NullPointerException("Please set Content-Type for POST requests.");
|
||||
}
|
||||
connection.setRequestProperty("Content-Type", p.contentType);
|
||||
connection.setDoOutput(true);
|
||||
if (p.data != null) {
|
||||
// Use gzip compression for memory-only transfers.
|
||||
// TODO(AlexZ): Move compression to the lower file-level (file storage queue) to save device space.
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
final GZIPOutputStream zos = new GZIPOutputStream(bos);
|
||||
try {
|
||||
zos.write(p.data);
|
||||
} finally {
|
||||
zos.close();
|
||||
}
|
||||
connection.setFixedLengthStreamingMode(bos.size());
|
||||
connection.setRequestProperty("Content-Encoding", "gzip");
|
||||
final OutputStream os = connection.getOutputStream();
|
||||
try {
|
||||
os.write(bos.toByteArray());
|
||||
} finally {
|
||||
os.close();
|
||||
}
|
||||
if (p.debugMode)
|
||||
Log.d(TAG, "Sent POST with gzipped content of size " + bos.size());
|
||||
} else {
|
||||
final File file = new File(p.inputFilePath);
|
||||
assert (file.length() == (int) file.length());
|
||||
connection.setFixedLengthStreamingMode((int) file.length());
|
||||
final BufferedInputStream istream = new BufferedInputStream(new FileInputStream(file), STREAM_BUFFER_SIZE);
|
||||
final BufferedOutputStream ostream = new BufferedOutputStream(connection.getOutputStream(), STREAM_BUFFER_SIZE);
|
||||
final byte[] buffer = new byte[STREAM_BUFFER_SIZE];
|
||||
int bytesRead;
|
||||
while ((bytesRead = istream.read(buffer, 0, STREAM_BUFFER_SIZE)) > 0) {
|
||||
ostream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
istream.close(); // IOException
|
||||
ostream.close(); // IOException
|
||||
if (p.debugMode)
|
||||
Log.d(TAG, "Sent POST with file of size " + file.length());
|
||||
}
|
||||
}
|
||||
// GET data from the server or receive POST response body
|
||||
p.httpResponseCode = connection.getResponseCode();
|
||||
if (p.debugMode)
|
||||
Log.d(TAG, "Received HTTP " + p.httpResponseCode + " from server.");
|
||||
p.receivedUrl = connection.getURL().toString();
|
||||
p.contentType = connection.getContentType();
|
||||
// This implementation receives any data only if we have HTTP::OK (200).
|
||||
if (p.httpResponseCode == HttpURLConnection.HTTP_OK) {
|
||||
OutputStream ostream;
|
||||
if (p.outputFilePath != null) {
|
||||
ostream = new BufferedOutputStream(new FileOutputStream(p.outputFilePath), STREAM_BUFFER_SIZE);
|
||||
} else {
|
||||
ostream = new ByteArrayOutputStream(STREAM_BUFFER_SIZE);
|
||||
}
|
||||
// TODO(AlexZ): Add HTTP resume support in the future for partially downloaded files
|
||||
final BufferedInputStream istream = new BufferedInputStream(connection.getInputStream(), STREAM_BUFFER_SIZE);
|
||||
final byte[] buffer = new byte[STREAM_BUFFER_SIZE];
|
||||
// gzip encoding is transparently enabled and we can't use Content-Length for
|
||||
// body reading if server has gzipped it.
|
||||
final String encoding = connection.getContentEncoding();
|
||||
int bytesExpected = (encoding != null && encoding.equalsIgnoreCase("gzip")) ? -1 : connection.getContentLength();
|
||||
int bytesRead;
|
||||
while ((bytesRead = istream.read(buffer, 0, STREAM_BUFFER_SIZE)) > 0) {
|
||||
if (bytesExpected == -1) {
|
||||
// Read everything if Content-Length is not known in advance.
|
||||
ostream.write(buffer, 0, bytesRead);
|
||||
} else {
|
||||
// Read only up-to Content-Length (sometimes servers/proxies add garbage at the end).
|
||||
if (bytesExpected < bytesRead) {
|
||||
ostream.write(buffer, 0, bytesExpected);
|
||||
break;
|
||||
}
|
||||
ostream.write(buffer, 0, bytesRead);
|
||||
bytesExpected -= bytesRead;
|
||||
}
|
||||
}
|
||||
istream.close(); // IOException
|
||||
ostream.close(); // IOException
|
||||
if (ostream.getClass().equals(ByteArrayOutputStream.class)) {
|
||||
p.data = ((ByteArrayOutputStream) ostream).toByteArray();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (connection != null)
|
||||
connection.disconnect();
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
public static class Params {
|
||||
public String url = null;
|
||||
// Can be different from url in case of redirects.
|
||||
public String receivedUrl = null;
|
||||
// SHOULD be specified for any POST request (any request where we send data to the server).
|
||||
// On return, contains received Content-Type
|
||||
public String contentType = null;
|
||||
// GET if null and inputFilePath is null.
|
||||
// Sent in POST otherwise.
|
||||
public byte[] data = null;
|
||||
// Send from input file if specified instead of data.
|
||||
public String inputFilePath = null;
|
||||
// Received data is stored here if not null or in data otherwise.
|
||||
public String outputFilePath = null;
|
||||
// Optionally client can override default HTTP User-Agent.
|
||||
public String userAgent = null;
|
||||
public int httpResponseCode = -1;
|
||||
public boolean debugMode = false;
|
||||
|
||||
public Params(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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 org.alohalytics;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Statistics {
|
||||
|
||||
private static final String TAG = "Alohalytics";
|
||||
private static boolean sDebugModeEnabled = false;
|
||||
|
||||
public static void setDebugMode(boolean enable) {
|
||||
sDebugModeEnabled = enable;
|
||||
debugCPP(enable);
|
||||
}
|
||||
|
||||
// Try to upload all collected statistics now.
|
||||
public static native void forceUpload();
|
||||
|
||||
public static boolean debugMode() {
|
||||
return sDebugModeEnabled;
|
||||
}
|
||||
|
||||
// Use this setup if you are releasing a new application and/or don't bother about already existing installations
|
||||
// prior to Alohalytics integration. Alohalytics will check new unique installations by internal logic only if use this function.
|
||||
public static void setup(final String serverUrl, final Context context) {
|
||||
setup(serverUrl, context, true);
|
||||
}
|
||||
|
||||
// Set firstAppLaunch to false if you definitely know that your app was previously installed
|
||||
// (before integrating with Alohalytics) to correctly calculate new unique installations.
|
||||
public static void setup(final String serverUrl, final Context context, boolean firstAppLaunch) {
|
||||
final String storagePath = context.getFilesDir().getAbsolutePath() + "/Alohalytics/";
|
||||
// Native code expects valid existing writable dir.
|
||||
(new File(storagePath)).mkdirs();
|
||||
final Pair<String, Boolean> id = getInstallationId(context);
|
||||
setupCPP(HttpTransport.class, serverUrl, storagePath, id.first);
|
||||
|
||||
// Calculate some basic statistics about installations/updates/launches.
|
||||
String versionName = "";
|
||||
long installTime = 0, updateTime = 0;
|
||||
try {
|
||||
final android.content.pm.PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||
if (packageInfo != null) {
|
||||
versionName = packageInfo.versionName;
|
||||
installTime = packageInfo.firstInstallTime;
|
||||
updateTime = packageInfo.lastUpdateTime;
|
||||
}
|
||||
} catch (android.content.pm.PackageManager.NameNotFoundException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
final SharedPreferences prefs = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
|
||||
// Is it a real new install?
|
||||
if (firstAppLaunch && id.second && installTime == updateTime) {
|
||||
logEvent("$install", new String[]{"version", versionName,
|
||||
"secondsBeforeLaunch", String.valueOf((System.currentTimeMillis() - installTime) / 1000)});
|
||||
// Collect device info once on start.
|
||||
SystemInfo.getDeviceInfoAsync(context);
|
||||
prefs.edit().putLong(PREF_APP_UPDATE_TIME, updateTime).apply();
|
||||
} else if (updateTime != installTime && updateTime != prefs.getLong(PREF_APP_UPDATE_TIME, 0)) {
|
||||
logEvent("$update", new String[]{"version", versionName,
|
||||
"userAgeInSeconds", String.valueOf((System.currentTimeMillis() - installTime) / 1000)});
|
||||
// Also collect device info on update.
|
||||
SystemInfo.getDeviceInfoAsync(context);
|
||||
prefs.edit().putLong(PREF_APP_UPDATE_TIME, updateTime).apply();
|
||||
} else {
|
||||
logEvent("$launch");
|
||||
}
|
||||
}
|
||||
|
||||
native static public void logEvent(String eventName);
|
||||
|
||||
native static public void logEvent(String eventName, String eventValue);
|
||||
|
||||
// eventDictionary is a key,value,key,value array.
|
||||
native static public void logEvent(String eventName, String[] eventDictionary);
|
||||
|
||||
static public void logEvent(String eventName, Map<String, String> eventDictionary) {
|
||||
// For faster native processing pass array of strings instead of a map.
|
||||
final String[] array = new String[eventDictionary.size() * 2];
|
||||
int index = 0;
|
||||
for (final Map.Entry<String, String> entry : eventDictionary.entrySet()) {
|
||||
array[index++] = entry.getKey();
|
||||
array[index++] = entry.getValue();
|
||||
}
|
||||
logEvent(eventName, array);
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/a/7929810
|
||||
private static String uniqueID = null;
|
||||
// Shared with other statistics modules.
|
||||
public static final String PREF_FILE = "ALOHALYTICS";
|
||||
private static final String PREF_UNIQUE_ID = "UNIQUE_ID";
|
||||
//private static final String PREF_APP_VERSION = "APP_VERSION";
|
||||
private static final String PREF_APP_UPDATE_TIME = "APP_UPDATE_TIME";
|
||||
|
||||
// Returns id and true if id was generated or false if id was read from preferences.
|
||||
// Please note, that only the very first call to getInstallationId can tell the truth.
|
||||
public synchronized static Pair<String, Boolean> getInstallationId(final Context context) {
|
||||
if (uniqueID == null) {
|
||||
final SharedPreferences sharedPrefs = context.getSharedPreferences(
|
||||
PREF_FILE, Context.MODE_PRIVATE);
|
||||
uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);
|
||||
if (uniqueID == null) {
|
||||
// "A:" means Android. It will be different for other platforms, for convenience debugging.
|
||||
uniqueID = "A:" + UUID.randomUUID().toString();
|
||||
final SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||
editor.putString(PREF_UNIQUE_ID, uniqueID);
|
||||
editor.apply();
|
||||
return Pair.create(uniqueID, true);
|
||||
}
|
||||
}
|
||||
return Pair.create(uniqueID, false);
|
||||
}
|
||||
|
||||
// Initialize internal C++ statistics engine.
|
||||
private native static void setupCPP(final Class httpTransportClass,
|
||||
final String serverUrl,
|
||||
final String storagePath,
|
||||
final String installationId);
|
||||
|
||||
private native static void debugCPP(boolean enable);
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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 org.alohalytics;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.google.android.gms.ads.identifier.AdvertisingIdClient;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class SystemInfo {
|
||||
private static final String TAG = "Alohalytics.SystemInfo";
|
||||
|
||||
private static void handleException(Exception ex) {
|
||||
if (Statistics.debugMode()) {
|
||||
if (ex.getMessage() != null) {
|
||||
Log.w(TAG, ex.getMessage());
|
||||
}
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void getDeviceInfoAsync(final Context context) {
|
||||
// Collect all information on a separate thread, because:
|
||||
// - Google Advertising ID should be requested in a separate thread.
|
||||
// - Do not block UI thread while querying many properties.
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
collectIds(context);
|
||||
collectDeviceDetails(context);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
// Used for convenient null-checks.
|
||||
private static class KeyValueWrapper {
|
||||
public HashMap<String, String> mPairs = new HashMap<>();
|
||||
|
||||
public void put(String key, String value) {
|
||||
if (key != null && value != null) {
|
||||
mPairs.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void put(String key, float value) {
|
||||
if (key != null) {
|
||||
mPairs.put(key, String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
public void put(String key, boolean value) {
|
||||
if (key != null) {
|
||||
mPairs.put(key, String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
public void put(String key, int value) {
|
||||
if (key != null) {
|
||||
mPairs.put(key, String.valueOf(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectIds(final Context context) {
|
||||
|
||||
final KeyValueWrapper ids = new KeyValueWrapper();
|
||||
// Retrieve GoogleAdvertisingId.
|
||||
// See sample code at http://developer.android.com/google/play-services/id.html
|
||||
try {
|
||||
ids.put("google_advertising_id", AdvertisingIdClient.getAdvertisingIdInfo(context.getApplicationContext()).getId());
|
||||
} catch (Exception ex) {
|
||||
handleException(ex);
|
||||
}
|
||||
|
||||
try {
|
||||
final String android_id = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
// This is a known bug workaround - https://code.google.com/p/android/issues/detail?id=10603
|
||||
if (!android_id.equals("9774d56d682e549c")) {
|
||||
ids.put("android_id", android_id);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
handleException(ex);
|
||||
}
|
||||
|
||||
try {
|
||||
// This code works only if the app has READ_PHONE_STATE permission.
|
||||
final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
ids.put("device_id", tm.getDeviceId());
|
||||
ids.put("sim_serial_number", tm.getSimSerialNumber());
|
||||
} catch (Exception ex) {
|
||||
handleException(ex);
|
||||
}
|
||||
|
||||
Statistics.logEvent("$androidIds", ids.mPairs);
|
||||
}
|
||||
|
||||
private static void collectDeviceDetails(Context context) {
|
||||
|
||||
final KeyValueWrapper kvs = new KeyValueWrapper();
|
||||
|
||||
final WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
|
||||
if (wm != null) {
|
||||
final DisplayMetrics metrics = new DisplayMetrics();
|
||||
wm.getDefaultDisplay().getMetrics(metrics);
|
||||
kvs.put("display_density", metrics.density);
|
||||
kvs.put("display_density_dpi", metrics.densityDpi);
|
||||
kvs.put("display_scaled_density", metrics.scaledDensity);
|
||||
kvs.put("display_width_pixels", metrics.widthPixels);
|
||||
kvs.put("display_height_pixels", metrics.heightPixels);
|
||||
kvs.put("display_xdpi", metrics.xdpi);
|
||||
kvs.put("display_ydpi", metrics.ydpi);
|
||||
}
|
||||
|
||||
final Configuration config = context.getResources().getConfiguration();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
kvs.put("dpi", config.densityDpi); // int value
|
||||
}
|
||||
|
||||
kvs.put("font_scale", config.fontScale);
|
||||
kvs.put("locale_country", config.locale.getCountry());
|
||||
kvs.put("locale_language", config.locale.getLanguage());
|
||||
kvs.put("locale_variant", config.locale.getVariant());
|
||||
|
||||
kvs.put("mcc", config.mcc);
|
||||
kvs.put("mnc", config.mnc == Configuration.MNC_ZERO ? 0 : config.mnc);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
|
||||
kvs.put("screen_width_dp", config.screenWidthDp);
|
||||
kvs.put("screen_height_dp", config.screenHeightDp);
|
||||
}
|
||||
|
||||
final ContentResolver cr = context.getContentResolver();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
kvs.put(Settings.Global.AIRPLANE_MODE_ON, Settings.Global.getString(cr, Settings.Global.AIRPLANE_MODE_ON)); // 1 or 0
|
||||
kvs.put(Settings.Global.ALWAYS_FINISH_ACTIVITIES, Settings.Global.getString(cr, Settings.Global.ALWAYS_FINISH_ACTIVITIES)); // 1 or 0
|
||||
kvs.put(Settings.Global.AUTO_TIME, Settings.Global.getString(cr, Settings.Global.AUTO_TIME)); // 1 or 0
|
||||
kvs.put(Settings.Global.AUTO_TIME_ZONE, Settings.Global.getString(cr, Settings.Global.AUTO_TIME_ZONE)); // 1 or 0
|
||||
kvs.put(Settings.Global.BLUETOOTH_ON, Settings.Global.getString(cr, Settings.Global.BLUETOOTH_ON)); // 1 or 0
|
||||
kvs.put(Settings.Global.DATA_ROAMING, Settings.Global.getString(cr, Settings.Global.DATA_ROAMING)); // 1 or 0
|
||||
kvs.put(Settings.Global.HTTP_PROXY, Settings.Global.getString(cr, Settings.Global.HTTP_PROXY)); // host:port
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
kvs.put(Settings.System.AUTO_TIME_ZONE, Settings.System.getString(cr, Settings.System.AUTO_TIME_ZONE));
|
||||
} else {
|
||||
kvs.put(Settings.System.AIRPLANE_MODE_ON, Settings.System.getString(cr, Settings.System.AIRPLANE_MODE_ON));
|
||||
kvs.put(Settings.System.ALWAYS_FINISH_ACTIVITIES, Settings.System.getString(cr, Settings.System.ALWAYS_FINISH_ACTIVITIES));
|
||||
kvs.put(Settings.System.AUTO_TIME, Settings.System.getString(cr, Settings.System.AUTO_TIME));
|
||||
kvs.put(Settings.Secure.BLUETOOTH_ON, Settings.Secure.getString(cr, Settings.Secure.BLUETOOTH_ON));
|
||||
kvs.put(Settings.Secure.DATA_ROAMING, Settings.Secure.getString(cr, Settings.Secure.DATA_ROAMING));
|
||||
kvs.put(Settings.Secure.HTTP_PROXY, Settings.Secure.getString(cr, Settings.Secure.HTTP_PROXY));
|
||||
}
|
||||
|
||||
kvs.put(Settings.Secure.ACCESSIBILITY_ENABLED, Settings.Secure.getString(cr, Settings.Secure.ACCESSIBILITY_ENABLED)); // 1 or 0
|
||||
kvs.put(Settings.Secure.INSTALL_NON_MARKET_APPS, Settings.Secure.getString(cr, Settings.Secure.INSTALL_NON_MARKET_APPS)); // 1 or 0
|
||||
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
|
||||
kvs.put(Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED, Settings.Secure.getString(cr, Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED)); // 1 or 0
|
||||
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
|
||||
kvs.put(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, Settings.Global.getString(cr, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED));
|
||||
}
|
||||
|
||||
kvs.put(Settings.System.DATE_FORMAT, Settings.System.getString(cr, Settings.System.DATE_FORMAT)); // dd/mm/yyyy
|
||||
kvs.put(Settings.System.SCREEN_OFF_TIMEOUT, Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT)); // milliseconds
|
||||
kvs.put(Settings.System.TIME_12_24, Settings.System.getString(cr, Settings.System.TIME_12_24)); // 12 or 24
|
||||
|
||||
kvs.put(Settings.Secure.ALLOW_MOCK_LOCATION, Settings.Secure.getString(cr, Settings.Secure.ALLOW_MOCK_LOCATION)); // 1 or 0
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
kvs.put(Settings.Secure.LOCATION_MODE, Settings.Secure.getString(cr, Settings.Secure.LOCATION_MODE)); // Int values 0 - 3
|
||||
}
|
||||
|
||||
// Most build params are never changed, others are changed only after firmware upgrade.
|
||||
kvs.put("build_version_sdk", Build.VERSION.SDK_INT);
|
||||
kvs.put("build_brand", Build.BRAND);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
for (int i = 0; i < Build.SUPPORTED_ABIS.length; ++i)
|
||||
kvs.put("build_cpu_abi" + (i + 1), Build.SUPPORTED_ABIS[i]);
|
||||
} else {
|
||||
kvs.put("build_cpu_abi1", Build.CPU_ABI);
|
||||
kvs.put("build_cpu_abi2", Build.CPU_ABI2);
|
||||
}
|
||||
kvs.put("build_device", Build.DEVICE);
|
||||
kvs.put("build_display", Build.DISPLAY);
|
||||
kvs.put("build_fingerprint", Build.FINGERPRINT);
|
||||
kvs.put("build_hardware", Build.HARDWARE);
|
||||
kvs.put("build_host", Build.HOST);
|
||||
kvs.put("build_id", Build.ID);
|
||||
kvs.put("build_manufacturer", Build.MANUFACTURER);
|
||||
kvs.put("build_model", Build.MODEL);
|
||||
kvs.put("build_product", Build.PRODUCT);
|
||||
kvs.put("build_serial", Build.SERIAL);
|
||||
kvs.put("build_tags", Build.TAGS);
|
||||
kvs.put("build_time", Build.TIME);
|
||||
kvs.put("build_type", Build.TYPE);
|
||||
kvs.put("build_user", Build.USER);
|
||||
|
||||
Statistics.logEvent("$androidDeviceInfo", kvs.mPairs);
|
||||
}
|
||||
|
||||
}
|
310
3party/Alohalytics/src/android/jni/jni_alohalytics.cc
Normal file
310
3party/Alohalytics/src/android/jni/jni_alohalytics.cc
Normal file
|
@ -0,0 +1,310 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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.
|
||||
*******************************************************************************/
|
||||
|
||||
#include <jni.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include "../../alohalytics.h"
|
||||
#include "../../http_client.h"
|
||||
#include "../../logger.h"
|
||||
|
||||
using std::string;
|
||||
using std::unique_ptr;
|
||||
|
||||
using namespace alohalytics;
|
||||
|
||||
// Implemented in jni_main.cc, you can use your own impl if necessary.
|
||||
extern JavaVM* GetJVM();
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename POINTER, typename DELETER>
|
||||
unique_ptr<POINTER, DELETER> MakePointerScopeGuard(POINTER* x, DELETER t) {
|
||||
return unique_ptr<POINTER, DELETER>(x, t);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
class ScopeGuard final {
|
||||
F f_;
|
||||
ScopeGuard(const ScopeGuard&) = delete;
|
||||
void operator=(const ScopeGuard&) = delete;
|
||||
|
||||
public:
|
||||
explicit ScopeGuard(const F& f) : f_(f) {}
|
||||
ScopeGuard(ScopeGuard&& other) : f_(std::forward<F>(other.f_)) {}
|
||||
~ScopeGuard() { f_(); }
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
ScopeGuard<F> MakeScopeGuard(F f) {
|
||||
return ScopeGuard<F>(f);
|
||||
}
|
||||
|
||||
// Cached class and methods for faster access from native code
|
||||
static jclass g_httpTransportClass = 0;
|
||||
static jmethodID g_httpTransportClass_run = 0;
|
||||
static jclass g_httpParamsClass = 0;
|
||||
static jmethodID g_httpParamsConstructor = 0;
|
||||
|
||||
// JNI helper, returns empty string if str == 0.
|
||||
string ToStdString(JNIEnv* env, jstring str) {
|
||||
string result;
|
||||
if (str) {
|
||||
char const* utfBuffer = env->GetStringUTFChars(str, 0);
|
||||
if (utfBuffer) {
|
||||
result = utfBuffer;
|
||||
env->ReleaseStringUTFChars(str, utfBuffer);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_alohalytics_Statistics_logEvent__Ljava_lang_String_2(JNIEnv* env, jclass, jstring eventName) {
|
||||
LogEvent(ToStdString(env, eventName));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_alohalytics_Statistics_logEvent__Ljava_lang_String_2Ljava_lang_String_2(
|
||||
JNIEnv* env, jclass, jstring eventName, jstring eventValue) {
|
||||
LogEvent(ToStdString(env, eventName), ToStdString(env, eventValue));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_alohalytics_Statistics_logEvent__Ljava_lang_String_2_3Ljava_lang_String_2(
|
||||
JNIEnv* env, jclass, jstring eventName, jobjectArray keyValuePairs) {
|
||||
const jsize count = env->GetArrayLength(keyValuePairs);
|
||||
TStringMap map;
|
||||
string key;
|
||||
for (jsize i = 0; i < count; ++i) {
|
||||
const jstring jni_string = static_cast<jstring>(env->GetObjectArrayElement(keyValuePairs, i));
|
||||
if ((i + 1) % 2) {
|
||||
key = ToStdString(env, jni_string);
|
||||
map[key] = "";
|
||||
} else {
|
||||
map[key] = ToStdString(env, jni_string);
|
||||
}
|
||||
if (jni_string) env->DeleteLocalRef(jni_string);
|
||||
}
|
||||
LogEvent(ToStdString(env, eventName), map);
|
||||
}
|
||||
|
||||
#define CLEAR_AND_RETURN_FALSE_ON_EXCEPTION \
|
||||
if (env->ExceptionCheck()) { \
|
||||
env->ExceptionDescribe(); \
|
||||
env->ExceptionClear(); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define RETURN_ON_EXCEPTION \
|
||||
if (env->ExceptionCheck()) { \
|
||||
env->ExceptionDescribe(); \
|
||||
return; \
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_alohalytics_Statistics_setupCPP(JNIEnv* env,
|
||||
jclass,
|
||||
jclass httpTransportClass,
|
||||
jstring serverUrl,
|
||||
jstring storagePath,
|
||||
jstring installationId) {
|
||||
g_httpTransportClass = static_cast<jclass>(env->NewGlobalRef(httpTransportClass));
|
||||
RETURN_ON_EXCEPTION
|
||||
g_httpTransportClass_run =
|
||||
env->GetStaticMethodID(g_httpTransportClass,
|
||||
"run",
|
||||
"(Lorg/alohalytics/HttpTransport$Params;)Lorg/alohalytics/HttpTransport$Params;");
|
||||
RETURN_ON_EXCEPTION
|
||||
g_httpParamsClass = env->FindClass("org/alohalytics/HttpTransport$Params");
|
||||
RETURN_ON_EXCEPTION
|
||||
g_httpParamsClass = static_cast<jclass>(env->NewGlobalRef(g_httpParamsClass));
|
||||
RETURN_ON_EXCEPTION
|
||||
g_httpParamsConstructor = env->GetMethodID(g_httpParamsClass, "<init>", "(Ljava/lang/String;)V");
|
||||
RETURN_ON_EXCEPTION
|
||||
|
||||
// Initialize statistics engine at the end of setup function, as it can use globals above.
|
||||
Stats::Instance()
|
||||
.SetClientId(ToStdString(env, installationId))
|
||||
.SetStoragePath(ToStdString(env, storagePath))
|
||||
.SetServerUrl(ToStdString(env, serverUrl));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_alohalytics_Statistics_debugCPP(JNIEnv* env, jclass, jboolean enableDebug) {
|
||||
Stats::Instance().SetDebugMode(enableDebug);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_alohalytics_Statistics_forceUpload(JNIEnv* env, jclass) {
|
||||
Stats::Instance().Upload();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
//***********************************************************************
|
||||
// Exported functions implementation
|
||||
//***********************************************************************
|
||||
namespace alohalytics {
|
||||
|
||||
bool HTTPClientPlatformWrapper::RunHTTPRequest() {
|
||||
// Attaching multiple times from the same thread is a no-op, which only gets good env for us.
|
||||
JNIEnv* env;
|
||||
if (JNI_OK !=
|
||||
::GetJVM()->AttachCurrentThread(
|
||||
#ifdef ANDROID
|
||||
&env,
|
||||
#else
|
||||
// Non-Android JAVA requires void** here.
|
||||
reinterpret_cast<void**>(&env),
|
||||
#endif
|
||||
nullptr)) {
|
||||
ALOG("ERROR while trying to attach JNI thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(AlexZ): May need to refactor if this method will be agressively used from the same thread.
|
||||
const auto detachThreadOnScopeExit = MakeScopeGuard([] { ::GetJVM()->DetachCurrentThread(); });
|
||||
|
||||
// Convenience lambda.
|
||||
const auto deleteLocalRef = [&env](jobject o) { env->DeleteLocalRef(o); };
|
||||
|
||||
// Create and fill request params.
|
||||
const auto jniUrl = MakePointerScopeGuard(env->NewStringUTF(url_requested_.c_str()), deleteLocalRef);
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
|
||||
const auto httpParamsObject = MakePointerScopeGuard(
|
||||
env->NewObject(g_httpParamsClass, g_httpParamsConstructor, jniUrl.get()), deleteLocalRef);
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
|
||||
// Cache it on the first call.
|
||||
const static jfieldID dataField = env->GetFieldID(g_httpParamsClass, "data", "[B");
|
||||
if (!post_body_.empty()) {
|
||||
const auto jniPostData = MakePointerScopeGuard(env->NewByteArray(post_body_.size()), deleteLocalRef);
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
|
||||
env->SetByteArrayRegion(
|
||||
jniPostData.get(), 0, post_body_.size(), reinterpret_cast<const jbyte*>(post_body_.data()));
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
|
||||
env->SetObjectField(httpParamsObject.get(), dataField, jniPostData.get());
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
}
|
||||
|
||||
const static jfieldID contentTypeField =
|
||||
env->GetFieldID(g_httpParamsClass, "contentType", "Ljava/lang/String;");
|
||||
if (!content_type_.empty()) {
|
||||
const auto jniContentType = MakePointerScopeGuard(env->NewStringUTF(content_type_.c_str()), deleteLocalRef);
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
|
||||
env->SetObjectField(httpParamsObject.get(), contentTypeField, jniContentType.get());
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
}
|
||||
|
||||
if (!user_agent_.empty()) {
|
||||
const static jfieldID userAgentField =
|
||||
env->GetFieldID(g_httpParamsClass, "userAgent", "Ljava/lang/String;");
|
||||
|
||||
const auto jniUserAgent = MakePointerScopeGuard(env->NewStringUTF(user_agent_.c_str()), deleteLocalRef);
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
|
||||
env->SetObjectField(httpParamsObject.get(), userAgentField, jniUserAgent.get());
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
}
|
||||
|
||||
if (!post_file_.empty()) {
|
||||
const static jfieldID inputFilePathField =
|
||||
env->GetFieldID(g_httpParamsClass, "inputFilePath", "Ljava/lang/String;");
|
||||
|
||||
const auto jniInputFilePath = MakePointerScopeGuard(env->NewStringUTF(post_file_.c_str()), deleteLocalRef);
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
|
||||
env->SetObjectField(httpParamsObject.get(), inputFilePathField, jniInputFilePath.get());
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
}
|
||||
|
||||
if (!received_file_.empty()) {
|
||||
const static jfieldID outputFilePathField =
|
||||
env->GetFieldID(g_httpParamsClass, "outputFilePath", "Ljava/lang/String;");
|
||||
|
||||
const auto jniOutputFilePath =
|
||||
MakePointerScopeGuard(env->NewStringUTF(received_file_.c_str()), deleteLocalRef);
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
|
||||
env->SetObjectField(httpParamsObject.get(), outputFilePathField, jniOutputFilePath.get());
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
}
|
||||
|
||||
const static jfieldID debugModeField =
|
||||
env->GetFieldID(g_httpParamsClass, "debugMode", "Z");
|
||||
env->SetBooleanField(httpParamsObject.get(), debugModeField, debug_mode_);
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
|
||||
// DO ALL MAGIC!
|
||||
// Current Java implementation simply reuses input params instance, so we don't need to
|
||||
// DeleteLocalRef(response).
|
||||
const jobject response =
|
||||
env->CallStaticObjectMethod(g_httpTransportClass, g_httpTransportClass_run, httpParamsObject.get());
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
// TODO(AlexZ): think about rethrowing corresponding C++ exceptions.
|
||||
env->ExceptionClear();
|
||||
return false;
|
||||
}
|
||||
|
||||
const static jfieldID httpResponseCodeField = env->GetFieldID(g_httpParamsClass, "httpResponseCode", "I");
|
||||
error_code_ = env->GetIntField(response, httpResponseCodeField);
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
|
||||
const static jfieldID receivedUrlField =
|
||||
env->GetFieldID(g_httpParamsClass, "receivedUrl", "Ljava/lang/String;");
|
||||
const auto jniReceivedUrl = MakePointerScopeGuard(
|
||||
static_cast<jstring>(env->GetObjectField(response, receivedUrlField)), deleteLocalRef);
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
if (jniReceivedUrl) {
|
||||
url_received_ = std::move(ToStdString(env, jniReceivedUrl.get()));
|
||||
}
|
||||
|
||||
// contentTypeField is already cached above.
|
||||
const auto jniContentType = MakePointerScopeGuard(
|
||||
static_cast<jstring>(env->GetObjectField(response, contentTypeField)), deleteLocalRef);
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
if (jniContentType) {
|
||||
content_type_ = std::move(ToStdString(env, jniContentType.get()));
|
||||
}
|
||||
|
||||
// dataField is already cached above.
|
||||
const auto jniData =
|
||||
MakePointerScopeGuard(static_cast<jbyteArray>(env->GetObjectField(response, dataField)), deleteLocalRef);
|
||||
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
|
||||
if (jniData) {
|
||||
jbyte* buffer = env->GetByteArrayElements(jniData.get(), nullptr);
|
||||
if (buffer) {
|
||||
server_response_.assign(reinterpret_cast<const char*>(buffer), env->GetArrayLength(jniData.get()));
|
||||
env->ReleaseByteArrayElements(jniData.get(), buffer, JNI_ABORT);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace alohalytics
|
43
3party/Alohalytics/src/android/jni/jni_main.cc
Normal file
43
3party/Alohalytics/src/android/jni/jni_main.cc
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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.
|
||||
*******************************************************************************/
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
namespace {
|
||||
|
||||
static JavaVM* g_jvm = 0;
|
||||
|
||||
} // namespace
|
||||
|
||||
// Exported for access from C++ code.
|
||||
// If you have your own JNI_OnLoad then you can skip this source and implement your own GetJVM()
|
||||
extern JavaVM* GetJVM() { return g_jvm; }
|
||||
|
||||
extern "C" {
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
|
||||
g_jvm = vm;
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
} // extern "C"
|
107
3party/Alohalytics/src/apple/http_client_apple.mm
Normal file
107
3party/Alohalytics/src/apple/http_client_apple.mm
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
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.
|
||||
*******************************************************************************/
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag
|
||||
#endif
|
||||
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSURL.h>
|
||||
#import <Foundation/NSData.h>
|
||||
#import <Foundation/NSStream.h>
|
||||
#import <Foundation/NSURLRequest.h>
|
||||
#import <Foundation/NSURLResponse.h>
|
||||
#import <Foundation/NSURLConnection.h>
|
||||
#import <Foundation/NSError.h>
|
||||
#import <Foundation/NSFileManager.h>
|
||||
|
||||
#include "../http_client.h"
|
||||
#include "../logger.h"
|
||||
#include "../gzip_wrapper.h"
|
||||
|
||||
#define TIMEOUT_IN_SECONDS 30.0
|
||||
|
||||
namespace alohalytics {
|
||||
|
||||
bool HTTPClientPlatformWrapper::RunHTTPRequest() {
|
||||
@autoreleasepool {
|
||||
|
||||
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:
|
||||
[NSURL URLWithString:[NSString stringWithUTF8String:url_requested_.c_str()]]
|
||||
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:TIMEOUT_IN_SECONDS];
|
||||
|
||||
if (!content_type_.empty())
|
||||
[request setValue:[NSString stringWithUTF8String:content_type_.c_str()] forHTTPHeaderField:@"Content-Type"];
|
||||
if (!user_agent_.empty())
|
||||
[request setValue:[NSString stringWithUTF8String:user_agent_.c_str()] forHTTPHeaderField:@"User-Agent"];
|
||||
|
||||
if (!post_body_.empty()) {
|
||||
// TODO(AlexZ): Compress data in file queue impl, before calling this method, to use less disk space in offline.
|
||||
const std::string compressed = Gzip(post_body_);
|
||||
request.HTTPBody = [NSData dataWithBytes:compressed.data() length:compressed.size()];
|
||||
[request setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
|
||||
request.HTTPMethod = @"POST";
|
||||
} else if (!post_file_.empty()) {
|
||||
NSError * err = nil;
|
||||
NSString * path = [NSString stringWithUTF8String:post_file_.c_str()];
|
||||
const unsigned long long file_size = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&err].fileSize;
|
||||
if (err) {
|
||||
error_code_ = err.code;
|
||||
if (debug_mode_) {
|
||||
ALOG("ERROR", error_code_, [err.localizedDescription UTF8String]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
request.HTTPBodyStream = [NSInputStream inputStreamWithFileAtPath:path];
|
||||
request.HTTPMethod = @"POST";
|
||||
[request setValue:[NSString stringWithFormat:@"%llu", file_size] forHTTPHeaderField:@"Content-Length"];
|
||||
}
|
||||
|
||||
NSHTTPURLResponse * response = nil;
|
||||
NSError * err = nil;
|
||||
NSData * url_data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&err];
|
||||
|
||||
if (response) {
|
||||
error_code_ = response.statusCode;
|
||||
url_received_ = [response.URL.absoluteString UTF8String];
|
||||
if (url_data) {
|
||||
if (received_file_.empty()) {
|
||||
server_response_.assign(reinterpret_cast<char const *>(url_data.bytes), url_data.length);
|
||||
}
|
||||
else {
|
||||
[url_data writeToFile:[NSString stringWithUTF8String:received_file_.c_str()] atomically:YES];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Request has failed if we are here.
|
||||
error_code_ = err.code;
|
||||
if (debug_mode_) {
|
||||
ALOG("ERROR", error_code_, ':', [err.localizedDescription UTF8String], "while connecting to", url_requested_);
|
||||
}
|
||||
return false;
|
||||
} // @autoreleasepool
|
||||
}
|
||||
|
||||
} // namespace alohalytics
|
24
3party/Alohalytics/src/cereal/LICENSE
Normal file
24
3party/Alohalytics/src/cereal/LICENSE
Normal file
|
@ -0,0 +1,24 @@
|
|||
Copyright (c) 2014, Randolph Voorhies, Shane Grant
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of cereal nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
4
3party/Alohalytics/src/cereal/README.cxa_demangle.cpp.md
Normal file
4
3party/Alohalytics/src/cereal/README.cxa_demangle.cpp.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
cxa_demangle.cpp was copied here to avoid compilation bug on Android, when
|
||||
compiler can't detect abi::__cxa_demangle() function from cxxabi.h include file.
|
||||
|
||||
The patch was applied to include/details.util.hpp by Alex Zolotarev
|
16
3party/Alohalytics/src/cereal/README.md
Normal file
16
3party/Alohalytics/src/cereal/README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
Header-only Cereal with no dependencies and no extra ```-I``` flags required.
|
||||
|
||||
See [examples/cereal/message.h](https://github.com/KnowSheet/3party/blob/master/examples/cereal/message.h) for example usage.
|
||||
|
||||
Ported by Dmitry "Dima" Korolev.
|
||||
|
||||
On Linux, it took the below commands. (MacOS has different ```sed -i``` setting, tweak by changing ```-i``` into ```-i ''```).
|
||||
|
||||
```
|
||||
wget https://github.com/USCiLab/cereal/archive/v1.0.0.tar.gz
|
||||
(tar xzf cereal-1.0.0.tar.gz; cp -r cereal-1.0.0/include/cereal include; rm -rf cereal-1.0.0)
|
||||
find include/ -mindepth 1 -maxdepth 1 -type f | xargs sed -i "s/^#include <cereal\/\(.*\)>$/#include \".\/\1\"/"
|
||||
find include/ -mindepth 2 -maxdepth 2 -type f | xargs sed -i "s/^#include <cereal\/\(.*\)>$/#include \"..\/\1\"/"
|
||||
find include/ -mindepth 3 -maxdepth 3 -type f | xargs sed -i "s/^#include <cereal\/\(.*\)>$/#include \"..\/..\/\1\"/"
|
||||
find include/ -mindepth 4 -maxdepth 4 -type f | xargs sed -i "s/^#include <cereal\/\(.*\)>$/#include \"..\/..\/..\/\1\"/"
|
||||
```
|
4965
3party/Alohalytics/src/cereal/cxa_demangle.cpp
Normal file
4965
3party/Alohalytics/src/cereal/cxa_demangle.cpp
Normal file
File diff suppressed because it is too large
Load diff
419
3party/Alohalytics/src/cereal/include/access.hpp
Normal file
419
3party/Alohalytics/src/cereal/include/access.hpp
Normal file
|
@ -0,0 +1,419 @@
|
|||
/*! \file access.hpp
|
||||
\brief Access control, default construction, and serialization disambiguation */
|
||||
/*
|
||||
Copyright (c) 2014, Randolph Voorhies, Shane Grant
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of cereal nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef CEREAL_ACCESS_HPP_
|
||||
#define CEREAL_ACCESS_HPP_
|
||||
|
||||
#include <type_traits>
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
#include "./details/helpers.hpp"
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
// ######################################################################
|
||||
//! A class that allows cereal to load smart pointers to types that have no default constructor
|
||||
/*! If your class does not have a default constructor, cereal will not be able
|
||||
to load any smart pointers to it unless you overload LoadAndConstruct
|
||||
for your class, and provide an appropriate load_and_construct method. You can also
|
||||
choose to define a member static function instead of specializing this class.
|
||||
|
||||
The specialization of LoadAndConstruct must be placed within the cereal namespace:
|
||||
|
||||
@code{.cpp}
|
||||
struct MyType
|
||||
{
|
||||
MyType( int x ); // note: no default ctor
|
||||
int myX;
|
||||
|
||||
// Define a serialize or load/save pair as you normally would
|
||||
template <class Archive>
|
||||
void serialize( Archive & ar )
|
||||
{
|
||||
ar( myX );
|
||||
}
|
||||
};
|
||||
|
||||
// Provide a specialization for LoadAndConstruct for your type
|
||||
namespace cereal
|
||||
{
|
||||
template <> struct LoadAndConstruct<MyType>
|
||||
{
|
||||
// load_and_construct will be passed the archive that you will be loading
|
||||
// from as well as a construct object which you can use as if it were the
|
||||
// constructor for your type. cereal will handle all memory management for you.
|
||||
template <class Archive>
|
||||
static void load_and_construct( Archive & ar, cereal::construct<MyType> & construct )
|
||||
{
|
||||
int x;
|
||||
ar( x );
|
||||
construct( x );
|
||||
}
|
||||
};
|
||||
} // end namespace cereal
|
||||
@endcode
|
||||
|
||||
Please note that just as in using external serialization functions, you cannot get
|
||||
access to non-public members of your class by befriending cereal::access. If you
|
||||
have the ability to modify the class you wish to serialize, it is recommended that you
|
||||
use member serialize functions and a static member load_and_construct function.
|
||||
|
||||
@tparam T The type to specialize for
|
||||
@ingroup Access */
|
||||
template <class T>
|
||||
struct LoadAndConstruct
|
||||
{
|
||||
//! Called by cereal if no default constructor exists to load and construct data simultaneously
|
||||
/*! Overloads of this should return a pointer to T and expect an archive as a parameter */
|
||||
static std::false_type load_and_construct(...)
|
||||
{ return std::false_type(); }
|
||||
};
|
||||
|
||||
// forward decl for construct
|
||||
//! @cond PRIVATE_NEVERDEFINED
|
||||
namespace memory_detail{ template <class Ar, class T> struct LoadAndConstructLoadWrapper; }
|
||||
//! @endcond
|
||||
|
||||
//! Used to construct types with no default constructor
|
||||
/*! When serializing a type that has no default constructor, cereal
|
||||
will attempt to call either the class static function load_and_construct
|
||||
or the appropriate template specialization of LoadAndConstruct. cereal
|
||||
will pass that function a reference to the archive as well as a reference
|
||||
to a construct object which should be used to perform the allocation once
|
||||
data has been appropriately loaded.
|
||||
|
||||
@code{.cpp}
|
||||
struct MyType
|
||||
{
|
||||
// note the lack of default constructor
|
||||
MyType( int xx, int yy );
|
||||
|
||||
int x, y;
|
||||
double notInConstructor;
|
||||
|
||||
template <class Archive>
|
||||
void serialize( Archive & ar )
|
||||
{
|
||||
ar( x, y );
|
||||
ar( notInConstructor );
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
static void load_and_construct( Archive & ar, cereal::construct<MyType> & construct )
|
||||
{
|
||||
int x, y;
|
||||
ar( x, y );
|
||||
|
||||
// use construct object to initialize with loaded data
|
||||
construct( x, y );
|
||||
|
||||
// access to member variables and functions via -> operator
|
||||
ar( construct->notInConstructor );
|
||||
|
||||
// could also do the above section by:
|
||||
double z;
|
||||
ar( z );
|
||||
construct->notInConstructor = z;
|
||||
}
|
||||
};
|
||||
@endcode
|
||||
|
||||
@tparam T The class type being serialized
|
||||
*/
|
||||
template <class T>
|
||||
class construct
|
||||
{
|
||||
public:
|
||||
//! Construct and initialize the type T with the given arguments
|
||||
/*! This will forward all arguments to the underlying type T,
|
||||
calling an appropriate constructor.
|
||||
|
||||
Calling this function more than once will result in an exception
|
||||
being thrown.
|
||||
|
||||
@param args The arguments to the constructor for T
|
||||
@throw Exception If called more than once */
|
||||
template <class ... Args>
|
||||
void operator()( Args && ... args );
|
||||
// implementation deferred due to reliance on cereal::access
|
||||
|
||||
//! Get a reference to the initialized underlying object
|
||||
/*! This must be called after the object has been initialized.
|
||||
|
||||
@return A reference to the initialized object
|
||||
@throw Exception If called before initialization */
|
||||
T * operator->()
|
||||
{
|
||||
if( !itsValid )
|
||||
throw Exception("Object must be initialized prior to accessing members");
|
||||
|
||||
return itsPtr;
|
||||
}
|
||||
|
||||
//! Returns a raw pointer to the initialized underlying object
|
||||
/*! This is mainly intended for use with passing an instance of
|
||||
a constructed object to cereal::base_class.
|
||||
|
||||
It is strongly recommended to avoid using this function in
|
||||
any other circumstance.
|
||||
|
||||
@return A raw pointer to the initialized type */
|
||||
T * ptr()
|
||||
{
|
||||
return operator->();
|
||||
}
|
||||
|
||||
private:
|
||||
template <class A, class B> friend struct ::cereal::memory_detail::LoadAndConstructLoadWrapper;
|
||||
|
||||
construct( T * p ) : itsPtr( p ), itsValid( false ) {}
|
||||
construct( construct const & ) = delete;
|
||||
construct & operator=( construct const & ) = delete;
|
||||
|
||||
T * itsPtr;
|
||||
bool itsValid;
|
||||
};
|
||||
|
||||
// ######################################################################
|
||||
//! A class that can be made a friend to give cereal access to non public functions
|
||||
/*! If you desire non-public serialization functions within a class, cereal can only
|
||||
access these if you declare cereal::access a friend.
|
||||
|
||||
@code{.cpp}
|
||||
class MyClass
|
||||
{
|
||||
private:
|
||||
friend class cereal::access; // gives access to the private serialize
|
||||
|
||||
template <class Archive>
|
||||
void serialize( Archive & ar )
|
||||
{
|
||||
// some code
|
||||
}
|
||||
};
|
||||
@endcode
|
||||
@ingroup Access */
|
||||
class access
|
||||
{
|
||||
public:
|
||||
// ####### Standard Serialization ########################################
|
||||
template<class Archive, class T> inline
|
||||
static auto member_serialize(Archive & ar, T & t) -> decltype(t.serialize(ar))
|
||||
{ t.serialize(ar); }
|
||||
|
||||
template<class Archive, class T> inline
|
||||
static auto member_save(Archive & ar, T const & t) -> decltype(t.save(ar))
|
||||
{ t.save(ar); }
|
||||
|
||||
template<class Archive, class T> inline
|
||||
static auto member_save_non_const(Archive & ar, T & t) -> decltype(t.save(ar))
|
||||
{ t.save(ar); }
|
||||
|
||||
template<class Archive, class T> inline
|
||||
static auto member_load(Archive & ar, T & t) -> decltype(t.load(ar))
|
||||
{ t.load(ar); }
|
||||
|
||||
template<class Archive, class T> inline
|
||||
static auto member_save_minimal(Archive const & ar, T const & t) -> decltype(t.save_minimal(ar))
|
||||
{ return t.save_minimal(ar); }
|
||||
|
||||
template<class Archive, class T> inline
|
||||
static auto member_save_minimal_non_const(Archive const & ar, T & t) -> decltype(t.save_minimal(ar))
|
||||
{ return t.save_minimal(ar); }
|
||||
|
||||
template<class Archive, class T, class U> inline
|
||||
static auto member_load_minimal(Archive const & ar, T & t, U && u) -> decltype(t.load_minimal(ar, std::forward<U>(u)))
|
||||
{ t.load_minimal(ar, std::forward<U>(u)); }
|
||||
|
||||
// ####### Versioned Serialization #######################################
|
||||
template<class Archive, class T> inline
|
||||
static auto member_serialize(Archive & ar, T & t, const std::uint32_t version ) -> decltype(t.serialize(ar, version))
|
||||
{ t.serialize(ar, version); }
|
||||
|
||||
template<class Archive, class T> inline
|
||||
static auto member_save(Archive & ar, T const & t, const std::uint32_t version ) -> decltype(t.save(ar, version))
|
||||
{ t.save(ar, version); }
|
||||
|
||||
template<class Archive, class T> inline
|
||||
static auto member_save_non_const(Archive & ar, T & t, const std::uint32_t version ) -> decltype(t.save(ar, version))
|
||||
{ t.save(ar, version); }
|
||||
|
||||
template<class Archive, class T> inline
|
||||
static auto member_load(Archive & ar, T & t, const std::uint32_t version ) -> decltype(t.load(ar, version))
|
||||
{ t.load(ar, version); }
|
||||
|
||||
template<class Archive, class T> inline
|
||||
static auto member_save_minimal(Archive const & ar, T const & t, const std::uint32_t version) -> decltype(t.save_minimal(ar, version))
|
||||
{ return t.save_minimal(ar, version); }
|
||||
|
||||
template<class Archive, class T> inline
|
||||
static auto member_save_minimal_non_const(Archive const & ar, T & t, const std::uint32_t version) -> decltype(t.save_minimal(ar, version))
|
||||
{ return t.save_minimal(ar, version); }
|
||||
|
||||
template<class Archive, class T, class U> inline
|
||||
static auto member_load_minimal(Archive const & ar, T & t, U && u, const std::uint32_t version) -> decltype(t.load_minimal(ar, std::forward<U>(u), version))
|
||||
{ t.load_minimal(ar, std::forward<U>(u), version); }
|
||||
|
||||
// ####### Other Functionality ##########################################
|
||||
// for detecting inheritance from enable_shared_from_this
|
||||
template <class T> inline
|
||||
static auto shared_from_this(T & t) -> decltype(t.shared_from_this());
|
||||
|
||||
// for placement new
|
||||
template <class T, class ... Args> inline
|
||||
static void construct( T *& ptr, Args && ... args )
|
||||
{
|
||||
new (ptr) T( std::forward<Args>( args )... );
|
||||
}
|
||||
|
||||
template <class T> inline
|
||||
static std::false_type load_and_construct(...)
|
||||
{ return std::false_type(); }
|
||||
|
||||
template<class T, class Archive> inline
|
||||
static auto load_and_construct(Archive & ar, ::cereal::construct<T> & construct) -> decltype(T::load_and_construct(ar, construct))
|
||||
{
|
||||
T::load_and_construct( ar, construct );
|
||||
}
|
||||
}; // end class access
|
||||
|
||||
// ######################################################################
|
||||
//! A specifier used in conjunction with cereal::specialize to disambiguate
|
||||
//! serialization in special cases
|
||||
/*! @relates specialize
|
||||
@ingroup Access */
|
||||
enum class specialization
|
||||
{
|
||||
member_serialize, //!< Force the use of a member serialize function
|
||||
member_load_save, //!< Force the use of a member load/save pair
|
||||
member_load_save_minimal, //!< Force the use of a member minimal load/save pair
|
||||
non_member_serialize, //!< Force the use of a non-member serialize function
|
||||
non_member_load_save, //!< Force the use of a non-member load/save pair
|
||||
non_member_load_save_minimal //!< Force the use of a non-member minimal load/save pair
|
||||
};
|
||||
|
||||
//! A class used to disambiguate cases where cereal cannot detect a unique way of serializing a class
|
||||
/*! cereal attempts to figure out which method of serialization (member vs. non-member serialize
|
||||
or load/save pair) at compile time. If for some reason cereal cannot find a non-ambiguous way
|
||||
of serializing a type, it will produce a static assertion complaining about this.
|
||||
|
||||
This can happen because you have both a serialize and load/save pair, or even because a base
|
||||
class has a serialize (public or private with friend access) and a derived class does not
|
||||
overwrite this due to choosing some other serialization type.
|
||||
|
||||
Specializing this class will tell cereal to explicitly use the serialization type you specify
|
||||
and it will not complain about ambiguity in its compile time selection. However, if cereal detects
|
||||
an ambiguity in specializations, it will continue to issue a static assertion.
|
||||
|
||||
@code{.cpp}
|
||||
class MyParent
|
||||
{
|
||||
friend class cereal::access;
|
||||
template <class Archive>
|
||||
void serialize( Archive & ar ) {}
|
||||
};
|
||||
|
||||
// Although serialize is private in MyParent, to cereal::access it will look public,
|
||||
// even through MyDerived
|
||||
class MyDerived : public MyParent
|
||||
{
|
||||
public:
|
||||
template <class Archive>
|
||||
void load( Archive & ar ) {}
|
||||
|
||||
template <class Archive>
|
||||
void save( Archive & ar ) {}
|
||||
};
|
||||
|
||||
// The load/save pair in MyDerived is ambiguous because serialize in MyParent can
|
||||
// be accessed from cereal::access. This looks the same as making serialize public
|
||||
// in MyParent, making it seem as though MyDerived has both a serialize and a load/save pair.
|
||||
// cereal will complain about this at compile time unless we disambiguate:
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
// This struct specialization will tell cereal which is the right way to serialize the ambiguity
|
||||
template <class Archive> struct specialize<Archive, MyDerived, cereal::specialization::member_load_save> {};
|
||||
|
||||
// If we only had a disambiguation for a specific archive type, it would look something like this
|
||||
template <> struct specialize<cereal::BinaryOutputArchive, MyDerived, cereal::specialization::member_load_save> {};
|
||||
}
|
||||
@endcode
|
||||
|
||||
You can also choose to use the macros CEREAL_SPECIALIZE_FOR_ALL_ARCHIVES or
|
||||
CEREAL_SPECIALIZE_FOR_ARCHIVE if you want to type a little bit less.
|
||||
|
||||
@tparam T The type to specialize the serialization for
|
||||
@tparam S The specialization type to use for T
|
||||
@ingroup Access */
|
||||
template <class Archive, class T, specialization S>
|
||||
struct specialize : public std::false_type {};
|
||||
|
||||
//! Convenient macro for performing specialization for all archive types
|
||||
/*! This performs specialization for the specific type for all types of archives.
|
||||
This macro should be placed at the global namespace.
|
||||
|
||||
@code{cpp}
|
||||
struct MyType {};
|
||||
CEREAL_SPECIALIZE_FOR_ALL_ARCHIVES( MyType, cereal::specialization::member_load_save );
|
||||
@endcode
|
||||
|
||||
@relates specialize
|
||||
@ingroup Access */
|
||||
#define CEREAL_SPECIALIZE_FOR_ALL_ARCHIVES( Type, Specialization ) \
|
||||
namespace cereal { template <class Archive> struct specialize<Archive, Type, Specialization> {}; }
|
||||
|
||||
//! Convenient macro for performing specialization for a single archive type
|
||||
/*! This performs specialization for the specific type for a single type of archive.
|
||||
This macro should be placed at the global namespace.
|
||||
|
||||
@code{cpp}
|
||||
struct MyType {};
|
||||
CEREAL_SPECIALIZE_FOR_ALL_ARCHIVES( cereal::XMLInputArchive, MyType, cereal::specialization::member_load_save );
|
||||
@endcode
|
||||
|
||||
@relates specialize
|
||||
@ingroup Access */
|
||||
#define CEREAL_SPECIALIZE_FOR_ARCHIVE( Archive, Type, Specialization ) \
|
||||
namespace cereal { template <> struct specialize<Archive, Type, Specialization> {}; }
|
||||
|
||||
// ######################################################################
|
||||
// Deferred Implementation, see construct for more information
|
||||
template <class T> template <class ... Args> inline
|
||||
void construct<T>::operator()( Args && ... args )
|
||||
{
|
||||
if( itsValid )
|
||||
throw Exception("Attempting to construct an already initialized object");
|
||||
|
||||
::cereal::access::construct( itsPtr, std::forward<Args>( args )... );
|
||||
itsValid = true;
|
||||
}
|
||||
} // namespace cereal
|
||||
|
||||
#endif // CEREAL_ACCESS_HPP_
|
162
3party/Alohalytics/src/cereal/include/archives/binary.hpp
Normal file
162
3party/Alohalytics/src/cereal/include/archives/binary.hpp
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*! \file binary.hpp
|
||||
\brief Binary input and output archives */
|
||||
/*
|
||||
Copyright (c) 2014, Randolph Voorhies, Shane Grant
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of cereal nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef CEREAL_ARCHIVES_BINARY_HPP_
|
||||
#define CEREAL_ARCHIVES_BINARY_HPP_
|
||||
|
||||
#include "../cereal.hpp"
|
||||
#include <sstream>
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
// ######################################################################
|
||||
//! An output archive designed to save data in a compact binary representation
|
||||
/*! This archive outputs data to a stream in an extremely compact binary
|
||||
representation with as little extra metadata as possible.
|
||||
|
||||
This archive does nothing to ensure that the endianness of the saved
|
||||
and loaded data is the same. If you need to have portability over
|
||||
architectures with different endianness, use PortableBinaryOutputArchive.
|
||||
|
||||
When using a binary archive and a file stream, you must use the
|
||||
std::ios::binary format flag to avoid having your data altered
|
||||
inadvertently.
|
||||
|
||||
\ingroup Archives */
|
||||
class BinaryOutputArchive : public OutputArchive<BinaryOutputArchive, AllowEmptyClassElision>
|
||||
{
|
||||
public:
|
||||
//! Construct, outputting to the provided stream
|
||||
/*! @param stream The stream to output to. Can be a stringstream, a file stream, or
|
||||
even cout! */
|
||||
BinaryOutputArchive(std::ostream & stream) :
|
||||
OutputArchive<BinaryOutputArchive, AllowEmptyClassElision>(this),
|
||||
itsStream(stream)
|
||||
{ }
|
||||
|
||||
//! Writes size bytes of data to the output stream
|
||||
void saveBinary( const void * data, std::size_t size )
|
||||
{
|
||||
auto const writtenSize = static_cast<std::size_t>( itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ), size ) );
|
||||
|
||||
if(writtenSize != size)
|
||||
throw Exception("Failed to write " + std::to_string(size) + " bytes to output stream! Wrote " + std::to_string(writtenSize));
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream & itsStream;
|
||||
};
|
||||
|
||||
// ######################################################################
|
||||
//! An input archive designed to load data saved using BinaryOutputArchive
|
||||
/* This archive does nothing to ensure that the endianness of the saved
|
||||
and loaded data is the same. If you need to have portability over
|
||||
architectures with different endianness, use PortableBinaryOutputArchive.
|
||||
|
||||
When using a binary archive and a file stream, you must use the
|
||||
std::ios::binary format flag to avoid having your data altered
|
||||
inadvertently.
|
||||
|
||||
\ingroup Archives */
|
||||
class BinaryInputArchive : public InputArchive<BinaryInputArchive, AllowEmptyClassElision>
|
||||
{
|
||||
public:
|
||||
//! Construct, loading from the provided stream
|
||||
BinaryInputArchive(std::istream & stream) :
|
||||
InputArchive<BinaryInputArchive, AllowEmptyClassElision>(this),
|
||||
itsStream(stream)
|
||||
{ }
|
||||
|
||||
//! Reads size bytes of data from the input stream
|
||||
void loadBinary( void * const data, std::size_t size )
|
||||
{
|
||||
auto const readSize = static_cast<std::size_t>( itsStream.rdbuf()->sgetn( reinterpret_cast<char*>( data ), size ) );
|
||||
|
||||
if(readSize != size)
|
||||
throw Exception("Failed to read " + std::to_string(size) + " bytes from input stream! Read " + std::to_string(readSize));
|
||||
}
|
||||
|
||||
private:
|
||||
std::istream & itsStream;
|
||||
};
|
||||
|
||||
// ######################################################################
|
||||
// Common BinaryArchive serialization functions
|
||||
|
||||
//! Saving for POD types to binary
|
||||
template<class T> inline
|
||||
typename std::enable_if<std::is_arithmetic<T>::value, void>::type
|
||||
save(BinaryOutputArchive & ar, T const & t)
|
||||
{
|
||||
ar.saveBinary(std::addressof(t), sizeof(t));
|
||||
}
|
||||
|
||||
//! Loading for POD types from binary
|
||||
template<class T> inline
|
||||
typename std::enable_if<std::is_arithmetic<T>::value, void>::type
|
||||
load(BinaryInputArchive & ar, T & t)
|
||||
{
|
||||
ar.loadBinary(std::addressof(t), sizeof(t));
|
||||
}
|
||||
|
||||
//! Serializing NVP types to binary
|
||||
template <class Archive, class T> inline
|
||||
CEREAL_ARCHIVE_RESTRICT(BinaryInputArchive, BinaryOutputArchive)
|
||||
serialize( Archive & ar, NameValuePair<T> & t )
|
||||
{
|
||||
ar( t.value );
|
||||
}
|
||||
|
||||
//! Serializing SizeTags to binary
|
||||
template <class Archive, class T> inline
|
||||
CEREAL_ARCHIVE_RESTRICT(BinaryInputArchive, BinaryOutputArchive)
|
||||
serialize( Archive & ar, SizeTag<T> & t )
|
||||
{
|
||||
ar( t.size );
|
||||
}
|
||||
|
||||
//! Saving binary data
|
||||
template <class T> inline
|
||||
void save(BinaryOutputArchive & ar, BinaryData<T> const & bd)
|
||||
{
|
||||
ar.saveBinary( bd.data, static_cast<std::size_t>( bd.size ) );
|
||||
}
|
||||
|
||||
//! Loading binary data
|
||||
template <class T> inline
|
||||
void load(BinaryInputArchive & ar, BinaryData<T> & bd)
|
||||
{
|
||||
ar.loadBinary(bd.data, static_cast<std::size_t>(bd.size));
|
||||
}
|
||||
} // namespace cereal
|
||||
|
||||
// register archives for polymorphic support
|
||||
CEREAL_REGISTER_ARCHIVE(cereal::BinaryOutputArchive)
|
||||
CEREAL_REGISTER_ARCHIVE(cereal::BinaryInputArchive)
|
||||
|
||||
#endif // CEREAL_ARCHIVES_BINARY_HPP_
|
868
3party/Alohalytics/src/cereal/include/archives/json.hpp
Normal file
868
3party/Alohalytics/src/cereal/include/archives/json.hpp
Normal file
|
@ -0,0 +1,868 @@
|
|||
/*! \file json.hpp
|
||||
\brief JSON input and output archives */
|
||||
/*
|
||||
Copyright (c) 2014, Randolph Voorhies, Shane Grant
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of cereal nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef CEREAL_ARCHIVES_JSON_HPP_
|
||||
#define CEREAL_ARCHIVES_JSON_HPP_
|
||||
|
||||
#include "../cereal.hpp"
|
||||
#include "../details/util.hpp"
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
//! An exception thrown when rapidjson fails an internal assertion
|
||||
/*! @ingroup Utility */
|
||||
struct RapidJSONException : Exception
|
||||
{ RapidJSONException( const char * what_ ) : Exception( what_ ) {} };
|
||||
}
|
||||
|
||||
// Override rapidjson assertions to throw exceptions by default
|
||||
#ifndef RAPIDJSON_ASSERT
|
||||
#define RAPIDJSON_ASSERT(x) if(!(x)){ \
|
||||
throw ::cereal::RapidJSONException("rapidjson internal assertion failure: " #x); }
|
||||
#endif // RAPIDJSON_ASSERT
|
||||
|
||||
#include "../external/rapidjson/prettywriter.h"
|
||||
#include "../external/rapidjson/genericstream.h"
|
||||
#include "../external/rapidjson/reader.h"
|
||||
#include "../external/rapidjson/document.h"
|
||||
#include "../external/base64.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
// ######################################################################
|
||||
//! An output archive designed to save data to JSON
|
||||
/*! This archive uses RapidJSON to build serialie data to JSON.
|
||||
|
||||
JSON archives provides a human readable output but at decreased
|
||||
performance (both in time and space) compared to binary archives.
|
||||
|
||||
JSON benefits greatly from name-value pairs, which if present, will
|
||||
name the nodes in the output. If these are not present, each level
|
||||
of the output will be given an automatically generated delimited name.
|
||||
|
||||
The precision of the output archive controls the number of decimals output
|
||||
for floating point numbers and should be sufficiently large (i.e. at least 20)
|
||||
if there is a desire to have binary equality between the numbers output and
|
||||
those read in. In general you should expect a loss of precision when going
|
||||
from floating point to text and back.
|
||||
|
||||
JSON archives do not output the size information for any dynamically sized structure
|
||||
and instead infer it from the number of children for a node. This means that data
|
||||
can be hand edited for dynamic sized structures and will still be readable. This
|
||||
is accomplished through the cereal::SizeTag object, which will cause the archive
|
||||
to output the data as a JSON array (e.g. marked by [] instead of {}), which indicates
|
||||
that the container is variable sized and may be edited.
|
||||
|
||||
\ingroup Archives */
|
||||
class JSONOutputArchive : public OutputArchive<JSONOutputArchive>
|
||||
{
|
||||
enum class NodeType { StartObject, InObject, StartArray, InArray };
|
||||
|
||||
typedef rapidjson::GenericWriteStream WriteStream;
|
||||
typedef rapidjson::PrettyWriter<WriteStream> JSONWriter;
|
||||
|
||||
public:
|
||||
/*! @name Common Functionality
|
||||
Common use cases for directly interacting with an JSONOutputArchive */
|
||||
//! @{
|
||||
|
||||
//! A class containing various advanced options for the JSON archive
|
||||
class Options
|
||||
{
|
||||
public:
|
||||
//! Default options
|
||||
static Options Default(){ return Options(); }
|
||||
|
||||
//! Default options with no indentation
|
||||
static Options NoIndent(){ return Options( std::numeric_limits<double>::max_digits10, IndentChar::space, 0 ); }
|
||||
|
||||
//! The character to use for indenting
|
||||
enum class IndentChar : char
|
||||
{
|
||||
space = ' ',
|
||||
tab = '\t',
|
||||
newline = '\n',
|
||||
carriage_return = '\r'
|
||||
};
|
||||
|
||||
//! Specify specific options for the JSONOutputArchive
|
||||
/*! @param precision The precision used for floating point numbers
|
||||
@param indentChar The type of character to indent with
|
||||
@param indentLength The number of indentChar to use for indentation
|
||||
(0 corresponds to no indentation) */
|
||||
explicit Options( int precision = std::numeric_limits<double>::max_digits10,
|
||||
IndentChar indentChar = IndentChar::space,
|
||||
unsigned int indentLength = 4 ) :
|
||||
itsPrecision( precision ),
|
||||
itsIndentChar( static_cast<char>(indentChar) ),
|
||||
itsIndentLength( indentLength ) { }
|
||||
|
||||
private:
|
||||
friend class JSONOutputArchive;
|
||||
int itsPrecision;
|
||||
char itsIndentChar;
|
||||
unsigned int itsIndentLength;
|
||||
};
|
||||
|
||||
//! Construct, outputting to the provided stream
|
||||
/*! @param stream The stream to output to.
|
||||
@param options The JSON specific options to use. See the Options struct
|
||||
for the values of default parameters */
|
||||
JSONOutputArchive(std::ostream & stream, Options const & options = Options::Default() ) :
|
||||
OutputArchive<JSONOutputArchive>(this),
|
||||
itsWriteStream(stream),
|
||||
itsWriter(itsWriteStream, options.itsPrecision),
|
||||
itsNextName(nullptr)
|
||||
{
|
||||
itsWriter.SetIndent( options.itsIndentChar, options.itsIndentLength );
|
||||
itsNameCounter.push(0);
|
||||
itsNodeStack.push(NodeType::StartObject);
|
||||
}
|
||||
|
||||
//! Destructor, flushes the JSON
|
||||
~JSONOutputArchive()
|
||||
{
|
||||
itsWriter.EndObject();
|
||||
}
|
||||
|
||||
//! Saves some binary data, encoded as a base64 string, with an optional name
|
||||
/*! This will create a new node, optionally named, and insert a value that consists of
|
||||
the data encoded as a base64 string */
|
||||
void saveBinaryValue( const void * data, size_t size, const char * name = nullptr )
|
||||
{
|
||||
setNextName( name );
|
||||
writeName();
|
||||
|
||||
auto base64string = base64::encode( reinterpret_cast<const unsigned char *>( data ), size );
|
||||
saveValue( base64string );
|
||||
};
|
||||
|
||||
//! @}
|
||||
/*! @name Internal Functionality
|
||||
Functionality designed for use by those requiring control over the inner mechanisms of
|
||||
the JSONOutputArchive */
|
||||
//! @{
|
||||
|
||||
//! Starts a new node in the JSON output
|
||||
/*! The node can optionally be given a name by calling setNextName prior
|
||||
to creating the node
|
||||
|
||||
Nodes only need to be started for types that are themselves objects or arrays */
|
||||
void startNode()
|
||||
{
|
||||
writeName();
|
||||
itsNodeStack.push(NodeType::StartObject);
|
||||
itsNameCounter.push(0);
|
||||
}
|
||||
|
||||
//! Designates the most recently added node as finished
|
||||
void finishNode()
|
||||
{
|
||||
// if we ended up serializing an empty object or array, writeName
|
||||
// will never have been called - so start and then immediately end
|
||||
// the object/array.
|
||||
//
|
||||
// We'll also end any object/arrays we happen to be in
|
||||
switch(itsNodeStack.top())
|
||||
{
|
||||
case NodeType::StartArray:
|
||||
itsWriter.StartArray();
|
||||
case NodeType::InArray:
|
||||
itsWriter.EndArray();
|
||||
break;
|
||||
case NodeType::StartObject:
|
||||
itsWriter.StartObject();
|
||||
case NodeType::InObject:
|
||||
itsWriter.EndObject();
|
||||
break;
|
||||
}
|
||||
|
||||
itsNodeStack.pop();
|
||||
itsNameCounter.pop();
|
||||
}
|
||||
|
||||
//! Sets the name for the next node created with startNode
|
||||
void setNextName( const char * name )
|
||||
{
|
||||
itsNextName = name;
|
||||
}
|
||||
|
||||
//! Saves a bool to the current node
|
||||
void saveValue(bool b) { itsWriter.Bool_(b); }
|
||||
//! Saves an int to the current node
|
||||
void saveValue(int i) { itsWriter.Int(i); }
|
||||
//! Saves a uint to the current node
|
||||
void saveValue(unsigned u) { itsWriter.Uint(u); }
|
||||
//! Saves an int64 to the current node
|
||||
void saveValue(int64_t i64) { itsWriter.Int64(i64); }
|
||||
//! Saves a uint64 to the current node
|
||||
void saveValue(uint64_t u64) { itsWriter.Uint64(u64); }
|
||||
//! Saves a double to the current node
|
||||
void saveValue(double d) { itsWriter.Double(d); }
|
||||
//! Saves a string to the current node
|
||||
void saveValue(std::string const & s) { itsWriter.String(s.c_str(), static_cast<rapidjson::SizeType>( s.size() )); }
|
||||
//! Saves a const char * to the current node
|
||||
void saveValue(char const * s) { itsWriter.String(s); }
|
||||
|
||||
private:
|
||||
// Some compilers/OS have difficulty disambiguating the above for various flavors of longs, so we provide
|
||||
// special overloads to handle these cases.
|
||||
|
||||
//! 32 bit signed long saving to current node
|
||||
template <class T> inline
|
||||
typename std::enable_if<sizeof(T) == sizeof(std::int32_t) && std::is_signed<T>::value, void>::type
|
||||
saveLong(T l){ saveValue( static_cast<std::int32_t>( l ) ); }
|
||||
|
||||
//! non 32 bit signed long saving to current node
|
||||
template <class T> inline
|
||||
typename std::enable_if<sizeof(T) != sizeof(std::int32_t) && std::is_signed<T>::value, void>::type
|
||||
saveLong(T l){ saveValue( static_cast<std::int64_t>( l ) ); }
|
||||
|
||||
//! 32 bit unsigned long saving to current node
|
||||
template <class T> inline
|
||||
typename std::enable_if<sizeof(T) == sizeof(std::uint32_t) && !std::is_signed<T>::value, void>::type
|
||||
saveLong(T lu){ saveValue( static_cast<std::uint32_t>( lu ) ); }
|
||||
|
||||
//! non 32 bit unsigned long saving to current node
|
||||
template <class T> inline
|
||||
typename std::enable_if<sizeof(T) != sizeof(std::uint32_t) && !std::is_signed<T>::value, void>::type
|
||||
saveLong(T lu){ saveValue( static_cast<std::uint64_t>( lu ) ); }
|
||||
|
||||
public:
|
||||
#ifdef _MSC_VER
|
||||
//! MSVC only long overload to current node
|
||||
void saveValue( unsigned long lu ){ saveLong( lu ); };
|
||||
#else // _MSC_VER
|
||||
//! Serialize a long if it would not be caught otherwise
|
||||
template <class T> inline
|
||||
typename std::enable_if<std::is_same<T, long>::value &&
|
||||
!std::is_same<T, std::int32_t>::value &&
|
||||
!std::is_same<T, std::int64_t>::value, void>::type
|
||||
saveValue( T t ){ saveLong( t ); }
|
||||
|
||||
//! Serialize an unsigned long if it would not be caught otherwise
|
||||
template <class T> inline
|
||||
typename std::enable_if<std::is_same<T, unsigned long>::value &&
|
||||
!std::is_same<T, std::uint32_t>::value &&
|
||||
!std::is_same<T, std::uint64_t>::value, void>::type
|
||||
saveValue( T t ){ saveLong( t ); }
|
||||
#endif // _MSC_VER
|
||||
|
||||
//! Save exotic arithmetic as strings to current node
|
||||
/*! Handles long long (if distinct from other types), unsigned long (if distinct), and long double */
|
||||
template<class T> inline
|
||||
typename std::enable_if<std::is_arithmetic<T>::value &&
|
||||
!std::is_same<T, long>::value &&
|
||||
!std::is_same<T, unsigned long>::value &&
|
||||
!std::is_same<T, std::int64_t>::value &&
|
||||
!std::is_same<T, std::uint64_t>::value &&
|
||||
(sizeof(T) >= sizeof(long double) || sizeof(T) >= sizeof(long long)), void>::type
|
||||
saveValue(T const & t)
|
||||
{
|
||||
std::stringstream ss; ss.precision( std::numeric_limits<long double>::max_digits10 );
|
||||
ss << t;
|
||||
saveValue( ss.str() );
|
||||
}
|
||||
|
||||
//! Write the name of the upcoming node and prepare object/array state
|
||||
/*! Since writeName is called for every value that is output, regardless of
|
||||
whether it has a name or not, it is the place where we will do a deferred
|
||||
check of our node state and decide whether we are in an array or an object.
|
||||
|
||||
The general workflow of saving to the JSON archive is:
|
||||
|
||||
1. (optional) Set the name for the next node to be created, usually done by an NVP
|
||||
2. Start the node
|
||||
3. (if there is data to save) Write the name of the node (this function)
|
||||
4. (if there is data to save) Save the data (with saveValue)
|
||||
5. Finish the node
|
||||
*/
|
||||
void writeName()
|
||||
{
|
||||
NodeType const & nodeType = itsNodeStack.top();
|
||||
|
||||
// Start up either an object or an array, depending on state
|
||||
if(nodeType == NodeType::StartArray)
|
||||
{
|
||||
itsWriter.StartArray();
|
||||
itsNodeStack.top() = NodeType::InArray;
|
||||
}
|
||||
else if(nodeType == NodeType::StartObject)
|
||||
{
|
||||
itsNodeStack.top() = NodeType::InObject;
|
||||
itsWriter.StartObject();
|
||||
}
|
||||
|
||||
// Array types do not output names
|
||||
if(nodeType == NodeType::InArray) return;
|
||||
|
||||
if(itsNextName == nullptr)
|
||||
{
|
||||
std::string name = "value" + std::to_string( itsNameCounter.top()++ ) + "\0";
|
||||
saveValue(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
saveValue(itsNextName);
|
||||
itsNextName = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
//! Designates that the current node should be output as an array, not an object
|
||||
void makeArray()
|
||||
{
|
||||
itsNodeStack.top() = NodeType::StartArray;
|
||||
}
|
||||
|
||||
//! @}
|
||||
|
||||
private:
|
||||
WriteStream itsWriteStream; //!< Rapidjson write stream
|
||||
JSONWriter itsWriter; //!< Rapidjson writer
|
||||
char const * itsNextName; //!< The next name
|
||||
std::stack<uint32_t> itsNameCounter; //!< Counter for creating unique names for unnamed nodes
|
||||
std::stack<NodeType> itsNodeStack;
|
||||
}; // JSONOutputArchive
|
||||
|
||||
// ######################################################################
|
||||
//! An input archive designed to load data from JSON
|
||||
/*! This archive uses RapidJSON to read in a JSON archive.
|
||||
|
||||
Input JSON should have been produced by the JSONOutputArchive. Data can
|
||||
only be added to dynamically sized containers (marked by JSON arrays) -
|
||||
the input archive will determine their size by looking at the number of child nodes.
|
||||
Only JSON originating from a JSONOutputArchive is officially supported, but data
|
||||
from other sources may work if properly formatted.
|
||||
|
||||
The JSONInputArchive does not require that nodes are loaded in the same
|
||||
order they were saved by JSONOutputArchive. Using name value pairs (NVPs),
|
||||
it is possible to load in an out of order fashion or otherwise skip/select
|
||||
specific nodes to load.
|
||||
|
||||
The default behavior of the input archive is to read sequentially starting
|
||||
with the first node and exploring its children. When a given NVP does
|
||||
not match the read in name for a node, the archive will search for that
|
||||
node at the current level and load it if it exists. After loading an out of
|
||||
order node, the archive will then proceed back to loading sequentially from
|
||||
its new position.
|
||||
|
||||
Consider this simple example where loading of some data is skipped:
|
||||
|
||||
@code{cpp}
|
||||
// imagine the input file has someData(1-9) saved in order at the top level node
|
||||
ar( someData1, someData2, someData3 ); // XML loads in the order it sees in the file
|
||||
ar( cereal::make_nvp( "hello", someData6 ) ); // NVP given does not
|
||||
// match expected NVP name, so we search
|
||||
// for the given NVP and load that value
|
||||
ar( someData7, someData8, someData9 ); // with no NVP given, loading resumes at its
|
||||
// current location, proceeding sequentially
|
||||
@endcode
|
||||
|
||||
\ingroup Archives */
|
||||
class JSONInputArchive : public InputArchive<JSONInputArchive>
|
||||
{
|
||||
private:
|
||||
typedef rapidjson::GenericReadStream ReadStream;
|
||||
typedef rapidjson::GenericValue<rapidjson::UTF8<>> JSONValue;
|
||||
typedef JSONValue::ConstMemberIterator MemberIterator;
|
||||
typedef JSONValue::ConstValueIterator ValueIterator;
|
||||
typedef rapidjson::Document::GenericValue GenericValue;
|
||||
|
||||
public:
|
||||
/*! @name Common Functionality
|
||||
Common use cases for directly interacting with an JSONInputArchive */
|
||||
//! @{
|
||||
|
||||
//! Construct, reading from the provided stream
|
||||
/*! @param stream The stream to read from */
|
||||
JSONInputArchive(std::istream & stream) :
|
||||
InputArchive<JSONInputArchive>(this),
|
||||
itsNextName( nullptr ),
|
||||
itsReadStream(stream)
|
||||
{
|
||||
itsDocument.ParseStream<0>(itsReadStream);
|
||||
itsIteratorStack.emplace_back(itsDocument.MemberBegin(), itsDocument.MemberEnd());
|
||||
}
|
||||
|
||||
//! Loads some binary data, encoded as a base64 string
|
||||
/*! This will automatically start and finish a node to load the data, and can be called directly by
|
||||
users.
|
||||
|
||||
Note that this follows the same ordering rules specified in the class description in regards
|
||||
to loading in/out of order */
|
||||
void loadBinaryValue( void * data, size_t size, const char * name = nullptr )
|
||||
{
|
||||
itsNextName = name;
|
||||
|
||||
std::string encoded;
|
||||
loadValue( encoded );
|
||||
auto decoded = base64::decode( encoded );
|
||||
|
||||
if( size != decoded.size() )
|
||||
throw Exception("Decoded binary data size does not match specified size");
|
||||
|
||||
std::memcpy( data, decoded.data(), decoded.size() );
|
||||
itsNextName = nullptr;
|
||||
};
|
||||
|
||||
private:
|
||||
//! @}
|
||||
/*! @name Internal Functionality
|
||||
Functionality designed for use by those requiring control over the inner mechanisms of
|
||||
the JSONInputArchive */
|
||||
//! @{
|
||||
|
||||
//! An internal iterator that handles both array and object types
|
||||
/*! This class is a variant and holds both types of iterators that
|
||||
rapidJSON supports - one for arrays and one for objects. */
|
||||
class Iterator
|
||||
{
|
||||
public:
|
||||
Iterator() : itsIndex( 0 ), itsType(Null_) {}
|
||||
|
||||
Iterator(MemberIterator begin, MemberIterator end) :
|
||||
itsMemberItBegin(begin), itsMemberItEnd(end), itsIndex(0), itsType(Member)
|
||||
{ }
|
||||
|
||||
Iterator(ValueIterator begin, ValueIterator end) :
|
||||
itsValueItBegin(begin), itsValueItEnd(end), itsIndex(0), itsType(Value)
|
||||
{ }
|
||||
|
||||
//! Advance to the next node
|
||||
Iterator & operator++()
|
||||
{
|
||||
++itsIndex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
//! Get the value of the current node
|
||||
GenericValue const & value()
|
||||
{
|
||||
switch(itsType)
|
||||
{
|
||||
case Value : return itsValueItBegin[itsIndex];
|
||||
case Member: return itsMemberItBegin[itsIndex].value;
|
||||
default: throw cereal::Exception("Invalid Iterator Type!");
|
||||
}
|
||||
}
|
||||
|
||||
//! Get the name of the current node, or nullptr if it has no name
|
||||
const char * name() const
|
||||
{
|
||||
if( itsType == Member && (itsMemberItBegin + itsIndex) != itsMemberItEnd )
|
||||
return itsMemberItBegin[itsIndex].name.GetString();
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//! Adjust our position such that we are at the node with the given name
|
||||
/*! @throws Exception if no such named node exists */
|
||||
inline void search( const char * searchName )
|
||||
{
|
||||
const auto len = std::strlen( searchName );
|
||||
size_t index = 0;
|
||||
for( auto it = itsMemberItBegin; it != itsMemberItEnd; ++it, ++index )
|
||||
if( std::strncmp( searchName, it->name.GetString(), len ) == 0 )
|
||||
{
|
||||
itsIndex = index;
|
||||
return;
|
||||
}
|
||||
|
||||
throw Exception("JSON Parsing failed - provided NVP not found");
|
||||
}
|
||||
|
||||
private:
|
||||
MemberIterator itsMemberItBegin, itsMemberItEnd; //!< The member iterator (object)
|
||||
ValueIterator itsValueItBegin, itsValueItEnd; //!< The value iterator (array)
|
||||
size_t itsIndex; //!< The current index of this iterator
|
||||
enum Type {Value, Member, Null_} itsType; //!< Whether this holds values (array) or members (objects) or nothing
|
||||
};
|
||||
|
||||
//! Searches for the expectedName node if it doesn't match the actualName
|
||||
/*! This needs to be called before every load or node start occurs. This function will
|
||||
check to see if an NVP has been provided (with setNextName) and if so, see if that name matches the actual
|
||||
next name given. If the names do not match, it will search in the current level of the JSON for that name.
|
||||
If the name is not found, an exception will be thrown.
|
||||
|
||||
Resets the NVP name after called.
|
||||
|
||||
@throws Exception if an expectedName is given and not found */
|
||||
inline void search()
|
||||
{
|
||||
// The name an NVP provided with setNextName()
|
||||
if( itsNextName )
|
||||
{
|
||||
// The actual name of the current node
|
||||
auto const actualName = itsIteratorStack.back().name();
|
||||
|
||||
// Do a search if we don't see a name coming up, or if the names don't match
|
||||
if( !actualName || std::strcmp( itsNextName, actualName ) != 0 )
|
||||
itsIteratorStack.back().search( itsNextName );
|
||||
}
|
||||
|
||||
itsNextName = nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
//! Starts a new node, going into its proper iterator
|
||||
/*! This places an iterator for the next node to be parsed onto the iterator stack. If the next
|
||||
node is an array, this will be a value iterator, otherwise it will be a member iterator.
|
||||
|
||||
By default our strategy is to start with the document root node and then recursively iterate through
|
||||
all children in the order they show up in the document.
|
||||
We don't need to know NVPs to do this; we'll just blindly load in the order things appear in.
|
||||
|
||||
If we were given an NVP, we will search for it if it does not match our the name of the next node
|
||||
that would normally be loaded. This functionality is provided by search(). */
|
||||
void startNode()
|
||||
{
|
||||
search();
|
||||
|
||||
if(itsIteratorStack.back().value().IsArray())
|
||||
itsIteratorStack.emplace_back(itsIteratorStack.back().value().Begin(), itsIteratorStack.back().value().End());
|
||||
else
|
||||
itsIteratorStack.emplace_back(itsIteratorStack.back().value().MemberBegin(), itsIteratorStack.back().value().MemberEnd());
|
||||
}
|
||||
|
||||
//! Finishes the most recently started node
|
||||
void finishNode()
|
||||
{
|
||||
itsIteratorStack.pop_back();
|
||||
++itsIteratorStack.back();
|
||||
}
|
||||
|
||||
//! Sets the name for the next node created with startNode
|
||||
void setNextName( const char * name )
|
||||
{
|
||||
itsNextName = name;
|
||||
}
|
||||
|
||||
//! Loads a value from the current node - small signed overload
|
||||
template<class T> inline
|
||||
typename std::enable_if<std::is_signed<T>::value && sizeof(T) < sizeof(int64_t), void>::type
|
||||
loadValue(T & val)
|
||||
{
|
||||
search();
|
||||
|
||||
val = itsIteratorStack.back().value().GetInt();
|
||||
++itsIteratorStack.back();
|
||||
}
|
||||
|
||||
//! Loads a value from the current node - small unsigned overload
|
||||
template<class T> inline
|
||||
typename std::enable_if<(std::is_unsigned<T>::value && sizeof(T) < sizeof(uint64_t)) &&
|
||||
!std::is_same<bool, T>::value, void>::type
|
||||
loadValue(T & val)
|
||||
{
|
||||
search();
|
||||
|
||||
val = itsIteratorStack.back().value().GetUint();
|
||||
++itsIteratorStack.back();
|
||||
}
|
||||
|
||||
//! Loads a value from the current node - bool overload
|
||||
void loadValue(bool & val) { search(); val = itsIteratorStack.back().value().GetBool_(); ++itsIteratorStack.back(); }
|
||||
//! Loads a value from the current node - int64 overload
|
||||
void loadValue(int64_t & val) { search(); val = itsIteratorStack.back().value().GetInt64(); ++itsIteratorStack.back(); }
|
||||
//! Loads a value from the current node - uint64 overload
|
||||
void loadValue(uint64_t & val) { search(); val = itsIteratorStack.back().value().GetUint64(); ++itsIteratorStack.back(); }
|
||||
//! Loads a value from the current node - float overload
|
||||
void loadValue(float & val) { search(); val = static_cast<float>(itsIteratorStack.back().value().GetDouble()); ++itsIteratorStack.back(); }
|
||||
//! Loads a value from the current node - double overload
|
||||
void loadValue(double & val) { search(); val = itsIteratorStack.back().value().GetDouble(); ++itsIteratorStack.back(); }
|
||||
//! Loads a value from the current node - string overload
|
||||
void loadValue(std::string & val) { search(); val = itsIteratorStack.back().value().GetString(); ++itsIteratorStack.back(); }
|
||||
|
||||
private:
|
||||
//! Convert a string to a long long
|
||||
void stringToNumber( std::string const & str, long long & val ) { val = std::stoll( str ); }
|
||||
//! Convert a string to an unsigned long long
|
||||
void stringToNumber( std::string const & str, unsigned long long & val ) { val = std::stoull( str ); }
|
||||
//! Convert a string to a long double
|
||||
void stringToNumber( std::string const & str, long double & val ) { val = std::stold( str ); }
|
||||
|
||||
public:
|
||||
//! Loads a value from the current node - long double and long long overloads
|
||||
template<class T> inline
|
||||
typename std::enable_if<std::is_arithmetic<T>::value &&
|
||||
!std::is_same<T, long>::value &&
|
||||
!std::is_same<T, unsigned long>::value &&
|
||||
!std::is_same<T, std::int64_t>::value &&
|
||||
!std::is_same<T, std::uint64_t>::value &&
|
||||
(sizeof(T) >= sizeof(long double) || sizeof(T) >= sizeof(long long)), void>::type
|
||||
loadValue(T & val)
|
||||
{
|
||||
std::string encoded;
|
||||
loadValue( encoded );
|
||||
stringToNumber( encoded, val );
|
||||
}
|
||||
|
||||
//! Loads the size for a SizeTag
|
||||
void loadSize(size_type & size)
|
||||
{
|
||||
size = (itsIteratorStack.rbegin() + 1)->value().Size();
|
||||
}
|
||||
|
||||
//! @}
|
||||
|
||||
private:
|
||||
const char * itsNextName; //!< Next name set by NVP
|
||||
ReadStream itsReadStream; //!< Rapidjson write stream
|
||||
std::vector<Iterator> itsIteratorStack; //!< 'Stack' of rapidJSON iterators
|
||||
rapidjson::Document itsDocument; //!< Rapidjson document
|
||||
};
|
||||
|
||||
// ######################################################################
|
||||
// JSONArchive prologue and epilogue functions
|
||||
// ######################################################################
|
||||
|
||||
// ######################################################################
|
||||
//! Prologue for NVPs for JSON archives
|
||||
/*! NVPs do not start or finish nodes - they just set up the names */
|
||||
template <class T> inline
|
||||
void prologue( JSONOutputArchive &, NameValuePair<T> const & )
|
||||
{ }
|
||||
|
||||
//! Prologue for NVPs for JSON archives
|
||||
template <class T> inline
|
||||
void prologue( JSONInputArchive &, NameValuePair<T> const & )
|
||||
{ }
|
||||
|
||||
// ######################################################################
|
||||
//! Epilogue for NVPs for JSON archives
|
||||
/*! NVPs do not start or finish nodes - they just set up the names */
|
||||
template <class T> inline
|
||||
void epilogue( JSONOutputArchive &, NameValuePair<T> const & )
|
||||
{ }
|
||||
|
||||
//! Epilogue for NVPs for JSON archives
|
||||
/*! NVPs do not start or finish nodes - they just set up the names */
|
||||
template <class T> inline
|
||||
void epilogue( JSONInputArchive &, NameValuePair<T> const & )
|
||||
{ }
|
||||
|
||||
// ######################################################################
|
||||
//! Prologue for SizeTags for JSON archives
|
||||
/*! SizeTags are strictly ignored for JSON, they just indicate
|
||||
that the current node should be made into an array */
|
||||
template <class T> inline
|
||||
void prologue( JSONOutputArchive & ar, SizeTag<T> const & )
|
||||
{
|
||||
ar.makeArray();
|
||||
}
|
||||
|
||||
//! Prologue for SizeTags for JSON archives
|
||||
template <class T> inline
|
||||
void prologue( JSONInputArchive &, SizeTag<T> const & )
|
||||
{ }
|
||||
|
||||
// ######################################################################
|
||||
//! Epilogue for SizeTags for JSON archives
|
||||
/*! SizeTags are strictly ignored for JSON */
|
||||
template <class T> inline
|
||||
void epilogue( JSONOutputArchive &, SizeTag<T> const & )
|
||||
{ }
|
||||
|
||||
//! Epilogue for SizeTags for JSON archives
|
||||
template <class T> inline
|
||||
void epilogue( JSONInputArchive &, SizeTag<T> const & )
|
||||
{ }
|
||||
|
||||
// ######################################################################
|
||||
//! Prologue for all other types for JSON archives (except minimal types)
|
||||
/*! Starts a new node, named either automatically or by some NVP,
|
||||
that may be given data by the type about to be archived
|
||||
|
||||
Minimal types do not start or finish nodes */
|
||||
template <class T> inline
|
||||
typename std::enable_if<!std::is_arithmetic<T>::value &&
|
||||
!traits::has_minimal_output_serialization<T, JSONOutputArchive>::value, void>::type
|
||||
prologue( JSONOutputArchive & ar, T const & )
|
||||
{
|
||||
ar.startNode();
|
||||
}
|
||||
|
||||
//! Prologue for all other types for JSON archives
|
||||
template <class T> inline
|
||||
typename std::enable_if<!std::is_arithmetic<T>::value &&
|
||||
!traits::has_minimal_input_serialization<T, JSONOutputArchive>::value, void>::type
|
||||
prologue( JSONInputArchive & ar, T const & )
|
||||
{
|
||||
ar.startNode();
|
||||
}
|
||||
|
||||
// ######################################################################
|
||||
//! Epilogue for all other types other for JSON archives (except minimal types
|
||||
/*! Finishes the node created in the prologue
|
||||
|
||||
Minimal types do not start or finish nodes */
|
||||
template <class T> inline
|
||||
typename std::enable_if<!std::is_arithmetic<T>::value &&
|
||||
!traits::has_minimal_output_serialization<T, JSONOutputArchive>::value, void>::type
|
||||
epilogue( JSONOutputArchive & ar, T const & )
|
||||
{
|
||||
ar.finishNode();
|
||||
}
|
||||
|
||||
//! Epilogue for all other types other for JSON archives
|
||||
template <class T> inline
|
||||
typename std::enable_if<!std::is_arithmetic<T>::value &&
|
||||
!traits::has_minimal_input_serialization<T, JSONOutputArchive>::value, void>::type
|
||||
epilogue( JSONInputArchive & ar, T const & )
|
||||
{
|
||||
ar.finishNode();
|
||||
}
|
||||
|
||||
// ######################################################################
|
||||
//! Prologue for arithmetic types for JSON archives
|
||||
template <class T> inline
|
||||
typename std::enable_if<std::is_arithmetic<T>::value, void>::type
|
||||
prologue( JSONOutputArchive & ar, T const & )
|
||||
{
|
||||
ar.writeName();
|
||||
}
|
||||
|
||||
//! Prologue for arithmetic types for JSON archives
|
||||
template <class T> inline
|
||||
typename std::enable_if<std::is_arithmetic<T>::value, void>::type
|
||||
prologue( JSONInputArchive &, T const & )
|
||||
{ }
|
||||
|
||||
// ######################################################################
|
||||
//! Epilogue for arithmetic types for JSON archives
|
||||
template <class T> inline
|
||||
typename std::enable_if<std::is_arithmetic<T>::value, void>::type
|
||||
epilogue( JSONOutputArchive &, T const & )
|
||||
{ }
|
||||
|
||||
//! Epilogue for arithmetic types for JSON archives
|
||||
template <class T> inline
|
||||
typename std::enable_if<std::is_arithmetic<T>::value, void>::type
|
||||
epilogue( JSONInputArchive &, T const & )
|
||||
{ }
|
||||
|
||||
// ######################################################################
|
||||
//! Prologue for strings for JSON archives
|
||||
template<class CharT, class Traits, class Alloc> inline
|
||||
void prologue(JSONOutputArchive & ar, std::basic_string<CharT, Traits, Alloc> const &)
|
||||
{
|
||||
ar.writeName();
|
||||
}
|
||||
|
||||
//! Prologue for strings for JSON archives
|
||||
template<class CharT, class Traits, class Alloc> inline
|
||||
void prologue(JSONInputArchive &, std::basic_string<CharT, Traits, Alloc> const &)
|
||||
{ }
|
||||
|
||||
// ######################################################################
|
||||
//! Epilogue for strings for JSON archives
|
||||
template<class CharT, class Traits, class Alloc> inline
|
||||
void epilogue(JSONOutputArchive &, std::basic_string<CharT, Traits, Alloc> const &)
|
||||
{ }
|
||||
|
||||
//! Epilogue for strings for JSON archives
|
||||
template<class CharT, class Traits, class Alloc> inline
|
||||
void epilogue(JSONInputArchive &, std::basic_string<CharT, Traits, Alloc> const &)
|
||||
{ }
|
||||
|
||||
// ######################################################################
|
||||
// Common JSONArchive serialization functions
|
||||
// ######################################################################
|
||||
|
||||
//! Serializing NVP types to JSON
|
||||
template <class T> inline
|
||||
void save( JSONOutputArchive & ar, NameValuePair<T> const & t )
|
||||
{
|
||||
ar.setNextName( t.name );
|
||||
ar( t.value );
|
||||
}
|
||||
|
||||
template <class T> inline
|
||||
void load( JSONInputArchive & ar, NameValuePair<T> & t )
|
||||
{
|
||||
ar.setNextName( t.name );
|
||||
ar( t.value );
|
||||
}
|
||||
|
||||
//! Saving for arithmetic to JSON
|
||||
template<class T> inline
|
||||
typename std::enable_if<std::is_arithmetic<T>::value, void>::type
|
||||
save(JSONOutputArchive & ar, T const & t)
|
||||
{
|
||||
ar.saveValue( t );
|
||||
}
|
||||
|
||||
//! Loading arithmetic from JSON
|
||||
template<class T> inline
|
||||
typename std::enable_if<std::is_arithmetic<T>::value, void>::type
|
||||
load(JSONInputArchive & ar, T & t)
|
||||
{
|
||||
ar.loadValue( t );
|
||||
}
|
||||
|
||||
//! saving string to JSON
|
||||
template<class CharT, class Traits, class Alloc> inline
|
||||
void save(JSONOutputArchive & ar, std::basic_string<CharT, Traits, Alloc> const & str)
|
||||
{
|
||||
ar.saveValue( str );
|
||||
}
|
||||
|
||||
//! loading string from JSON
|
||||
template<class CharT, class Traits, class Alloc> inline
|
||||
void load(JSONInputArchive & ar, std::basic_string<CharT, Traits, Alloc> & str)
|
||||
{
|
||||
ar.loadValue( str );
|
||||
}
|
||||
|
||||
// ######################################################################
|
||||
//! Saving SizeTags to JSON
|
||||
template <class T> inline
|
||||
void save( JSONOutputArchive &, SizeTag<T> const & )
|
||||
{
|
||||
// nothing to do here, we don't explicitly save the size
|
||||
}
|
||||
|
||||
//! Loading SizeTags from JSON
|
||||
template <class T> inline
|
||||
void load( JSONInputArchive & ar, SizeTag<T> & st )
|
||||
{
|
||||
ar.loadSize( st.size );
|
||||
}
|
||||
} // namespace cereal
|
||||
|
||||
// register archives for polymorphic support
|
||||
CEREAL_REGISTER_ARCHIVE(cereal::JSONInputArchive)
|
||||
CEREAL_REGISTER_ARCHIVE(cereal::JSONOutputArchive)
|
||||
|
||||
#endif // CEREAL_ARCHIVES_JSON_HPP_
|
|
@ -0,0 +1,242 @@
|
|||
/*! \file binary.hpp
|
||||
\brief Binary input and output archives */
|
||||
/*
|
||||
Copyright (c) 2014, Randolph Voorhies, Shane Grant
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of cereal nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef CEREAL_ARCHIVES_PORTABLE_BINARY_HPP_
|
||||
#define CEREAL_ARCHIVES_PORTABLE_BINARY_HPP_
|
||||
|
||||
#include "../cereal.hpp"
|
||||
#include <sstream>
|
||||
#include <limits>
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
namespace portable_binary_detail
|
||||
{
|
||||
//! Returns true if the current machine is little endian
|
||||
/*! @ingroup Internal */
|
||||
inline bool is_little_endian()
|
||||
{
|
||||
static std::int32_t test = 1;
|
||||
return *reinterpret_cast<std::int8_t*>( &test ) == 1;
|
||||
}
|
||||
|
||||
//! Swaps the order of bytes for some chunk of memory
|
||||
/*! @param data The data as a uint8_t pointer
|
||||
@tparam DataSize The true size of the data
|
||||
@ingroup Internal */
|
||||
template <std::size_t DataSize>
|
||||
inline void swap_bytes( std::uint8_t * data )
|
||||
{
|
||||
for( std::size_t i = 0, end = DataSize / 2; i < end; ++i )
|
||||
std::swap( data[i], data[DataSize - i - 1] );
|
||||
}
|
||||
} // end namespace portable_binary_detail
|
||||
|
||||
// ######################################################################
|
||||
//! An output archive designed to save data in a compact binary representation portable over different architectures
|
||||
/*! This archive outputs data to a stream in an extremely compact binary
|
||||
representation with as little extra metadata as possible.
|
||||
|
||||
This archive will record the endianness of the data and assuming that
|
||||
the user takes care of ensuring serialized types are the same size
|
||||
across machines, is portable over different architectures.
|
||||
|
||||
When using a binary archive and a file stream, you must use the
|
||||
std::ios::binary format flag to avoid having your data altered
|
||||
inadvertently.
|
||||
|
||||
\warning This archive has not been thoroughly tested across different architectures.
|
||||
Please report any issues, optimizations, or feature requests at
|
||||
<a href="www.github.com/USCiLab/cereal">the project github</a>.
|
||||
|
||||
\ingroup Archives */
|
||||
class PortableBinaryOutputArchive : public OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision>
|
||||
{
|
||||
public:
|
||||
//! Construct, outputting to the provided stream
|
||||
/*! @param stream The stream to output to. Can be a stringstream, a file stream, or
|
||||
even cout! */
|
||||
PortableBinaryOutputArchive(std::ostream & stream) :
|
||||
OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision>(this),
|
||||
itsStream(stream)
|
||||
{
|
||||
this->operator()( portable_binary_detail::is_little_endian() );
|
||||
}
|
||||
|
||||
//! Writes size bytes of data to the output stream
|
||||
void saveBinary( const void * data, std::size_t size )
|
||||
{
|
||||
auto const writtenSize = static_cast<std::size_t>( itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ), size ) );
|
||||
|
||||
if(writtenSize != size)
|
||||
throw Exception("Failed to write " + std::to_string(size) + " bytes to output stream! Wrote " + std::to_string(writtenSize));
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream & itsStream;
|
||||
};
|
||||
|
||||
// ######################################################################
|
||||
//! An input archive designed to load data saved using PortableBinaryOutputArchive
|
||||
/*! This archive outputs data to a stream in an extremely compact binary
|
||||
representation with as little extra metadata as possible.
|
||||
|
||||
This archive will load the endianness of the serialized data and
|
||||
if necessary transform it to match that of the local machine. This comes
|
||||
at a significant performance cost compared to non portable archives if
|
||||
the transformation is necessary, and also causes a small performance hit
|
||||
even if it is not necessary.
|
||||
|
||||
It is recommended to use portable archives only if you know that you will
|
||||
be sending binary data to machines with different endianness.
|
||||
|
||||
The archive will do nothing to ensure types are the same size - that is
|
||||
the responsibility of the user.
|
||||
|
||||
When using a binary archive and a file stream, you must use the
|
||||
std::ios::binary format flag to avoid having your data altered
|
||||
inadvertently.
|
||||
|
||||
\warning This archive has not been thoroughly tested across different architectures.
|
||||
Please report any issues, optimizations, or feature requests at
|
||||
<a href="www.github.com/USCiLab/cereal">the project github</a>.
|
||||
|
||||
\ingroup Archives */
|
||||
class PortableBinaryInputArchive : public InputArchive<PortableBinaryInputArchive, AllowEmptyClassElision>
|
||||
{
|
||||
public:
|
||||
//! Construct, loading from the provided stream
|
||||
/*! @param stream The stream to read from. */
|
||||
PortableBinaryInputArchive(std::istream & stream) :
|
||||
InputArchive<PortableBinaryInputArchive, AllowEmptyClassElision>(this),
|
||||
itsStream(stream),
|
||||
itsConvertEndianness( false )
|
||||
{
|
||||
bool streamLittleEndian;
|
||||
this->operator()( streamLittleEndian );
|
||||
itsConvertEndianness = portable_binary_detail::is_little_endian() ^ streamLittleEndian;
|
||||
}
|
||||
|
||||
//! Reads size bytes of data from the input stream
|
||||
/*! @param data The data to save
|
||||
@param size The number of bytes in the data
|
||||
@tparam DataSize T The size of the actual type of the data elements being loaded */
|
||||
template <std::size_t DataSize>
|
||||
void loadBinary( void * const data, std::size_t size )
|
||||
{
|
||||
// load data
|
||||
auto const readSize = static_cast<std::size_t>( itsStream.rdbuf()->sgetn( reinterpret_cast<char*>( data ), size ) );
|
||||
|
||||
if(readSize != size)
|
||||
throw Exception("Failed to read " + std::to_string(size) + " bytes from input stream! Read " + std::to_string(readSize));
|
||||
|
||||
// flip bits if needed
|
||||
if( itsConvertEndianness )
|
||||
{
|
||||
std::uint8_t * ptr = reinterpret_cast<std::uint8_t*>( data );
|
||||
for( std::size_t i = 0; i < size; i += DataSize )
|
||||
portable_binary_detail::swap_bytes<DataSize>( ptr );
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::istream & itsStream;
|
||||
bool itsConvertEndianness; //!< If set to true, we will need to swap bytes upon loading
|
||||
};
|
||||
|
||||
// ######################################################################
|
||||
// Common BinaryArchive serialization functions
|
||||
|
||||
//! Saving for POD types to portable binary
|
||||
template<class T> inline
|
||||
typename std::enable_if<std::is_arithmetic<T>::value, void>::type
|
||||
save(PortableBinaryOutputArchive & ar, T const & t)
|
||||
{
|
||||
static_assert( !std::is_floating_point<T>::value ||
|
||||
(std::is_floating_point<T>::value && std::numeric_limits<T>::is_iec559),
|
||||
"Portable binary only supports IEEE 754 standardized floating point" );
|
||||
ar.saveBinary(std::addressof(t), sizeof(t));
|
||||
}
|
||||
|
||||
//! Loading for POD types from portable binary
|
||||
template<class T> inline
|
||||
typename std::enable_if<std::is_arithmetic<T>::value, void>::type
|
||||
load(PortableBinaryInputArchive & ar, T & t)
|
||||
{
|
||||
static_assert( !std::is_floating_point<T>::value ||
|
||||
(std::is_floating_point<T>::value && std::numeric_limits<T>::is_iec559),
|
||||
"Portable binary only supports IEEE 754 standardized floating point" );
|
||||
ar.template loadBinary<sizeof(T)>(std::addressof(t), sizeof(t));
|
||||
}
|
||||
|
||||
//! Serializing NVP types to portable binary
|
||||
template <class Archive, class T> inline
|
||||
CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive, PortableBinaryOutputArchive)
|
||||
serialize( Archive & ar, NameValuePair<T> & t )
|
||||
{
|
||||
ar( t.value );
|
||||
}
|
||||
|
||||
//! Serializing SizeTags to portable binary
|
||||
template <class Archive, class T> inline
|
||||
CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive, PortableBinaryOutputArchive)
|
||||
serialize( Archive & ar, SizeTag<T> & t )
|
||||
{
|
||||
ar( t.size );
|
||||
}
|
||||
|
||||
//! Saving binary data to portable binary
|
||||
template <class T> inline
|
||||
void save(PortableBinaryOutputArchive & ar, BinaryData<T> const & bd)
|
||||
{
|
||||
typedef typename std::remove_pointer<T>::type TT;
|
||||
static_assert( !std::is_floating_point<TT>::value ||
|
||||
(std::is_floating_point<TT>::value && std::numeric_limits<TT>::is_iec559),
|
||||
"Portable binary only supports IEEE 754 standardized floating point" );
|
||||
|
||||
ar.saveBinary( bd.data, static_cast<std::size_t>( bd.size ) );
|
||||
}
|
||||
|
||||
//! Loading binary data from portable binary
|
||||
template <class T> inline
|
||||
void load(PortableBinaryInputArchive & ar, BinaryData<T> & bd)
|
||||
{
|
||||
typedef typename std::remove_pointer<T>::type TT;
|
||||
static_assert( !std::is_floating_point<TT>::value ||
|
||||
(std::is_floating_point<TT>::value && std::numeric_limits<TT>::is_iec559),
|
||||
"Portable binary only supports IEEE 754 standardized floating point" );
|
||||
|
||||
ar.template loadBinary<sizeof(TT)>( bd.data, static_cast<std::size_t>( bd.size ) );
|
||||
}
|
||||
} // namespace cereal
|
||||
|
||||
// register archives for polymorphic support
|
||||
CEREAL_REGISTER_ARCHIVE(cereal::PortableBinaryOutputArchive)
|
||||
CEREAL_REGISTER_ARCHIVE(cereal::PortableBinaryInputArchive)
|
||||
|
||||
#endif // CEREAL_ARCHIVES_PORTABLE_BINARY_HPP_
|
830
3party/Alohalytics/src/cereal/include/archives/xml.hpp
Normal file
830
3party/Alohalytics/src/cereal/include/archives/xml.hpp
Normal file
|
@ -0,0 +1,830 @@
|
|||
/*! \file xml.hpp
|
||||
\brief XML input and output archives */
|
||||
/*
|
||||
Copyright (c) 2014, Randolph Voorhies, Shane Grant
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of cereal nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef CEREAL_ARCHIVES_XML_HPP_
|
||||
#define CEREAL_ARCHIVES_XML_HPP_
|
||||
#include "../cereal.hpp"
|
||||
#include "../details/util.hpp"
|
||||
|
||||
#include "../external/rapidxml/rapidxml.hpp"
|
||||
#include "../external/rapidxml/rapidxml_print.hpp"
|
||||
#include "../external/base64.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
namespace xml_detail
|
||||
{
|
||||
#ifndef CEREAL_XML_STRING_VALUE
|
||||
//! The default name for the root node in a cereal xml archive.
|
||||
/*! You can define CEREAL_XML_STRING_VALUE to be different assuming you do so
|
||||
before this file is included. */
|
||||
#define CEREAL_XML_STRING_VALUE "cereal"
|
||||
#endif // CEREAL_XML_STRING_VALUE
|
||||
|
||||
//! The name given to the root node in a cereal xml archive
|
||||
static const char * CEREAL_XML_STRING = CEREAL_XML_STRING_VALUE;
|
||||
}
|
||||
|
||||
// ######################################################################
|
||||
//! An output archive designed to save data to XML
|
||||
/*! This archive uses RapidXML to build an in memory XML tree of the
|
||||
data it serializes before outputting it to its stream upon destruction.
|
||||
The envisioned way of using this archive is in an RAII fashion, letting
|
||||
the automatic destruction of the object cause the flush to its stream.
|
||||
|
||||
XML archives provides a human readable output but at decreased
|
||||
performance (both in time and space) compared to binary archives.
|
||||
|
||||
XML benefits greatly from name-value pairs, which if present, will
|
||||
name the nodes in the output. If these are not present, each level
|
||||
of the output tree will be given an automatically generated delimited name.
|
||||
|
||||
The precision of the output archive controls the number of decimals output
|
||||
for floating point numbers and should be sufficiently large (i.e. at least 20)
|
||||
if there is a desire to have binary equality between the numbers output and
|
||||
those read in. In general you should expect a loss of precision when going
|
||||
from floating point to text and back.
|
||||
|
||||
XML archives can optionally print the type of everything they serialize, which
|
||||
adds an attribute to each node.
|
||||
|
||||
XML archives do not output the size information for any dynamically sized structure
|
||||
and instead infer it from the number of children for a node. This means that data
|
||||
can be hand edited for dynamic sized structures and will still be readable. This
|
||||
is accomplished through the cereal::SizeTag object, which will also add an attribute
|
||||
to its parent field.
|
||||
\ingroup Archives */
|
||||
class XMLOutputArchive : public OutputArchive<XMLOutputArchive>
|
||||
{
|
||||
public:
|
||||
/*! @name Common Functionality
|
||||
Common use cases for directly interacting with an XMLOutputArchive */
|
||||
//! @{
|
||||
|
||||
//! A class containing various advanced options for the XML archive
|
||||
class Options
|
||||
{
|
||||
public:
|
||||
//! Default options
|
||||
static Options Default(){ return Options(); }
|
||||
|
||||
//! Default options with no indentation
|
||||
static Options NoIndent(){ return Options( std::numeric_limits<double>::max_digits10, false ); }
|
||||
|
||||
//! Specify specific options for the XMLOutputArchive
|
||||
/*! @param precision The precision used for floating point numbers
|
||||
@param indent Whether to indent each line of XML
|
||||
@param outputType Whether to output the type of each serialized object as an attribute */
|
||||
explicit Options( int precision = std::numeric_limits<double>::max_digits10,
|
||||
bool indent = true,
|
||||
bool outputType = false ) :
|
||||
itsPrecision( precision ),
|
||||
itsIndent( indent ),
|
||||
itsOutputType( outputType ) { }
|
||||
|
||||
private:
|
||||
friend class XMLOutputArchive;
|
||||
int itsPrecision;
|
||||
bool itsIndent;
|
||||
bool itsOutputType;
|
||||
};
|
||||
|
||||
//! Construct, outputting to the provided stream upon destruction
|
||||
/*! @param stream The stream to output to. Note that XML is only guaranteed to flush
|
||||
its output to the stream upon destruction.
|
||||
@param options The XML specific options to use. See the Options struct
|
||||
for the values of default parameters */
|
||||
XMLOutputArchive( std::ostream & stream, Options const & options = Options::Default() ) :
|
||||
OutputArchive<XMLOutputArchive>(this),
|
||||
itsStream(stream),
|
||||
itsOutputType( options.itsOutputType ),
|
||||
itsIndent( options.itsIndent )
|
||||
{
|
||||
// rapidxml will delete all allocations when xml_document is cleared
|
||||
auto node = itsXML.allocate_node( rapidxml::node_declaration );
|
||||
node->append_attribute( itsXML.allocate_attribute( "version", "1.0" ) );
|
||||
node->append_attribute( itsXML.allocate_attribute( "encoding", "utf-8" ) );
|
||||
itsXML.append_node( node );
|
||||
|
||||
// allocate root node
|
||||
auto root = itsXML.allocate_node( rapidxml::node_element, xml_detail::CEREAL_XML_STRING );
|
||||
itsXML.append_node( root );
|
||||
itsNodes.emplace( root );
|
||||
|
||||
// set attributes on the streams
|
||||
itsStream << std::boolalpha;
|
||||
itsStream.precision( options.itsPrecision );
|
||||
itsOS << std::boolalpha;
|
||||
itsOS.precision( options.itsPrecision );
|
||||
}
|
||||
|
||||
//! Destructor, flushes the XML
|
||||
~XMLOutputArchive()
|
||||
{
|
||||
const int flags = itsIndent ? 0x0 : rapidxml::print_no_indenting;
|
||||
rapidxml::print( itsStream, itsXML, flags );
|
||||
itsXML.clear();
|
||||
}
|
||||
|
||||
//! Saves some binary data, encoded as a base64 string, with an optional name
|
||||
/*! This can be called directly by users and it will automatically create a child node for
|
||||
the current XML node, populate it with a base64 encoded string, and optionally name
|
||||
it. The node will be finished after it has been populated. */
|
||||
void saveBinaryValue( const void * data, size_t size, const char * name = nullptr )
|
||||
{
|
||||
itsNodes.top().name = name;
|
||||
|
||||
startNode();
|
||||
|
||||
auto base64string = base64::encode( reinterpret_cast<const unsigned char *>( data ), size );
|
||||
saveValue( base64string );
|
||||
|
||||
if( itsOutputType )
|
||||
itsNodes.top().node->append_attribute( itsXML.allocate_attribute( "type", "cereal binary data" ) );
|
||||
|
||||
finishNode();
|
||||
};
|
||||
|
||||
//! @}
|
||||
/*! @name Internal Functionality
|
||||
Functionality designed for use by those requiring control over the inner mechanisms of
|
||||
the XMLOutputArchive */
|
||||
//! @{
|
||||
|
||||
//! Creates a new node that is a child of the node at the top of the stack
|
||||
/*! Nodes will be given a name that has either been pre-set by a name value pair,
|
||||
or generated based upon a counter unique to the parent node. If you want to
|
||||
give a node a specific name, use setNextName prior to calling startNode.
|
||||
|
||||
The node will then be pushed onto the node stack. */
|
||||
void startNode()
|
||||
{
|
||||
// generate a name for this new node
|
||||
const auto nameString = itsNodes.top().getValueName();
|
||||
|
||||
// allocate strings for all of the data in the XML object
|
||||
auto namePtr = itsXML.allocate_string( nameString.data(), nameString.size() );
|
||||
|
||||
// insert into the XML
|
||||
auto node = itsXML.allocate_node( rapidxml::node_element, namePtr, nullptr, nameString.size() );
|
||||
itsNodes.top().node->append_node( node );
|
||||
itsNodes.emplace( node );
|
||||
}
|
||||
|
||||
//! Designates the most recently added node as finished
|
||||
void finishNode()
|
||||
{
|
||||
itsNodes.pop();
|
||||
}
|
||||
|
||||
//! Sets the name for the next node created with startNode
|
||||
void setNextName( const char * name )
|
||||
{
|
||||
itsNodes.top().name = name;
|
||||
}
|
||||
|
||||
//! Saves some data, encoded as a string, into the current top level node
|
||||
/*! The data will be be named with the most recent name if one exists,
|
||||
otherwise it will be given some default delimited value that depends upon
|
||||
the parent node */
|
||||
template <class T> inline
|
||||
void saveValue( T const & value )
|
||||
{
|
||||
itsOS.clear(); itsOS.seekp( 0, std::ios::beg );
|
||||
itsOS << value << std::ends;
|
||||
|
||||
// allocate strings for all of the data in the XML object
|
||||
auto dataPtr = itsXML.allocate_string( itsOS.str().c_str() );
|
||||
|
||||
// insert into the XML
|
||||
itsNodes.top().node->append_node( itsXML.allocate_node( rapidxml::node_data, nullptr, dataPtr ) );
|
||||
}
|
||||
|
||||
//! Overload for uint8_t prevents them from being serialized as characters
|
||||
void saveValue( uint8_t const & value )
|
||||
{
|
||||
saveValue( static_cast<uint32_t>( value ) );
|
||||
}
|
||||
|
||||
//! Overload for int8_t prevents them from being serialized as characters
|
||||
void saveValue( int8_t const & value )
|
||||
{
|
||||
saveValue( static_cast<int32_t>( value ) );
|
||||
}
|
||||
|
||||
//! Causes the type to be appended as an attribute to the most recently made node if output type is set to true
|
||||
template <class T> inline
|
||||
void insertType()
|
||||
{
|
||||
if( !itsOutputType )
|
||||
return;
|
||||
|
||||
// generate a name for this new node
|
||||
const auto nameString = util::demangledName<T>();
|
||||
|
||||
// allocate strings for all of the data in the XML object
|
||||
auto namePtr = itsXML.allocate_string( nameString.data(), nameString.size() );
|
||||
|
||||
itsNodes.top().node->append_attribute( itsXML.allocate_attribute( "type", namePtr ) );
|
||||
}
|
||||
|
||||
//! Appends an attribute to the current top level node
|
||||
void appendAttribute( const char * name, const char * value )
|
||||
{
|
||||
auto namePtr = itsXML.allocate_string( name );
|
||||
auto valuePtr = itsXML.allocate_string( value );
|
||||
itsNodes.top().node->append_attribute( itsXML.allocate_attribute( namePtr, valuePtr ) );
|
||||
}
|
||||
|
||||
protected:
|
||||
//! A struct that contains metadata about a node
|
||||
struct NodeInfo
|
||||
{
|
||||
NodeInfo( rapidxml::xml_node<> * n = nullptr,
|
||||
const char * nm = nullptr ) :
|
||||
node( n ),
|
||||
counter( 0 ),
|
||||
name( nm )
|
||||
{ }
|
||||
|
||||
rapidxml::xml_node<> * node; //!< A pointer to this node
|
||||
size_t counter; //!< The counter for naming child nodes
|
||||
const char * name; //!< The name for the next child node
|
||||
|
||||
//! Gets the name for the next child node created from this node
|
||||
/*! The name will be automatically generated using the counter if
|
||||
a name has not been previously set. If a name has been previously
|
||||
set, that name will be returned only once */
|
||||
std::string getValueName()
|
||||
{
|
||||
if( name )
|
||||
{
|
||||
auto n = name;
|
||||
name = nullptr;
|
||||
return {n};
|
||||
}
|
||||
else
|
||||
return "value" + std::to_string( counter++ ) + "\0";
|
||||
}
|
||||
}; // NodeInfo
|
||||
|
||||
//! @}
|
||||
|
||||
private:
|
||||
std::ostream & itsStream; //!< The output stream
|
||||
rapidxml::xml_document<> itsXML; //!< The XML document
|
||||
std::stack<NodeInfo> itsNodes; //!< A stack of nodes added to the document
|
||||
std::ostringstream itsOS; //!< Used to format strings internally
|
||||
bool itsOutputType; //!< Controls whether type information is printed
|
||||
bool itsIndent; //!< Controls whether indenting is used
|
||||
}; // XMLOutputArchive
|
||||
|
||||
// ######################################################################
|
||||
//! An output archive designed to load data from XML
|
||||
/*! This archive uses RapidXML to build an in memory XML tree of the
|
||||
data in the stream it is given before loading any types serialized.
|
||||
|
||||
Input XML should have been produced by the XMLOutputArchive. Data can
|
||||
only be added to dynamically sized containers - the input archive will
|
||||
determine their size by looking at the number of child nodes. Data that
|
||||
did not originate from an XMLOutputArchive is not officially supported,
|
||||
but may be possible to use if properly formatted.
|
||||
|
||||
The XMLInputArchive does not require that nodes are loaded in the same
|
||||
order they were saved by XMLOutputArchive. Using name value pairs (NVPs),
|
||||
it is possible to load in an out of order fashion or otherwise skip/select
|
||||
specific nodes to load.
|
||||
|
||||
The default behavior of the input archive is to read sequentially starting
|
||||
with the first node and exploring its children. When a given NVP does
|
||||
not match the read in name for a node, the archive will search for that
|
||||
node at the current level and load it if it exists. After loading an out of
|
||||
order node, the archive will then proceed back to loading sequentially from
|
||||
its new position.
|
||||
|
||||
Consider this simple example where loading of some data is skipped:
|
||||
|
||||
@code{cpp}
|
||||
// imagine the input file has someData(1-9) saved in order at the top level node
|
||||
ar( someData1, someData2, someData3 ); // XML loads in the order it sees in the file
|
||||
ar( cereal::make_nvp( "hello", someData6 ) ); // NVP given does not
|
||||
// match expected NVP name, so we search
|
||||
// for the given NVP and load that value
|
||||
ar( someData7, someData8, someData9 ); // with no NVP given, loading resumes at its
|
||||
// current location, proceeding sequentially
|
||||
@endcode
|
||||
|
||||
\ingroup Archives */
|
||||
class XMLInputArchive : public InputArchive<XMLInputArchive>
|
||||
{
|
||||
public:
|
||||
/*! @name Common Functionality
|
||||
Common use cases for directly interacting with an XMLInputArchive */
|
||||
//! @{
|
||||
|
||||
//! Construct, reading in from the provided stream
|
||||
/*! Reads in an entire XML document from some stream and parses it as soon
|
||||
as serialization starts
|
||||
|
||||
@param stream The stream to read from. Can be a stringstream or a file. */
|
||||
XMLInputArchive( std::istream & stream ) :
|
||||
InputArchive<XMLInputArchive>( this ),
|
||||
itsData( std::istreambuf_iterator<char>( stream ), std::istreambuf_iterator<char>() )
|
||||
{
|
||||
try
|
||||
{
|
||||
itsData.push_back('\0'); // rapidxml will do terrible things without the data being null terminated
|
||||
itsXML.parse<rapidxml::parse_no_data_nodes | rapidxml::parse_declaration_node>( reinterpret_cast<char *>( itsData.data() ) );
|
||||
}
|
||||
catch( rapidxml::parse_error const & )
|
||||
{
|
||||
//std::cerr << "-----Original-----" << std::endl;
|
||||
//stream.seekg(0);
|
||||
//std::cout << std::string( std::istreambuf_iterator<char>( stream ), std::istreambuf_iterator<char>() ) << std::endl;
|
||||
|
||||
//std::cerr << "-----Error-----" << std::endl;
|
||||
//std::cerr << e.what() << std::endl;
|
||||
//std::cerr << e.where<char>() << std::endl;
|
||||
throw Exception("XML Parsing failed - likely due to invalid characters or invalid naming");
|
||||
}
|
||||
|
||||
// Parse the root
|
||||
auto root = itsXML.first_node( xml_detail::CEREAL_XML_STRING );
|
||||
if( root == nullptr )
|
||||
throw Exception("Could not detect cereal root node - likely due to empty or invalid input");
|
||||
else
|
||||
itsNodes.emplace( root );
|
||||
}
|
||||
|
||||
//! Loads some binary data, encoded as a base64 string, optionally specified by some name
|
||||
/*! This will automatically start and finish a node to load the data, and can be called directly by
|
||||
users.
|
||||
|
||||
Note that this follows the same ordering rules specified in the class description in regards
|
||||
to loading in/out of order */
|
||||
void loadBinaryValue( void * data, size_t size, const char * name = nullptr )
|
||||
{
|
||||
setNextName( name );
|
||||
startNode();
|
||||
|
||||
std::string encoded;
|
||||
loadValue( encoded );
|
||||
|
||||
auto decoded = base64::decode( encoded );
|
||||
|
||||
if( size != decoded.size() )
|
||||
throw Exception("Decoded binary data size does not match specified size");
|
||||
|
||||
std::memcpy( data, decoded.data(), decoded.size() );
|
||||
|
||||
finishNode();
|
||||
};
|
||||
|
||||
//! @}
|
||||
/*! @name Internal Functionality
|
||||
Functionality designed for use by those requiring control over the inner mechanisms of
|
||||
the XMLInputArchive */
|
||||
//! @{
|
||||
|
||||
//! Prepares to start reading the next node
|
||||
/*! This places the next node to be parsed onto the nodes stack.
|
||||
|
||||
By default our strategy is to start with the document root node and then
|
||||
recursively iterate through all children in the order they show up in the document.
|
||||
We don't need to know NVPs do to this; we'll just blindly load in the order things appear in.
|
||||
|
||||
We check to see if the specified NVP matches what the next automatically loaded node is. If they
|
||||
match, we just continue as normal, going in order. If they don't match, we attempt to find a node
|
||||
named after the NVP that is being loaded. If that NVP does not exist, we throw an exception. */
|
||||
void startNode()
|
||||
{
|
||||
auto next = itsNodes.top().child; // By default we would move to the next child node
|
||||
auto const expectedName = itsNodes.top().name; // this is the expected name from the NVP, if provided
|
||||
|
||||
// If we were given an NVP name, look for it in the current level of the document.
|
||||
// We only need to do this if either we have exhausted the siblings of the current level or
|
||||
// the NVP name does not match the name of the node we would normally read next
|
||||
if( expectedName && ( next == nullptr || std::strcmp( next->name(), expectedName ) != 0 ) )
|
||||
{
|
||||
next = itsNodes.top().search( expectedName );
|
||||
|
||||
if( next == nullptr )
|
||||
throw Exception("XML Parsing failed - provided NVP not found");
|
||||
}
|
||||
|
||||
itsNodes.emplace( next );
|
||||
}
|
||||
|
||||
//! Finishes reading the current node
|
||||
void finishNode()
|
||||
{
|
||||
// remove current
|
||||
itsNodes.pop();
|
||||
|
||||
// advance parent
|
||||
itsNodes.top().advance();
|
||||
|
||||
// Reset name
|
||||
itsNodes.top().name = nullptr;
|
||||
}
|
||||
|
||||
//! Sets the name for the next node created with startNode
|
||||
void setNextName( const char * name )
|
||||
{
|
||||
itsNodes.top().name = name;
|
||||
}
|
||||
|
||||
//! Loads a bool from the current top node
|
||||
template <class T> inline
|
||||
typename std::enable_if<std::is_unsigned<T>::value && std::is_same<T, bool>::value, void>::type
|
||||
loadValue( T & value )
|
||||
{
|
||||
std::istringstream is( itsNodes.top().node->value() );
|
||||
is.setf( std::ios::boolalpha );
|
||||
is >> value;
|
||||
}
|
||||
|
||||
//! Loads a type best represented as an unsigned long from the current top node
|
||||
template <class T> inline
|
||||
typename std::enable_if<std::is_unsigned<T>::value && !std::is_same<T, bool>::value && sizeof(T) < sizeof(long long), void>::type
|
||||
loadValue( T & value )
|
||||
{
|
||||
value = static_cast<T>( std::stoul( itsNodes.top().node->value() ) );
|
||||
}
|
||||
|
||||
//! Loads a type best represented as an unsigned long long from the current top node
|
||||
template <class T> inline
|
||||
typename std::enable_if<std::is_unsigned<T>::value && !std::is_same<T, bool>::value && sizeof(T) >= sizeof(long long), void>::type
|
||||
loadValue( T & value )
|
||||
{
|
||||
value = static_cast<T>( std::stoull( itsNodes.top().node->value() ) );
|
||||
}
|
||||
|
||||
//! Loads a type best represented as an int from the current top node
|
||||
template <class T> inline
|
||||
typename std::enable_if<std::is_signed<T>::value && sizeof(T) <= sizeof(int), void>::type
|
||||
loadValue( T & value )
|
||||
{
|
||||
value = static_cast<T>( std::stoi( itsNodes.top().node->value() ) );
|
||||
}
|
||||
|
||||
//! Loads a type best represented as a long from the current top node
|
||||
template <class T> inline
|
||||
typename std::enable_if<std::is_signed<T>::value && (sizeof(T) > sizeof(int)) && (sizeof(T) <= sizeof(long)), void>::type
|
||||
loadValue( T & value )
|
||||
{
|
||||
value = static_cast<T>( std::stol( itsNodes.top().node->value() ) );
|
||||
}
|
||||
|
||||
//! Loads a type best represented as a long long from the current top node
|
||||
template <class T> inline
|
||||
typename std::enable_if<std::is_signed<T>::value && (sizeof(T) > sizeof(long)) && (sizeof(T) <= sizeof(long long)), void>::type
|
||||
loadValue( T & value )
|
||||
{
|
||||
value = static_cast<T>( std::stoll( itsNodes.top().node->value() ) );
|
||||
}
|
||||
|
||||
//! Loads a type best represented as a float from the current top node
|
||||
void loadValue( float & value )
|
||||
{
|
||||
try
|
||||
{
|
||||
value = std::stof( itsNodes.top().node->value() );
|
||||
}
|
||||
catch( std::out_of_range const & )
|
||||
{
|
||||
// special case for denormalized values
|
||||
std::istringstream is( itsNodes.top().node->value() );
|
||||
is >> value;
|
||||
if( std::fpclassify( value ) != FP_SUBNORMAL )
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
//! Loads a type best represented as a double from the current top node
|
||||
void loadValue( double & value )
|
||||
{
|
||||
try
|
||||
{
|
||||
value = std::stod( itsNodes.top().node->value() );
|
||||
}
|
||||
catch( std::out_of_range const & )
|
||||
{
|
||||
// special case for denormalized values
|
||||
std::istringstream is( itsNodes.top().node->value() );
|
||||
is >> value;
|
||||
if( std::fpclassify( value ) != FP_SUBNORMAL )
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
//! Loads a type best represented as a long double from the current top node
|
||||
void loadValue( long double & value )
|
||||
{
|
||||
try
|
||||
{
|
||||
value = std::stold( itsNodes.top().node->value() );
|
||||
}
|
||||
catch( std::out_of_range const & )
|
||||
{
|
||||
// special case for denormalized values
|
||||
std::istringstream is( itsNodes.top().node->value() );
|
||||
is >> value;
|
||||
if( std::fpclassify( value ) != FP_SUBNORMAL )
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
//! Loads a string from the current node from the current top node
|
||||
template<class CharT, class Traits, class Alloc> inline
|
||||
void loadValue( std::basic_string<CharT, Traits, Alloc> & str )
|
||||
{
|
||||
std::basic_istringstream<CharT, Traits> is( itsNodes.top().node->value() );
|
||||
|
||||
str.assign( std::istreambuf_iterator<CharT, Traits>( is ),
|
||||
std::istreambuf_iterator<CharT, Traits>() );
|
||||
}
|
||||
|
||||
//! Loads the size of the current top node
|
||||
template <class T> inline
|
||||
void loadSize( T & value )
|
||||
{
|
||||
value = getNumChildren( itsNodes.top().node );
|
||||
}
|
||||
|
||||
protected:
|
||||
//! Gets the number of children (usually interpreted as size) for the specified node
|
||||
static size_t getNumChildren( rapidxml::xml_node<> * node )
|
||||
{
|
||||
size_t size = 0;
|
||||
node = node->first_node(); // get first child
|
||||
|
||||
while( node != nullptr )
|
||||
{
|
||||
++size;
|
||||
node = node->next_sibling();
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
//! A struct that contains metadata about a node
|
||||
/*! Keeps track of some top level node, its number of
|
||||
remaining children, and the current active child node */
|
||||
struct NodeInfo
|
||||
{
|
||||
NodeInfo( rapidxml::xml_node<> * n = nullptr ) :
|
||||
node( n ),
|
||||
child( n->first_node() ),
|
||||
size( XMLInputArchive::getNumChildren( n ) ),
|
||||
name( nullptr )
|
||||
{ }
|
||||
|
||||
//! Advances to the next sibling node of the child
|
||||
/*! If this is the last sibling child will be null after calling */
|
||||
void advance()
|
||||
{
|
||||
if( size > 0 )
|
||||
{
|
||||
--size;
|
||||
child = child->next_sibling();
|
||||
}
|
||||
}
|
||||
|
||||
//! Searches for a child with the given name in this node
|
||||
/*! @param searchName The name to search for (must be null terminated)
|
||||
@return The node if found, nullptr otherwise */
|
||||
rapidxml::xml_node<> * search( const char * searchName )
|
||||
{
|
||||
if( searchName )
|
||||
{
|
||||
size_t new_size = XMLInputArchive::getNumChildren( node );
|
||||
const size_t name_size = rapidxml::internal::measure( searchName );
|
||||
|
||||
for( auto new_child = node->first_node(); new_child != nullptr; new_child = new_child->next_sibling() )
|
||||
{
|
||||
if( rapidxml::internal::compare( new_child->name(), new_child->name_size(), searchName, name_size, true ) )
|
||||
{
|
||||
size = new_size;
|
||||
child = new_child;
|
||||
|
||||
return new_child;
|
||||
}
|
||||
--new_size;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rapidxml::xml_node<> * node; //!< A pointer to this node
|
||||
rapidxml::xml_node<> * child; //!< A pointer to its current child
|
||||
size_t size; //!< The remaining number of children for this node
|
||||
const char * name; //!< The NVP name for next next child node
|
||||
}; // NodeInfo
|
||||
|
||||
//! @}
|
||||
|
||||
private:
|
||||
std::vector<char> itsData; //!< The raw data loaded
|
||||
rapidxml::xml_document<> itsXML; //!< The XML document
|
||||
std::stack<NodeInfo> itsNodes; //!< A stack of nodes read from the document
|
||||
};
|
||||
|
||||
// ######################################################################
|
||||
// XMLArchive prologue and epilogue functions
|
||||
// ######################################################################
|
||||
|
||||
// ######################################################################
|
||||
//! Prologue for NVPs for XML output archives
|
||||
/*! NVPs do not start or finish nodes - they just set up the names */
|
||||
template <class T> inline
|
||||
void prologue( XMLOutputArchive &, NameValuePair<T> const & )
|
||||
{ }
|
||||
|
||||
//! Prologue for NVPs for XML input archives
|
||||
template <class T> inline
|
||||
void prologue( XMLInputArchive &, NameValuePair<T> const & )
|
||||
{ }
|
||||
|
||||
// ######################################################################
|
||||
//! Epilogue for NVPs for XML output archives
|
||||
/*! NVPs do not start or finish nodes - they just set up the names */
|
||||
template <class T> inline
|
||||
void epilogue( XMLOutputArchive &, NameValuePair<T> const & )
|
||||
{ }
|
||||
|
||||
//! Epilogue for NVPs for XML input archives
|
||||
template <class T> inline
|
||||
void epilogue( XMLInputArchive &, NameValuePair<T> const & )
|
||||
{ }
|
||||
|
||||
// ######################################################################
|
||||
//! Prologue for SizeTags for XML output archives
|
||||
/*! SizeTags do not start or finish nodes */
|
||||
template <class T> inline
|
||||
void prologue( XMLOutputArchive & ar, SizeTag<T> const & )
|
||||
{
|
||||
ar.appendAttribute( "size", "dynamic" );
|
||||
}
|
||||
|
||||
template <class T> inline
|
||||
void prologue( XMLInputArchive &, SizeTag<T> const & )
|
||||
{ }
|
||||
|
||||
//! Epilogue for SizeTags for XML output archives
|
||||
/*! SizeTags do not start or finish nodes */
|
||||
template <class T> inline
|
||||
void epilogue( XMLOutputArchive &, SizeTag<T> const & )
|
||||
{ }
|
||||
|
||||
template <class T> inline
|
||||
void epilogue( XMLInputArchive &, SizeTag<T> const & )
|
||||
{ }
|
||||
|
||||
// ######################################################################
|
||||
//! Prologue for all other types for XML output archives (except minimal types)
|
||||
/*! Starts a new node, named either automatically or by some NVP,
|
||||
that may be given data by the type about to be archived
|
||||
|
||||
Minimal types do not start or end nodes */
|
||||
template <class T> inline
|
||||
typename std::enable_if<!traits::has_minimal_output_serialization<T, XMLOutputArchive>::value, void>::type
|
||||
prologue( XMLOutputArchive & ar, T const & )
|
||||
{
|
||||
ar.startNode();
|
||||
ar.insertType<T>();
|
||||
}
|
||||
|
||||
//! Prologue for all other types for XML input archives (except minimal types)
|
||||
template <class T> inline
|
||||
typename std::enable_if<!traits::has_minimal_input_serialization<T, XMLInputArchive>::value, void>::type
|
||||
prologue( XMLInputArchive & ar, T const & )
|
||||
{
|
||||
ar.startNode();
|
||||
}
|
||||
|
||||
// ######################################################################
|
||||
//! Epilogue for all other types other for XML output archives (except minimal types)
|
||||
/*! Finishes the node created in the prologue
|
||||
|
||||
Minimal types do not start or end nodes */
|
||||
template <class T> inline
|
||||
typename std::enable_if<!traits::has_minimal_output_serialization<T, XMLOutputArchive>::value, void>::type
|
||||
epilogue( XMLOutputArchive & ar, T const & )
|
||||
{
|
||||
ar.finishNode();
|
||||
}
|
||||
|
||||
//! Epilogue for all other types other for XML output archives (except minimal types)
|
||||
template <class T> inline
|
||||
typename std::enable_if<!traits::has_minimal_input_serialization<T, XMLInputArchive>::value, void>::type
|
||||
epilogue( XMLInputArchive & ar, T const & )
|
||||
{
|
||||
ar.finishNode();
|
||||
}
|
||||
|
||||
// ######################################################################
|
||||
// Common XMLArchive serialization functions
|
||||
// ######################################################################
|
||||
|
||||
//! Saving NVP types to XML
|
||||
template <class T> inline
|
||||
void save( XMLOutputArchive & ar, NameValuePair<T> const & t )
|
||||
{
|
||||
ar.setNextName( t.name );
|
||||
ar( t.value );
|
||||
}
|
||||
|
||||
//! Loading NVP types from XML
|
||||
template <class T> inline
|
||||
void load( XMLInputArchive & ar, NameValuePair<T> & t )
|
||||
{
|
||||
ar.setNextName( t.name );
|
||||
ar( t.value );
|
||||
}
|
||||
|
||||
// ######################################################################
|
||||
//! Saving SizeTags to XML
|
||||
template <class T> inline
|
||||
void save( XMLOutputArchive &, SizeTag<T> const & )
|
||||
{ }
|
||||
|
||||
//! Loading SizeTags from XML
|
||||
template <class T> inline
|
||||
void load( XMLInputArchive & ar, SizeTag<T> & st )
|
||||
{
|
||||
ar.loadSize( st.size );
|
||||
}
|
||||
|
||||
// ######################################################################
|
||||
//! Saving for POD types to xml
|
||||
template<class T> inline
|
||||
typename std::enable_if<std::is_arithmetic<T>::value, void>::type
|
||||
save(XMLOutputArchive & ar, T const & t)
|
||||
{
|
||||
ar.saveValue( t );
|
||||
}
|
||||
|
||||
//! Loading for POD types from xml
|
||||
template<class T> inline
|
||||
typename std::enable_if<std::is_arithmetic<T>::value, void>::type
|
||||
load(XMLInputArchive & ar, T & t)
|
||||
{
|
||||
ar.loadValue( t );
|
||||
}
|
||||
|
||||
// ######################################################################
|
||||
//! saving string to xml
|
||||
template<class CharT, class Traits, class Alloc> inline
|
||||
void save(XMLOutputArchive & ar, std::basic_string<CharT, Traits, Alloc> const & str)
|
||||
{
|
||||
ar.saveValue( str );
|
||||
}
|
||||
|
||||
//! loading string from xml
|
||||
template<class CharT, class Traits, class Alloc> inline
|
||||
void load(XMLInputArchive & ar, std::basic_string<CharT, Traits, Alloc> & str)
|
||||
{
|
||||
ar.loadValue( str );
|
||||
}
|
||||
} // namespace cereal
|
||||
|
||||
// register archives for polymorphic support
|
||||
CEREAL_REGISTER_ARCHIVE(cereal::XMLOutputArchive)
|
||||
CEREAL_REGISTER_ARCHIVE(cereal::XMLInputArchive)
|
||||
|
||||
#endif // CEREAL_ARCHIVES_XML_HPP_
|
967
3party/Alohalytics/src/cereal/include/cereal.hpp
Normal file
967
3party/Alohalytics/src/cereal/include/cereal.hpp
Normal file
|
@ -0,0 +1,967 @@
|
|||
/*! \file cereal.hpp
|
||||
\brief Main cereal functionality */
|
||||
/*
|
||||
Copyright (c) 2014, Randolph Voorhies, Shane Grant
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of cereal nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef CEREAL_CEREAL_HPP_
|
||||
#define CEREAL_CEREAL_HPP_
|
||||
|
||||
#include <type_traits>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
#include "./details/traits.hpp"
|
||||
#include "./details/helpers.hpp"
|
||||
#include "./types/base_class.hpp"
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
// ######################################################################
|
||||
//! Creates a name value pair
|
||||
/*! @relates NameValuePair
|
||||
@ingroup Utility */
|
||||
template <class T> inline
|
||||
NameValuePair<T> make_nvp( std::string const & name, T && value )
|
||||
{
|
||||
return {name.c_str(), std::forward<T>(value)};
|
||||
}
|
||||
|
||||
//! Creates a name value pair
|
||||
/*! @relates NameValuePair
|
||||
@ingroup Utility */
|
||||
template <class T> inline
|
||||
NameValuePair<T> make_nvp( const char * name, T && value )
|
||||
{
|
||||
return {name, std::forward<T>(value)};
|
||||
}
|
||||
|
||||
//! Creates a name value pair for the variable T with the same name as the variable
|
||||
/*! @relates NameValuePair
|
||||
@ingroup Utility */
|
||||
#define CEREAL_NVP(T) ::cereal::make_nvp(#T, T)
|
||||
|
||||
// ######################################################################
|
||||
//! Convenience function to create binary data for both const and non const pointers
|
||||
/*! @param data Pointer to beginning of the data
|
||||
@param size The size in bytes of the data
|
||||
@relates BinaryData
|
||||
@ingroup Utility */
|
||||
template <class T> inline
|
||||
BinaryData<T> binary_data( T && data, size_t size )
|
||||
{
|
||||
return {std::forward<T>(data), size};
|
||||
}
|
||||
|
||||
// ######################################################################
|
||||
//! Creates a size tag from some variable.
|
||||
/*! Will normally be used to serialize size (e.g. size()) information for
|
||||
variable size containers. If you have a variable sized container,
|
||||
the very first thing it serializes should be its size, wrapped in
|
||||
a SizeTag.
|
||||
|
||||
@relates SizeTag
|
||||
@ingroup Utility */
|
||||
template <class T>
|
||||
SizeTag<T> make_size_tag( T && sz )
|
||||
{
|
||||
return {std::forward<T>(sz)};
|
||||
}
|
||||
|
||||
// ######################################################################
|
||||
//! Called before a type is serialized to set up any special archive state
|
||||
//! for processing some type
|
||||
/*! If designing a serializer that needs to set up any kind of special
|
||||
state or output extra information for a type, specialize this function
|
||||
for the archive type and the types that require the extra information.
|
||||
@ingroup Internal */
|
||||
template <class Archive, class T>
|
||||
void prologue( Archive & /* archive */, T const & /* data */)
|
||||
{ }
|
||||
|
||||
//! Called after a type is serialized to tear down any special archive state
|
||||
//! for processing some type
|
||||
/*! @ingroup Internal */
|
||||
template <class Archive, class T>
|
||||
void epilogue( Archive & /* archive */, T const & /* data */)
|
||||
{ }
|
||||
|
||||
// ######################################################################
|
||||
//! Special flags for archives
|
||||
/*! AllowEmptyClassElision
|
||||
This allows for empty classes to be serialized even if they do not provide
|
||||
a serialization function. Classes with no data members are considered to be
|
||||
empty. Be warned that if this is enabled and you attempt to serialize an
|
||||
empty class with improperly formed serialize or load/save functions, no
|
||||
static error will occur - the error will propogate silently and your
|
||||
intended serialization functions may not be called. You can manually
|
||||
ensure that your classes that have custom serialization are correct
|
||||
by using the traits is_output_serializable and is_input_serializable
|
||||
in cereal/details/traits.hpp.
|
||||
@ingroup Internal */
|
||||
enum Flags { AllowEmptyClassElision = 1 };
|
||||
|
||||
// ######################################################################
|
||||
//! Registers a specific Archive type with cereal
|
||||
/*! This registration should be done once per archive. A good place to
|
||||
put this is immediately following the definition of your archive.
|
||||
Archive registration is only strictly necessary if you wish to
|
||||
support pointers to polymorphic data types. All archives that
|
||||
come with cereal are already registered.
|
||||
@ingroup Internal */
|
||||
#define CEREAL_REGISTER_ARCHIVE(Archive) \
|
||||
namespace cereal { namespace detail { \
|
||||
template <class T> \
|
||||
typename polymorphic_serialization_support<Archive, T>::type \
|
||||
instantiate_polymorphic_binding( T*, Archive*, adl_tag ); \
|
||||
} } // end namespaces
|
||||
|
||||
//! Defines a class version for some type
|
||||
/*! Versioning information is optional and adds some small amount of
|
||||
overhead to serialization. This overhead will occur both in terms of
|
||||
space in the archive (the version information for each class will be
|
||||
stored exactly once) as well as runtime (versioned serialization functions
|
||||
must check to see if they need to load or store version information).
|
||||
|
||||
Versioning is useful if you plan on fundamentally changing the way some
|
||||
type is serialized in the future. Versioned serialization functions
|
||||
cannot be used to load non-versioned data.
|
||||
|
||||
By default, all types have an assumed version value of zero. By
|
||||
using this macro, you may change the version number associated with
|
||||
some type. cereal will then use this value as a second parameter
|
||||
to your serialization functions.
|
||||
|
||||
The interface for the serialization functions is nearly identical
|
||||
to non-versioned serialization with the addition of a second parameter,
|
||||
const std::uint32_t version, which will be supplied with the correct
|
||||
version number. Serializing the version number on a save happens
|
||||
automatically.
|
||||
|
||||
Versioning cannot be mixed with non-versioned serialization functions.
|
||||
Having both types will result result in a compile time error.
|
||||
|
||||
Example interface for versioning on a non-member serialize function:
|
||||
|
||||
@code{cpp}
|
||||
CEREAL_CLASS_VERSION( Mytype, 77 ); // register class version
|
||||
|
||||
template <class Archive>
|
||||
void serialize( Archive & ar, Mytype & t, const std::uint32_t version )
|
||||
{
|
||||
// When performing a load, the version associated with the class
|
||||
// is whatever it was when that data was originally serialized
|
||||
//
|
||||
// When we save, we'll use the version that is defined in the macro
|
||||
|
||||
if( version >= some_number )
|
||||
// do this
|
||||
else
|
||||
// do that
|
||||
}
|
||||
@endcode
|
||||
|
||||
Interfaces for other forms of serialization functions is similar. This
|
||||
macro should be placed at global scope.
|
||||
@ingroup Utility */
|
||||
#define CEREAL_CLASS_VERSION(TYPE, VERSION_NUMBER) \
|
||||
namespace cereal { namespace detail { \
|
||||
template <> struct Version<TYPE> \
|
||||
{ \
|
||||
static const std::uint32_t version = VERSION_NUMBER; \
|
||||
static Version<TYPE> registerVersion() \
|
||||
{ \
|
||||
::cereal::detail::StaticObject<Versions>::getInstance().mapping.emplace( \
|
||||
std::type_index(typeid(TYPE)).hash_code(), VERSION_NUMBER ); \
|
||||
return {}; \
|
||||
} \
|
||||
}; /* end Version */ \
|
||||
static const auto CEREAL_CLASS_VERSION_REGISTER##TYPE##VERSION_NUMBER = \
|
||||
Version<TYPE>::registerVersion(); \
|
||||
} } // end namespaces
|
||||
|
||||
//! The base output archive class
|
||||
/*! This is the base output archive for all output archives. If you create
|
||||
a custom archive class, it should derive from this, passing itself as
|
||||
a template parameter for the ArchiveType.
|
||||
|
||||
The base class provides all of the functionality necessary to
|
||||
properly forward data to the correct serialization functions.
|
||||
|
||||
Individual archives should use a combination of prologue and
|
||||
epilogue functions together with specializations of serialize, save,
|
||||
and load to alter the functionality of their serialization.
|
||||
|
||||
@tparam ArchiveType The archive type that derives from OutputArchive
|
||||
@tparam Flags Flags to control advanced functionality. See the Flags
|
||||
enum for more information.
|
||||
@ingroup Internal */
|
||||
template<class ArchiveType, std::uint32_t Flags = 0>
|
||||
class OutputArchive : public detail::OutputArchiveBase
|
||||
{
|
||||
public:
|
||||
//! Construct the output archive
|
||||
/*! @param derived A pointer to the derived ArchiveType (pass this from the derived archive) */
|
||||
OutputArchive(ArchiveType * const derived) : self(derived), itsCurrentPointerId(1), itsCurrentPolymorphicTypeId(1)
|
||||
{ }
|
||||
|
||||
//! Serializes all passed in data
|
||||
/*! This is the primary interface for serializing data with an archive */
|
||||
template <class ... Types> inline
|
||||
ArchiveType & operator()( Types && ... args )
|
||||
{
|
||||
self->process( std::forward<Types>( args )... );
|
||||
return *self;
|
||||
}
|
||||
|
||||
/*! @name Boost Transition Layer
|
||||
Functionality that mirrors the syntax for Boost. This is useful if you are transitioning
|
||||
a large project from Boost to cereal. The preferred interface for cereal is using operator(). */
|
||||
//! @{
|
||||
|
||||
//! Serializes passed in data
|
||||
/*! This is a boost compatability layer and is not the preferred way of using
|
||||
cereal. If you are transitioning from boost, use this until you can
|
||||
transition to the operator() overload */
|
||||
template <class T> inline
|
||||
ArchiveType & operator&( T && arg )
|
||||
{
|
||||
self->process( std::forward<T>( arg ) );
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Serializes passed in data
|
||||
/*! This is a boost compatability layer and is not the preferred way of using
|
||||
cereal. If you are transitioning from boost, use this until you can
|
||||
transition to the operator() overload */
|
||||
template <class T> inline
|
||||
ArchiveType & operator<<( T && arg )
|
||||
{
|
||||
self->process( std::forward<T>( arg ) );
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! @}
|
||||
|
||||
//! Registers a shared pointer with the archive
|
||||
/*! This function is used to track shared pointer targets to prevent
|
||||
unnecessary saves from taking place if multiple shared pointers
|
||||
point to the same data.
|
||||
|
||||
@internal
|
||||
@param addr The address (see shared_ptr get()) pointed to by the shared pointer
|
||||
@return A key that uniquely identifies the pointer */
|
||||
inline std::uint32_t registerSharedPointer( void const * addr )
|
||||
{
|
||||
// Handle null pointers by just returning 0
|
||||
if(addr == 0) return 0;
|
||||
|
||||
auto id = itsSharedPointerMap.find( addr );
|
||||
if( id == itsSharedPointerMap.end() )
|
||||
{
|
||||
auto ptrId = itsCurrentPointerId++;
|
||||
itsSharedPointerMap.insert( {addr, ptrId} );
|
||||
return ptrId | detail::msb_32bit; // mask MSB to be 1
|
||||
}
|
||||
else
|
||||
return id->second;
|
||||
}
|
||||
|
||||
//! Registers a polymorphic type name with the archive
|
||||
/*! This function is used to track polymorphic types to prevent
|
||||
unnecessary saves of identifying strings used by the polymorphic
|
||||
support functionality.
|
||||
|
||||
@internal
|
||||
@param name The name to associate with a polymorphic type
|
||||
@return A key that uniquely identifies the polymorphic type name */
|
||||
inline std::uint32_t registerPolymorphicType( char const * name )
|
||||
{
|
||||
auto id = itsPolymorphicTypeMap.find( name );
|
||||
if( id == itsPolymorphicTypeMap.end() )
|
||||
{
|
||||
auto polyId = itsCurrentPolymorphicTypeId++;
|
||||
itsPolymorphicTypeMap.insert( {name, polyId} );
|
||||
return polyId | detail::msb_32bit; // mask MSB to be 1
|
||||
}
|
||||
else
|
||||
return id->second;
|
||||
}
|
||||
|
||||
private:
|
||||
//! Serializes data after calling prologue, then calls epilogue
|
||||
template <class T> inline
|
||||
void process( T && head )
|
||||
{
|
||||
prologue( *self, head );
|
||||
self->processImpl( head );
|
||||
epilogue( *self, head );
|
||||
}
|
||||
|
||||
//! Unwinds to process all data
|
||||
template <class T, class ... Other> inline
|
||||
void process( T && head, Other && ... tail )
|
||||
{
|
||||
self->process( std::forward<T>( head ) );
|
||||
self->process( std::forward<Other>( tail )... );
|
||||
}
|
||||
|
||||
//! Serialization of a virtual_base_class wrapper
|
||||
/*! \sa virtual_base_class */
|
||||
template <class T> inline
|
||||
ArchiveType & processImpl(virtual_base_class<T> const & b)
|
||||
{
|
||||
traits::detail::base_class_id id(b.base_ptr);
|
||||
if(itsBaseClassSet.count(id) == 0)
|
||||
{
|
||||
itsBaseClassSet.insert(id);
|
||||
self->processImpl( *b.base_ptr );
|
||||
}
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Serialization of a base_class wrapper
|
||||
/*! \sa base_class */
|
||||
template <class T> inline
|
||||
ArchiveType & processImpl(base_class<T> const & b)
|
||||
{
|
||||
self->processImpl( *b.base_ptr );
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Member serialization
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_member_serialize<T, ArchiveType>::value && !traits::has_invalid_output_versioning<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_member_serialize<T, ArchiveType>::value || traits::is_output_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T const & t)
|
||||
{
|
||||
access::member_serialize(*self, const_cast<T &>(t));
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Non member serialization
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_non_member_serialize<T, ArchiveType>::value && !traits::has_invalid_output_versioning<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_non_member_serialize<T, ArchiveType>::value || traits::is_output_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T const & t)
|
||||
{
|
||||
serialize(*self, const_cast<T &>(t));
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Member split (save)
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_member_save<T, ArchiveType>::value && !traits::has_invalid_output_versioning<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_member_save<T, ArchiveType>::value || traits::is_output_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T const & t)
|
||||
{
|
||||
access::member_save(*self, t);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Non member split (save)
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_non_member_save<T, ArchiveType>::value && !traits::has_invalid_output_versioning<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_non_member_save<T, ArchiveType>::value || traits::is_output_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T const & t)
|
||||
{
|
||||
save(*self, t);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Member split (save_minimal)
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_member_save_minimal<T, ArchiveType>::value && !traits::has_invalid_output_versioning<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_member_save_minimal<T, ArchiveType>::value || traits::is_output_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T const & t)
|
||||
{
|
||||
self->process( access::member_save_minimal(*self, t) );
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Non member split (save_minimal)
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_non_member_save_minimal<T, ArchiveType>::value && !traits::has_invalid_output_versioning<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_non_member_save_minimal<T, ArchiveType>::value || traits::is_output_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T const & t)
|
||||
{
|
||||
self->process( save_minimal(*self, t) );
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Empty class specialization
|
||||
template <class T> inline
|
||||
typename std::enable_if<(Flags & AllowEmptyClassElision) &&
|
||||
!traits::is_specialized<T, ArchiveType>::value &&
|
||||
!traits::is_output_serializable<T, ArchiveType>::value && std::is_empty<T>::value, ArchiveType &>::type
|
||||
processImpl(T const &)
|
||||
{
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! No matching serialization
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_invalid_output_versioning<T, ArchiveType>::value ||
|
||||
(!traits::is_specialized<T, ArchiveType>::value && !traits::is_output_serializable<T, ArchiveType>::value &&
|
||||
(!(Flags & AllowEmptyClassElision) || ((Flags & AllowEmptyClassElision) && !std::is_empty<T>::value))),
|
||||
ArchiveType &>::type
|
||||
processImpl(T const &)
|
||||
{
|
||||
static_assert(traits::is_output_serializable<T, ArchiveType>::value, "Trying to serialize an unserializable type with an output archive. \n\n "
|
||||
"Types must either have a serialize function, load/save pair, or load_minimal/save_minimal pair (you may not mix these). \n "
|
||||
"Use specialization (see access.hpp) if you need to disambiguate between serialize vs load/save functions. \n "
|
||||
"In addition, you may not mix versioned with non-versioned serialization functions. \n "
|
||||
"Serialize functions generally have the following signature: \n\n "
|
||||
"template<class Archive> \n "
|
||||
" void serialize(Archive & ar) \n "
|
||||
" { \n "
|
||||
" ar( member1, member2, member3 ); \n "
|
||||
" } \n\n " );
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Registers a class version with the archive and serializes it if necessary
|
||||
/*! If this is the first time this class has been serialized, we will record its
|
||||
version number and serialize that.
|
||||
|
||||
@tparam T The type of the class being serialized
|
||||
@param version The version number associated with it */
|
||||
template <class T> inline
|
||||
void registerClassVersion( const std::uint32_t version )
|
||||
{
|
||||
static const auto hash = std::type_index(typeid(T)).hash_code();
|
||||
const auto insertResult = itsVersionedTypes.insert( hash );
|
||||
if( insertResult.second ) // insertion took place, serialize the version number
|
||||
process( make_nvp<ArchiveType>("cereal_class_version", version) );
|
||||
}
|
||||
|
||||
//! Member serialization
|
||||
/*! Versioning implementation */
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_member_versioned_serialize<T, ArchiveType>::value && !traits::has_invalid_output_versioning<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_member_serialize<T, ArchiveType>::value || traits::is_output_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T const & t)
|
||||
{
|
||||
registerClassVersion<T>( detail::Version<T>::version );
|
||||
access::member_serialize(*self, const_cast<T &>(t), detail::Version<T>::version);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Non member serialization
|
||||
/*! Versioning implementation */
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_non_member_versioned_serialize<T, ArchiveType>::value && !traits::has_invalid_output_versioning<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_non_member_serialize<T, ArchiveType>::value || traits::is_output_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T const & t)
|
||||
{
|
||||
registerClassVersion<T>( detail::Version<T>::version );
|
||||
serialize(*self, const_cast<T &>(t), detail::Version<T>::version);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Member split (save)
|
||||
/*! Versioning implementation */
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_member_versioned_save<T, ArchiveType>::value && !traits::has_invalid_output_versioning<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_member_save<T, ArchiveType>::value || traits::is_output_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T const & t)
|
||||
{
|
||||
registerClassVersion<T>( detail::Version<T>::version );
|
||||
access::member_save(*self, t, detail::Version<T>::version);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Non member split (save)
|
||||
/*! Versioning implementation */
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_non_member_versioned_save<T, ArchiveType>::value && !traits::has_invalid_output_versioning<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_non_member_save<T, ArchiveType>::value || traits::is_output_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T const & t)
|
||||
{
|
||||
registerClassVersion<T>( detail::Version<T>::version );
|
||||
save(*self, t, detail::Version<T>::version);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Member split (save_minimal)
|
||||
/*! Versioning implementation */
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_member_versioned_save_minimal<T, ArchiveType>::value && !traits::has_invalid_output_versioning<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_member_save_minimal<T, ArchiveType>::value || traits::is_output_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T const & t)
|
||||
{
|
||||
registerClassVersion<T>( detail::Version<T>::version );
|
||||
self->process( access::member_save_minimal(*self, t, detail::Version<T>::version) );
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Non member split (save_minimal)
|
||||
/*! Versioning implementation */
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_non_member_versioned_save_minimal<T, ArchiveType>::value && !traits::has_invalid_output_versioning<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_non_member_save_minimal<T, ArchiveType>::value || traits::is_output_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T const & t)
|
||||
{
|
||||
registerClassVersion<T>( detail::Version<T>::version );
|
||||
self->process( save_minimal(*self, t, detail::Version<T>::version) );
|
||||
return *self;
|
||||
}
|
||||
|
||||
private:
|
||||
ArchiveType * const self;
|
||||
|
||||
//! A set of all base classes that have been serialized
|
||||
std::unordered_set<traits::detail::base_class_id, traits::detail::base_class_id_hash> itsBaseClassSet;
|
||||
|
||||
//! Maps from addresses to pointer ids
|
||||
std::unordered_map<void const *, std::uint32_t> itsSharedPointerMap;
|
||||
|
||||
//! The id to be given to the next pointer
|
||||
std::uint32_t itsCurrentPointerId;
|
||||
|
||||
//! Maps from polymorphic type name strings to ids
|
||||
std::unordered_map<char const *, std::uint32_t> itsPolymorphicTypeMap;
|
||||
|
||||
//! The id to be given to the next polymorphic type name
|
||||
std::uint32_t itsCurrentPolymorphicTypeId;
|
||||
|
||||
//! Keeps track of classes that have versioning information associated with them
|
||||
std::unordered_set<size_type> itsVersionedTypes;
|
||||
}; // class OutputArchive
|
||||
|
||||
// ######################################################################
|
||||
//! The base input archive class
|
||||
/*! This is the base input archive for all input archives. If you create
|
||||
a custom archive class, it should derive from this, passing itself as
|
||||
a template parameter for the ArchiveType.
|
||||
|
||||
The base class provides all of the functionality necessary to
|
||||
properly forward data to the correct serialization functions.
|
||||
|
||||
Individual archives should use a combination of prologue and
|
||||
epilogue functions together with specializations of serialize, save,
|
||||
and load to alter the functionality of their serialization.
|
||||
|
||||
@tparam ArchiveType The archive type that derives from InputArchive
|
||||
@tparam Flags Flags to control advanced functionality. See the Flags
|
||||
enum for more information.
|
||||
@ingroup Internal */
|
||||
template<class ArchiveType, std::uint32_t Flags = 0>
|
||||
class InputArchive : public detail::InputArchiveBase
|
||||
{
|
||||
public:
|
||||
//! Construct the output archive
|
||||
/*! @param derived A pointer to the derived ArchiveType (pass this from the derived archive) */
|
||||
InputArchive(ArchiveType * const derived) :
|
||||
self(derived),
|
||||
itsBaseClassSet(),
|
||||
itsSharedPointerMap(),
|
||||
itsPolymorphicTypeMap(),
|
||||
itsVersionedTypes()
|
||||
{ }
|
||||
|
||||
//! Serializes all passed in data
|
||||
/*! This is the primary interface for serializing data with an archive */
|
||||
template <class ... Types> inline
|
||||
ArchiveType & operator()( Types && ... args )
|
||||
{
|
||||
process( std::forward<Types>( args )... );
|
||||
return *self;
|
||||
}
|
||||
|
||||
/*! @name Boost Transition Layer
|
||||
Functionality that mirrors the syntax for Boost. This is useful if you are transitioning
|
||||
a large project from Boost to cereal. The preferred interface for cereal is using operator(). */
|
||||
//! @{
|
||||
|
||||
//! Serializes passed in data
|
||||
/*! This is a boost compatability layer and is not the preferred way of using
|
||||
cereal. If you are transitioning from boost, use this until you can
|
||||
transition to the operator() overload */
|
||||
template <class T> inline
|
||||
ArchiveType & operator&( T && arg )
|
||||
{
|
||||
self->process( std::forward<T>( arg ) );
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Serializes passed in data
|
||||
/*! This is a boost compatability layer and is not the preferred way of using
|
||||
cereal. If you are transitioning from boost, use this until you can
|
||||
transition to the operator() overload */
|
||||
template <class T> inline
|
||||
ArchiveType & operator>>( T && arg )
|
||||
{
|
||||
self->process( std::forward<T>( arg ) );
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! @}
|
||||
|
||||
//! Retrieves a shared pointer given a unique key for it
|
||||
/*! This is used to retrieve a previously registered shared_ptr
|
||||
which has already been loaded.
|
||||
|
||||
@param id The unique id that was serialized for the pointer
|
||||
@return A shared pointer to the data
|
||||
@throw Exception if the id does not exist */
|
||||
inline std::shared_ptr<void> getSharedPointer(std::uint32_t const id)
|
||||
{
|
||||
if(id == 0) return std::shared_ptr<void>(nullptr);
|
||||
|
||||
auto iter = itsSharedPointerMap.find( id );
|
||||
if(iter == itsSharedPointerMap.end())
|
||||
throw Exception("Error while trying to deserialize a smart pointer. Could not find id " + std::to_string(id));
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
//! Registers a shared pointer to its unique identifier
|
||||
/*! After a shared pointer has been allocated for the first time, it should
|
||||
be registered with its loaded id for future references to it.
|
||||
|
||||
@param id The unique identifier for the shared pointer
|
||||
@param ptr The actual shared pointer */
|
||||
inline void registerSharedPointer(std::uint32_t const id, std::shared_ptr<void> ptr)
|
||||
{
|
||||
std::uint32_t const stripped_id = id & ~detail::msb_32bit;
|
||||
itsSharedPointerMap[stripped_id] = ptr;
|
||||
}
|
||||
|
||||
//! Retrieves the string for a polymorphic type given a unique key for it
|
||||
/*! This is used to retrieve a string previously registered during
|
||||
a polymorphic load.
|
||||
|
||||
@param id The unique id that was serialized for the polymorphic type
|
||||
@return The string identifier for the tyep */
|
||||
inline std::string getPolymorphicName(std::uint32_t const id)
|
||||
{
|
||||
auto name = itsPolymorphicTypeMap.find( id );
|
||||
if(name == itsPolymorphicTypeMap.end())
|
||||
{
|
||||
throw Exception("Error while trying to deserialize a polymorphic pointer. Could not find type id " + std::to_string(id));
|
||||
}
|
||||
return name->second;
|
||||
}
|
||||
|
||||
//! Registers a polymorphic name string to its unique identifier
|
||||
/*! After a polymorphic type has been loaded for the first time, it should
|
||||
be registered with its loaded id for future references to it.
|
||||
|
||||
@param id The unique identifier for the polymorphic type
|
||||
@param name The name associated with the tyep */
|
||||
inline void registerPolymorphicName(std::uint32_t const id, std::string const & name)
|
||||
{
|
||||
std::uint32_t const stripped_id = id & ~detail::msb_32bit;
|
||||
itsPolymorphicTypeMap.insert( {stripped_id, name} );
|
||||
}
|
||||
|
||||
private:
|
||||
//! Serializes data after calling prologue, then calls epilogue
|
||||
template <class T> inline
|
||||
void process( T && head )
|
||||
{
|
||||
prologue( *self, head );
|
||||
self->processImpl( head );
|
||||
epilogue( *self, head );
|
||||
}
|
||||
|
||||
//! Unwinds to process all data
|
||||
template <class T, class ... Other> inline
|
||||
void process( T && head, Other && ... tail )
|
||||
{
|
||||
process( std::forward<T>( head ) );
|
||||
process( std::forward<Other>( tail )... );
|
||||
}
|
||||
|
||||
//! Serialization of a virtual_base_class wrapper
|
||||
/*! \sa virtual_base_class */
|
||||
template <class T> inline
|
||||
ArchiveType & processImpl(virtual_base_class<T> & b)
|
||||
{
|
||||
traits::detail::base_class_id id(b.base_ptr);
|
||||
if(itsBaseClassSet.count(id) == 0)
|
||||
{
|
||||
itsBaseClassSet.insert(id);
|
||||
self->processImpl( *b.base_ptr );
|
||||
}
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Serialization of a base_class wrapper
|
||||
/*! \sa base_class */
|
||||
template <class T> inline
|
||||
ArchiveType & processImpl(base_class<T> & b)
|
||||
{
|
||||
self->processImpl( *b.base_ptr );
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Member serialization
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_member_serialize<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_member_serialize<T, ArchiveType>::value || traits::is_input_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T & t)
|
||||
{
|
||||
access::member_serialize(*self, t);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Non member serialization
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_non_member_serialize<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_non_member_serialize<T, ArchiveType>::value || traits::is_input_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T & t)
|
||||
{
|
||||
serialize(*self, t);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Member split (load)
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_member_load<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_member_load<T, ArchiveType>::value || traits::is_input_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T & t)
|
||||
{
|
||||
access::member_load(*self, t);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Non member split (load)
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_non_member_load<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_non_member_load<T, ArchiveType>::value || traits::is_input_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T & t)
|
||||
{
|
||||
load(*self, t);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Member split (load_minimal)
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_member_load_minimal<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_member_load_minimal<T, ArchiveType>::value || traits::is_input_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T & t)
|
||||
{
|
||||
typename traits::has_member_save_minimal<T, ArchiveType>::type value;
|
||||
self->process( value );
|
||||
access::member_load_minimal(*self, t, value);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Non member split (load_minimal)
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_non_member_load_minimal<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_non_member_load_minimal<T, ArchiveType>::value || traits::is_input_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T & t)
|
||||
{
|
||||
typename traits::has_non_member_save_minimal<T, ArchiveType>::type value;
|
||||
self->process( value );
|
||||
load_minimal(*self, t, value);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Empty class specialization
|
||||
template <class T> inline
|
||||
typename std::enable_if<(Flags & AllowEmptyClassElision) &&
|
||||
!traits::is_specialized<T, ArchiveType>::value &&
|
||||
!traits::is_input_serializable<T, ArchiveType>::value && std::is_empty<T>::value, ArchiveType &>::type
|
||||
processImpl(T const &)
|
||||
{
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! No matching serialization
|
||||
template <class T> inline
|
||||
typename std::enable_if<!traits::is_specialized<T, ArchiveType>::value && !traits::is_input_serializable<T, ArchiveType>::value &&
|
||||
(!(Flags & AllowEmptyClassElision) || ((Flags & AllowEmptyClassElision) && !std::is_empty<T>::value)),
|
||||
ArchiveType &>::type
|
||||
processImpl(T const &)
|
||||
{
|
||||
static_assert(traits::is_output_serializable<T, ArchiveType>::value, "Trying to serialize an unserializable type with an output archive. \n\n "
|
||||
"Types must either have a serialize function, load/save pair, or load_minimal/save_minimal pair (you may not mix these). \n "
|
||||
"Use specialization (see access.hpp) if you need to disambiguate between serialize vs load/save functions. \n "
|
||||
"In addition, you may not mix versioned with non-versioned serialization functions. \n "
|
||||
"Serialize functions generally have the following signature: \n\n "
|
||||
"template<class Archive> \n "
|
||||
" void serialize(Archive & ar) \n "
|
||||
" { \n "
|
||||
" ar( member1, member2, member3 ); \n "
|
||||
" } \n\n " );
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Registers a class version with the archive and serializes it if necessary
|
||||
/*! If this is the first time this class has been serialized, we will record its
|
||||
version number and serialize that.
|
||||
|
||||
@tparam T The type of the class being serialized
|
||||
@param version The version number associated with it */
|
||||
template <class T> inline
|
||||
std::uint32_t loadClassVersion()
|
||||
{
|
||||
static const auto hash = std::type_index(typeid(T)).hash_code();
|
||||
auto lookupResult = itsVersionedTypes.find( hash );
|
||||
|
||||
if( lookupResult != itsVersionedTypes.end() ) // already exists
|
||||
return lookupResult->second;
|
||||
else // need to load
|
||||
{
|
||||
std::uint32_t version;
|
||||
|
||||
process( make_nvp<ArchiveType>("cereal_class_version", version) );
|
||||
itsVersionedTypes.emplace_hint( lookupResult, hash, version );
|
||||
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
||||
//! Member serialization
|
||||
/*! Versioning implementation */
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_member_versioned_serialize<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_member_serialize<T, ArchiveType>::value || traits::is_input_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T & t)
|
||||
{
|
||||
const auto version = loadClassVersion<T>();
|
||||
access::member_serialize(*self, t, version);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Non member serialization
|
||||
/*! Versioning implementation */
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_non_member_versioned_serialize<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_non_member_serialize<T, ArchiveType>::value || traits::is_input_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T & t)
|
||||
{
|
||||
const auto version = loadClassVersion<T>();
|
||||
serialize(*self, t, version);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Member split (load)
|
||||
/*! Versioning implementation */
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_member_versioned_load<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_member_load<T, ArchiveType>::value || traits::is_input_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T & t)
|
||||
{
|
||||
const auto version = loadClassVersion<T>();
|
||||
access::member_load(*self, t, version);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Non member split (load)
|
||||
/*! Versioning implementation */
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_non_member_versioned_load<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_non_member_load<T, ArchiveType>::value || traits::is_input_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T & t)
|
||||
{
|
||||
const auto version = loadClassVersion<T>();
|
||||
load(*self, t, version);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Member split (load_minimal)
|
||||
/*! Versioning implementation */
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_member_versioned_load_minimal<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_member_load_minimal<T, ArchiveType>::value || traits::is_input_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T & t)
|
||||
{
|
||||
const auto version = loadClassVersion<T>();
|
||||
typename traits::has_member_versioned_save_minimal<T, ArchiveType>::type value;
|
||||
self->process(value);
|
||||
access::member_load_minimal(*self, t, value, version);
|
||||
return *self;
|
||||
}
|
||||
|
||||
//! Non member split (load_minimal)
|
||||
/*! Versioning implementation */
|
||||
template <class T> inline
|
||||
typename std::enable_if<traits::has_non_member_versioned_load_minimal<T, ArchiveType>::value &&
|
||||
(traits::is_specialized_non_member_load_minimal<T, ArchiveType>::value || traits::is_input_serializable<T, ArchiveType>::value),
|
||||
ArchiveType &>::type
|
||||
processImpl(T & t)
|
||||
{
|
||||
const auto version = loadClassVersion<T>();
|
||||
typename traits::has_non_member_versioned_save_minimal<T, ArchiveType>::type value;
|
||||
self->process(value);
|
||||
load_minimal(*self, t, value, version);
|
||||
return *self;
|
||||
}
|
||||
|
||||
private:
|
||||
ArchiveType * const self;
|
||||
|
||||
//! A set of all base classes that have been serialized
|
||||
std::unordered_set<traits::detail::base_class_id, traits::detail::base_class_id_hash> itsBaseClassSet;
|
||||
|
||||
//! Maps from pointer ids to metadata
|
||||
std::unordered_map<std::uint32_t, std::shared_ptr<void>> itsSharedPointerMap;
|
||||
|
||||
//! Maps from name ids to names
|
||||
std::unordered_map<std::uint32_t, std::string> itsPolymorphicTypeMap;
|
||||
|
||||
//! Maps from type hash codes to version numbers
|
||||
std::unordered_map<std::size_t, std::uint32_t> itsVersionedTypes;
|
||||
}; // class InputArchive
|
||||
} // namespace cereal
|
||||
|
||||
// This include needs to come after things such as binary_data, make_nvp, etc
|
||||
#include "./types/common.hpp"
|
||||
|
||||
#endif // CEREAL_CEREAL_HPP_
|
451
3party/Alohalytics/src/cereal/include/details/helpers.hpp
Normal file
451
3party/Alohalytics/src/cereal/include/details/helpers.hpp
Normal file
|
@ -0,0 +1,451 @@
|
|||
/*! \file helpers.hpp
|
||||
\brief Internal helper functionality
|
||||
\ingroup Internal */
|
||||
/*
|
||||
Copyright (c) 2014, Randolph Voorhies, Shane Grant
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of cereal nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef CEREAL_DETAILS_HELPERS_HPP_
|
||||
#define CEREAL_DETAILS_HELPERS_HPP_
|
||||
|
||||
#include <type_traits>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../details/static_object.hpp"
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
// ######################################################################
|
||||
//! An exception class thrown when things go wrong at runtime
|
||||
/*! @ingroup Utility */
|
||||
struct Exception : public std::runtime_error
|
||||
{
|
||||
Exception( const std::string & what_ ) : std::runtime_error(what_) {}
|
||||
Exception( const char * what_ ) : std::runtime_error(what_) {}
|
||||
};
|
||||
|
||||
// ######################################################################
|
||||
//! The size type used by cereal
|
||||
/*! To ensure compatability between 32, 64, etc bit machines, we need to use
|
||||
* a fixed size type instead of size_t, which may vary from machine to
|
||||
* machine. */
|
||||
using size_type = uint64_t;
|
||||
|
||||
// forward decls
|
||||
class BinaryOutputArchive;
|
||||
class BinaryInputArchive;
|
||||
|
||||
// ######################################################################
|
||||
namespace detail
|
||||
{
|
||||
struct NameValuePairCore {};
|
||||
}
|
||||
|
||||
//! For holding name value pairs
|
||||
/*! This pairs a name (some string) with some value such that an archive
|
||||
can potentially take advantage of the pairing.
|
||||
|
||||
In serialization functions, NameValuePairs are usually created like so:
|
||||
@code{.cpp}
|
||||
struct MyStruct
|
||||
{
|
||||
int a, b, c, d, e;
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive & archive)
|
||||
{
|
||||
archive( CEREAL_NVP(a),
|
||||
CEREAL_NVP(b),
|
||||
CEREAL_NVP(c),
|
||||
CEREAL_NVP(d),
|
||||
CEREAL_NVP(e) );
|
||||
}
|
||||
};
|
||||
@endcode
|
||||
|
||||
Alternatively, you can give you data members custom names like so:
|
||||
@code{.cpp}
|
||||
struct MyStruct
|
||||
{
|
||||
int a, b, my_embarrassing_variable_name, d, e;
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive & archive)
|
||||
{
|
||||
archive( CEREAL_NVP(a),
|
||||
CEREAL_NVP(b),
|
||||
cereal::make_nvp("var", my_embarrassing_variable_name) );
|
||||
CEREAL_NVP(d),
|
||||
CEREAL_NVP(e) );
|
||||
}
|
||||
};
|
||||
@endcode
|
||||
|
||||
There is a slight amount of overhead to creating NameValuePairs, so there
|
||||
is a third method which will elide the names when they are not needed by
|
||||
the Archive:
|
||||
|
||||
@code{.cpp}
|
||||
struct MyStruct
|
||||
{
|
||||
int a, b;
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive & archive)
|
||||
{
|
||||
archive( cereal::make_nvp<Archive>(a),
|
||||
cereal::make_nvp<Archive>(b) );
|
||||
}
|
||||
};
|
||||
@endcode
|
||||
|
||||
This third method is generally only used when providing generic type
|
||||
support. Users writing their own serialize functions will normally
|
||||
explicitly control whether they want to use NVPs or not.
|
||||
|
||||
@internal */
|
||||
template <class T>
|
||||
class NameValuePair : detail::NameValuePairCore
|
||||
{
|
||||
private:
|
||||
// If we get passed an RValue, we'll just make a local copy if it here
|
||||
// otherwise, we store a reference. If we were passed an array, don't
|
||||
// decay the type - keep it as an array, and then proceed as normal
|
||||
// with the RValue business
|
||||
using DT = typename std::conditional<std::is_array<typename std::remove_reference<T>::type>::value,
|
||||
typename std::remove_cv<T>::type,
|
||||
typename std::decay<T>::type>::type;
|
||||
using Type = typename std::conditional<std::is_rvalue_reference<T>::value,
|
||||
DT,
|
||||
typename std::add_lvalue_reference<DT>::type>::type;
|
||||
// prevent nested nvps
|
||||
static_assert( !std::is_base_of<detail::NameValuePairCore, T>::value,
|
||||
"Cannot pair a name to a NameValuePair" );
|
||||
|
||||
public:
|
||||
//! Constructs a new NameValuePair
|
||||
/*! @param n The name of the pair
|
||||
@param v The value to pair. Ideally this should be an l-value reference so that
|
||||
the value can be both loaded and saved to. If you pass an r-value reference,
|
||||
the NameValuePair will store a copy of it instead of a reference. Thus you should
|
||||
only pass r-values in cases where this makes sense, such as the result of some
|
||||
size() call. In either case, any constness will be stripped away
|
||||
@internal */
|
||||
NameValuePair( char const * n, T && v ) : name(n), value(const_cast<Type>(v)) {}
|
||||
|
||||
char const * name;
|
||||
Type value;
|
||||
};
|
||||
|
||||
//! A specialization of make_nvp<> that simply forwards the value for binary archives
|
||||
/*! @relates NameValuePair
|
||||
@internal */
|
||||
template<class Archive, class T> inline
|
||||
typename
|
||||
std::enable_if<std::is_same<Archive, ::cereal::BinaryInputArchive>::value ||
|
||||
std::is_same<Archive, ::cereal::BinaryOutputArchive>::value,
|
||||
T && >::type
|
||||
make_nvp( const char *, T && value )
|
||||
{
|
||||
return std::forward<T>(value);
|
||||
}
|
||||
|
||||
//! A specialization of make_nvp<> that actually creates an nvp for non-binary archives
|
||||
/*! @relates NameValuePair
|
||||
@internal */
|
||||
template<class Archive, class T> inline
|
||||
typename
|
||||
std::enable_if<!std::is_same<Archive, ::cereal::BinaryInputArchive>::value &&
|
||||
!std::is_same<Archive, ::cereal::BinaryOutputArchive>::value,
|
||||
NameValuePair<T> >::type
|
||||
make_nvp( const char * name, T && value)
|
||||
{
|
||||
return {name, std::forward<T>(value)};
|
||||
}
|
||||
|
||||
//! Convenience for creating a templated NVP
|
||||
/*! For use in inteneral generic typing functions which have an
|
||||
Archive type declared
|
||||
@internal */
|
||||
#define _CEREAL_NVP(name, value) ::cereal::make_nvp<Archive>(name, value)
|
||||
|
||||
// ######################################################################
|
||||
//! A wrapper around data that can be serialized in a binary fashion
|
||||
/*! This class is used to demarcate data that can safely be serialized
|
||||
as a binary chunk of data. Individual archives can then choose how
|
||||
best represent this during serialization.
|
||||
|
||||
@internal */
|
||||
template <class T>
|
||||
struct BinaryData
|
||||
{
|
||||
//! Internally store the pointer as a void *, keeping const if created with
|
||||
//! a const pointer
|
||||
using PT = typename std::conditional<std::is_const<typename std::remove_pointer<T>::type>::value,
|
||||
const void *,
|
||||
void *>::type;
|
||||
|
||||
BinaryData( T && d, uint64_t s ) : data(d), size(s) {}
|
||||
|
||||
PT data; //!< pointer to beginning of data
|
||||
uint64_t size; //!< size in bytes
|
||||
};
|
||||
|
||||
// ######################################################################
|
||||
namespace detail
|
||||
{
|
||||
// base classes for type checking
|
||||
struct OutputArchiveBase {};
|
||||
struct InputArchiveBase {};
|
||||
|
||||
// forward decls for polymorphic support
|
||||
template <class Archive, class T> struct polymorphic_serialization_support;
|
||||
struct adl_tag;
|
||||
|
||||
// used during saving pointers
|
||||
static const int32_t msb_32bit = 0x80000000;
|
||||
static const int32_t msb2_32bit = 0x40000000;
|
||||
}
|
||||
|
||||
// ######################################################################
|
||||
//! A wrapper around size metadata
|
||||
/*! This class provides a way for archives to have more flexibility over how
|
||||
they choose to serialize size metadata for containers. For some archive
|
||||
types, the size may be implicitly encoded in the output (e.g. JSON) and
|
||||
not need an explicit entry. Specializing serialize or load/save for
|
||||
your archive and SizeTags allows you to choose what happens.
|
||||
|
||||
@internal */
|
||||
template <class T>
|
||||
class SizeTag
|
||||
{
|
||||
private:
|
||||
// If we get passed an RValue, we'll just make a local copy if it here
|
||||
// otherwise, we store a reference
|
||||
using DT = typename std::decay<T>::type;
|
||||
using Type = typename std::conditional<std::is_rvalue_reference<T>::value,
|
||||
DT,
|
||||
typename std::add_lvalue_reference<DT>::type>::type;
|
||||
|
||||
public:
|
||||
SizeTag( T && sz ) : size(const_cast<Type>(sz)) {}
|
||||
|
||||
Type size;
|
||||
};
|
||||
|
||||
// ######################################################################
|
||||
//! A wrapper around a key and value for serializing data into maps.
|
||||
/*! This class just provides a grouping of keys and values into a struct for
|
||||
human readable archives. For example, XML archives will use this wrapper
|
||||
to write maps like so:
|
||||
|
||||
@code{.xml}
|
||||
<mymap>
|
||||
<item0>
|
||||
<key>MyFirstKey</key>
|
||||
<value>MyFirstValue</value>
|
||||
</item0>
|
||||
<item1>
|
||||
<key>MySecondKey</key>
|
||||
<value>MySecondValue</value>
|
||||
</item1>
|
||||
</mymap>
|
||||
@endcode
|
||||
|
||||
\sa make_map_item
|
||||
@internal */
|
||||
template <class Key, class Value>
|
||||
struct MapItem
|
||||
{
|
||||
using DecayKey = typename std::decay<Key>::type;
|
||||
using KeyType = typename std::conditional<
|
||||
std::is_rvalue_reference<Key>::value,
|
||||
DecayKey,
|
||||
typename std::add_lvalue_reference<DecayKey>::type>::type;
|
||||
|
||||
using DecayValue = typename std::decay<Value>::type;
|
||||
using ValueType = typename std::conditional<
|
||||
std::is_rvalue_reference<Value>::value,
|
||||
DecayValue,
|
||||
typename std::add_lvalue_reference<DecayValue>::type>::type;
|
||||
|
||||
//! Construct a MapItem from a key and a value
|
||||
/*! @internal */
|
||||
MapItem( Key && key_, Value && value_ ) : key(const_cast<KeyType>(key_)), value(const_cast<ValueType>(value_)) {}
|
||||
|
||||
KeyType key;
|
||||
ValueType value;
|
||||
|
||||
//! Serialize the MapItem with the NVPs "key" and "value"
|
||||
template <class Archive> inline
|
||||
void serialize(Archive & archive)
|
||||
{
|
||||
archive( make_nvp<Archive>("key", key),
|
||||
make_nvp<Archive>("value", value) );
|
||||
}
|
||||
};
|
||||
|
||||
//! Create a MapItem so that human readable archives will group keys and values together
|
||||
/*! @internal
|
||||
@relates MapItem */
|
||||
template <class KeyType, class ValueType> inline
|
||||
MapItem<KeyType, ValueType> make_map_item(KeyType && key, ValueType && value)
|
||||
{
|
||||
return {std::forward<KeyType>(key), std::forward<ValueType>(value)};
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
// ######################################################################
|
||||
//! Holds all registered version information
|
||||
struct Versions
|
||||
{
|
||||
std::unordered_map<std::size_t, std::uint32_t> mapping;
|
||||
}; // struct Versions
|
||||
|
||||
//! Version information class
|
||||
/*! This is the base case for classes that have not been explicitly
|
||||
registered */
|
||||
template <class T> struct Version
|
||||
{
|
||||
static const std::uint32_t version = 0;
|
||||
// we don't need to explicitly register these types since they
|
||||
// always get a version number of 0
|
||||
};
|
||||
|
||||
#ifdef CEREAL_FUTURE_EXPERIMENTAL
|
||||
// ######################################################################
|
||||
//! A class that can store any type
|
||||
/*! This is inspired by boost::any and is intended to be a very light-weight
|
||||
replacement for internal use only.
|
||||
|
||||
This class is only here as a candidate for issue #46 (see github) and
|
||||
should be considered unsupported until a future version of cereal.
|
||||
*/
|
||||
class Any
|
||||
{
|
||||
private:
|
||||
//! Convenience alias for decay
|
||||
template <class T>
|
||||
using ST = typename std::decay<T>::type;
|
||||
|
||||
struct Base
|
||||
{
|
||||
virtual ~Base() {}
|
||||
virtual std::unique_ptr<Base> clone() const = 0;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct Derived : Base
|
||||
{
|
||||
template <class U>
|
||||
Derived( U && data ) : value( std::forward<U>( data ) ) {}
|
||||
|
||||
std::unique_ptr<Base> clone() const
|
||||
{
|
||||
return std::unique_ptr<Base>( new Derived<T>( value ) );
|
||||
}
|
||||
|
||||
T value;
|
||||
};
|
||||
|
||||
public:
|
||||
//! Construct from any type
|
||||
template <class T> inline
|
||||
Any( T && data ) :
|
||||
itsPtr( new Derived<ST<T>>( std::forward<T>( data ) ) )
|
||||
{ }
|
||||
|
||||
//! Convert to any type, if possible
|
||||
/*! Attempts to perform the conversion if possible,
|
||||
otherwise throws an exception.
|
||||
|
||||
@throw Exception if conversion is not possible (see get() for more info)
|
||||
@tparam The requested type to convert to */
|
||||
template <class T> inline
|
||||
operator T()
|
||||
{
|
||||
return get<ST<T>>();
|
||||
}
|
||||
|
||||
Any() : itsPtr() {}
|
||||
Any( Any & other ) : itsPtr( other.clone() ) {}
|
||||
Any( Any const & other ) : itsPtr( other.clone() ) {}
|
||||
Any( Any && other ) : itsPtr( std::move( other.itsPtr ) ) {}
|
||||
|
||||
Any & operator=( Any const & other )
|
||||
{
|
||||
if( itsPtr == other.itsPtr ) return *this;
|
||||
|
||||
auto cloned = other.clone();
|
||||
itsPtr = std::move( cloned );
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Any & operator=( Any && other )
|
||||
{
|
||||
if( itsPtr == other.itsPtr ) return *this;
|
||||
|
||||
itsPtr = std::move( other.itsPtr );
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
//! Get the contained type as type T
|
||||
/*! @tparam T The requested type to convert to
|
||||
@return The type converted to T
|
||||
@throw Exception if conversion is impossible */
|
||||
template <class T>
|
||||
ST<T> & get()
|
||||
{
|
||||
auto * derived = dynamic_cast<Derived<ST<T>> *>( itsPtr.get() );
|
||||
|
||||
if( !derived )
|
||||
throw ::cereal::Exception( "Invalid conversion requested on ASDFA" );
|
||||
|
||||
return derived->value;
|
||||
}
|
||||
|
||||
//! Clones the held data if it exists
|
||||
std::unique_ptr<Base> clone() const
|
||||
{
|
||||
if( itsPtr ) return itsPtr->clone();
|
||||
else return {};
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Base> itsPtr;
|
||||
}; // struct Any
|
||||
#endif // CEREAL_FUTURE_EXPERIMENTAL
|
||||
} // namespace detail
|
||||
} // namespace cereal
|
||||
|
||||
#endif // CEREAL_DETAILS_HELPERS_HPP_
|
|
@ -0,0 +1,414 @@
|
|||
/*! \file polymorphic_impl.hpp
|
||||
\brief Internal polymorphism support
|
||||
\ingroup Internal */
|
||||
/*
|
||||
Copyright (c) 2014, Randolph Voorhies, Shane Grant
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of cereal nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* This code is heavily inspired by the boost serialization implementation by the following authors
|
||||
|
||||
(C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
|
||||
Use, modification and distribution is subject to the Boost Software
|
||||
License, Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
See http://www.boost.org for updates, documentation, and revision history.
|
||||
|
||||
(C) Copyright 2006 David Abrahams - http://www.boost.org.
|
||||
|
||||
See /boost/serialization/export.hpp and /boost/archive/detail/register_archive.hpp for their
|
||||
implementation.
|
||||
*/
|
||||
#ifndef CEREAL_DETAILS_POLYMORPHIC_IMPL_HPP_
|
||||
#define CEREAL_DETAILS_POLYMORPHIC_IMPL_HPP_
|
||||
|
||||
#include "../details/static_object.hpp"
|
||||
#include "../types/memory.hpp"
|
||||
#include "../types/string.hpp"
|
||||
#include <functional>
|
||||
#include <typeindex>
|
||||
#include <map>
|
||||
|
||||
//! Binds a polymorhic type to all registered archives
|
||||
/*! This binds a polymorphic type to all registered archives that
|
||||
have been registered with CEREAL_REGISTER_ARCHIVE. This must be called
|
||||
after all archives are registered (usually after the archives themselves
|
||||
have been included). */
|
||||
#define CEREAL_BIND_TO_ARCHIVES(T) \
|
||||
namespace cereal { \
|
||||
namespace detail { \
|
||||
template<> \
|
||||
struct init_binding<T> { \
|
||||
static bind_to_archives<T> const & b; \
|
||||
}; \
|
||||
bind_to_archives<T> const & init_binding<T>::b = \
|
||||
::cereal::detail::StaticObject< \
|
||||
bind_to_archives<T> \
|
||||
>::getInstance().bind(); \
|
||||
}} // end namespaces
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
//! Binds a compile time type with a user defined string
|
||||
template <class T>
|
||||
struct binding_name {};
|
||||
|
||||
//! A structure holding a map from type_indices to output serializer functions
|
||||
/*! A static object of this map should be created for each registered archive
|
||||
type, containing entries for every registered type that describe how to
|
||||
properly cast the type to its real type in polymorphic scenarios for
|
||||
shared_ptr, weak_ptr, and unique_ptr. */
|
||||
template <class Archive>
|
||||
struct OutputBindingMap
|
||||
{
|
||||
//! A serializer function
|
||||
/*! Serializer functions return nothing and take an archive as
|
||||
their first parameter (will be cast properly inside the function,
|
||||
and a pointer to actual data (contents of smart_ptr's get() function)
|
||||
as their second parameter */
|
||||
typedef std::function<void(void*, void const *)> Serializer;
|
||||
|
||||
//! Struct containing the serializer functions for all pointer types
|
||||
struct Serializers
|
||||
{
|
||||
Serializer shared_ptr, //!< Serializer function for shared/weak pointers
|
||||
unique_ptr; //!< Serializer function for unique pointers
|
||||
};
|
||||
|
||||
//! A map of serializers for pointers of all registered types
|
||||
std::map<std::type_index, Serializers> map;
|
||||
};
|
||||
|
||||
//! An empty noop deleter
|
||||
template<class T> struct EmptyDeleter { void operator()(T *) const {} };
|
||||
|
||||
//! A structure holding a map from type name strings to input serializer functions
|
||||
/*! A static object of this map should be created for each registered archive
|
||||
type, containing entries for every registered type that describe how to
|
||||
properly cast the type to its real type in polymorphic scenarios for
|
||||
shared_ptr, weak_ptr, and unique_ptr. */
|
||||
template <class Archive>
|
||||
struct InputBindingMap
|
||||
{
|
||||
//! Shared ptr serializer function
|
||||
/*! Serializer functions return nothing and take an archive as
|
||||
their first parameter (will be cast properly inside the function,
|
||||
and a shared_ptr (or unique_ptr for the unique case) of any base
|
||||
type. Internally it will properly be loaded and cast to the
|
||||
correct type. */
|
||||
typedef std::function<void(void*, std::shared_ptr<void> & )> SharedSerializer;
|
||||
//! Unique ptr serializer function
|
||||
typedef std::function<void(void*, std::unique_ptr<void, EmptyDeleter<void>> & )> UniqueSerializer;
|
||||
|
||||
//! Struct containing the serializer functions for all pointer types
|
||||
struct Serializers
|
||||
{
|
||||
SharedSerializer shared_ptr; //!< Serializer function for shared/weak pointers
|
||||
UniqueSerializer unique_ptr; //!< Serializer function for unique pointers
|
||||
};
|
||||
|
||||
//! A map of serializers for pointers of all registered types
|
||||
std::map<std::string, Serializers> map;
|
||||
};
|
||||
|
||||
// forward decls for archives from cereal.hpp
|
||||
struct InputArchiveBase;
|
||||
struct OutputArchiveBase;
|
||||
|
||||
//! Creates a binding (map entry) between an input archive type and a polymorphic type
|
||||
/*! Bindings are made when types are registered, assuming that at least one
|
||||
archive has already been registered. When this struct is created,
|
||||
it will insert (at run time) an entry into a map that properly handles
|
||||
casting for serializing polymorphic objects */
|
||||
template <class Archive, class T> struct InputBindingCreator
|
||||
{
|
||||
//! Initialize the binding
|
||||
InputBindingCreator()
|
||||
{
|
||||
typename InputBindingMap<Archive>::Serializers serializers;
|
||||
|
||||
serializers.shared_ptr =
|
||||
[](void * arptr, std::shared_ptr<void> & dptr)
|
||||
{
|
||||
Archive & ar = *static_cast<Archive*>(arptr);
|
||||
std::shared_ptr<T> ptr;
|
||||
|
||||
ar( _CEREAL_NVP("ptr_wrapper", ::cereal::memory_detail::make_ptr_wrapper(ptr)) );
|
||||
|
||||
dptr = ptr;
|
||||
};
|
||||
|
||||
serializers.unique_ptr =
|
||||
[](void * arptr, std::unique_ptr<void, EmptyDeleter<void>> & dptr)
|
||||
{
|
||||
Archive & ar = *static_cast<Archive*>(arptr);
|
||||
std::unique_ptr<T> ptr;
|
||||
|
||||
ar( _CEREAL_NVP("ptr_wrapper", ::cereal::memory_detail::make_ptr_wrapper(ptr)) );
|
||||
|
||||
dptr.reset(ptr.release());
|
||||
};
|
||||
|
||||
StaticObject<InputBindingMap<Archive>>::getInstance().map.insert( { std::string(binding_name<T>::name()), serializers } );
|
||||
}
|
||||
};
|
||||
|
||||
//! Creates a binding (map entry) between an output archive type and a polymorphic type
|
||||
/*! Bindings are made when types are registered, assuming that at least one
|
||||
archive has already been registered. When this struct is created,
|
||||
it will insert (at run time) an entry into a map that properly handles
|
||||
casting for serializing polymorphic objects */
|
||||
template <class Archive, class T> struct OutputBindingCreator
|
||||
{
|
||||
//! Writes appropriate metadata to the archive for this polymorphic type
|
||||
static void writeMetadata(Archive & ar)
|
||||
{
|
||||
// Register the polymorphic type name with the archive, and get the id
|
||||
char const * name = binding_name<T>::name();
|
||||
std::uint32_t id = ar.registerPolymorphicType(name);
|
||||
|
||||
// Serialize the id
|
||||
ar( _CEREAL_NVP("polymorphic_id", id) );
|
||||
|
||||
// If the msb of the id is 1, then the type name is new, and we should serialize it
|
||||
if( id & detail::msb_32bit )
|
||||
{
|
||||
std::string namestring(name);
|
||||
ar( _CEREAL_NVP("polymorphic_name", namestring) );
|
||||
}
|
||||
}
|
||||
|
||||
//! Holds a properly typed shared_ptr to the polymorphic type
|
||||
class PolymorphicSharedPointerWrapper
|
||||
{
|
||||
public:
|
||||
/*! Wrap a raw polymorphic pointer in a shared_ptr to its true type
|
||||
|
||||
The wrapped pointer will not be responsible for ownership of the held pointer
|
||||
so it will not attempt to destroy it; instead the refcount of the wrapped
|
||||
pointer will be tied to a fake 'ownership pointer' that will do nothing
|
||||
when it ultimately goes out of scope.
|
||||
|
||||
The main reason for doing this, other than not to destroy the true object
|
||||
with our wrapper pointer, is to avoid meddling with the internal reference
|
||||
count in a polymorphic type that inherits from std::enable_shared_from_this.
|
||||
|
||||
@param dptr A void pointer to the contents of the shared_ptr to serialize */
|
||||
PolymorphicSharedPointerWrapper( void const * dptr ) : refCount()
|
||||
{
|
||||
#ifdef _LIBCPP_VERSION
|
||||
// libc++ needs this hacky workaround, see http://llvm.org/bugs/show_bug.cgi?id=18843
|
||||
wrappedPtr = std::shared_ptr<T const>(
|
||||
std::const_pointer_cast<T const>(
|
||||
std::shared_ptr<T>( refCount, static_cast<T *>(const_cast<void *>(dptr) ))));
|
||||
#else // NOT _LIBCPP_VERSION
|
||||
wrappedPtr = std::shared_ptr<T const>( refCount, static_cast<T const *>(dptr) );
|
||||
#endif // _LIBCPP_VERSION
|
||||
}
|
||||
|
||||
//! Get the wrapped shared_ptr */
|
||||
inline std::shared_ptr<T const> const & operator()() const { return wrappedPtr; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<void> refCount; //!< The ownership pointer
|
||||
std::shared_ptr<T const> wrappedPtr; //!< The wrapped pointer
|
||||
};
|
||||
|
||||
//! Does the actual work of saving a polymorphic shared_ptr
|
||||
/*! This function will properly create a shared_ptr from the void * that is passed in
|
||||
before passing it to the archive for serialization.
|
||||
|
||||
In addition, this will also preserve the state of any internal enable_shared_from_this mechanisms
|
||||
|
||||
@param ar The archive to serialize to
|
||||
@param dptr Pointer to the actual data held by the shared_ptr */
|
||||
static inline void savePolymorphicSharedPtr( Archive & ar, void const * dptr, std::true_type /* has_shared_from_this */ )
|
||||
{
|
||||
::cereal::memory_detail::EnableSharedStateHelper<T> state( static_cast<T *>(const_cast<void *>(dptr)) );
|
||||
PolymorphicSharedPointerWrapper psptr( dptr );
|
||||
ar( _CEREAL_NVP("ptr_wrapper", memory_detail::make_ptr_wrapper( psptr() ) ) );
|
||||
}
|
||||
|
||||
//! Does the actual work of saving a polymorphic shared_ptr
|
||||
/*! This function will properly create a shared_ptr from the void * that is passed in
|
||||
before passing it to the archive for serialization.
|
||||
|
||||
This version is for types that do not inherit from std::enable_shared_from_this.
|
||||
|
||||
@param ar The archive to serialize to
|
||||
@param dptr Pointer to the actual data held by the shared_ptr */
|
||||
static inline void savePolymorphicSharedPtr( Archive & ar, void const * dptr, std::false_type /* has_shared_from_this */ )
|
||||
{
|
||||
PolymorphicSharedPointerWrapper psptr( dptr );
|
||||
ar( _CEREAL_NVP("ptr_wrapper", memory_detail::make_ptr_wrapper( psptr() ) ) );
|
||||
}
|
||||
|
||||
//! Initialize the binding
|
||||
OutputBindingCreator()
|
||||
{
|
||||
typename OutputBindingMap<Archive>::Serializers serializers;
|
||||
|
||||
serializers.shared_ptr =
|
||||
[&](void * arptr, void const * dptr)
|
||||
{
|
||||
Archive & ar = *static_cast<Archive*>(arptr);
|
||||
|
||||
writeMetadata(ar);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
savePolymorphicSharedPtr( ar, dptr, ::cereal::traits::has_shared_from_this<T>::type() ); // MSVC doesn't like typename here
|
||||
#else // not _MSC_VER
|
||||
savePolymorphicSharedPtr( ar, dptr, typename ::cereal::traits::has_shared_from_this<T>::type() );
|
||||
#endif // _MSC_VER
|
||||
};
|
||||
|
||||
serializers.unique_ptr =
|
||||
[&](void * arptr, void const * dptr)
|
||||
{
|
||||
Archive & ar = *static_cast<Archive*>(arptr);
|
||||
|
||||
writeMetadata(ar);
|
||||
|
||||
std::unique_ptr<T const, EmptyDeleter<T const>> const ptr(static_cast<T const *>(dptr));
|
||||
|
||||
ar( _CEREAL_NVP("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) );
|
||||
};
|
||||
|
||||
StaticObject<OutputBindingMap<Archive>>::getInstance().map.insert( {std::type_index(typeid(T)), serializers} );
|
||||
}
|
||||
};
|
||||
|
||||
//! Used to help out argument dependent lookup for finding potential overloads
|
||||
//! of instantiate_polymorphic_binding
|
||||
struct adl_tag {};
|
||||
|
||||
//! Causes the static object bindings between an archive type and a serializable type T
|
||||
template <class Archive, class T>
|
||||
struct create_bindings
|
||||
{
|
||||
static const InputBindingCreator<Archive, T> &
|
||||
load(std::true_type)
|
||||
{
|
||||
return cereal::detail::StaticObject<InputBindingCreator<Archive, T>>::getInstance();
|
||||
}
|
||||
|
||||
static const OutputBindingCreator<Archive, T> &
|
||||
save(std::true_type)
|
||||
{
|
||||
return cereal::detail::StaticObject<OutputBindingCreator<Archive, T>>::getInstance();
|
||||
}
|
||||
|
||||
inline static void load(std::false_type) {}
|
||||
inline static void save(std::false_type) {}
|
||||
};
|
||||
|
||||
//! When specialized, causes the compiler to instantiate its parameter
|
||||
template <void(*)()>
|
||||
struct instantiate_function {};
|
||||
|
||||
/*! This struct is used as the return type of instantiate_polymorphic_binding
|
||||
for specific Archive types. When the compiler looks for overloads of
|
||||
instantiate_polymorphic_binding, it will be forced to instantiate this
|
||||
struct during overload resolution, even though it will not be part of a valid
|
||||
overload */
|
||||
template <class Archive, class T>
|
||||
struct polymorphic_serialization_support
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
//! Creates the appropriate bindings depending on whether the archive supports
|
||||
//! saving or loading
|
||||
virtual void instantiate();
|
||||
#else // NOT _MSC_VER
|
||||
//! Creates the appropriate bindings depending on whether the archive supports
|
||||
//! saving or loading
|
||||
static void instantiate();
|
||||
//! This typedef causes the compiler to instantiate this static function
|
||||
typedef instantiate_function<instantiate> unused;
|
||||
#endif // _MSC_VER
|
||||
};
|
||||
|
||||
// instantiate implementation
|
||||
template <class Archive, class T>
|
||||
void polymorphic_serialization_support<Archive,T>::instantiate()
|
||||
{
|
||||
create_bindings<Archive,T>::save( std::is_base_of<detail::OutputArchiveBase, Archive>() );
|
||||
|
||||
create_bindings<Archive,T>::load( std::is_base_of<detail::InputArchiveBase, Archive>() );
|
||||
}
|
||||
|
||||
//! Begins the binding process of a type to all registered archives
|
||||
/*! Archives need to be registered prior to this struct being instantiated via
|
||||
the CEREAL_REGISTER_ARCHIVE macro. Overload resolution will then force
|
||||
several static objects to be made that allow us to bind together all
|
||||
registered archive types with the parameter type T. */
|
||||
template <class T>
|
||||
struct bind_to_archives
|
||||
{
|
||||
//! Binding for non abstract types
|
||||
void bind(std::false_type) const
|
||||
{
|
||||
instantiate_polymorphic_binding((T*) 0, 0, adl_tag{});
|
||||
}
|
||||
|
||||
//! Binding for abstract types
|
||||
void bind(std::true_type) const
|
||||
{ }
|
||||
|
||||
//! Binds the type T to all registered archives
|
||||
/*! If T is abstract, we will not serialize it and thus
|
||||
do not need to make a binding */
|
||||
bind_to_archives const & bind() const
|
||||
{
|
||||
static_assert( std::is_polymorphic<T>::value,
|
||||
"Attempting to register non polymorphic type" );
|
||||
bind( std::is_abstract<T>() );
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
//! Used to hide the static object used to bind T to registered archives
|
||||
template <class T>
|
||||
struct init_binding;
|
||||
|
||||
//! Base case overload for instantiation
|
||||
/*! This will end up always being the best overload due to the second
|
||||
parameter always being passed as an int. All other overloads will
|
||||
accept pointers to archive types and have lower precedence than int.
|
||||
|
||||
Since the compiler needs to check all possible overloads, the
|
||||
other overloads created via CEREAL_REGISTER_ARCHIVE, which will have
|
||||
lower precedence due to requring a conversion from int to (Archive*),
|
||||
will cause their return types to be instantiated through the static object
|
||||
mechanisms even though they are never called.
|
||||
|
||||
See the documentation for the other functions to try and understand this */
|
||||
template <class T>
|
||||
void instantiate_polymorphic_binding( T*, int, adl_tag ) {}
|
||||
} // namespace detail
|
||||
} // namespace cereal
|
||||
|
||||
#endif // CEREAL_DETAILS_POLYMORPHIC_IMPL_HPP_
|
|
@ -0,0 +1,77 @@
|
|||
/*! \file static_object.hpp
|
||||
\brief Internal polymorphism static object support
|
||||
\ingroup Internal */
|
||||
/*
|
||||
Copyright (c) 2014, Randolph Voorhies, Shane Grant
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of cereal nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef CEREAL_DETAILS_STATIC_OBJECT_HPP_
|
||||
#define CEREAL_DETAILS_STATIC_OBJECT_HPP_
|
||||
|
||||
#include "../details/util.hpp"
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
//! A static, pre-execution object
|
||||
/*! This class will create a single copy (singleton) of some
|
||||
type and ensures that merely referencing this type will
|
||||
cause it to be instantiated and initialized pre-execution.
|
||||
|
||||
For example, this is used heavily in the polymorphic pointer
|
||||
serialization mechanisms to bind various archive types with
|
||||
different polymorphic classes */
|
||||
template <class T>
|
||||
class StaticObject
|
||||
{
|
||||
private:
|
||||
//! Forces instantiation at pre-execution time
|
||||
static void instantiate( T const & ) {}
|
||||
|
||||
static T & create()
|
||||
{
|
||||
static T t;
|
||||
instantiate(instance);
|
||||
return t;
|
||||
}
|
||||
|
||||
StaticObject( StaticObject const & /*other*/ ) {}
|
||||
|
||||
public:
|
||||
static T & getInstance()
|
||||
{
|
||||
return create();
|
||||
}
|
||||
|
||||
private:
|
||||
static T & instance;
|
||||
};
|
||||
|
||||
template <class T> T & StaticObject<T>::instance = StaticObject<T>::create();
|
||||
}
|
||||
} // namespace cereal
|
||||
|
||||
#endif // CEREAL_DETAILS_STATIC_OBJECT_HPP_
|
1273
3party/Alohalytics/src/cereal/include/details/traits.hpp
Normal file
1273
3party/Alohalytics/src/cereal/include/details/traits.hpp
Normal file
File diff suppressed because it is too large
Load diff
104
3party/Alohalytics/src/cereal/include/details/util.hpp
Normal file
104
3party/Alohalytics/src/cereal/include/details/util.hpp
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*! \file util.hpp
|
||||
\brief Internal misc utilities
|
||||
\ingroup Internal */
|
||||
/*
|
||||
Copyright (c) 2014, Randolph Voorhies, Shane Grant
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of cereal nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef CEREAL_DETAILS_UTIL_HPP_
|
||||
#define CEREAL_DETAILS_UTIL_HPP_
|
||||
|
||||
#include <typeinfo>
|
||||
#include <string>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
namespace cereal
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
//! Demangles the type encoded in a string
|
||||
/*! @internal */
|
||||
inline std::string demangle( std::string const & name )
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
//! Gets the demangled name of a type
|
||||
/*! @internal */
|
||||
template <class T> inline
|
||||
std::string demangledName()
|
||||
{
|
||||
return typeid( T ).name();
|
||||
}
|
||||
} // namespace util
|
||||
} // namespace cereal
|
||||
#else // clang or gcc
|
||||
|
||||
// Patch by Alex Zolotarev to avoid Cereal compilation error for Android.
|
||||
#ifndef ANDROID
|
||||
#include <cxxabi.h>
|
||||
#else
|
||||
namespace __cxxabiv1 {
|
||||
extern "C" char * __cxa_demangle(const char *mangled_name, char *output_buffer, size_t *length, int *status);
|
||||
}
|
||||
namespace abi = __cxxabiv1;
|
||||
// This one was copied directly from the NDK r10d.
|
||||
#include "../../cxa_demangle.cpp"
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
//! Demangles the type encoded in a string
|
||||
/*! @internal */
|
||||
inline std::string demangle(std::string mangledName)
|
||||
{
|
||||
int status = 0;
|
||||
char *demangledName = NULL;
|
||||
std::size_t len;
|
||||
|
||||
demangledName = abi::__cxa_demangle(mangledName.c_str(), 0, &len, &status);
|
||||
|
||||
std::string retName(demangledName);
|
||||
free(demangledName);
|
||||
|
||||
return retName;
|
||||
}
|
||||
|
||||
//! Gets the demangled name of a type
|
||||
/*! @internal */
|
||||
template<class T> inline
|
||||
std::string demangledName()
|
||||
{ return demangle(typeid(T).name()); }
|
||||
}
|
||||
} // namespace cereal
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#endif // CEREAL_DETAILS_UTIL_HPP_
|
125
3party/Alohalytics/src/cereal/include/external/base64.hpp
vendored
Normal file
125
3party/Alohalytics/src/cereal/include/external/base64.hpp
vendored
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
Copyright (C) 2004-2008 René Nyffenegger
|
||||
|
||||
This source code is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this source code must not be misrepresented; you must not
|
||||
claim that you wrote the original source code. If you use this source code
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original source code.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
*/
|
||||
|
||||
#ifndef CEREAL_EXTERNAL_BASE64_HPP_
|
||||
#define CEREAL_EXTERNAL_BASE64_HPP_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace base64
|
||||
{
|
||||
static const std::string chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
static inline bool is_base64(unsigned char c) {
|
||||
return (isalnum(c) || (c == '+') || (c == '/'));
|
||||
}
|
||||
|
||||
inline std::string encode(unsigned char const* bytes_to_encode, size_t in_len) {
|
||||
std::string ret;
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
unsigned char char_array_3[3];
|
||||
unsigned char char_array_4[4];
|
||||
|
||||
while (in_len--) {
|
||||
char_array_3[i++] = *(bytes_to_encode++);
|
||||
if (i == 3) {
|
||||
char_array_4[0] = (unsigned char) ((char_array_3[0] & 0xfc) >> 2);
|
||||
char_array_4[1] = (unsigned char) ( ( ( char_array_3[0] & 0x03 ) << 4 ) + ( ( char_array_3[1] & 0xf0 ) >> 4 ) );
|
||||
char_array_4[2] = (unsigned char) ( ( ( char_array_3[1] & 0x0f ) << 2 ) + ( ( char_array_3[2] & 0xc0 ) >> 6 ) );
|
||||
char_array_4[3] = (unsigned char) ( char_array_3[2] & 0x3f );
|
||||
|
||||
for(i = 0; (i <4) ; i++)
|
||||
ret += chars[char_array_4[i]];
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i)
|
||||
{
|
||||
for(j = i; j < 3; j++)
|
||||
char_array_3[j] = '\0';
|
||||
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (j = 0; (j < i + 1); j++)
|
||||
ret += chars[char_array_4[j]];
|
||||
|
||||
while((i++ < 3))
|
||||
ret += '=';
|
||||
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
inline std::string decode(std::string const& encoded_string) {
|
||||
size_t in_len = encoded_string.size();
|
||||
size_t i = 0;
|
||||
size_t j = 0;
|
||||
int in_ = 0;
|
||||
unsigned char char_array_4[4], char_array_3[3];
|
||||
std::string ret;
|
||||
|
||||
while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
|
||||
char_array_4[i++] = encoded_string[in_]; in_++;
|
||||
if (i ==4) {
|
||||
for (i = 0; i <4; i++)
|
||||
char_array_4[i] = (unsigned char) chars.find( char_array_4[i] );
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (i = 0; (i < 3); i++)
|
||||
ret += char_array_3[i];
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i) {
|
||||
for (j = i; j <4; j++)
|
||||
char_array_4[j] = 0;
|
||||
|
||||
for (j = 0; j <4; j++)
|
||||
char_array_4[j] = (unsigned char) chars.find( char_array_4[j] );
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
} // base64
|
||||
#endif // CEREAL_EXTERNAL_BASE64_HPP_
|
821
3party/Alohalytics/src/cereal/include/external/rapidjson/document.h
vendored
Normal file
821
3party/Alohalytics/src/cereal/include/external/rapidjson/document.h
vendored
Normal file
|
@ -0,0 +1,821 @@
|
|||
#ifndef RAPIDJSON_DOCUMENT_H_
|
||||
#define RAPIDJSON_DOCUMENT_H_
|
||||
|
||||
#include "reader.h"
|
||||
#include "internal/strfunc.h"
|
||||
#include <new> // placement new
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4127) // conditional expression is constant
|
||||
#endif
|
||||
|
||||
namespace rapidjson {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// GenericValue
|
||||
|
||||
//! Represents a JSON value. Use Value for UTF8 encoding and default allocator.
|
||||
/*!
|
||||
A JSON value can be one of 7 types. This class is a variant type supporting
|
||||
these types.
|
||||
|
||||
Use the Value if UTF8 and default allocator
|
||||
|
||||
\tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document)
|
||||
\tparam Allocator Allocator type for allocating memory of object, array and string.
|
||||
*/
|
||||
#pragma pack (push, 4)
|
||||
template <typename Encoding, typename Allocator = MemoryPoolAllocator<> >
|
||||
class GenericValue {
|
||||
public:
|
||||
//! Name-value pair in an object.
|
||||
struct Member {
|
||||
GenericValue<Encoding, Allocator> name; //!< name of member (must be a string)
|
||||
GenericValue<Encoding, Allocator> value; //!< value of member.
|
||||
};
|
||||
|
||||
typedef Encoding EncodingType; //!< Encoding type from template parameter.
|
||||
typedef Allocator AllocatorType; //!< Allocator type from template parameter.
|
||||
typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding.
|
||||
typedef Member* MemberIterator; //!< Member iterator for iterating in object.
|
||||
typedef const Member* ConstMemberIterator; //!< Constant member iterator for iterating in object.
|
||||
typedef GenericValue* ValueIterator; //!< Value iterator for iterating in array.
|
||||
typedef const GenericValue* ConstValueIterator; //!< Constant value iterator for iterating in array.
|
||||
|
||||
//!@name Constructors and destructor.
|
||||
//@{
|
||||
|
||||
//! Default constructor creates a null value.
|
||||
GenericValue() : flags_(kNull_Flag) {}
|
||||
|
||||
//! Copy constructor is not permitted.
|
||||
private:
|
||||
GenericValue(const GenericValue& rhs);
|
||||
|
||||
public:
|
||||
|
||||
//! Constructor with JSON value type.
|
||||
/*! This creates a Value of specified type with default content.
|
||||
\param type Type of the value.
|
||||
\note Default content for number is zero.
|
||||
*/
|
||||
GenericValue(Type type) {
|
||||
static const unsigned defaultFlags[7] = {
|
||||
kNull_Flag, kFalseFlag, kTrueFlag, kObjectFlag, kArrayFlag, kConstStringFlag,
|
||||
kNumberFlag | kIntFlag | kUintFlag | kInt64Flag | kUint64Flag | kDoubleFlag
|
||||
};
|
||||
RAPIDJSON_ASSERT(type <= kNumberType);
|
||||
flags_ = defaultFlags[type];
|
||||
memset(&data_, 0, sizeof(data_));
|
||||
}
|
||||
|
||||
//! Constructor for boolean value.
|
||||
GenericValue(bool b) : flags_(b ? kTrueFlag : kFalseFlag) {}
|
||||
|
||||
//! Constructor for int value.
|
||||
GenericValue(int i) : flags_(kNumberIntFlag) {
|
||||
data_.n.i64 = i;
|
||||
if (i >= 0)
|
||||
flags_ |= kUintFlag | kUint64Flag;
|
||||
}
|
||||
|
||||
//! Constructor for unsigned value.
|
||||
GenericValue(unsigned u) : flags_(kNumberUintFlag) {
|
||||
data_.n.u64 = u;
|
||||
if (!(u & 0x80000000))
|
||||
flags_ |= kIntFlag | kInt64Flag;
|
||||
}
|
||||
|
||||
//! Constructor for int64_t value.
|
||||
GenericValue(int64_t i64) : flags_(kNumberInt64Flag) {
|
||||
data_.n.i64 = i64;
|
||||
if (i64 >= 0) {
|
||||
flags_ |= kNumberUint64Flag;
|
||||
if (!(i64 & 0xFFFFFFFF00000000LL))
|
||||
flags_ |= kUintFlag;
|
||||
if (!(i64 & 0xFFFFFFFF80000000LL))
|
||||
flags_ |= kIntFlag;
|
||||
}
|
||||
else if (i64 >= -2147483648LL)
|
||||
flags_ |= kIntFlag;
|
||||
}
|
||||
|
||||
//! Constructor for uint64_t value.
|
||||
GenericValue(uint64_t u64) : flags_(kNumberUint64Flag) {
|
||||
data_.n.u64 = u64;
|
||||
if (!(u64 & 0x8000000000000000ULL))
|
||||
flags_ |= kInt64Flag;
|
||||
if (!(u64 & 0xFFFFFFFF00000000ULL))
|
||||
flags_ |= kUintFlag;
|
||||
if (!(u64 & 0xFFFFFFFF80000000ULL))
|
||||
flags_ |= kIntFlag;
|
||||
}
|
||||
|
||||
//! Constructor for double value.
|
||||
GenericValue(double d) : flags_(kNumberDoubleFlag) { data_.n.d = d; }
|
||||
|
||||
//! Constructor for constant string (i.e. do not make a copy of string)
|
||||
GenericValue(const Ch* s, SizeType length) {
|
||||
RAPIDJSON_ASSERT(s != NULL);
|
||||
flags_ = kConstStringFlag;
|
||||
data_.s.str = s;
|
||||
data_.s.length = length;
|
||||
}
|
||||
|
||||
//! Constructor for constant string (i.e. do not make a copy of string)
|
||||
GenericValue(const Ch* s) { SetStringRaw(s, internal::StrLen(s)); }
|
||||
|
||||
//! Constructor for copy-string (i.e. do make a copy of string)
|
||||
GenericValue(const Ch* s, SizeType length, Allocator& allocator) { SetStringRaw(s, length, allocator); }
|
||||
|
||||
//! Constructor for copy-string (i.e. do make a copy of string)
|
||||
GenericValue(const Ch*s, Allocator& allocator) { SetStringRaw(s, internal::StrLen(s), allocator); }
|
||||
|
||||
//! Destructor.
|
||||
/*! Need to destruct elements of array, members of object, or copy-string.
|
||||
*/
|
||||
~GenericValue() {
|
||||
if (Allocator::kNeedFree) { // Shortcut by Allocator's trait
|
||||
switch(flags_) {
|
||||
case kArrayFlag:
|
||||
for (GenericValue* v = data_.a.elements; v != data_.a.elements + data_.a.size; ++v)
|
||||
v->~GenericValue();
|
||||
Allocator::Free(data_.a.elements);
|
||||
break;
|
||||
|
||||
case kObjectFlag:
|
||||
for (Member* m = data_.o.members; m != data_.o.members + data_.o.size; ++m) {
|
||||
m->name.~GenericValue();
|
||||
m->value.~GenericValue();
|
||||
}
|
||||
Allocator::Free(data_.o.members);
|
||||
break;
|
||||
|
||||
case kCopyStringFlag:
|
||||
Allocator::Free(const_cast<Ch*>(data_.s.str));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//@}
|
||||
|
||||
//!@name Assignment operators
|
||||
//@{
|
||||
|
||||
//! Assignment with move semantics.
|
||||
/*! \param rhs Source of the assignment. It will become a null value after assignment.
|
||||
*/
|
||||
GenericValue& operator=(GenericValue& rhs) {
|
||||
RAPIDJSON_ASSERT(this != &rhs);
|
||||
this->~GenericValue();
|
||||
memcpy(this, &rhs, sizeof(GenericValue));
|
||||
rhs.flags_ = kNull_Flag;
|
||||
return *this;
|
||||
}
|
||||
|
||||
//! Assignment with primitive types.
|
||||
/*! \tparam T Either Type, int, unsigned, int64_t, uint64_t, const Ch*
|
||||
\param value The value to be assigned.
|
||||
*/
|
||||
template <typename T>
|
||||
GenericValue& operator=(T value) {
|
||||
this->~GenericValue();
|
||||
new (this) GenericValue(value);
|
||||
return *this;
|
||||
}
|
||||
//@}
|
||||
|
||||
//!@name Type
|
||||
//@{
|
||||
|
||||
Type GetType() const { return static_cast<Type>(flags_ & kTypeMask); }
|
||||
bool IsNull_() const { return flags_ == kNull_Flag; }
|
||||
bool IsFalse() const { return flags_ == kFalseFlag; }
|
||||
bool IsTrue() const { return flags_ == kTrueFlag; }
|
||||
bool IsBool_() const { return (flags_ & kBool_Flag) != 0; }
|
||||
bool IsObject() const { return flags_ == kObjectFlag; }
|
||||
bool IsArray() const { return flags_ == kArrayFlag; }
|
||||
bool IsNumber() const { return (flags_ & kNumberFlag) != 0; }
|
||||
bool IsInt() const { return (flags_ & kIntFlag) != 0; }
|
||||
bool IsUint() const { return (flags_ & kUintFlag) != 0; }
|
||||
bool IsInt64() const { return (flags_ & kInt64Flag) != 0; }
|
||||
bool IsUint64() const { return (flags_ & kUint64Flag) != 0; }
|
||||
bool IsDouble() const { return (flags_ & kDoubleFlag) != 0; }
|
||||
bool IsString() const { return (flags_ & kStringFlag) != 0; }
|
||||
|
||||
//@}
|
||||
|
||||
//!@name Null_
|
||||
//@{
|
||||
|
||||
GenericValue& SetNull_() { this->~GenericValue(); new (this) GenericValue(); return *this; }
|
||||
|
||||
//@}
|
||||
|
||||
//!@name Bool_
|
||||
//@{
|
||||
|
||||
bool GetBool_() const { RAPIDJSON_ASSERT(IsBool_()); return flags_ == kTrueFlag; }
|
||||
GenericValue& SetBool_(bool b) { this->~GenericValue(); new (this) GenericValue(b); return *this; }
|
||||
|
||||
//@}
|
||||
|
||||
//!@name Object
|
||||
//@{
|
||||
|
||||
//! Set this value as an empty object.
|
||||
GenericValue& SetObject() { this->~GenericValue(); new (this) GenericValue(kObjectType); return *this; }
|
||||
|
||||
//! Get the value associated with the object's name.
|
||||
GenericValue& operator[](const Ch* name) {
|
||||
if (Member* member = FindMember(name))
|
||||
return member->value;
|
||||
else {
|
||||
static GenericValue Null_Value;
|
||||
return Null_Value;
|
||||
}
|
||||
}
|
||||
const GenericValue& operator[](const Ch* name) const { return const_cast<GenericValue&>(*this)[name]; }
|
||||
|
||||
//! Member iterators.
|
||||
ConstMemberIterator MemberBegin() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.members; }
|
||||
ConstMemberIterator MemberEnd() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.members + data_.o.size; }
|
||||
MemberIterator MemberBegin() { RAPIDJSON_ASSERT(IsObject()); return data_.o.members; }
|
||||
MemberIterator MemberEnd() { RAPIDJSON_ASSERT(IsObject()); return data_.o.members + data_.o.size; }
|
||||
|
||||
//! Check whether a member exists in the object.
|
||||
bool HasMember(const Ch* name) const { return FindMember(name) != 0; }
|
||||
|
||||
//! Add a member (name-value pair) to the object.
|
||||
/*! \param name A string value as name of member.
|
||||
\param value Value of any type.
|
||||
\param allocator Allocator for reallocating memory.
|
||||
\return The value itself for fluent API.
|
||||
\note The ownership of name and value will be transfered to this object if success.
|
||||
*/
|
||||
GenericValue& AddMember(GenericValue& name, GenericValue& value, Allocator& allocator) {
|
||||
RAPIDJSON_ASSERT(IsObject());
|
||||
RAPIDJSON_ASSERT(name.IsString());
|
||||
Object& o = data_.o;
|
||||
if (o.size >= o.capacity) {
|
||||
if (o.capacity == 0) {
|
||||
o.capacity = kDefaultObjectCapacity;
|
||||
o.members = (Member*)allocator.Malloc(o.capacity * sizeof(Member));
|
||||
}
|
||||
else {
|
||||
SizeType oldCapacity = o.capacity;
|
||||
o.capacity *= 2;
|
||||
o.members = (Member*)allocator.Realloc(o.members, oldCapacity * sizeof(Member), o.capacity * sizeof(Member));
|
||||
}
|
||||
}
|
||||
o.members[o.size].name.RawAssign(name);
|
||||
o.members[o.size].value.RawAssign(value);
|
||||
o.size++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GenericValue& AddMember(const Ch* name, Allocator& nameAllocator, GenericValue& value, Allocator& allocator) {
|
||||
GenericValue n(name, internal::StrLen(name), nameAllocator);
|
||||
return AddMember(n, value, allocator);
|
||||
}
|
||||
|
||||
GenericValue& AddMember(const Ch* name, GenericValue& value, Allocator& allocator) {
|
||||
GenericValue n(name, internal::StrLen(name));
|
||||
return AddMember(n, value, allocator);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
GenericValue& AddMember(const Ch* name, T value, Allocator& allocator) {
|
||||
GenericValue n(name, internal::StrLen(name));
|
||||
GenericValue v(value);
|
||||
return AddMember(n, v, allocator);
|
||||
}
|
||||
|
||||
//! Remove a member in object by its name.
|
||||
/*! \param name Name of member to be removed.
|
||||
\return Whether the member existed.
|
||||
\note Removing member is implemented by moving the last member. So the ordering of members is changed.
|
||||
*/
|
||||
bool RemoveMember(const Ch* name) {
|
||||
RAPIDJSON_ASSERT(IsObject());
|
||||
if (Member* m = FindMember(name)) {
|
||||
RAPIDJSON_ASSERT(data_.o.size > 0);
|
||||
RAPIDJSON_ASSERT(data_.o.members != 0);
|
||||
|
||||
Member* last = data_.o.members + (data_.o.size - 1);
|
||||
if (data_.o.size > 1 && m != last) {
|
||||
// Move the last one to this place
|
||||
m->name = last->name;
|
||||
m->value = last->value;
|
||||
}
|
||||
else {
|
||||
// Only one left, just destroy
|
||||
m->name.~GenericValue();
|
||||
m->value.~GenericValue();
|
||||
}
|
||||
--data_.o.size;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//@}
|
||||
|
||||
//!@name Array
|
||||
//@{
|
||||
|
||||
//! Set this value as an empty array.
|
||||
GenericValue& SetArray() { this->~GenericValue(); new (this) GenericValue(kArrayType); return *this; }
|
||||
|
||||
//! Get the number of elements in array.
|
||||
SizeType Size() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size; }
|
||||
|
||||
//! Get the capacity of array.
|
||||
SizeType Capacity() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.capacity; }
|
||||
|
||||
//! Check whether the array is empty.
|
||||
bool Empty() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size == 0; }
|
||||
|
||||
//! Remove all elements in the array.
|
||||
/*! This function do not deallocate memory in the array, i.e. the capacity is unchanged.
|
||||
*/
|
||||
void Clear() {
|
||||
RAPIDJSON_ASSERT(IsArray());
|
||||
for (SizeType i = 0; i < data_.a.size; ++i)
|
||||
data_.a.elements[i].~GenericValue();
|
||||
data_.a.size = 0;
|
||||
}
|
||||
|
||||
//! Get an element from array by index.
|
||||
/*! \param index Zero-based index of element.
|
||||
\note
|
||||
\code
|
||||
Value a(kArrayType);
|
||||
a.PushBack(123);
|
||||
int x = a[0].GetInt(); // Error: operator[ is ambiguous, as 0 also mean a null pointer of const char* type.
|
||||
int y = a[SizeType(0)].GetInt(); // Cast to SizeType will work.
|
||||
int z = a[0u].GetInt(); // This works too.
|
||||
\endcode
|
||||
*/
|
||||
GenericValue& operator[](SizeType index) {
|
||||
RAPIDJSON_ASSERT(IsArray());
|
||||
RAPIDJSON_ASSERT(index < data_.a.size);
|
||||
return data_.a.elements[index];
|
||||
}
|
||||
const GenericValue& operator[](SizeType index) const { return const_cast<GenericValue&>(*this)[index]; }
|
||||
|
||||
//! Element iterator
|
||||
ValueIterator Begin() { RAPIDJSON_ASSERT(IsArray()); return data_.a.elements; }
|
||||
ValueIterator End() { RAPIDJSON_ASSERT(IsArray()); return data_.a.elements + data_.a.size; }
|
||||
ConstValueIterator Begin() const { return const_cast<GenericValue&>(*this).Begin(); }
|
||||
ConstValueIterator End() const { return const_cast<GenericValue&>(*this).End(); }
|
||||
|
||||
//! Request the array to have enough capacity to store elements.
|
||||
/*! \param newCapacity The capacity that the array at least need to have.
|
||||
\param allocator The allocator for allocating memory. It must be the same one use previously.
|
||||
\return The value itself for fluent API.
|
||||
*/
|
||||
GenericValue& Reserve(SizeType newCapacity, Allocator &allocator) {
|
||||
RAPIDJSON_ASSERT(IsArray());
|
||||
if (newCapacity > data_.a.capacity) {
|
||||
data_.a.elements = (GenericValue*)allocator.Realloc(data_.a.elements, data_.a.capacity * sizeof(GenericValue), newCapacity * sizeof(GenericValue));
|
||||
data_.a.capacity = newCapacity;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
//! Append a value at the end of the array.
|
||||
/*! \param value The value to be appended.
|
||||
\param allocator The allocator for allocating memory. It must be the same one use previously.
|
||||
\return The value itself for fluent API.
|
||||
\note The ownership of the value will be transfered to this object if success.
|
||||
\note If the number of elements to be appended is known, calls Reserve() once first may be more efficient.
|
||||
*/
|
||||
GenericValue& PushBack(GenericValue& value, Allocator& allocator) {
|
||||
RAPIDJSON_ASSERT(IsArray());
|
||||
if (data_.a.size >= data_.a.capacity)
|
||||
Reserve(data_.a.capacity == 0 ? kDefaultArrayCapacity : data_.a.capacity * 2, allocator);
|
||||
data_.a.elements[data_.a.size++].RawAssign(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
GenericValue& PushBack(T value, Allocator& allocator) {
|
||||
GenericValue v(value);
|
||||
return PushBack(v, allocator);
|
||||
}
|
||||
|
||||
//! Remove the last element in the array.
|
||||
GenericValue& PopBack() {
|
||||
RAPIDJSON_ASSERT(IsArray());
|
||||
RAPIDJSON_ASSERT(!Empty());
|
||||
data_.a.elements[--data_.a.size].~GenericValue();
|
||||
return *this;
|
||||
}
|
||||
//@}
|
||||
|
||||
//!@name Number
|
||||
//@{
|
||||
|
||||
int GetInt() const { RAPIDJSON_ASSERT(flags_ & kIntFlag); return data_.n.i.i; }
|
||||
unsigned GetUint() const { RAPIDJSON_ASSERT(flags_ & kUintFlag); return data_.n.u.u; }
|
||||
int64_t GetInt64() const { RAPIDJSON_ASSERT(flags_ & kInt64Flag); return data_.n.i64; }
|
||||
uint64_t GetUint64() const { RAPIDJSON_ASSERT(flags_ & kUint64Flag); return data_.n.u64; }
|
||||
|
||||
double GetDouble() const {
|
||||
RAPIDJSON_ASSERT(IsNumber());
|
||||
if ((flags_ & kDoubleFlag) != 0) return data_.n.d; // exact type, no conversion.
|
||||
if ((flags_ & kIntFlag) != 0) return data_.n.i.i; // int -> double
|
||||
if ((flags_ & kUintFlag) != 0) return data_.n.u.u; // unsigned -> double
|
||||
if ((flags_ & kInt64Flag) != 0) return (double)data_.n.i64; // int64_t -> double (may lose precision)
|
||||
RAPIDJSON_ASSERT((flags_ & kUint64Flag) != 0); return (double)data_.n.u64; // uint64_t -> double (may lose precision)
|
||||
}
|
||||
|
||||
GenericValue& SetInt(int i) { this->~GenericValue(); new (this) GenericValue(i); return *this; }
|
||||
GenericValue& SetUint(unsigned u) { this->~GenericValue(); new (this) GenericValue(u); return *this; }
|
||||
GenericValue& SetInt64(int64_t i64) { this->~GenericValue(); new (this) GenericValue(i64); return *this; }
|
||||
GenericValue& SetUint64(uint64_t u64) { this->~GenericValue(); new (this) GenericValue(u64); return *this; }
|
||||
GenericValue& SetDouble(double d) { this->~GenericValue(); new (this) GenericValue(d); return *this; }
|
||||
|
||||
//@}
|
||||
|
||||
//!@name String
|
||||
//@{
|
||||
|
||||
const Ch* GetString() const { RAPIDJSON_ASSERT(IsString()); return data_.s.str; }
|
||||
|
||||
//! Get the length of string.
|
||||
/*! Since rapidjson permits "\u0000" in the json string, strlen(v.GetString()) may not equal to v.GetStringLength().
|
||||
*/
|
||||
SizeType GetStringLength() const { RAPIDJSON_ASSERT(IsString()); return data_.s.length; }
|
||||
|
||||
//! Set this value as a string without copying source string.
|
||||
/*! This version has better performance with supplied length, and also support string containing null character.
|
||||
\param s source string pointer.
|
||||
\param length The length of source string, excluding the trailing null terminator.
|
||||
\return The value itself for fluent API.
|
||||
*/
|
||||
GenericValue& SetString(const Ch* s, SizeType length) { this->~GenericValue(); SetStringRaw(s, length); return *this; }
|
||||
|
||||
//! Set this value as a string without copying source string.
|
||||
/*! \param s source string pointer.
|
||||
\return The value itself for fluent API.
|
||||
*/
|
||||
GenericValue& SetString(const Ch* s) { return SetString(s, internal::StrLen(s)); }
|
||||
|
||||
//! Set this value as a string by copying from source string.
|
||||
/*! This version has better performance with supplied length, and also support string containing null character.
|
||||
\param s source string.
|
||||
\param length The length of source string, excluding the trailing null terminator.
|
||||
\param allocator Allocator for allocating copied buffer. Commonly use document.GetAllocator().
|
||||
\return The value itself for fluent API.
|
||||
*/
|
||||
GenericValue& SetString(const Ch* s, SizeType length, Allocator& allocator) { this->~GenericValue(); SetStringRaw(s, length, allocator); return *this; }
|
||||
|
||||
//! Set this value as a string by copying from source string.
|
||||
/*! \param s source string.
|
||||
\param allocator Allocator for allocating copied buffer. Commonly use document.GetAllocator().
|
||||
\return The value itself for fluent API.
|
||||
*/
|
||||
GenericValue& SetString(const Ch* s, Allocator& allocator) { SetString(s, internal::StrLen(s), allocator); return *this; }
|
||||
|
||||
//@}
|
||||
|
||||
//! Generate events of this value to a Handler.
|
||||
/*! This function adopts the GoF visitor pattern.
|
||||
Typical usage is to output this JSON value as JSON text via Writer, which is a Handler.
|
||||
It can also be used to deep clone this value via GenericDocument, which is also a Handler.
|
||||
\tparam Handler type of handler.
|
||||
\param handler An object implementing concept Handler.
|
||||
*/
|
||||
template <typename Handler>
|
||||
const GenericValue& Accept(Handler& handler) const {
|
||||
switch(GetType()) {
|
||||
case kNull_Type: handler.Null_(); break;
|
||||
case kFalseType: handler.Bool_(false); break;
|
||||
case kTrueType: handler.Bool_(true); break;
|
||||
|
||||
case kObjectType:
|
||||
handler.StartObject();
|
||||
for (Member* m = data_.o.members; m != data_.o.members + data_.o.size; ++m) {
|
||||
handler.String(m->name.data_.s.str, m->name.data_.s.length, false);
|
||||
m->value.Accept(handler);
|
||||
}
|
||||
handler.EndObject(data_.o.size);
|
||||
break;
|
||||
|
||||
case kArrayType:
|
||||
handler.StartArray();
|
||||
for (GenericValue* v = data_.a.elements; v != data_.a.elements + data_.a.size; ++v)
|
||||
v->Accept(handler);
|
||||
handler.EndArray(data_.a.size);
|
||||
break;
|
||||
|
||||
case kStringType:
|
||||
handler.String(data_.s.str, data_.s.length, false);
|
||||
break;
|
||||
|
||||
case kNumberType:
|
||||
if (IsInt()) handler.Int(data_.n.i.i);
|
||||
else if (IsUint()) handler.Uint(data_.n.u.u);
|
||||
else if (IsInt64()) handler.Int64(data_.n.i64);
|
||||
else if (IsUint64()) handler.Uint64(data_.n.u64);
|
||||
else handler.Double(data_.n.d);
|
||||
break;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename, typename>
|
||||
friend class GenericDocument;
|
||||
|
||||
enum {
|
||||
kBool_Flag = 0x100,
|
||||
kNumberFlag = 0x200,
|
||||
kIntFlag = 0x400,
|
||||
kUintFlag = 0x800,
|
||||
kInt64Flag = 0x1000,
|
||||
kUint64Flag = 0x2000,
|
||||
kDoubleFlag = 0x4000,
|
||||
kStringFlag = 0x100000,
|
||||
kCopyFlag = 0x200000,
|
||||
|
||||
// Initial flags of different types.
|
||||
kNull_Flag = kNull_Type,
|
||||
kTrueFlag = kTrueType | kBool_Flag,
|
||||
kFalseFlag = kFalseType | kBool_Flag,
|
||||
kNumberIntFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag,
|
||||
kNumberUintFlag = kNumberType | kNumberFlag | kUintFlag | kUint64Flag | kInt64Flag,
|
||||
kNumberInt64Flag = kNumberType | kNumberFlag | kInt64Flag,
|
||||
kNumberUint64Flag = kNumberType | kNumberFlag | kUint64Flag,
|
||||
kNumberDoubleFlag = kNumberType | kNumberFlag | kDoubleFlag,
|
||||
kConstStringFlag = kStringType | kStringFlag,
|
||||
kCopyStringFlag = kStringType | kStringFlag | kCopyFlag,
|
||||
kObjectFlag = kObjectType,
|
||||
kArrayFlag = kArrayType,
|
||||
|
||||
kTypeMask = 0xFF // bitwise-and with mask of 0xFF can be optimized by compiler
|
||||
};
|
||||
|
||||
static const SizeType kDefaultArrayCapacity = 16;
|
||||
static const SizeType kDefaultObjectCapacity = 16;
|
||||
|
||||
struct String {
|
||||
const Ch* str;
|
||||
SizeType length;
|
||||
unsigned hashcode; //!< reserved
|
||||
}; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode
|
||||
|
||||
// By using proper binary layout, retrieval of different integer types do not need conversions.
|
||||
union Number {
|
||||
#if RAPIDJSON_ENDIAN == RAPIDJSON_LITTLEENDIAN
|
||||
struct I {
|
||||
int i;
|
||||
char padding[4];
|
||||
}i;
|
||||
struct U {
|
||||
unsigned u;
|
||||
char padding2[4];
|
||||
}u;
|
||||
#else
|
||||
struct I {
|
||||
char padding[4];
|
||||
int i;
|
||||
}i;
|
||||
struct U {
|
||||
char padding2[4];
|
||||
unsigned u;
|
||||
}u;
|
||||
#endif
|
||||
int64_t i64;
|
||||
uint64_t u64;
|
||||
double d;
|
||||
}; // 8 bytes
|
||||
|
||||
struct Object {
|
||||
Member* members;
|
||||
SizeType size;
|
||||
SizeType capacity;
|
||||
}; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode
|
||||
|
||||
struct Array {
|
||||
GenericValue<Encoding, Allocator>* elements;
|
||||
SizeType size;
|
||||
SizeType capacity;
|
||||
}; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode
|
||||
|
||||
union Data {
|
||||
String s;
|
||||
Number n;
|
||||
Object o;
|
||||
Array a;
|
||||
}; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode
|
||||
|
||||
//! Find member by name.
|
||||
Member* FindMember(const Ch* name) {
|
||||
RAPIDJSON_ASSERT(name);
|
||||
RAPIDJSON_ASSERT(IsObject());
|
||||
|
||||
SizeType length = internal::StrLen(name);
|
||||
|
||||
Object& o = data_.o;
|
||||
for (Member* member = o.members; member != data_.o.members + data_.o.size; ++member)
|
||||
if (length == member->name.data_.s.length && memcmp(member->name.data_.s.str, name, length * sizeof(Ch)) == 0)
|
||||
return member;
|
||||
|
||||
return 0;
|
||||
}
|
||||
const Member* FindMember(const Ch* name) const { return const_cast<GenericValue&>(*this).FindMember(name); }
|
||||
|
||||
// Initialize this value as array with initial data, without calling destructor.
|
||||
void SetArrayRaw(GenericValue* values, SizeType count, Allocator& alloctaor) {
|
||||
flags_ = kArrayFlag;
|
||||
data_.a.elements = (GenericValue*)alloctaor.Malloc(count * sizeof(GenericValue));
|
||||
memcpy(data_.a.elements, values, count * sizeof(GenericValue));
|
||||
data_.a.size = data_.a.capacity = count;
|
||||
}
|
||||
|
||||
//! Initialize this value as object with initial data, without calling destructor.
|
||||
void SetObjectRaw(Member* members, SizeType count, Allocator& alloctaor) {
|
||||
flags_ = kObjectFlag;
|
||||
data_.o.members = (Member*)alloctaor.Malloc(count * sizeof(Member));
|
||||
memcpy(data_.o.members, members, count * sizeof(Member));
|
||||
data_.o.size = data_.o.capacity = count;
|
||||
}
|
||||
|
||||
//! Initialize this value as constant string, without calling destructor.
|
||||
void SetStringRaw(const Ch* s, SizeType length) {
|
||||
RAPIDJSON_ASSERT(s != NULL);
|
||||
flags_ = kConstStringFlag;
|
||||
data_.s.str = s;
|
||||
data_.s.length = length;
|
||||
}
|
||||
|
||||
//! Initialize this value as copy string with initial data, without calling destructor.
|
||||
void SetStringRaw(const Ch* s, SizeType length, Allocator& allocator) {
|
||||
RAPIDJSON_ASSERT(s != NULL);
|
||||
flags_ = kCopyStringFlag;
|
||||
data_.s.str = (Ch *)allocator.Malloc((length + 1) * sizeof(Ch));
|
||||
data_.s.length = length;
|
||||
memcpy(const_cast<Ch*>(data_.s.str), s, length * sizeof(Ch));
|
||||
const_cast<Ch*>(data_.s.str)[length] = '\0';
|
||||
}
|
||||
|
||||
//! Assignment without calling destructor
|
||||
void RawAssign(GenericValue& rhs) {
|
||||
memcpy(this, &rhs, sizeof(GenericValue));
|
||||
rhs.flags_ = kNull_Flag;
|
||||
}
|
||||
|
||||
Data data_;
|
||||
unsigned flags_;
|
||||
};
|
||||
#pragma pack (pop)
|
||||
|
||||
//! Value with UTF8 encoding.
|
||||
typedef GenericValue<UTF8<> > Value;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// GenericDocument
|
||||
|
||||
//! A document for parsing JSON text as DOM.
|
||||
/*!
|
||||
\implements Handler
|
||||
\tparam Encoding encoding for both parsing and string storage.
|
||||
\tparam Alloactor allocator for allocating memory for the DOM, and the stack during parsing.
|
||||
*/
|
||||
template <typename Encoding, typename Allocator = MemoryPoolAllocator<> >
|
||||
class GenericDocument : public GenericValue<Encoding, Allocator> {
|
||||
public:
|
||||
typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding.
|
||||
typedef GenericValue<Encoding, Allocator> ValueType; //!< Value type of the document.
|
||||
typedef Allocator AllocatorType; //!< Allocator type from template parameter.
|
||||
|
||||
//! Constructor
|
||||
/*! \param allocator Optional allocator for allocating stack memory.
|
||||
\param stackCapacity Initial capacity of stack in bytes.
|
||||
*/
|
||||
GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(allocator, stackCapacity), parseError_(0), errorOffset_(0) {}
|
||||
|
||||
//! Parse JSON text from an input stream.
|
||||
/*! \tparam parseFlags Combination of ParseFlag.
|
||||
\param stream Input stream to be parsed.
|
||||
\return The document itself for fluent API.
|
||||
*/
|
||||
template <unsigned parseFlags, typename Stream>
|
||||
GenericDocument& ParseStream(Stream& stream) {
|
||||
ValueType::SetNull_(); // Remove existing root if exist
|
||||
GenericReader<Encoding, Allocator> reader;
|
||||
if (reader.template Parse<parseFlags>(stream, *this)) {
|
||||
RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object
|
||||
this->RawAssign(*stack_.template Pop<ValueType>(1)); // Add this-> to prevent issue 13.
|
||||
parseError_ = 0;
|
||||
errorOffset_ = 0;
|
||||
}
|
||||
else {
|
||||
parseError_ = reader.GetParseError();
|
||||
errorOffset_ = reader.GetErrorOffset();
|
||||
ClearStack();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
//! Parse JSON text from a mutable string.
|
||||
/*! \tparam parseFlags Combination of ParseFlag.
|
||||
\param str Mutable zero-terminated string to be parsed.
|
||||
\return The document itself for fluent API.
|
||||
*/
|
||||
template <unsigned parseFlags>
|
||||
GenericDocument& ParseInsitu(Ch* str) {
|
||||
GenericInsituStringStream<Encoding> s(str);
|
||||
return ParseStream<parseFlags | kParseInsituFlag>(s);
|
||||
}
|
||||
|
||||
//! Parse JSON text from a read-only string.
|
||||
/*! \tparam parseFlags Combination of ParseFlag (must not contain kParseInsituFlag).
|
||||
\param str Read-only zero-terminated string to be parsed.
|
||||
*/
|
||||
template <unsigned parseFlags>
|
||||
GenericDocument& Parse(const Ch* str) {
|
||||
RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag));
|
||||
GenericStringStream<Encoding> s(str);
|
||||
return ParseStream<parseFlags>(s);
|
||||
}
|
||||
|
||||
//! Whether a parse error was occured in the last parsing.
|
||||
bool HasParseError() const { return parseError_ != 0; }
|
||||
|
||||
//! Get the message of parsing error.
|
||||
const char* GetParseError() const { return parseError_; }
|
||||
|
||||
//! Get the offset in character of the parsing error.
|
||||
size_t GetErrorOffset() const { return errorOffset_; }
|
||||
|
||||
//! Get the allocator of this document.
|
||||
Allocator& GetAllocator() { return stack_.GetAllocator(); }
|
||||
|
||||
//! Get the capacity of stack in bytes.
|
||||
size_t GetStackCapacity() const { return stack_.GetCapacity(); }
|
||||
|
||||
private:
|
||||
// Prohibit assignment
|
||||
GenericDocument& operator=(const GenericDocument&);
|
||||
|
||||
friend class GenericReader<Encoding, Allocator>; // for Reader to call the following private handler functions
|
||||
|
||||
// Implementation of Handler
|
||||
void Null_() { new (stack_.template Push<ValueType>()) ValueType(); }
|
||||
void Bool_(bool b) { new (stack_.template Push<ValueType>()) ValueType(b); }
|
||||
void Int(int i) { new (stack_.template Push<ValueType>()) ValueType(i); }
|
||||
void Uint(unsigned i) { new (stack_.template Push<ValueType>()) ValueType(i); }
|
||||
void Int64(int64_t i) { new (stack_.template Push<ValueType>()) ValueType(i); }
|
||||
void Uint64(uint64_t i) { new (stack_.template Push<ValueType>()) ValueType(i); }
|
||||
void Double(double d) { new (stack_.template Push<ValueType>()) ValueType(d); }
|
||||
|
||||
void String(const Ch* str, SizeType length, bool copy) {
|
||||
if (copy)
|
||||
new (stack_.template Push<ValueType>()) ValueType(str, length, GetAllocator());
|
||||
else
|
||||
new (stack_.template Push<ValueType>()) ValueType(str, length);
|
||||
}
|
||||
|
||||
void StartObject() { new (stack_.template Push<ValueType>()) ValueType(kObjectType); }
|
||||
|
||||
void EndObject(SizeType memberCount) {
|
||||
typename ValueType::Member* members = stack_.template Pop<typename ValueType::Member>(memberCount);
|
||||
stack_.template Top<ValueType>()->SetObjectRaw(members, (SizeType)memberCount, GetAllocator());
|
||||
}
|
||||
|
||||
void StartArray() { new (stack_.template Push<ValueType>()) ValueType(kArrayType); }
|
||||
|
||||
void EndArray(SizeType elementCount) {
|
||||
ValueType* elements = stack_.template Pop<ValueType>(elementCount);
|
||||
stack_.template Top<ValueType>()->SetArrayRaw(elements, elementCount, GetAllocator());
|
||||
}
|
||||
|
||||
void ClearStack() {
|
||||
if (Allocator::kNeedFree)
|
||||
while (stack_.GetSize() > 0) // Here assumes all elements in stack array are GenericValue (Member is actually 2 GenericValue objects)
|
||||
(stack_.template Pop<ValueType>(1))->~ValueType();
|
||||
else
|
||||
stack_.Clear();
|
||||
}
|
||||
|
||||
static const size_t kDefaultStackCapacity = 1024;
|
||||
internal::Stack<Allocator> stack_;
|
||||
const char* parseError_;
|
||||
size_t errorOffset_;
|
||||
};
|
||||
|
||||
typedef GenericDocument<UTF8<> > Document;
|
||||
|
||||
} // namespace rapidjson
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif // RAPIDJSON_DOCUMENT_H_
|
47
3party/Alohalytics/src/cereal/include/external/rapidjson/filestream.h
vendored
Normal file
47
3party/Alohalytics/src/cereal/include/external/rapidjson/filestream.h
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
#ifndef RAPIDJSON_FILESTREAM_H_
|
||||
#define RAPIDJSON_FILESTREAM_H_
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace rapidjson {
|
||||
|
||||
//! Wrapper of C file stream for input or output.
|
||||
/*!
|
||||
This simple wrapper does not check the validity of the stream.
|
||||
\implements Stream
|
||||
*/
|
||||
class FileStream {
|
||||
public:
|
||||
typedef char Ch; //!< Character type. Only support char.
|
||||
|
||||
FileStream(FILE* fp) : fp_(fp), count_(0) { Read(); }
|
||||
|
||||
char Peek() const { return current_; }
|
||||
char Take() { char c = current_; Read(); return c; }
|
||||
size_t Tell() const { return count_; }
|
||||
void Put(char c) { fputc(c, fp_); }
|
||||
|
||||
// Not implemented
|
||||
char* PutBegin() { return 0; }
|
||||
size_t PutEnd(char*) { return 0; }
|
||||
|
||||
private:
|
||||
void Read() {
|
||||
RAPIDJSON_ASSERT(fp_ != 0);
|
||||
int c = fgetc(fp_);
|
||||
if (c != EOF) {
|
||||
current_ = (char)c;
|
||||
count_++;
|
||||
}
|
||||
else
|
||||
current_ = '\0';
|
||||
}
|
||||
|
||||
FILE* fp_;
|
||||
char current_;
|
||||
size_t count_;
|
||||
};
|
||||
|
||||
} // namespace rapidjson
|
||||
|
||||
#endif // RAPIDJSON_FILESTREAM_H_
|
94
3party/Alohalytics/src/cereal/include/external/rapidjson/genericstream.h
vendored
Normal file
94
3party/Alohalytics/src/cereal/include/external/rapidjson/genericstream.h
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
// Generic*Stream code from https://code.google.com/p/rapidjson/issues/detail?id=20
|
||||
#ifndef RAPIDJSON_GENERICSTREAM_H_
|
||||
#define RAPIDJSON_GENERICSTREAM_H_
|
||||
|
||||
#include "rapidjson.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace rapidjson {
|
||||
|
||||
//! Wrapper of std::istream for input.
|
||||
class GenericReadStream {
|
||||
public:
|
||||
typedef char Ch; //!< Character type (byte).
|
||||
|
||||
//! Constructor.
|
||||
/*!
|
||||
\param is Input stream.
|
||||
*/
|
||||
GenericReadStream(std::istream & is) : is_(&is) {
|
||||
}
|
||||
|
||||
|
||||
Ch Peek() const {
|
||||
if(is_->eof()) return '\0';
|
||||
return static_cast<char>(is_->peek());
|
||||
}
|
||||
|
||||
Ch Take() {
|
||||
if(is_->eof()) return '\0';
|
||||
return static_cast<char>(is_->get());
|
||||
}
|
||||
|
||||
size_t Tell() const {
|
||||
return (int)is_->tellg();
|
||||
}
|
||||
|
||||
// Not implemented
|
||||
void Put(Ch) { RAPIDJSON_ASSERT(false); }
|
||||
void Flush() { RAPIDJSON_ASSERT(false); }
|
||||
Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
|
||||
size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; }
|
||||
|
||||
std::istream * is_;
|
||||
};
|
||||
|
||||
|
||||
//! Wrapper of std::ostream for output.
|
||||
class GenericWriteStream {
|
||||
public:
|
||||
typedef char Ch; //!< Character type. Only support char.
|
||||
|
||||
//! Constructor
|
||||
/*!
|
||||
\param os Output stream.
|
||||
*/
|
||||
GenericWriteStream(std::ostream& os) : os_(os) {
|
||||
}
|
||||
|
||||
void Put(char c) {
|
||||
os_.put(c);
|
||||
}
|
||||
|
||||
void PutN(char c, size_t n) {
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
Put(c);
|
||||
}
|
||||
}
|
||||
|
||||
void Flush() {
|
||||
os_.flush();
|
||||
}
|
||||
|
||||
size_t Tell() const {
|
||||
return (int)os_.tellp();
|
||||
}
|
||||
|
||||
// Not implemented
|
||||
char Peek() const { RAPIDJSON_ASSERT(false); }
|
||||
char Take() { RAPIDJSON_ASSERT(false); }
|
||||
char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
|
||||
size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; }
|
||||
|
||||
private:
|
||||
std::ostream& os_;
|
||||
};
|
||||
|
||||
template<>
|
||||
inline void PutN(GenericWriteStream& stream, char c, size_t n) {
|
||||
stream.PutN(c, n);
|
||||
}
|
||||
|
||||
} // namespace rapidjson
|
||||
|
||||
#endif // RAPIDJSON_GENERICSTREAM_H_
|
54
3party/Alohalytics/src/cereal/include/external/rapidjson/internal/pow10.h
vendored
Normal file
54
3party/Alohalytics/src/cereal/include/external/rapidjson/internal/pow10.h
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
#ifndef RAPIDJSON_POW10_
|
||||
#define RAPIDJSON_POW10_
|
||||
|
||||
namespace rapidjson {
|
||||
namespace internal {
|
||||
|
||||
//! Computes integer powers of 10 in double (10.0^n).
|
||||
/*! This function uses lookup table for fast and accurate results.
|
||||
\param n positive/negative exponent. Must <= 308.
|
||||
\return 10.0^n
|
||||
*/
|
||||
inline double Pow10(int n) {
|
||||
static const double e[] = { // 1e-308...1e308: 617 * 8 bytes = 4936 bytes
|
||||
1e-308,1e-307,1e-306,1e-305,1e-304,1e-303,1e-302,1e-301,1e-300,
|
||||
1e-299,1e-298,1e-297,1e-296,1e-295,1e-294,1e-293,1e-292,1e-291,1e-290,1e-289,1e-288,1e-287,1e-286,1e-285,1e-284,1e-283,1e-282,1e-281,1e-280,
|
||||
1e-279,1e-278,1e-277,1e-276,1e-275,1e-274,1e-273,1e-272,1e-271,1e-270,1e-269,1e-268,1e-267,1e-266,1e-265,1e-264,1e-263,1e-262,1e-261,1e-260,
|
||||
1e-259,1e-258,1e-257,1e-256,1e-255,1e-254,1e-253,1e-252,1e-251,1e-250,1e-249,1e-248,1e-247,1e-246,1e-245,1e-244,1e-243,1e-242,1e-241,1e-240,
|
||||
1e-239,1e-238,1e-237,1e-236,1e-235,1e-234,1e-233,1e-232,1e-231,1e-230,1e-229,1e-228,1e-227,1e-226,1e-225,1e-224,1e-223,1e-222,1e-221,1e-220,
|
||||
1e-219,1e-218,1e-217,1e-216,1e-215,1e-214,1e-213,1e-212,1e-211,1e-210,1e-209,1e-208,1e-207,1e-206,1e-205,1e-204,1e-203,1e-202,1e-201,1e-200,
|
||||
1e-199,1e-198,1e-197,1e-196,1e-195,1e-194,1e-193,1e-192,1e-191,1e-190,1e-189,1e-188,1e-187,1e-186,1e-185,1e-184,1e-183,1e-182,1e-181,1e-180,
|
||||
1e-179,1e-178,1e-177,1e-176,1e-175,1e-174,1e-173,1e-172,1e-171,1e-170,1e-169,1e-168,1e-167,1e-166,1e-165,1e-164,1e-163,1e-162,1e-161,1e-160,
|
||||
1e-159,1e-158,1e-157,1e-156,1e-155,1e-154,1e-153,1e-152,1e-151,1e-150,1e-149,1e-148,1e-147,1e-146,1e-145,1e-144,1e-143,1e-142,1e-141,1e-140,
|
||||
1e-139,1e-138,1e-137,1e-136,1e-135,1e-134,1e-133,1e-132,1e-131,1e-130,1e-129,1e-128,1e-127,1e-126,1e-125,1e-124,1e-123,1e-122,1e-121,1e-120,
|
||||
1e-119,1e-118,1e-117,1e-116,1e-115,1e-114,1e-113,1e-112,1e-111,1e-110,1e-109,1e-108,1e-107,1e-106,1e-105,1e-104,1e-103,1e-102,1e-101,1e-100,
|
||||
1e-99, 1e-98, 1e-97, 1e-96, 1e-95, 1e-94, 1e-93, 1e-92, 1e-91, 1e-90, 1e-89, 1e-88, 1e-87, 1e-86, 1e-85, 1e-84, 1e-83, 1e-82, 1e-81, 1e-80,
|
||||
1e-79, 1e-78, 1e-77, 1e-76, 1e-75, 1e-74, 1e-73, 1e-72, 1e-71, 1e-70, 1e-69, 1e-68, 1e-67, 1e-66, 1e-65, 1e-64, 1e-63, 1e-62, 1e-61, 1e-60,
|
||||
1e-59, 1e-58, 1e-57, 1e-56, 1e-55, 1e-54, 1e-53, 1e-52, 1e-51, 1e-50, 1e-49, 1e-48, 1e-47, 1e-46, 1e-45, 1e-44, 1e-43, 1e-42, 1e-41, 1e-40,
|
||||
1e-39, 1e-38, 1e-37, 1e-36, 1e-35, 1e-34, 1e-33, 1e-32, 1e-31, 1e-30, 1e-29, 1e-28, 1e-27, 1e-26, 1e-25, 1e-24, 1e-23, 1e-22, 1e-21, 1e-20,
|
||||
1e-19, 1e-18, 1e-17, 1e-16, 1e-15, 1e-14, 1e-13, 1e-12, 1e-11, 1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1e+0,
|
||||
1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20,
|
||||
1e+21, 1e+22, 1e+23, 1e+24, 1e+25, 1e+26, 1e+27, 1e+28, 1e+29, 1e+30, 1e+31, 1e+32, 1e+33, 1e+34, 1e+35, 1e+36, 1e+37, 1e+38, 1e+39, 1e+40,
|
||||
1e+41, 1e+42, 1e+43, 1e+44, 1e+45, 1e+46, 1e+47, 1e+48, 1e+49, 1e+50, 1e+51, 1e+52, 1e+53, 1e+54, 1e+55, 1e+56, 1e+57, 1e+58, 1e+59, 1e+60,
|
||||
1e+61, 1e+62, 1e+63, 1e+64, 1e+65, 1e+66, 1e+67, 1e+68, 1e+69, 1e+70, 1e+71, 1e+72, 1e+73, 1e+74, 1e+75, 1e+76, 1e+77, 1e+78, 1e+79, 1e+80,
|
||||
1e+81, 1e+82, 1e+83, 1e+84, 1e+85, 1e+86, 1e+87, 1e+88, 1e+89, 1e+90, 1e+91, 1e+92, 1e+93, 1e+94, 1e+95, 1e+96, 1e+97, 1e+98, 1e+99, 1e+100,
|
||||
1e+101,1e+102,1e+103,1e+104,1e+105,1e+106,1e+107,1e+108,1e+109,1e+110,1e+111,1e+112,1e+113,1e+114,1e+115,1e+116,1e+117,1e+118,1e+119,1e+120,
|
||||
1e+121,1e+122,1e+123,1e+124,1e+125,1e+126,1e+127,1e+128,1e+129,1e+130,1e+131,1e+132,1e+133,1e+134,1e+135,1e+136,1e+137,1e+138,1e+139,1e+140,
|
||||
1e+141,1e+142,1e+143,1e+144,1e+145,1e+146,1e+147,1e+148,1e+149,1e+150,1e+151,1e+152,1e+153,1e+154,1e+155,1e+156,1e+157,1e+158,1e+159,1e+160,
|
||||
1e+161,1e+162,1e+163,1e+164,1e+165,1e+166,1e+167,1e+168,1e+169,1e+170,1e+171,1e+172,1e+173,1e+174,1e+175,1e+176,1e+177,1e+178,1e+179,1e+180,
|
||||
1e+181,1e+182,1e+183,1e+184,1e+185,1e+186,1e+187,1e+188,1e+189,1e+190,1e+191,1e+192,1e+193,1e+194,1e+195,1e+196,1e+197,1e+198,1e+199,1e+200,
|
||||
1e+201,1e+202,1e+203,1e+204,1e+205,1e+206,1e+207,1e+208,1e+209,1e+210,1e+211,1e+212,1e+213,1e+214,1e+215,1e+216,1e+217,1e+218,1e+219,1e+220,
|
||||
1e+221,1e+222,1e+223,1e+224,1e+225,1e+226,1e+227,1e+228,1e+229,1e+230,1e+231,1e+232,1e+233,1e+234,1e+235,1e+236,1e+237,1e+238,1e+239,1e+240,
|
||||
1e+241,1e+242,1e+243,1e+244,1e+245,1e+246,1e+247,1e+248,1e+249,1e+250,1e+251,1e+252,1e+253,1e+254,1e+255,1e+256,1e+257,1e+258,1e+259,1e+260,
|
||||
1e+261,1e+262,1e+263,1e+264,1e+265,1e+266,1e+267,1e+268,1e+269,1e+270,1e+271,1e+272,1e+273,1e+274,1e+275,1e+276,1e+277,1e+278,1e+279,1e+280,
|
||||
1e+281,1e+282,1e+283,1e+284,1e+285,1e+286,1e+287,1e+288,1e+289,1e+290,1e+291,1e+292,1e+293,1e+294,1e+295,1e+296,1e+297,1e+298,1e+299,1e+300,
|
||||
1e+301,1e+302,1e+303,1e+304,1e+305,1e+306,1e+307,1e+308
|
||||
};
|
||||
RAPIDJSON_ASSERT(n <= 308);
|
||||
return n < -308 ? 0.0 : e[n + 308];
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace rapidjson
|
||||
|
||||
#endif // RAPIDJSON_POW10_
|
82
3party/Alohalytics/src/cereal/include/external/rapidjson/internal/stack.h
vendored
Normal file
82
3party/Alohalytics/src/cereal/include/external/rapidjson/internal/stack.h
vendored
Normal file
|
@ -0,0 +1,82 @@
|
|||
#ifndef RAPIDJSON_INTERNAL_STACK_H_
|
||||
#define RAPIDJSON_INTERNAL_STACK_H_
|
||||
|
||||
namespace rapidjson {
|
||||
namespace internal {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Stack
|
||||
|
||||
//! A type-unsafe stack for storing different types of data.
|
||||
/*! \tparam Allocator Allocator for allocating stack memory.
|
||||
*/
|
||||
template <typename Allocator>
|
||||
class Stack {
|
||||
public:
|
||||
Stack(Allocator* allocator, size_t stack_capacity) : allocator_(allocator), own_allocator_(0), stack_(0), stack_top_(0), stack_end_(0), stack_capacity_(stack_capacity) {
|
||||
RAPIDJSON_ASSERT(stack_capacity_ > 0);
|
||||
if (!allocator_)
|
||||
own_allocator_ = allocator_ = new Allocator();
|
||||
stack_top_ = stack_ = (char*)allocator_->Malloc(stack_capacity_);
|
||||
stack_end_ = stack_ + stack_capacity_;
|
||||
}
|
||||
|
||||
~Stack() {
|
||||
Allocator::Free(stack_);
|
||||
delete own_allocator_; // Only delete if it is owned by the stack
|
||||
}
|
||||
|
||||
void Clear() { /*stack_top_ = 0;*/ stack_top_ = stack_; }
|
||||
|
||||
template<typename T>
|
||||
T* Push(size_t count = 1) {
|
||||
// Expand the stack if needed
|
||||
if (stack_top_ + sizeof(T) * count >= stack_end_) {
|
||||
size_t new_capacity = stack_capacity_ * 2;
|
||||
size_t size = GetSize();
|
||||
size_t new_size = GetSize() + sizeof(T) * count;
|
||||
if (new_capacity < new_size)
|
||||
new_capacity = new_size;
|
||||
stack_ = (char*)allocator_->Realloc(stack_, stack_capacity_, new_capacity);
|
||||
stack_capacity_ = new_capacity;
|
||||
stack_top_ = stack_ + size;
|
||||
stack_end_ = stack_ + stack_capacity_;
|
||||
}
|
||||
T* ret = (T*)stack_top_;
|
||||
stack_top_ += sizeof(T) * count;
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* Pop(size_t count) {
|
||||
RAPIDJSON_ASSERT(GetSize() >= count * sizeof(T));
|
||||
stack_top_ -= count * sizeof(T);
|
||||
return (T*)stack_top_;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* Top() {
|
||||
RAPIDJSON_ASSERT(GetSize() >= sizeof(T));
|
||||
return (T*)(stack_top_ - sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* Bottom() { return (T*)stack_; }
|
||||
|
||||
Allocator& GetAllocator() { return *allocator_; }
|
||||
size_t GetSize() const { return stack_top_ - stack_; }
|
||||
size_t GetCapacity() const { return stack_capacity_; }
|
||||
|
||||
private:
|
||||
Allocator* allocator_;
|
||||
Allocator* own_allocator_;
|
||||
char *stack_;
|
||||
char *stack_top_;
|
||||
char *stack_end_;
|
||||
size_t stack_capacity_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace rapidjson
|
||||
|
||||
#endif // RAPIDJSON_STACK_H_
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue