You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2018/05/10 08:32:11 UTC

groovy git commit: Add documentation for metaclasses(closes #698)

Repository: groovy
Updated Branches:
  refs/heads/master 6d5bc0db7 -> 775706596


Add documentation for metaclasses(closes #698)


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/77570659
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/77570659
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/77570659

Branch: refs/heads/master
Commit: 7757065964922ee344e362b18413a0012308088b
Parents: 6d5bc0d
Author: Ruben Laguna <ru...@gmail.com>
Authored: Tue May 8 13:25:13 2018 +0200
Committer: sunlan <su...@apache.org>
Committed: Thu May 10 16:31:46 2018 +0800

----------------------------------------------------------------------
 gradle/asciidoctor.gradle              |  16 +++-
 src/spec/doc/core-metaprogramming.adoc | 110 ++++++++++++++++++++++++++--
 2 files changed, 118 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/77570659/gradle/asciidoctor.gradle
----------------------------------------------------------------------
diff --git a/gradle/asciidoctor.gradle b/gradle/asciidoctor.gradle
index 0718f5e..90f352b 100644
--- a/gradle/asciidoctor.gradle
+++ b/gradle/asciidoctor.gradle
@@ -51,6 +51,7 @@ asciidoctor {
                 jdk: "http://docs.oracle.com/javase/8/docs/api/index.html",
                 gjdk: "http://docs.groovy-lang.org/${version}/html/groovy-jdk/index.html",
                 gapi: "http://docs.groovy-lang.org/${version}/html/gapi/index.html",
+                gapid:  "http://docs.groovy-lang.org/${version}/html/gapi/",
         ]
 
         baseUrls.each { macroName, baseURL ->
@@ -59,12 +60,23 @@ asciidoctor {
                     def (className, anchor) = target.split('#') as List
                     options = [
                             "type"  : ":link",
-                            "target": calculateDocUrl(baseURL, className, anchor)
+                            "target": calculateDocUrl(baseURL, className, anchor),
                     ]
 
                     createInline(parent, "anchor", attributes.text?:target, attributes, options).render()
             }
         }
+
+        inlinemacro('gapid') { parent, target, attributes ->
+            def (className, anchor) = target.split('#') as List
+
+            def partialUrl = { -> className.replace('.', '/') + '.html' + (anchor ? '#' + anchor.replace(',',', ') : '')}
+            options = [
+                    "type": ":link",
+                    "target": "${baseUrls['gapid']}${partialUrl()}",
+            ]
+            createInline(parent, "anchor", attributes.text?:target, attributes, options).render()
+        }
     }
 }
 
@@ -148,4 +160,4 @@ String calculateDocUrl(String baseUrl, String className, String anchor) {
     if (className == "index") return baseUrl
 
     return baseUrl + "?" + className.replace('.', '/') + '.html' + (anchor ? '#' + anchor : '')
-}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy/blob/77570659/src/spec/doc/core-metaprogramming.adoc
----------------------------------------------------------------------
diff --git a/src/spec/doc/core-metaprogramming.adoc b/src/spec/doc/core-metaprogramming.adoc
index 960e98a..0247589 100644
--- a/src/spec/doc/core-metaprogramming.adoc
+++ b/src/spec/doc/core-metaprogramming.adoc
@@ -345,24 +345,122 @@ There is a distinct section on `@Category` in the <<core-metaprogramming.adoc#xf
 
 === Metaclasses
 
-(TBD)
+As explained in other section, Metaclasses has a central role in the method resolution. For every method invocation from groovy code, Groovy will find the `MetaClass` for the given object and delegate the method resolution to the metaclass via gapid:groovy.lang.MetaClass#invokeMethod(java.lang.Class,java.lang.Object,java.lang.String,java.lang.Object,boolean,boolean)[MetaClass#invokeMethod] which should not be confused with gapid:groovy.lang.GroovyObject#invokeMethod(java.lang.String,java.lang.Object)[GroovyObject#invokeMethod].
+
+
+==== The default metaclass `MetaClassImpl`
+By default objects get an instance of `MetaClassImpl` that implements the default method lookup. This method lookup includes looking up of the method in the object class ("regular" method) but also if no method is found this way it will resort to calling `methodMissing` and ultimately gapid:groovy.lang.GroovyObject.html#invokeMethod(java.lang.String,java.lang.Object)[GroovyObject#invokeMethod]
+
+
+[source,groovy]
+----
+class Foo {}
+
+def f = new Foo()
+
+assert f.metaClass =~ /MetaClassImpl/
+----
+
+
 
 ==== Custom metaclasses
 
