/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package org.voltdb.compiler; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import org.voltdb.VoltDB.Configuration; import org.voltdb.compiler.procedures.FloatParamToGetNiceComplaint; import org.voltdb.compiler.procedures.InsertAggregatesOfFloat; import org.voltdb.compiler.procedures.InsertAggregatesOfFloatInHaving; import org.voltdb.compiler.procedures.InsertAggregatesOfFloatWithSetops; import org.voltdb_testprocs.regressionsuites.failureprocs.DeterministicRONonSeqProc; import org.voltdb_testprocs.regressionsuites.failureprocs.DeterministicROSeqProc; import org.voltdb_testprocs.regressionsuites.failureprocs.DeterministicRWProc1; import org.voltdb_testprocs.regressionsuites.failureprocs.DeterministicRWProc2; import org.voltdb_testprocs.regressionsuites.failureprocs.NondeterministicROProc; import org.voltdb_testprocs.regressionsuites.failureprocs.NondeterministicRWProc; import org.voltdb_testprocs.regressionsuites.failureprocs.ProcSPNoncandidate1; import org.voltdb_testprocs.regressionsuites.failureprocs.ProcSPNoncandidate2; import org.voltdb_testprocs.regressionsuites.failureprocs.ProcSPNoncandidate3; import org.voltdb_testprocs.regressionsuites.failureprocs.ProcSPNoncandidate4; import org.voltdb_testprocs.regressionsuites.failureprocs.ProcSPNoncandidate5; import org.voltdb_testprocs.regressionsuites.failureprocs.ProcSPNoncandidate6; import org.voltdb_testprocs.regressionsuites.failureprocs.ProcSPcandidate1; import org.voltdb_testprocs.regressionsuites.failureprocs.ProcSPcandidate2; import org.voltdb_testprocs.regressionsuites.failureprocs.ProcSPcandidate3; import org.voltdb_testprocs.regressionsuites.failureprocs.ProcSPcandidate4; import org.voltdb_testprocs.regressionsuites.failureprocs.ProcSPcandidate5; import org.voltdb_testprocs.regressionsuites.failureprocs.ProcSPcandidate6; import org.voltdb_testprocs.regressionsuites.failureprocs.ProcSPcandidate7; import junit.framework.TestCase; public class TestVoltCompilerAnnotationsAndWarnings extends TestCase { /** * Test whether a DDL stored procedure does not compile properly. The test * testSimple cannot test compilation failure. It's sometimes useful to have * a single test to test compilation success. * * @param simpleSchema * A string containing the test schema. * @param procObject * This tells what procedure to test. If this is an object of * type Class<?> we we add its procedures to the builder. If it's * an array of two strings we add a statement procedure whose * name is the first string and whose DDL definition is the * second string. Note that the "create procedure as" part is * added for you, so all you need is the SQL text for the * procedure. * @param errorMessages * The error messages we expect to see, as Java regular * expressions. This may be true of no errors are expected. * @param expectSuccess * If this is true, the compilation should succeed. * @throws Exception */ public void testCompilationFailure(String testName, String simpleSchema, Object procObject, String[] errorMessages, boolean expectSuccess) throws Exception { VoltProjectBuilder builder = new VoltProjectBuilder(); ByteArrayOutputStream capturer = new ByteArrayOutputStream(); PrintStream capturing = new PrintStream(capturer); builder.setCompilerDebugPrintStream(capturing); builder.addLiteralSchema(simpleSchema); if (procObject instanceof String[]) { String[] stmtProcDescrip = (String[]) procObject; assertTrue(stmtProcDescrip.length == 2); builder.addStmtProcedure(stmtProcDescrip[0], stmtProcDescrip[1]); } else if (procObject instanceof Class<?>) { Class<?> procKlazz = (Class<?>) procObject; builder.addProcedures(procKlazz); } else { assertTrue("Bad type of object for parameter \"procObject\"", false); } boolean success = builder.compile(Configuration.getPathToCatalogForTest("annotations.jar")); assertEquals(String.format("Expected compilation %s", (expectSuccess ? "success" : "failure")), expectSuccess, success); if (errorMessages != null) { String captured = capturer.toString("UTF-8"); String[] lines = captured.split("\n"); System.out.printf("\n" + ":----------------------------------------------------------------------:\n" + ": %s: Start of captured output\n" + ":----------------------------------------------------------------------:\n", testName); System.out.println(captured); System.out.printf("\n" + ":----------------------------------------------------------------------:\n" + ": %s: End of captured output\n" + ":----------------------------------------------------------------------:\n", testName); // Output should include a line suggesting replacement of float with // double. for (String oneMessagePattern : errorMessages) { assertTrue(foundLineMatching(lines, oneMessagePattern)); } } } /** * Test that a stored procedure with a parameter type of float will not * compile. The Java type float is not legal for stored procedures. * * @throws Exception */ public void testFloatParamComplaint() throws Exception { String simpleSchema = "create table floatie (" + "ival bigint default 0 not null, " + "fval float not null," + "PRIMARY KEY(ival)" + ");" + "partition table floatie on column ival;"; String[][] partitionInfo = new String[][] { { "floatie", "ival" } }; testCompilationFailure("testFloatParamComplaint", simpleSchema, FloatParamToGetNiceComplaint.class, new String[] { ".*FloatParamToGetNiceComplaint.* float.* double.*" }, false); } /** * Test that a stored procedure with an aggregate whose parameter is FLOAT * causes a compilation error. * * @throws Exception */ public void testInsertAggregatesOfFloat() throws Exception { String simpleSchema = "create table floatingaggs_input ( alpha float );" + "create table floatingaggs_output ( beta float );" + ""; testCompilationFailure("testInsertAggregatesOfFloat", simpleSchema, InsertAggregatesOfFloat.class, new String[] { ".*InsertAggregatesOfFloat.*Aggregate functions of floating point columns may not be deterministic. We suggest converting to DECIMAL.*" }, false); } /** * Test that a stored procedure with an aggregate of floating point type in * a having clause causes a compilation error. * * @throws Exception */ public void testInsertAggregatesOfFloatInHaving() throws Exception { String simpleSchema = "create table floatingaggs_input ( alpha float );" + "create table floatingaggs_output ( beta float );" + "create table intaggs ( gamma integer );" + ""; testCompilationFailure("testInsertAggregatesOfFloatInHaving", simpleSchema, InsertAggregatesOfFloatInHaving.class, new String[] { ".*InsertAggregatesOfFloatInHaving.*Aggregate functions of floating point columns may not be deterministic. We suggest converting to DECIMAL.*" }, false); } /** * Test that a statement procedure with an aggregate of floating point type * causes a compilation error. * * @throws Exception */ public void testAggregatesOfFloatDDL() throws Exception { String simpleSchema = "create table floatingaggs_input ( alpha float );" + "create table floatingaggs_output ( beta float );" + ""; testCompilationFailure("testAggregatesOfFloatDDL", simpleSchema, new String[] { "InsertAggregatesOfFloatDDL", "insert into floatingaggs_output select sum(alpha) from floatingaggs_input;", }, new String[] { ".*InsertAggregatesOfFloatDDL.*Aggregate functions of floating point columns may not be deterministic. We suggest converting to DECIMAL.*" }, false); } /** * Test that a statement procedure with an aggregate of floating point type * in a subquery in a from clause causes an error. * * @throws Exception */ public void testAggregatesOfFloatInSubquery() throws Exception { String simpleSchema = "create table floatingaggs_input ( alpha float );" + "create table floatingaggs_output ( beta float );" + ""; testCompilationFailure("testAggregatesOfFloatInSubquery", simpleSchema, new String[] { "AggregatesOfFloatInSubquery", "insert into floatingaggs_output select sq.ss from ( select sum(alpha) as ss from floatingaggs_input where alpha > 0.0 order by ss ) as sq;" }, new String[] { ".*AggregatesOfFloatInSubquery.*Aggregate functions of floating point columns may not be deterministic. We suggest converting to DECIMAL.*" }, false); } /** * Test that a statement procedure with an expression which has a * subexpression which is an aggregate of floating point type in a subquery * in a from clause causes an error. * * @throws Exception */ public void testAggregatesOfFloatInComplexSubquery() throws Exception { String simpleSchema = "create table floatingaggs_input ( alpha float );" + "create table floatingaggs_output ( beta float );" + ""; testCompilationFailure("testAggregatesOfFloatInComplexSubquery", simpleSchema, new String[] { "AggregatesOfFloatInComplexSubquery", "insert into floatingaggs_output select sq.ss + 100 from ( select sum(alpha) as ss from floatingaggs_input where alpha > 0.0 order by ss ) as sq;" }, new String[] { ".*AggregatesOfFloatInComplexSubquery.*Aggregate functions of floating point columns may not be deterministic. We suggest converting to DECIMAL.*" }, false); } /** * Test that a statement procedure with an expression which has a * subexpression which is an aggregate whose type is float and whose * expression is more than a column reference and which is also part of a * larger expression causes a compilation error. * * @throws Exception */ public void testAggregatesOfFloatInComplexSubquery2() throws Exception { String simpleSchema = "create table floatingaggs_input ( alpha float );" + "create table floatingaggs_output ( beta float );" + ""; testCompilationFailure("testAggregatesOfFloatInComplexSubquery2", simpleSchema, new String[] { "AggregatesOfFloatInComplexSubquery2", "insert into floatingaggs_output select sq.ss + 100 from ( select sum(alpha + 42) as ss from floatingaggs_input where alpha > 0.0 order by ss ) as sq;" }, new String[] { ".*AggregatesOfFloatInComplexSubquery2.*Aggregate functions of floating point columns may not be deterministic. We suggest converting to DECIMAL.*" }, false); } /** * Test that a statement procedure with an expression which has a * subexpression which is an aggregate whose type is float and is in a * subquery causes a compilation error. * * @throws Exception */ public void testAggregatesOfFloatInLeftOfJoin() throws Exception { String simpleSchema = "create table alpha ( af float );" + "create table beta ( bf float );" + ""; testCompilationFailure("testAggregatesOfFloatInLeftOfJoin", simpleSchema, new String[] { "AggregatesOfFloatInLeftOfJoin", "insert into alpha select lf.ss+rf.ss from (select sum(af) as ss from alpha ) as lf inner join ( select bf as ss from beta ) as rf on true;" }, new String[] { ".*AggregatesOfFloatInLeftOfJoin.*Aggregate functions of floating point columns may not be deterministic. We suggest converting to DECIMAL.*" }, false); } /** * Test that a statement procedure with an expression which has a * subexpression which is an aggregate whose type is float and is in a * subquery causes a compilation error. * * @throws Exception */ public void testAggregatesOfFloatInRightOfJoins() throws Exception { String simpleSchema = "create table alpha ( af float );" + "create table beta ( bf float );" + ""; testCompilationFailure("testAggregatesOfFloatInRightOfJoin", simpleSchema, new String[] { "AggregatesOfFloatInRightOfJoin", "insert into alpha select lf.ss+rf.ss from (select af as ss from alpha ) as lf inner join ( select sum(bf) as ss from beta ) as rf on true;" }, new String[] { ".*AggregatesOfFloatInRightOfJoin.*Aggregate functions of floating point columns may not be deterministic. We suggest converting to DECIMAL.*" }, false); } /** * Test that a statement procedure with an aggregate expression of floating * point type in a subquery which is found in a union expression causes a * compilation error. This can't be a single statement procedure. It has to * be a Java Stored Procedure. This will only be a warning, so the * compilation will pass. */ public void testAggregatesOfFloatInSetops() throws Exception { String simpleSchema = "create table floatingaggs_input ( alpha float );" + "create table floatingaggs_output ( beta float );" + ""; testCompilationFailure("testAggregatesOfFloatInSetops", simpleSchema, InsertAggregatesOfFloatWithSetops.class, new String[] { ".*InsertAggregatesOfFloatWithSetops.*Aggregate functions of floating point columns may not be deterministic. We suggest converting to DECIMAL.*" }, true); } /** * Test that Min does not trigger non-determinism errors. * * @throws Exception */ public void testMinOfFloatIsOk() throws Exception { String simpleSchema = "create table alpha ( af float );" + "create table beta ( bf float );" + ""; testCompilationFailure("testMinOfFloat", simpleSchema, new String[] { "MinOfFloat", "insert into alpha select lf.ss+rf.ss from (select af as ss from alpha ) as lf inner join ( select min(bf) as ss from beta ) as rf on true;" }, null, true); } /** * Test that Max does not trigger non-determinism errors. * * @throws Exception */ public void testMaxOfFloatIsOk() throws Exception { String simpleSchema = "create table alpha ( af float );" + "create table beta ( bf float );" + ""; testCompilationFailure("testMaxOfFloat", simpleSchema, new String[] { "MaxOfFloat", "insert into alpha select lf.ss+rf.ss from (select af as ss from alpha ) as lf inner join ( select max(bf) as ss from beta ) as rf on true;" }, null, true); } /** * Test that we haven't broken the obvious good case. * * @throws Exception */ public void testGoodInsert() throws Exception { String simpleSchema = "create table floatingaggs_input ( alpha float );" + "create table floatingaggs_output ( beta float );" + ""; testCompilationFailure("testAggregatesOfFloatInComplexSubquery2", simpleSchema, new String[] { "AggregatesOfFloatInComplexSubquery2", "insert into floatingaggs_output select alpha from floatingaggs_input;" }, null, true); } public void testSimple() throws Exception { String simpleSchema = "create table blah (" + "ival bigint default 0 not null, " + "sval varchar(255) not null" + ");" + "create table indexed_replicated_blah (" + "ival bigint default 0 not null, " + "sval varchar(255) not null, " + "PRIMARY KEY(ival)" + ");" + "create table indexed_partitioned_blah (" + "ival bigint default 0 not null, " + "sval varchar(255) not null, " + "PRIMARY KEY(ival)" + ");" + "create table floatingaggs_input (" + "alpha float" + ");" + "create table floatingaggs_output (" + "beta float" + ");" + ""; VoltProjectBuilder builder = new VoltProjectBuilder(); ByteArrayOutputStream capturer = new ByteArrayOutputStream(); PrintStream capturing = new PrintStream(capturer); builder.setCompilerDebugPrintStream(capturing); builder.addLiteralSchema(simpleSchema); builder.addPartitionInfo("blah", "ival"); builder.addPartitionInfo("indexed_partitioned_blah", "ival"); // Note: indexed_replicated_blah is left as a replicated table. builder.addStmtProcedure("Insert", // Include lots of filthy whitespace to test output cleanup. "insert into\t \tblah values\n\n(? \t ,\t\t\t?) ;", null); builder.addProcedures(NondeterministicROProc.class); builder.addProcedures(NondeterministicRWProc.class); builder.addProcedures(DeterministicRONonSeqProc.class); builder.addProcedures(DeterministicROSeqProc.class); builder.addProcedures(DeterministicRWProc1.class); builder.addProcedures(DeterministicRWProc2.class); builder.addProcedures(ProcSPcandidate1.class); builder.addProcedures(ProcSPcandidate2.class); builder.addProcedures(ProcSPcandidate3.class); builder.addProcedures(ProcSPcandidate4.class); builder.addProcedures(ProcSPcandidate5.class); builder.addProcedures(ProcSPcandidate6.class); builder.addProcedures(ProcSPcandidate7.class); builder.addProcedures(ProcSPNoncandidate1.class); builder.addProcedures(ProcSPNoncandidate2.class); builder.addProcedures(ProcSPNoncandidate3.class); builder.addProcedures(ProcSPNoncandidate4.class); builder.addProcedures(ProcSPNoncandidate5.class); builder.addProcedures(ProcSPNoncandidate6.class); builder.addStmtProcedure("StmtSPcandidate1", "select count(*) from blah where ival = ?", null); builder.addStmtProcedure("StmtSPcandidate2", "select count(*) from blah where ival = 12345678", null); builder.addStmtProcedure("StmtSPcandidate3", "select count(*) from blah, indexed_replicated_blah " + "where indexed_replicated_blah.sval = blah.sval and blah.ival = 12345678", null); builder.addStmtProcedure("StmtSPcandidate4", "select count(*) from blah, indexed_replicated_blah " + "where indexed_replicated_blah.sval = blah.sval and blah.ival = abs(1)+1", null); builder.addStmtProcedure("StmtSPcandidate5", "select count(*) from blah where sval = ? and ival = 12345678", null); builder.addStmtProcedure("StmtSPcandidate6", "select count(*) from blah where sval = ? and ival = ?", null); builder.addStmtProcedure("StmtSPNoncandidate1", "select count(*) from blah where sval = ?", null); builder.addStmtProcedure("StmtSPNoncandidate2", "select count(*) from blah where sval = '12345678'", null); builder.addStmtProcedure("StmtSPNoncandidate3", "select count(*) from indexed_replicated_blah where ival = ?", null); builder.addStmtProcedure("FullIndexScan", "select ival, sval from indexed_replicated_blah", null); boolean success = builder.compile(Configuration.getPathToCatalogForTest("annotations.jar")); assert(success); String captured = capturer.toString("UTF-8"); System.out.print("\n" + ":----------------------------------------------------------------------:\n" + ": Start of captured output\n" + ":----------------------------------------------------------------------:\n"); System.out.println(captured); System.out.print("\n" + ":----------------------------------------------------------------------:\n" + ": End of captured output\n" + ":----------------------------------------------------------------------:\n"); String[] lines = captured.split("\n"); assertTrue(foundLineMatching(lines, ".*\\[READ].*NondeterministicROProc.*")); assertTrue(foundLineMatching(lines, ".*\\[READ].*NondeterministicROProc.*")); assertTrue(foundLineMatching(lines, ".*\\[READ].*DeterministicRONonSeqProc.*")); assertTrue(foundLineMatching(lines, ".*\\[READ].*DeterministicROSeqProc.*")); assertTrue(foundLineMatching(lines, ".*\\[WRITE].*Insert.*")); assertTrue(foundLineMatching(lines, ".*\\[WRITE].*NondeterministicRWProc.*")); assertTrue(foundLineMatching(lines, ".*\\[WRITE].*DeterministicRWProc.*")); assertTrue(foundLineMatching(lines, ".*\\[TABLE SCAN].*select ival, sval from indexed_replicated_blah.*")); assertEquals(1, countLinesMatching(lines, ".*\\[NDC].*NDC=true.*")); assertFalse(foundLineMatching(lines, ".*\\[NDC].*NDC=false.*")); assertFalse(foundLineMatching(lines, ".*\\[WRITE].*NondeterministicROProc.*")); assertFalse(foundLineMatching(lines, ".*\\[WRITE].*DeterministicRONonSeqProc.*")); assertFalse(foundLineMatching(lines, ".*\\[WRITE].*DeterministicROSeqProc.*")); assertFalse(foundLineMatching(lines, ".*\\[TABLE SCAN].*DeterministicRONonSeqProc.*")); assertFalse(foundLineMatching(lines, ".*\\[READ].*Insert.*")); assertFalse(foundLineMatching(lines, ".*\\[READ].*BLAH.insert.*")); assertFalse(foundLineMatching(lines, ".*\\[TABLE SCAN].*Insert.*")); assertFalse(foundLineMatching(lines, ".*\\[TABLE SCAN].*BLAH.insert.*")); assertFalse(foundLineMatching(lines, ".*\\[READ].*NondeterministicRWProc.*")); assertFalse(foundLineMatching(lines, ".*\\[READ].*DeterministicRWProc.*")); assertFalse(foundLineMatching(lines, ".*DeterministicRWProc.*non-deterministic.*")); // test SP improvement warnings String absPattern = "abs\\s*\\(\\s*1\\s*\\)\\s*\\+\\s*1"; // Pattern for abs(1) + 1, with whitespace between tokens String pattern0 = "adding a \\'PARTITION ON TABLE BLAH COLUMN IVAL PARAMETER 0\\' .*"; String pattern1 = "adding a \\'PARTITION ON TABLE BLAH COLUMN IVAL PARAMETER 1\\' .*"; assertEquals(4, countLinesMatching(lines, ".*" + pattern0 + ".*")); // StmtSPcandidates 1,2,3,4 assertEquals(2, countLinesMatching(lines, ".*\\[StmtSPcandidate.].*12345678.*" + pattern0)); // 2, 3 assertEquals(1, countLinesMatching(lines, ".*\\[StmtSPcandidate.].*" + absPattern + ".*" + pattern0)); // just 4 assertEquals(1, countLinesMatching(lines, ".*\\[StmtSPcandidate.].*12345678.*" + pattern1)); // just 5 assertEquals(2, countLinesMatching(lines, ".*\\[StmtSPcandidate.].*" + pattern1)); // 5, 6 assertEquals(1, countLinesMatching(lines, ".*\\[ProcSPcandidate.\\.class].*designating parameter 0 .*")); // ProcSPcandidate 1 assertEquals(4, countLinesMatching(lines, ".*\\[ProcSPcandidate.\\.class].*added parameter .*87654321.*")); // 2, 3, 5, 6 assertEquals(1, countLinesMatching(lines, ".*\\[ProcSPcandidate.\\.class].*added parameter .*" + absPattern + ".*")); // just 4 assertEquals(1, countLinesMatching(lines, ".*\\[ProcSPcandidate.\\.class].*designating parameter 1 .*")); // 7 // Non-candidates disqualify themselves by various means. assertEquals(0, countLinesMatching(lines, ".*\\[SPNoncandidate.].*partitioninfo=BLAH\\.IVAL:0.*")); assertEquals(0, countLinesMatching(lines, ".*\\[SPNoncandidate.].*partitioninfo=BLAH\\.IVAL:1.*")); assertEquals(0, countLinesMatching(lines, ".*\\[ProcSPNoncandidate.\\.class].* parameter .*")); assertEquals(0, countLinesMatching(lines, ".*\\[ProcSPNoncandidate.\\.class].* parameter .*")); // test prettying-up of statements in feedback output. ("^[^ ].*") is used to confirm that the (symbol-prefixed) log statements contain the original ugliness. // While ("^ .*") is used to confirm that the (space-prefixed) feedback to the user is cleaned up. assertTrue(foundLineMatching(lines, "^[^ ].*values.* .*")); // includes 2 embedded or trailing spaces. assertFalse(foundLineMatching(lines, "^ .*values.* .*")); // includes 2 embedded or trailing spaces. assertTrue(foundLineMatching(lines, "^[^ ].*nsert.* .*values.*")); // includes 2 embedded spaces. assertFalse(foundLineMatching(lines, "^ .*nsert.* .*values.*")); // includes 2 embedded spaces. assertTrue(foundLineMatching(lines, "^[^ ].*values.*\u0009.*")); // that's an embedded or trailing unicode tab. assertFalse(foundLineMatching(lines, "^ .*values.*\u0009.*")); // that's an embedded or trailing unicode tab. assertTrue(foundLineMatching(lines, "^[^ ].*nsert.*\u0009.*values.*")); // that's an embedded unicode tab. assertFalse(foundLineMatching(lines, "^ .*nsert.*\u0009.*values.*")); // that's an embedded unicode tab. assertTrue(foundLineMatching(lines, "^[^ ].*values.*\\s\\s.*")); // includes 2 successive embedded or trailing whitespace of any kind assertFalse(foundLineMatching(lines, "^ .*values.*\\s\\s.*")); // includes 2 successive embedded or trailing whitespace of any kind assertTrue(foundLineMatching(lines, "^[^ ].*nsert.*\\s\\s.*values.*")); // includes 2 successive embedded whitespace of any kind assertFalse(foundLineMatching(lines, "^ .*nsert.*\\s\\s.*values.*")); // includes 2 successive embedded whitespace of any kind } private boolean foundLineMatching(String[] lines, String pattern) { for (String string : lines) { if (string.matches(pattern)) { return true; } } return false; } private int countLinesMatching(String[] lines, String pattern) { int count = 0; for (String string : lines) { if (string.matches(pattern)) { ++count; } } return count; } }