8042947: Implement classfile tests for AnnotationDefault attribute
authoraeremeev
Mon, 20 Apr 2015 12:45:41 +0300
changeset 30010 319ded3e820a
parent 29963 ac3f5a39d4ff
child 30011 34df75eba76b
8042947: Implement classfile tests for AnnotationDefault attribute Reviewed-by: jjg, shurailine, anazarov
langtools/test/tools/javac/classfiles/attributes/AnnotationDefault/AnnotationDefault.java.template
langtools/test/tools/javac/classfiles/attributes/AnnotationDefault/AnnotationDefaultTest.java
langtools/test/tools/javac/classfiles/attributes/AnnotationDefault/AnnotationDefaultVerifier.java
langtools/test/tools/javac/classfiles/attributes/AnnotationDefault/ExpectedValues.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/classfiles/attributes/AnnotationDefault/AnnotationDefault.java.template	Mon Apr 20 12:45:41 2015 +0300
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.annotation.*;
+
+%REPEATABLE%
+@Retention(RetentionPolicy.%POLICY%)
+public @interface AnnotationDefault {
+    @ExpectedValues(tag = 'Z', name = "booleanDefault", values = "1")
+    boolean booleanDefault() default true;
+
+    @ExpectedValues(tag = 'C', name = "charDefault", values = "1")
+    char charDefault() default 1;
+
+    @ExpectedValues(tag = 'B', name = "byteDefault", values = "1")
+    byte byteDefault() default 1;
+
+    @ExpectedValues(tag = 'S', name = "shortDefault", values = "1")
+    short shortDefault() default 1;
+
+    @ExpectedValues(tag = 'I', name = "intDefault", values = "1")
+    int intDefault() default 1;
+
+    @ExpectedValues(tag = 'J', name = "longDefault", values = "1")
+    long longDefault() default 1;
+
+    @ExpectedValues(tag = 'F', name = "floatDefault", values = "1.0")
+    float floatDefault() default 1.0f;
+
+    @ExpectedValues(tag = 'D', name = "doubleDefault", values = "1.0")
+    double doubleDefault() default 1.0;
+
+    @ExpectedValues(tag = 's', name = "stringDefault", values = "DEFAULT_VALUE")
+    String stringDefault() default "DEFAULT_VALUE";
+
+    @ExpectedValues(tag = 'e', name = "enumDefault", values = {"LAnnotationDefault$DefaultValues;", "VALUE1"})
+    DefaultValues enumDefault() default DefaultValues.VALUE1;
+
+    @ExpectedValues(tag = 'c', name = "clazzDefault1", values = "V")
+    Class<?> clazzDefault1() default void.class;
+
+    @ExpectedValues(tag = 'c', name = "clazzDefault2", values = "Ljava/lang/Void;")
+    Class<?> clazzDefault2() default Void.class;
+
+    @ExpectedValues(tag = '[', name = "arrayDefault1", values = {"1", "2", "3"})
+    int[] arrayDefault1() default {1, 2, 3};
+
+    @ExpectedValues(tag = '[', name = "arrayDefault2", values = {"DEFAULT_VALUE_1", "DEFAULT_VALUE_2", "DEFAULT_VALUE_3"})
+    String[] arrayDefault2() default {"DEFAULT_VALUE_1", "DEFAULT_VALUE_2", "DEFAULT_VALUE_3"};
+
+    @ExpectedValues(tag = '[', name = "arrayOfEnums", values = {"LAnnotationDefault$DefaultValues;", "VALUE2",
+                "LAnnotationDefault$DefaultValues;", "VALUE3"})
+    DefaultValues[] arrayOfEnums() default {DefaultValues.VALUE2, DefaultValues.VALUE3};
+
+    @ExpectedValues(tag = '[', name = "arrayOfAnno", values = {"LAnnotationDefault$DefaultAnnotation;", "value", "DEFAULT_VALUE1",
+                        "LAnnotationDefault$DefaultAnnotation;", "value", "DEFAULT_VALUE2"})
+    DefaultAnnotation[] arrayOfAnno() default {@DefaultAnnotation(value = "DEFAULT_VALUE1"), @DefaultAnnotation(value = "DEFAULT_VALUE2")};
+
+    @ExpectedValues(tag = '@', name = "annoDefault", values = {"LAnnotationDefault$DefaultAnnotation;", "value", "DEFAULT_VALUE"})
+    DefaultAnnotation annoDefault() default @DefaultAnnotation(value = "DEFAULT_VALUE");
+
+    @interface DefaultAnnotation {
+        String value() default "NOT_DEFAULT_VALUE";
+    }
+
+    enum DefaultValues {
+        VALUE1, VALUE2, VALUE3
+    }
+}
+
+@Retention(RetentionPolicy.%POLICY%)
+@interface Container {
+    AnnotationDefault[] value();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/classfiles/attributes/AnnotationDefault/AnnotationDefaultTest.java	Mon Apr 20 12:45:41 2015 +0300
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8042947
+ * @summary Checking AnnotationDefault attribute.
+ * @library /tools/lib /tools/javac/lib ../lib
+ * @build AnnotationDefaultTest TestBase TestResult InMemoryFileManager ToolBox AnnotationDefaultVerifier
+ * @run main AnnotationDefaultTest
+ */
+
+import com.sun.tools.classfile.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.file.Files;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class AnnotationDefaultTest extends TestResult {
+
+    private final static String templateFileName = "AnnotationDefault.java.template";
+
+    private final AnnotationDefaultVerifier verifier;
+
+    public AnnotationDefaultTest() {
+        verifier = new AnnotationDefaultVerifier();
+    }
+
+    private void test(String template, Map<String, String> replacements, boolean hasDefault) {
+        String source = replace(template, replacements);
+        addTestCase(source);
+        try {
+            printf("Testing source:\n%s\n", source);
+            String className = "AnnotationDefault";
+            InMemoryFileManager fileManager = compile(source);
+
+            // Map <method-name, expected-annotation-default-values>
+            Map<String, ExpectedValues> expectedValues =
+                    getExpectedValues(forName(className, fileManager));
+            ClassFile classFile = readClassFile(fileManager.getClasses().get(className));
+
+            for (Method method : classFile.methods) {
+                String methodName = method.getName(classFile.constant_pool);
+                printf("Testing method : %s\n", methodName);
+                AnnotationDefault_attribute attr =
+                        (AnnotationDefault_attribute) method.attributes
+                                .get(Attribute.AnnotationDefault);
+
+                if (hasDefault && !checkNotNull(attr, "Attribute is not null")
+                        || !hasDefault && checkNull(attr, "Attribute is null")) {
+                    // stop checking, attr is null
+                    continue;
+                }
+
+                checkEquals(countNumberOfAttributes(method.attributes.attrs),
+                        1l,
+                        "Number of AnnotationDefault attribute");
+                checkEquals(classFile.constant_pool
+                        .getUTF8Value(attr.attribute_name_index),
+                        "AnnotationDefault", "attribute_name_index");
+
+                ExpectedValues expectedValue = expectedValues.get(methodName);
+                checkEquals((char) attr.default_value.tag, expectedValue.tag(),
+                        String.format("check tag : %c %s", expectedValue.tag(), expectedValue.name()));
+                verifier.testElementValue(attr.default_value.tag,
+                        this, classFile, attr.default_value,
+                        expectedValue.values());
+                verifier.testLength(attr.default_value.tag, this, attr);
+            }
+        } catch (Exception e) {
+            addFailure(e);
+        }
+    }
+
+    private Class<?> forName(String className, InMemoryFileManager fileManager) throws ClassNotFoundException {
+        return fileManager.getClassLoader(null).loadClass(className);
+    }
+
+    private Map<String, ExpectedValues> getExpectedValues(Class<?> clazz) {
+        return Stream.of(clazz.getMethods())
+                .map(method -> method.getAnnotation(ExpectedValues.class))
+                .filter(Objects::nonNull)
+                .collect(Collectors.toMap(
+                        ExpectedValues::name,
+                        Function.identity()));
+    }
+
+    private String replace(String template, Map<String, String> replacements) {
+        String ans = template;
+        for (Map.Entry<String, String> replace : replacements.entrySet()) {
+            ans = ans.replaceAll(replace.getKey(), replace.getValue());
+        }
+        return ans;
+    }
+
+    private long countNumberOfAttributes(Attribute[] attrs) {
+        return Stream.of(attrs)
+                .filter(x -> x instanceof AnnotationDefault_attribute)
+                .count();
+    }
+
+    public String getSource(File templateFileName) throws IOException {
+        return Files.lines(templateFileName.toPath())
+                .filter(str -> !str.startsWith("/*") && !str.startsWith(" *"))
+                .collect(Collectors.joining("\n"));
+    }
+
+    public void test() throws TestFailedException {
+        try {
+            String template = getSource(getSourceFile(templateFileName));
+            for (int i = 0; i < 2; ++i) {
+                for (String repeatable : new String[] {"", "@Repeatable(Container.class)"}) {
+                    for (RetentionPolicy policy : RetentionPolicy.values()) {
+                        final int finalI = i;
+                        Map<String, String> replacements = new HashMap<String, String>(){{
+                            put("%POLICY%", policy.toString());
+                            if (finalI != 0) {
+                                put("default.*\n", ";\n");
+                            }
+                            put("%REPEATABLE%", repeatable);
+                        }};
+                        test(template, replacements, i == 0);
+                    }
+                }
+            }
+        } catch (Throwable e) {
+            addFailure(e);
+        } finally {
+            checkStatus();
+        }
+    }
+
+    public static void main(String[] args) throws TestFailedException {
+        new AnnotationDefaultTest().test();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/classfiles/attributes/AnnotationDefault/AnnotationDefaultVerifier.java	Mon Apr 20 12:45:41 2015 +0300
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import com.sun.tools.classfile.Annotation;
+import com.sun.tools.classfile.AnnotationDefault_attribute;
+import com.sun.tools.classfile.ClassFile;
+import com.sun.tools.classfile.ConstantPool;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class AnnotationDefaultVerifier {
+
+    private final Map<Integer, TestElementValue> verifiers;
+
+    public AnnotationDefaultVerifier() {
+        this.verifiers = new HashMap<>();
+        verifiers.put((int) 'B', new TestIntegerElementValue());
+        verifiers.put((int) 'C', new TestIntegerElementValue());
+        verifiers.put((int) 'D', new TestDoubleElementValue());
+        verifiers.put((int) 'F', new TestFloatElementValue());
+        verifiers.put((int) 'I', new TestIntegerElementValue());
+        verifiers.put((int) 'J', new TestLongElementValue());
+        verifiers.put((int) 'S', new TestIntegerElementValue());
+        verifiers.put((int) 'Z', new TestIntegerElementValue());
+        verifiers.put((int) 's', new TestStringElementValue());
+        verifiers.put((int) 'e', new TestEnumElementValue());
+        verifiers.put((int) 'c', new TestClassElementValue());
+        verifiers.put((int) '[', new TestArrayElementValue());
+        verifiers.put((int) '@', new TestAnnotationElementValue());
+    }
+
+    public void testLength(int tag, TestResult testResult, AnnotationDefault_attribute attr) {
+        verifiers.get(tag).testLength(testResult, attr);
+    }
+
+    public void testElementValue(int tag, TestResult testResult, ClassFile classFile,
+                                 Annotation.element_value element_value, String[] values)
+            throws ConstantPool.UnexpectedEntry, ConstantPool.InvalidIndex {
+        get(tag).testElementValue(testResult, classFile, element_value, values);
+    }
+
+    private TestElementValue get(int tag) {
+        TestElementValue ev = verifiers.get(tag);
+        if (ev == null) {
+            throw new IllegalArgumentException("Unknown tag : " + (char) tag);
+        }
+        return ev;
+    }
+
+    private abstract class TestElementValue {
+        public void testLength(TestResult testCase, AnnotationDefault_attribute attr) {
+            testCase.checkEquals(attr.attribute_length, 1 + attr.default_value.length(),
+                    "attribute_length");
+        }
+
+        public String[] getValues(String[] values, int index, int length) {
+            return Arrays.copyOfRange(values, index, index + length);
+        }
+
+        public int getLength() {
+            return 1;
+        }
+
+        public abstract void testElementValue(
+                TestResult testCase,
+                ClassFile classFile,
+                Annotation.element_value element_value,
+                String[] values)
+                throws ConstantPool.InvalidIndex, ConstantPool.UnexpectedEntry;
+    }
+
+    private class TestIntegerElementValue extends TestElementValue {
+
+        @Override
+        public void testElementValue(
+                TestResult testCase,
+                ClassFile classFile,
+                Annotation.element_value element_value,
+                String[] values) throws ConstantPool.InvalidIndex {
+            Annotation.Primitive_element_value ev =
+                    (Annotation.Primitive_element_value) element_value;
+            ConstantPool.CONSTANT_Integer_info info =
+                    (ConstantPool.CONSTANT_Integer_info)
+                            classFile.constant_pool.get(ev.const_value_index);
+            testCase.checkEquals(info.value, Integer.parseInt(values[0]), "const_value_index");
+        }
+    }
+
+    private class TestLongElementValue extends TestElementValue {
+        @Override
+        public void testElementValue(
+                TestResult testCase,
+                ClassFile classFile,
+                Annotation.element_value element_value,
+                String[] values) throws ConstantPool.InvalidIndex {
+            Annotation.Primitive_element_value ev =
+                    (Annotation.Primitive_element_value) element_value;
+            ConstantPool.CONSTANT_Long_info info =
+                    (ConstantPool.CONSTANT_Long_info)
+                            classFile.constant_pool.get(ev.const_value_index);
+            testCase.checkEquals(info.value, Long.parseLong(values[0]), "const_value_index");
+        }
+    }
+
+    private class TestFloatElementValue extends TestElementValue {
+        @Override
+        public void testElementValue(
+                TestResult testCase,
+                ClassFile classFile,
+                Annotation.element_value element_value,
+                String[] values) throws ConstantPool.InvalidIndex {
+            Annotation.Primitive_element_value ev =
+                    (Annotation.Primitive_element_value) element_value;
+            ConstantPool.CONSTANT_Float_info info =
+                    (ConstantPool.CONSTANT_Float_info)
+                            classFile.constant_pool.get(ev.const_value_index);
+            testCase.checkEquals(info.value, Float.parseFloat(values[0]), "const_value_index");
+        }
+    }
+
+    private class TestDoubleElementValue extends TestElementValue {
+        @Override
+        public void testElementValue(
+                TestResult testCase,
+                ClassFile classFile,
+                Annotation.element_value element_value,
+                String[] values) throws ConstantPool.InvalidIndex {
+            Annotation.Primitive_element_value ev =
+                    (Annotation.Primitive_element_value) element_value;
+            ConstantPool.CONSTANT_Double_info info =
+                    (ConstantPool.CONSTANT_Double_info)
+                            classFile.constant_pool.get(ev.const_value_index);
+            testCase.checkEquals(info.value, Double.parseDouble(values[0]), "const_value_index");
+        }
+    }
+
+    private class TestStringElementValue extends TestElementValue {
+        @Override
+        public void testElementValue(
+                TestResult testCase,
+                ClassFile classFile,
+                Annotation.element_value element_value,
+                String[] values) throws ConstantPool.InvalidIndex {
+            Annotation.Primitive_element_value ev =
+                    (Annotation.Primitive_element_value) element_value;
+            ConstantPool.CONSTANT_Utf8_info info =
+                    (ConstantPool.CONSTANT_Utf8_info)
+                            classFile.constant_pool.get(ev.const_value_index);
+            testCase.checkEquals(info.value, values[0], "const_value_index");
+        }
+    }
+
+    private class TestEnumElementValue extends TestElementValue {
+
+        @Override
+        public int getLength() {
+            return 2;
+        }
+
+        @Override
+        public void testElementValue(
+                TestResult testCase,
+                ClassFile classFile,
+                Annotation.element_value element_value,
+                String[] values)
+                throws ConstantPool.InvalidIndex, ConstantPool.UnexpectedEntry {
+            Annotation.Enum_element_value ev = (Annotation.Enum_element_value) element_value;
+            testCase.checkEquals(classFile.constant_pool.getUTF8Info(ev.type_name_index).value,
+                    values[0], "type_name_index");
+            testCase.checkEquals(classFile.constant_pool.getUTF8Info(ev.const_name_index).value,
+                    values[1], "const_name_index");
+        }
+    }
+
+    private class TestClassElementValue extends TestElementValue {
+        @Override
+        public void testElementValue(
+                TestResult testCase,
+                ClassFile classFile,
+                Annotation.element_value element_value,
+                String[] values)
+                throws ConstantPool.InvalidIndex, ConstantPool.UnexpectedEntry {
+            Annotation.Class_element_value ev = (Annotation.Class_element_value) element_value;
+            testCase.checkEquals(
+                    classFile.constant_pool.getUTF8Info(ev.class_info_index).value,
+                    values[0], "class_info_index");
+        }
+    }
+
+    private class TestAnnotationElementValue extends TestElementValue {
+        @Override
+        public void testLength(TestResult testCase, AnnotationDefault_attribute attr) {
+            // Suppress, since it is hard to test the length of this kind of element values.
+        }
+
+        @Override
+        public int getLength() {
+            // Expected that the test uses DefaultAnnotation
+            // tag (1 byte) + annotation_value (2 bytes) which contains const_value
+            return 3;
+        }
+
+        @Override
+        public void testElementValue(
+                TestResult testCase,
+                ClassFile classFile,
+                Annotation.element_value element_value,
+                String[] values)
+                throws ConstantPool.InvalidIndex, ConstantPool.UnexpectedEntry {
+            Annotation ev = ((Annotation.Annotation_element_value) element_value)
+                    .annotation_value;
+            testCase.checkEquals(
+                    classFile.constant_pool.getUTF8Info(ev.type_index).value,
+                    values[0],
+                    "type_index");
+            for (int i = 0; i < ev.num_element_value_pairs; ++i) {
+                Annotation.element_value_pair pair = ev.element_value_pairs[i];
+                testCase.checkEquals(
+                        classFile.constant_pool.getUTF8Info(pair.element_name_index).value,
+                        values[2 * i + 1],
+                        "element_name_index");
+                TestElementValue testElementValue = verifiers.get(pair.value.tag);
+                testElementValue.testElementValue(
+                        testCase,
+                        classFile,
+                        pair.value,
+                        new String[]{values[2 * i + 2]});
+            }
+        }
+    }
+
+    private class TestArrayElementValue extends TestElementValue {
+        @Override
+        public void testLength(TestResult testCase, AnnotationDefault_attribute attr) {
+            Annotation.Array_element_value ev =
+                    (Annotation.Array_element_value) attr.default_value;
+            int sizeOfTag = ev.values[0].tag == 'e' ? 0 : 1;
+            // tag (1 byte) + array header (2 byte) + length of entries
+            testCase.checkEquals(attr.attribute_length, 1 + 2 +
+                    (sizeOfTag + ev.length() / ev.num_values) * ev.num_values, "attribute_length");
+        }
+
+        @Override
+        public void testElementValue(
+                TestResult testCase,
+                ClassFile classFile,
+                Annotation.element_value element_value,
+                String[] values)
+                throws ConstantPool.InvalidIndex, ConstantPool.UnexpectedEntry {
+            Annotation.Array_element_value ev =
+                    (Annotation.Array_element_value) element_value;
+            int index = 0;
+            for (int i = 0; i < ev.num_values; ++i) {
+                TestElementValue testElementValue = verifiers.get(ev.values[i].tag);
+                int length = testElementValue.getLength();
+                testElementValue.testElementValue(
+                        testCase,
+                        classFile,
+                        ev.values[i],
+                        testElementValue.getValues(values, index, length));
+                index += length;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/classfiles/attributes/AnnotationDefault/ExpectedValues.java	Mon Apr 20 12:45:41 2015 +0300
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExpectedValues {
+    char tag();
+    String name();
+    String[] values();
+}