Skip to content

Commit

Permalink
Fixes reflection on String constructors.
Browse files Browse the repository at this point in the history
	Change on 2016/07/07 by kstanger <kstanger@google.com>

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=126814024
  • Loading branch information
kstanger authored and Keith Stanger committed Jul 18, 2016
1 parent 729deb3 commit d8ed765
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 103 deletions.
3 changes: 3 additions & 0 deletions jre_emul/Classes/IOSReflection.m
Expand Up @@ -238,6 +238,9 @@ Method JreFindClassMethod(Class cls, const char *name) {
return nil; return nil;
} }
Method method = JreFindInstanceMethod(cls, methodInfo->selector); Method method = JreFindInstanceMethod(cls, methodInfo->selector);
if (!method) {
method = JreFindClassMethod(cls, methodInfo->selector);
}
if (!method) { if (!method) {
return nil; return nil;
} }
Expand Down
33 changes: 16 additions & 17 deletions jre_emul/Classes/NSString+JavaString.m
Expand Up @@ -934,23 +934,22 @@ jint javaStringHashCode(NSString *string) {


+ (const J2ObjcClassInfo *)__metadata { + (const J2ObjcClassInfo *)__metadata {
static const J2ObjcMethodInfo methods[] = { static const J2ObjcMethodInfo methods[] = {
{ "init", NULL, 0x1, -1, -1, -1, -1, -1, -1 }, { "string", NULL, 0x1, -1, -1, -1, -1, -1, -1 },
{ "initWithByteArray:", NULL, 0x1, -1, 0, -1, -1, -1, -1 }, { "stringWithBytes:", NULL, 0x1, -1, 0, -1, -1, -1, -1 },
{ "initWithByteArray:withInt:", NULL, 0x1, -1, 1, -1, -1, -1, -1 }, { "stringWithBytes:hibyte:", NULL, 0x1, -1, 1, -1, -1, -1, -1 },
{ "initWithByteArray:withInt:withInt:", NULL, 0x1, -1, 2, -1, -1, -1, -1 }, { "stringWithBytes:offset:length:", NULL, 0x1, -1, 2, -1, -1, -1, -1 },
{ "initWithByteArray:withInt:withInt:withInt:", NULL, 0x1, -1, 3, -1, -1, -1, -1 }, { "stringWithBytes:hibyte:offset:length:", NULL, 0x1, -1, 3, -1, -1, -1, -1 },
{ "initWithByteArray:withInt:withInt:withNSString:", NULL, 0x1, -1, 4, 5, -1, -1, -1 }, { "stringWithBytes:offset:length:charsetName:", NULL, 0x1, -1, 4, 5, -1, -1, -1 },
{ "initWithByteArray:withInt:withInt:withJavaNioCharsetCharset:", NULL, 0x1, -1, 6, -1, -1, -1, { "stringWithBytes:offset:length:charset:", NULL, 0x1, -1, 6, -1, -1, -1, -1 },
-1 }, { "stringWithBytes:charset:", NULL, 0x1, -1, 7, -1, -1, -1, -1 },
{ "initWithByteArray:withJavaNioCharsetCharset:", NULL, 0x1, -1, 7, -1, -1, -1, -1 }, { "stringWithBytes:charsetName:", NULL, 0x1, -1, 8, 5, -1, -1, -1 },
{ "initWithByteArray:withNSString:", NULL, 0x1, -1, 8, 5, -1, -1, -1 }, { "stringWithCharacters:", NULL, 0x1, -1, 9, -1, -1, -1, -1 },
{ "initWithCharArray:", NULL, 0x1, -1, 9, -1, -1, -1, -1 }, { "stringWithCharacters:offset:length:", NULL, 0x1, -1, 10, -1, -1, -1, -1 },
{ "initWithCharArray:withInt:withInt:", NULL, 0x1, -1, 10, -1, -1, -1, -1 }, { "stringWithInts:offset:length:", NULL, 0x1, -1, 11, -1, -1, -1, -1 },
{ "initWithIntArray:withInt:withInt:", NULL, 0x1, -1, 11, -1, -1, -1, -1 }, { "stringWithOffset:length:characters:", NULL, 0x0, -1, 12, -1, -1, -1, -1 },
{ "initWithInt:withInt:withCharArray:", NULL, 0x0, -1, 12, -1, -1, -1, -1 }, { "stringWithString:", NULL, 0x1, -1, 13, -1, -1, -1, -1 },
{ "initWithNSString:", NULL, 0x1, -1, 13, -1, -1, -1, -1 }, { "stringWithJavaLangStringBuffer:", NULL, 0x1, -1, 14, -1, -1, -1, -1 },
{ "initWithJavaLangStringBuffer:", NULL, 0x1, -1, 14, -1, -1, -1, -1 }, { "stringWithJavaLangStringBuilder:", NULL, 0x1, -1, 15, -1, -1, -1, -1 },
{ "initWithJavaLangStringBuilder:", NULL, 0x1, -1, 15, -1, -1, -1, -1 },
{ "valueOfChars:", "LNSString;", 0x9, 16, 9, -1, -1, -1, -1 }, { "valueOfChars:", "LNSString;", 0x9, 16, 9, -1, -1, -1, -1 },
{ "valueOfChars:offset:count:", "LNSString;", 0x9, 16, 10, -1, -1, -1, -1 }, { "valueOfChars:offset:count:", "LNSString;", 0x9, 16, 10, -1, -1, -1, -1 },
{ "formatWithJavaUtilLocale:withNSString:withNSObjectArray:", "LNSString;", 0x89, 17, 18, -1, { "formatWithJavaUtilLocale:withNSString:withNSObjectArray:", "LNSString;", 0x89, 17, 18, -1,
Expand Down
95 changes: 43 additions & 52 deletions jre_emul/Classes/java/lang/reflect/Constructor.m
Expand Up @@ -24,7 +24,6 @@
#import "J2ObjC_source.h" #import "J2ObjC_source.h"
#import "NSException+JavaThrowable.h" #import "NSException+JavaThrowable.h"
#import "java/lang/AssertionError.h" #import "java/lang/AssertionError.h"
#import "java/lang/ExceptionInInitializerError.h"
#import "java/lang/IllegalArgumentException.h" #import "java/lang/IllegalArgumentException.h"
#import "java/lang/reflect/InvocationTargetException.h" #import "java/lang/reflect/InvocationTargetException.h"
#import "java/lang/reflect/Method.h" #import "java/lang/reflect/Method.h"
Expand All @@ -42,68 +41,60 @@ + (instancetype)constructorWithMethodSignature:(NSMethodSignature *)methodSignat
metadata:metadata] autorelease]; metadata:metadata] autorelease];
} }


- (id)newInstanceWithNSObjectArray:(IOSObjectArray *)initArgs { static id NewInstance(JavaLangReflectConstructor *self, void (^fillArgs)(NSInvocation *)) {
id newInstance = [self allocInstance]; const char *name = self->metadata_->selector;
NSInvocation *invocation = [self invocationForTarget:newInstance]; Class cls = self->class_.objcClass;

bool isFactory = false;
jint argCount = initArgs ? initArgs->size_ : 0; Method method = JreFindInstanceMethod(cls, name);
IOSObjectArray *parameterTypes = [self getParameterTypesInternal]; if (!method) {
if (argCount != parameterTypes->size_) { // Special case for constructors declared as class methods.
@throw AUTORELEASE([[JavaLangIllegalArgumentException alloc] initWithNSString: method = JreFindClassMethod(cls, name);
@"wrong number of arguments"]); isFactory = true;
}

for (jint i = 0; i < argCount; i++) {
J2ObjcRawValue arg;
if (![parameterTypes->buffer_[i] __unboxValue:initArgs->buffer_[i] toRawValue:&arg]) {
@throw AUTORELEASE([[JavaLangIllegalArgumentException alloc] initWithNSString:
@"argument type mismatch"]);
}
[invocation setArgument:&arg atIndex:i + SKIPPED_ARGUMENTS];
} }

NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:
[self invoke:invocation]; [NSMethodSignature signatureWithObjCTypes:method_getTypeEncoding(method)]];

[invocation setSelector:sel_registerName(name)];
return newInstance; fillArgs(invocation);
}

- (id)jniNewInstance:(const J2ObjcRawValue *)args {
id newInstance = [self allocInstance];
NSInvocation *invocation = [self invocationForTarget:newInstance];
for (int i = 0; i < [self getNumParams]; i++) {
[invocation setArgument:(void *)&args[i] atIndex:i + SKIPPED_ARGUMENTS];
}
[self invoke:invocation];
return newInstance;
}

- (id)allocInstance {
id newInstance; id newInstance;
@try { @try {
newInstance = AUTORELEASE([class_.objcClass alloc]); if (isFactory) {
[invocation invokeWithTarget:cls];
[invocation getReturnValue:&newInstance];
} else {
newInstance = [[cls alloc] autorelease];
[invocation invokeWithTarget:newInstance];
}
} }
@catch (NSException *e) { @catch (NSException *e) {
@throw AUTORELEASE([[JavaLangExceptionInInitializerError alloc] initWithNSException:e]); @throw create_JavaLangReflectInvocationTargetException_initWithNSException_(e);
} }
return newInstance; return newInstance;
} }