-(TBD)
+You can change the metaclass of any object or class and replace with a custom implementation of the `MetaClass` gapi:groovy.lang.MetaClass[interface]. Usually you will want to subclass one of the existing metaclasses `MetaClassImpl`, `DelegatingMetaClass`, `ExpandoMetaClass`, `ProxyMetaClass`, etc. otherwise you will need to implement the complete method lookup logic. Before using a new metaclass instance you should call  gapid:groovy.lang.MetaClass#initialize()[] otherwise the metaclass may or may not behave as expected.
 
 ===== Delegating metaclass
 
-(TBD)
+If you only need to decorate an existing metaclass the `DelegatingMetaClass` simplifies that use case. The old metaclass implementation is still accessible via `super` making easy to apply pretransformations to the inputs, routing to other methods and postprocess the outputs.
 
-===== Magic package (Maksym Stavytskyi)
+[source,groovy]
+----
+class Foo { def bar() { "bar" } }
+
+class MyFooMetaClass extends DelegatingMetaClass {
+  MyFooMetaClass(MetaClass metaClass) { super(metaClass) }
+  MyFooMetaClass(Class theClass) { super(theClass) }
+
+  Object invokeMethod(Object object, String methodName, Object[] args) {
+     def result = super.invokeMethod(object,methodName.toLowerCase(), args)
+     result.toUpperCase();
+  }
+}
+
+
+def mc =  new MyFooMetaClass(Foo.metaClass)
+mc.initialize()
+
+Foo.metaClass = mc
+def f = new Foo()
+
+assert f.BAR() == "BAR" // the new metaclass routes .BAR() to .bar() and uppercases the result
+----
+
+===== Magic package
+
+It is possible to change the metaclass at startup time by giving the metaclass a specially crafted (magic) class name  and package name. In order to change the metaclass for `java.lang.Integer` it's enough to put a class `groovy.runtime.metaclass.java.lang.IntegerMetaClass` in the classpath. This is useful, for example,  when working with frameworks if you want to to metaclass changes before your code is executed by the framework. The general form of the magic package is `groovy.runtime.metaclass.[package].[class]MetaClass`. In the example below the `[package]` is `java.lang` and the `[class]` is `Integer`:
+
+
+[source,groovy]
+----
+// file: IntegerMetaClass.groovy
+package groovy.runtime.metaclass.java.lang;
+
+class IntegerMetaClass extends DelegatingMetaClass {
+  IntegerMetaClass(MetaClass metaClass) { super(metaClass) }
+  IntegerMetaClass(Class theClass) { super(theClass) }
+  Object invokeMethod(Object object, String name, Object[] args) {
+    if (name =~ /isBiggerThan/) {
+      def other = name.split(/isBiggerThan/)[1].toInteger()
+      object > other
+    } else {
+      return super.invokeMethod(object,name, args);
+    }
+  }
+}
+----
+
+By compiling the above file with `groovyc IntegerMetaClass.groovy` a `./groovy/runtime/metaclass/java/lang/IntegerMetaClass.class` will be generated. The example below will use this new metaclass:
+
+[source,groovy]
+----
+// File testInteger.groovy
+def i = 10
+
+assert i.isBiggerThan5()
+assert !i.isBiggerThan15()
+
+println i.isBiggerThan5()
+----
+
+By running that file with `groovy -cp . testInteger.groovy` the `IntegerMetaClass` will be in the classpath and therefore it will become the metaclass for `java.lang.Integer` intercepting the method calls to `isBiggerThan*()` methods.
 
-(TBD)
 
 ==== Per instance metaclass
 
-(TBD)
+You can change the metaclass of individual objects separately, so it's possible to have multiple object of the same class with different metaclasses.
+
+[source,groovy]
+----
+class Foo { def bar() { "bar" }}
+
+class FooMetaClass extends DelegatingMetaClass {
+  FooMetaClass(MetaClass metaClass) { super(metaClass) }
+  Object invokeMethod(Object object, String name, Object[] args) {
+      super.invokeMethod(object,name,args).toUpperCase()
+  }
+}
+
+def f1 = new Foo()
+def f2 = new Foo()
+f2.metaClass = new FooMetaClass(f2.metaClass)
 
+assert f1.bar() == "bar"
+assert f2.bar() == "BAR"
+assert f1.metaClass =~ /MetaClassImpl/
+assert f2.metaClass =~ /FooMetaClass/
+assert f1.class.toString() == "class Foo"
+assert f2.class.toString() == "class Foo"
+----
 
 [[metaprogramming_emc]]
 ==== ExpandoMetaClass