Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: de714d0f31cc
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 11c23a9e3586
Choose a head ref
  • 3 commits
  • 8 files changed
  • 2 contributors

Commits on Jan 11, 2017

  1. Copy the full SHA
    438a0a0 View commit details

Commits on Jan 12, 2017

  1. [ji] deal with conflicting get/is Java method alias (for real)

    currently we're nondeterministic and depend on reflected method order
    
    foo -> getFoo method shall always win, since foo? is there for isFoo
    in case both getFoo and isFoo are specified
    
    the update should align nicely with expectations in #3470
    
    due compatibility we can not fix #4432
    isAnnotation() + getAnnotation(param) case is different since the get
    method isn't a (real) getter (and we're avoding a clash due #3262)
    kares committed Jan 12, 2017
    Copy the full SHA
    74c8725 View commit details

Commits on Jan 17, 2017

  1. Merge pull request #4435 from kares/test-ji-is3

    [ji] deterministic Java bean-style method aliases
    kares authored Jan 17, 2017
    Copy the full SHA
    11c23a9 View commit details
21 changes: 21 additions & 0 deletions core/src/main/java/org/jruby/javasupport/JavaUtil.java
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@
import static java.lang.Character.isUpperCase;
import static java.lang.Character.isDigit;
import static java.lang.Character.toLowerCase;
import static java.lang.Character.toUpperCase;

import java.math.BigDecimal;
import java.math.BigInteger;
@@ -368,6 +369,26 @@ else if ( beanMethodName.startsWith("is") && length > 2 ) {
return null;
}

// property -> getProperty
public static String toJavaGetName(final String propertyName) {
if ( propertyName == null ) return null;
final int len = propertyName.length();
if ( len == 0 ) return null;
final char first = toUpperCase(propertyName.charAt(0));
if ( len == 1 ) return "get" + first;
return "get" + first + propertyName.substring(1);
}

// property -> isProperty
public static String toJavaIsName(final String propertyName) {
if ( propertyName == null ) return null;
final int len = propertyName.length();
if ( len == 0 ) return null;
final char first = toUpperCase(propertyName.charAt(0));
if ( len == 1 ) return "is" + first;
return "is" + first + propertyName.substring(1);
}

