groovy-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sun...@apache.org
Subject groovy git commit: Add documentation for metaclasses(closes #698)
Date Thu, 10 May 2018 08:33:58 GMT
Repository: groovy
Updated Branches:
  refs/heads/GROOVY_2_6_X 7982b1abe -> 353a7840a


Add documentation for metaclasses(closes #698)

(cherry picked from commit 7757065)


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

Branch: refs/heads/GROOVY_2_6_X
Commit: 353a7840a4d5b39cc084620a373e838030c24734
Parents: 7982b1a
Author: Ruben Laguna <ruben.laguna@gmail.com>
Authored: Tue May 8 19:25:13 2018 +0800
Committer: sunlan <sunlan@apache.org>
Committed: Thu May 10 16:33:47 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/353a7840/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/353a7840/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


Mime
View raw message