Java tutorial
/* * This file is part of the PSL software. * Copyright 2011-2015 University of Maryland * Copyright 2013-2018 The Regents of the University of California * * 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.linqs.psl.parser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import org.linqs.psl.PSLTest; import org.linqs.psl.database.DataStore; import org.linqs.psl.database.rdbms.RDBMSDataStore; import org.linqs.psl.database.rdbms.driver.H2DatabaseDriver; import org.linqs.psl.database.rdbms.driver.H2DatabaseDriver.Type; import org.linqs.psl.model.atom.QueryAtom; import org.linqs.psl.model.predicate.StandardPredicate; import org.linqs.psl.model.rule.Rule; import org.linqs.psl.model.rule.arithmetic.WeightedArithmeticRule; import org.linqs.psl.model.rule.arithmetic.expression.SummationAtom; import org.linqs.psl.model.rule.arithmetic.expression.SummationAtomOrAtom; import org.linqs.psl.model.term.ConstantType; import org.apache.commons.lang3.StringUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class ModelLoaderTest { private DataStore dataStore; private StandardPredicate singlePredicate; private StandardPredicate doublePredicate; @Before public void setup() { dataStore = new RDBMSDataStore(new H2DatabaseDriver(Type.Memory, this.getClass().getName(), true)); singlePredicate = StandardPredicate.get("Single", ConstantType.UniqueStringID); dataStore.registerPredicate(singlePredicate); doublePredicate = StandardPredicate.get("Double", ConstantType.UniqueStringID, ConstantType.UniqueStringID); dataStore.registerPredicate(doublePredicate); } @Test public void testBase() { String input = "1: Single(A) & Double(A, B) >> Single(B) ^2\n" + "5: Single(B) & Double(B, A) >> Single(A) ^2\n"; String[] expected = new String[] { "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "5.0: ( SINGLE(B) & DOUBLE(B, A) ) >> SINGLE(A) ^2" }; PSLTest.assertModel(dataStore, input, expected); } @Test // Shortest rule we can think of. public void testShortRule() { String input = "~Single(A) ."; String[] expected = new String[] { "~( SINGLE(A) ) ." }; PSLTest.assertModel(dataStore, input, expected); } @Test // Very long rule. public void testLongRule() { List<String> parts = new ArrayList<String>(); for (char i = 'A'; i < 'Z'; i++) { parts.add(String.format("Single(%c) & Double(%c, Z)", i, i)); } String input = String.format("1: %s >> Single(Z) ^2", StringUtils.join(parts, " & ")); String expected = String.format("1.0: ( %s ) >> SINGLE(Z) ^2", StringUtils.join(parts, " & ").toUpperCase()); PSLTest.assertModel(dataStore, input, new String[] { expected }); } @Test // General check for comment support. public void testComments() { String input = "# This is a comment!\n" + "#This is a comment!\n" + "## This is another comment (but actually the same form).\n" + " # This is a comment!\n" + "\n" + "//This is a comment!\n" + "// This is a comment!\n" + "//// This is another comment (but actually the same form).\n" + " // This is a comment!\n" + "\n" + "/* Block time! */\n" + "/* Block time!\n" + " Sill in a comment\n" + "*/\n" + "/** Block time (javadoc style)!\n" + " * Sill in a comment\n" + " */\n" + "\n" + "1: Single(A) & Double(A, B) >> Single(B) ^2\n" + "// Another comment.\n" + "1: Single(C) & Double(C, D) >> Single(D) ^2 // Inline comment!\n" + "1: Single(E) & Double(E, F) >> Single(F) ^2 # Inline comment!\n" + "1: Single(G) & Double(G, H) >> Single(H) ^2 /* Inline comment! */\n" + "1: Single(I) & Double(I, J) >> Single(J) // ^2 // Changing a rule.\n" + "1: Single(K) & Double(K, L) /* & Single(C) */ >> Single(L) ^2 // Inside of other syntax.\n" + ""; String[] expected = new String[] { "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE(C) & DOUBLE(C, D) ) >> SINGLE(D) ^2", "1.0: ( SINGLE(E) & DOUBLE(E, F) ) >> SINGLE(F) ^2", "1.0: ( SINGLE(G) & DOUBLE(G, H) ) >> SINGLE(H) ^2", "1.0: ( SINGLE(I) & DOUBLE(I, J) ) >> SINGLE(J)", "1.0: ( SINGLE(K) & DOUBLE(K, L) ) >> SINGLE(L) ^2" }; PSLTest.assertModel(dataStore, input, expected); } @Test public void testConstants() { String input = "1: Single(A) & Double(A, \"bar\") & Single(\"bar\") >> Double(A, \"bar\") ^2\n" + "1: Single(B) & Double(B, 'bar') & Single('bar') >> Double(B, 'bar') ^2\n" + "1: Single(C) & Double(C, 'BAR') & Single('BAR') >> Double(C, 'BAR') ^2\n" + "1: Single(D) & Double(D, '1BAR') & Single('1BAR') >> Double(D, '1BAR') ^2\n" + // Note that all constants get quotes, but they will get converted into their native types later. "1: Single(E) & Double(E, '1') & Single('1') >> Double(E, '1') ^2\n" + "1: Single(F) & Double(F, '999') & Single('999') >> Double(F, '999') ^2\n" + "1: Single(G) & Double(G, \"999\") & Single(\"999\") >> Double(G, \"999\") ^2\n" + // Spaces "1: Single(A) & Double(A, ' Z') & Single(' Z') >> Double(A, ' Z') ^2\n" + "1: Single(A) & Double(A, 'Z ') & Single('Z ') >> Double(A, 'Z ') ^2\n" + "1: Single(A) & Double(A, ' Z') & Single(' Z') >> Double(A, ' Z') ^2\n" + "1: Single(A) & Double(A, 'Z ') & Single('Z ') >> Double(A, 'Z ') ^2\n" + "1: Single(A) & Double(A, ' Z ') & Single(' Z ') >> Double(A, ' Z ') ^2\n" + "1: Single(A) & Double(A, ' Z ') & Single(' Z ') >> Double(A, ' Z ') ^2\n" + "1: Single(A) & Double(A, ' ') & Single(' ') >> Double(A, ' ') ^2\n" + "1: Single(A) & Double(A, ' ') & Single(' ') >> Double(A, ' ') ^2\n" + "1: Single(A) & Double(A, 'A B') & Single('A B') >> Double(A, 'A B') ^2\n" + "1: Single(A) & Double(A, 'A B') & Single('A B') >> Double(A, 'A B') ^2\n" + "1: Single(A) & Double(A, ' A B ') & Single(' A B ') >> Double(A, ' A B ') ^2\n" + // Underscores "1: Single(A) & Double(A, '_Z') & Single('_Z') >> Double(A, '_Z') ^2\n" + "1: Single(A) & Double(A, 'Z_') & Single('Z_') >> Double(A, 'Z_') ^2\n" + "1: Single(A) & Double(A, '__Z') & Single('__Z') >> Double(A, '__Z') ^2\n" + "1: Single(A) & Double(A, 'Z__') & Single('Z__') >> Double(A, 'Z__') ^2\n" + "1: Single(A) & Double(A, '_Z_') & Single('_Z_') >> Double(A, '_Z_') ^2\n" + "1: Single(A) & Double(A, '__Z__') & Single('__Z__') >> Double(A, '__Z__') ^2\n" + "1: Single(A) & Double(A, '_') & Single('_') >> Double(A, '_') ^2\n" + "1: Single(A) & Double(A, '__') & Single('__') >> Double(A, '__') ^2\n" + "1: Single(A) & Double(A, 'A_B') & Single('A_B') >> Double(A, 'A_B') ^2\n" + "1: Single(A) & Double(A, 'A__B') & Single('A__B') >> Double(A, 'A__B') ^2\n" + "1: Single(A) & Double(A, '_A_B_') & Single('_A_B_') >> Double(A, '_A_B_') ^2\n" + ""; String[] expected = new String[] { "1.0: ( SINGLE(A) & DOUBLE(A, 'bar') & SINGLE('bar') ) >> DOUBLE(A, 'bar') ^2", "1.0: ( SINGLE(B) & DOUBLE(B, 'bar') & SINGLE('bar') ) >> DOUBLE(B, 'bar') ^2", "1.0: ( SINGLE(C) & DOUBLE(C, 'BAR') & SINGLE('BAR') ) >> DOUBLE(C, 'BAR') ^2", "1.0: ( SINGLE(D) & DOUBLE(D, '1BAR') & SINGLE('1BAR') ) >> DOUBLE(D, '1BAR') ^2", "1.0: ( SINGLE(E) & DOUBLE(E, '1') & SINGLE('1') ) >> DOUBLE(E, '1') ^2", "1.0: ( SINGLE(F) & DOUBLE(F, '999') & SINGLE('999') ) >> DOUBLE(F, '999') ^2", "1.0: ( SINGLE(G) & DOUBLE(G, '999') & SINGLE('999') ) >> DOUBLE(G, '999') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, ' Z') & SINGLE(' Z') ) >> DOUBLE(A, ' Z') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, 'Z ') & SINGLE('Z ') ) >> DOUBLE(A, 'Z ') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, ' Z') & SINGLE(' Z') ) >> DOUBLE(A, ' Z') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, 'Z ') & SINGLE('Z ') ) >> DOUBLE(A, 'Z ') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, ' Z ') & SINGLE(' Z ') ) >> DOUBLE(A, ' Z ') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, ' Z ') & SINGLE(' Z ') ) >> DOUBLE(A, ' Z ') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, ' ') & SINGLE(' ') ) >> DOUBLE(A, ' ') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, ' ') & SINGLE(' ') ) >> DOUBLE(A, ' ') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, 'A B') & SINGLE('A B') ) >> DOUBLE(A, 'A B') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, 'A B') & SINGLE('A B') ) >> DOUBLE(A, 'A B') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, ' A B ') & SINGLE(' A B ') ) >> DOUBLE(A, ' A B ') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, '_Z') & SINGLE('_Z') ) >> DOUBLE(A, '_Z') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, 'Z_') & SINGLE('Z_') ) >> DOUBLE(A, 'Z_') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, '__Z') & SINGLE('__Z') ) >> DOUBLE(A, '__Z') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, 'Z__') & SINGLE('Z__') ) >> DOUBLE(A, 'Z__') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, '_Z_') & SINGLE('_Z_') ) >> DOUBLE(A, '_Z_') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, '__Z__') & SINGLE('__Z__') ) >> DOUBLE(A, '__Z__') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, '_') & SINGLE('_') ) >> DOUBLE(A, '_') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, '__') & SINGLE('__') ) >> DOUBLE(A, '__') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, 'A_B') & SINGLE('A_B') ) >> DOUBLE(A, 'A_B') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, 'A__B') & SINGLE('A__B') ) >> DOUBLE(A, 'A__B') ^2", "1.0: ( SINGLE(A) & DOUBLE(A, '_A_B_') & SINGLE('_A_B_') ) >> DOUBLE(A, '_A_B_') ^2" }; PSLTest.assertModel(dataStore, input, expected); } @Test // We are actually testing both numeric constants and coefficients. public void testNumericConstants() { String input = "1: 1 Single(A) = 1 ^2\n" + "1: 1.0 Single(A) = 1 ^2\n" + "1: 1.5 Single(A) = 1 ^2\n" + "1: 0.5 Single(A) = 1 ^2\n" + "1: -1.0 Single(A) = 1 ^2\n" + "1: 5E10 Single(A) = 1 ^2\n" + "1: 5e10 Single(A) = 1 ^2\n" + "1: -5e10 Single(A) = 1 ^2\n" + "1: 5e-10 Single(A) = 1 ^2\n" + "1: 1.2e10 Single(A) = 1 ^2\n" + "1: -1.2e10 Single(A) = 1 ^2\n" + "1: 1.2e-10 Single(A) = 1 ^2\n" + ""; String[] expected = new String[] { "1.0: 1.0 * SINGLE(A) = 1.0 ^2", "1.0: 1.0 * SINGLE(A) = 1.0 ^2", "1.0: 1.5 * SINGLE(A) = 1.0 ^2", "1.0: 0.5 * SINGLE(A) = 1.0 ^2", "1.0: -1.0 * SINGLE(A) = 1.0 ^2", "1.0: 5.0E10 * SINGLE(A) = 1.0 ^2", "1.0: 5.0E10 * SINGLE(A) = 1.0 ^2", "1.0: -5.0E10 * SINGLE(A) = 1.0 ^2", "1.0: 5.0E-10 * SINGLE(A) = 1.0 ^2", "1.0: 1.2E10 * SINGLE(A) = 1.0 ^2", "1.0: -1.2E10 * SINGLE(A) = 1.0 ^2", "1.0: 1.2E-10 * SINGLE(A) = 1.0 ^2" }; PSLTest.assertModel(dataStore, input, expected); } @Test // Using some more unusual identifiers. public void testIdentifiers() { String input = "1: Single(A1) >> Single(A1) ^2\n" + "1: Single(A1A) >> Single(A1A) ^2\n" + "1: Single(A_A) >> Single(A_A) ^2\n" + "1: Single(A_1) >> Single(A_1) ^2\n" + "1: Single(A__) >> Single(A__) ^2\n" + ""; String[] expected = new String[] { "1.0: SINGLE(A1) >> SINGLE(A1) ^2", "1.0: SINGLE(A1A) >> SINGLE(A1A) ^2", "1.0: SINGLE(A_A) >> SINGLE(A_A) ^2", "1.0: SINGLE(A_1) >> SINGLE(A_1) ^2", "1.0: SINGLE(A__) >> SINGLE(A__) ^2" }; PSLTest.assertModel(dataStore, input, expected); } @Test // Test possible values for the rule weight. public void testWeight() { String input = "1: Single(A) >> Single(A)\n" + "0: Single(B) >> Single(B)\n" + "0.5: Single(C) >> Single(C)\n" + "999999: Single(D) >> Single(D)\n" + "9999999999: Single(E) >> Single(E)\n" + "0000000001: Single(F) >> Single(F)\n" + "0.001: Single(G) >> Single(G)\n" + "0.00000001: Single(H) >> Single(H)\n" + "2E10: Single(I) >> Single(I)\n" + "2e10: Single(J) >> Single(J)\n" + "2e-10: Single(K) >> Single(K)\n" + "2.5e10: Single(L) >> Single(L)\n" + "2.5e-10: Single(M) >> Single(M)\n" + ""; String[] expected = new String[] { "1.0: SINGLE(A) >> SINGLE(A)", "0.0: SINGLE(B) >> SINGLE(B)", "0.5: SINGLE(C) >> SINGLE(C)", "999999.0: SINGLE(D) >> SINGLE(D)", "9.999999999E9: SINGLE(E) >> SINGLE(E)", "1.0: SINGLE(F) >> SINGLE(F)", "0.001: SINGLE(G) >> SINGLE(G)", "1.0E-8: SINGLE(H) >> SINGLE(H)", "2.0E10: SINGLE(I) >> SINGLE(I)", "2.0E10: SINGLE(J) >> SINGLE(J)", "2.0E-10: SINGLE(K) >> SINGLE(K)", "2.5E10: SINGLE(L) >> SINGLE(L)", "2.5E-10: SINGLE(M) >> SINGLE(M)" }; PSLTest.assertModel(dataStore, input, expected); } @Test public void testImpliedBy() { String input = "1: Single(A) & Double(A, B) >> Single(B) ^2\n" + "1: Single(D) << Single(C) & Double(C, D) ^2\n"; String[] expected = new String[] { "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE(C) & DOUBLE(C, D) ) >> SINGLE(D) ^2" }; PSLTest.assertModel(dataStore, input, expected); } @Test public void testDisjunction() { String input = "1: Single(A) | Double(A, B) << Single(B) & Single(A) ^2\n" + "1: Single(A) & Double(B, C) >> Single(B) | Single(C) ^2\n"; String[] expected = new String[] { "1.0: ( SINGLE(B) & SINGLE(A) ) >> ( SINGLE(A) | DOUBLE(A, B) ) ^2", "1.0: ( SINGLE(A) & DOUBLE(B, C) ) >> ( SINGLE(B) | SINGLE(C) ) ^2" }; PSLTest.assertModel(dataStore, input, expected); } /** * Negation is only allowed on an atom or another negation. * This is because we only allow conjunctions in the body and disjunctions in the head. */ @Test public void testNegation() { String input = "1: ~Single(A) & Double(A, B) >> ~Single(B) ^2\n" + "1: ~Single(C) & ~~Double(C, D) >> ~~~Single(D) ^2\n" + "1: !Single(E) & Double(E, F) >> !Single(F) ^2\n" + "1: !Single(G) & !!Double(G, H) >> !!!Single(H) ^2\n" + ""; String[] expected = new String[] { "1.0: ( ~( SINGLE(A) ) & DOUBLE(A, B) ) >> ~( SINGLE(B) ) ^2", "1.0: ( ~( SINGLE(C) ) & ~( ~( DOUBLE(C, D) ) ) ) >> ~( ~( ~( SINGLE(D) ) ) ) ^2", "1.0: ( ~( SINGLE(E) ) & DOUBLE(E, F) ) >> ~( SINGLE(F) ) ^2", "1.0: ( ~( SINGLE(G) ) & ~( ~( DOUBLE(G, H) ) ) ) >> ~( ~( ~( SINGLE(H) ) ) ) ^2" }; PSLTest.assertModel(dataStore, input, expected); try { PSLTest.assertRule(dataStore, "1: ~( Single(A) & Single(B) ) >> Double(A, B) ^2", ""); fail("Negation not allowed on a conjunction."); } catch (org.antlr.v4.runtime.RecognitionException ex) { // Exception expected. } try { PSLTest.assertRule(dataStore, "1: Double(A, B) >> ~( Single(A) | Single(B) ) ^2", ""); fail("Negation not allowed on a disjunction."); } catch (org.antlr.v4.runtime.RecognitionException ex) { // Exception expected. } } @Test public void testTermEquality() { String input = "1: A == B & Double(A, B) >> Single(B) ^2\n" + "1: A == 'Bar' & Double(A, B) >> Single(B) ^2\n" + "1: 'Foo' == B & Double(A, B) >> Single(B) ^2\n" + "1: 'Foo' == 'Bar' & Double(A, B) >> Single(B) ^2\n" + "1: A ~= B & Double(A, B) >> Single(B) ^2\n" + "1: A ~= 'Bar' & Double(A, B) >> Single(B) ^2\n" + "1: 'Foo' ~= B & Double(A, B) >> Single(B) ^2\n" + "1: 'Foo' ~= 'Bar' & Double(A, B) >> Single(B) ^2\n" + ""; String[] expected = new String[] { "1.0: ( (A == B) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( (A == 'Bar') & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( ('Foo' == B) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( ('Foo' == 'Bar') & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( (A != B) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( (A != 'Bar') & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( ('Foo' != B) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( ('Foo' != 'Bar') & DOUBLE(A, B) ) >> SINGLE(B) ^2" }; PSLTest.assertModel(dataStore, input, expected); } @Test public void testAlternativeSyntax() { String input = "1: Single(A) & Double(A, B) >> Single(B)\n" + "1: Single(C) && Double(C, D) >> Single(D)\n" + "1: Single(E) & Double(E, F) >> Single(F)\n" + "1: Single(G) & Double(G, H) -> Single(H)\n" + "1: Single(K) | Single(L) << Double(K, L)\n" + "1: Single(M) || Single(N) << Double(M, N)\n" + "1: Single(O) | Single(P) << Double(O, P)\n" + "1: Single(Q) | Single(R) <- Double(Q, R)\n" + "1: S != T & Double(S, T) >> Single(T)\n" + "1: U ~= V & Double(U, V) >> Single(V)\n" + ""; String[] expected = new String[] { "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B)", "1.0: ( SINGLE(C) & DOUBLE(C, D) ) >> SINGLE(D)", "1.0: ( SINGLE(E) & DOUBLE(E, F) ) >> SINGLE(F)", "1.0: ( SINGLE(G) & DOUBLE(G, H) ) >> SINGLE(H)", "1.0: DOUBLE(K, L) >> ( SINGLE(K) | SINGLE(L) )", "1.0: DOUBLE(M, N) >> ( SINGLE(M) | SINGLE(N) )", "1.0: DOUBLE(O, P) >> ( SINGLE(O) | SINGLE(P) )", "1.0: DOUBLE(Q, R) >> ( SINGLE(Q) | SINGLE(R) )", "1.0: ( (S != T) & DOUBLE(S, T) ) >> SINGLE(T)", "1.0: ( (U != V) & DOUBLE(U, V) ) >> SINGLE(V)" }; PSLTest.assertModel(dataStore, input, expected); } @Test public void testMultiplyCoefficient() { String input = "1: 1 Single(A) = 1 ^2\n" + "1: 1 * Single(A) = 1 ^2\n" + ""; String[] expected = new String[] { "1.0: 1.0 * SINGLE(A) = 1.0 ^2", "1.0: 1.0 * SINGLE(A) = 1.0 ^2" }; PSLTest.assertModel(dataStore, input, expected); } @Test // Just throw in a bunch of arithmetic rules used in other tests. public void testGeneralArithmetic() { String input = "Single(A) + Single(B) = 1 .\n" + "Double(+A, 'Foo') = 1 .\n" + "Single(+A) + Single(+B) = 1 .\n" + "Single(+A) = 1 . {A: Single(A)}\n" + "Single(+A) = 1 . {A: Single(A) || Double(A, A) }\n" + "Double(+A, B) = 1 . {A: Single(B)}\n" + "Single(+A) + Single(+B) = 1 . {A: Single(A)} {B: Single(B)}\n" + "|A| Single(+A) = 1 .\n" + "|A| Single(+A) = |A| .\n" + "|A| Single(+A) + |B| Single(+B) = 1 .\n" + "@Max[|A|, 0] Single(+A) = 1 .\n" + "@Max[1, 0] Single(+A) = 1 .\n" + "@Max[|A|, |B|] Single(+A) + Single(+B) = 1 .\n" + "@Min[1, 0] Single(A) = 1 .\n" + ""; String[] expected = new String[] { "1.0 * SINGLE(A) + 1.0 * SINGLE(B) = 1.0 .", "1.0 * DOUBLE(+A, 'Foo') = 1.0 .", "1.0 * SINGLE(+A) + 1.0 * SINGLE(+B) = 1.0 .", "1.0 * SINGLE(+A) = 1.0 .\n{A : SINGLE(A)}", "1.0 * SINGLE(+A) = 1.0 .\n{A : ( SINGLE(A) | DOUBLE(A, A) )}", "1.0 * DOUBLE(+A, B) = 1.0 .\n{A : SINGLE(B)}", "1.0 * SINGLE(+A) + 1.0 * SINGLE(+B) = 1.0 .\n{A : SINGLE(A)}\n{B : SINGLE(B)}", "|A| * SINGLE(+A) = 1.0 .", "|A| * SINGLE(+A) = |A| .", "|A| * SINGLE(+A) + |B| * SINGLE(+B) = 1.0 .", "@Max[|A|, 0.0] * SINGLE(+A) = 1.0 .", "1.0 * SINGLE(+A) = 1.0 .", "@Max[|A|, |B|] * SINGLE(+A) + 1.0 * SINGLE(+B) = 1.0 .", "0.0 * SINGLE(A) = 1.0 ." }; PSLTest.assertModel(dataStore, input, expected); } @Test public void testLoadRuleBase() { String input = "1: Single(A) & Double(A, B) >> Single(B) ^2"; String expected = "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) ^2"; PSLTest.assertRule(dataStore, input, expected); } @Test public void testLoadRuleBadCount() { // Having zero rules is a parse error, so the exception is different. try { PSLTest.assertRule(dataStore, "// Just a comment", ""); fail("ModelLoader.LoadRule() with no rule did not throw an exception."); } catch (org.antlr.v4.runtime.NoViableAltException ex) { // Exception expected. } String input = "1: Single(A) & Double(A, B) >> Single(B) ^2\n" + "5: Single(B) & Double(B, A) >> Single(A) ^2\n"; String expected = "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) ^2"; try { PSLTest.assertRule(dataStore, input, expected); fail("ModelLoader.LoadRule() with more than one rule did not throw an exception."); } catch (IllegalArgumentException ex) { // Exception expected. } } @Test // Floats must have a leading number (".1" is not good, must be "0.1"). public void testLeadDigitOnFloat() { String[] input = new String[] { ".1: Single(A) & Double(A, B) >> Single(B) ^2", "1: .1 Single(A) = 1 ^2" }; String[] expected = new String[] { "0.1: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: 0.1 * SINGLE(A) = 1.0 ^2" }; for (int i = 0; i < input.length; i++) { try { PSLTest.assertRule(dataStore, input[i], expected[i]); fail(String.format("Rule: %d - Exception not thrown when float used without leading digit.", i)); } catch (Exception ex) { // Exception expected. } } } @Test // Various misc syntax errors. public void testGeneralBadSyntax() { String[] input = new String[] { // Missing comma "1: Single(A) & Double(A B) >> Single(B) ^2", // Unknown predicate "1: Unknown(A) & Double(A, B) >> Single(B) ^2", // Mismatched quotes. "1: Single(A) & Double(\"Foo', B) >> Single(B) ^2", // Mismatched parens. "1: ( Single(A) & Double(A, B) >> Single(B) ^2", "1: Single(A) & Double(A, B) ) >> Single(B) ^2", // Missing unweighted period. "Single(A) & Double(A, B) >> Single(B) ^2", // Negative weight "-1: Single(A) & Double(A B) >> Single(B) ^2" }; String[] expected = new String[] { "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( UNKNOWN(A) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE(A) & DOUBLE('Foo', B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) . ^2", "-1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) ^2" }; for (int i = 0; i < input.length; i++) { try { PSLTest.assertRule(dataStore, input[i], expected[i]); fail(String.format("Rule: %d - Exception not thrown on general syntax error.", i)); } catch (Exception ex) { // Exception expected. } } } @Test public void testBadSquaring() { String[] input = new String[] { // TODO(eriq): This is a bad input but not caught by the parser. // "1: Single(A) & Double(A, B) >> Single(B) ^2.5", "1: Single(A) & Double(A, B) >> Single(B) ^3", "1: Single(A) & Double(A, B) >> Single(B) ^-1", "1: Single(A) & Double(A, B) >> Single(B) ^-2.0", "1: Single(A) & Double(A, B) >> Single(B) ^-2.5", "1: Single(A) & Double(A, B) >> Single(B) ^-3" }; String[] expected = new String[] { // "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B)", "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B)", "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B)", "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B)", "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B)", "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B)" }; for (int i = 0; i < input.length; i++) { try { PSLTest.assertRule(dataStore, input[i], expected[i]); fail(String.format("Rule: %d - Exception not thrown on bad square error.", i)); } catch (Exception ex) { // Exception expected. } } } @Test // First test only rules that are fully specified. public void testLoadRulePartialCompleteRules() { String[] inputs = new String[] { "1: Single(A) & Double(A, B) >> Single(B) ^2", "Single(A) & Double(A, B) >> Single(B) .", "1: 1 Single(A) = 1 ^2", "1 Single(A) = 1 .", "Single(+A) = 1 . {A: Single(A)}", "1: Single(+A) = 1 {A: Single(A)}", "1: Single(+A) = 1 ^2 {A: Single(A)}" }; String[] expected = new String[] { "1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) .", "1.0: 1.0 * SINGLE(A) = 1.0 ^2", "1.0 * SINGLE(A) = 1.0 .", "1.0 * SINGLE(+A) = 1.0 .\n{A : SINGLE(A)}", "1.0: 1.0 * SINGLE(+A) = 1.0\n{A : SINGLE(A)}", "1.0: 1.0 * SINGLE(+A) = 1.0 ^2\n{A : SINGLE(A)}", }; try { for (int i = 0; i < inputs.length; i++) { RulePartial partial = ModelLoader.loadRulePartial(dataStore, inputs[i]); assertEquals(String.format("Expected RulePartial #%d to be a rule, but was not.", i), true, partial.isRule()); Rule rule = partial.toRule(); PSLTest.assertStringEquals(expected[i], rule.toString(), true, String.format("Rule %d string mismatch", i)); } } catch (IOException ex) { fail("Unexpected IOException thrown from ModelLoader.loadRulePartial(): " + ex); } } @Test // First test only rules that are fully specified. public void testLoadRulePartialPartialRules() { String[] inputs = new String[] { "Single(A) & Double(A, B) >> Single(B)", "1 Single(A) = 1", "Single(+A) = 1 {A: Single(A)}", "Single(+A) + Single(+B) = 1 {A: Single(A)} {B: Single(B)}" }; String[] unweightedExpected = new String[] { "( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) .", "1.0 * SINGLE(A) = 1.0 .", "1.0 * SINGLE(+A) = 1.0 .\n{A : SINGLE(A)}", "1.0 * SINGLE(+A) + 1.0 * SINGLE(+B) = 1.0 .\n{A : SINGLE(A)}\n{B : SINGLE(B)}" }; // Weight all the variants with 5 and square them. String[] weightedExpected = new String[] { "5.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "5.0: 1.0 * SINGLE(A) = 1.0 ^2", "5.0: 1.0 * SINGLE(+A) = 1.0 ^2\n{A : SINGLE(A)}", "5.0: 1.0 * SINGLE(+A) + 1.0 * SINGLE(+B) = 1.0 ^2\n{A : SINGLE(A)}\n{B : SINGLE(B)}" }; try { for (int i = 0; i < inputs.length; i++) { RulePartial partial = ModelLoader.loadRulePartial(dataStore, inputs[i]); assertEquals(String.format("Expected RulePartial #%d to not a rule, but was.", i), false, partial.isRule()); Rule unweightedRule = partial.toRule(); PSLTest.assertStringEquals(unweightedExpected[i], unweightedRule.toString(), true, String.format("Unweighted rule %d string mismatch", i)); Rule weightedRule = partial.toRule(5.0, true); PSLTest.assertStringEquals(weightedExpected[i], weightedRule.toString(), true, String.format("Weighted rule %d string mismatch", i)); } } catch (IOException ex) { fail("Unexpected IOException thrown from ModelLoader.loadRulePartial(): " + ex); } } @Test public void testNonSymmetric() { String input = "1: Single(A) & Single(B) & (A % B) >> Double(A, B) ^2\n" + "1: Single(C) & Single(D) & (C ^ D) >> Double(C, D) ^2\n" + ""; String[] expected = new String[] { "1.0: ( SINGLE(A) & SINGLE(B) & (A % B) ) >> DOUBLE(A, B) ^2", "1.0: ( SINGLE(C) & SINGLE(D) & (C % D) ) >> DOUBLE(C, D) ^2" }; PSLTest.assertModel(dataStore, input, expected); } @Test public void testArithmeticCoefficientOperationOrder() { String input = "1.0 + 2.0 * 3.0 * Single(A) = 99 .\n" + "1.0 + 2.0 * 3.0 * Single(A) + Single(B) = 99 .\n" + "99 = 1.0 + 2.0 * 3.0 * Single(A) .\n" + "1.0 + 2.0 * 3.0 * Single(A) = 4.0 + 5.0 * 6.0 * Single(B) .\n" + "1.0 + 2.0 - 3.0 * Single(A) = 99 .\n" + "1.0 - 2.0 + 3.0 * Single(A) = 99 .\n" + "1.0 + 2.0 + 3.0 - 4.0 * Single(A) = 99 .\n" + "1.0 - 2.0 - 3.0 + 4.0 * Single(A) = 99 .\n" + "1.0 + (2.0 * 3.0) * Single(A) = 99 .\n" + "(1.0 + 2.0) * 3.0 * Single(A) = 99 .\n" + "1.0 + 2.0 * |A| * Single(+A) = 99 .\n" + "1.0 + 2.0 * @Min(3.0, 4.0) * Single(+A) = 99 .\n" + "1.0 + 2.0 * @Max(3.0, 4.0) * Single(+A) = 99 .\n" + "1.0 + 2.0 * 3.0 + 4.0 * Single(A) = 99 .\n" + "1.0 + (2.0 * 3.0) + 4.0 * Single(A) = 99 .\n" + "(1.0 + 2.0) * (3.0 + 4.0) * Single(A) = 99 .\n" + "1.0 - 2.0 / 4.0 - 5.0 * Single(A) = 99 .\n" + "1.0 - (2.0 / 4.0) - 5.0 * Single(A) = 99 .\n" + "(1.0 - 2.0) / (4.0 - 5.0) * Single(A) = 99 .\n" + ""; String[] expected = new String[] { "7.0 * SINGLE(A) = 99.0 .", "7.0 * SINGLE(A) + 1.0 * SINGLE(B) = 99.0 .", "-7.0 * SINGLE(A) = -99.0 .", "7.0 * SINGLE(A) + -34.0 * SINGLE(B) = 0.0 .", "0.0 * SINGLE(A) = 99.0 .", "2.0 * SINGLE(A) = 99.0 .", "2.0 * SINGLE(A) = 99.0 .", "0.0 * SINGLE(A) = 99.0 .", "7.0 * SINGLE(A) = 99.0 .", "9.0 * SINGLE(A) = 99.0 .", "(1.0 + (2.0 * |A|)) * SINGLE(+A) = 99.0 .", "7.0 * SINGLE(+A) = 99.0 .", "9.0 * SINGLE(+A) = 99.0 .", "11.0 * SINGLE(A) = 99.0 .", "11.0 * SINGLE(A) = 99.0 .", "21.0 * SINGLE(A) = 99.0 .", "-4.5 * SINGLE(A) = 99.0 .", "-4.5 * SINGLE(A) = 99.0 .", "1.0 * SINGLE(A) = 99.0 ." }; PSLTest.assertModel(dataStore, input, expected); } @Test public void testNotEquals() { // Test both syntaxes. String input = "1: A != B & Single(A) & Single(B) >> Double(A, B) ^2\n" + "1: (C != D) & Single(C) & Single(D) >> Double(C, D) ^2\n" + "1: E!=F & Single(E) & Single(F) >> Double(E, F) ^2\n" + "1: (G!=H) & Single(G) & Single(H) >> Double(G, H) ^2\n" + "1: I - J & Single(I) & Single(J) >> Double(I, J) ^2\n" + "1: (K - L) & Single(K) & Single(L) >> Double(K, L) ^2\n" + "1: M-N & Single(M) & Single(N) >> Double(M, N) ^2\n" + "1: (O-P) & Single(O) & Single(P) >> Double(O, P) ^2\n" + ""; String[] expected = new String[] { "1.0: ( (A != B) & SINGLE(A) & SINGLE(B) ) >> DOUBLE(A, B) ^2", "1.0: ( (C != D) & SINGLE(C) & SINGLE(D) ) >> DOUBLE(C, D) ^2", "1.0: ( (E != F) & SINGLE(E) & SINGLE(F) ) >> DOUBLE(E, F) ^2", "1.0: ( (G != H) & SINGLE(G) & SINGLE(H) ) >> DOUBLE(G, H) ^2", "1.0: ( (I != J) & SINGLE(I) & SINGLE(J) ) >> DOUBLE(I, J) ^2", "1.0: ( (K != L) & SINGLE(K) & SINGLE(L) ) >> DOUBLE(K, L) ^2", "1.0: ( (M != N) & SINGLE(M) & SINGLE(N) ) >> DOUBLE(M, N) ^2", "1.0: ( (O != P) & SINGLE(O) & SINGLE(P) ) >> DOUBLE(O, P) ^2" }; PSLTest.assertModel(dataStore, input, expected); } @Test public void testFilterOperatorOrder() { String input = "Single(+A) + Double(B, C) = 1 . {A: Single(A) || Single(B) || Single(C)}\n" + "Single(+A) + Double(B, C) = 1 . {A: Single(A) && Single(B) && Single(C)}\n" + "Single(+A) + Double(B, C) = 1 . {A: Single(A) || Single(B) && Single(C)}\n" + "Single(+A) + Double(B, C) = 1 . {A: Single(A) && Single(B) || Single(C)}\n" + "Single(+A) + Double(B, C) = 1 . {A: (Single(A) || Single(B)) || Single(C)}\n" + "Single(+A) + Double(B, C) = 1 . {A: Single(A) || (Single(B) || Single(C))}\n" + "Single(+A) + Double(B, C) = 1 . {A: (Single(A) && Single(B)) && Single(C)}\n" + "Single(+A) + Double(B, C) = 1 . {A: Single(A) && (Single(B) && Single(C))}\n" + "Single(+A) + Double(B, C) = 1 . {A: (Single(A) || Single(B)) && Single(C)}\n" + "Single(+A) + Double(B, C) = 1 . {A: Single(A) || (Single(B) && Single(C))}\n" + "Single(+A) + Double(B, C) = 1 . {A: (Single(A) && Single(B)) || Single(C)}\n" + "Single(+A) + Double(B, C) = 1 . {A: Single(A) && (Single(B) || Single(C))}\n" + ""; String[] expected = new String[] { "1.0 * SINGLE(+A) + 1.0 * DOUBLE(B, C) = 1.0 .\n{A : ( SINGLE(A) | SINGLE(B) | SINGLE(C) )}", "1.0 * SINGLE(+A) + 1.0 * DOUBLE(B, C) = 1.0 .\n{A : ( SINGLE(A) & SINGLE(B) & SINGLE(C) )}", "1.0 * SINGLE(+A) + 1.0 * DOUBLE(B, C) = 1.0 .\n{A : ( SINGLE(A) | ( SINGLE(B) & SINGLE(C) ) )}", "1.0 * SINGLE(+A) + 1.0 * DOUBLE(B, C) = 1.0 .\n{A : ( ( SINGLE(A) & SINGLE(B) ) | SINGLE(C) )}", "1.0 * SINGLE(+A) + 1.0 * DOUBLE(B, C) = 1.0 .\n{A : ( SINGLE(A) | SINGLE(B) | SINGLE(C) )}", "1.0 * SINGLE(+A) + 1.0 * DOUBLE(B, C) = 1.0 .\n{A : ( SINGLE(A) | SINGLE(B) | SINGLE(C) )}", "1.0 * SINGLE(+A) + 1.0 * DOUBLE(B, C) = 1.0 .\n{A : ( SINGLE(A) & SINGLE(B) & SINGLE(C) )}", "1.0 * SINGLE(+A) + 1.0 * DOUBLE(B, C) = 1.0 .\n{A : ( SINGLE(A) & SINGLE(B) & SINGLE(C) )}", "1.0 * SINGLE(+A) + 1.0 * DOUBLE(B, C) = 1.0 .\n{A : ( ( SINGLE(A) & SINGLE(C) ) | ( SINGLE(B) & SINGLE(C) ) )}", "1.0 * SINGLE(+A) + 1.0 * DOUBLE(B, C) = 1.0 .\n{A : ( SINGLE(A) | ( SINGLE(B) & SINGLE(C) ) )}", "1.0 * SINGLE(+A) + 1.0 * DOUBLE(B, C) = 1.0 .\n{A : ( ( SINGLE(A) & SINGLE(B) ) | SINGLE(C) )}", "1.0 * SINGLE(+A) + 1.0 * DOUBLE(B, C) = 1.0 .\n{A : ( ( SINGLE(A) & SINGLE(B) ) | ( SINGLE(A) & SINGLE(C) ) )}" }; PSLTest.assertModel(dataStore, input, expected); } @Test public void testArithmeticSubtraction() { String input = "Double(A, B) - Double(B, A) = 0.0 .\n" + "0.0 = Double(A, B) - Double(B, A) .\n" + "Double(A, B) + Double(B, A) = 0.0 .\n" + "0.0 = Double(A, B) + Double(B, A) .\n" + ""; String[] expected = new String[] { "1.0 * DOUBLE(A, B) + -1.0 * DOUBLE(B, A) = 0.0 .", "-1.0 * DOUBLE(A, B) + 1.0 * DOUBLE(B, A) = 0.0 .", "1.0 * DOUBLE(A, B) + 1.0 * DOUBLE(B, A) = 0.0 .", "-1.0 * DOUBLE(A, B) + -1.0 * DOUBLE(B, A) = 0.0 ." }; PSLTest.assertModel(dataStore, input, expected); } @Test public void testLogicalParens() { String input = "1.0: Single(A) & Single(B) >> Double(A, B) ^2\n" + "1.0: Single(C) & Single(D) >> Double(C, D)\n" + "1.0: ( Single(E) && Single(F) ) -> Double(E, F) ^2\n" + "1.0: ( Single(G) && Single(H) ) -> Double(G, H)\n" + "1.0: (Single(I)) & Single(J) >> Double(I, J)\n" + "1.0: (Single(K)) & (Single(L)) >> Double(K, L)\n" + "1.0: ((Single(M)) & (Single(N))) >> Double(M, N)\n" + "1.0: (Single(O)) & Single(P) >> (Double(O, P))\n" + "1.0: (((Single(Q)) & (Single(R))) & Double(R, Q)) >> Double(Q, R)\n" + ""; String[] expected = new String[] { "1.0: ( SINGLE(A) & SINGLE(B) ) >> DOUBLE(A, B) ^2", "1.0: ( SINGLE(C) & SINGLE(D) ) >> DOUBLE(C, D)", "1.0: ( SINGLE(E) & SINGLE(F) ) >> DOUBLE(E, F) ^2", "1.0: ( SINGLE(G) & SINGLE(H) ) >> DOUBLE(G, H)", "1.0: ( SINGLE(I) & SINGLE(J) ) >> DOUBLE(I, J)", "1.0: ( SINGLE(K) & SINGLE(L) ) >> DOUBLE(K, L)", "1.0: ( SINGLE(M) & SINGLE(N) ) >> DOUBLE(M, N)", "1.0: ( SINGLE(O) & SINGLE(P) ) >> DOUBLE(O, P)", "1.0: ( SINGLE(Q) & SINGLE(R) & DOUBLE(R, Q) ) >> DOUBLE(Q, R)", }; PSLTest.assertModel(dataStore, input, expected); } @Test public void testArithmeticParens() { String input = "Single(A) + Single(B) = 0.0 .\n" + "(Single(A) + Single(B)) = 0.0 .\n" + ""; String[] expected = new String[] { "1.0 * SINGLE(A) + 1.0 * SINGLE(B) = 0.0 .", "1.0 * SINGLE(A) + 1.0 * SINGLE(B) = 0.0 ." }; PSLTest.assertModel(dataStore, input, expected); } @Test public void testArithmeticDivideByZero() { String[] input = new String[] { "Single(A) / 0 + Single(B) = 0.0 .", "2 / 0 * Single(A) + Single(B) = 0.0 .", "2 / (2 - 2) * Single(A) + Single(B) = 0.0 .", "Single(A) / (-1 + 1) + Single(B) = 0.0 .", "Single(A) / @Min[0, 1] + Single(B) = 0.0 ." }; for (String rule : input) { try { PSLTest.assertRule(dataStore, rule, ""); fail("Divide by zero did not throw exception."); } catch (RuntimeException ex) { if (!(ex.getCause() instanceof ArithmeticException)) { fail("Divide by zero threw a non-Arithmetic exception: " + ex.getCause() + "."); } } } } // Make sure that arithmetic rules properly differentiate between // QueryAtoms and SummationAtoms. @Test public void testArithmeticSummationAtom() { // QueryAtom String input = "1.0: Double(A, B) <= 1.0 ^2"; List<Rule> rules = PSLTest.getRules(dataStore, input); assertEquals(1, rules.size()); assertEquals(WeightedArithmeticRule.class, rules.get(0).getClass()); WeightedArithmeticRule rule = (WeightedArithmeticRule) rules.get(0); List<SummationAtomOrAtom> atoms = rule.getExpression().getAtoms(); assertEquals(1, atoms.size()); assertEquals(QueryAtom.class, atoms.get(0).getClass()); // SummationAtom input = "1.0: Double(+A, B) <= 1.0 ^2"; rules = PSLTest.getRules(dataStore, input); assertEquals(1, rules.size()); assertEquals(WeightedArithmeticRule.class, rules.get(0).getClass()); rule = (WeightedArithmeticRule) rules.get(0); atoms = rule.getExpression().getAtoms(); assertEquals(1, atoms.size()); assertEquals(SummationAtom.class, atoms.get(0).getClass()); } @Test public void testNegativeWeights() { String input = "-1: Single(A) & Double(A, B) >> Single(B) ^2\n" + "-5.2: Single(B) & Double(B, A) >> Single(A) ^2\n"; String[] expected = new String[] { "-1.0: ( SINGLE(A) & DOUBLE(A, B) ) >> SINGLE(B) ^2", "-5.2: ( SINGLE(B) & DOUBLE(B, A) ) >> SINGLE(A) ^2" }; PSLTest.assertModel(dataStore, input, expected); } @Test public void testNonAlphanumericConstants() { String input = "1: Single('') & Double(A, B) >> Single(B) ^2\n" + "1: Single('\\\\') & Double(A, B) >> Single(B) ^2\n" + "1: Single('\\'') & Double(A, B) >> Single(B) ^2\n" + "1: Single('\"') & Double(A, B) >> Single(B) ^2\n" + "1: Single('a\n\t\rb') & Double(A1, B) >> Single(B) ^2\n" + "1: Single('a\\n\\t\\rb') & Double(A2, B) >> Single(B) ^2\n" + "1: Single('abc') & Double(A, B) >> Single(B) ^2\n" + "1: Single('123') & Double(A, B) >> Single(B) ^2\n" + "1: Single('`~!@#$%^&*()-_=+') & Double(A, B) >> Single(B) ^2\n" + "1: Single('{[}]|;:') & Double(A, B) >> Single(B) ^2\n" + "1: Single('<,>.?/') & Double(A, B) >> Single(B) ^2\n" + "1: Single(\"\") & Double(Z, B) >> Single(B) ^2\n" + "1: Single(\"\\\\\") & Double(Z, B) >> Single(B) ^2\n" + "1: Single(\"'\") & Double(Z, B) >> Single(B) ^2\n" + "1: Single(\"\\\"\") & Double(Z, B) >> Single(B) ^2\n" + "1: Single(\"a\n\t\rb\") & Double(Z1, B) >> Single(B) ^2\n" + "1: Single(\"a\\n\\t\\rb\") & Double(Z2, B) >> Single(B) ^2\n" + "1: Single(\"abc\") & Double(Z, B) >> Single(B) ^2\n" + "1: Single(\"123\") & Double(Z, B) >> Single(B) ^2\n" + "1: Single(\"`~!@#$%^&*()-_=+\") & Double(Z, B) >> Single(B) ^2\n" + "1: Single(\"{[}]|;:\") & Double(Z, B) >> Single(B) ^2\n" + "1: Single(\"<,>.?/\") & Double(Z, B) >> Single(B) ^2\n" + ""; String[] expected = new String[] { "1.0: ( SINGLE('') & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('\\\\') & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('\\'') & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('\"') & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('a\n\t\rb') & DOUBLE(A1, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('a\n\t\rb') & DOUBLE(A2, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('abc') & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('123') & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('`~!@#$%^&*()-_=+') & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('{[}]|;:') & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('<,>.?/') & DOUBLE(A, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('') & DOUBLE(Z, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('\\\\') & DOUBLE(Z, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('\\'') & DOUBLE(Z, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('\"') & DOUBLE(Z, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('a\n\t\rb') & DOUBLE(Z1, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('a\n\t\rb') & DOUBLE(Z2, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('abc') & DOUBLE(Z, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('123') & DOUBLE(Z, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('`~!@#$%^&*()-_=+') & DOUBLE(Z, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('{[}]|;:') & DOUBLE(Z, B) ) >> SINGLE(B) ^2", "1.0: ( SINGLE('<,>.?/') & DOUBLE(Z, B) ) >> SINGLE(B) ^2", }; PSLTest.assertModel(dataStore, input, expected); } }