/**
* Build a Ruby name from a Java name by treating '_' as divider and successive
* caps as all the same word.
Original file line number Diff line number Diff line change
@@ -157,14 +157,14 @@ private void setupClassConstructors(final Class<?> javaClass, final State state)
}

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;
}

AssignedName assignedName = state.instanceNames.get(name);

if (assignedName == null) {
state.instanceNames.put(name, new AssignedName(name, Priority.METHOD));
} else {
@@ -188,15 +188,16 @@ private static void setupInstanceMethods(Map<String, NamedInstaller> methodCallb
}

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

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

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

// JRUBY-6967: Types with java.lang.Comparable were using Ruby Comparable#== instead of dispatching directly to
89 changes: 49 additions & 40 deletions core/src/main/java/org/jruby/javasupport/binding/Initializer.java
Original file line number Diff line number Diff line change
@@ -3,19 +3,14 @@
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.internal.runtime.methods.JavaMethod;
import org.jruby.javasupport.Java;
import org.jruby.javasupport.JavaClass;
import org.jruby.javasupport.JavaSupport;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Block;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.IdUtil;
import org.jruby.util.collections.*;
import org.jruby.util.collections.ClassValue;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;

@@ -28,7 +23,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import static org.jruby.runtime.Visibility.PUBLIC;
@@ -175,89 +169,104 @@ private static void setupStaticMethods(Map<String, NamedInstaller> methodCallbac
}

protected static void assignStaticAliases(final State state) {
for (Map.Entry<String, NamedInstaller> entry : state.staticInstallers.entrySet()) {
final Map<String, NamedInstaller> installers = state.staticInstallers;
for (Map.Entry<String, NamedInstaller> entry : installers.entrySet()) {
// no aliases for __method methods
if (entry.getKey().endsWith(METHOD_MANGLE)) continue;

if (entry.getValue().type == NamedInstaller.STATIC_METHOD && entry.getValue().hasLocalMethod()) {
assignAliases((MethodInstaller) entry.getValue(), state.staticNames);
assignAliases((MethodInstaller) entry.getValue(), state.staticNames, installers);
}
}
}

protected static void assignAliases(MethodInstaller installer, Map<String, AssignedName> assignedNames) {
String name = installer.name;
static void assignAliases(final MethodInstaller installer,
final Map<String, AssignedName> assignedNames, final Map<String, NamedInstaller> installers) {

final String name = installer.name;
String rubyCasedName = JavaUtil.getRubyCasedName(name);
addUnassignedAlias(rubyCasedName, assignedNames, installer);
addUnassignedAlias(rubyCasedName, assignedNames, installer, Priority.ALIAS);

String javaPropertyName = JavaUtil.getJavaPropertyName(name);
String rubyPropertyName = null;

for (Method method: installer.methods) {
final List<Method> methods = installer.methods;

for ( int i = 0; i < methods.size(); i++ ) {
final Method method = methods.get(i);
Class<?>[] argTypes = method.getParameterTypes();
Class<?> resultType = method.getReturnType();
int argCount = argTypes.length;

// Add scala aliases for apply/update to roughly equivalent Ruby names
if (rubyCasedName.equals("apply")) {
addUnassignedAlias("[]", assignedNames, installer);
if (name.equals("apply")) {
addUnassignedAlias("[]", assignedNames, installer, Priority.ALIAS);
}
if (rubyCasedName.equals("update") && argCount == 2) {
addUnassignedAlias("[]=", assignedNames, installer);
if (name.equals("update") && argCount == 2) {
addUnassignedAlias("[]=", assignedNames, installer, Priority.ALIAS);
}

// Scala aliases for $ method names
if (name.startsWith("$")) {
addUnassignedAlias(ClassInitializer.fixScalaNames(name), assignedNames, installer);
addUnassignedAlias(ClassInitializer.fixScalaNames(name), assignedNames, installer, Priority.ALIAS);
}

String rubyPropertyName = null;

// Add property name aliases
if (javaPropertyName != null) {
if (rubyCasedName.startsWith("get_")) {
rubyPropertyName = rubyCasedName.substring(4);
if (argCount == 0) { // getFoo => foo
addUnassignedAlias(javaPropertyName, assignedNames, installer);
addUnassignedAlias(rubyPropertyName, assignedNames, installer);
if (argCount == 0) { // getFoo => foo
addUnassignedAlias(javaPropertyName, assignedNames, installer, Priority.GET_ALIAS);
addUnassignedAlias(rubyPropertyName, assignedNames, installer, Priority.GET_ALIAS);
}
} else if (rubyCasedName.startsWith("set_")) {
rubyPropertyName = rubyCasedName.substring(4);
if (argCount == 1 && resultType == void.class) { // setFoo(Foo) => foo=(Foo)
addUnassignedAlias(javaPropertyName + '=', assignedNames, installer);
addUnassignedAlias(rubyPropertyName + '=', assignedNames, installer);
rubyPropertyName = rubyCasedName.substring(4); // TODO do not add foo? for setFoo (returning boolean)
if (argCount == 1 && resultType == void.class) { // setFoo(Foo) => foo=(Foo)
addUnassignedAlias(javaPropertyName + '=', assignedNames, installer, Priority.ALIAS);
addUnassignedAlias(rubyPropertyName + '=', assignedNames, installer, Priority.ALIAS);
}
} else if (rubyCasedName.startsWith("is_")) {
rubyPropertyName = rubyCasedName.substring(3);
if (resultType == boolean.class) { // isFoo() => foo, isFoo(*) => foo(*)
addUnassignedAlias(javaPropertyName, assignedNames, installer);
addUnassignedAlias(rubyPropertyName, assignedNames, installer);
// TODO (9.2) should be another check here to make sure these are only for getters
// ... e.g. isFoo() and not arbitrary isFoo(param) see GH-4432
if (resultType == boolean.class) { // isFoo() => foo, isFoo(*) => foo(*)
addUnassignedAlias(javaPropertyName, assignedNames, installer, Priority.IS_ALIAS);
addUnassignedAlias(rubyPropertyName, assignedNames, installer, Priority.IS_ALIAS);
// foo? is added bellow
}
}
}

// Additionally add ?-postfixed aliases to any boolean methods and properties.
if (resultType == boolean.class) {
// is_something?, contains_thing?
addUnassignedAlias(rubyCasedName + '?', assignedNames, installer);
if (rubyPropertyName != null) { // something?
addUnassignedAlias(rubyPropertyName + '?', assignedNames, installer);
// isFoo -> isFoo?, contains -> contains?
addUnassignedAlias(rubyCasedName + '?', assignedNames, installer, Priority.ALIAS);
if (rubyPropertyName != null) { // isFoo -> foo?
addUnassignedAlias(rubyPropertyName + '?', assignedNames, installer, Priority.ALIAS);
}
}
}
}

private static void addUnassignedAlias(String name, Map<String, AssignedName> assignedNames,
MethodInstaller installer) {
if (name == null) return;
private static boolean addUnassignedAlias(final String name,
final Map<String, AssignedName> assignedNames, final MethodInstaller installer,
final Priority aliasType) {

AssignedName assignedName = assignedNames.get(name);
// TODO: missing additional logic for dealing with conflicting protected fields.
if (Priority.ALIAS.moreImportantThan(assignedName)) {

if (aliasType.moreImportantThan(assignedName)) {
installer.addAlias(name);
assignedNames.put(name, new AssignedName(name, Priority.ALIAS));
assignedNames.put(name, new AssignedName(name, aliasType));
return true;
}
else if (Priority.ALIAS.asImportantAs(assignedName)) {
if (aliasType.asImportantAs(assignedName)) {
installer.addAlias(name);
return true;
}

// TODO: missing additional logic for dealing with conflicting protected fields.

return false;
}

protected static String fixScalaNames(final String name) {
Original file line number Diff line number Diff line change
@@ -45,10 +45,11 @@ public RubyModule initialize(RubyModule proxy) {
handleScalaSingletons(javaClass, state);

// Now add all aliases for the static methods (fields) as appropriate
for (Map.Entry<String, NamedInstaller> entry : state.staticInstallers.entrySet()) {
final Map<String, NamedInstaller> installers = state.staticInstallers;
for (Map.Entry<String, NamedInstaller> entry : installers.entrySet()) {
final NamedInstaller installer = entry.getValue();
if (installer.type == NamedInstaller.STATIC_METHOD && installer.hasLocalMethod()) {
assignAliases((MethodInstaller) installer, state.staticNames);
assignAliases((MethodInstaller) installer, state.staticNames, installers);
}
}

Original file line number Diff line number Diff line change
@@ -2,19 +2,19 @@

import org.jruby.RubyModule;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.java.invokers.MethodInvoker;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
* Created by headius on 2/26/15.
*/
public abstract class MethodInstaller extends NamedInstaller {

protected final List<Method> methods = new ArrayList<>(4);
protected List<String> aliases;
final ArrayList<Method> methods = new ArrayList<>(4);
private List<String> aliases;
private boolean localMethod;

public MethodInstaller(String name, int type) { super(name, type); }
@@ -29,13 +29,19 @@ final void addMethod(final Method method, final Class<?> clazz) {

// called only by initializing thread; no synchronization required
final void addAlias(final String alias) {
List<String> aliases = this.aliases;
Collection<String> aliases = this.aliases;
if (aliases == null) {
aliases = this.aliases = new ArrayList<>(4);
}
if ( ! aliases.contains(alias) ) aliases.add(alias);
}

final void removeAlias(final String alias) {
Collection<String> aliases = this.aliases;
if (aliases == null) return;
aliases.remove(alias);
}

final void defineMethods(RubyModule target, DynamicMethod invoker) {
defineMethods(target, invoker, true);
}
11 changes: 9 additions & 2 deletions core/src/main/java/org/jruby/javasupport/binding/Priority.java
Original file line number Diff line number Diff line change
@@ -6,8 +6,15 @@
* an alias (ALIAS) in a superclass, but not a method (METHOD).
*/
public enum Priority {
RESERVED(0), METHOD(1), FIELD(2), PROTECTED_METHOD(3),
WEAKLY_RESERVED(4), ALIAS(5), PROTECTED_FIELD(6);
RESERVED(0),
METHOD(1),
FIELD(2),
PROTECTED_METHOD(3),
WEAKLY_RESERVED(4),
ALIAS(5),
GET_ALIAS(6),
IS_ALIAS(7),
PROTECTED_FIELD(8);

private final int value;

88 changes: 88 additions & 0 deletions test/jruby/test_higher_javasupport.rb
Original file line number Diff line number Diff line change
@@ -716,6 +716,94 @@ def test_make_sure_we_are_not_writing_class_methods_to_the_same_place
assert_nothing_raised { AlphaSingleton.getInstance.alpha }
end

def test_conflicting_getter_aliasing
assert BetaSingleton.instance.respond_to?(:beta)
assert BetaSingleton.instance.respond_to?(:beta?)
assert BetaSingleton.respond_to?(:betac)
assert BetaSingleton.respond_to?(:betac?)

instance = BetaSingleton.instance
assert_equal 'Beta', instance.getBeta
assert_equal 'Beta', instance.get_beta
assert_equal 'beta', instance.beta
assert_equal true, instance.beta?

assert_equal 'Beta2', instance.getBeta2
assert_equal 'Beta2', instance.get_beta2
assert_equal 'Beta2', instance.beta2
assert_equal true, instance.beta2?

assert_equal 'Beta3', instance.getBeta3
assert_equal 'Beta3', instance.get_beta3
assert_equal 'Beta3', instance.beta3
assert_equal true, instance.beta3?

assert_equal 'Beta4', instance.getBeta4
assert_equal 'Beta4', instance.get_beta4
assert_equal true, instance.beta4

assert_equal 'Beta5', instance.getBeta5
assert_equal 'Beta5', instance.get_beta5
assert_equal true, instance.beta5(nil)

assert_equal 'beta6', instance.beta6
assert_equal true, instance.beta6?

assert_equal nil , instance.beta7
assert_equal true, instance.beta7?

assert_equal 'BetaCased', instance.getBetaCased
assert_equal 'BetaCased', instance.get_beta_cased
assert_equal 'betaCased', instance.betaCased
assert_equal 'betaCased', instance.beta_cased
assert_equal true, instance.beta_cased?
assert_equal true, instance.isBetaCased
assert_equal true, instance.is_beta_cased
assert_equal true, instance.is_beta_cased?

assert_equal 'BetaCased2', instance.betaCased2
assert_equal 'BetaCased2', instance.beta_cased2
assert_equal true, instance.beta_cased2?

assert_equal 'BetaCased3', instance.betaCased3
assert_equal 'BetaCased3', instance.beta_cased3
assert_equal true, instance.beta_cased3?

#

assert_equal 'BetaCasedc', instance.class.getBetaCasedc
assert_equal 'BetaCasedc', instance.class.get_beta_casedc
assert_equal 'betaCasedc', instance.class.betaCasedc
assert_equal 'betaCasedc', instance.class.beta_casedc
assert_equal true, instance.class.beta_casedc?
assert_equal true, instance.class.isBetaCasedc
assert_equal true, instance.class.is_beta_casedc?

assert_equal 'BetaCasedc2', instance.class.betaCasedc2
assert_equal 'BetaCasedc2', instance.class.beta_casedc2
assert_equal true, instance.class.beta_casedc2?

assert_equal 'BetaCasedc3', instance.class.betaCasedc3
assert_equal 'BetaCasedc3', instance.class.beta_casedc3
assert_equal true, instance.class.beta_casedc3?

klass = BetaSingleton
assert_equal 'BetaClass', klass.getBetac
assert_equal 'BetaClass', klass.get_betac
assert_equal 'betaClass', klass.betac
assert_equal true, klass.betac?

assert_equal 'BetaClass2', klass.betac2
assert_equal true, klass.betac2?

assert_equal 'BetaClass3', klass.betac3
assert_equal true, klass.betac3?

assert_equal 'betaClass4', klass.betac4
assert_equal 'BetaClass4', klass.get_betac4
assert_raises(NoMethodError) { klass.betac4? }
end

java_import 'org.jruby.javasupport.test.Color'

def test_lazy_proxy_method_tests_for_alias_and_respond_to
161 changes: 157 additions & 4 deletions test/org/jruby/test/BetaSingleton.java
Original file line number Diff line number Diff line change
@@ -7,11 +7,164 @@ public static final BetaSingleton getInstance() {
return INSTANCE;
}
private static final BetaSingleton INSTANCE = new BetaSingleton();
private BetaSingleton() {
private BetaSingleton() { /* no-op */ }

// class :

public static String betac() {
return "betaClass";
}

public static boolean isBetac() {
return true;
}

public static String getBetac() {
return "BetaClass";
}

// 2

public static boolean isBetac2() {
return true;
}

public static String getBetac2() {
return "BetaClass2";
}

// 3

public static String getBetac3() {
return "BetaClass3";
}

public static boolean isBetac3() {
return true;
}

public String beta() {
return "Beta";

// 4

public static String getBetac4() {
return "BetaClass4";
}

public static Object betac4() {
return "betaClass4";
}

//

public String getBeta() { return "Beta"; }

public String beta() { return "beta"; }

public boolean isBeta() { return true; }

// 2

public String getBeta2() { return "Beta2"; }

public boolean isBeta2() { return true; }

// 3

public boolean isBeta3() { return true; }

public String getBeta3() { return "Beta3"; }

// 4

public boolean beta4() {
return true;
}

public String getBeta4() {
return "Beta4";
}

// 5

public String getBeta5() {
return "Beta5";
}

public boolean beta5(final Object arg) {
return true;
}

// 6

public Object beta6() {
return "beta6";
}

public boolean isBeta6() {
return true;
}

// 7

public boolean isBeta7() {
return true;
}

public Boolean beta7() {
return null;
}

//

public static String betaCasedc() {
return "betaCasedc";
}

public static boolean isBetaCasedc() {
return true;
}

public static String getBetaCasedc() {
return "BetaCasedc";
}

// 2

public static boolean isBetaCasedc2() {
return true;
}

public static String getBetaCasedc2() {
return "BetaCasedc2";
}

// 3

public static String getBetaCasedc3() {
return "BetaCasedc3";
}

public static boolean isBetaCasedc3() {
return true;
}

//

public String getBetaCased() { return "BetaCased"; }

public String betaCased() { return "betaCased"; }

public boolean isBetaCased() { return true; }

// 2

public String getBetaCased2() { return "BetaCased2"; }

public boolean isBetaCased2() { return true; }

// 3

public boolean isBetaCased3() { return true; }

public String getBetaCased3() { return "BetaCased3"; }

}