blob: 79cea7a45f79ae250db1ebd68322ddc20d934ae7 [file] [log] [blame]
# Copyright 2013, Big Switch Networks, Inc.
#
# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
# the following special exception:
#
# LOXI Exception
#
# As a special exception to the terms of the EPL, you may distribute libraries
# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
# that copyright and licensing notices generated by LoxiGen are not altered or removed
# from the LoxiGen Libraries and the notice provided below is (i) included in
# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
# documentation for the LoxiGen Libraries, if distributed in binary form.
#
# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
#
# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
# a copy of the EPL at:
#
# http://www.eclipse.org/legal/epl-v10.html
#
# 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
# EPL for the specific language governing permissions and limitations
# under the EPL.
# Prototype of an Intermediate Object model for the java code generator
# A lot of this stuff could/should probably be merged with the python utilities
import collections
from collections import namedtuple, defaultdict, OrderedDict
import logging
import os
import pdb
import re
from generic_utils import find, memoize, OrderedSet, OrderedDefaultDict
from loxi_globals import OFVersions
import loxi_globals
from loxi_ir import *
import loxi_utils.loxi_utils as loxi_utils
import test_data
import java_gen.java_type as java_type
from java_gen.java_type import erase_type_annotation
logger = logging.getLogger(__name__)
def java_class_name(c_name):
return java_type.name_c_to_caps_camel(c_name) if c_name != "of_header" else "OFMessage"
class JavaModel(object):
# registry for enums that should not be generated
# set(${java_enum_name})
enum_blacklist = set(("OFDefinitions", "OFPortNo", "OFVlanId", "OFGroup"))
# registry for enum *entry* that should not be generated
# map: ${java_enum_name} -> set(${java_entry_entry_name})
enum_entry_blacklist = defaultdict(lambda: set(), OFFlowWildcards=set([ "NW_DST_BITS", "NW_SRC_BITS", "NW_SRC_SHIFT", "NW_DST_SHIFT" ]))
# registry of interfaces that should not be generated
# set(java_names)
# OFUint structs are there for god-knows what in loci. We certainly don't need them.
interface_blacklist = set( ("OFUint8", "OFUint32",))
# registry of interface properties that should not be generated
# map: $java_type -> set(java_name_property)
read_blacklist = defaultdict(lambda: set(),
OFExperimenter=set(('data','subtype')),
OFActionExperimenter=set(('data',)),
OFExperimenterStatsRequest=set(('data','subtype')),
OFExperimenterStatsReply=set(('data','subtype')),
OFInstructionExperimenter=set(('data',)))
# map: $java_type -> set(java_name_property)
write_blacklist = defaultdict(
lambda: set(),
OFOxm=set(('typeLen',)),
OFAction=set(('type',)),
OFInstruction=set(('type',)),
OFFlowMod=set(('command', )),
OFExperimenter=set(('data','subtype')),
OFActionExperimenter=set(('data',)),
OFBsnTlv=set(('type',)))
# interfaces that are virtual
virtual_interfaces = set(['OFOxm', 'OFInstruction', 'OFFlowMod', 'OFBsnVport' ])
# Registry of nullable properties:
# ${java_class_name} -> set(${java_property_name})
nullable_map = defaultdict(lambda: set(),
)
# represents a subgroup of a bitmask enum that is actualy a normal enumerable within a masked part of the enum
# e.g., the flags STP.* in OF1.0 port state are bit mask entries, but instead enumerables according to the mask "STP_MASK"
# name: a name for the group
# mask: java name of the enum entry that defines the mask
# members: set of names of the members of the group
MaskedEnumGroup = namedtuple("MaskedEnumGroup", ("name", "mask", "members"))
# registry of MaskedEnumGroups (see above).
# map: ${java_enum_name}: tuple(MaskedEnumGroup)
masked_enum_groups = defaultdict(lambda: (),
OFPortState = (MaskedEnumGroup("stp_flags", mask="STP_MASK", members=set(("STP_LISTEN", "STP_LEARN", "STP_FORWARD", "STP_BLOCK"))), ),
OFConfigFlags = (
MaskedEnumGroup("frag_flags", mask="FRAG_MASK", members=set(("FRAG_NORMAL", "FRAG_DROP", "FRAG_REASM"))),
),
OFTableConfig = (
MaskedEnumGroup("table_miss_flags", mask="TABLE_MISS_MASK", members=set(("TABLE_MISS_CONTROLLER", "TABLE_MISS_CONTINUE", "TABLE_MISS_DROP"))),
),
OFGetConfigReply = (
MaskedEnumGroup("flags", mask="OFP_FRAG_MASK", members=set(("FRAG_NORMAL", "FRAG_DROP", "FRAG_REASM"))),
),
OFSetConfig = (
MaskedEnumGroup("flags", mask="OFP_FRAG_MASK", members=set(("FRAG_NORMAL", "FRAG_DROP", "FRAG_REASM"))),
),
)
# represents a metadata property associated with an EnumClass
# name:
class OFEnumPropertyMetadata(namedtuple("OFEnumPropertyMetadata", ("name", "type", "value"))):
"""
represents a metadata property associated with an Enum Class
@param name name of metadata property
@param type java_type instance describing the type
@value: Generator function f(entry) that generates the value
"""
@property
def variable_name(self):
return self.name[0].lower() + self.name[1:]
@property
def getter_name(self):
prefix = "is" if self.type == java_type.boolean else "get"
return prefix+self.name
""" Metadata container. """
OFEnumMetadata = namedtuple("OFEnumMetadata", ("properties", "to_string"))
def gen_port_speed(enum_entry):
""" Generator function for OFortFeatures.PortSpeed"""
splits = enum_entry.name.split("_")
if len(splits)>=2:
m = re.match(r'\d+[MGTP]B', splits[1])
if m:
return "PortSpeed.SPEED_{}".format(splits[1])
return "PortSpeed.SPEED_NONE";
def gen_stp_state(enum_entry):
""" Generator function for OFPortState.StpState"""
splits = enum_entry.name.split("_")
if len(splits)>=1:
if splits[0] == "STP":
return "true"
return "false"
# registry for metadata properties for enums
# map: ${java_enum_name}: OFEnumMetadata
enum_metadata_map = defaultdict(lambda: JavaModel.OFEnumMetadata((), None),
OFPortFeatures = OFEnumMetadata((OFEnumPropertyMetadata("PortSpeed", java_type.port_speed, gen_port_speed),), None),
OFPortState = OFEnumMetadata((OFEnumPropertyMetadata("StpState", java_type.boolean, gen_stp_state),), None),
)
@property
@memoize
def versions(self):
return OrderedSet( JavaOFVersion(ir_version) for ir_version in OFVersions.target_versions)
@property
@memoize
def interfaces(self):
interfaces = [ JavaOFInterface(ir_class) for ir_class in loxi_globals.unified.classes ]
interfaces = [ i for i in interfaces if i.name not in self.interface_blacklist ]
return interfaces
@memoize
def interface_by_name(self, name):
return find(lambda i: erase_type_annotation(i.name) == erase_type_annotation(name), self.interfaces)
@property
@memoize
def all_classes(self):
return [clazz for interface in self.interfaces for clazz in interface.versioned_classes]
@property
@memoize
def enums(self):
name_version_enum_map = OrderedDefaultDict(lambda: OrderedDict())
name_stable_map = {}
for version in self.versions:
logger.debug("version: {}".format(version.ir_version))
of_protocol = loxi_globals.ir[version.ir_version]
for enum in of_protocol.enums:
name_version_enum_map[enum.name][version] = enum
stable = (enum.params.get('stable') == 'True')
logger.debug("Enum: %s stable: %s", enum.name, stable)
if not enum.name in name_stable_map:
name_stable_map[enum.name] = stable
else:
if name_stable_map[enum.name] != stable:
raise Exception("Inconsistent enum stability (should be caught " +\
" by IR)")
enums = [ JavaEnum(name, name_stable_map[name], version_enum_map) for name, version_enum_map,
in name_version_enum_map.items() ]
# inelegant - need java name here
enums = [ enum for enum in enums if enum.name not in self.enum_blacklist ]
return enums
@memoize
def enum_by_name(self, name):
res = find(lambda e: e.name == name, self.enums)
if not res:
raise KeyError("Could not find enum with name %s" % name)
return res
@property
@memoize
def of_factories(self):
prefix = "org.projectfloodlight.openflow.protocol"
factories = OrderedDict()
sub_factory_classes = ("OFAction", "OFInstruction", "OFMeterBand", "OFOxm", "OFQueueProp", "OFErrorMsg", "OFActionId", "OFInstructionId", "OFBsnTlv")
for base_class in sub_factory_classes:
package = base_class[2:].lower()
remove_prefix = base_class[2].lower() + base_class[3:]
# HACK need to have a better way to deal with parameterized base classes
annotated_base_class = base_class + "<?>" if base_class == "OFOxm" else base_class
factories[base_class] = OFFactory(package="%s.%s" % (prefix, package),
name=base_class + "s", members=[], remove_prefix=remove_prefix, base_class=annotated_base_class, sub_factories={}, xid_generator= (base_class == "OFErrorMsg"))
factories[""] = OFFactory(
package=prefix,
name="OFFactory",
remove_prefix="",
members=[], base_class="OFMessage", sub_factories=OrderedDict(
("{}{}s".format(n[2].lower(), n[3:]), "{}s".format(n)) for n in sub_factory_classes ),
xid_generator=True)
for i in self.interfaces:
for n, factory in factories.items():
if n == "":
factory.members.append(i)
break
else:
super_class = self.interface_by_name(n)
if i.is_instance_of(super_class):
factory.members.append(i)
break
return factories.values()
@memoize
def factory_of(self, interface):
for factory in self.of_factories:
if interface in factory.members:
return factory
return None
def generate_class(self, clazz):
""" return wether or not to generate implementation class clazz.
Now true for everything except OFTableModVer10.
@param clazz JavaOFClass instance
"""
if clazz.interface.name.startswith("OFMatchV"):
return True
elif clazz.name == "OFTableModVer10":
# tablemod ver 10 is a hack and has no oftype defined
return False
if loxi_utils.class_is_message(clazz.interface.c_name):
return True
if loxi_utils.class_is_oxm(clazz.interface.c_name):
return True
if loxi_utils.class_is_action(clazz.interface.c_name):
return True
if loxi_utils.class_is_instruction(clazz.interface.c_name):
return True
else:
return True
@property
@memoize
def oxm_map(self):
OxmMapEntry = namedtuple("OxmMapEntry", ["type_name", "value", "masked" ])
return OrderedDict( (oxm.name, OxmMapEntry(type_name=oxm.member_by_name("value").java_type.public_type,
value=re.sub(r'^of_oxm_', r'', re.sub(r'_masked$', r'', oxm.ir_class.name)).upper(),
masked=oxm.ir_class.name.endswith("_masked")))
for oxm in self.interfaces if oxm.ir_class.is_subclassof("of_oxm") )
class OFFactory(namedtuple("OFFactory", ("package", "name", "members", "remove_prefix", "base_class", "sub_factories", "xid_generator"))):
@property
def factory_classes(self):
return [ OFFactoryClass(
package="org.projectfloodlight.openflow.protocol.ver{}".format(version.dotless_version),
name="{}Ver{}".format(self.name, version.dotless_version),
interface=self,
version=version
) for version in model.versions ]
def method_name(self, member, builder=True):
n = member.variable_name
if n.startswith(self.remove_prefix):
n = n[len(self.remove_prefix):]
n = n[0].lower() + n[1:]
if builder:
return "build" + n[0].upper() + n[1:]
else:
return n
def of_version(self, version):
for fc in self.factory_classes:
if fc.version == version:
return fc
return None
OFGenericClass = namedtuple("OFGenericClass", ("package", "name"))
class OFFactoryClass(namedtuple("OFFactoryClass", ("package", "name", "interface", "version"))):
@property
def base_class(self):
return self.interface.base_class
@property
def versioned_base_class(self):
base_class_interface = model.interface_by_name(self.interface.base_class)
if base_class_interface and base_class_interface.has_version(self.version):
return base_class_interface.versioned_class(self.version)
else:
return None
model = JavaModel()
#######################################################################
### OFVersion
#######################################################################
class JavaOFVersion(object):
""" Models a version of OpenFlow. contains methods to convert the internal
Loxi version to a java constant / a string """
def __init__(self, ir_version):
assert isinstance(ir_version, OFVersion)
self.ir_version = ir_version
self.int_version = self.ir_version.wire_version
@property
def dotless_version(self):
return self.ir_version.version.replace(".", "")
@property
def constant_version(self):
return "OF_" + self.dotless_version
def __repr__(self):
return "JavaOFVersion(%d)" % self.int_version
def __str__(self):
return self.ir_version.version
def __hash__(self):
return hash(self.ir_version)
def __eq__(self, other):
if other is None or type(self) != type(other):
return False
return (self.ir_version,) == (other.ir_version,)
#######################################################################
### Interface
#######################################################################
class JavaOFInterface(object):
""" Models an OpenFlow Message class for the purpose of the java class.
Version agnostic, in contrast to the loxi_ir python model.
"""
def __init__(self, ir_class):
""""
@param c_name: loxi style name (e.g., of_flow_add)
@param version_map map of { JavaOFVersion: OFClass (from loxi_ir) }
"""
self.ir_class = ir_class
self.c_name = ir_class.name
self.version_map = { JavaOFVersion(v): c for v,c in ir_class.version_classes.items() }
# name: the Java Type name, e.g., OFFlowAdd
self.name = java_class_name(self.c_name)
# variable_name name to use for variables of this type. i.e., flowAdd
self.variable_name = self.name[2].lower() + self.name[3:]
self.title_name = self.variable_name[0].upper() + self.variable_name[1:]
# name for use in constants: FLOW_ADD
self.constant_name = self.c_name.upper().replace("OF_", "")
pck_suffix, parent_interface, self.type_annotation = self.class_info()
self.package = "org.projectfloodlight.openflow.protocol.%s" % pck_suffix if pck_suffix else "org.projectfloodlight.openflow.protocol"
if self.name != parent_interface:
self.parent_interface = parent_interface
else:
self.parent_interface = None
@property
@memoize
def all_parent_interfaces(self):
return [ "OFObject" ] + \
([ self.parent_interface ] if self.parent_interface else [] )+ \
self.additional_parent_interfaces
@property
@memoize
def additional_parent_interfaces(self):
if loxi_utils.class_is_message(self.c_name) and not self.is_virtual:
m = re.match(r'(.*)Request$', self.name)
if m:
reply_name = m.group(1) + "Reply"
if model.interface_by_name(reply_name):
return ["OFRequest<%s>" % reply_name ]
elif self.name == "OFBundleCtrlMsg":
reply_name = "OFBundleCtrlMsg"
return ["OFRequest<%s>" % reply_name ]
return []
def is_instance_of(self, other_class):
if self == other_class:
return True
parent = self.super_class
if parent is None:
return False
else:
return parent.is_instance_of(other_class)
@property
def super_class(self):
if not self.parent_interface:
return None
else:
return model.interface_by_name(self.parent_interface)
def inherited_declaration(self, type_spec="?"):
if self.type_annotation:
return "%s<%s>" % (self.name, type_spec)
else:
return "%s" % self.name
@property
def type_variable(self):
if self.type_annotation:
return "<T>"
else:
return "";
def class_info(self):
""" return tuple of (package_prefix, parent_class) for the current JavaOFInterface"""
# FIXME: This code could be cleaned up further. Maybe some of the exceptions
# here could be folded into ir, or the type arithmetic specified in a more general
# fashion
def calc_package(i):
if i.is_subclassof("of_error_msg"):
return "errormsg"
elif i.is_instanceof("of_action"):
return "action"
elif i.is_instanceof("of_action_id"):
return "actionid"
elif i.is_instanceof("of_instruction"):
return "instruction"
elif i.is_instanceof("of_instruction_id"):
return "instructionid"
elif i.is_instanceof("of_oxm"):
return "oxm"
elif i.is_instanceof("of_meter_band"):
return "meterband"
elif i.is_instanceof("of_queue_prop"):
return "queueprop"
elif i.is_instanceof("of_bsn_tlv"):
return "bsntlv"
else:
return ""
def calc_super_name(i):
if re.match('of_match_.*', i.name):
return "Match"
else:
ir_super_class = self.ir_class.superclass
return java_class_name(ir_super_class.name) if ir_super_class else ""
package = calc_package(self.ir_class)
super_name = calc_super_name(self.ir_class)
if self.name == "OFStatsRequest":
# stats_requests are special because of their type annotation
return (package, "OFMessage", "T extends OFStatsReply")
elif self.ir_class.is_subclassof('of_stats_request'):
# stats_request subclasses are special because of their type annotation
reply_name = re.sub(r'Request$', 'Reply', self.name)
super_type_annotation = "T" if self.ir_class.virtual else reply_name
type_annotation = "T extends {}".format(reply_name) if self.ir_class.virtual \
else ""
return (package, "{}<{}>".format(super_name, super_type_annotation),
type_annotation)
elif self.name == "OFOxm":
return (package, None, "T extends OFValueType<T>")
elif loxi_utils.class_is_oxm(self.c_name):
# look up type from member value for OFValueType type annotation
if self.member_by_name("value") is not None:
return (package, "OFOxm<%s>" % self.member_by_name("value").java_type.public_type, None)
else:
return (package, "OFOxm", None)
else:
return (package, super_name, None)
@property
@memoize
def writeable_members(self):
return [ m for m in self.members if m.is_writeable ]
@memoize
def member_by_name(self, name):
return find(lambda m: m.name == name, self.members)
@property
@memoize
def members(self):
return self.ir_model_members + self.virtual_members
@property
@memoize
def ir_model_members(self):
"""return a list of all members to be exposed by this interface. Corresponds to
the union of the members of the vesioned classes without length, fieldlength
and pads (those are handled automatically during (de)serialization and not exposed"""
all_versions = []
member_map = collections.OrderedDict()
member_version_map = {}
for (version, of_class) in self.version_map.items():
for of_member in of_class.members:
if isinstance(of_member, OFLengthMember) or \
isinstance(of_member, OFFieldLengthMember) or \
isinstance(of_member, OFPadMember):
continue
java_member = JavaMember.for_of_member(self, of_member)
if of_member.name not in member_map:
member_map[of_member.name] = java_member
member_version_map[of_member.name] = version
else:
existing = member_map[of_member.name]
if existing.java_type.public_type != java_member.java_type.public_type:
raise Exception(
"Error constructing interface {}: type signatures do not match up between versions.\n"
" Member Name: {}\n"
" Existing: Version={}, Java={}, IR={}\n"
" New: Version={}, Java={}, IR={}"
.format(self.name, existing.name,
member_version_map[of_member.name], existing.java_type.public_type, existing.member.oftype,
version, java_member.java_type.public_type, java_member.member.oftype)
)
return tuple(m for m in member_map.values() if m.name not in model.read_blacklist[self.name])
@property
def virtual_members(self):
virtual_members = []
if self.name == "OFOxm":
virtual_members += [
JavaVirtualMember(self, "value", java_type.generic_t),
JavaVirtualMember(self, "mask", java_type.generic_t),
JavaVirtualMember(self, "matchField", java_type.make_match_field_jtype("T")),
JavaVirtualMember(self, "masked", java_type.boolean),
JavaVirtualMember(self, "canonical", java_type.make_oxm_jtype("T"))
]
elif self.ir_class.is_subclassof("of_oxm"):
value = find(lambda m: m.name=="value", self.ir_model_members)
if value:
field_type = java_type.make_match_field_jtype(value.java_type.public_type)
else:
field_type = java_type.make_match_field_jtype()
virtual_members += [
JavaVirtualMember(self, "matchField", field_type),
JavaVirtualMember(self, "masked", java_type.boolean),
JavaVirtualMember(self, "canonical", java_type.make_oxm_jtype(value.java_type.public_type),
custom_template=lambda builder: "OFOxm{}_getCanonical.java".format(".Builder" if builder else "")),
]
if not find(lambda x: x.name == "mask", self.ir_model_members):
virtual_members.append(
JavaVirtualMember(self, "mask", find(lambda x: x.name == "value", self.ir_model_members).java_type))
if not find(lambda m: m.name == "version", self.ir_model_members):
virtual_members.append(JavaVirtualMember(self, "version", java_type.of_version))
return tuple(virtual_members)
@property
@memoize
def is_virtual(self):
""" Is this interface virtual. If so, do not generate a builder interface """
return self.name in model.virtual_interfaces or all(ir_class.virtual for ir_class in self.version_map.values())
@property
def is_universal(self):
""" Is this interface universal, i.e., does it exist in all OF versions? """
return len(self.all_versions) == len(model.versions)
@property
@memoize
def all_versions(self):
""" return list of all versions that this interface exists in """
return self.version_map.keys()
def has_version(self, version):
return version in self.version_map
def versioned_class(self, version):
return JavaOFClass(self, version, self.version_map[version])
@property
@memoize
def versioned_classes(self):
return [ self.versioned_class(version) for version in self.all_versions ]
#######################################################################
### (Versioned) Classes
#######################################################################
class JavaOFClass(object):
""" Models an OpenFlow Message class for the purpose of the java class.
Version specific child of a JavaOFInterface
"""
def __init__(self, interface, version, ir_class):
"""
@param interface JavaOFInterface instance of the parent interface
@param version JavaOFVersion
@param ir_class OFClass from loxi_ir
"""
self.interface = interface
self.ir_class = ir_class
self.c_name = self.ir_class.name
self.version = version
self.constant_name = self.c_name.upper().replace("OF_", "")
self.package = "org.projectfloodlight.openflow.protocol.ver%s" % version.dotless_version
self.generated = False
@property
@memoize
def unit_test(self):
return JavaUnitTestSet(self)
@property
def name(self):
return "%sVer%s" % (self.interface.name, self.version.dotless_version)
@property
def variable_name(self):
return self.name[2].lower() + self.name[3:]
@property
def length(self):
if self.is_fixed_length:
return self.min_length
else:
raise Exception("No fixed length for class %s, version %s" % (self.name, self.version))
@property
def min_length(self):
""" @return the minimum wire length of an instance of this class in bytes """
return self.ir_class.base_length
@property
def is_fixed_length(self):
""" true iff this class serializes to a fixed length on the wire """
return self.ir_class.is_fixed_length and not self.is_virtual
def all_properties(self):
return self.interface.members
@property
@memoize
def data_members(self):
return [ prop for prop in self.members if prop.is_data ]
@property
@memoize
def fixed_value_members(self):
return [ prop for prop in self.members if prop.is_fixed_value ]
@property
@memoize
def public_members(self):
return [ prop for prop in self.members if prop.is_public ]
@property
@memoize
def members(self):
return self.ir_model_members + self.virtual_members
@property
@memoize
def ir_model_members(self):
members = [ JavaMember.for_of_member(self, of_member) for of_member in self.ir_class.members ]
return tuple(members)
@property
def virtual_members(self):
virtual_members = []
if self.ir_class.is_subclassof("of_oxm"):
value_member = find(lambda m: m.name, self.ir_model_members)
if value_member:
oxm_entry = model.oxm_map[self.interface.name]
virtual_members += [
JavaVirtualMember(self, "matchField", java_type.make_match_field_jtype(value_member.java_type.public_type), "MatchField.%s" % oxm_entry.value),
JavaVirtualMember(self, "masked", java_type.boolean, "true" if oxm_entry.masked else "false"),
]
else:
virtual_members += [
JavaVirtualMember(self, "matchField", java_type.make_match_field_jtype(), "null"),
JavaVirtualMember(self, "masked", java_type.boolean, "false"),
]
if not find(lambda m: m.name == "version", self.ir_model_members):
virtual_members.append(JavaVirtualMember(self, "version", java_type.of_version, "OFVersion.%s" % self.version.constant_version))
return tuple(virtual_members)
@memoize
def member_by_name(self, name):
return find(lambda m: m.name == name, self.members)
def all_versions(self):
return [ JavaOFVersion(int_version)
for int_version in of_g.unified[self.c_name]
if int_version != 'union' and int_version != 'object_id' ]
def version_is_inherited(self, version):
return 'use_version' in of_g.unified[self.ir_class.name][version.int_version]
def inherited_from(self, version):
return JavaOFVersion(of_g.unified[self.ir_class.name][version.int_version]['use_version'])
@property
def is_virtual(self):
return self.ir_class.virtual # type_maps.class_is_virtual(self.c_name) or self.ir_class.virtual
@property
def discriminator(self):
return find(lambda m: isinstance(m, OFDiscriminatorMember), self.ir_class.members)
@property
def is_extension(self):
return type_maps.message_is_extension(self.c_name, -1)
@property
def align(self):
return int(self.ir_class.params['align']) if 'align' in self.ir_class.params else 0
@property
def length_includes_align(self):
return self.ir_class.params['length_includes_align'] == "True" if 'length_includes_align' in self.ir_class.params else False
@property
@memoize
def superclass(self):
return find(lambda c: c.version == self.version and c.c_name == self.ir_class.superclass, model.all_classes)
@property
@memoize
def subclasses(self):
return [ c for c in model.all_classes if c.version == self.version and c.ir_class.superclass
and c.ir_class.superclass.name == self.c_name ]
#######################################################################
### Member
#######################################################################
class JavaMember(object):
""" Models a property (member) of an openflow class. """
def __init__(self, msg, name, java_type, member):
self.msg = msg
self.name = name
self.java_type = java_type
self.member = member
self.c_name = self.member.name if(hasattr(self.member, "name")) else ""
@property
def title_name(self):
return self.name[0].upper() + self.name[1:]
@property
def constant_name(self):
return self.c_name.upper()
@property
def getter_name(self):
return ("is" if self.java_type.public_type == "boolean" else "get") + self.title_name
@property
def setter_name(self):
return "set" + self.title_name
@property
def default_name(self):
if self.is_fixed_value:
return self.constant_name
else:
return "DEFAULT_"+self.constant_name
@property
def default_value(self):
if self.is_fixed_value:
return self.enum_value
else:
default = self.java_type.default_op(self.msg.version)
if default == "null" and not self.is_nullable:
return None
else:
return default
@property
def enum_value(self):
if self.name == "version":
return "OFVersion.%s" % self.msg.version.constant_version
java_type = self.java_type.public_type;
try:
global model
enum = model.enum_by_name(java_type)
entry = enum.entry_by_version_value(self.msg.version, self.value)
return "%s.%s" % ( enum.name, entry.name)
except KeyError, e:
logger.debug("No enum found for type %s version %s value %s", java_type, self.msg.version, self.value)
return self.value
@property
def is_pad(self):
return isinstance(self.member, OFPadMember)
def is_type_value(self, version=None):
if(version==None):
return any(self.is_type_value(version) for version in self.msg.all_versions)
try:
return self.c_name in get_type_values(self.msg.c_name, version.int_version)
except:
return False
@property
def is_field_length_value(self):
return isinstance(self.member, OFFieldLengthMember)
@property
def is_discriminator(self):
return isinstance(self.member, OFDiscriminatorMember)
@property
def is_length_value(self):
return isinstance(self.member, OFLengthMember)
@property
def is_public(self):
return not (self.is_pad or self.is_length_value)
@property
def is_data(self):
return isinstance(self.member, OFDataMember) and self.name != "version"
@property
def is_fixed_value(self):
return hasattr(self.member, "value") or self.name == "version" \
or ( self.name == "length" and self.msg.is_fixed_length) \
or ( self.name == "len" and self.msg.is_fixed_length)
@property
def value(self):
if self.name == "version":
return self.msg.version.int_version
elif self.name == "length" or self.name == "len":
return self.msg.length
else:
return self.java_type.format_value(self.member.value)
@property
def priv_value(self):
if self.name == "version":
return self.java_type.format_value(self.msg.version.int_version, pub_type=False)
elif self.name == "length" or self.name == "len":
return self.java_type.format_value(self.msg.length, pub_type=False)
else:
return self.java_type.format_value(self.member.value, pub_type=False)
@property
def is_writeable(self):
return self.is_data and not self.name in model.write_blacklist[self.msg.name]
def get_type_value_info(self, version):
return get_type_values(msg.c_name, version.int_version)[self.c_name]
@property
def length(self):
if hasattr(self.member, "length"):
return self.member.length
else:
count, base = loxi_utils.type_dec_to_count_base(self.member.type)
return of_g.of_base_types[base]['bytes'] * count
@staticmethod
def for_of_member(java_class, member):
if isinstance(member, OFPadMember):
return JavaMember(None, "", None, member)
else:
if member.name == 'len':
name = 'length'
elif member.name == 'value_mask':
name = 'mask'
elif member.name == 'group_id':
name = 'group'
else:
name = java_type.name_c_to_camel(member.name)
j_type = java_type.convert_to_jtype(java_class.c_name, member.name, member.oftype)
return JavaMember(java_class, name, j_type, member)
@property
def is_universal(self):
for version, ir_class in self.msg.ir_class.version_classes.items():
if not ir_class.member_by_name(self.member.name):
return False
return True
@property
def is_virtual(self):
return False
def __hash__(self):
return hash(self.name)
def __eq__(self, other):
if other is None or type(self) != type(other):
return False
return (self.name,) == (other.name,)
@property
def is_nullable(self):
return self.name in model.nullable_map[self.msg.name]
class JavaVirtualMember(JavaMember):
""" Models a virtual property (member) of an openflow class that is not backed by a loxi ir member """
def __init__(self, msg, name, java_type, value=None, custom_template=None):
JavaMember.__init__(self, msg, name, java_type, member=None)
self._value = value
self.custom_template = custom_template
@property
def is_fixed_value(self):
return True
@property
def value(self):
return self._value
@property
def priv_value(self):
return self._value
@property
def is_universal(self):
return True
@property
def is_virtual(self):
return True
#######################################################################
### Unit Test
#######################################################################
class JavaUnitTestSet(object):
def __init__(self, java_class):
self.java_class = java_class
first_data_file_name = "of{version}/{name}.data".format(version=java_class.version.dotless_version,
name=java_class.c_name[3:])
glob_file_name = "of{version}/{name}__*.data".format(version=java_class.version.dotless_version,
name=java_class.c_name[3:])
test_class_name = self.java_class.name + "Test"
self.test_units = []
if test_data.exists(first_data_file_name):
self.test_units.append(JavaUnitTest(java_class, first_data_file_name, test_class_name))
i = 1
for f in test_data.glob(glob_file_name):
m = re.match(".*__(.*).data", f)
if m:
suffix = java_type.name_c_to_caps_camel(m.group(1))
else:
suffix = str(i)
i += 1
test_class_name = self.java_class.name + suffix + "Test"
self.test_units.append(JavaUnitTest(java_class, f, test_class_name))
@property
def package(self):
return self.java_class.package
@property
def has_test_data(self):
return len(self.test_units) > 0
@property
def length(self):
return len(self.test_units)
def get_test_unit(self, i):
return self.test_units[i]
class JavaUnitTest(object):
def __init__(self, java_class, file_name=None, test_class_name=None):
self.java_class = java_class
if file_name is None:
self.data_file_name = "of{version}/{name}.data".format(version=java_class.version.dotless_version,
name=java_class.c_name[3:])
else:
self.data_file_name = file_name
if test_class_name is None:
self.test_class_name = self.java_class.name + "Test"
else:
self.test_class_name = test_class_name
@property
def package(self):
return self.java_class.package
@property
def name(self):
return self.test_class_name
@property
def interface(self):
return self.java_class.interface
@property
def has_test_data(self):
return test_data.exists(self.data_file_name)
@property
@memoize
def test_data(self):
return test_data.read(self.data_file_name)
#######################################################################
### Enums
#######################################################################
class JavaEnum(object):
def __init__(self, c_name, stable, version_enum_map):
self.c_name = c_name
self.stable = stable
self.name = "OF" + java_type.name_c_to_caps_camel("_".join(c_name.split("_")[1:]))
# Port_features has constants that start with digits
self.name_prefix = "PF_" if self.name == "OFPortFeatures" else ""
self.version_enums = version_enum_map
entry_name_version_value_map = OrderedDefaultDict(lambda: OrderedDict())
for version, ir_enum in version_enum_map.items():
for ir_entry in ir_enum.entries:
entry_name_version_value_map[ir_entry.name][version] = ir_entry.value
self.entries = [ JavaEnumEntry(self, name, version_value_map)
for (name, version_value_map) in entry_name_version_value_map.items() ]
self.entries = [ e for e in self.entries if e.name not in model.enum_entry_blacklist[self.name] ]
self.package = "org.projectfloodlight.openflow.protocol"
static_metadata = model.enum_metadata_map[self.name]
if self.stable:
# need this to look up wire_type, which does not matter
any_version = version_enum_map.keys()[0]
# if this is a 'stable' enum, i.e., its value won't change, add
# a "Metadata" (virtual) field "StableValue" to it that returns
# its wirevalue.
stable_value = JavaModel.OFEnumPropertyMetadata("StableValue",
self.wire_type(any_version),
value = lambda entry: entry.stable_value)
self.metadata = JavaModel.OFEnumMetadata(
properties=static_metadata.properties + (stable_value, ),
to_string=static_metadata.to_string
)
else:
self.metadata = static_metadata
def wire_type(self, version):
ir_enum = self.version_enums[version]
if "wire_type" in ir_enum.params:
return java_type.convert_enum_wire_type_to_jtype(ir_enum.params["wire_type"])
else:
return java_type.u8
@property
@memoize
def is_bitmask(self):
return any(ir_enum.is_bitmask for ir_enum in self.version_enums.values())
@property
def versions(self):
return self.version_enums.keys()
@memoize
def entry_by_name(self, name):
res = find(lambda e: e.name == name, self.entries)
if res:
return res
else:
raise KeyError("Enum %s: no entry with name %s" % (self.name, name))
@memoize
def entry_by_c_name(self, name):
res = find(lambda e: e.c_name == name, self.entries)
if res:
return res
else:
raise KeyError("Enum %s: no entry with c_name %s" % (self.name, name))
@memoize
def entry_by_version_value(self, version, value):
res = find(lambda e: e.values[version] == value if version in e.values else False, self.entries)
if res:
return res
else:
raise KeyError("Enum %s: no entry with version %s, value %s" % (self.name, version, value))
# values: Map JavaVersion->Value
class JavaEnumEntry(object):
def __init__(self, enum, name, values):
self.enum = enum
self.name = enum.name_prefix + "_".join(name.split("_")[1:]).upper()
self.values = values
@property
def constructor_params(self):
return [ (m.type, m.value(self)) for m in self.enum.metadata.properties ]
def has_value(self, version):
return version in self.values
def value(self, version):
return self.values[version]
def format_value(self, version):
res = self.enum.wire_type(version).format_value(self.values[version])
return res
def all_values(self, versions, not_present=None):
return [ self.values[version] if version in self.values else not_present for version in versions ]
@property
def stable_value(self):
if self.enum.stable:
return self.values.values()[0]
else:
raise Exception("Enum {} not stable".format(self.enum.name))
@property
@memoize
def masked_enum_group(self):
group = find(lambda g: self.name in g.members, model.masked_enum_groups[self.enum.name])
return group
@property
@memoize
def is_mask(self):
return any(self.name == g.mask for g in model.masked_enum_groups[self.enum.name])