[NO ISSUE][TYPE] Introduce IATypeVisitor

- user mode changes: no
- storage format changes: no
- interface changes: yes

Interface changes:
- Add accept method to IAType

Details:
Several operation require to traverse IAType to process types. The IATypeVisitor provides
an easy way to traverse nested types (e.g., comparing AsterixDB type and Parquet Type)

Change-Id: I16cbb7b3675846b3a4097d5a38ee15fa28634f92
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/12824
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Wael Alkowaileet <wael.y.k@gmail.com>
Reviewed-by: Dmitry Lychagin <dmitry.lychagin@couchbase.com>
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/ARecordType.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/ARecordType.java
index b91b4ae..a79b8de 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/ARecordType.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/ARecordType.java
@@ -428,4 +428,9 @@
         }
         return false;
     }
+
+    @Override
+    public <R, T> R accept(IATypeVisitor<R, T> visitor, T arg) {
+        return visitor.visit(this, arg);
+    }
 }
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/AUnionType.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/AUnionType.java
index 7ef18d4..caa926d 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/AUnionType.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/AUnionType.java
@@ -70,6 +70,16 @@
         return false;
     }
 
+    public IAType getType(ATypeTag typeTag) {
+        for (int i = 0; i < unionList.size(); i++) {
+            IAType type = unionList.get(i);
+            if (typeTag == type.getTypeTag()) {
+                return type;
+            }
+        }
+        return null;
+    }
+
     public IAType getActualType() {
         return unionList.get(AUnionType.OPTIONAL_TYPE_INDEX_IN_UNION_LIST);
     }
@@ -241,6 +251,11 @@
         return jsonObject;
     }
 
+    @Override
+    public <R, T> R accept(IATypeVisitor<R, T> visitor, T arg) {
+        return visitor.visit(this, arg);
+    }
+
     public static IJsonSerializable fromJson(IPersistedResourceRegistry registry, JsonNode json)
             throws HyracksDataException {
         String typeName = json.get(TYPE_NAME_FIELD).asText();
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/AbstractCollectionType.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/AbstractCollectionType.java
index ef15224..9b75566 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/AbstractCollectionType.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/AbstractCollectionType.java
@@ -70,6 +70,11 @@
         return isTyped() && itemType.getTypeName().equals(type.getTypeName());
     }
 
+    @Override
+    public <R, T> R accept(IATypeVisitor<R, T> visitor, T arg) {
+        return visitor.visit(this, arg);
+    }
+
     JsonNode convertToJson(IPersistedResourceRegistry registry, Class<? extends IJsonSerializable> clazz, long version)
             throws HyracksDataException {
         final ObjectNode jsonObject = registry.getClassIdentifier(clazz, version);
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/BuiltinType.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/BuiltinType.java
index bdac9e9..2404ac2d 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/BuiltinType.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/BuiltinType.java
@@ -1081,6 +1081,14 @@
         return getType().getTypeTag().serialize();
     }
 