- (NSInvocation *)invocationForTarget:(id)object { - (id)newInstanceWithNSObjectArray:(IOSObjectArray *)initArgs {
NSInvocation *invocation = jint argCount = initArgs ? initArgs->size_ : 0;
[NSInvocation invocationWithMethodSignature:methodSignature_]; IOSObjectArray *parameterTypes = [self getParameterTypesInternal];
[invocation setSelector:JreMethodSelector(metadata_)]; if (argCount != parameterTypes->size_) {
[invocation setTarget:object]; @throw create_JavaLangIllegalArgumentException_initWithNSString_(@"wrong number of arguments");
return invocation; }

return NewInstance(self, ^(NSInvocation *invocation) {
for (jint i = 0; i < argCount; i++) {
J2ObjcRawValue arg;
if (![parameterTypes->buffer_[i] __unboxValue:initArgs->buffer_[i] toRawValue:&arg]) {
@throw create_JavaLangIllegalArgumentException_initWithNSString_(@"argument type mismatch");
}
[invocation setArgument:&arg atIndex:i + SKIPPED_ARGUMENTS];
}
});
} }


- (void)invoke:(NSInvocation *)invocation { - (id)jniNewInstance:(const J2ObjcRawValue *)args {
@try { return NewInstance(self, ^(NSInvocation *invocation) {
[invocation invoke]; for (int i = 0; i < [self getNumParams]; i++) {
} [invocation setArgument:(void *)&args[i] atIndex:i + SKIPPED_ARGUMENTS];
@catch (NSException *e) { }
@throw AUTORELEASE( });
[[JavaLangReflectInvocationTargetException alloc] initWithNSException:e]);
}
} }


