darcy@1554: /* darcy@1554: * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. darcy@1554: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. darcy@1554: * darcy@1554: * This code is free software; you can redistribute it and/or modify it darcy@1554: * under the terms of the GNU General Public License version 2 only, as darcy@1554: * published by the Free Software Foundation. darcy@1554: * darcy@1554: * This code is distributed in the hope that it will be useful, but WITHOUT darcy@1554: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or darcy@1554: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License darcy@1554: * version 2 for more details (a copy is included in the LICENSE file that darcy@1554: * accompanied this code). darcy@1554: * darcy@1554: * You should have received a copy of the GNU General Public License version darcy@1554: * 2 along with this work; if not, write to the Free Software Foundation, darcy@1554: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. darcy@1554: * darcy@1554: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA darcy@1554: * or visit www.oracle.com if you need additional information or have any darcy@1554: * questions. darcy@1554: */ darcy@1554: darcy@1554: /** darcy@1554: * @test darcy@1554: * @bug 7195131 darcy@1554: * @author sogoel darcy@1554: * @summary Combo test for all possible combinations for Target values darcy@1554: * @build Helper darcy@1554: * @compile TargetAnnoCombo.java TestCaseGenerator.java darcy@1554: * @run main TargetAnnoCombo darcy@1554: */ darcy@1554: darcy@1554: import java.io.IOException; darcy@1554: import java.util.ArrayList; darcy@1554: import java.util.Arrays; darcy@1554: import java.util.HashSet; darcy@1554: import java.util.Set; darcy@1554: import javax.tools.Diagnostic; darcy@1554: import javax.tools.DiagnosticCollector; darcy@1554: import javax.tools.JavaFileObject; darcy@1554: darcy@1554: /* darcy@1554: * TargetAnnoCombo gets a list of test case numbers using TestCaseGenerator. darcy@1554: * For each of the test case number, @Target sets for base and container annotations darcy@1554: * are determined, source files are generated, compiled, and the result is verified darcy@1554: * based on if the @Target set for base and container is a positive or negative combination. darcy@1554: * darcy@1554: * @Target sets for base and container annotations are determined using a bit mapping of darcy@1554: * 10 ElementType enum constants defined in JDK8. darcy@1554: * darcy@1554: * Bit Target value darcy@1554: * 0 "ElementType.ANNOTATION_TYPE" darcy@1554: * 1 "ElementType.CONSTRUCTOR" darcy@1554: * 2 "ElementType.FIELD" darcy@1554: * 3 "ElementType.LOCAL_VARIABLE" darcy@1554: * 4 "ElementType.METHOD" darcy@1554: * 5 "ElementType.TYPE" darcy@1554: * 6 "ElementType.PARAMETER" darcy@1554: * 7 "ElementType.PACKAGE" darcy@1554: * 8 "ElementType.TYPE_USE" darcy@1554: * 9 "ElementType.TYPE_PARAMETER" darcy@1554: * darcy@1554: * Group 1: darcy@1554: * 20 bits mapping, representing a test case number, is used for all target set darcy@1554: * combinations ( 0 to 1048575 ) including empty @Target sets => @Target({}). darcy@1554: * From this 20 bits, 10 bits are for base followed by 10 bits for container darcy@1554: * where each bit maps to an ElementType enum constant defined in JDK8. darcy@1554: * darcy@1554: * Examples: darcy@1554: * Test case number: 4, binary: 100 => container=100, base=[], container=["ElementType.FIELD"] darcy@1554: * Test case number: 1003575, binary: 11110101000000110111 => base=1111010100, container=0000110111; darcy@1554: * base=["ElementType.PARAMETER", "ElementType.TYPE_USE", "ElementType.METHOD", "ElementType.FIELD", "ElementType.PACKAGE", "ElementType.TYPE_PARAMETER"], darcy@1554: * container=["ElementType.TYPE", "ElementType.METHOD", "ElementType.ANNOTATION_TYPE", "ElementType.CONSTRUCTOR", "ElementType.FIELD"] darcy@1554: * darcy@1554: * In the following groups, no @Target set is represented by null. darcy@1554: * Group 2: darcy@1554: * @Target is not defined on base. darcy@1554: * Target sets for container are determined using the 10-bit binary number darcy@1554: * resulting in 1024 test cases, mapping them to test case numbers from darcy@1554: * 1048576 to (1048576 + 1023) => 1048576 to 1049599. darcy@1554: * darcy@1554: * Example: darcy@1554: * Test case number: 1048587 => 1048587 - 1048576 = test case 11 in Group 2, binary: 1011 => darcy@1554: * base = null, darcy@1554: * container = ["ElementType.ANNOTATION_TYPE","ElementType.CONSTRUCTOR","ElementType.LOCAL_VARIABLE"] darcy@1554: * darcy@1554: * Group 3: darcy@1554: * @Target is not defined on container darcy@1554: * Target sets for base are determined using the 10-bit binary number darcy@1554: * resulting in 1024 test cases, mapping them to test case numbers from darcy@1554: * 1049600 to (1049600 + 1023) => 1049600 to 1050623. darcy@1554: * darcy@1554: * Example: darcy@1554: * Test case number: 1049708 => 1049708 - 1049600 = test case 108 in Group 3, binary: 1101100 => darcy@1554: * base = ["ElementType.FIELD", "ElementType.LOCAL_VARIABLE", "ElementType.TYPE", "ElementType.PARAMETER"], darcy@1554: * container = null darcy@1554: * darcy@1554: * For the above group, test case number: 1049855 gives compiler error, JDK-8006547 filed darcy@1554: * darcy@1554: * Group 4: darcy@1554: * @Target not defined for both base and container annotations. darcy@1554: * darcy@1554: * This is the last test and corresponds to test case number 1050624. base=null, container=null darcy@1554: * darcy@1554: * Examples to run this test: darcy@1554: * 1. Run a specific test case number: darcy@1554: * ${JTREG} -DTestCaseNum=10782 -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java darcy@1554: * 2. Run specific number of tests: darcy@1554: * ${JTREG} -DNumberOfTests=4 -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java darcy@1554: * 3. Run specific number of tests with a seed: darcy@1554: * ${JTREG} -DNumberOfTests=4 -DTestSeed=-972894659 -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java darcy@1554: * 4. Run tests in default mode (number of tests = 1000): darcy@1554: * ${JTREG} -DTestMode=DEFAULT -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java darcy@1554: * 5. Run all tests (FULL mode): darcy@1554: * ${JTREG} -DTestMode=FULL -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java darcy@1554: * darcy@1554: */ darcy@1554: darcy@1554: public class TargetAnnoCombo { darcy@1554: int errors = 0; darcy@1554: static final String TESTPKG = "testpkg"; darcy@1554: /* darcy@1554: * Set it to true to get more debug information including base and darcy@1554: * container target sets for a given test case number darcy@1554: */ darcy@1554: static final boolean DEBUG = false; darcy@1554: darcy@1554: // JDK 5/6/7/8 Targets darcy@1554: static final String[] targetVals = {"ElementType.ANNOTATION_TYPE", darcy@1554: "ElementType.CONSTRUCTOR", "ElementType.FIELD", darcy@1554: "ElementType.LOCAL_VARIABLE", "ElementType.METHOD", darcy@1554: "ElementType.TYPE", "ElementType.PARAMETER", darcy@1554: "ElementType.PACKAGE", "ElementType.TYPE_USE", darcy@1554: "ElementType.TYPE_PARAMETER"}; darcy@1554: darcy@1554: // TYPE_USE and TYPE_PARAMETER (added in JDK8) are not part of default Target set darcy@1554: static final int DEFAULT_TARGET_CNT = 8; darcy@1554: darcy@1554: public static void main(String args[]) throws Exception { darcy@1554: darcy@1554: /* maxTestNum = (base and container combinations of targetVals elems [0 - 1048575 combos]) darcy@1554: * + (combinations where base or container has no Target [1024 combos]) darcy@1554: * + (no -1 even though 1st test is number 0 as last test is where both darcy@1554: * base and container have no target) darcy@1554: */ darcy@1554: darcy@1554: int maxTestNum = (int)Math.pow(2, 2*targetVals.length) + 2*(int)Math.pow(2, targetVals.length); darcy@1554: TestCaseGenerator tcg = new TestCaseGenerator(maxTestNum); darcy@1554: TargetAnnoCombo tac = new TargetAnnoCombo(); darcy@1554: darcy@1554: int testCtr = 0; darcy@1554: int testCase = -1; darcy@1554: while ( (testCase=tcg.getNextTestCase()) != -1 ) { darcy@1554: tac.executeTestCase(testCase, maxTestNum); darcy@1554: testCtr++; darcy@1554: } darcy@1554: darcy@1554: System.out.println("Total tests run: " + testCtr); darcy@1554: if (tac.errors > 0) darcy@1554: throw new Exception(tac.errors + " errors found"); darcy@1554: } darcy@1554: darcy@1554: /* darcy@1554: * For given testCase, determine the base and container annotation Target sets, darcy@1554: * get if testCase should compile, get test source file(s), get compilation result and verify. darcy@1554: * darcy@1554: */ darcy@1554: private void executeTestCase(int testCase, int maxTestNum) { darcy@1554: darcy@1554: // Determine base and container annotation Target sets for the testCase darcy@1554: Set baseAnnoTarget = null; darcy@1554: Set conAnnoTarget = null; darcy@1554: darcy@1554: //Number of base and container combinations [0 - 1048575 combos] darcy@1554: int baseContCombos = (int)Math.pow(2, 2*targetVals.length); darcy@1554: //Number of either base or container combinations when one of them has no @Target [1024 combos] darcy@1554: int targetValsCombos = (int)Math.pow(2, targetVals.length); darcy@1554: darcy@1554: if (testCase >= baseContCombos) { darcy@1554: //Base annotation do not have @Target darcy@1554: if (testCase < baseContCombos + targetValsCombos) { darcy@1554: baseAnnoTarget = null; darcy@1554: conAnnoTarget = getSetFromBitVec(Integer.toBinaryString(testCase - baseContCombos)); darcy@1554: } else if (testCase < baseContCombos + 2*targetValsCombos) { darcy@1554: //Container annotation do not have @Target darcy@1554: baseAnnoTarget = getSetFromBitVec(Integer.toBinaryString(testCase - baseContCombos - targetValsCombos)); darcy@1554: conAnnoTarget = null; darcy@1554: } else { darcy@1554: //Both Base and Container annotation do not have @Target darcy@1554: baseAnnoTarget = null; darcy@1554: conAnnoTarget = null; darcy@1554: } darcy@1554: } else { darcy@1554: //TestCase number is represented as 10-bits for base followed by container bits darcy@1554: String bin = Integer.toBinaryString(testCase); darcy@1554: String base="", cont=bin; darcy@1554: if (bin.length() > targetVals.length){ darcy@1554: base = bin.substring(0, bin.length() - targetVals.length); darcy@1554: cont = bin.substring(bin.length() - targetVals.length,bin.length()); darcy@1554: } darcy@1554: baseAnnoTarget = getSetFromBitVec(base); darcy@1554: conAnnoTarget = getSetFromBitVec(cont); darcy@1554: } darcy@1554: darcy@1554: debugPrint("Test case number = " + testCase + " => binary = " + Integer.toBinaryString(testCase)); darcy@1554: debugPrint(" => baseAnnoTarget = " + baseAnnoTarget); darcy@1554: debugPrint(" => containerAnnoTarget = " + conAnnoTarget); darcy@1554: darcy@1554: // Determine if a testCase should compile or not darcy@1554: String className = "TC" + testCase; darcy@1554: boolean shouldCompile = isValidSubSet(baseAnnoTarget, conAnnoTarget); darcy@1554: darcy@1554: // Get test source file(s) darcy@1554: Iterable files = getFileList(className, baseAnnoTarget, darcy@1554: conAnnoTarget, shouldCompile); darcy@1554: darcy@1554: // Get result of compiling test src file(s) darcy@1554: boolean result = getCompileResult(className, shouldCompile, files); darcy@1554: darcy@1554: // List test src code if test fails darcy@1554: if(!result) { darcy@1554: System.out.println("FAIL: Test " + testCase); darcy@1554: try { darcy@1554: for (JavaFileObject f: files) { darcy@1554: System.out.println("File: " + f.getName() + "\n" + f.getCharContent(true)); darcy@1554: } darcy@1554: } catch (IOException ioe) { darcy@1554: System.out.println("Exception: " + ioe); darcy@1554: } darcy@1554: } else { darcy@1554: debugPrint("PASS: Test " + testCase); darcy@1554: } darcy@1554: } darcy@1554: darcy@1554: // Get a Set based on bits that are set to 1 darcy@1554: public Set getSetFromBitVec(String bitVec) { darcy@1554: Set ret = new HashSet<>(); darcy@1554: char[] bit = bitVec.toCharArray(); darcy@1554: for (int i=bit.length-1, j=0; i>=0; i--, j++){ darcy@1554: if (bit[i] == '1') { darcy@1554: ret.add(targetVals[j]); darcy@1554: } darcy@1554: } darcy@1554: return ret; darcy@1554: } darcy@1554: darcy@1554: // Compile the test source file(s) and return test result darcy@1554: private boolean getCompileResult(String className, boolean shouldCompile, darcy@1554: Iterable files) { darcy@1554: darcy@1554: DiagnosticCollector diagnostics = darcy@1554: new DiagnosticCollector(); darcy@1554: Helper.compileCode(diagnostics, files); darcy@1554: darcy@1554: // Test case pass or fail darcy@1554: boolean ok = false; darcy@1554: darcy@1554: String errMesg = ""; darcy@1554: int numDiags = diagnostics.getDiagnostics().size(); darcy@1554: darcy@1554: if (numDiags == 0) { darcy@1554: if (shouldCompile) { darcy@1554: debugPrint("Test passed, compiled as expected."); darcy@1554: ok = true; darcy@1554: } else { darcy@1554: errMesg = "Test failed, compiled unexpectedly."; darcy@1554: ok = false; darcy@1554: } darcy@1554: } else { darcy@1554: if (shouldCompile) { darcy@1554: // did not compile darcy@1554: errMesg = "Test failed, did not compile."; darcy@1554: ok = false; darcy@1554: } else { darcy@1554: // Error in compilation as expected darcy@1554: String expectedErrKey = "compiler.err.invalid.repeatable." + darcy@1554: "annotation.incompatible.target"; darcy@1554: for (Diagnostic d : diagnostics.getDiagnostics()) { darcy@1554: if((d.getKind() == Diagnostic.Kind.ERROR) && darcy@1554: d.getCode().contains(expectedErrKey)) { darcy@1554: // Error message as expected darcy@1554: debugPrint("Error message as expected."); darcy@1554: ok = true; darcy@1554: break; darcy@1554: } else { darcy@1554: // error message is incorrect darcy@1554: ok = false; darcy@1554: } darcy@1554: } darcy@1554: if (!ok) { darcy@1554: errMesg = "Incorrect error received when compiling " + darcy@1554: className + ", expected: " + expectedErrKey; darcy@1554: } darcy@1554: } darcy@1554: } darcy@1554: darcy@1554: if(!ok) { darcy@1554: error(errMesg); darcy@1554: for (Diagnostic d : diagnostics.getDiagnostics()) darcy@1554: System.out.println(" Diags: " + d); darcy@1554: } darcy@1554: return ok; darcy@1554: } darcy@1554: darcy@1554: private void debugPrint(String string) { darcy@1554: if(DEBUG) darcy@1554: System.out.println(string); darcy@1554: } darcy@1554: darcy@1554: // Create src code and corresponding JavaFileObjects darcy@1554: private Iterable getFileList(String className, darcy@1554: Set baseAnnoTarget, Set conAnnoTarget, darcy@1554: boolean shouldCompile) { darcy@1554: darcy@1554: String srcContent = ""; darcy@1554: String pkgInfoContent = ""; darcy@1554: String template = Helper.template; darcy@1554: String baseTarget = "", conTarget = ""; darcy@1554: darcy@1554: String target = Helper.ContentVars.TARGET.getVal(); darcy@1554: if(baseAnnoTarget != null) { darcy@1554: baseTarget = target.replace("#VAL", baseAnnoTarget.toString()) darcy@1554: .replace("[", "{").replace("]", "}"); darcy@1554: } darcy@1554: if(conAnnoTarget != null) { darcy@1554: conTarget = target.replace("#VAL", conAnnoTarget.toString()) darcy@1554: .replace("[", "{").replace("]", "}"); darcy@1554: } darcy@1554: darcy@1554: String annoData = Helper.ContentVars.IMPORTSTMTS.getVal() + darcy@1554: conTarget + darcy@1554: Helper.ContentVars.CONTAINER.getVal() + darcy@1554: baseTarget + darcy@1554: Helper.ContentVars.REPEATABLE.getVal() + darcy@1554: Helper.ContentVars.BASE.getVal(); darcy@1554: darcy@1554: JavaFileObject pkgInfoFile = null; darcy@1554: darcy@1554: /* darcy@1554: * If shouldCompile = true and no @Target is specified for container annotation, darcy@1554: * then all 8 ElementType enum constants are applicable as targets for darcy@1554: * container annotation. darcy@1554: */ darcy@1554: if(shouldCompile && conAnnoTarget == null) { darcy@1554: //conAnnoTarget = new HashSet(Arrays.asList(targetVals)); darcy@1554: conAnnoTarget = getDefaultTargetSet(); darcy@1554: } darcy@1554: darcy@1554: if(shouldCompile) { darcy@1554: boolean isPkgCasePresent = new ArrayList(conAnnoTarget).contains("ElementType.PACKAGE"); darcy@1554: String repeatableAnno = Helper.ContentVars.BASEANNO.getVal() + " " + Helper.ContentVars.BASEANNO.getVal(); darcy@1554: for(String s: conAnnoTarget) { darcy@1554: s = s.replace("ElementType.",""); darcy@1554: String replaceStr = "/*"+s+"*/"; darcy@1554: if(s.equalsIgnoreCase("PACKAGE")) { darcy@1554: //Create packageInfo file darcy@1554: String pkgInfoName = TESTPKG + "." + "package-info"; darcy@1554: pkgInfoContent = repeatableAnno + "\npackage " + TESTPKG + ";" + annoData; darcy@1554: pkgInfoFile = Helper.getFile(pkgInfoName, pkgInfoContent); darcy@1554: } else { darcy@1554: template = template.replace(replaceStr, repeatableAnno); darcy@1554: //srcContent = template.replace("#ClassName",className); darcy@1554: if(!isPkgCasePresent) { darcy@1554: srcContent = template.replace("/*ANNODATA*/", annoData).replace("#ClassName",className); darcy@1554: } else { darcy@1554: replaceStr = "/*PACKAGE*/"; darcy@1554: srcContent = template.replace(replaceStr, "package " + TESTPKG + ";") darcy@1554: .replace("#ClassName", className); darcy@1554: } darcy@1554: } darcy@1554: } darcy@1554: } else { darcy@1554: // For invalid cases, compilation should fail at declaration site darcy@1554: template = "class #ClassName {}"; darcy@1554: srcContent = annoData + template.replace("#ClassName",className); darcy@1554: } darcy@1554: JavaFileObject srcFile = Helper.getFile(className, srcContent); darcy@1554: Iterable files = null; darcy@1554: if(pkgInfoFile != null) darcy@1554: files = Arrays.asList(pkgInfoFile,srcFile); darcy@1554: else darcy@1554: files = Arrays.asList(srcFile); darcy@1554: return files; darcy@1554: } darcy@1554: darcy@1554: private Set getDefaultTargetSet() { darcy@1554: Set defaultSet = new HashSet<>(); darcy@1554: int ctr = 0; darcy@1554: for(String s : targetVals) { darcy@1554: if(ctr++ < DEFAULT_TARGET_CNT) { darcy@1554: defaultSet.add(s); darcy@1554: } darcy@1554: } darcy@1554: return defaultSet; darcy@1554: } darcy@1554: darcy@1554: private boolean isValidSubSet(Set baseAnnoTarget, Set conAnnoTarget) { darcy@1554: /* darcy@1554: * RULE 1: conAnnoTarget should be a subset of baseAnnoTarget darcy@1554: * RULE 2: For empty @Target ({}) - annotation cannot be applied anywhere darcy@1554: * - Empty sets for both is valid darcy@1554: * - Empty baseTarget set is invalid with non-empty conTarget set darcy@1554: * - Non-empty baseTarget set is valid with empty conTarget set darcy@1554: * RULE 3: For no @Target specified - annotation can be applied to any JDK 7 targets darcy@1554: * - No @Target for both is valid darcy@1554: * - No @Target for baseTarget set with @Target conTarget set is valid darcy@1554: * - @Target for baseTarget set with no @Target for conTarget is invalid darcy@1554: */ darcy@1554: darcy@1554: darcy@1554: /* If baseAnno has no @Target, Foo can be either applied to @Target specified for container annotation darcy@1554: * else will be applicable for all default targets if no @Target is present for container annotation. darcy@1554: * In both cases, the set will be a valid set with no @Target for base annotation darcy@1554: */ darcy@1554: if(baseAnnoTarget == null) { darcy@1554: if(conAnnoTarget == null) return true; darcy@1554: return !(conAnnoTarget.contains("ElementType.TYPE_USE") || conAnnoTarget.contains("ElementType.TYPE_PARAMETER")); darcy@1554: } darcy@1554: darcy@1554: Set tempBaseSet = new HashSet<>(baseAnnoTarget); darcy@1554: // If BaseAnno has TYPE, then ANNOTATION_TYPE is allowed by default darcy@1554: if(baseAnnoTarget.contains("ElementType.TYPE")) { darcy@1554: tempBaseSet.add("ElementType.ANNOTATION_TYPE"); darcy@1554: } darcy@1554: darcy@1554: /* darcy@1554: * If containerAnno has no @Target, only valid case if baseAnnoTarget has all targets defined darcy@1554: * else invalid set darcy@1554: */ darcy@1554: if(conAnnoTarget == null) { darcy@1554: return (tempBaseSet.containsAll(getDefaultTargetSet())); darcy@1554: } darcy@1554: darcy@1554: // At this point, neither conAnnoTarget or baseAnnoTarget are null darcy@1554: if(conAnnoTarget.size() == 0) return true; darcy@1554: darcy@1554: // At this point, conAnnoTarget is non-empty darcy@1554: if (baseAnnoTarget.size() == 0) return false; darcy@1554: darcy@1554: // At this point, neither conAnnoTarget or baseAnnoTarget are empty darcy@1554: return tempBaseSet.containsAll(conAnnoTarget); darcy@1554: } darcy@1554: darcy@1554: void error(String msg) { darcy@1554: System.out.println("ERROR: " + msg); darcy@1554: errors++; darcy@1554: } darcy@1554: darcy@1554: // Lists the start and end range for the given set of target vals darcy@1554: void showGroups() { darcy@1554: //Group 1: All target set combinations ( 0 to 1048575 ) including empty @Target sets => @Target({}) darcy@1554: int grpEnd1 = (int)Math.pow(2, 2*targetVals.length) - 1; darcy@1554: System.out.println("[Group 1]: 0 - " + grpEnd1); darcy@1554: darcy@1554: //Group 2: @Target not defined for base annotation ( 1048576 - 1049599 ). darcy@1554: System.out.print("[Group 2]: " + (grpEnd1 + 1) + " - "); darcy@1554: int grpEnd2 = grpEnd1 + 1 + (int)Math.pow(2, targetVals.length) - 1; darcy@1554: System.out.println(grpEnd2); darcy@1554: darcy@1554: //Group 3: @Target not defined for container annotation ( 1049600 - 1050623 ). darcy@1554: System.out.print("[Group 3]: " + (grpEnd2 + 1) + " - "); darcy@1554: int grpEnd3 = grpEnd2 + 1 + (int)Math.pow(2, targetVals.length) - 1; darcy@1554: System.out.println(grpEnd3); darcy@1554: darcy@1554: //Group 4: @Target not defined for both base and container annotations ( 1050624 ). darcy@1554: System.out.println("[Group 4]: " + (grpEnd3 + 1)); darcy@1554: } darcy@1554: }