forked from adamdruppe/arsd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jni.d
2906 lines (2527 loc) · 100 KB
/
jni.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/++
Provides easy interoperability with Java code through JNI.
Given this Java:
```java
class Hello {
public native void hi(String s);
public native String stringFromJNI();
public native String returnNull();
public native void throwException();
static {
System.loadLibrary("myjni");
}
public static void main(String[] args) {
System.out.println("Hello from Java!");
Hello h = new Hello();
// we can pass data back and forth normally
h.hi("jni");
System.out.println(h.stringFromJNI());
System.out.println(h.returnNull()); // it can handle null too
// and even forward exceptions (sort of, it puts it in a RuntimeException right now)
h.throwException();
}
public void printMember() {
System.out.println("Member: " + member);
}
public int member;
}
```
And this D:
---
import arsd.jni;
// if it was in a Java package, you'd pass that
// in the string here instead of "".
final class Hello : JavaClass!("", Hello) {
@Export string stringFromJNI() {
return "hey, D returned this";
}
@Export string returnNull() {
return null;
}
@Export void throwException() {
throw new Exception("exception from D");
}
@Export void hi(string name) {
import std.stdio;
writefln("hello from D, %s", name);
}
// D can also access Java methods
@Import void printMember();
// and fields, as properties
@Import @property int member(); // getter for java's `int member`
@Import @property void member(int); // setter for java's `int member`
}
version(Windows) {
import core.sys.windows.dll;
mixin SimpleDllMain;
}
---
We can:
$(CONSOLE
$ javac Hello.java
$ dmd -shared myjni.d jni.d # compile into a shared lib
$ LD_LIBRARY_PATH=. java Hello
Hello from Java!
hello from D, jni
hey, D returned this
null
Exception in thread "main" java.lang.RuntimeException: [email protected](14): exception from D
----------------
??:? void myjni.Hello.throwException() [0x7f51d86dc17b]
??:? Java_Hello_throwException [0x7f51d86dd3e0]
??:? [0x7f51dd018406]
??:? [0x7f51dd007ffc]
??:? [0x7f51dd0004e6]
??:? [0x7f51f16b0709]
??:? [0x7f51f16c1339]
??:? [0x7f51f16d208d]
??:? [0x7f51f1f97058]
??:? [0x7f51f1fae06a]
at Hello.throwException(Native Method)
at Hello.main(Hello.java:17)
)
Please note: on Windows, use `-m32mscoff` or `-m64` when compiling with dmd.
Exact details subject to change, especially of how I pass the exceptions over.
It is also possible to call Java methods and create Java objects from D with the `@Import` uda.
While you can write pretty ordinary looking D code, there's some things to keep in mind for safety and efficiency.
$(WARNING
ALL references passed to you through Java, including
arrays, objects, and even the `this` pointer, MUST NOT
be stored outside the lifetime of the immediate function
they were passed to!
You may be able to get the D compiler to help you with
this with the scope attribute, but regardless, don't
do it.
)
It is YOUR responsibility to make sure parameter and return types
match between D and Java. The library will `static assert` given
unrepresentable types, but it cannot see what Java actually expects.
Getting this wrong can lead to memory corruption and crashes.
$(TIP
When possible, use `wstring` instead of `string` when
working with Java APIs. `wstring` matches the format
of Java's `String` so it avoids a conversion step.
)
All [JavaClass] sub-objects should be marked `final` on the D
side. Java may subclass them, but D can't (at least not now).
Do not use default arguments on the exported methods. No promise
the wrapper will do what you want when called from Java.
You may choose to only import JavaClass from here to minimize the
namespace pollution.
Constructing Java objects works and it will pin it. Just remember
that `this` inside a method is still subject to escaping restrictions!
+/
module arsd.jni;
// I need to figure out some way that users can set this. maybe. or dynamically fall back from newest to oldest we can handle
__gshared auto JNI_VERSION_DESIRED = JNI_VERSION_1_6;
// i could perhaps do a struct to bean thingy
/*
New Java classes:
class Foo : extends!Bar {
mixin stuff;
}
mixin stuff;
The `extends` template creates a wrapper that calls the nonvirtual
methods, so `super()` just works.
receiving an object should perhaps always give a subclass that is javafied;
calls the virtuals, unless of course it is final.
dynamic downcasts of java objects will probably never work.
*/
/+
For interfaces:
Java interfaces should just inherit from IJavaObject. Otherwise they
work as normal in D. The final class is responsible for setting @Import
and @Export on the methods and declaring they are implemented.
Note that you can define D interfaces as well, that are not necessarily
known to Java. If your interface uses IJavaObject though, it assumes
that there is some kind of relationship. (mismatching this is not
necessarily fatal, but may cause runtime exceptions or compile errors.)
For parent classes:
The CRTP limits this. May switch to mixin template... but right now
the third argument to JavaClass declares the parent. It will alias this
to a thing that returns the casted (well, realistically, reconstructed) version.
+/
/+
FIXME: D lambdas might be automagically wrapped in a Java class... will
need to know what parent class Java expects and which method to override.
+/
// FIXME: if user defines an interface with the appropriate RAII return values,
// it should let them do that for more efficiency
// e.g. @Import Manual!(int[]) getJavaArray();
/+
So in Java, a lambda expression is turned into an anonymous class
that implements the one abstract method in the required interface.
In D, they are a different type. And with no implicit construction I
can't convert automatically.
But I could prolly do something like javaLambda!Interface(x => foo)
but woof that isn't so much different than an anonymous class anymore.
+/
/// hack used by the translator for default constructors not really being a default constructor
struct Default {}
/+
final class CharSequence : JavaClass!("java.lang", CharSequence) {
@Import string toString(); // this triggers a dmd segfault! whoa. FIXME dmd
}
+/
/++
Java's String class implements its CharSequence interface. D's
string is not a class at all, so it cannot directly do that. Instead,
this translation of the interface has static methods to return a dummy
class wrapping D's string.
+/
interface CharSequence : JavaInterface!("java.lang", CharSequence) {
///
static CharSequence fromDString(string data) {
auto env = activeEnv;
assert(env !is null);
wchar[1024] buffer;
const(wchar)[] translated;
if(data.length < 1024) {
size_t len;
foreach(wchar ch; data)
buffer[len++] = ch;
translated = buffer[0 .. len];
} else {
import std.conv;
translated = to!wstring(data);
}
// Java copies the buffer so it is perfectly fine to return here now
return dummyClass!(typeof(this))((*env).NewString(env, translated.ptr, cast(jsize) translated.length));
}
///
static CharSequence fromDString(wstring data) {
auto env = activeEnv;
assert(env !is null);
return dummyClass!(typeof(this))((*env).NewString(env, data.ptr, cast(jsize) data.length));
}
}
/++
Indicates that your interface represents an interface in Java.
Use this on the declaration, then your other classes can implement
it fairly normally (just with the @Import and @Export annotations added
in the appropriate places). D will require something be filled in on the
child classes so be sure to copy the @Import declarations there.
---
interface IFoo : JavaInterface!("com.example", IFoo) {
string whatever();
}
final class Foo : JavaClass!("com.example", Foo), IFoo {
// need to tell D that the implementation exists, just in Java.
// (This actually generates the D implementation that just forwards to the existing java method)
@Import string whatever();
}
---
+/
interface JavaInterface(string javaPackage, CRTP) : IJavaObject {
mixin JavaPackageId!(javaPackage, CRTP);
mixin JavaInterfaceMembers!(null);
}
/// I may not keep this. But for now if you need a dummy class in D
/// to represent some object that implements this interface in Java,
/// you can use this. The dummy class assumes all interface methods are @Imported.
static T dummyClass(T)(jobject obj) {
return new class T {
jobject getJavaHandle() { return obj; }
};
}
/++
Can be used as a UDA for methods or classes where the D name
and the Java name don't match (for example, if it happens to be
a D keyword).
---
@JavaName("version")
@Import int version_();
---
+/
struct JavaName {
string name;
}
private string getJavaName(alias a)() {
string name = __traits(identifier, a);
static foreach(attr; __traits(getAttributes, a))
static if(is(typeof(attr) == JavaName))
name = attr.name;
return name;
}
/+
to benchmark build stats
cd ~/Android/d_android/java_bindings/android/java
/usr/bin/time -f "%E %M" dmd -o- -c `find . | grep -E '\.d$'` ~/arsd/jni.d -I../..
+/
/+ Java class file definitions { +/
// see: https://docs.oracle.com/javase/specs/jvms/se13/html/jvms-4.html
version(WithClassLoadSupport) {
import arsd.declarativeloader;
/// translator.
void jarToD()(string jarPath, string dPackagePrefix, string outputDirectory, JavaTranslationConfig jtc, bool delegate(string className) classFilter = null) {
import std.zip;
import std.file;
import std.algorithm;
auto zip = new ZipArchive(read(jarPath));
ClassFile[string] allClasses;
foreach(name, am; zip.directory) {
if(name.endsWith(".class")) {
zip.expand(am);
ClassFile cf;
auto classBytes = cast(ubyte[]) am.expandedData;
auto originalClassBytes = classBytes;
debug try {
cf.loadFrom!ClassFile(classBytes);
} catch(Exception e) {
std.file.write("spam.bin", originalClassBytes);
throw e;
} else
cf.loadFrom!ClassFile(classBytes);
string className = cf.className.idup;
if(classFilter is null || classFilter(className))
allClasses[className] = cf;
//rawClassBytesToD(cast(ubyte[]) am.expandedData, dPackagePrefix, outputDirectory, jtc);
//am.expandedData = null; // let the GC take it if it wants
}
}
foreach(name, cf; allClasses)
rawClassStructToD(cf, dPackagePrefix, outputDirectory, jtc, allClasses);
}
private inout(char)[] fixupKeywordsInJavaPackageName(inout(char)[] s) {
import std.string;
s ~= "."; // lol i suck
s = s.replace(".function.", ".function_.");
s = s.replace(".ref.", ".ref_.");
s = s.replace(".module.", ".module_.");
s = s.replace(".package.", ".package_.");
s = s.replace(".debug.", ".debug_.");
s = s.replace(".version.", ".version_.");
s = s.replace(".asm.", ".asm_.");
s = s.replace(".shared.", ".shared_.");
s = s.replace(".scope.", ".scope_.");
return s[0 .. $-1]; // god i am such a bad programmer
}
private inout(char)[] fixupJavaClassName(inout(char)[] s) {
import std.algorithm : among;
if(s.among("Throwable", "Object", "Exception", "Error", "TypeInfo", "ClassInfo", "version"))
s = cast(typeof(s)) "Java" ~ s;
return s;
}
/// For the translator
struct JavaTranslationConfig {
/// List the Java methods, imported to D.
bool doImports;
/// List the native methods, assuming they should be exported from D
bool doExports;
/// Put implementations inline. If false, this separates interface from impl for quicker builds with dmd -i.
bool inlineImplementations;
/// Treat native functions as imports, otherwise fills in as exports. Make sure doImports == true.
bool nativesAreImports = true;
}
/// translator
void rawClassBytesToD()(ubyte[] bytes, string dPackagePrefix, string outputDirectory, JavaTranslationConfig jtc) {
ClassFile f;
f.loadFrom(bytes);
rawClassStructToD(f, dPackagePrefix, outputDirectory, jtc, null);
}
/// translator.
void rawClassStructToD()(ref ClassFile cf, string dPackagePrefix, string outputDirectory, JavaTranslationConfig jtc, ClassFile[string] allClasses) {
import std.file;
import std.path;
import std.algorithm;
import std.array;
import std.string;
string importPrefix = "import";
const(char)[] javaPackage;
const(char)[] lastClassName;
const(char)[] originalJavaPackage;
const(char)[] originalClassName;
const(char)[] cn = cf.className;
auto idx = cn.lastIndexOf("/");
if(idx != -1) {
javaPackage = cn[0 .. idx].replace("$", "_").replace("/", ".").fixupKeywordsInJavaPackageName;
lastClassName = cn[idx + 1 .. $];
originalJavaPackage = cn[0 .. idx].replace("/", ".");
originalClassName = lastClassName;
} else {
lastClassName = cn;
originalJavaPackage = "";
originalClassName = lastClassName;
}
lastClassName = lastClassName.replace("$", "_"); // NOTE rughs strings in this file
lastClassName = fixupJavaClassName(lastClassName);
auto filename = (outputDirectory.length ? (outputDirectory ~ "/") : "")
~ (dPackagePrefix.length ? (dPackagePrefix.replace(".", "/") ~ "/") : "")
~ javaPackage.replace(".", "/");
mkdirRecurse(filename);
if(filename.length)
filename ~= "/";
filename ~= lastClassName ~ ".d";
if(filename.indexOf("-") != -1)
return;
string dco;
auto thisModule = cast(string)((dPackagePrefix.length ? (dPackagePrefix ~ ".") : "") ~ javaPackage);
if(thisModule.length && thisModule[$-1] != '.')
thisModule ~= ".";
thisModule ~= lastClassName;
bool isInterface = (cf.access_flags & 0x0200) ? true : false;
bool isAbstract = (cf.access_flags & ClassFile.ACC_ABSTRACT) ? true : false;
if(jtc.inlineImplementations) {
dco = "module " ~ thisModule ~ ";\n\n";
} else {
dco ~= "module " ~ thisModule ~ "_d_interface;\n";
}
dco ~= "import arsd.jni : IJavaObjectImplementation, JavaPackageId, JavaName, IJavaObject, ImportExportImpl, JavaInterfaceMembers;\n";
dco ~= "static import arsd.jni;\n\n";
string[string] javaPackages;
string[string] javaPackagesReturn;
string[string] javaPackagesArguments;
string dc;
if(lastClassName != originalClassName)
dc ~= "@JavaName(\""~originalClassName~"\")\n";
bool outputMixinTemplate = false;
string mainThing;
//string helperThing;
// so overriding Java classes from D is iffy and with separate implementation
// non final leads to linker errors anyway...
//mainThing ~= (isInterface ? "interface " : (jtc.inlineImplementations ? "class " : isAbstract ? "abstract class " : "final class ")) ~ lastClassName ~ " : ";
mainThing ~= "final class " ~ lastClassName ~ " : IJavaObject {\n";
mainThing ~= "\tstatic immutable string[] _d_canCastTo = [\n";
// not putting super class on inline implementations since that forces vtable...
if(jtc.inlineImplementations) {
auto scn = cf.superclassName;
if(scn.length) {
mainThing ~= "\t\t\"" ~ scn ~ "\",\n";
}
/+
//if(!scn.startsWith("java/")) {
// superclasses need the implementation too so putting it in the return list lol
if(scn.length && scn != "java/lang/Object") { // && scn in allClasses) {
mainThing ~= javaObjectToDTypeString(scn, javaPackages, javaPackagesReturn, importPrefix);
mainThing ~= ", ";
}
//}
+/
}
foreach(name; cf.interfacesNames) {
//if(name.startsWith("java/"))
//continue; // these probably aren't important to D and can really complicate version management
//if(name !in allClasses)
//continue;
//mainThing ~= javaObjectToDTypeString(name, javaPackages, javaPackagesReturn, importPrefix);
//mainThing ~= ", ";
mainThing ~= "\t\t\"" ~ name ~ "\",\n";
}
mainThing ~= "\t];\n";
//helperThing ~= "interface " ~ lastClassName ~ "_d_methods : ";
string[string] mentioned;
string[string] processed;
void addMethods(ClassFile* current, bool isTopLevel) {
if(current is null) return;
if(current.className in processed) return;
foreach(method; current.methodsListing) {
bool native = (method.flags & 0x0100) ? true : false;
if(jtc.nativesAreImports) {
native = false; // kinda hacky but meh
if(!jtc.doImports)
continue;
} else {
if(native && !jtc.doExports)
continue;
if(!native && !jtc.doImports)
continue;
}
auto port = native ? "@Export" : "@Import";
if(method.flags & 1) { // public
if(!isTopLevel && method.name == "<init>")
continue;
bool maybeOverride = false;// !isInterface;
if(method.flags & 0x0008) {
port ~= " static";
}
if(method.flags & method_info.ACC_ABSTRACT) {
//if(!isInterface)
//port ~= " abstract";
} else {
// this represents a default implementation in a Java interface
// D cannot express this... so I need to add it to the mixin template
// associated with this interface as well.
//if(isInterface && (!(method.flags & 0x0008))) {
//addToMixinTemplate = true;
//}
}
//if(maybeOverride && method.isOverride(allClasses))
//port ~= " override";
auto name = method.name;
// FIXME: maybe check name for other D keywords but since so many overlap with java I think we will be ok most the time for now
import std.algorithm : among;
if(name.among("package", "export", "bool", "module", "debug",
"delete", "with", "version", "cast", "union", "align",
"alias", "in", "out", "toString", "init", "lazy",
"immutable", "is", "function", "delegate", "template",
"scope")) {
// toString is special btw in order to avoid a dmd bug
port ~= " @JavaName(\""~name~"\")";
name ~= "_";
}
// NOTE rughs strings in this file
name = name.replace("$", "_");
bool ctor = name == "<init>";
auto sig = method.signature;
auto lidx = sig.lastIndexOf(")");
assert(lidx != -1);
auto retJava = sig[lidx + 1 .. $];
auto argsJava = sig[1 .. lidx];
string ret = ctor ? "" : javaSignatureToDTypeString(retJava, javaPackages, javaPackagesReturn, importPrefix);
string args = javaSignatureToDTypeString(argsJava, javaPackages, javaPackagesArguments, importPrefix);
auto oargs = args;
if(!jtc.inlineImplementations) {
if(ctor && args.length == 0)
args = "arsd.jni.Default";
}
string men = cast(immutable) (name ~ "(" ~ args ~ ")");
if(men in mentioned)
continue; // avoid duplicate things. idk why this is there though
mentioned[men] = men;
string proto = cast(string) ("\t"~port~" " ~ ret ~ (ret.length ? " " : "") ~ (ctor ? "this" : name) ~ "("~args~")"~(native ? " { assert(0); }" : ";")~"\n");
mainThing ~= proto;
if(oargs.length == 0 && name == "toString_" && !(method.flags & 0x0008))
mainThing ~= "\toverride string toString() { return arsd.jni.javaObjectToString(this); }\n";
}
}
processed[current.className.idup] = "done";
if(current.superclassName.length) {
auto c = current.superclassName in allClasses;
addMethods(c, false);
}
foreach(iface; current.interfacesNames) {
auto c = iface in allClasses;
addMethods(c, false);
}
}
addMethods(&cf, true);
mainThing ~= "\tmixin IJavaObjectImplementation!(false);\n";
mainThing ~= "\tpublic static immutable string _javaParameterString = \"L" ~ cn ~ ";\";\n";
mainThing ~= "}\n\n";
dc ~= mainThing;
dc ~= "\n\n";
foreach(pkg, prefix; javaPackages) {
auto m = (dPackagePrefix.length ? (dPackagePrefix ~ ".") : "") ~ pkg;
// keeping thisModule because of the prefix nonsense
//if(m == thisModule)
//continue;
if(jtc.inlineImplementations)
dco ~= "import " ~ prefix ~ " = " ~ m ~ ";\n";
else
dco ~= "import " ~ prefix ~ " = " ~ m ~ "_d_interface;\n";
}
if(javaPackages.keys.length)
dco ~= "\n";
dco ~= dc;
if(jtc.inlineImplementations) {
dco ~= "\nmixin ImportExportImpl!"~lastClassName~";\n";
std.file.write(filename, dco);
} else {
string impl;
impl ~= "module " ~ thisModule ~ ";\n";
impl ~= "public import " ~ thisModule ~ "_d_interface;\n\n";
impl ~= "import arsd.jni : ImportExportImpl;\n";
impl ~= "mixin ImportExportImpl!"~lastClassName~";\n";
impl ~= "\n";
foreach(pkg, prefix; javaPackagesReturn) {
// I also need to import implementations of return values so they just work
auto m = (dPackagePrefix.length ? (dPackagePrefix ~ ".") : "") ~ pkg;
impl ~= "import " ~ prefix ~ " = " ~ m ~ ";\n";
}
std.file.write(filename, impl);
std.file.write(filename[0 .. $-2] ~ "_d_interface.d", dco);
}
}
string javaObjectToDTypeString(const(char)[] input, ref string[string] javaPackages, ref string[string] detailedPackages, string importPrefix) {
string ret;
if(input == "java/lang/String") {
ret = "string"; // or could be wstring...
} else if(input == "java/lang/Object") {
ret = "IJavaObject";
} else {
// NOTE rughs strings in this file
string type = input.replace("$", "_").idup;
string jp, cn, dm;
auto idx = type.lastIndexOf("/");
if(idx != -1) {
jp = type[0 .. idx].replace("/", ".").fixupKeywordsInJavaPackageName;
cn = type[idx + 1 .. $].fixupJavaClassName;
dm = jp ~ "." ~ cn;
} else {
cn = type;
dm = jp;
}
string prefix;
if(auto n = dm in javaPackages) {
prefix = *n;
} else {
import std.conv;
// FIXME: this scheme sucks, would prefer something deterministic
prefix = importPrefix ~ to!string(javaPackages.keys.length);
//prefix = dm.replace(".", "0");
javaPackages[dm] = prefix;
detailedPackages[dm] = prefix;
}
ret = prefix ~ (prefix.length ? ".":"") ~ cn;
}
return ret;
}
string javaSignatureToDTypeString(ref const(char)[] js, ref string[string] javaPackages, ref string[string] detailedPackages, string importPrefix) {
string all;
while(js.length) {
string type;
switch(js[0]) {
case '[':
js = js[1 .. $];
type = javaSignatureToDTypeString(js, javaPackages, detailedPackages, importPrefix);
type ~= "[]";
break;
case 'L':
import std.string;
auto idx = js.indexOf(";");
type = js[1 .. idx].idup;
js = js[idx + 1 .. $];
type = javaObjectToDTypeString(type, javaPackages, detailedPackages, importPrefix);
break;
case 'V': js = js[1 .. $]; type = "void"; break;
case 'Z': js = js[1 .. $]; type = "bool"; break;
case 'B': js = js[1 .. $]; type = "byte"; break;
case 'C': js = js[1 .. $]; type = "wchar"; break;
case 'S': js = js[1 .. $]; type = "short"; break;
case 'J': js = js[1 .. $]; type = "long"; break;
case 'F': js = js[1 .. $]; type = "float"; break;
case 'D': js = js[1 .. $]; type = "double"; break;
case 'I': js = js[1 .. $]; type = "int"; break;
default: assert(0, js);
}
if(all.length) all ~= ", ";
all ~= type;
}
return all;
}
struct cp_info {
enum CONSTANT_Class = 7; // sizeof = 2
struct CONSTANT_Class_info {
@BigEndian:
ushort name_index;
}
enum CONSTANT_Fieldref = 9; // sizeof = 4
struct CONSTANT_Fieldref_info {
@BigEndian:
ushort class_index;
ushort name_and_type_index;
}
enum CONSTANT_Methodref = 10; // sizeof = 4
struct CONSTANT_Methodref_info {
@BigEndian:
ushort class_index;
ushort name_and_type_index;
}
enum CONSTANT_InterfaceMethodref = 11; // sizeof = 4
struct CONSTANT_InterfaceMethodref_info {
@BigEndian:
ushort class_index;
ushort name_and_type_index;
}
enum CONSTANT_String = 8; // sizeof = 2
struct CONSTANT_String_info {
@BigEndian:
ushort string_index;
}
enum CONSTANT_Integer = 3; // sizeof = 4
struct CONSTANT_Integer_info {
@BigEndian:
int bytes;
}
enum CONSTANT_Float = 4; // sizeof = 4
struct CONSTANT_Float_info {
@BigEndian:
float bytes;
}
enum CONSTANT_Long = 5; // sizeof = 8, but eats two slots
struct CONSTANT_Long_info {
@BigEndian:
long bytes;
}
enum CONSTANT_Double = 6; // sizeof = 8, but eats two slots
struct CONSTANT_Double_info {
@BigEndian:
double bytes;
}
enum CONSTANT_NameAndType = 12; // sizeof = 4
struct CONSTANT_NameAndType_info {
@BigEndian:
ushort name_index;
ushort descriptor_index;
}
enum CONSTANT_Utf8 = 1; // sizeof = 2 + length
struct CONSTANT_Utf8_info {
@BigEndian:
ushort length;
@NumElements!length char[] bytes; // actually modified UTF-8 but meh
}
enum CONSTANT_MethodHandle = 15; // sizeof = 3
struct CONSTANT_MethodHandle_info {
@BigEndian:
ubyte reference_kind;
ushort reference_index;
}
enum CONSTANT_MethodType = 16; // sizeof = 2; descriptor index
struct CONSTANT_MethodType_info {
@BigEndian:
ushort descriptor_index;
}
enum CONSTANT_InvokeDynamic = 18; // sizeof = 4
struct CONSTANT_InvokeDynamic_info {
@BigEndian:
ushort bootstrap_method_attr_index;
ushort name_and_type_index;
}
enum CONSTANT_Module = 19;
struct CONSTANT_Module_info {
@BigEndian:
ushort name_index;
}
enum CONSTANT_Package = 20;
struct CONSTANT_Package_info {
@BigEndian:
ushort name_index;
}
ubyte tag;
@Tagged!(tag)
union Info {
@Tag(CONSTANT_Class) CONSTANT_Class_info class_info;
@Tag(CONSTANT_Fieldref) CONSTANT_Fieldref_info fieldref_info;
@Tag(CONSTANT_Methodref) CONSTANT_Methodref_info methodref_info;
@Tag(CONSTANT_InterfaceMethodref) CONSTANT_InterfaceMethodref_info interfaceMethodref_info;
@Tag(CONSTANT_String) CONSTANT_String_info string_info;
@Tag(CONSTANT_Integer) CONSTANT_Integer_info integer_info;
@Tag(CONSTANT_Float) CONSTANT_Float_info float_info;
@Tag(CONSTANT_Long) CONSTANT_Long_info long_info;
@Tag(CONSTANT_Double) CONSTANT_Double_info double_info;
@Tag(CONSTANT_NameAndType) CONSTANT_NameAndType_info nameAndType_info;
@Tag(CONSTANT_Utf8) CONSTANT_Utf8_info utf8_info;
@Tag(CONSTANT_MethodHandle) CONSTANT_MethodHandle_info methodHandle_info;
@Tag(CONSTANT_MethodType) CONSTANT_MethodType_info methodType_info;
@Tag(CONSTANT_InvokeDynamic) CONSTANT_InvokeDynamic_info invokeDynamic_info;
@Tag(CONSTANT_Module) CONSTANT_Module_info module_info;
@Tag(CONSTANT_Package) CONSTANT_Package_info package_info;
}
Info info;
bool takesTwoSlots() {
return (tag == CONSTANT_Long || tag == CONSTANT_Double);
}
string toString() {
if(tag == CONSTANT_Utf8)
return cast(string) info.utf8_info.bytes;
import std.format;
return format("cp_info(%s)", tag);
}
}
struct field_info {
@BigEndian:
enum ACC_PUBLIC = 0x0001;
enum ACC_PRIVATE = 0x0002;
enum ACC_PROTECTED = 0x0004;
enum ACC_STATIC = 0x0008;
enum ACC_FINAL = 0x0010;
enum ACC_VOLATILE = 0x0040;
enum ACC_TRANSIENT = 0x0080;
enum ACC_SYNTHETIC = 0x1000;
enum ACC_ENUM = 0x4000;
ushort access_flags;
ushort name_index;
ushort descriptor_index;
ushort attributes_count;
@NumElements!attributes_count attribute_info[] attributes;
}
struct method_info {
@BigEndian:
ushort access_flags;
ushort name_index;
ushort descriptor_index;
ushort attributes_count;
@NumElements!attributes_count attribute_info[] attributes;
enum ACC_PUBLIC = 0x0001;
enum ACC_PRIVATE = 0x0002;
enum ACC_PROTECTED = 0x0004;
enum ACC_STATIC = 0x0008;
enum ACC_FINAL = 0x0010;
enum ACC_SYNCHRONIZED = 0x0020;
enum ACC_BRIDGE = 0x0040;
enum ACC_VARARGS = 0x0080;
enum ACC_NATIVE = 0x0100;
enum ACC_ABSTRACT = 0x0400;
enum ACC_STRICT = 0x0800;
enum ACC_SYNTHETIC = 0x1000;
}
struct attribute_info {
@BigEndian:
ushort attribute_name_index;
uint attribute_length;
@NumBytes!attribute_length ubyte[] info;
}
struct ClassFile {
@BigEndian:
enum ACC_PUBLIC = 0x0001;
enum ACC_FINAL = 0x0010;
enum ACC_SUPER = 0x0020;
enum ACC_INTERFACE = 0x0200;
enum ACC_ABSTRACT = 0x0400;
enum ACC_SYNTHETIC = 0x1000;
enum ACC_ANNOTATION = 0x2000;
enum ACC_ENUM = 0x4000;
const(char)[] className() {
return this.constant(this.constant(this.this_class).info.class_info.name_index).info.utf8_info.bytes;
}
const(char)[] superclassName() {
if(this.super_class)
return this.constant(this.constant(this.super_class).info.class_info.name_index).info.utf8_info.bytes;
return null;
}
const(char)[][] interfacesNames() {
typeof(return) ret;
foreach(iface; interfaces) {
ret ~= this.constant(this.constant(iface).info.class_info.name_index).info.utf8_info.bytes;
}
return ret;
}
Method[] methodsListing() {
Method[] ms;
foreach(met; this.methods) {
Method m;
m.name = this.constant(met.name_index).info.utf8_info.bytes;
m.signature = this.constant(met.descriptor_index).info.utf8_info.bytes;
m.flags = met.access_flags;
m.cf = &this;
ms ~= m;
}
return ms;
}
bool hasConcreteMethod(const(char)[] name, const(char)[] signature, ClassFile[string] allClasses) {
// I don't really care cuz I don't use the same root in D
if(this.className == "java/lang/Object")
return false;
foreach(m; this.methodsListing) {
if(m.name == name)// && m.signature == signature)
return true;
//return (m.flags & method_info.ACC_ABSTRACT) ? false : true; // abstract impls do not count as methods as far as overrides are concerend...
}
if(auto s = this.superclassName in allClasses)
return s.hasConcreteMethod(name, signature, allClasses);
return false;
}
static struct Method {
const(char)[] name;
const(char)[] signature;
ushort flags;
ClassFile* cf;
bool isOverride(ClassFile[string] allClasses) {
if(name == "<init>")
return false;
if(auto s = cf.superclassName in allClasses)
return s.hasConcreteMethod(name, signature, allClasses);
return false;
}
}
@MustBe(0xcafebabe) uint magic;
ushort minor_version;
ushort major_version;
ushort constant_pool_count_;
// the zeroth item of the constant pool is null, but not actually in the file.
ushort constant_pool_count() { return cast(ushort)(constant_pool_count_ - 1); }