Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
D
djinni
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
cpp-libs
djinni
Commits
f0aec36d
Commit
f0aec36d
authored
May 15, 2015
by
Jacob Potter
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow interface objects to be implemented in both ObjC and C++.
parent
64fb18b8
Changes
26
Hide whitespace changes
Inline
Side-by-side
Showing
26 changed files
with
411 additions
and
145 deletions
+411
-145
example/generated-src/objc/TXSSortItems+Private.mm
example/generated-src/objc/TXSSortItems+Private.mm
+22
-17
example/generated-src/objc/TXSTextboxListener+Private.mm
example/generated-src/objc/TXSTextboxListener+Private.mm
+12
-3
src/source/ObjcGenerator.scala
src/source/ObjcGenerator.scala
+1
-1
src/source/ObjcMarshal.scala
src/source/ObjcMarshal.scala
+4
-4
src/source/ObjcppGenerator.scala
src/source/ObjcppGenerator.scala
+71
-37
test-suite/djinni/test.djinni
test-suite/djinni/test.djinni
+1
-0
test-suite/djinni/token.djinni
test-suite/djinni/token.djinni
+2
-1
test-suite/generated-src/cpp/test_helpers.hpp
test-suite/generated-src/cpp/test_helpers.hpp
+2
-0
test-suite/generated-src/cpp/token.hpp
test-suite/generated-src/cpp/token.hpp
+4
-0
test-suite/generated-src/java/com/dropbox/djinni/test/TestHelpers.java
...nerated-src/java/com/dropbox/djinni/test/TestHelpers.java
+2
-0
test-suite/generated-src/java/com/dropbox/djinni/test/Token.java
...ite/generated-src/java/com/dropbox/djinni/test/Token.java
+9
-0
test-suite/generated-src/jni/NativeTestHelpers.cpp
test-suite/generated-src/jni/NativeTestHelpers.cpp
+9
-0
test-suite/generated-src/jni/NativeToken.cpp
test-suite/generated-src/jni/NativeToken.cpp
+19
-0
test-suite/generated-src/jni/NativeToken.hpp
test-suite/generated-src/jni/NativeToken.hpp
+2
-0
test-suite/generated-src/objc/DBClientInterface+Private.mm
test-suite/generated-src/objc/DBClientInterface+Private.mm
+12
-3
test-suite/generated-src/objc/DBConstantsInterface+Private.mm
...-suite/generated-src/objc/DBConstantsInterface+Private.mm
+22
-17
test-suite/generated-src/objc/DBCppException+Private.mm
test-suite/generated-src/objc/DBCppException+Private.mm
+22
-17
test-suite/generated-src/objc/DBTestHelpers+Private.mm
test-suite/generated-src/objc/DBTestHelpers+Private.mm
+34
-21
test-suite/generated-src/objc/DBTestHelpers.h
test-suite/generated-src/objc/DBTestHelpers.h
+8
-5
test-suite/generated-src/objc/DBToken+Private.h
test-suite/generated-src/objc/DBToken+Private.h
+2
-2
test-suite/generated-src/objc/DBToken+Private.mm
test-suite/generated-src/objc/DBToken+Private.mm
+54
-15
test-suite/generated-src/objc/DBToken.h
test-suite/generated-src/objc/DBToken.h
+3
-1
test-suite/handwritten-src/cpp/test_helpers.cpp
test-suite/handwritten-src/cpp/test_helpers.cpp
+9
-1
test-suite/handwritten-src/java/com/dropbox/djinni/test/TokenTest.java
...ndwritten-src/java/com/dropbox/djinni/test/TokenTest.java
+20
-0
test-suite/handwritten-src/objc/tests/DBTokenTests.mm
test-suite/handwritten-src/objc/tests/DBTokenTests.mm
+61
-0
test-suite/objc/DjinniObjcTest.xcodeproj/project.pbxproj
test-suite/objc/DjinniObjcTest.xcodeproj/project.pbxproj
+4
-0
No files found.
example/generated-src/objc/TXSSortItems+Private.mm
View file @
f0aec36d
...
...
@@ -22,23 +22,6 @@ static_assert(__has_feature(objc_arc), "Djinni requires ARC to be enabled for th
@end
namespace
djinni_generated
{
auto
SortItems
::
toCpp
(
ObjcType
objc
)
->
CppType
{
return
objc
?
objc
.
cppRef
.
get
()
:
nullptr
;
}
auto
SortItems
::
fromCpp
(
const
CppType
&
cpp
)
->
ObjcType
{
return
!
cpp
?
nil
:
::
djinni
::
DbxCppWrapperCache
<::
textsort
::
SortItems
>::
getInstance
()
->
get
(
cpp
,
[]
(
const
CppType
&
p
)
{
return
[[
TXSSortItems
alloc
]
initWithCpp
:
p
];
});
}
}
// namespace djinni_generated
@implementation
TXSSortItems
-
(
id
)
initWithCpp
:(
const
std
::
shared_ptr
<::
textsort
::
SortItems
>&
)
cppRef
...
...
@@ -65,3 +48,25 @@ auto SortItems::fromCpp(const CppType& cpp) -> ObjcType
}
@end
namespace
djinni_generated
{
auto
SortItems
::
toCpp
(
ObjcType
objc
)
->
CppType
{
if
(
!
objc
)
{
return
nullptr
;
}
return
objc
.
cppRef
.
get
();
}
auto
SortItems
::
fromCpp
(
const
CppType
&
cpp
)
->
ObjcType
{
if
(
!
cpp
)
{
return
nil
;
}
return
::
djinni
::
DbxCppWrapperCache
<::
textsort
::
SortItems
>::
getInstance
()
->
get
(
cpp
,
[]
(
const
CppType
&
p
)
{
return
[[
TXSSortItems
alloc
]
initWithCpp
:
p
];
});
}
}
// namespace djinni_generated
example/generated-src/objc/TXSTextboxListener+Private.mm
View file @
f0aec36d
...
...
@@ -24,15 +24,24 @@ public:
}
};
}
// namespace djinni_generated
namespace
djinni_generated
{
auto
TextboxListener
::
toCpp
(
ObjcType
objc
)
->
CppType
{
return
objc
?
:
:
djinni
::
DbxObjcWrapperCache
<
ObjcProxy
>::
getInstance
()
->
get
(
objc
)
:
nullptr
;
if
(
!
objc
)
{
return
nullptr
;
}
return
::
djinni
::
DbxObjcWrapperCache
<
ObjcProxy
>::
getInstance
()
->
get
(
objc
);
}
auto
TextboxListener
::
fromCpp
(
const
CppType
&
cpp
)
->
ObjcType
{
assert
(
!
cpp
||
dynamic_cast
<
ObjcProxy
*>
(
cpp
.
get
()));
return
cpp
?
static_cast
<
ObjcProxy
&>
(
*
cpp
).
Handle
:
:
get
()
:
nil
;
if
(
!
cpp
)
{
return
nil
;
}
return
dynamic_cast
<
ObjcProxy
&>
(
*
cpp
).
Handle
::
get
();
}
}
// namespace djinni_generated
src/source/ObjcGenerator.scala
View file @
f0aec36d
...
...
@@ -147,7 +147,7 @@ class ObjcGenerator(spec: Spec) extends Generator(spec) {
w
.
wl
(
s
";"
)
}
w
.
wl
if
(
i
.
ext
.
cpp
)
w
.
wl
(
s
"@interface $self : NSObject"
)
else
w
.
wl
(
s
"@protocol $self
"
)
if
(
i
.
ext
.
objc
)
w
.
wl
(
s
"@protocol $self"
)
else
w
.
wl
(
s
"@interface $self : NSObject
"
)
for
(
m
<-
i
.
methods
)
{
w
.
wl
writeDoc
(
w
,
m
.
doc
)
...
...
src/source/ObjcMarshal.scala
View file @
f0aec36d
...
...
@@ -50,12 +50,12 @@ class ObjcMarshal(spec: Spec) extends Marshal(spec) {
List
(
ImportRef
(
q
(
spec
.
objcIncludePrefix
+
headerName
(
d
.
name
))))
case
DInterface
=>
val
ext
=
d
.
body
.
asInstanceOf
[
Interface
].
ext
if
(
ext
.
cpp
)
{
List
(
ImportRef
(
"<Foundation/Foundation.h>"
),
DeclRef
(
s
"@class ${typename(d.name, d.body)};"
,
None
))
}
else
if
(
ext
.
objc
)
{
if
(
ext
.
objc
)
{
List
(
ImportRef
(
"<Foundation/Foundation.h>"
),
DeclRef
(
s
"@protocol ${typename(d.name, d.body)};"
,
None
))
}
else
if
(
ext
.
cpp
)
{
List
(
ImportRef
(
"<Foundation/Foundation.h>"
),
DeclRef
(
s
"@class ${typename(d.name, d.body)};"
,
None
))
}
else
{
List
()
}
...
...
src/source/ObjcppGenerator.scala
View file @
f0aec36d
...
...
@@ -111,11 +111,24 @@ class ObjcppGenerator(spec: Spec) extends Generator(spec) {
refs
.
body
.
add
(
"#include <utility>"
)
refs
.
body
.
add
(
"#import "
+
q
(
spec
.
objcBaseLibIncludePrefix
+
"DJIError.h"
))
refs
.
body
.
add
(
"#include <exception>"
)
}
if
(
i
.
ext
.
objc
)
{
refs
.
body
.
add
(
"#import "
+
q
(
spec
.
objcBaseLibIncludePrefix
+
"DJIObjcWrapperCache+Private.h"
))
refs
.
body
.
add
(
"!#import "
+
q
(
spec
.
objcppIncludePrefix
+
objcppMarshal
.
privateHeaderName
(
ident
.
name
)))
}
writeObjcFile
(
privateBodyName
(
ident
.
name
),
origin
,
refs
.
body
,
w
=>
{
arcAssert
(
w
)
writeObjcFile
(
privateBodyName
(
ident
.
name
),
origin
,
refs
.
body
,
w
=>
{
arcAssert
(
w
)
val
objcSelf
=
if
(
i
.
ext
.
objc
&&
i
.
ext
.
cpp
)
self
+
"CppProxy"
else
self
if
(
i
.
ext
.
cpp
)
{
w
.
wl
w
.
wl
(
s
"@interface $self ()"
)
if
(
i
.
ext
.
objc
)
w
.
wl
(
s
"@interface $objcSelf : NSObject<$self>"
)
else
w
.
wl
(
s
"@interface $objcSelf ()"
)
w
.
wl
w
.
wl
(
s
"@property (nonatomic, readonly) ::djinni::DbxCppWrapperCache<$cppSelf>::Handle cppRef;"
)
w
.
wl
...
...
@@ -123,21 +136,7 @@ class ObjcppGenerator(spec: Spec) extends Generator(spec) {
w
.
wl
w
.
wl
(
"@end"
)
w
.
wl
wrapNamespace
(
w
,
spec
.
objcppNamespace
,
w
=>
{
w
.
wl
(
s
"auto $helperClass::toCpp(ObjcType objc) -> CppType"
)
w
.
braced
{
w
.
wl
(
s
"return objc ? objc.cppRef.get() : nullptr;"
)
}
w
.
wl
w
.
wl
(
s
"auto $helperClass::fromCpp(const CppType& cpp) -> ObjcType"
)
w
.
braced
{
w
.
wl
(
s
"return !cpp ? nil : ::djinni::DbxCppWrapperCache<$cppSelf>::getInstance()->get(cpp, [] (const CppType& p)"
).
bracedEnd
(
");"
)
{
w
.
wl
(
s
"return [[$self alloc] initWithCpp:p];"
)
}
}
})
w
.
wl
w
.
wl
(
s
"@implementation $self"
)
w
.
wl
(
s
"@implementation $objcSelf"
)
w
.
wl
w
.
wl
(
s
"- (id)initWithCpp:(const std::shared_ptr<$cppSelf>&)cppRef"
)
w
.
braced
{
...
...
@@ -161,16 +160,11 @@ class ObjcppGenerator(spec: Spec) extends Generator(spec) {
}
w
.
wl
w
.
wl
(
"@end"
)
})
}
if
(
i
.
ext
.
objc
)
{
refs
.
body
.
add
(
"#import "
+
q
(
spec
.
objcBaseLibIncludePrefix
+
"DJIObjcWrapperCache+Private.h"
))
refs
.
body
.
add
(
"!#import "
+
q
(
spec
.
objcppIncludePrefix
+
objcppMarshal
.
privateHeaderName
(
ident
.
name
)))
}
writeObjcFile
(
privateBodyName
(
ident
.
name
),
origin
,
refs
.
body
,
w
=>
{
arcAssert
(
w
)
if
(
i
.
ext
.
objc
)
{
w
.
wl
val
objcExtSelf
=
objcppMarshal
.
helperClass
(
"objc_proxy"
)
wrapNamespace
(
w
,
spec
.
objcppNamespace
,
w
=>
{
w
.
wl
(
s
"class $helperClass::ObjcProxy final"
)
w
.
wl
(
s
": public $cppSelf"
)
...
...
@@ -192,20 +186,60 @@ class ObjcppGenerator(spec: Spec) extends Generator(spec) {
}
}
}
w
.
wl
w
.
wl
(
s
"auto $helperClass::toCpp(ObjcType objc) -> CppType"
)
w
.
braced
{
w
.
wl
(
s
"return objc ? ::djinni::DbxObjcWrapperCache<ObjcProxy>::getInstance()->get(objc) : nullptr;"
)
})
}
w
.
wl
wrapNamespace
(
w
,
spec
.
objcppNamespace
,
w
=>
{
// ObjC-to-C++ coversion
w
.
wl
(
s
"auto $helperClass::toCpp(ObjcType objc) -> CppType"
).
braced
{
// Handle null
w
.
w
(
"if (!objc)"
).
braced
{
w
.
wl
(
"return nullptr;"
)
}
w
.
wl
w
.
wl
(
s
"auto $helperClass::fromCpp(const CppType& cpp) -> ObjcType"
)
w
.
braced
{
w
.
wl
(
s
"assert(!cpp || dynamic_cast<ObjcProxy*>(cpp.get()));"
)
w
.
wl
(
s
"return cpp ? static_cast<ObjcProxy&>(*cpp).Handle::get() : nil;"
)
if
(
i
.
ext
.
cpp
&&
!
i
.
ext
.
objc
)
{
// C++ only. In this case we generate a class instead of a protocol, so
// we don't have to do any casting at all, just access cppRef directly.
w
.
wl
(
s
"return objc.cppRef.get();"
)
}
else
{
// ObjC only, or ObjC and C++.
val
objcExtSelf
=
objcppMarshal
.
helperClass
(
"objc_proxy"
)
if
(
i
.
ext
.
cpp
)
{
// If it could be implemented in C++, we might have to unwrap a proxy object.
w
.
w
(
s
"if ([(id)objc isKindOfClass:[$objcSelf class]])"
).
braced
{
w
.
wl
(
s
"return (($objcSelf*)objc).cppRef.get();"
)
}
}
w
.
wl
(
s
"return ::djinni::DbxObjcWrapperCache<$objcExtSelf>::getInstance()->get(objc);"
)
}
})
}
w
.
wl
w
.
wl
(
s
"auto $helperClass::fromCpp(const CppType& cpp) -> ObjcType"
).
braced
{
// Handle null
w
.
w
(
"if (!cpp)"
).
braced
{
w
.
wl
(
"return nil;"
)
}
if
(
i
.
ext
.
objc
&&
!
i
.
ext
.
cpp
)
{
// ObjC only. In this case we *must* unwrap a proxy object - the dynamic_cast will
// throw bad_cast if we gave it something of the wrong type.
val
objcExtSelf
=
objcppMarshal
.
helperClass
(
"objc_proxy"
)
w
.
wl
(
s
"return dynamic_cast<$objcExtSelf&>(*cpp).Handle::get();"
)
}
else
{
// C++ only, or C++ and ObjC.
if
(
i
.
ext
.
objc
)
{
// If it could be implemented in ObjC, we might have to unwrap a proxy object.
val
objcExtSelf
=
objcppMarshal
.
helperClass
(
"objc_proxy"
)
w
.
w
(
s
"if (auto cppPtr = dynamic_cast<${objcExtSelf}*>(cpp.get()))"
).
braced
{
w
.
wl
(
"return cppPtr->Handle::get();"
)
}
}
w
.
w
(
s
"return ::djinni::DbxCppWrapperCache<$cppSelf>::getInstance()->get(cpp, [] (const CppType& p)"
).
bracedEnd
(
");"
)
{
w
.
wl
(
s
"return [[$objcSelf alloc] initWithCpp:p];"
)
}
}
}
})
}
}
)
}
override
def
generateRecord
(
origin
:
String
,
ident
:
Ident
,
doc
:
Doc
,
params
:
Seq
[
TypeParam
],
r
:
Record
)
{
...
...
test-suite/djinni/test.djinni
View file @
f0aec36d
...
...
@@ -25,6 +25,7 @@ test_helpers = interface +c {
static create_cpp_token(): token;
static check_cpp_token(t: token);
static cpp_token_id(t: token): i64;
static check_token_type(t: token, type: string);
static return_none(): optional<i32>;
...
...
test-suite/djinni/token.djinni
View file @
f0aec36d
token = interface +c +j {
token = interface +c +j +o {
whoami() : string;
}
test-suite/generated-src/cpp/test_helpers.hpp
View file @
f0aec36d
...
...
@@ -62,6 +62,8 @@ public:
static
int64_t
cpp_token_id
(
const
std
::
shared_ptr
<
Token
>
&
t
);
static
void
check_token_type
(
const
std
::
shared_ptr
<
Token
>
&
t
,
const
std
::
string
&
type
);
static
std
::
experimental
::
optional
<
int32_t
>
return_none
();
/** Ensures that we generate integer translation code */
...
...
test-suite/generated-src/cpp/token.hpp
View file @
f0aec36d
...
...
@@ -3,7 +3,11 @@
#pragma once
#include <string>
class
Token
{
public:
virtual
~
Token
()
{}
virtual
std
::
string
whoami
()
=
0
;
};
test-suite/generated-src/java/com/dropbox/djinni/test/TestHelpers.java
View file @
f0aec36d
...
...
@@ -47,6 +47,8 @@ public abstract class TestHelpers {
public
static
native
long
cppTokenId
(
Token
t
);
public
static
native
void
checkTokenType
(
Token
t
,
String
type
);
public
static
native
Integer
returnNone
();
/** Ensures that we generate integer translation code */
...
...
test-suite/generated-src/java/com/dropbox/djinni/test/Token.java
View file @
f0aec36d
...
...
@@ -6,6 +6,7 @@ package com.dropbox.djinni.test;
import
java.util.concurrent.atomic.AtomicBoolean
;
public
abstract
class
Token
{
public
abstract
String
whoami
();
public
static
final
class
CppProxy
extends
Token
{
...
...
@@ -29,5 +30,13 @@ public abstract class Token {
destroy
();
super
.
finalize
();
}
@Override
public
String
whoami
()
{
assert
!
this
.
destroyed
.
get
()
:
"trying to use a destroyed object"
;
return
native_whoami
(
this
.
nativeRef
);
}
private
native
String
native_whoami
(
long
_nativeRef
);
}
}
test-suite/generated-src/jni/NativeTestHelpers.cpp
View file @
f0aec36d
...
...
@@ -202,6 +202,15 @@ CJNIEXPORT jlong JNICALL Java_com_dropbox_djinni_test_TestHelpers_cppTokenId(JNI
}
JNI_TRANSLATE_EXCEPTIONS_RETURN
(
jniEnv
,
0
/* value doesn't matter */
)
}
CJNIEXPORT
void
JNICALL
Java_com_dropbox_djinni_test_TestHelpers_checkTokenType
(
JNIEnv
*
jniEnv
,
jobject
/*this*/
,
jobject
j_t
,
jstring
j_type
)
{
try
{
DJINNI_FUNCTION_PROLOGUE0
(
jniEnv
);
::
TestHelpers
::
check_token_type
(
::
djinni_generated
::
NativeToken
::
toCpp
(
jniEnv
,
j_t
),
::
djinni
::
String
::
toCpp
(
jniEnv
,
j_type
));
}
JNI_TRANSLATE_EXCEPTIONS_RETURN
(
jniEnv
,
)
}
CJNIEXPORT
jobject
JNICALL
Java_com_dropbox_djinni_test_TestHelpers_returnNone
(
JNIEnv
*
jniEnv
,
jobject
/*this*/
)
{
try
{
...
...
test-suite/generated-src/jni/NativeToken.cpp
View file @
f0aec36d
...
...
@@ -2,6 +2,7 @@
// This file generated by Djinni from token.djinni
#include "NativeToken.hpp" // my header
#include "Marshal.hpp"
namespace
djinni_generated
{
...
...
@@ -13,6 +14,14 @@ NativeToken::JavaProxy::JavaProxy(JniType j) : JavaProxyCacheEntry(j) { }
NativeToken
::
JavaProxy
::~
JavaProxy
()
=
default
;
std
::
string
NativeToken
::
JavaProxy
::
whoami
()
{
auto
jniEnv
=
::
djinni
::
jniGetThreadEnv
();
::
djinni
::
JniLocalScope
jscope
(
jniEnv
,
10
);
const
auto
&
data
=
::
djinni
::
JniClass
<::
djinni_generated
::
NativeToken
>::
get
();
auto
jret
=
(
jstring
)
jniEnv
->
CallObjectMethod
(
getGlobalRef
(),
data
.
method_whoami
);
::
djinni
::
jniExceptionCheck
(
jniEnv
);
return
::
djinni
::
String
::
toCpp
(
jniEnv
,
jret
);
}
CJNIEXPORT
void
JNICALL
Java_com_dropbox_djinni_test_Token_00024CppProxy_nativeDestroy
(
JNIEnv
*
jniEnv
,
jobject
/*this*/
,
jlong
nativeRef
)
{
...
...
@@ -22,4 +31,14 @@ CJNIEXPORT void JNICALL Java_com_dropbox_djinni_test_Token_00024CppProxy_nativeD
}
JNI_TRANSLATE_EXCEPTIONS_RETURN
(
jniEnv
,
)
}
CJNIEXPORT
jstring
JNICALL
Java_com_dropbox_djinni_test_Token_00024CppProxy_native_1whoami
(
JNIEnv
*
jniEnv
,
jobject
/*this*/
,
jlong
nativeRef
)
{
try
{
DJINNI_FUNCTION_PROLOGUE1
(
jniEnv
,
nativeRef
);
const
auto
&
ref
=
::
djinni
::
CppProxyHandle
<::
Token
>::
get
(
nativeRef
);
auto
r
=
ref
->
whoami
();
return
::
djinni
::
String
::
fromCpp
(
jniEnv
,
r
).
release
();
}
JNI_TRANSLATE_EXCEPTIONS_RETURN
(
jniEnv
,
0
/* value doesn't matter */
)
}
}
// namespace djinni_generated
test-suite/generated-src/jni/NativeToken.hpp
View file @
f0aec36d
...
...
@@ -31,6 +31,7 @@ private:
JavaProxy
(
JniType
j
);
~
JavaProxy
();
std
::
string
whoami
()
override
;
private:
using
::
djinni
::
JavaProxyCacheEntry
::
getGlobalRef
;
...
...
@@ -39,6 +40,7 @@ private:
};
const
::
djinni
::
GlobalRef
<
jclass
>
clazz
{
::
djinni
::
jniFindClass
(
"com/dropbox/djinni/test/Token"
)
};
const
jmethodID
method_whoami
{
::
djinni
::
jniGetMethodID
(
clazz
.
get
(),
"whoami"
,
"()Ljava/lang/String;"
)
};
};
}
// namespace djinni_generated
test-suite/generated-src/objc/DBClientInterface+Private.mm
View file @
f0aec36d
...
...
@@ -35,15 +35,24 @@ public:
}
};
}
// namespace djinni_generated
namespace
djinni_generated
{
auto
ClientInterface
::
toCpp
(
ObjcType
objc
)
->
CppType
{
return
objc
?
:
:
djinni
::
DbxObjcWrapperCache
<
ObjcProxy
>::
getInstance
()
->
get
(
objc
)
:
nullptr
;
if
(
!
objc
)
{
return
nullptr
;
}
return
::
djinni
::
DbxObjcWrapperCache
<
ObjcProxy
>::
getInstance
()
->
get
(
objc
);
}
auto
ClientInterface
::
fromCpp
(
const
CppType
&
cpp
)
->
ObjcType
{
assert
(
!
cpp
||
dynamic_cast
<
ObjcProxy
*>
(
cpp
.
get
()));
return
cpp
?
static_cast
<
ObjcProxy
&>
(
*
cpp
).
Handle
:
:
get
()
:
nil
;
if
(
!
cpp
)
{
return
nil
;
}
return
dynamic_cast
<
ObjcProxy
&>
(
*
cpp
).
Handle
::
get
();
}
}
// namespace djinni_generated
test-suite/generated-src/objc/DBConstantsInterface+Private.mm
View file @
f0aec36d
...
...
@@ -19,23 +19,6 @@ static_assert(__has_feature(objc_arc), "Djinni requires ARC to be enabled for th
@end
namespace
djinni_generated
{
auto
ConstantsInterface
::
toCpp
(
ObjcType
objc
)
->
CppType
{
return
objc
?
objc
.
cppRef
.
get
()
:
nullptr
;
}
auto
ConstantsInterface
::
fromCpp
(
const
CppType
&
cpp
)
->
ObjcType
{
return
!
cpp
?
nil
:
::
djinni
::
DbxCppWrapperCache
<::
ConstantsInterface
>::
getInstance
()
->
get
(
cpp
,
[]
(
const
CppType
&
p
)
{
return
[[
DBConstantsInterface
alloc
]
initWithCpp
:
p
];
});
}
}
// namespace djinni_generated
@implementation
DBConstantsInterface
-
(
id
)
initWithCpp
:(
const
std
::
shared_ptr
<::
ConstantsInterface
>&
)
cppRef
...
...
@@ -53,3 +36,25 @@ auto ConstantsInterface::fromCpp(const CppType& cpp) -> ObjcType
}
@end
namespace
djinni_generated
{
auto
ConstantsInterface
::
toCpp
(
ObjcType
objc
)
->
CppType
{
if
(
!
objc
)
{
return
nullptr
;
}
return
objc
.
cppRef
.
get
();
}
auto
ConstantsInterface
::
fromCpp
(
const
CppType
&
cpp
)
->
ObjcType
{
if
(
!
cpp
)
{
return
nil
;
}
return
::
djinni
::
DbxCppWrapperCache
<::
ConstantsInterface
>::
getInstance
()
->
get
(
cpp
,
[]
(
const
CppType
&
p
)
{
return
[[
DBConstantsInterface
alloc
]
initWithCpp
:
p
];
});
}
}
// namespace djinni_generated
test-suite/generated-src/objc/DBCppException+Private.mm
View file @
f0aec36d
...
...
@@ -20,23 +20,6 @@ static_assert(__has_feature(objc_arc), "Djinni requires ARC to be enabled for th
@end
namespace
djinni_generated
{
auto
CppException
::
toCpp
(
ObjcType
objc
)
->
CppType
{
return
objc
?
objc
.
cppRef
.
get
()
:
nullptr
;
}
auto
CppException
::
fromCpp
(
const
CppType
&
cpp
)
->
ObjcType
{
return
!
cpp
?
nil
:
::
djinni
::
DbxCppWrapperCache
<::
CppException
>::
getInstance
()
->
get
(
cpp
,
[]
(
const
CppType
&
p
)
{
return
[[
DBCppException
alloc
]
initWithCpp
:
p
];
});
}
}
// namespace djinni_generated
@implementation
DBCppException
-
(
id
)
initWithCpp
:(
const
std
::
shared_ptr
<::
CppException
>&
)
cppRef
...
...
@@ -62,3 +45,25 @@ auto CppException::fromCpp(const CppType& cpp) -> ObjcType
}
@end
namespace
djinni_generated
{
auto
CppException
::
toCpp
(
ObjcType
objc
)
->
CppType
{
if
(
!
objc
)
{
return
nullptr
;
}
return
objc
.
cppRef
.
get
();
}
auto
CppException
::
fromCpp
(
const
CppType
&
cpp
)
->
ObjcType
{
if
(
!
cpp
)
{
return
nil
;
}
return
::
djinni
::
DbxCppWrapperCache
<::
CppException
>::
getInstance
()
->
get
(
cpp
,
[]
(
const
CppType
&
p
)
{
return
[[
DBCppException
alloc
]
initWithCpp
:
p
];
});
}
}
// namespace djinni_generated
test-suite/generated-src/objc/DBTestHelpers+Private.mm
View file @
f0aec36d
...
...
@@ -26,23 +26,6 @@ static_assert(__has_feature(objc_arc), "Djinni requires ARC to be enabled for th
@end
namespace
djinni_generated
{
auto
TestHelpers
::
toCpp
(
ObjcType
objc
)
->
CppType
{
return
objc
?
objc
.
cppRef
.
get
()
:
nullptr
;
}
auto
TestHelpers
::
fromCpp
(
const
CppType
&
cpp
)
->
ObjcType
{
return
!
cpp
?
nil
:
::
djinni
::
DbxCppWrapperCache
<::
TestHelpers
>::
getInstance
()
->
get
(
cpp
,
[]
(
const
CppType
&
p
)
{
return
[[
DBTestHelpers
alloc
]
initWithCpp
:
p
];
});
}
}
// namespace djinni_generated
@implementation
DBTestHelpers
-
(
id
)
initWithCpp
:(
const
std
::
shared_ptr
<::
TestHelpers
>&
)
cppRef
...
...
@@ -161,33 +144,41 @@ auto TestHelpers::fromCpp(const CppType& cpp) -> ObjcType
}
DJINNI_TRANSLATE_EXCEPTIONS
()
}
+
(
nullable
DBToken
*
)
tokenId
:(
nullable
DBToken
*
)
t
{
+
(
nullable
id
<
DBToken
>
)
tokenId
:(
nullable
id
<
DBToken
>
)
t
{
try
{
auto
r
=
::
TestHelpers
::
token_id
(
::
djinni_generated
::
Token
::
toCpp
(
t
));
return
::
djinni_generated
::
Token
::
fromCpp
(
r
);
}
DJINNI_TRANSLATE_EXCEPTIONS
()
}
+
(
nullable
DBToken
*
)
createCppToken
{
+
(
nullable
id
<
DBToken
>
)
createCppToken
{
try
{
auto
r
=
::
TestHelpers
::
create_cpp_token
();
return
::
djinni_generated
::
Token
::
fromCpp
(
r
);
}
DJINNI_TRANSLATE_EXCEPTIONS
()
}
+
(
void
)
checkCppToken
:(
nullable
DBToken
*
)
t
{
+
(
void
)
checkCppToken
:(
nullable
id
<
DBToken
>
)
t
{
try
{
::
TestHelpers
::
check_cpp_token
(
::
djinni_generated
::
Token
::
toCpp
(
t
));
}
DJINNI_TRANSLATE_EXCEPTIONS
()
}
+
(
int64_t
)
cppTokenId
:(
nullable
DBToken
*
)
t
{
+
(
int64_t
)
cppTokenId
:(
nullable
id
<
DBToken
>
)
t
{
try
{
auto
r
=
::
TestHelpers
::
cpp_token_id
(
::
djinni_generated
::
Token
::
toCpp
(
t
));
return
::
djinni
::
I64
::
fromCpp
(
r
);
}
DJINNI_TRANSLATE_EXCEPTIONS
()
}
+
(
void
)
checkTokenType
:(
nullable
id
<
DBToken
>
)
t
type
:(
nonnull
NSString
*
)
type
{
try
{
::
TestHelpers
::
check_token_type
(
::
djinni_generated
::
Token
::
toCpp
(
t
),
::
djinni
::
String
::
toCpp
(
type
));
}
DJINNI_TRANSLATE_EXCEPTIONS
()
}
+
(
nullable
NSNumber
*
)
returnNone
{
try
{
auto
r
=
::
TestHelpers
::
return_none
();
...
...
@@ -203,3 +194,25 @@ auto TestHelpers::fromCpp(const CppType& cpp) -> ObjcType
}
@end
namespace
djinni_generated
{
auto
TestHelpers
::
toCpp
(
ObjcType
objc
)
->
CppType
{
if
(
!
objc
)
{
return
nullptr
;
}
return
objc
.
cppRef
.
get
();
}
auto
TestHelpers
::
fromCpp
(
const
CppType
&
cpp
)
->
ObjcType
{
if
(
!
cpp
)
{
return
nil
;
}
return
::
djinni
::
DbxCppWrapperCache
<::
TestHelpers
>::
getInstance
()
->
get
(
cpp
,
[]
(
const
CppType
&
p
)
{
return
[[
DBTestHelpers
alloc
]
initWithCpp
:
p
];
});
}
}
// namespace djinni_generated
test-suite/generated-src/objc/DBTestHelpers.h
View file @
f0aec36d
...
...
@@ -8,8 +8,8 @@
#import "DBPrimitiveList.h"
#import "DBSetRecord.h"
#import <Foundation/Foundation.h>
@class
DBToken
;
@protocol
DBClientInterface
;
@protocol
DBToken
;
@interface
DBTestHelpers
:
NSObject
...
...
@@ -46,13 +46,16 @@
+
(
void
)
checkEnum
:(
DBColor
)
c
;
+
(
nullable
DBToken
*
)
tokenId
:(
nullable
DBToken
*
)
t
;
+
(
nullable
id
<
DBToken
>
)
tokenId
:(
nullable
id
<
DBToken
>
)
t
;
+
(
nullable
DBToken
*
)
createCppToken
;
+
(
nullable
id
<
DBToken
>
)
createCppToken
;
+
(
void
)
checkCppToken
:(
nullable
DBToken
*
)
t
;
+
(
void
)
checkCppToken
:(
nullable
id
<
DBToken
>
)
t
;
+
(
int64_t
)
cppTokenId
:(
nullable
DBToken
*
)
t
;
+
(
int64_t
)
cppTokenId
:(
nullable
id
<
DBToken
>
)
t
;
+
(
void
)
checkTokenType
:(
nullable
id
<
DBToken
>
)
t
type
:(
nonnull
NSString
*
)
type
;
+
(
nullable
NSNumber
*
)
returnNone
;
...
...
test-suite/generated-src/objc/DBToken+Private.h
View file @
f0aec36d
...
...
@@ -6,7 +6,7 @@
static_assert
(
__has_feature
(
objc_arc
),
"Djinni requires ARC to be enabled for this file"
);
@
class
DBToken
;
@
protocol
DBToken
;
namespace
djinni_generated
{
...
...
@@ -14,7 +14,7 @@ class Token
{
public:
using
CppType
=
std
::
shared_ptr
<::
Token
>
;
using
ObjcType
=
DBToken
*
;
using
ObjcType
=
id
<
DBToken
>
;
using
Boxed
=
Token
;
...
...
test-suite/generated-src/objc/DBToken+Private.mm
View file @
f0aec36d
...
...
@@ -5,12 +5,14 @@
#import "DBToken.h"
#import "DJICppWrapperCache+Private.h"
#import "DJIError.h"
#import "DJIMarshal+Private.h"
#import "DJIObjcWrapperCache+Private.h"
#include <exception>
#include <utility>
static_assert
(
__has_feature
(
objc_arc
),
"Djinni requires ARC to be enabled for this file"
);
@interface
DBToken
()
@interface
DBToken
CppProxy
:
NSObject
<
DBToken
>
@property
(
nonatomic
,
readonly
)
::
djinni
::
DbxCppWrapperCache
<::
Token
>::
Handle
cppRef
;
...
...
@@ -18,31 +20,68 @@ static_assert(__has_feature(objc_arc), "Djinni requires ARC to be enabled for th
@end
namespace
djinni_generated
{
@implementation
DBTokenCppProxy
auto
Token
::
toCpp
(
ObjcType
objc
)
->
CppType
-
(
id
)
initWithCpp
:(
const
std
::
shared_ptr
<::
Token
>&
)
cppRef
{
return
objc
?
objc
.
cppRef
.
get
()
:
nullptr
;
if
(
self
=
[
super
init
])
{
_cppRef
.
assign
(
cppRef
);
}
return
self
;
}
auto
Token
::
fromCpp
(
const
CppType
&
cpp
)
->
ObjcType
-
(
nonnull
NSString
*
)
whoami
{
try
{
auto
r
=
_cppRef
.
get
()
->
whoami
();
return
::
djinni
::
String
::
fromCpp
(
r
);
}
DJINNI_TRANSLATE_EXCEPTIONS
()
}
@end
namespace
djinni_generated
{
class
Token
::
ObjcProxy
final
:
public
::
Token
,
public
::
djinni
::
DbxObjcWrapperCache
<
ObjcProxy
>::
Handle
{
return
!
cpp
?
nil
:
::
djinni
::
DbxCppWrapperCache
<::
Token
>::
getInstance
()
->
get
(
cpp
,
[]
(
const
CppType
&
p
)
public:
using
Handle
::
Handle
;
std
::
string
whoami
()
override
{
return
[[
DBToken
alloc
]
initWithCpp
:
p
];
});
}
@autoreleasepool
{
auto
r
=
[
Handle
:
:
get
()
whoami
];
return
::
djinni
::
String
::
toCpp
(
r
);
}
}
};
}
// namespace djinni_generated
@implementation
DBToken
namespace
djinni_generated
{
-
(
id
)
initWithCpp
:(
const
std
::
shared_ptr
<::
Token
>&
)
cppRef
auto
Token
::
toCpp
(
ObjcType
objc
)
->
CppType
{
if
(
self
=
[
super
init
]
)
{
_cppRef
.
assign
(
cppRef
)
;
if
(
!
objc
)
{
return
nullptr
;
}
return
self
;
if
([(
id
)
objc
isKindOfClass
:[
DBTokenCppProxy
class
]])
{
return
((
DBTokenCppProxy
*
)
objc
).
cppRef
.
get
();
}
return
::
djinni
::
DbxObjcWrapperCache
<
ObjcProxy
>::
getInstance
()
->
get
(
objc
);
}
@end
auto
Token
::
fromCpp
(
const
CppType
&
cpp
)
->
ObjcType
{
if
(
!
cpp
)
{
return
nil
;
}
if
(
auto
cppPtr
=
dynamic_cast
<
ObjcProxy
*>
(
cpp
.
get
()))
{
return
cppPtr
->
Handle
::
get
();
}
return
::
djinni
::
DbxCppWrapperCache
<::
Token
>::
getInstance
()
->
get
(
cpp
,
[]
(
const
CppType
&
p
)
{
return
[[
DBTokenCppProxy
alloc
]
initWithCpp
:
p
];
});
}
}
// namespace djinni_generated
test-suite/generated-src/objc/DBToken.h
View file @
f0aec36d
...
...
@@ -4,6 +4,8 @@
#import <Foundation/Foundation.h>
@interface
DBToken
:
NSObject
@protocol
DBToken
-
(
nonnull
NSString
*
)
whoami
;
@end
test-suite/handwritten-src/cpp/test_helpers.cpp
View file @
f0aec36d
...
...
@@ -90,7 +90,9 @@ std::shared_ptr<Token> TestHelpers::token_id(const std::shared_ptr<Token> & in)
return
in
;
}
class
CppToken
:
public
Token
{};
class
CppToken
:
public
Token
{
std
::
string
whoami
()
{
return
"C++"
;
}
};
std
::
shared_ptr
<
Token
>
TestHelpers
::
create_cpp_token
()
{
return
std
::
make_shared
<
CppToken
>
();
...
...
@@ -105,6 +107,12 @@ int64_t TestHelpers::cpp_token_id(const std::shared_ptr<Token> & in) {
return
reinterpret_cast
<
int64_t
>
(
in
.
get
());
}
void
TestHelpers
::
check_token_type
(
const
std
::
shared_ptr
<
Token
>
&
t
,
const
std
::
string
&
type
)
{
if
(
t
->
whoami
()
!=
type
)
{
throw
std
::
invalid_argument
(
"wrong token type"
);
}
}
std
::
experimental
::
optional
<
int32_t
>
TestHelpers
::
return_none
()
{
return
{};
}
...
...
test-suite/handwritten-src/java/com/dropbox/djinni/test/TokenTest.java
View file @
f0aec36d
...
...
@@ -5,6 +5,7 @@ import junit.framework.TestCase;
public
class
TokenTest
extends
TestCase
{
private
class
JavaToken
extends
Token
{
public
String
whoami
()
{
return
"Java"
;
}
}
@Override
...
...
@@ -29,6 +30,25 @@ public class TokenTest extends TestCase {
System
.
runFinalization
();
}
public
void
testTokenType
()
{
TestHelpers
.
checkTokenType
(
new
JavaToken
(),
"Java"
);
TestHelpers
.
checkTokenType
(
TestHelpers
.
createCppToken
(),
"C++"
);
boolean
threw
=
false
;
try
{
TestHelpers
.
checkTokenType
(
new
JavaToken
(),
"foo"
);
}
catch
(
RuntimeException
e
)
{
threw
=
true
;
}
assertTrue
(
threw
);
threw
=
false
;
try
{
TestHelpers
.
checkTokenType
(
TestHelpers
.
createCppToken
(),
"foo"
);
}
catch
(
RuntimeException
e
)
{
threw
=
true
;
}
assertTrue
(
threw
);
}
public
void
testNotCppToken
()
{
boolean
threw
=
false
;
try
{
...
...
test-suite/handwritten-src/objc/tests/DBTokenTests.mm
0 → 100644
View file @
f0aec36d
#import "DBTestHelpers.h"
#import "DBToken.h"
#import <XCTest/XCTest.h>
@interface
DBTokenTests
:
XCTestCase
@end
@interface
DBObjcToken
:
NSObject
<
DBToken
>
-
(
NSString
*
)
whoami
;
@end
@implementation
DBObjcToken
-
(
NSString
*
)
whoami
{
return
@"ObjC"
;
}
@end
@implementation
DBTokenTests
-
(
void
)
setUp
{
[
super
setUp
];
}
-
(
void
)
tearDown
{
[
super
tearDown
];
}
-
(
void
)
testTokens
{
id
<
DBToken
>
t
=
[[
DBObjcToken
alloc
]
init
];
XCTAssertEqual
([
DBTestHelpers
tokenId
:
t
],
t
);
}
-
(
void
)
testNullToken
{
XCTAssertNil
([
DBTestHelpers
tokenId
:
nil
]);
}
-
(
void
)
testTokenType
{
[
DBTestHelpers
checkTokenType
:[[
DBObjcToken
alloc
]
init
]
type
:
@"ObjC"
];
[
DBTestHelpers
checkTokenType
:[
DBTestHelpers
createCppToken
]
type
:
@"C++"
];
XCTAssertThrows
([
DBTestHelpers
checkTokenType
:[[
DBObjcToken
alloc
]
init
]
type
:
@"foo"
]);
XCTAssertThrows
([
DBTestHelpers
checkTokenType
:[
DBTestHelpers
createCppToken
]
type
:
@"foo"
]);
}
-
(
void
)
testCppToken
{
id
<
DBToken
>
ct
=
[
DBTestHelpers
createCppToken
];
XCTAssertEqual
([
DBTestHelpers
tokenId
:
ct
],
ct
);
[
DBTestHelpers
checkCppToken
:
ct
];
ct
=
nil
;
}
-
(
void
)
testNotCppToken
{
XCTAssertThrows
([
DBTestHelpers
checkCppToken
:[[
DBObjcToken
alloc
]
init
]]);
}
@end
test-suite/objc/DjinniObjcTest.xcodeproj/project.pbxproj
View file @
f0aec36d
...
...
@@ -24,6 +24,7 @@
65868B5F1989FE4200D60EEE
/* UIKit.framework in Frameworks */
=
{
isa
=
PBXBuildFile
;
fileRef
=
65868B5E1989FE4200D60EEE
/* UIKit.framework */
;
};
65868B621989FE4200D60EEE
/* libDjinniObjcTest.a in Frameworks */
=
{
isa
=
PBXBuildFile
;
fileRef
=
65868B4A1989FE4200D60EEE
/* libDjinniObjcTest.a */
;
};
6D66A8A91A3B09F000B312E8
/* DBConstantTests.mm in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
6D66A8A81A3B09F000B312E8
/* DBConstantTests.mm */
;
};
A20094101B06982F00EF8D9B
/* DBTokenTests.mm in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
A200940E1B0697D300EF8D9B
/* DBTokenTests.mm */
;
};
A238CA8C1AF84B7100CDDCE5
/* DBAssortedIntegers+Private.mm in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
A238CA741AF84B7100CDDCE5
/* DBAssortedIntegers+Private.mm */
;
};
A238CA8E1AF84B7100CDDCE5
/* DBClientReturnedRecord+Private.mm in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
A238CA761AF84B7100CDDCE5
/* DBClientReturnedRecord+Private.mm */
;
};
A238CA901AF84B7100CDDCE5
/* DBConstants+Private.mm in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
A238CA781AF84B7100CDDCE5
/* DBConstants+Private.mm */
;
};
...
...
@@ -118,6 +119,7 @@
65868B5B1989FE4200D60EEE
/* XCTest.framework */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
wrapper.framework
;
name
=
XCTest.framework
;
path
=
Library/Frameworks/XCTest.framework
;
sourceTree
=
DEVELOPER_DIR
;
};
65868B5E1989FE4200D60EEE
/* UIKit.framework */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
wrapper.framework
;
name
=
UIKit.framework
;
path
=
Library/Frameworks/UIKit.framework
;
sourceTree
=
DEVELOPER_DIR
;
};
6D66A8A81A3B09F000B312E8
/* DBConstantTests.mm */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
sourcecode.cpp.objcpp
;
path
=
DBConstantTests.mm
;
sourceTree
=
"<group>"
;
};
A200940E1B0697D300EF8D9B
/* DBTokenTests.mm */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
sourcecode.cpp.objcpp
;
path
=
DBTokenTests.mm
;
sourceTree
=
"<group>"
;
};
A238CA741AF84B7100CDDCE5
/* DBAssortedIntegers+Private.mm */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
sourcecode.cpp.objcpp
;
path
=
"DBAssortedIntegers+Private.mm"
;
sourceTree
=
"<group>"
;
};
A238CA761AF84B7100CDDCE5
/* DBClientReturnedRecord+Private.mm */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
sourcecode.cpp.objcpp
;
path
=
"DBClientReturnedRecord+Private.mm"
;
sourceTree
=
"<group>"
;
};
A238CA781AF84B7100CDDCE5
/* DBConstants+Private.mm */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
sourcecode.cpp.objcpp
;
path
=
"DBConstants+Private.mm"
;
sourceTree
=
"<group>"
;
};
...
...
@@ -273,6 +275,7 @@
6536CD7919A6C99800DD7715
/* Tests */
=
{
isa
=
PBXGroup
;
children
=
(
A200940E1B0697D300EF8D9B
/* DBTokenTests.mm */
,
6536CD7A19A6C99800DD7715
/* DBClientInterfaceTests.mm */
,
6536CD7B19A6C99800DD7715
/* DBCppExceptionTests.mm */
,
6536CD7C19A6C99800DD7715
/* DBMapRecordTests.mm */
,
...
...
@@ -568,6 +571,7 @@
CFFD58B21B041BD9001E10B6
/* DBConstantsInterface.mm in Sources */
,
6536CD9319A6C9A800DD7715
/* DBRecordWithDerivingsObjcTests.mm in Sources */
,
6536CD9019A6C9A800DD7715
/* DBNestedCollectionTests.mm in Sources */
,
A20094101B06982F00EF8D9B
/* DBTokenTests.mm in Sources */
,
6536CD8F19A6C9A800DD7715
/* DBMapRecordTests.mm in Sources */
,
6536CD9419A6C9A800DD7715
/* DBSetRecordTests.mm in Sources */
,
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment