Add P4 info to java constant tool

Change-Id: I73a451404e8b657845bfc9b6a37abd824a10910e
diff --git a/tools/dev/bin/onos-gen-p4-constants b/tools/dev/bin/onos-gen-p4-constants
new file mode 100755
index 0000000..8e68acc
--- /dev/null
+++ b/tools/dev/bin/onos-gen-p4-constants
@@ -0,0 +1,219 @@
+#!/usr/bin/env python2.7
+# -*- utf-8 -*-
+import argparse
+import re
+import google.protobuf.text_format as tf
+from p4.config import p4info_pb2
+
+
+copyright = '''/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed 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.
+ */
+'''
+
+imports = '''
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiActionProfileId;
+import org.onosproject.net.pi.model.PiControlMetadataId;
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.model.PiTableId;'''
+
+PKG_FMT = 'package org.onosproject.pipelines.%s;'
+
+CLASS_OPEN = 'public final class %s {'
+CLASS_CLOSE = '}'
+
+DEFAULT_CONSTRUCTOR = '''
+    // hide default constructor
+    private %s() {
+    }
+'''
+
+CONST_FMT = '    public static final %s %s = %s;'
+SHORT_CONST_FMT ='''    public static final %s %s =
+            %s;'''
+JAVA_STR = 'String'
+EMPTY_STR = ''
+JAVA_DOC_FMT = '''/**
+ * Constants for %s pipeline.
+ */'''
+
+
+PI_HF_FIELD_ID = 'PiMatchFieldId'
+PI_HF_FIELD_ID_CST = 'PiMatchFieldId.of("%s")'
+
+PI_TBL_ID = 'PiTableId'
+PI_TBL_ID_CST = 'PiTableId.of("%s")'
+
+PI_CTR_ID = 'PiCounterId'
+PI_CTR_ID_CST = 'PiCounterId.of("%s")'
+
+PI_ACT_ID = 'PiActionId'
+PI_ACT_ID_CST = 'PiActionId.of("%s")'
+
+PI_ACT_PRM_ID = 'PiActionParamId'
+PI_ACT_PRM_ID_CST = 'PiActionParamId.of("%s")'
+
+PI_ACT_PROF_ID = 'PiActionProfileId'
+PI_ACT_PROF_ID_CST = 'PiActionProfileId.of("%s")'
+
+PI_PKT_META_ID = 'PiControlMetadataId'
+PI_PKT_META_ID_CST = 'PiControlMetadataId.of("%s")'
+
+class ConstantClassGenerator(object):
+    headers = set()
+    header_fields = set()
+    tables = set()
+    counters = set()
+    direct_counters = set()
+    actions = set()
+    action_params = set()
+    action_profiles = set()
+    packet_metadata = set()
+
+    # https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case
+    def convert_camel_to_all_caps(self, name):
+        s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
+        s1 = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).upper()
+        return s1.replace('.', '_')
+
+    def __init__(self, base_name):
+
+        self.class_name = base_name.title() + 'Constants'
+        self.package_name = PKG_FMT % (base_name, )
+        self.java_doc = JAVA_DOC_FMT % (base_name, )
+
+    def parse(self, p4info):
+        for tbl in p4info.tables:
+            for mf in tbl.match_fields:
+                self.header_fields.add(mf.name)
+
+            self.tables.add(tbl.preamble.name)
+
+        for ctr in p4info.counters:
+            self.counters.add(ctr.preamble.name)
+
+        for dir_ctr in p4info.direct_counters:
+            self.direct_counters.add(dir_ctr.preamble.name)
+
+        for act in p4info.actions:
+            self.actions.add(act.preamble.name)
+
+            for param in act.params:
+                self.action_params.add(param.name)
+
+        for act_prof in p4info.action_profiles:
+            self.action_profiles.add(act_prof.preamble.name)
+
+        for cpm in p4info.controller_packet_metadata:
+            for mta in cpm.metadata:
+                self.packet_metadata.add(mta.name)
+
+    def const_line(self, name, type, constructor):
+        var_name = self.convert_camel_to_all_caps(name)
+        val = constructor % (name, )
+
+        line = CONST_FMT % (type, var_name, val)
+        if len(line) > 80:
+            line = SHORT_CONST_FMT % (type, var_name, val)
+        return line
+
+    def generate_java(self):
+        lines = list()
+        lines.append(copyright)
+        lines.append(self.package_name)
+        lines.append(imports)
+        lines.append(self.java_doc)
+        # generate the class
+        lines.append(CLASS_OPEN % (self.class_name, ))
+        lines.append(DEFAULT_CONSTRUCTOR % (self.class_name, ))
+
+        if len(self.header_fields) is not 0:
+            lines.append('    // Header field IDs')
+        for hf in self.header_fields:
+            lines.append(self.const_line(hf, PI_HF_FIELD_ID, PI_HF_FIELD_ID_CST))
+
+        if len(self.tables) is not 0:
+            lines.append('    // Table IDs')
+        for tbl in self.tables:
+            lines.append(self.const_line(tbl, PI_TBL_ID, PI_TBL_ID_CST))
+
+        if len(self.counters) is not 0:
+            lines.append('    // Indirect Counter IDs')
+        for ctr in self.counters:
+            lines.append(self.const_line(ctr, PI_CTR_ID, PI_CTR_ID_CST))
+
+        if len(self.direct_counters) is not 0:
+            lines.append('    // Direct Counter IDs')
+        for dctr in self.direct_counters:
+            lines.append(self.const_line(dctr, PI_CTR_ID, PI_CTR_ID_CST))
+
+        if len(self.actions) is not 0:
+            lines.append('    // Action IDs')
+        for act in self.actions:
+            lines.append(self.const_line(act, PI_ACT_ID, PI_ACT_ID_CST))
+
+        if len(self.action_params) is not 0:
+            lines.append('    // Action Param IDs')
+        for act_prm in self.action_params:
+            lines.append(self.const_line(act_prm, PI_ACT_PRM_ID, PI_ACT_PRM_ID_CST))
+
+        if len(self.action_profiles) is not 0:
+            lines.append('    // Action Profile IDs')
+        for act_prof in self.action_profiles:
+            lines.append(self.const_line(act_prof, PI_ACT_PROF_ID, PI_ACT_PROF_ID_CST))
+
+        if len(self.packet_metadata) is not 0:
+            lines.append('    // Packet Metadata IDs')
+        for pmeta in self.packet_metadata:
+            lines.append(self.const_line(pmeta, PI_PKT_META_ID, PI_PKT_META_ID_CST))
+        lines.append(CLASS_CLOSE)
+        # end of class
+
+        return '\n'.join(lines)
+
+
+def main():
+    parser = argparse.ArgumentParser(prog='onos-gen-p4-constants',
+                                     description='ONOS P4Info to Java constant generator.')
+    parser.add_argument('name', help='Name of the constant, will be used as class name')
+    parser.add_argument('p4info', help='P4Info file')
+    parser.add_argument('-o', '--output', help='output path', default='-')
+    args = parser.parse_args()
+
+    base_name = args.name
+    file_name = args.p4info
+    p4info = p4info_pb2.P4Info()
+    with open(file_name, 'r') as intput_file:
+        s = intput_file.read()
+        tf.Merge(s, p4info)
+
+    gen = ConstantClassGenerator(base_name)
+    gen.parse(p4info)
+
+    java_code = gen.generate_java()
+
+    if args.output == '-':
+        # std output
+        print java_code
+    else:
+        with open(args.output, 'w') as output_file:
+            output_file.write(java_code)
+
+
+if __name__ == '__main__':
+    main()