Skip to content
Permalink
Browse files
GROOVY-10592: support property notation for static interface accessors
  • Loading branch information
eric-milles committed Apr 22, 2022
1 parent 25919b0 commit de777e60c32d325cb25fe60efa14031d75e457a7
Showing 2 changed files with 131 additions and 79 deletions.
@@ -116,6 +116,7 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiConsumer;
import java.util.function.Function;

import static groovy.lang.Tuple.tuple;
@@ -2459,77 +2460,73 @@ private static int distanceToObject(Class c) {
*/
private void setupProperties(final PropertyDescriptor[] propertyDescriptors) {
if (theCachedClass.isInterface) {
LinkedList<CachedClass> superClasses = new LinkedList<>();
superClasses.add(ReflectionCache.OBJECT_CLASS);
LinkedList<CachedClass> superInterfaces = new LinkedList<>(theCachedClass.getInterfaces());

CachedClass superClass = ReflectionCache.OBJECT_CLASS;
List<CachedClass> superInterfaces = new ArrayList<>(theCachedClass.getInterfaces());
superInterfaces.remove(theCachedClass); // always includes interface theCachedClass
// sort interfaces so that we may ensure a deterministic behaviour in case of
// ambiguous fields (class implementing two interfaces using the same field)
// ambiguous fields -- class implementing two interfaces using the same field
if (superInterfaces.size() > 1) {
superInterfaces.sort(CACHED_CLASS_NAME_COMPARATOR);
}

Map<String, MetaProperty> iPropertyIndex = classPropertyIndex.computeIfAbsent(theCachedClass, k -> new LinkedHashMap<>());
for (CachedClass iclass : superInterfaces) {
Map<String, MetaProperty> sPropertyIndex = classPropertyIndex.computeIfAbsent(iclass, k -> new LinkedHashMap<>());
Map<String, MetaProperty> iPropertyIndex = classPropertyIndex.computeIfAbsent(theCachedClass, x -> new LinkedHashMap<>());
for (CachedClass sInterface : superInterfaces) {
Map<String, MetaProperty> sPropertyIndex = classPropertyIndex.computeIfAbsent(sInterface, x -> new LinkedHashMap<>());
copyNonPrivateFields(sPropertyIndex, iPropertyIndex, null);
addFields(iclass, iPropertyIndex);
addFields(sInterface, iPropertyIndex);
}
addFields(theCachedClass, iPropertyIndex);

applyPropertyDescriptors(propertyDescriptors);
applyStrayPropertyMethods(superClasses, classPropertyIndex, true);

makeStaticPropertyIndex();
applyStrayPropertyMethods(superClass, classPropertyIndex.computeIfAbsent(superClass, x -> new LinkedHashMap<>()), true);
} else {
LinkedList<CachedClass> superClasses = getSuperClasses();
LinkedList<CachedClass> superInterfaces = new LinkedList<>(theCachedClass.getInterfaces());

List<CachedClass> superClasses = getSuperClasses();
List<CachedClass> superInterfaces = new ArrayList<>(theCachedClass.getInterfaces());
// sort interfaces so that we may ensure a deterministic behaviour in case of
// ambiguous fields (class implementing two interfaces using the same field)
// ambiguous fields -- class implementing two interfaces using the same field
if (superInterfaces.size() > 1) {
superInterfaces.sort(CACHED_CLASS_NAME_COMPARATOR);
}

// if this an Array, then add the special read-only "length" property
if (theCachedClass.isArray) {
if (theCachedClass.isArray) { // add the special read-only "length" property
LinkedHashMap<String, MetaProperty> map = new LinkedHashMap<>();
map.put("length", arrayLengthProperty);
classPropertyIndex.put(theCachedClass, map);
}

inheritStaticInterfaceFields(superClasses, new LinkedHashSet<>(superInterfaces));
inheritStaticInterfaceFields(superClasses, superInterfaces);
inheritFields(superClasses);

applyPropertyDescriptors(propertyDescriptors);

applyStrayPropertyMethods(superClasses, classPropertyIndex, true);
applyStrayPropertyMethods(superClasses, classPropertyIndexForSuper, false);

makeStaticPropertyIndex();
}
fillStaticPropertyIndex();
}

private void makeStaticPropertyIndex() {
LinkedHashMap<String, MetaProperty> propertyMap = classPropertyIndex.computeIfAbsent(theCachedClass, k -> new LinkedHashMap<>());
for (Map.Entry<String, MetaProperty> entry : propertyMap.entrySet()) {
MetaProperty mp = entry.getValue();
if (mp instanceof CachedField) {
CachedField mfp = (CachedField) mp;
if (!mfp.isStatic()) continue;
} else if (mp instanceof MetaBeanProperty) {
MetaProperty result = establishStaticMetaProperty(mp);
if (result == null) continue;
else {
mp = result;
}
} else if (mp instanceof MultipleSetterProperty) {
MultipleSetterProperty msp = (MultipleSetterProperty) mp;
mp = msp.createStaticVersion();
private void fillStaticPropertyIndex() {
BiConsumer<String, MetaProperty> indexStaticProperty = (name, prop) -> {
if (prop instanceof CachedField) {
CachedField field = (CachedField) prop;
if (!field.isStatic()) { prop = null; }
} else if (prop instanceof MetaBeanProperty) {
prop = establishStaticMetaProperty(prop);
} else if (prop instanceof MultipleSetterProperty) {
prop = ((MultipleSetterProperty) prop).createStaticVersion();
} else {
continue; // ignore all other types
prop = null; // ignore all other types
}
staticPropertyIndex.put(entry.getKey(), mp);

if (prop != null) staticPropertyIndex.put(name, prop);
};

classPropertyIndex.computeIfAbsent(theCachedClass, x -> new LinkedHashMap<>()).forEach(indexStaticProperty);

if (theCachedClass.isInterface) { // GROOVY-10592: static interface accessors
Map<String, MetaProperty> strayProperties = new LinkedHashMap<>();
applyStrayPropertyMethods(theCachedClass, strayProperties, true);
strayProperties.forEach(indexStaticProperty);
}
}

@@ -2568,7 +2565,7 @@ private static MetaProperty establishStaticMetaProperty(final MetaProperty mp) {
return staticProperty;
}

private void inheritStaticInterfaceFields(List<CachedClass> superClasses, Set<CachedClass> interfaces) {
private void inheritStaticInterfaceFields(List<CachedClass> superClasses, Iterable<CachedClass> interfaces) {
for (CachedClass iclass : interfaces) {
LinkedHashMap<String, MetaProperty> iPropertyIndex = classPropertyIndex.computeIfAbsent(iclass, k -> new LinkedHashMap<>());
addFields(iclass, iPropertyIndex);
@@ -2611,31 +2608,36 @@ private static void copyNonPrivateFields(Map<String, MetaProperty> from, Map<Str
}
}

private void applyStrayPropertyMethods(LinkedList<CachedClass> superClasses, Map<CachedClass, LinkedHashMap<String, MetaProperty>> classPropertyIndex, boolean isThis) {
// now look for any stray getters that may be used to define a property
for (CachedClass superClass : superClasses) {
MetaMethodIndex.Header header = metaMethodIndex.getHeader(superClass.getTheClass());
Map<String, MetaProperty> propertyIndex = classPropertyIndex.computeIfAbsent(superClass, sc -> new LinkedHashMap<>());
for (MetaMethodIndex.Entry e = header.head; e != null; e = e.nextClassEntry) {
String methodName = e.name;
int methodNameLength = methodName.length();
boolean isBooleanGetter = methodName.startsWith("is");
if (methodNameLength < (isBooleanGetter ? 3 : 4)) continue;

boolean isGetter = methodName.startsWith("get") || isBooleanGetter;
boolean isSetter = methodName.startsWith("set");
if (!isGetter && !isSetter) continue;

Object propertyMethods = filterPropertyMethod(isThis ? e.methods : e.methodsForSuper, isGetter, isBooleanGetter);
if (propertyMethods == null) continue;
private void applyStrayPropertyMethods(Iterable<CachedClass> classes, Map<CachedClass, LinkedHashMap<String, MetaProperty>> propertyIndex, boolean isThis) {
for (CachedClass cc : classes) {
applyStrayPropertyMethods(cc, propertyIndex.computeIfAbsent(cc, x -> new LinkedHashMap<>()), isThis);
}
}

String propName = getPropName(methodName);
if (propertyMethods instanceof MetaMethod) {
createMetaBeanProperty(propertyIndex, propName, isGetter, (MetaMethod) propertyMethods);
} else {
for (MetaMethod m : (Iterable<MetaMethod>) propertyMethods) {
createMetaBeanProperty(propertyIndex, propName, isGetter, m);
}
/**
* Looks for any stray getters/setters that may be used to define a property.
*/
private void applyStrayPropertyMethods(CachedClass source, Map<String, MetaProperty> target, boolean isThis) {
MetaMethodIndex.Header header = metaMethodIndex.getHeader(source.getTheClass());
for (MetaMethodIndex.Entry e = header.head; e != null; e = e.nextClassEntry) {
String methodName = e.name;
int methodNameLength = methodName.length();
boolean isBooleanGetter = methodName.startsWith("is");
if (methodNameLength < (isBooleanGetter ? 3 : 4)) continue;

boolean isGetter = methodName.startsWith("get") || isBooleanGetter;
boolean isSetter = methodName.startsWith("set");
if (!isGetter && !isSetter) continue;

Object propertyMethods = filterPropertyMethod(isThis ? e.methods : e.methodsForSuper, isGetter, isBooleanGetter);
if (propertyMethods == null) continue;

String propName = getPropName(methodName);
if (propertyMethods instanceof MetaMethod) {
createMetaBeanProperty(target, propName, isGetter, (MetaMethod) propertyMethods);
} else {
for (MetaMethod m : (Iterable<MetaMethod>) propertyMethods) {
createMetaBeanProperty(target, propName, isGetter, m);
}
}
}
@@ -18,6 +18,8 @@
*/
package groovy.bugs

import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit
import org.junit.Test

import static groovy.test.GroovyAssert.assertScript
@@ -26,27 +28,75 @@ final class Groovy8579 {

@Test
void testCallToStaticInterfaceMethod1() {
assertScript '''
@groovy.transform.CompileStatic
Comparator test() {
Map.Entry.comparingByKey()
}
['CompileDynamic', 'CompileStatic', 'TypeChecked'].each { mode ->
assertScript """
@groovy.transform.${mode}
def test() {
Map.Entry.comparingByKey()
}
assert test() instanceof Comparator
'''
assert test() instanceof Comparator
"""
}
}

@Test
void testCallToStaticInterfaceMethod2() {
assertScript '''
import static java.util.Map.Entry.comparingByKey
['CompileDynamic', 'CompileStatic', 'TypeChecked'].each { mode ->
assertScript """
import static java.util.Map.Entry.comparingByKey
@groovy.transform.CompileStatic
Comparator test() {
comparingByKey()
}
@groovy.transform.${mode}
def test() {
comparingByKey()
}
assert test() instanceof Comparator
"""
}
}

assert test() instanceof Comparator
'''
@Test // GROOVY-10592
void testCallToStaticInterfaceMethod3() {
['CompileDynamic', 'CompileStatic', 'TypeChecked'].each { mode ->
def sourceDir = File.createTempDir()
def config = new CompilerConfiguration(
targetDirectory: File.createTempDir(),
jointCompilationOptions: [memStub: true]
)
try {
def a = new File(sourceDir, 'Face.java')
a.write '''
interface Face {
static String getValue() {
return "value";
}
static void setValue(String value) {
if (!"value".equals(value))
throw new AssertionError();
}
}
'''
def b = new File(sourceDir, 'Main.groovy')
b.write """
@groovy.transform.${mode}
void test() {
assert Face.value == 'value'
Face.value = 'value'
}
test()
"""

def loader = new GroovyClassLoader(this.class.classLoader)
def cu = new JavaAwareCompilationUnit(config, loader)
cu.addSources(a, b)
cu.compile()

loader.loadClass('Main').main()
} finally {
sourceDir.deleteDir()
config.targetDirectory.deleteDir()
}
}
}
}

0 comments on commit de777e6

Please sign in to comment.