Skip to content
8 changes: 4 additions & 4 deletions CodenameOne/src/com/codename1/system/package-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/// [support for making platform native API calls](https://www.codenameone.com/how-do-i---access-native-device-functionality-invoke-native-interfaces.html). Notice
/// that when we say "native" we do not mean C/C++ always but rather the platforms "native" environment. So in the
/// case of Android the Java code will be invoked with full access to the Android API's, in case of iOS an Objective-C
/// message would be sent and so forth.
/// or Swift message would be sent and so forth.
///
/// Native interfaces are designed to only allow primitive types, Strings, arrays (single dimension only!) of primitives
/// and PeerComponent values. Any other type of parameter/return type is prohibited. However, once in the native layer
Expand Down Expand Up @@ -59,9 +59,9 @@
/// These sources should be placed under the appropriate folder in the native directory and are sent to the
/// server for compilation.
///
/// For Objective-C, one would need to define a class matching the name of the package and the class name
/// combined where the "." elements are replaced by underscores. One would need to provide both a header and
/// an "m" file following this convention e.g.:
/// For iOS, one would need to define a class matching the name of the package and the class name
/// combined where the "." elements are replaced by underscores. This class can be implemented in Objective-C
/// (by providing both a header and an "m" file) or in Swift. Objective-C classes follow this convention e.g.:
///
/// ```java
/// @interface com_my_code_MyNative : NSObject {
Expand Down
10 changes: 5 additions & 5 deletions CodenameOne/src/com/codename1/system/package.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<a href="https://www.codenameone.com/how-do-i---access-native-device-functionality-invoke-native-interfaces.html">
support for making platform native API calls</a>. Notice
that when we say "native" we do not mean C/C++ always but rather the platforms "native" environment. So in the
case of Android the Java code will be invoked with full access to the Android API's, in case of iOS an Objective-C
message would be sent and so forth.
case of Android the Java code will be invoked with full access to the Android API's, in case of iOS an Objective-C
or Swift message would be sent and so forth.
</p>
<p>
Native interfaces are designed to only allow primitive types, Strings, arrays (single dimension only!) of primitives
Expand Down Expand Up @@ -66,9 +66,9 @@
server for compilation.
</p>
<p>
For Objective-C, one would need to define a class matching the name of the package and the class name
combined where the "." elements are replaced by underscores. One would need to provide both a header and
an "m" file following this convention e.g.:
For iOS, one would need to define a class matching the name of the package and the class name
combined where the "." elements are replaced by underscores. This class can be implemented in Objective-C
(by providing both a header and an "m" file) or in Swift. Objective-C classes follow this convention e.g.:
</p>
<pre>
@interface com_my_code_MyNative : NSObject {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
import java.awt.image.ImageProducer;
import java.awt.image.RGBImageFilter;
import java.io.*;
import java.lang.reflect.Method;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.channels.FileChannel;
Expand Down Expand Up @@ -3972,6 +3974,117 @@ static String xmlize(String s) {
}


@Override
protected String registerNativeImplementationsAndCreateStubs(ClassLoader parentClassLoader, File stubDir, File... classesDirectory) throws MalformedURLException, IOException {
Class[] discoveredNativeInterfaces = findNativeInterfaces(parentClassLoader, classesDirectory);
String registerNativeFunctions = "";
if (discoveredNativeInterfaces != null && discoveredNativeInterfaces.length > 0) {
for (Class n : discoveredNativeInterfaces) {
registerNativeFunctions += " NativeLookup.register(" + n.getName() + ".class, "
+ n.getName() + "Stub.class" + ");\n";
}
}

if (discoveredNativeInterfaces != null && discoveredNativeInterfaces.length > 0) {
for (Class currentNative : discoveredNativeInterfaces) {
File folder = new File(stubDir, currentNative.getPackage().getName().replace('.', File.separatorChar));
folder.mkdirs();
File javaFile = new File(folder, currentNative.getSimpleName() + "Stub.java");

String javaImplSourceFile = "package " + currentNative.getPackage().getName() + ";\n\n"
+ "import com.codename1.ui.PeerComponent;\n\n"
+ "public class " + currentNative.getSimpleName() + "Stub implements " + currentNative.getSimpleName() + "{\n"
+ " private final Object impl = createImpl();\n\n"
+ " private static Object createImpl() {\n"
+ " try {\n"
+ " return Class.forName(\"" + currentNative.getName() + getImplSuffix() + "\").newInstance();\n"
+ " } catch (Throwable t) {\n"
+ " throw new RuntimeException(\"Failed to instantiate native implementation for " + currentNative.getName() + "\", t);\n"
+ " }\n"
+ " }\n\n"
+ " private Object __cn1Invoke(String methodName, Object[] args) {\n"
+ " try {\n"
+ " java.lang.reflect.Method[] methods = impl.getClass().getMethods();\n"
+ " for (java.lang.reflect.Method method : methods) {\n"
+ " if (method.getName().equals(methodName) && method.getParameterTypes().length == args.length) {\n"
+ " return method.invoke(impl, args);\n"
+ " }\n"
+ " }\n"
+ " throw new RuntimeException(methodName + \" with \" + args.length + \" args\");\n"
+ " } catch (Throwable t) {\n"
+ " throw new RuntimeException(\"Failed to invoke native method \" + methodName, t);\n"
+ " }\n"
+ " }\n\n";

for (Method m : currentNative.getMethods()) {
String name = m.getName();
if (name.equals("hashCode") || name.equals("equals") || name.equals("toString")) {
continue;
}

Class returnType = m.getReturnType();

javaImplSourceFile += " public " + returnType.getSimpleName() + " " + name + "(";
Class[] params = m.getParameterTypes();
String args = "";
if (params != null && params.length > 0) {
for (int iter = 0; iter < params.length; iter++) {
if (iter > 0) {
javaImplSourceFile += ", ";
args += ", ";
}
javaImplSourceFile += params[iter].getSimpleName() + " param" + iter;
if (params[iter].getName().equals("com.codename1.ui.PeerComponent")) {
args += convertPeerComponentToNative("param" + iter);
} else {
args += "param" + iter;
}
}
}
javaImplSourceFile += ") {\n";
String invocationExpression = "__cn1Invoke(\"" + name + "\", new Object[]{" + args + "})";
if (Void.class == returnType || Void.TYPE == returnType) {
javaImplSourceFile += " " + invocationExpression + ";\n }\n\n";
} else {
if (returnType.getName().equals("com.codename1.ui.PeerComponent")) {
javaImplSourceFile += " return " + generatePeerComponentCreationCode(invocationExpression) + ";\n }\n\n";
} else if (returnType.isPrimitive()) {
if (returnType == Boolean.TYPE) {
javaImplSourceFile += " return ((Boolean)" + invocationExpression + ").booleanValue();\n }\n\n";
} else if (returnType == Integer.TYPE) {
javaImplSourceFile += " return ((Integer)" + invocationExpression + ").intValue();\n }\n\n";
} else if (returnType == Long.TYPE) {
javaImplSourceFile += " return ((Long)" + invocationExpression + ").longValue();\n }\n\n";
} else if (returnType == Byte.TYPE) {
javaImplSourceFile += " return ((Byte)" + invocationExpression + ").byteValue();\n }\n\n";
} else if (returnType == Short.TYPE) {
javaImplSourceFile += " return ((Short)" + invocationExpression + ").shortValue();\n }\n\n";
} else if (returnType == Character.TYPE) {
javaImplSourceFile += " return ((Character)" + invocationExpression + ").charValue();\n }\n\n";
} else if (returnType == Float.TYPE) {
javaImplSourceFile += " return ((Float)" + invocationExpression + ").floatValue();\n }\n\n";
} else if (returnType == Double.TYPE) {
javaImplSourceFile += " return ((Double)" + invocationExpression + ").doubleValue();\n }\n\n";
} else {
javaImplSourceFile += " return (" + returnType.getSimpleName() + ")" + invocationExpression + ";\n }\n\n";
}
} else {
javaImplSourceFile += " return (" + returnType.getSimpleName() + ")" + invocationExpression + ";\n }\n\n";
}
}
}

javaImplSourceFile += "}\n";

try (FileOutputStream out = new FileOutputStream(javaFile)) {
out.write(javaImplSourceFile.getBytes(StandardCharsets.UTF_8));
}
}
}

return registerNativeFunctions;
}

@Override
protected String generatePeerComponentCreationCode(String methodCallString) {
return "PeerComponent.create(" + methodCallString + ")";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1099,14 +1099,42 @@ public void usesClassMethod(String cls, String method) {
+ "#include \"java_lang_String.h\"\n"
+ "#import \"CodenameOne_GLViewController.h\"\n"
+ "#import <UIKit/UIKit.h>\n"
+ "#import \"" + classNameWithUnderscores + "Impl.h\"\n" + newVMInclude
+ newVMInclude
+ "#include \"" + classNameWithUnderscores + "ImplCodenameOne.h\"\n\n"
+ "static id cn1_createNativeInterfacePeer(NSString* className) {\n"
+ " NSMutableArray* candidates = [NSMutableArray arrayWithObject:className];\n"
+ " NSString* executableName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@\"CFBundleExecutable\"];\n"
+ " NSString* bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@\"CFBundleName\"];\n"
+ " NSArray* moduleNames = @[executableName ?: @\"\", bundleName ?: @\"\"];\n"
+ " for(NSString* moduleName in moduleNames) {\n"
+ " if(moduleName.length == 0) {\n"
+ " continue;\n"
+ " }\n"
+ " NSString* sanitized = [[moduleName stringByReplacingOccurrencesOfString:@\"-\" withString:@\"_\"] stringByReplacingOccurrencesOfString:@\" \" withString:@\"_\"];\n"
+ " [candidates addObject:[sanitized stringByAppendingFormat:@\".%@\", className]];\n"
+ " if(![sanitized isEqualToString:moduleName]) {\n"
+ " [candidates addObject:[moduleName stringByAppendingFormat:@\".%@\", className]];\n"
+ " }\n"
+ " }\n"
+ " Class cls = Nil;\n"
+ " for(NSString* candidate in candidates) {\n"
+ " cls = NSClassFromString(candidate);\n"
+ " if(cls != Nil) {\n"
+ " break;\n"
+ " }\n"
+ " }\n"
+ " if(cls == Nil) {\n"
+ " NSLog(@\"[CN1] Failed to find native interface class %@. Tried: %@\", className, candidates);\n"
+ " return nil;\n"
+ " }\n"
+ " return [[cls alloc] init];\n"
+ "}\n\n"
+ "JAVA_LONG " + classNameWithUnderscores + "ImplCodenameOne_initializeNativePeer__" + postfixForNewVM + "(" + prefixForNewVM + ") {\n"
+ " " + classNameWithUnderscores + "Impl* i = [[" + classNameWithUnderscores + "Impl alloc] init];\n"
+ " id i = cn1_createNativeInterfacePeer(@\"" + classNameWithUnderscores + "Impl\");\n"
+ " return i;\n"
+ "}\n\n"
+ "void " + classNameWithUnderscores + "ImplCodenameOne_releaseNativePeerInstance___long(" + prefix2ForNewVM + "JAVA_LONG l) {\n"
+ " " + classNameWithUnderscores + "Impl* i = (" + classNameWithUnderscores + "Impl*)l;\n"
+ " id i = (id)l;\n"
+ " [i release];\n"
+ "}\n\n"
+ "extern NSData* arrayToData(JAVA_OBJECT arr);\n"
Expand Down Expand Up @@ -1135,8 +1163,7 @@ public void usesClassMethod(String cls, String method) {
String mFileBody;

mFileArgs = "(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT me";
mFileBody = " " + classNameWithUnderscores + "Impl* ptr = (" + classNameWithUnderscores +
"Impl*)get_field_" + classNameWithUnderscores + "ImplCodenameOne_nativePeer(me);\n";
mFileBody = " id ptr = (id)get_field_" + classNameWithUnderscores + "ImplCodenameOne_nativePeer(me);\n";


if(!(returnType.equals(Void.class) || returnType.equals(Void.TYPE))) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.codenameone.examples.hellocodenameone

class SwiftKotlinNativeImpl {
fun implementationLanguage(): String {
return "kotlin"
}

fun isSupported(): Boolean {
return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.codenameone.examples.hellocodenameone;

import com.codename1.system.NativeLookup;
import com.codename1.ui.CN;

public final class NativeInterfaceLanguageValidator {
private NativeInterfaceLanguageValidator() {
}

public static void validate() {
String platformName = CN.getPlatformName();
String normalizedPlatform = platformName == null ? "" : platformName.toLowerCase();
boolean isAndroid = normalizedPlatform.contains("android");
boolean isIos = normalizedPlatform.contains("ios") || normalizedPlatform.contains("iphone");
if (!isAndroid && !isIos) {
return;
}

SwiftKotlinNative nativeImpl = NativeLookup.create(SwiftKotlinNative.class);
if (nativeImpl == null || !nativeImpl.isSupported()) {
throw new IllegalStateException("SwiftKotlinNative is not available on " + platformName);
}

String expected = isAndroid ? "kotlin" : "swift";
String actual = nativeImpl.implementationLanguage();
if (!expected.equalsIgnoreCase(actual)) {
throw new IllegalStateException("Expected " + expected + " implementation on " + platformName + " but got " + actual);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.codenameone.examples.hellocodenameone;

import com.codename1.system.NativeInterface;

public interface SwiftKotlinNative extends NativeInterface {
String implementationLanguage();
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ open class HelloCodenameOne : Lifecycle() {
"Jailbroken device detected by Display.isJailbrokenDevice()."
}
DefaultMethodDemo.validate()
NativeInterfaceLanguageValidator.validate()
Cn1ssDeviceRunner.addTest(KotlinUiTest())
TestReporting.setInstance(Cn1ssDeviceRunnerReporter())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Foundation

@objc(com_codenameone_examples_hellocodenameone_SwiftKotlinNativeImpl)
class com_codenameone_examples_hellocodenameone_SwiftKotlinNativeImpl: NSObject {
@objc func implementationLanguage() -> String {
return "swift"
}

@objc func isSupported() -> Bool {
return true
}
}
Loading