Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Field annotations #624

Merged
merged 4 commits into from
May 21, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions lib/ruby/shared/jruby/core_ext/class.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def java_signature(signature_source)
# become_java!(child_loader, dump_dir)
def become_java!(*args)
self_r = JRuby.reference0(self)

if args.size > 0
dump_dir = nil
child_loader = true
Expand All @@ -80,7 +80,9 @@ def become_java!(*args)
else
self_r.reify_with_ancestors
end


generate_java_fields

self_r.reified_class
end

Expand Down Expand Up @@ -182,4 +184,50 @@ def add_method_signature(name, classes)

nil
end

def java_field(signature)
signature = signature.to_s

signature = signature.split(/\s/)

raise "Java Field must be specified as a string with the format <Type Name>" if signature.size != 2

type, name = signature
java_fields << name
add_field_signature(name, type)
end


def add_field_signature(name, type)
self_r = JRuby.reference0(self)

signature = JRuby::JavaSignature.new(nil, nil)
java_return_type = signature.as_java_type(type)

self_r.add_field_signature(name, java_return_type.to_java(JClass))
end

def add_field_annotation(name, annotations = {})
name = name.to_s
self_r = JRuby.reference0(self)

for cls, params in annotations
params ||= {}
self_r.add_field_annotation(name, _anno_class(cls), params)
end

nil
end

def generate_java_fields
java_fields.each do |field_name|
field = java_class.get_declared_field(field_name)
define_method(field_name) { field.get(self) }
define_method(:"#{field_name}=") { |v| field.set(self, v) }
end
end

def java_fields
@java_fields ||= []
end
end
30 changes: 30 additions & 0 deletions spec/java_integration/fixtures/FieldAnnotations.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package java_integration.fixtures;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class FieldAnnotations {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Annotated {
}

public static List<Annotation> countAnnotated(Class cls) {
Field[] declaredFields = cls.getDeclaredFields();
List<Annotation> annos = new ArrayList<Annotation>();
for (Field field: declaredFields) {
Annotated anno = field.getAnnotation(Annotated.class);
if (anno != null) {
annos.add(anno);
}
}

return annos;
}
}
20 changes: 20 additions & 0 deletions spec/java_integration/reify/annos_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,24 @@ def baz; end
Java::java_integration.fixtures.MethodAnnotations.countAnnotated(ClassWithAnnotatedMethods2).size.should == 2
end
end

context "field annotations using #add_field_annotation" do
let(:cls) do
Class.new do
java_field "java.lang.String foo"
add_field_annotation(:foo, Java::java_integration.fixtures.FieldAnnotations::Annotated => {})

java_field "java.lang.String bar"
add_field_annotation(:bar, Java::java_integration.fixtures.FieldAnnotations::Annotated => {})

java_field "java.lang.String baz"

become_java!
end
end

it "has two annotated fields" do
Java::java_integration.fixtures.FieldAnnotations.countAnnotated(cls).size.should == 2
end
end
end
40 changes: 40 additions & 0 deletions spec/java_integration/reify/become_java_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,44 @@ class JRUBY5564; end
cl.load_class(a_class.get_name).should == a_class
end

describe "java fields" do
let(:cls) {
Class.new(&fields)
}
let(:fields) { proc {
java_field "java.lang.String foo"
} }

subject { cls.become_java! }

it "adds specified fields to java_class" do
subject.get_declared_fields.map { |f| f.get_name }.should == %w(ruby rubyClass foo)
end

it "lets you write to the fields" do
subject

cls.new.should respond_to :foo
cls.new.should respond_to :foo=

field = subject.get_declared_fields.to_a.detect { |f| f.get_name == "foo" }
instance = cls.new
instance.foo = "String Value"
instance.foo.should == "String Value"
field.get(instance).should == "String Value"
end

