1) Fixes a bug in the checking of missing members. Before, MemberInfo contains
both the owner and the name. However, this is wrong, as the field reference or
method call may reference the same member with a different owner (e.g.,
subclass).
2) The type in ASM may refer to internal names, but also may refer to array descriptor. And arrays have methods such as clone(). Before, I just assume that a type can only be internal names.
RELNOTES: None.
PiperOrigin-RevId: 189630806
diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassCache.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassCache.java
index c855afd..b618834 100644
--- a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassCache.java
+++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassCache.java
@@ -251,14 +251,14 @@
@Override
public FieldVisitor visitField(
int access, String name, String desc, String signature, Object value) {
- members.add(MemberInfo.create(internalName, name, desc));
+ members.add(MemberInfo.create(name, desc));
return null;
}
@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
- members.add(MemberInfo.create(internalName, name, desc));
+ members.add(MemberInfo.create(name, desc));
return null;
}
diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassInfo.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassInfo.java
index d091464..e017e69 100644
--- a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassInfo.java
+++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassInfo.java
@@ -18,7 +18,6 @@
import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.memoized.Memoized;
import com.google.common.base.Strings;
-import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -60,18 +59,14 @@
/** A member is either a method or a field. */
@AutoValue
- public abstract static class MemberInfo implements Comparable<MemberInfo> {
+ public abstract static class MemberInfo {
- public static MemberInfo create(String owner, String memberName, String descriptor) {
- checkArgument(!Strings.isNullOrEmpty(owner), "Empty owner name: %s", owner);
+ public static MemberInfo create(String memberName, String descriptor) {
checkArgument(!Strings.isNullOrEmpty(memberName), "Empty method name: %s", memberName);
checkArgument(!Strings.isNullOrEmpty(descriptor), "Empty descriptor: %s", descriptor);
- return new AutoValue_ClassInfo_MemberInfo(owner, memberName, descriptor);
+ return new AutoValue_ClassInfo_MemberInfo(memberName, descriptor);
}
- /** The declaring class of this member. */
- public abstract String owner();
-
/** The name of the member. */
public abstract String memberName();
@@ -81,14 +76,5 @@
@Memoized
@Override
public abstract int hashCode();
-
- @Override
- public int compareTo(MemberInfo other) {
- return ComparisonChain.start()
- .compare(this.owner(), other.owner())
- .compare(this.memberName(), other.memberName())
- .compare(this.descriptor(), other.descriptor())
- .result();
- }
}
}
diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/DepsCheckerClassVisitor.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/DepsCheckerClassVisitor.java
index 8c4b1ce..de5bdac 100644
--- a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/DepsCheckerClassVisitor.java
+++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/DepsCheckerClassVisitor.java
@@ -13,6 +13,7 @@
// limitations under the License.
package com.google.devtools.build.importdeps;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableSet;
@@ -91,17 +92,28 @@
}
private void checkMember(String owner, String name, String desc) {
- checkDescriptor(desc);
- AbstractClassEntryState state = checkInternalName(owner);
+ try {
+ if (checkInternalNameOrArrayDescriptor(owner)) {
+ // The owner is an array descriptor.
+ return; // Assume all methods of arrays exist by default.
+ }
+ checkDescriptor(desc);
+ AbstractClassEntryState state = checkInternalName(owner);
- Optional<ClassInfo> classInfo = state.classInfo();
- if (!classInfo.isPresent()) {
- checkState(state.isMissingState(), "The state should be MissingState. %s", state);
- return; // The class is already missing.
- }
- MemberInfo member = MemberInfo.create(owner, name, desc);
- if (!classInfo.get().containsMember(member)) {
- resultCollector.addMissingMember(member);
+ Optional<ClassInfo> classInfo = state.classInfo();
+ if (!classInfo.isPresent()) {
+ checkState(state.isMissingState(), "The state should be MissingState. %s", state);
+ return; // The class is already missing.
+ }
+ MemberInfo member = MemberInfo.create(name, desc);
+ if (!classInfo.get().containsMember(member)) {
+ resultCollector.addMissingMember(owner, member);
+ }
+ } catch (RuntimeException e) {
+ System.err.printf(
+ "A runtime exception occurred when checking the member: owner=%s, name=%s, desc=%s\n",
+ owner, name, desc);
+ throw e;
}
}
@@ -138,7 +150,25 @@
}
}
+ /**
+ * Checks the type, and returns {@literal true} if the type is an array descriptor, otherwise
+ * {@literal false}
+ */
+ private boolean checkInternalNameOrArrayDescriptor(String type) {
+ if (type.charAt(0) == '[') {
+ checkDescriptor(type);
+ return true;
+ } else {
+ checkInternalName(type);
+ return false;
+ }
+ }
+
private AbstractClassEntryState checkInternalName(String internalName) {
+ checkArgument(
+ internalName.length() > 0 && Character.isJavaIdentifierStart(internalName.charAt(0)),
+ "The internal name is invalid. %s",
+ internalName);
AbstractClassEntryState state = classCache.getClassState(internalName);
if (state.isMissingState()) {
resultCollector.addMissingOrIncompleteClass(internalName, state);
@@ -267,7 +297,7 @@
@Override
public void visitTypeInsn(int opcode, String type) {
- checkInternalName(type);
+ checkInternalNameOrArrayDescriptor(type);
super.visitTypeInsn(opcode, type);
}
diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ImportDepsChecker.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ImportDepsChecker.java
index 0087624..a6045ce 100644
--- a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ImportDepsChecker.java
+++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ImportDepsChecker.java
@@ -17,7 +17,7 @@
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.importdeps.AbstractClassEntryState.IncompleteState;
-import com.google.devtools.build.importdeps.ClassInfo.MemberInfo;
+import com.google.devtools.build.importdeps.ResultCollector.MissingMember;
import java.io.Closeable;
import java.io.IOError;
import java.io.IOException;
@@ -76,6 +76,12 @@
reader.accept(checker, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
} catch (IOException e) {
throw new IOError(e);
+ } catch (RuntimeException e) {
+ System.err.printf(
+ "A runtime exception occurred when processing the class %s "
+ + "in the zip file %s\n",
+ name, path);
+ throw e;
}
});
}
@@ -116,8 +122,8 @@
.collect(Collectors.joining(" -> ")))
.append('\n');
}
- ImmutableList<MemberInfo> missingMembers = resultCollector.getSortedMissingMembers();
- for (MemberInfo missing : missingMembers) {
+ ImmutableList<MissingMember> missingMembers = resultCollector.getSortedMissingMembers();
+ for (MissingMember missing : missingMembers) {
builder
.append("Missing member '")
.append(missing.memberName())
diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ResultCollector.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ResultCollector.java
index a935ebe..734b24b 100644
--- a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ResultCollector.java
+++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ResultCollector.java
@@ -16,6 +16,8 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.importdeps.AbstractClassEntryState.IncompleteState;
import com.google.devtools.build.importdeps.ClassInfo.MemberInfo;
@@ -28,7 +30,7 @@
private final HashSet<String> missingClasss = new HashSet<>();
private final HashMap<String, IncompleteState> incompleteClasses = new HashMap<>();
- private final HashSet<MemberInfo> missingMembers = new HashSet<>();
+ private final HashSet<MissingMember> missingMembers = new HashSet<>();
public ResultCollector() {}
@@ -57,8 +59,8 @@
return missingClasss.isEmpty() && incompleteClasses.isEmpty() && missingMembers.isEmpty();
}
- public void addMissingMember(MemberInfo member) {
- missingMembers.add(member);
+ public void addMissingMember(String owner, MemberInfo member) {
+ missingMembers.add(MissingMember.create(owner, member));
}
public ImmutableList<String> getSortedMissingClassInternalNames() {
@@ -70,7 +72,44 @@
Comparator.comparing(a -> a.classInfo().get().internalName()), incompleteClasses.values());
}
- public ImmutableList<MemberInfo> getSortedMissingMembers() {
+ public ImmutableList<MissingMember> getSortedMissingMembers() {
return ImmutableList.sortedCopyOf(missingMembers);
}
+
+ /**
+ * A missing member on the classpath. This class does not contain the member name and description,
+ * but also the owner of the member.
+ */
+ @AutoValue
+ public abstract static class MissingMember implements Comparable<MissingMember> {
+
+ public static MissingMember create(String owner, String memberName, String descriptor) {
+ return create(owner, MemberInfo.create(memberName, descriptor));
+ }
+
+ public static MissingMember create(String owner, MemberInfo member) {
+ return new AutoValue_ResultCollector_MissingMember(owner, member);
+ }
+
+ public abstract String owner();
+
+ public abstract MemberInfo member();
+
+ public final String memberName() {
+ return member().memberName();
+ }
+
+ public final String descriptor() {
+ return member().descriptor();
+ }
+
+ @Override
+ public int compareTo(MissingMember other) {
+ return ComparisonChain.start()
+ .compare(this.owner(), other.owner())
+ .compare(this.memberName(), other.memberName())
+ .compare(this.descriptor(), other.descriptor())
+ .result();
+ }
+ }
}
diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ClassInfoTest.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ClassInfoTest.java
index 54d53f2..2a09eac 100644
--- a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ClassInfoTest.java
+++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ClassInfoTest.java
@@ -27,8 +27,8 @@
public class ClassInfoTest {
public static final String JAVA_LANG_OBJECT = "java/lang/Object";
- private final MemberInfo hashCodeMethod = MemberInfo.create(JAVA_LANG_OBJECT, "hashCode", "()I");
- private final MemberInfo sizeMethod = MemberInfo.create(JAVA_LANG_OBJECT, "clear", "()V");
+ private final MemberInfo hashCodeMethod = MemberInfo.create("hashCode", "()I");
+ private final MemberInfo sizeMethod = MemberInfo.create("clear", "()V");
private final ClassInfo objectClass =
ClassInfo.create(JAVA_LANG_OBJECT, ImmutableList.of(), ImmutableSet.of(hashCodeMethod));
@@ -39,23 +39,22 @@
@Test
public void testMemberInfo() {
- MemberInfo memberInfo = MemberInfo.create(JAVA_LANG_OBJECT, "a", "I");
+ MemberInfo memberInfo = MemberInfo.create("a", "I");
assertThat(memberInfo.memberName()).isEqualTo("a");
assertThat(memberInfo.descriptor()).isEqualTo("I");
- assertThat(memberInfo).isEqualTo(MemberInfo.create("java/lang/Object", "a", "I"));
+ assertThat(memberInfo).isEqualTo(MemberInfo.create("a", "I"));
- assertThat(hashCodeMethod).isEqualTo(MemberInfo.create("java/lang/Object", "hashCode", "()I"));
- assertThat(sizeMethod).isEqualTo(MemberInfo.create("java/lang/Object", "clear", "()V"));
+ assertThat(hashCodeMethod).isEqualTo(MemberInfo.create("hashCode", "()I"));
+ assertThat(sizeMethod).isEqualTo(MemberInfo.create("clear", "()V"));
}
@Test
public void testClassInfoCorrectlySet() {
assertThat(objectClass.internalName()).isEqualTo("java/lang/Object");
assertThat(objectClass.declaredMembers())
- .containsExactly(MemberInfo.create("java/lang/Object", "hashCode", "()I"))
+ .containsExactly(MemberInfo.create("hashCode", "()I"))
.inOrder();
- assertThat(objectClass.containsMember(MemberInfo.create("java/lang/Object", "hashCode", "()I")))
- .isTrue();
+ assertThat(objectClass.containsMember(MemberInfo.create("hashCode", "()I"))).isTrue();
assertThat(listClass.internalName()).isEqualTo("java/util/List");
assertThat(listClass.declaredMembers()).containsExactly(sizeMethod);
@@ -67,16 +66,11 @@
ClassInfo parent = objectClass;
ClassInfo child = listClass;
assertThat(child.superClasses()).contains(parent);
- assertThat(parent.containsMember(MemberInfo.create("java/lang/Object", "hashCode", "()I")))
- .isTrue();
- assertThat(parent.containsMember(MemberInfo.create("java/lang/Object", "size", "()I")))
- .isFalse();
- assertThat(parent.containsMember(MemberInfo.create("java/lang/Object", "clear", "()V")))
- .isFalse();
+ assertThat(parent.containsMember(MemberInfo.create("hashCode", "()I"))).isTrue();
+ assertThat(parent.containsMember(MemberInfo.create("size", "()I"))).isFalse();
+ assertThat(parent.containsMember(MemberInfo.create("clear", "()V"))).isFalse();
- assertThat(child.containsMember(MemberInfo.create("java/lang/Object", "hashCode", "()I")))
- .isTrue();
- assertThat(child.containsMember(MemberInfo.create("java/lang/Object", "clear", "()V")))
- .isTrue();
+ assertThat(child.containsMember(MemberInfo.create("hashCode", "()I"))).isTrue();
+ assertThat(child.containsMember(MemberInfo.create("clear", "()V"))).isTrue();
}
}
diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/DepsCheckerClassVisitorTest.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/DepsCheckerClassVisitorTest.java
index 3d19b06..277fd80 100644
--- a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/DepsCheckerClassVisitorTest.java
+++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/DepsCheckerClassVisitorTest.java
@@ -16,7 +16,7 @@
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
-import com.google.devtools.build.importdeps.ClassInfo.MemberInfo;
+import com.google.devtools.build.importdeps.ResultCollector.MissingMember;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
@@ -80,19 +80,19 @@
assertThat(collector.getSortedMissingClassInternalNames()).isEmpty();
assertThat(collector.getSortedMissingMembers())
.containsExactly(
- MemberInfo.create(
+ MissingMember.create(
"com/google/devtools/build/importdeps/testdata/Library$Class1",
"I",
"Lcom/google/devtools/build/importdeps/testdata/Library$Class1;"),
- MemberInfo.create(
+ MissingMember.create(
"com/google/devtools/build/importdeps/testdata/Library$Class3",
"field",
"Lcom/google/devtools/build/importdeps/testdata/Library$Class4;"),
- MemberInfo.create(
+ MissingMember.create(
"com/google/devtools/build/importdeps/testdata/Library$Class4",
"createClass5",
"()Lcom/google/devtools/build/importdeps/testdata/Library$Class5;"),
- MemberInfo.create(
+ MissingMember.create(
"com/google/devtools/build/importdeps/testdata/Library$Class5",
"create",
"(Lcom/google/devtools/build/importdeps/testdata/Library$Class7;)"
@@ -111,9 +111,10 @@
ResultCollector resultCollector = new ResultCollector();
try (ClassCache cache = new ClassCache(ImmutableList.copyOf(classpath));
ZipFile zipFile = new ZipFile(clientJar.toFile())) {
-
- AbstractClassEntryState state = cache.getClassState("java/lang/invoke/LambdaMetafactory");
- System.out.println(state);
+ assertThat(cache.getClassState("java/lang/invoke/LambdaMetafactory").isExistingState())
+ .isTrue();
+ AbstractClassEntryState state = cache.getClassState("java/lang/Enum");
+ assertThat(state.isExistingState()).isTrue();
for (String clientClass : clientClasses) {
ZipEntry entry = zipFile.getEntry(clientClass + ".class");
try (InputStream classStream = zipFile.getInputStream(entry)) {
diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/LazyClassEntryStateTest.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/LazyClassEntryStateTest.java
index 428b88e..6b0a651 100644
--- a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/LazyClassEntryStateTest.java
+++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/LazyClassEntryStateTest.java
@@ -32,7 +32,7 @@
public static final String LIST_CLASS_NAME = "java/util/List";
public static final ImmutableSet<MemberInfo> METHOD_LIST =
- ImmutableSet.of(MemberInfo.create(LIST_CLASS_NAME, "hashCode", "()I"));
+ ImmutableSet.of(MemberInfo.create("hashCode", "()I"));
public static final ClassInfo LIST_CLASS_INFO =
ClassInfo.create(LIST_CLASS_NAME, ImmutableList.of(), METHOD_LIST);
@@ -51,8 +51,7 @@
ClassInfo classInfo = state.classInfo().get();
assertThat(classInfo.internalName()).isEqualTo("java/util/List");
assertThat(classInfo.declaredMembers()).hasSize(1);
- assertThat(classInfo.declaredMembers())
- .containsExactly(MemberInfo.create("java/util/List", "hashCode", "()I"));
+ assertThat(classInfo.declaredMembers()).containsExactly(MemberInfo.create("hashCode", "()I"));
}
@Test
@@ -74,8 +73,7 @@
ClassInfo classInfo = state.classInfo().get();
assertThat(classInfo.internalName()).isEqualTo("java/util/List");
assertThat(classInfo.declaredMembers()).hasSize(1);
- assertThat(classInfo.declaredMembers())
- .containsExactly(MemberInfo.create("java/util/List", "hashCode", "()I"));
+ assertThat(classInfo.declaredMembers()).containsExactly(MemberInfo.create("hashCode", "()I"));
ImmutableList<String> failurePath = state.getResolutionFailurePath();
assertThat(failurePath).hasSize(1);
diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ResultCollectorTest.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ResultCollectorTest.java
index 0d78504..3b67288 100644
--- a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ResultCollectorTest.java
+++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ResultCollectorTest.java
@@ -20,6 +20,7 @@
import com.google.devtools.build.importdeps.AbstractClassEntryState.IncompleteState;
import com.google.devtools.build.importdeps.AbstractClassEntryState.MissingState;
import com.google.devtools.build.importdeps.ClassInfo.MemberInfo;
+import com.google.devtools.build.importdeps.ResultCollector.MissingMember;
import java.io.IOException;
import org.junit.Before;
import org.junit.Test;
@@ -50,9 +51,9 @@
assertThat(collector.getSortedMissingClassInternalNames()).containsExactly("java.lang.String");
assertThat(collector.getSortedMissingMembers()).isEmpty();
- collector.addMissingMember(MemberInfo.create("java/lang/Object", "field", "I"));
+ collector.addMissingMember("java/lang/Object", MemberInfo.create("field", "I"));
assertThat(collector.getSortedMissingMembers())
- .containsExactly(MemberInfo.create("java/lang/Object", "field", "I"));
+ .containsExactly(MissingMember.create("java/lang/Object", "field", "I"));
assertThat(collector.getSortedMissingClassInternalNames()).containsExactly("java.lang.String");
}
@@ -78,4 +79,35 @@
.containsExactly("java.lang.String", "java.lang.Integer", "java.lang.Object");
assertThat(collector.getSortedMissingMembers()).isEmpty();
}
+
+ @Test
+ public void testMissingMember() {
+ String owner = "owner";
+ String name = "name";
+ String desc = "desc";
+ MissingMember member = MissingMember.create(owner, name, desc);
+ assertThat(member.owner()).isEqualTo(owner);
+ assertThat(member.memberName()).isEqualTo(name);
+ assertThat(member.descriptor()).isEqualTo(desc);
+ assertThat(member.member()).isEqualTo(MemberInfo.create(name, desc));
+
+ MissingMember member2 = MissingMember.create(owner, MemberInfo.create(name, desc));
+ assertThat(member2).isEqualTo(member);
+ }
+
+ @Test
+ public void testMemberComparison() {
+ MissingMember member1 = MissingMember.create("A", MemberInfo.create("B", "C"));
+ MissingMember member2 = MissingMember.create("A", MemberInfo.create("B", "C"));
+ assertThat(member1.compareTo(member2)).isEqualTo(0);
+
+ MissingMember member3 = MissingMember.create("B", MemberInfo.create("B", "C"));
+ assertThat(member1.compareTo(member3)).isEqualTo(-1);
+ assertThat(member3.compareTo(member1)).isEqualTo(1);
+
+ MissingMember member4 = MissingMember.create("A", MemberInfo.create("C", "C"));
+ assertThat(member1.compareTo(member4)).isEqualTo(-1);
+
+ assertThat(member3.compareTo(member4)).isEqualTo(1);
+ }
}
diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/Client.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/Client.java
index 6b833c3..307f2d8 100644
--- a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/Client.java
+++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/Client.java
@@ -57,6 +57,15 @@
Class8[][] array = new Class8[10][10];
Class9[] array2 = new Class9[10];
array2[0] = new Class10();
+ Object[] copy = array.clone();
+ array = (Class8[][]) copy;
+ System.out.println(array.clone().length);
+ }
+
+ public void testEnums() {
+ EnumTest a = EnumTest.A;
+ System.out.println(a.ordinal());
+ System.out.println(a.name());
}
/** An inner annotation. */
@@ -64,4 +73,10 @@
@Target(ElementType.TYPE)
@AnnotationAnnotation
public @interface NestedAnnotation {}
+
+ public enum EnumTest {
+ A,
+ B,
+ C
+ }
}