// Returns the class name, like java.lang.reflect.Constructor does. // Returns the class name, like java.lang.reflect.Constructor does.
Expand Down
18 changes: 18 additions & 0 deletions jre_emul/Tests/com/google/j2objc/StringTest.java
Expand Up @@ -16,6 +16,8 @@


import junit.framework.TestCase; import junit.framework.TestCase;


import java.lang.reflect.Constructor;

/** /**
* Additional tests for java.lang.String support. * Additional tests for java.lang.String support.
* *
Expand All @@ -28,4 +30,20 @@ public void testRegexReplace() {
assertEquals("103456789", "000103456789".replaceFirst("^0+(?!$)", "")); assertEquals("103456789", "000103456789".replaceFirst("^0+(?!$)", ""));
assertEquals("103456789", "000103456789".replaceAll("^0+(?!$)", "")); assertEquals("103456789", "000103456789".replaceAll("^0+(?!$)", ""));
} }

public void testReflectOnStringConstructors() throws Exception {
Constructor<String> c;

c = String.class.getDeclaredConstructor();
assertNotNull(c);
assertEquals("", c.newInstance());

c = String.class.getDeclaredConstructor(char[].class);
assertNotNull(c);
assertEquals("foo", c.newInstance(new char[] { 'f', 'o', 'o' }));

c = String.class.getDeclaredConstructor(char[].class, int.class, int.class);
assertNotNull(c);
assertEquals("bar", c.newInstance(new char[] { 'f', 'o', 'o', 'b', 'a', 'r' }, 3, 3));
}
} }
Expand Up @@ -16,7 +16,6 @@


package com.google.devtools.j2objc.translate; package com.google.devtools.j2objc.translate;


import com.google.common.collect.ImmutableMap;
import com.google.devtools.j2objc.Options; import com.google.devtools.j2objc.Options;
import com.google.devtools.j2objc.ast.Block; import com.google.devtools.j2objc.ast.Block;
import com.google.devtools.j2objc.ast.ClassInstanceCreation; import com.google.devtools.j2objc.ast.ClassInstanceCreation;
Expand All @@ -41,8 +40,6 @@
import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.Modifier;


import java.util.Map;

/** /**
* Translates invocations of mapped constructors to method invocation nodes. * Translates invocations of mapped constructors to method invocation nodes.
* Adds copyWithZone methods to Cloneable types. * Adds copyWithZone methods to Cloneable types.
Expand All @@ -51,34 +48,6 @@
*/ */
public class JavaToIOSMethodTranslator extends TreeVisitor { public class JavaToIOSMethodTranslator extends TreeVisitor {


/**
* We convert all the String constructor invocations to factory method
* invocations because we want to avoid calling [NSString alloc].
* TODO(kstanger): This may not actually be necessary, investigate.
*/
private static final Map<String, String> STRING_CONSTRUCTOR_TO_METHOD_MAPPINGS =
ImmutableMap.<String, String>builder()
.put("java.lang.String.String()V", "string")
.put("java.lang.String.String(Ljava/lang/String;)V", "stringWithString:")
.put("java.lang.String.String([B)V", "stringWithBytes:")
.put("java.lang.String.String([BLjava/lang/String;)V", "stringWithBytes:charsetName:")
.put("java.lang.String.String([BLjava/nio/charset/Charset;)V", "stringWithBytes:charset:")
.put("java.lang.String.String([BI)V", "stringWithBytes:hibyte:")
.put("java.lang.String.String([BII)V", "stringWithBytes:offset:length:")
.put("java.lang.String.String([BIII)V", "stringWithBytes:hibyte:offset:length:")
.put("java.lang.String.String([BIILjava/lang/String;)V",
"stringWithBytes:offset:length:charsetName:")
.put("java.lang.String.String([BIILjava/nio/charset/Charset;)V",
"stringWithBytes:offset:length:charset:")
.put("java.lang.String.String([C)V", "stringWithCharacters:")
.put("java.lang.String.String([CII)V", "stringWithCharacters:offset:length:")
.put("java.lang.String.String([III)V", "stringWithInts:offset:length:")
.put("java.lang.String.String(II[C)V", "stringWithOffset:length:characters:")
.put("java.lang.String.String(Ljava/lang/StringBuffer;)V", "stringWithJavaLangStringBuffer:")
.put("java.lang.String.String(Ljava/lang/StringBuilder;)V",
"stringWithJavaLangStringBuilder:")
.build();

@Override @Override
public boolean visit(MethodDeclaration node) { public boolean visit(MethodDeclaration node) {
IMethodBinding method = node.getMethodBinding(); IMethodBinding method = node.getMethodBinding();
Expand Down Expand Up @@ -111,7 +80,7 @@ public boolean visit(ClassInstanceCreation node) {


IMethodBinding binding = node.getMethodBinding(); IMethodBinding binding = node.getMethodBinding();
String key = BindingUtil.getMethodKey(binding); String key = BindingUtil.getMethodKey(binding);
String selector = STRING_CONSTRUCTOR_TO_METHOD_MAPPINGS.get(key); String selector = NameTable.STRING_CONSTRUCTOR_TO_METHOD_MAPPINGS.get(key);
if (selector != null) { if (selector != null) {
assert !node.hasRetainedResult(); assert !node.hasRetainedResult();
if (key.equals("java.lang.String.String(Ljava/lang/String;)V")) { if (key.equals("java.lang.String.String(Ljava/lang/String;)V")) {
Expand Down
Expand Up @@ -273,6 +273,33 @@ public class NameTable {
"superclass", "toManyRelationshipKeys", "toOneRelationshipKeys", "superclass", "toManyRelationshipKeys", "toOneRelationshipKeys",
"version"); "version");


/**
* We convert all the String constructor invocations to factory method
* invocations because we want to avoid calling [NSString alloc].
*/
public static final Map<String, String> STRING_CONSTRUCTOR_TO_METHOD_MAPPINGS =
ImmutableMap.<String, String>builder()
.put("java.lang.String.String()V", "string")
.put("java.lang.String.String(Ljava/lang/String;)V", "stringWithString:")
.put("java.lang.String.String([B)V", "stringWithBytes:")
.put("java.lang.String.String([BLjava/lang/String;)V", "stringWithBytes:charsetName:")
.put("java.lang.String.String([BLjava/nio/charset/Charset;)V", "stringWithBytes:charset:")
.put("java.lang.String.String([BI)V", "stringWithBytes:hibyte:")
.put("java.lang.String.String([BII)V", "stringWithBytes:offset:length:")
.put("java.lang.String.String([BIII)V", "stringWithBytes:hibyte:offset:length:")
.put("java.lang.String.String([BIILjava/lang/String;)V",
"stringWithBytes:offset:length:charsetName:")
.put("java.lang.String.String([BIILjava/nio/charset/Charset;)V",
"stringWithBytes:offset:length:charset:")
.put("java.lang.String.String([C)V", "stringWithCharacters:")
.put("java.lang.String.String([CII)V", "stringWithCharacters:offset:length:")
.put("java.lang.String.String([III)V", "stringWithInts:offset:length:")
.put("java.lang.String.String(II[C)V", "stringWithOffset:length:characters:")
.put("java.lang.String.String(Ljava/lang/StringBuffer;)V", "stringWithJavaLangStringBuffer:")
.put("java.lang.String.String(Ljava/lang/StringBuilder;)V",
"stringWithJavaLangStringBuilder:")
.build();

/** /**
* Map of package names to their specified prefixes. Multiple packages * Map of package names to their specified prefixes. Multiple packages
* can share a prefix; for example, the com.google.common packages in * can share a prefix; for example, the com.google.common packages in
Expand All @@ -292,8 +319,10 @@ public static class Factory {
// concurrent map, or make a copy for each NameTable. // concurrent map, or make a copy for each NameTable.
private PackagePrefixes prefixMap = Options.getPackagePrefixes(); private PackagePrefixes prefixMap = Options.getPackagePrefixes();


private final Map<String, String> methodMappings = private final Map<String, String> methodMappings = ImmutableMap.<String, String>builder()
ImmutableMap.copyOf(Options.getMethodMappings()); .putAll(Options.getMethodMappings())
.putAll(STRING_CONSTRUCTOR_TO_METHOD_MAPPINGS)
.build();


private Factory() { private Factory() {
// Make sure the input mapping files have valid selectors. // Make sure the input mapping files have valid selectors.
Expand Down

0 comments on commit d8ed765

Please sign in to comment.