/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* Licensed 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 org.drools.compiler.integrationtests;
import org.drools.compiler.Cheese;
import org.drools.compiler.Cheesery;
import org.drools.core.base.ClassObjectType;
import org.drools.core.impl.InternalKnowledgeBase;
import org.drools.core.reteoo.EntryPointNode;
import org.drools.core.reteoo.LeftInputAdapterNode;
import org.drools.core.reteoo.LeftTupleSink;
import org.drools.core.reteoo.ObjectTypeNode;
import org.drools.core.reteoo.ReteDumper;
import org.junit.Test;
import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.Results;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.rule.FactHandle;
import org.kie.internal.builder.conf.PropertySpecificOption;
import org.kie.internal.utils.KieHelper;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class FromTest {
public static class ListsContainer {
public List<String> getList1() {
return Arrays.asList( "a", "bb", "ccc" );
}
public List<String> getList2() {
return Arrays.asList( "1", "22", "333" );
}
public Number getSingleValue() {
return 1;
}
}
@Test
public void testFromSharing() {
// Keeping original test as non-property reactive by default, just allowed.
String drl =
"import " + ListsContainer.class.getCanonicalName() + "\n" +
"global java.util.List output1;\n" +
"global java.util.List output2;\n" +
"rule R1 when\n" +
" ListsContainer( $list : list1 )\n" +
" $s : String( length == 2 ) from $list\n" +
"then\n" +
" output1.add($s);\n" +
"end\n" +
"rule R2 when\n" +
" ListsContainer( $list : list2 )\n" +
" $s : String( length == 2 ) from $list\n" +
"then\n" +
" output2.add($s);\n" +
"end\n" +
"rule R3 when\n" +
" ListsContainer( $list : list2 )\n" +
" $s : String( length == 2 ) from $list\n" +
"then\n" +
" output2.add($s);\n" +
"end\n";
KieBase kbase = new KieHelper(PropertySpecificOption.ALLOWED).addContent( drl, ResourceType.DRL ).build();
KieSession ksession = kbase.newKieSession();
ReteDumper.dumpRete(kbase);
List<String> output1 = new ArrayList<String>();
ksession.setGlobal( "output1", output1 );
List<String> output2 = new ArrayList<String>();
ksession.setGlobal( "output2", output2 );
FactHandle fh = ksession.insert( new ListsContainer() );
ksession.fireAllRules();
assertEquals("bb", output1.get( 0 ));
assertEquals("22", output2.get( 0 ));
assertEquals("22", output2.get( 1 ));
EntryPointNode epn = ( (InternalKnowledgeBase)kbase ).getRete().getEntryPointNodes().values().iterator().next();
ObjectTypeNode otn = epn.getObjectTypeNodes().get( new ClassObjectType( ListsContainer.class ) );
// There is only 1 LIA
assertEquals( 1, otn.getObjectSinkPropagator().size() );
LeftInputAdapterNode lian = (LeftInputAdapterNode)otn.getObjectSinkPropagator().getSinks()[0];
// There are only 2 FromNodes since R2 and R3 are sharing the second From
LeftTupleSink[] sinks = lian.getSinkPropagator().getSinks();
assertEquals( 2, sinks.length );
// The first from has R1 has sink
assertEquals( 1, sinks[0].getSinkPropagator().size() );
// The second from has both R2 and R3 as sinks
assertEquals( 2, sinks[1].getSinkPropagator().size() );
}
@Test
public void testFromSharingWithPropertyReactive() {
// As above but with property reactive as default
String drl =
"import " + ListsContainer.class.getCanonicalName() + "\n" +
"global java.util.List output1;\n" +
"global java.util.List output2;\n" +
"rule R1 when\n" +
" ListsContainer( $list : list1 )\n" +
" $s : String( length == 2 ) from $list\n" +
"then\n" +
" output1.add($s);\n" +
"end\n" +
"rule R2 when\n" +
" ListsContainer( $list : list2 )\n" +
" $s : String( length == 2 ) from $list\n" +
"then\n" +
" output2.add($s);\n" +
"end\n" +
"rule R3 when\n" +
" ListsContainer( $list : list2 )\n" +
" $s : String( length == 2 ) from $list\n" +
"then\n" +
" output2.add($s);\n" +
"end\n";
// property reactive as default:
KieBase kbase = new KieHelper().addContent( drl, ResourceType.DRL ).build();
KieSession ksession = kbase.newKieSession();
ReteDumper.dumpRete(kbase);
List<String> output1 = new ArrayList<String>();
ksession.setGlobal( "output1", output1 );
List<String> output2 = new ArrayList<String>();
ksession.setGlobal( "output2", output2 );
FactHandle fh = ksession.insert( new ListsContainer() );
ksession.fireAllRules();
assertEquals("bb", output1.get( 0 ));
assertEquals("22", output2.get( 0 ));
assertEquals("22", output2.get( 1 ));
EntryPointNode epn = ( (InternalKnowledgeBase)kbase ).getRete().getEntryPointNodes().values().iterator().next();
ObjectTypeNode otn = epn.getObjectTypeNodes().get( new ClassObjectType( ListsContainer.class ) );
// There are 2 LIAs, one for the list1 and the other for the list2
assertEquals( 2, otn.getObjectSinkPropagator().size() );
LeftInputAdapterNode lia0 = (LeftInputAdapterNode)otn.getObjectSinkPropagator().getSinks()[0];
// There are only 2 FromNodes since R2 and R3 are sharing the second From
// The first FROM node has R1 has sink
LeftTupleSink[] sinks0 = lia0.getSinkPropagator().getSinks();
assertEquals( 1, sinks0.length );
assertEquals( 1, sinks0[0].getSinkPropagator().size() );
// The second FROM node has both R2 and R3 as sinks
LeftInputAdapterNode lia1 = (LeftInputAdapterNode)otn.getObjectSinkPropagator().getSinks()[1];
LeftTupleSink[] sinks1 = lia1.getSinkPropagator().getSinks();
assertEquals( 1, sinks1.length );
assertEquals( 2, sinks1[0].getSinkPropagator().size() );
}
@Test
public void testFromSharingWithAccumulate() {
String drl =
"package org.drools.compiler\n" +
"\n" +
"import java.util.List;\n" +
"import java.util.ArrayList;\n" +
"\n" +
"global java.util.List output1;\n" +
"global java.util.List output2;\n" +
"\n" +
"rule R1\n" +
" when\n" +
" $cheesery : Cheesery()\n" +
" $list : List( ) from accumulate( $cheese : Cheese( ) from $cheesery.getCheeses(),\n" +
" init( List l = new ArrayList(); ),\n" +
" action( l.add( $cheese ); )\n" +
" result( l ) )\n" +
" then\n" +
" output1.add( $list );\n" +
"end\n" +
"rule R2\n" +
" when\n" +
" $cheesery : Cheesery()\n" +
" $list : List( ) from accumulate( $cheese : Cheese( ) from $cheesery.getCheeses(),\n" +
" init( List l = new ArrayList(); ),\n" +
" action( l.add( $cheese ); )\n" +
" result( l ) )\n" +
" then\n" +
" output2.add( $list );\n" +
"end\n";
KieBase kbase = new KieHelper().addContent( drl, ResourceType.DRL ).build();
KieSession ksession = kbase.newKieSession();
List<?> output1 = new ArrayList<Object>();
ksession.setGlobal( "output1", output1 );
List<?> output2 = new ArrayList<Object>();
ksession.setGlobal( "output2", output2 );
Cheesery cheesery = new Cheesery();
cheesery.addCheese( new Cheese( "stilton", 8 ) );
cheesery.addCheese( new Cheese( "provolone", 8 ) );
FactHandle cheeseryHandle = ksession.insert( cheesery );
ksession.fireAllRules();
assertEquals( 1, output1.size() );
assertEquals( 2, ( (List) output1.get( 0 ) ).size() );
assertEquals( 1, output2.size() );
assertEquals( 2, ( (List) output2.get( 0 ) ).size() );
output1.clear();
output2.clear();
ksession.update( cheeseryHandle, cheesery );
ksession.fireAllRules();
assertEquals( 1, output1.size() );
assertEquals( 2, ( (List) output1.get( 0 ) ).size() );
assertEquals( 1, output2.size() );
assertEquals( 2, ( (List) output2.get( 0 ) ).size() );
}
@Test
public void testFromWithSingleValue() {
// DROOLS-1243
String drl =
"import " + ListsContainer.class.getCanonicalName() + "\n" +
"global java.util.List out;\n" +
"rule R1 when\n" +
" $list : ListsContainer( )\n" +
" $s : Integer() from $list.singleValue\n" +
"then\n" +
" out.add($s);\n" +
"end\n";
KieBase kbase = new KieHelper().addContent( drl, ResourceType.DRL ).build();
KieSession ksession = kbase.newKieSession();
List<Integer> out = new ArrayList<Integer>();
ksession.setGlobal( "out", out );
ksession.insert( new ListsContainer() );
ksession.fireAllRules();
assertEquals( 1, out.size() );
assertEquals( 1, (int)out.get(0) );
}
@Test
public void testFromWithSingleValueAndIncompatibleType() {
// DROOLS-1243
String drl =
"import " + ListsContainer.class.getCanonicalName() + "\n" +
"global java.util.List out;\n" +
"rule R1 when\n" +
" $list : ListsContainer( )\n" +
" $s : String() from $list.singleValue\n" +
"then\n" +
" out.add($s);\n" +
"end\n";
KieServices ks = KieServices.Factory.get();
KieFileSystem kfs = ks.newKieFileSystem().write( "src/main/resources/r1.drl", drl );
Results results = ks.newKieBuilder( kfs ).buildAll().getResults();
assertFalse( results.getMessages().isEmpty() );
}
public static class Container2 {
private Number wrapped;
public Container2(Number wrapped) {
this.wrapped = wrapped;
}
public Number getSingleValue() {
return this.wrapped;
}
}
@Test
public void testFromWithInterfaceAndAbstractClass() {
String drl =
"import " + Container2.class.getCanonicalName() + "\n" +
"import " + Comparable.class.getCanonicalName() + "\n" +
"global java.util.List out;\n" +
"rule R1 when\n" +
" $c2 : Container2( )\n" +
" $s : Comparable() from $c2.singleValue\n" +
"then\n" +
" out.add($s);\n" +
"end\n";
KieBase kbase = new KieHelper().addContent( drl, ResourceType.DRL ).build();
KieSession ksession = kbase.newKieSession();
List<Integer> out = new ArrayList<Integer>();
ksession.setGlobal( "out", out );
ksession.insert( new Container2( new Integer(1) ) );
ksession.fireAllRules();
assertEquals( 1, out.size() );
assertEquals( 1, (int)out.get(0) );
out.clear();
ksession.insert( new Container2( new AtomicInteger(1) ) );
ksession.fireAllRules();
assertEquals( 0, out.size() );
}
public static class Container2b {
private AtomicInteger wrapped;
public Container2b(AtomicInteger wrapped) {
this.wrapped = wrapped;
}
public AtomicInteger getSingleValue() {
return this.wrapped;
}
}
public static interface CustomIntegerMarker {}
public static class CustomInteger extends AtomicInteger implements CustomIntegerMarker {
public CustomInteger(int initialValue) {
super(initialValue);
}
}
@Test
public void testFromWithInterfaceAndConcreteClass() {
String drl =
"import " + Container2b.class.getCanonicalName() + "\n" +
"import " + CustomIntegerMarker.class.getCanonicalName() + "\n" +
"global java.util.List out;\n" +
"rule R1 when\n" +
" $c2 : Container2b( )\n" +
" $s : CustomIntegerMarker() from $c2.singleValue\n" +
"then\n" +
" out.add($s);\n" +
"end\n";
KieBase kbase = new KieHelper().addContent( drl, ResourceType.DRL ).build();
KieSession ksession = kbase.newKieSession();
List<AtomicInteger> out = new ArrayList<>();
ksession.setGlobal( "out", out );
ksession.insert( new Container2b( new CustomInteger(1) ) );
ksession.fireAllRules();
assertEquals( 1, out.size() );
assertEquals( 1, ((CustomInteger)out.get(0)).get() );
out.clear();
ksession.insert( new Container2b( new AtomicInteger(1) ) );
ksession.fireAllRules();
assertEquals( 0, out.size() );
}
public static class Container3 {
private Integer wrapped;
public Container3(Integer wrapped) {
this.wrapped = wrapped;
}
public Integer getSingleValue() {
return this.wrapped;
}
}
@Test
public void testFromWithInterfaceAndFinalClass() {
String drl =
"import " + Container3.class.getCanonicalName() + "\n" +
"import " + CustomIntegerMarker.class.getCanonicalName() + "\n" +
"global java.util.List out;\n" +
"rule R1 when\n" +
" $c3 : Container3( )\n" +
" $s : CustomIntegerMarker() from $c3.singleValue\n" +
"then\n" +
" out.add($s);\n" +
"end\n";
KieServices ks = KieServices.Factory.get();
KieFileSystem kfs = ks.newKieFileSystem().write( "src/main/resources/r1.drl", drl );
Results results = ks.newKieBuilder( kfs ).buildAll().getResults();
// Integer is final class, so there cannot be ever the case of pattern matching in the `from` on a non-extended interface to ever match.
assertFalse( results.getMessages().isEmpty() );
}
}