+    /**
+     * Visit built-in type as a flat type
+     */
+    @Override
+    public <R, T> R accept(IATypeVisitor<R, T> visitor, T arg) {
+        return visitor.visitFlat(this, arg);
+    }
+
     private static JsonNode convertToJson(IPersistedResourceRegistry registry, short tag, long version) {
         ObjectNode jsonNode = registry.getClassIdentifier(BuiltinType.class, version);
         jsonNode.put(TAG_FIELD, tag);
@@ -1090,7 +1098,10 @@
     @SuppressWarnings("squid:S1172") // unused parameter
     public static IJsonSerializable fromJson(IPersistedResourceRegistry registry, JsonNode json) {
         byte tag = (byte) json.get(TAG_FIELD).shortValue();
-        ATypeTag typeTag = VALUE_TYPE_MAPPING[tag];
+        return getBuiltinType(VALUE_TYPE_MAPPING[tag]);
+    }
+
+    public static IAType getBuiltinType(ATypeTag typeTag) {
         switch (typeTag) {
             case TYPE:
                 return ALL_TYPE;
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/IAType.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/IAType.java
index 0e6cc4f..ed4c2bb 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/IAType.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/IAType.java
@@ -31,4 +31,11 @@
 
     public String getTypeName();
 
+    /**
+     * Allow for additional traversal and processing for {@link IAType}
+     *
+     * @param visitor visitor
+     * @param arg     visitor's argument
+     */
+    <R, T> R accept(IATypeVisitor<R, T> visitor, T arg);
 }
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/IATypeVisitor.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/IATypeVisitor.java
new file mode 100644
index 0000000..0951763
--- /dev/null
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/IATypeVisitor.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.om.types;
+
+/**
+ * Allows for a specialized processing for the {@link IAType}
+ *
+ * @param <R> return type
+ * @param <T> argument type
+ */
+public interface IATypeVisitor<R, T> {
+    R visit(ARecordType recordType, T arg);
+
+    R visit(AbstractCollectionType collectionType, T arg);
+
+    R visit(AUnionType unionType, T arg);
+
+    R visitFlat(IAType flatType, T arg);
+}
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/visitor/SimpleStringBuilderForIATypeVisitor.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/visitor/SimpleStringBuilderForIATypeVisitor.java
new file mode 100644
index 0000000..9f0c82b
--- /dev/null
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/visitor/SimpleStringBuilderForIATypeVisitor.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.om.types.visitor;
+
+import java.util.List;
+
+import org.apache.asterix.om.types.ARecordType;
+import org.apache.asterix.om.types.ATypeTag;
+import org.apache.asterix.om.types.AUnionType;
+import org.apache.asterix.om.types.AbstractCollectionType;
+import org.apache.asterix.om.types.IAType;
+import org.apache.asterix.om.types.IATypeVisitor;
+
+/**
+ * This visitor produces a oneliner JSON-like representation of {@link IAType} to be interpreted by the user.
+ */
+public class SimpleStringBuilderForIATypeVisitor implements IATypeVisitor<Void, StringBuilder> {
+    /**
+     * Example: {"field1":string,"field2":[bigint]}
+     */
+    @Override
+    public Void visit(ARecordType recordType, StringBuilder arg) {
+        String[] fieldNames = recordType.getFieldNames();
+        IAType[] fieldTypes = recordType.getFieldTypes();
+
+        arg.append("{");
+        for (int i = 0; i < fieldNames.length; i++) {
+            if (i > 0) {
+                arg.append(',');
+            }
+            arg.append(fieldNames[i]);
+            arg.append(':');
+            fieldTypes[i].accept(this, arg);
+        }
+        arg.append("}");
+        return null;
+    }
+
+    /**
+     * Example:
+     * - Array: [{"field1":bigint}]
+     * - Multiset: {{bigint}}
+     */
+    @Override
+    public Void visit(AbstractCollectionType collectionType, StringBuilder arg) {
+        IAType itemType = collectionType.getItemType();
+
+        arg.append(collectionType.getTypeTag() == ATypeTag.ARRAY ? "[" : "{{");
+        itemType.accept(this, arg);
+        arg.append(collectionType.getTypeTag() == ATypeTag.ARRAY ? "]" : "}}");
+        return null;
+    }
+
+    /**
+     * Example: A union type of array, object, and bigint
+     * - <[{"field1":...}],{"field1:...},bigint>
+     */
+    @Override
+    public Void visit(AUnionType unionType, StringBuilder arg) {
+        List<IAType> unionList = unionType.getUnionList();
+
+        arg.append("<");
+        for (int i = 0; i < unionList.size(); i++) {
+            if (i > 0) {
+                arg.append(',');
+            }
+            unionList.get(i).accept(this, arg);
+        }
+        arg.append(">");
+        return null;
+    }
+
+    /**
+     * Example:
+     * - bigint
+     * - string
+     */
+    @Override
+    public Void visitFlat(IAType flatType, StringBuilder arg) {
+        arg.append(flatType.getTypeTag());
+        return null;
+    }
+}
diff --git a/asterixdb/asterix-om/src/test/java/org/apache/asterix/test/om/types/visitor/TypeSimpleStringBuilderTest.java b/asterixdb/asterix-om/src/test/java/org/apache/asterix/test/om/types/visitor/TypeSimpleStringBuilderTest.java
new file mode 100644
index 0000000..8186810
--- /dev/null
+++ b/asterixdb/asterix-om/src/test/java/org/apache/asterix/test/om/types/visitor/TypeSimpleStringBuilderTest.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.test.om.types.visitor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.asterix.om.types.AOrderedListType;
+import org.apache.asterix.om.types.ARecordType;
+import org.apache.asterix.om.types.AUnionType;
+import org.apache.asterix.om.types.AUnorderedListType;
+import org.apache.asterix.om.types.BuiltinType;
+import org.apache.asterix.om.types.IAType;
+import org.apache.asterix.om.types.visitor.SimpleStringBuilderForIATypeVisitor;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TypeSimpleStringBuilderTest {
+    private static final ARecordType ROOT_TYPE;
+    private static final String EXPECTED_STRING;
+
+    static {
+        StringBuilder expectedStringBuilder = new StringBuilder();
+        //Record with two fields
+        ARecordType recordType = createNestedRecord(expectedStringBuilder);
+        String recordTypeString = getStringAndReset(expectedStringBuilder);
+
+        //Array of records
+        AOrderedListType arrayOfRecords = new AOrderedListType(recordType, "arrayOfRecords");
+        surroundString(expectedStringBuilder, "[", "]", recordTypeString);
+        String arrayOfRecordsString = getStringAndReset(expectedStringBuilder);
+
+        //Multiset of records
+        AUnorderedListType multiSetOfRecords = new AUnorderedListType(recordType, "multiSetOfRecords");
+        surroundString(expectedStringBuilder, "{{", "}}", recordTypeString);
+        String multiSetOfRecordsString = getStringAndReset(expectedStringBuilder);
+
+        //Union
+        List<IAType> unionList = new ArrayList<>();
+        unionList.add(recordType);
+        unionList.add(arrayOfRecords);
+        unionList.add(multiSetOfRecords);
+        unionList.add(BuiltinType.AINT64);
+        AUnionType unionType = new AUnionType(unionList, "unionType");
+        surroundString(expectedStringBuilder, "<", ">", recordTypeString, arrayOfRecordsString, multiSetOfRecordsString,
+                BuiltinType.AINT64.getTypeTag().toString());
+        String unionTypeString = getStringAndReset(expectedStringBuilder);
+
+        //Root type
+        String[] rootFieldNames = { BuiltinType.ANY.getTypeName(), arrayOfRecords.getTypeName(),
+                multiSetOfRecords.getTypeName(), unionType.getTypeName() };
+        IAType[] rootFieldTypes = { BuiltinType.ANY, arrayOfRecords, multiSetOfRecords, unionType };
+        ROOT_TYPE = new ARecordType("rootType", rootFieldNames, rootFieldTypes, false);
+        buildRecordString(expectedStringBuilder, rootFieldNames, rootFieldTypes[0].getTypeTag().toString(),
+                arrayOfRecordsString, multiSetOfRecordsString, unionTypeString);
+        EXPECTED_STRING = getStringAndReset(expectedStringBuilder);
+    }
+
+    private static ARecordType createNestedRecord(StringBuilder builder) {
+        String[] fieldNames = { "field1", "field2" };
+        IAType[] fieldTypes = { BuiltinType.ASTRING, BuiltinType.AINT64 };
+        buildRecordString(builder, fieldNames, fieldTypes[0].getTypeTag().toString(),
+                fieldTypes[1].getTypeTag().toString());
+        return new ARecordType("nestedRecord", fieldNames, fieldTypes, true);
+    }
+
+    private static void surroundString(StringBuilder builder, String open, String close, String... strings) {
+        builder.append(open);
+        for (int i = 0; i < strings.length; i++) {
+            if (i > 0) {
+                builder.append(',');
+            }
+            builder.append(strings[i]);
+        }
+        builder.append(close);
+    }
+
+    private static void buildRecordString(StringBuilder builder, String[] fieldNames, String... fieldTypesStrings) {
+        builder.append('{');
+        for (int i = 0; i < fieldNames.length; i++) {
+            if (i > 0) {
+                builder.append(',');
+            }
+            builder.append(fieldNames[i]);
+            builder.append(':');
+            builder.append(fieldTypesStrings[i]);
+        }
+        builder.append('}');
+    }
+
+    private static String getStringAndReset(StringBuilder expectedStringBuilder) {
+        String value = expectedStringBuilder.toString();
+        expectedStringBuilder.setLength(0);
+        return value;
+    }
+
+    @Test
+    public void testSimpleStringBuilderForIAType() {
+        StringBuilder builder = new StringBuilder();
+        SimpleStringBuilderForIATypeVisitor visitor = new SimpleStringBuilderForIATypeVisitor();
+        ROOT_TYPE.accept(visitor, builder);
+        Assert.assertEquals(EXPECTED_STRING, builder.toString());
+    }
+
+}