Skip to content

Commit

Permalink
Pull all initialize logic up and out into initializer classes.
Browse files Browse the repository at this point in the history
  • Loading branch information
headius committed Feb 26, 2015
1 parent 725df3f commit be31344
Show file tree
Hide file tree
Showing 20 changed files with 1,300 additions and 1,112 deletions.
1,136 changes: 25 additions & 1,111 deletions core/src/main/java/org/jruby/javasupport/JavaClass.java

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/javasupport/JavaMethod.java
Expand Up @@ -107,7 +107,7 @@ public JavaMethod(Ruby runtime, Method method) {
// Special classes like Collections.EMPTY_LIST are inner classes that are private but
// implement public interfaces. Their methods are all public methods for the public
// interface. Let these public methods execute via setAccessible(true).
if (JavaClass.CAN_SET_ACCESSIBLE) {
if (JavaUtil.CAN_SET_ACCESSIBLE) {
// we should be able to setAccessible ok...
try {
if (methodIsPublic &&
Expand Down
30 changes: 30 additions & 0 deletions core/src/main/java/org/jruby/javasupport/JavaUtil.java
Expand Up @@ -42,8 +42,11 @@
import static java.lang.Character.isUpperCase;
import static java.lang.Character.isDigit;
import static java.lang.Character.toLowerCase;

import java.lang.reflect.ReflectPermission;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.AccessController;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
Expand All @@ -55,6 +58,7 @@
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyBasicObject;
import org.jruby.RubyInstanceConfig;
import org.jruby.ext.bigdecimal.RubyBigDecimal;
import org.jruby.RubyBignum;
import org.jruby.RubyBoolean;
Expand Down Expand Up @@ -85,8 +89,34 @@
import org.jruby.util.ByteList;
import org.jruby.util.CodegenUtils;
import org.jruby.util.TypeConverter;
import org.jruby.util.cli.Options;

public class JavaUtil {

public static final boolean CAN_SET_ACCESSIBLE;

static {
boolean canSetAccessible = false;

if (RubyInstanceConfig.CAN_SET_ACCESSIBLE) {
try {
AccessController.checkPermission(new ReflectPermission("suppressAccessChecks"));
canSetAccessible = true;
} catch (Throwable t) {
// added this so if things are weird in the future we can debug without
// spinning a new binary
if (Options.JI_LOGCANSETACCESSIBLE.load()) {
t.printStackTrace();
}

// assume any exception means we can't suppress access checks
canSetAccessible = false;
}
}

CAN_SET_ACCESSIBLE = canSetAccessible;
}

public static IRubyObject[] convertJavaArrayToRuby(Ruby runtime, Object[] objects) {
if (objects == null) return IRubyObject.NULL_ARRAY;

Expand Down
15 changes: 15 additions & 0 deletions core/src/main/java/org/jruby/javasupport/binding/AssignedName.java
@@ -0,0 +1,15 @@
package org.jruby.javasupport.binding;

/**
* Created by headius on 2/26/15.
*/
public class AssignedName {
String name;
Priority type;

public AssignedName() {}
public AssignedName(String name, Priority type) {
this.name = name;
this.type = type;
}
}
235 changes: 235 additions & 0 deletions core/src/main/java/org/jruby/javasupport/binding/ClassInitializer.java
@@ -0,0 +1,235 @@
package org.jruby.javasupport.binding;

import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.javasupport.Java;
import org.jruby.javasupport.JavaClass;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.Map;

/**
* Created by headius on 2/26/15.
*/
public class ClassInitializer extends Initializer {
public ClassInitializer(Ruby runtime, Class<?> javaClass) {
super(runtime, javaClass);
}

public void initialize(JavaClass javaClassObject, RubyModule proxy) {
RubyClass proxyClass = (RubyClass)proxy;
Class<?> superclass = javaClass.getSuperclass();

final State state = new State(runtime, superclass);

super.initializeBase(proxy);

if ( javaClass.isArray() || javaClass.isPrimitive() ) {
// see note below re: 2-field kludge
javaClassObject.setProxyClass(proxyClass);
javaClassObject.setProxyModule(proxy);
return;
}

setupClassFields(javaClass, state);
setupClassMethods(javaClass, state);
setupClassConstructors(javaClass, state);

javaClassObject.staticAssignedNames = Collections.unmodifiableMap(state.staticNames);
javaClassObject.instanceAssignedNames = Collections.unmodifiableMap(state.instanceNames);

proxyClass.setReifiedClass(javaClass);

assert javaClassObject.proxyClass == null;
javaClassObject.unfinishedProxyClass = proxyClass;

// flag the class as a Java class proxy.
proxy.setJavaProxy(true);
proxy.getSingletonClass().setJavaProxy(true);

// set parent to either package module or outer class
final RubyModule parent;
final Class<?> enclosingClass = javaClass.getEnclosingClass();
if ( enclosingClass != null ) {
parent = Java.getProxyClass(runtime, enclosingClass);
} else {
parent = Java.getJavaPackageModule(runtime, javaClass.getPackage());
}
proxy.setParent(parent);

// set the Java class name and package
if ( javaClass.isAnonymousClass() ) {
String baseName = ""; // javaClass.getSimpleName() returns "" for anonymous
if ( enclosingClass != null ) {
// instead of an empty name anonymous classes will have a "conforming"
// although not valid (by Ruby semantics) RubyClass name e.g. :
// 'Java::JavaUtilConcurrent::TimeUnit::1' for $1 anonymous enum class
// NOTE: if this turns out suitable shall do the same for method etc.
final String className = javaClass.getName();
final int length = className.length();
final int offset = enclosingClass.getName().length();
if ( length > offset && className.charAt(offset) != '$' ) {
baseName = className.substring( offset );
}
else if ( length > offset + 1 ) { // skip '$'
baseName = className.substring( offset + 1 );
}
}
proxy.setBaseName( baseName );
}
else {
proxy.setBaseName( javaClass.getSimpleName() );
}

installClassFields(proxyClass, state);
installClassInstanceMethods(proxyClass, state);
installClassConstructors(proxyClass, state);
installClassClasses(javaClass, proxyClass);

// FIXME: bit of a kludge here (non-interface classes assigned to both
// class and module fields). simplifies proxy extender code, will go away
// when JI is overhauled (and proxy extenders are deprecated).
javaClassObject.setProxyClass(proxyClass);
javaClassObject.setProxyModule(proxy);

javaClassObject.applyProxyExtenders();

// TODO: we can probably release our references to the constantFields
// array and static/instance callback hashes at this point.
}

private static void installClassInstanceMethods(final RubyClass proxy, final Initializer.State state) {
installClassStaticMethods(proxy, state);
//assert state.instanceInstallers != null;
for ( Map.Entry<String, NamedInstaller> entry : state.instanceInstallers.entrySet() ) {
entry.getValue().install(proxy);
}
}

private static void setupClassFields(Class<?> javaClass, Initializer.State state) {
Field[] fields = JavaClass.getFields(javaClass);

for (int i = fields.length; --i >= 0;) {
Field field = fields[i];
if (javaClass != field.getDeclaringClass()) continue;

if (ConstantField.isConstant(field)) {
state.constantFields.add(new ConstantField(field));
continue;
}

int modifiers = field.getModifiers();
if (Modifier.isStatic(modifiers)) {
addField(state.staticInstallers, state.staticNames, field, Modifier.isFinal(modifiers), true);
} else {
addField(state.instanceInstallers, state.instanceNames, field, Modifier.isFinal(modifiers), false);
}
}
}

private void setupClassMethods(Class<?> javaClass, State state) {
// TODO: protected methods. this is going to require a rework of some of the mechanism.
Method[] methods = getMethods(javaClass);

for (int i = methods.length; --i >= 0;) {
// we need to collect all methods, though we'll only
// install the ones that are named in this class
Method method = methods[i];
String name = method.getName();

if (Modifier.isStatic(method.getModifiers())) {
prepareStaticMethod(javaClass, state, method, name);
} else {
prepareInstanceMethod(javaClass, state, method, name);
}
}

// try to wire up Scala singleton logic if present
handleScalaSingletons(javaClass, state);

// now iterate over all installers and make sure they also have appropriate aliases
assignStaticAliases(state);
assignInstanceAliases(state);
}

private void setupClassConstructors(final Class<?> javaClass, final State state) {
// TODO: protected methods. this is going to require a rework
// of some of the mechanism.
final Constructor[] constructors = JavaClass.getConstructors(javaClass);

// create constructorInstaller; if there are no constructors, it will disable construction
ConstructorInvokerInstaller constructorInstaller = new ConstructorInvokerInstaller("__jcreate!");

for ( int i = constructors.length; --i >= 0; ) {
// we need to collect all methods, though we'll only
// install the ones that are named in this class
constructorInstaller.addConstructor(constructors[i], javaClass);
}

state.constructorInstaller = constructorInstaller;
}

private void prepareInstanceMethod(Class<?> javaClass, State state, Method method, String name) {
AssignedName assignedName = state.instanceNames.get(name);

// For JRUBY-4505, restore __method methods for reserved names
if (INSTANCE_RESERVED_NAMES.containsKey(method.getName())) {
setupInstanceMethods(state.instanceInstallers, javaClass, method, name + METHOD_MANGLE);
return;
}

if (assignedName == null) {
state.instanceNames.put(name, new AssignedName(name, Priority.METHOD));
} else {
if (Priority.METHOD.lessImportantThan(assignedName)) return;
if (!Priority.METHOD.asImportantAs(assignedName)) {
state.instanceInstallers.remove(name);
state.instanceInstallers.remove(name + '=');
state.instanceNames.put(name, new AssignedName(name, Priority.METHOD));
}
}
setupInstanceMethods(state.instanceInstallers, javaClass, method, name);
}

private static void setupInstanceMethods(Map<String, NamedInstaller> methodCallbacks, Class<?> javaClass, Method method, String name) {
MethodInstaller invoker = (MethodInstaller) methodCallbacks.get(name);
if (invoker == null) {
invoker = new InstanceMethodInvokerInstaller(name);
methodCallbacks.put(name, invoker);
}
invoker.addMethod(method, javaClass);
}

private static void assignInstanceAliases(State state) {
for (Map.Entry<String, NamedInstaller> entry : state.instanceInstallers.entrySet()) {
if (entry.getValue().type == NamedInstaller.INSTANCE_METHOD) {
MethodInstaller methodInstaller = (MethodInstaller)entry.getValue();

// no aliases for __method methods
if (entry.getKey().endsWith("__method")) continue;

if (methodInstaller.hasLocalMethod()) {
assignAliases(methodInstaller, state.instanceNames);
}

// JRUBY-6967: Types with java.lang.Comparable were using Ruby Comparable#== instead of dispatching directly to
// java.lang.Object.equals. We force an alias here to ensure we always use equals.
if (entry.getKey().equals("equals")) {
// we only install "local" methods, but need to force this so it will bind
methodInstaller.setLocalMethod(true);
methodInstaller.addAlias("==");
}
}
}
}

private static void installClassConstructors(final RubyModule proxy, final State state) {
if ( state.constructorInstaller != null ) state.constructorInstaller.install(proxy);
}

}
@@ -0,0 +1,38 @@
package org.jruby.javasupport.binding;

import org.jruby.RubyModule;
import org.jruby.javasupport.JavaUtil;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
* Created by headius on 2/26/15.
*/
public class ConstantField {

final Field field;

public ConstantField(Field field) { this.field = field; }

void install(final RubyModule proxy) {
final String name = field.getName();
if ( proxy.getConstantAt(name) == null ) {
try {
final Object value = field.get(null);
proxy.setConstant(name, JavaUtil.convertJavaToUsableRubyObject(proxy.getRuntime(), value));
}
catch (IllegalAccessException iae) {
// if we can't read it, we don't set it
}
}
}

private static final int CONSTANT = Modifier.FINAL | Modifier.PUBLIC | Modifier.STATIC;

static boolean isConstant(final Field field) {
return (field.getModifiers() & CONSTANT) == CONSTANT &&
Character.isUpperCase( field.getName().charAt(0) );
}

}

0 comments on commit be31344

Please sign in to comment.