context "many fields" do
let(:fields) { proc {
java_field "java.lang.String foo"
java_field "java.lang.String bar"
java_field "java.lang.String baz"
java_field "java.lang.String zot"
java_field "java.lang.String allegro"
}}
it "keeps the ordering as specified" do
subject.get_declared_fields.map { |f| f.get_name }.should == %w(ruby rubyClass foo bar baz zot allegro)
end
end
end
end
57 changes: 57 additions & 0 deletions src/org/jruby/RubyClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -87,6 +88,7 @@
import org.jruby.util.log.LoggerFactory;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;

/**
*
Expand Down Expand Up @@ -1344,6 +1346,26 @@ public synchronized void reify(String classDumpDir, boolean useChildLoader) {
m.voidreturn();
m.end();

// define fields
for (Map.Entry<String, Class> fieldSignature : getFieldSignatures().entrySet()) {
String fieldName = fieldSignature.getKey();
Class type = fieldSignature.getValue();
Map<Class, Map<String, Object>> fieldAnnos = getFieldAnnotations().get(fieldName);

FieldVisitor fieldVisitor = cw.visitField(ACC_PUBLIC, fieldName, ci(type), null, null);

if (fieldAnnos == null) {
continue;
}

for (Map.Entry<Class, Map<String, Object>> fieldAnno : fieldAnnos.entrySet()) {
Class annoType = fieldAnno.getKey();
AnnotationVisitor av = fieldVisitor.visitAnnotation(ci(annoType), true);
CodegenUtils.visitAnnotationFields(av, fieldAnno.getValue());
}
fieldVisitor.visitEnd();
}

// gather a list of instance methods, so we don't accidentally make static ones that conflict
Set<String> instanceMethods = new HashSet<String>();

Expand Down Expand Up @@ -1561,6 +1583,12 @@ public Map<String,Map<Class,Map<String,Object>>> getMethodAnnotations() {
return methodAnnotations;
}

public Map<String,Map<Class,Map<String,Object>>> getFieldAnnotations() {
if (fieldAnnotations == null) return Collections.EMPTY_MAP;

return fieldAnnotations;
}

public synchronized void addMethodAnnotation(String methodName, Class annotation, Map fields) {
if (methodAnnotations == null) methodAnnotations = new Hashtable<String,Map<Class,Map<String,Object>>>();

Expand All @@ -1573,18 +1601,43 @@ public synchronized void addMethodAnnotation(String methodName, Class annotation
annos.put(annotation, fields);
}

public synchronized void addFieldAnnotation(String fieldName, Class annotation, Map fields) {
if (fieldAnnotations == null) fieldAnnotations = new Hashtable<String,Map<Class,Map<String,Object>>>();

Map<Class,Map<String,Object>> annos = fieldAnnotations.get(fieldName);
if (annos == null) {
annos = new Hashtable<Class,Map<String,Object>>();
fieldAnnotations.put(fieldName, annos);
}

annos.put(annotation, fields);
}


public Map<String,Class[]> getMethodSignatures() {
if (methodSignatures == null) return Collections.EMPTY_MAP;

return methodSignatures;
}

public Map<String, Class> getFieldSignatures() {
if (fieldSignatures == null) return Collections.EMPTY_MAP;

return fieldSignatures;
}

public synchronized void addMethodSignature(String methodName, Class[] types) {
if (methodSignatures == null) methodSignatures = new Hashtable<String,Class[]>();

methodSignatures.put(methodName, types);
}

public synchronized void addFieldSignature(String fieldName, Class type) {
if (fieldSignatures == null) fieldSignatures = new LinkedHashMap<String, Class>();

fieldSignatures.put(fieldName, type);
}

public Map<Class,Map<String,Object>> getClassAnnotations() {
if (classAnnotations == null) return Collections.EMPTY_MAP;

Expand Down Expand Up @@ -1884,8 +1937,12 @@ public boolean hasObjectID() {

private Map<String, Map<Class, Map<String,Object>>> methodAnnotations;

private Map<String, Map<Class, Map<String,Object>>> fieldAnnotations;

private Map<String, Class[]> methodSignatures;

private Map<String, Class> fieldSignatures;

private Map<Class, Map<String,Object>> classAnnotations;

/** A cached tuple of method, type, and generation for dumping */
Expand Down