| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package groovy.lang |
| |
| import groovy.test.GroovyTestCase |
| |
| import java.util.concurrent.locks.ReentrantLock |
| import org.codehaus.groovy.reflection.ClassInfo |
| |
| class MixinTest extends GroovyTestCase { |
| |
| @groovy.transform.CompileStatic |
| protected void setUp() { |
| ClassInfo.clearModifiedExpandos() |
| } |
| |
| protected void tearDown() { |
| ArrayList.metaClass = null |
| List.metaClass = null |
| ObjToTest.metaClass = null |
| } |
| |
| void testOneClass() { |
| List.mixin ListExt |
| ArrayList.mixin ArrayListExt |
| assertEquals 1, [0, 1].swap()[0] |
| assertEquals 0, [0, 1].swap()[1] |
| assertEquals 0, [0, 1].swap().unswap()[0] |
| assertEquals 1, [0, 1].swap().unswap()[1] |
| } |
| |
| void testWithList() { |
| ArrayList.mixin ArrayListExt, ListExt |
| assertEquals 1, [0, 1].swap()[0] |
| assertEquals 0, [0, 1].swap()[1] |
| assertEquals 0, [0, 1].swap().unswap()[0] |
| assertEquals 1, [0, 1].swap().unswap()[1] |
| } |
| |
| void testCombined() { |
| ArrayList.mixin Combined |
| assertEquals 1, [0, 1].swap()[0] |
| assertEquals 0, [0, 1].swap()[1] |
| assertEquals 0, [0, 1].swap().unswap()[0] |
| assertEquals 1, [0, 1].swap().unswap()[1] |
| } |
| |
| void testWithEmc() { |
| ArrayList.metaClass.unswap = { |
| [delegate[1], delegate[0]] |
| } |
| ArrayList.mixin ArrayListExt |
| assertEquals 1, [0, 1].swap()[0] |
| assertEquals 0, [0, 1].swap()[1] |
| assertEquals 0, [0, 1].swap().unswap()[0] |
| assertEquals 1, [0, 1].swap().unswap()[1] |
| } |
| |
| void testGroovyObject() { |
| def obj = new ObjToTest() |
| assertEquals "original", obj.value |
| obj.metaClass.mixin ObjToTestCategory |
| assertEquals "changed by category", obj.value |
| assertEquals "original", new ObjToTest().value |
| } |
| |
| void testGroovyObjectWithEmc() { |
| ObjToTest.metaClass.getValue = {-> |
| "emc changed" |
| } |
| ObjToTest obj = new ObjToTest() |
| assertEquals "emc changed", obj.getValue() |
| obj.metaClass.mixin ObjToTestCategory |
| assertEquals "changed by category", obj.value |
| assertEquals "emc changed", new ObjToTest().value |
| } |
| |
| void testFlatten() { |
| Object.metaClass.mixin DeepFlattenToCategory |
| assertEquals([8, 9, 3, 2, 1, 4], [[8, 9] as Object[], [3, 2, [2: 1, 3: 4]], [2, 3]].flattenTo() as List) |
| |
| def x = [-2, -2, -3, -3] |
| x.metaClass.mixin NoFlattenArrayListCategory |
| assertEquals([x, 8, 9, 3, 2, 1, 4], [x, [8, 9] as Object[], [3, 2, [2: 1, 3: 4]], [2, 3]].flattenTo() as List) |
| |
| x.metaClass = null |
| x.metaClass.flattenTo = {Set set -> set << delegate } |
| assertEquals([x, 8, 9, 3, 2, 1, 4], [x, [8, 9] as Object[], [3, 2, [2: 1, 3: 4]], [2, 3]].flattenTo() as List) |
| |
| x.metaClass = null |
| Object.metaClass.flattenTo(ArrayList) {-> |
| LinkedHashSet set = new LinkedHashSet() |
| delegate.flattenTo(set) |
| return set |
| } |
| Object.metaClass.flattenTo(ArrayList) {Set set -> |
| set << "oops" |
| return Collection.metaClass.invokeMethod(delegate, "flattenTo", set) |
| } |
| ArrayList.metaClass = null |
| assertEquals(["oops", -2, -3, 8, 9, 3, 2, 1, 4], [x, [8, 9] as Object[], [3, 2, [2: 1, 3: 4]], [2, 3]].flattenTo() as List) |
| |
| ArrayList.metaClass = null |
| Object.metaClass { |
| flattenTo(ArrayList) {-> |
| LinkedHashSet set = new LinkedHashSet() |
| delegate.flattenTo(set) |
| return set |
| } |
| |
| flattenTo(ArrayList) {Set set -> |
| set << "oopsssss" |
| return Collection.metaClass.invokeMethod(delegate, "flattenTo", set) |
| } |
| |
| asList {-> |
| delegate as List |
| } |
| } |
| assertEquals(["oopsssss", -2, -3, 8, 9, 3, 2, 1, 4], [x, [8, 9] as Object[], [3, 2, [2: 1, 3: 4]], [2, 3]].flattenTo().asList()) |
| |
| ArrayList.metaClass = null |
| Object.metaClass { |
| define(ArrayList) { |
| flattenTo {-> |
| LinkedHashSet set = new LinkedHashSet() |
| delegate.flattenTo(set) |
| return set |
| } |
| |
| flattenTo {Set set -> |
| set << "ssoops" |
| return Collection.metaClass.invokeMethod(delegate, "flattenTo", set) |
| } |
| } |
| |
| asList {-> |
| delegate as List |
| } |
| } |
| assertEquals(["ssoops", -2, -3, 8, 9, 3, 2, 1, 4], [x, [8, 9] as Object[], [3, 2, [2: 1, 3: 4]], [2, 3]].flattenTo().asList()) |
| |
| Object.metaClass = null |
| } |
| |
| void testMixingLockable() { |
| Object.metaClass.mixin ReentrantLock |
| def name = "abcdef" |
| name.lock() |
| try { |
| assertTrue name.isLocked() |
| } |
| finally { |
| name.unlock() |
| } |
| Object.metaClass = null |
| } |
| |
| void testConcurrentQueue() { |
| ReentrantLock.metaClass { |
| withLock {Closure operation -> |
| lock() |
| try { |
| operation() |
| } |
| finally { |
| unlock() |
| } |
| } |
| } |
| |
| def queue = new ConcurrentQueue() |
| queue.put 1 |
| queue.put 2 |
| queue.put 3 |
| assertEquals 1, queue.get() |
| assertEquals 2, queue.get() |
| assertEquals 3, queue.get() |
| |
| ReentrantLock.metaClass = null |
| } |
| |
| void testDynamicConcurrentQueue() { |
| ReentrantLock.metaClass { |
| withLock {Closure operation -> |
| lock() |
| try { |
| operation() |
| } |
| finally { |
| unlock() |
| } |
| } |
| } |
| |
| def queue = new Object() |
| queue.metaClass { |
| mixin LinkedList, ReentrantLock |
| |
| get {-> |
| withLock { |
| removeFirst() |
| } |
| } |
| |
| put {obj -> |
| withLock { |
| addLast(obj) |
| } |
| } |
| } |
| |
| queue.put 1 |
| queue.put 2 |
| queue.put 3 |
| assertEquals 1, queue.get() |
| assertEquals 2, queue.get() |
| assertEquals 3, queue.get() |
| |
| queue.metaClass { |
| iterator {-> |
| mixedIn[LinkedList].iterator() |
| } |
| |
| duplicateEachElement { |
| withLock { |
| LinkedList newList = new LinkedList() |
| mixedIn[LinkedList].each { |
| newList << it |
| newList << it |
| } |
| mixedIn[LinkedList] = newList |
| } |
| } |
| } |
| |
| queue.put 1 |
| queue.put 2 |
| queue.put 3 |
| queue.duplicateEachElement() |
| assertEquals 1, queue.get() |
| assertEquals 1, queue.get() |
| assertEquals 2, queue.get() |
| assertEquals 2, queue.get() |
| assertEquals 3, queue.get() |
| assertEquals 3, queue.get() |
| |
| ReentrantLock.metaClass = null |
| } |
| |
| void testNoDupCollection() { |
| def list = new Object() |
| list.metaClass { |
| mixin NoDuplicateCollection, LinkedList |
| |
| find = {Closure check -> |
| mixedIn[LinkedList].find(check) |
| } |
| } |
| |
| list.put 1 |
| list.put 1 |
| list.put 2 |
| list.put 2 |
| list.put 3 |
| list.put 3 |
| |
| assertEquals(3, list.size()) |
| assertEquals 1, list[0] |
| assertEquals 2, list[1] |
| assertEquals 3, list[2] |
| } |
| |
| void testList() { |
| def u = [] |
| u.metaClass { |
| mixin HashSet |
| |
| leftShift {obj -> |
| mixedIn[List] << obj |
| mixedIn[Set] << obj |
| } |
| } |
| |
| u << 1 |
| u << 2 |
| u << 1 |
| u << 2 |
| u << 1 |
| u << 2 |
| |
| assertEquals 6, u.size() |
| assertEquals 6, ((List) u).size() |
| assertEquals 6, ((Collection) u).size() |
| assertEquals 2, u.mixedIn[Set].size() |
| assertEquals 2, ((Set) u).size() |
| } |
| |
| void testWPM() { |
| |
| new WPM_B().foo() |
| |
| WPM_C.metaClass { mixin WPM_B } |
| |
| def c = new WPM_C() |
| c.foo() |
| c.foobar() |
| } |
| |
| void testStackOverflow() { |
| Overflow_B.metaClass { |
| mixin Overflow_A |
| |
| foo = {-> |
| println 'New foo ' + receive('') |
| } |
| } |
| |
| final Overflow_A a = new Overflow_A() |
| a.foo() |
| |
| final Overflow_B b = new Overflow_B() |
| b.foo() |
| } |
| |
| void testStackOverflowErrorWithMixinsAndClosure() { |
| assertScript """ |
| class Groovy3474A { |
| int counter = 1 |
| protected final foo() { |
| bar { counter } |
| } |
| private final String bar(Closure code) { |
| return "Bar " + code() |
| } |
| } |
| |
| class Groovy3474B extends Groovy3474A {} |
| |
| class Groovy3474C {} |
| Groovy3474C.metaClass { mixin Groovy3474B } |
| def c = new Groovy3474C() |
| assert c.foo() == 'Bar 1' |
| println "testStackOverflowErrorWithMixinsAndClosure() Done" |
| """ |
| } |
| |
| void testMixinWithVarargs() { |
| assertScript """ |
| class Dsl { |
| static novarargs(java.util.List s) { "novarargs" + s.size() } |
| static plainVarargs(Object... s) { "plainVarargs" + s.size() } |
| static mixedVarargs(int foo, String... s) { "mixedVarargs" + s.size() } |
| } |
| this.metaClass.mixin(Dsl) |
| assert novarargs(["a", "b"]) == "novarargs2" |
| assert plainVarargs("a", "b", 35) == "plainVarargs3" |
| assert mixedVarargs(3, "a", "b", "c", "d") == "mixedVarargs4" |
| """ |
| } |
| } |
| |
| class ArrayListExt { |
| static def swap(ArrayList self) { |
| [self[1], self[0]] |
| } |
| } |
| |
| class ListExt { |
| static def unswap(List self) { |
| [self[1], self[0]] |
| } |
| } |
| |
| class Combined { |
| static def swap(ArrayList self) { |
| [self[1], self[0]] |
| } |
| |
| static def unswap(List self) { |
| [self[1], self[0]] |
| } |
| } |
| |
| class ObjToTest { |
| def getValue() { |
| "original" |
| } |
| } |
| |
| class ObjToTestCategory { |
| static getValue(ObjToTest self) { |
| "changed by category" |
| } |
| } |
| |
| class DeepFlattenToCategory { |
| static Set flattenTo(element) { |
| LinkedHashSet set = new LinkedHashSet() |
| element.flattenTo(set) |
| return set |
| } |
| |
| // Object - put to result set |
| static void flattenTo(element, Set addTo) { |
| addTo << element |
| } |
| |
| // Collection - flatten each element |
| static void flattenTo(Collection elements, Set addTo) { |
| elements.each {element -> |
| element.flattenTo(addTo) |
| } |
| } |
| |
| // Map - flatten each value |
| static void flattenTo(Map elements, Set addTo) { |
| elements.values().flattenTo(addTo) |
| } |
| |
| // Array - flatten each element |
| static void flattenTo(Object[] elements, Set addTo) { |
| elements.each {element -> |
| element.flattenTo(addTo) |
| } |
| } |
| } |
| |
| class NoFlattenArrayListCategory { |
| // Object - put to result set |
| |
| static void flattenTo(ArrayList element, Set addTo) { |
| addTo << element |
| } |
| } |
| |
| class ConcurrentQueue { |
| static { |
| ConcurrentQueue.metaClass.mixin LinkedList, ReentrantLock |
| } |
| |
| def get() { |
| withLock { |
| removeFirst() |
| } |
| } |
| |
| void put(def obj) { |
| withLock { |
| addLast(obj) |
| } |
| } |
| } |
| |
| class NoDuplicateCollection { |
| void put(def obj) { |
| def clone = find { |
| it == obj |
| } |
| |
| if (!clone) |
| add obj |
| } |
| } |
| |
| class WPM_A { |
| |
| final foo() { |
| bar() |
| } |
| |
| private final String bar() { return "Bar" } |
| } |
| |
| class WPM_B extends WPM_A { |
| def foobar() { |
| super.foo() |
| } |
| } |
| |
| class WPM_C {} |
| |
| class Overflow_A { |
| public void foo() { |
| println 'Original foo ' + receive('') |
| } |
| |
| protected Object receive() { |
| return "Message" |
| } |
| |
| protected Object receive(Object param) { |
| receive() + param |
| } |
| } |
| |
| class Overflow_B {} |
| |