/* * Copyright 2003-2016 JetBrains s.r.o. * * 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 jetbrains.mps.lang.pattern; import jetbrains.mps.smodel.SModelId; import jetbrains.mps.smodel.SNodePointer; import jetbrains.mps.smodel.SNodeUtil; import jetbrains.mps.smodel.SReference; import jetbrains.mps.smodel.adapter.BootstrapAdapterFactory; import org.hamcrest.Matchers; import org.jetbrains.mps.openapi.language.SConcept; import org.jetbrains.mps.openapi.language.SContainmentLink; import org.jetbrains.mps.openapi.language.SProperty; import org.jetbrains.mps.openapi.language.SReferenceLink; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeReference; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ErrorCollector; import java.util.List; /** * Tests for lang.pattern runtime code, namely {@link NodeMatcher} and {@link ChildMatcher}. * @author Artem Tikhomirov */ public class NodeMatcherTest { /*package*/ static SConcept ourConcept1 = BootstrapAdapterFactory.getConcept(1, 2, 3, "C1"); /*package*/ static SConcept ourConcept2 = BootstrapAdapterFactory.getConcept(1, 2, 7, "C2"); /*package*/ static SContainmentLink ourC1Child1 = BootstrapAdapterFactory.getContainmentLink(1, 2, 3, 4, "L1"); /*package*/ static SContainmentLink ourC1Child2 = BootstrapAdapterFactory.getContainmentLink(1, 2, 3, 5, "L2"); /*package*/ static SReferenceLink ourC1Ref = BootstrapAdapterFactory.getReferenceLink(1, 2, 3, 6, "R"); @Rule public ErrorCollector myErrors = new ErrorCollector(); @Test public void testPropertyValues() { final SNode patternNode = newNode(ourConcept1); patternNode.addChild(ourC1Child1, newNode(ourConcept1)); final SNode patternChild = newNode(ourConcept1); patternNode.addChild(ourC1Child1, patternChild); final SProperty p1 = SNodeUtil.property_INamedConcept_name; final SProperty p2 = SNodeUtil.property_BaseConcept_alias; final SNode actualNode = newNode(ourConcept1); actualNode.addChild(ourC1Child1, newNode(ourConcept1)); final SNode actualChild = newNode(ourConcept1); actualNode.addChild(ourC1Child1, actualChild); actualNode.setProperty(p1, "true"); actualNode.setProperty(p2, "yes"); actualChild.setProperty(p1, "One"); actualChild.setProperty(p2, "Two"); ValueContainer vc = new ValueContainer(); final NodeMatcher top = new NodeMatcher(vc); top.property(p1, "top1").property(p2, "top2").child(ourC1Child1).at(1).property(p1, "cp1").property(p2, "cp2"); final boolean matched = top.match(patternNode, actualNode); myErrors.checkThat("Shall match", matched, Matchers.equalTo(true)); myErrors.checkThat(vc.getProperty("top1"), Matchers.equalTo("true")); myErrors.checkThat(vc.getProperty("top2"), Matchers.equalTo("yes")); myErrors.checkThat(vc.getProperty("cp1"), Matchers.equalTo("One")); myErrors.checkThat(vc.getProperty("cp2"), Matchers.equalTo("Two")); } @Test public void testNodeValues() { final SNode patternNode = newNode(ourConcept1); patternNode.addChild(ourC1Child1, newNode(ourConcept1)); patternNode.addChild(ourC1Child2, newNode(ourConcept2)); final SNode patternChild = newNode(ourConcept1); patternNode.addChild(ourC1Child1, patternChild); patternChild.addChild(ourC1Child2, newNode(ourConcept2)); ValueContainer vc = new ValueContainer(); final NodeMatcher top = new NodeMatcher(vc); top.capture("top"). child(ourC1Child1).at(1).capture("c1_2"). // second child in the first role child(ourC1Child2).at(0).capture("c1_c1_1").done().done(). // we are at c1_2 done().done(). // pop c1_2, pop ourC1Child1 child(ourC1Child2).at(0).capture("c2_1"); final SNode actualNode = newNode(ourConcept1, "1"); // {level}_{aggregation link 1-index}_{1-index in role} actualNode.addChild(ourC1Child1, newNode(ourConcept1,"2_1_1")); final SNode actualChild1 = newNode(ourConcept1, "2_1_2"); actualNode.addChild(ourC1Child1, actualChild1); final SNode actualGrandChild = newNode(ourConcept2, "3_2_1"); actualChild1.addChild(ourC1Child2, actualGrandChild); final SNode actualChild2 = newNode(ourConcept2, "2_2_1"); actualNode.addChild(ourC1Child2, actualChild2); final boolean matched = top.match(patternNode, actualNode); myErrors.checkThat("Shall match", matched, Matchers.equalTo(true)); myErrors.checkThat(vc.getNode("top"), Matchers.equalTo(actualNode)); myErrors.checkThat(vc.getNode("c1_2"), Matchers.equalTo(actualChild1)); myErrors.checkThat(vc.getNode("c2_1"), Matchers.equalTo(actualChild2)); myErrors.checkThat(vc.getNode("c1_c1_1"), Matchers.equalTo(actualGrandChild)); } @Test public void testChildListValues() { final SNode patternNode = newNode(ourConcept1); ValueContainer vc = new ValueContainer(); final NodeMatcher top = new NodeMatcher(vc); top.child(ourC1Child1).capture("firstList").done().child(ourC1Child2).capture("secondList"); final SNode actualNode = newNode(ourConcept1); actualNode.addChild(ourC1Child1, newNode(ourConcept1)); actualNode.addChild(ourC1Child2, newNode(ourConcept2)); actualNode.addChild(ourC1Child1, newNode(ourConcept1)); actualNode.addChild(ourC1Child2, newNode(ourConcept2)); actualNode.addChild(ourC1Child1, newNode(ourConcept1)); final boolean matched = top.match(patternNode, actualNode); myErrors.checkThat("Shall match", matched, Matchers.equalTo(true)); final List<SNode> firstList = vc.getList("firstList"); final List<SNode> secondList = vc.getList("secondList"); myErrors.checkThat(firstList, Matchers.notNullValue()); myErrors.checkThat(secondList, Matchers.notNullValue()); if (firstList != null) { myErrors.checkThat(firstList.size(), Matchers.equalTo(3)); } if (secondList != null) { myErrors.checkThat(secondList.size(), Matchers.equalTo(2)); } } @Test public void testReferenceValues() { final SNode patternNode = newNode(ourConcept1); final SNode patternChild = newNode(ourConcept1); patternNode.addChild(ourC1Child1, patternChild); ValueContainer vc = new ValueContainer(); final NodeMatcher top = new NodeMatcher(vc); top.association(ourC1Ref, "r1").child(ourC1Child1).at(0).association(ourC1Ref, "r2"); final SModelReference targetModel = new jetbrains.mps.smodel.SModelReference(null, SModelId.generate(), "M"); final SNode targetNode = newNode(ourConcept2); final SNodeReference targetNodeRef = new SNodePointer(targetModel, targetNode.getNodeId()); final SNode actualNode = newNode(ourConcept1); final SNode actualChild = newNode(ourConcept1); actualNode.addChild(ourC1Child1, actualChild); // doesn't matter where the reference point, but can't use SNode as ImmatureReferences.getInstance == null deep in SReferenceBase actualNode.setReference(ourC1Ref, SReference.create(ourC1Ref, actualNode, targetModel, targetNode.getNodeId())); actualChild.setReference(ourC1Ref, SReference.create(ourC1Ref, actualChild, targetModel, targetNode.getNodeId())); final boolean matched = top.match(patternNode, actualNode); myErrors.checkThat("Shall match", matched, Matchers.equalTo(true)); myErrors.checkThat(vc.getRefTargetPointer("r1"), Matchers.equalTo(targetNodeRef)); myErrors.checkThat(vc.getRefTargetPointer("r2"), Matchers.equalTo(targetNodeRef)); } @Test public void testMatch() { // match fails for != children count final SNode patternNode = newNode(ourConcept1); patternNode.addChild(ourC1Child1, newNode(ourConcept1)); patternNode.addChild(ourC1Child2, newNode(ourConcept2)); final SNode actualNodeOneChild = newNode(ourConcept1); actualNodeOneChild.addChild(ourC1Child1, newNode(ourConcept1)); // the one that shall match final SNode actualNodeTwoChildrenRight = newNode(ourConcept1); actualNodeTwoChildrenRight.addChild(ourC1Child1, newNode(ourConcept1)); actualNodeTwoChildrenRight.addChild(ourC1Child2, newNode(ourConcept2)); // number of children is correct, but concept of the second is wrong final SNode actualNodeTwoChildrenWrong = newNode(ourConcept1); actualNodeTwoChildrenWrong.addChild(ourC1Child1, newNode(ourConcept1)); actualNodeTwoChildrenWrong.addChild(ourC1Child2, newNode(ourConcept1)); final SNode actualNodeThreeChildren = newNode(ourConcept1); actualNodeThreeChildren.addChild(ourC1Child1, newNode(ourConcept1)); actualNodeThreeChildren.addChild(ourC1Child2, newNode(ourConcept2)); actualNodeThreeChildren.addChild(ourC1Child2, newNode(ourConcept1)); final NodeMatcher defaultMatcher = new NodeMatcher(new ValueContainer()); myErrors.checkThat(defaultMatcher.match(patternNode, actualNodeOneChild), Matchers.equalTo(false)); myErrors.checkThat(defaultMatcher.match(patternNode, actualNodeTwoChildrenRight), Matchers.equalTo(true)); // match fails for != concept myErrors.checkThat(defaultMatcher.match(patternNode, actualNodeTwoChildrenWrong), Matchers.equalTo(false)); myErrors.checkThat(defaultMatcher.match(patternNode, actualNodeThreeChildren), Matchers.equalTo(false)); // match fails for != property final SProperty p = SNodeUtil.property_INamedConcept_name; patternNode.setProperty(p, "NAME"); // with property set in the pattern, node that used to match now fails myErrors.checkThat(defaultMatcher.match(patternNode, actualNodeTwoChildrenRight), Matchers.equalTo(false)); actualNodeTwoChildrenRight.setProperty(p, "NAME"); myErrors.checkThat(defaultMatcher.match(patternNode, actualNodeTwoChildrenRight), Matchers.equalTo(true)); } @Test public void testComposite() { final SNode patternNode = newNode(ourConcept1, "Root"); patternNode.addChild(ourC1Child1, newNode(ourConcept1, "ChildOne")); patternNode.addChild(ourC1Child2, newNode(ourConcept2)); final SProperty p = SNodeUtil.property_INamedConcept_name; ValueContainer vc = new ValueContainer(); final NodeMatcher top = new NodeMatcher(vc); top.capture("TopNodeVar").property(p, "PropertyVar"). child(ourC1Child1). at(0).capture("firstChild").property(p, "ChildName").done(). done(). child(ourC1Child2).capture("list"); final SNode actualNode = newNode(ourConcept1, "ActualTopNodeName"); actualNode.addChild(ourC1Child2, newNode(ourConcept2)); actualNode.addChild(ourC1Child2, newNode(ourConcept2)); actualNode.addChild(ourC1Child2, newNode(ourConcept2)); final SNode actualChild = newNode(ourConcept1, "ActualChildOne"); actualNode.addChild(ourC1Child1, actualChild); final boolean matched = top.match(patternNode, actualNode); myErrors.checkThat("Shall match", matched, Matchers.equalTo(true)); final List<SNode> listValue = vc.getList("list"); myErrors.checkThat(listValue, Matchers.notNullValue()); if (listValue != null) { myErrors.checkThat(listValue.size(), Matchers.equalTo(3)); } myErrors.checkThat(vc.getNode("firstChild"), Matchers.equalTo(actualChild)); myErrors.checkThat(vc.getNode("TopNodeVar"), Matchers.equalTo(actualNode)); myErrors.checkThat(vc.getProperty("PropertyVar"), Matchers.equalTo("ActualTopNodeName")); myErrors.checkThat(vc.getProperty("ChildName"), Matchers.equalTo("ActualChildOne")); } /* Turned off unless we decide whether it's right to check child concept/properties for pattern nodes that are captured as a whole. */ @Test public void testDisjunction() { // third child in pattern nodes is to overcome bypass in NodeMatcher#matchStructure() // which grabs a node marked with variable as is, and doesn't compare its attributes. // Since we grab first child as 'cld' and use mismatched property value to check match fails, // this bypass avoids property match and the test fails. final SProperty p = SNodeUtil.property_INamedConcept_name; final SNode pattern1Node = newNode(ourConcept1); final SNode pattern1Child = newNode(ourConcept1); pattern1Node.addChild(ourC1Child1, pattern1Child); pattern1Child.addChild(ourC1Child1, newNode(ourConcept1)); pattern1Child.setProperty(p, "First"); final SNode pattern2Node = newNode(ourConcept1); final SNode pattern2Child = newNode(ourConcept2); pattern2Node.addChild(ourC1Child2, pattern2Child); pattern2Child.addChild(ourC1Child2, newNode(ourConcept2)); pattern2Node.setProperty(p, "Second"); ValueContainer vc = new ValueContainer(); final NodeMatcher top = new NodeMatcher(vc); // extra child().done() after capture('cld') is to avoid aforementioned bypass. top.disjunct(pattern1Node, new NodeMatcher(vc).child(ourC1Child1).at(0).capture("cld").child(ourC1Child1).done().done().done()); top.disjunct(pattern2Node, new NodeMatcher(vc).child(ourC1Child2).at(0).capture("cld").child(ourC1Child2).done().done().done()); SNode sample1Node = newNode(ourConcept1); SNode sample1Child = newNode(ourConcept1); sample1Node.addChild(ourC1Child1, sample1Child); sample1Child.addChild(ourC1Child1, newNode(ourConcept1)); sample1Child.setProperty(p, "First"); SNode sample2Node = newNode(ourConcept1); SNode sample2Child = newNode(ourConcept2); sample2Node.addChild(ourC1Child2, sample2Child); sample2Child.addChild(ourC1Child2, newNode(ourConcept2)); sample2Node.setProperty(p, "Second"); SNode sample3Node = newNode(ourConcept2); sample3Node.setProperty(p, "Second"); // quite similar to patternNodes: with pattern1 it's different child concept, with pattern2 it's different role SNode sample4Node = newNode(ourConcept1); final SNode sample4Child = newNode(ourConcept2); sample4Node.addChild(ourC1Child1, sample4Child); sample3Node.addChild(ourC1Child1, newNode(ourConcept2)); // matching property values to ensure we check complete structure sample4Child.setProperty(p, "First"); sample4Node.setProperty(p, "Second"); // we pretend OrPattern is attached to the topmost node, fakePatternNode would be instance // of quotation node OrPattern attribute was attached to. final SNode fakePatternNode = newNode(ourConcept2); myErrors.checkThat(top.match(fakePatternNode, sample1Node), Matchers.equalTo(true)); myErrors.checkThat(vc.getNode("cld"), Matchers.equalTo(sample1Child)); sample1Child.setProperty(p, "mismatch"); myErrors.checkThat(top.match(fakePatternNode, sample1Node), Matchers.equalTo(false)); // myErrors.checkThat(top.match(fakePatternNode, sample2Node), Matchers.equalTo(true)); myErrors.checkThat(vc.getNode("cld"), Matchers.equalTo(sample2Child)); sample2Node.setProperty(p, "mismatch"); myErrors.checkThat(top.match(fakePatternNode, sample2Node), Matchers.equalTo(false)); // vc.reset(new ValueContainer()); myErrors.checkThat(top.match(fakePatternNode, sample3Node), Matchers.equalTo(false)); myErrors.checkThat(vc.getNode("cld"), Matchers.nullValue()); // vc.reset(new ValueContainer()); myErrors.checkThat(top.match(fakePatternNode, sample4Node), Matchers.equalTo(false)); myErrors.checkThat(vc.getNode("cld"), Matchers.nullValue()); } private static SNode newNode(SConcept c) { return new jetbrains.mps.smodel.SNode(c); } private static SNode newNode(SConcept c, String name) { final SNode rv = newNode(c); rv.setProperty(SNodeUtil.property_INamedConcept_name, name); return rv; } }