com.btisystems.pronx.ems.MibEntityCompiler.java Source code

Java tutorial

Introduction

Here is the source code for com.btisystems.pronx.ems.MibEntityCompiler.java

Source

/**
 *                     GNU GENERAL PUBLIC LICENSE
 *                        Version 3, 29 June 2007
 *
 *  Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 *  Everyone is permitted to copy and distribute verbatim copies
 *  of this license document, but changing it is not allowed.
 *
 *  This program is free software: you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License as published
 *  by the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 for more details.
 */
package com.btisystems.pronx.ems;

import com.btisystems.pronx.ems.core.model.AbstractRootEntity;
import com.btisystems.pronx.ems.core.model.DeviceEntity;
import com.btisystems.pronx.ems.core.model.GeneratedIdentifiers;
import com.btisystems.pronx.ems.core.model.IDeviceEntity;
import com.btisystems.pronx.ems.core.model.IIndexed;
import com.btisystems.pronx.ems.core.model.ITableAccess;
import com.btisystems.pronx.ems.core.model.IVariableBindingSetter;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import com.sun.codemodel.JArray;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JCase;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JSwitch;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;
import net.percederberg.mibble.MibType;
import net.percederberg.mibble.MibValue;
import net.percederberg.mibble.MibValueSymbol;
import net.percederberg.mibble.snmp.SnmpIndex;
import net.percederberg.mibble.snmp.SnmpObjectType;
import net.percederberg.mibble.type.ChoiceType;
import net.percederberg.mibble.type.Constraint;
import net.percederberg.mibble.type.ObjectIdentifierType;
import net.percederberg.mibble.type.SizeConstraint;
import net.percederberg.mibble.type.StringType;
import net.percederberg.mibble.type.ValueConstraint;
import net.percederberg.mibble.value.NumberValue;
import net.percederberg.mibble.value.ObjectIdentifierValue;
import net.percederberg.mibble.value.StringValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.VariableBinding;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

/**
 * Manages the 'compilation' of a Mib, by generating Java entity classes for
 * each of the managed objects.
 */
public class MibEntityCompiler extends AbstractMibCompiler {

    private static final String INTERFACE_PREFIX = "I";

    private static final Logger LOG = LoggerFactory.getLogger(MibEntityCompiler.class);

    private static final int PUBLIC_FINAL_STATIC = JMod.PUBLIC | JMod.FINAL | JMod.STATIC;

    private final Map<String, List<MibValueSymbol>> rootSymbolMap;
    private final Map<String, JDefinedClass> oidClasses = new TreeMap<>();
    private final Map<String, JDefinedClass> interfaceMap;
    public static final Set<String> IMPORTED_PACKAGES = new HashSet<>();
    public static final Set<String> IMPORTED_SYMBOLS = new HashSet<>();

    /**
     * Maintains an occurrence count for each unqualified class name. Used to
     * assign a numeric suffix for names of tables and fields that are derived
     * from the class name.
     */
    private final Map<String, Integer> classNameOccurrences = new HashMap<>();

    /**
     * Class constructor.
     *
     * @param symbolMap associates the root OIDs with the symbols that belong to
     *            the associated tree
     * @param packageName the name of the package to which classes are to be
     *            added
     * @param interfaceMap associates an OID with the {@link JDefinedClass} that
     *            defines the interface to be implemented by an entity derived
     *            from that OID.
     */
    public MibEntityCompiler(final Map<String, List<MibValueSymbol>> symbolMap, final String packageName,
            final Map<String, JDefinedClass> interfaceMap) {
        super(packageName);
        this.rootSymbolMap = symbolMap;
        this.interfaceMap = interfaceMap;
    }

    public MibEntityCompiler(final Map<String, List<MibValueSymbol>> symbolMap, final String packageName,
            final Map<String, JDefinedClass> interfaceMap, final JCodeModel codeModel) {
        this(symbolMap, packageName, interfaceMap);
        this.codeModel = codeModel;

    }

    /**
     * Class constructor.
     *
     * @param symbolMap associates the root OIDs with the symbols that belong to
     *            the associated tree
     * @param packageName the name of the package to which classes are to be
     *            added
     */
    public MibEntityCompiler(final Map<String, List<MibValueSymbol>> symbolMap, final String packageName) {
        this(symbolMap, packageName, null);
    }

    /**
     * Compile the Mib to java code
     *
     * @param codeModel the Java Code Model which will hold the generated code
     */
    public final void compile(final JCodeModel codeModel) {
        this.codeModel = codeModel;
        for (final Entry<String, List<MibValueSymbol>> rootEntry : rootSymbolMap.entrySet()) {
            for (final MibValueSymbol child : rootEntry.getValue()) {
                buildClass(createEntityPackageName(rootEntry.getKey(), child), child, null);
            }
        }
    }

    /**
     * Compile interfaces.
     *
     * @param codeModel the code model
     * @param commonIdentifiers the common identifiers
     */
    public final void compileInterfaces(final JCodeModel codeModel, final Set<MibValueSymbol> commonIdentifiers) {
        this.codeModel = codeModel;
        for (final Entry<String, List<MibValueSymbol>> rootEntry : rootSymbolMap.entrySet()) {
            for (final MibValueSymbol child : rootEntry.getValue()) {
                buildInterface(createEntityPackageName(rootEntry.getKey(), child), child, commonIdentifiers);
            }
        }
    }

    private JClass buildInterface(final String packageName, final MibValueSymbol entity,
            final Set<MibValueSymbol> commonIdentifiers) {
        LOG.debug("build Interface:{}.{}", packageName, getInterfaceName(entity.getName()));
        final JDefinedClass definedInterface = createInterface(packageName, getInterfaceName(entity.getName()));
        definedInterface._extends(IDeviceEntity.class);

        definedInterface.metadata = new ClassMetadata(definedInterface);

        // TODO SJ If it's a table and there is an interface for the
        // corresponding entry, let the interface
        // extend ITableAccess<entryInterface>

        final IClassBuilder interfaceBuilder = new IClassBuilder() {

            @Override
            public JClass buildCodeForClass(final String packageName, final MibValueSymbol entity) {
                return buildInterface(packageName, entity, commonIdentifiers);
            }
        };

        for (final MibValueSymbol child : entity.getChildren()) {
            if (child != null && commonIdentifiers.contains(child)) {
                LOG.debug("Identifier is common and will be included:{}", child);

                // Establish type of field - this will create a new class if
                // necessary.
                final JType type = getFieldType(generateChildPackageName(packageName, entity.getName()), child,
                        interfaceBuilder);
                if (type == null) {
                    LOG.debug("Not generating empty entity:{}", child.getName());
                } else {
                    final String fieldName = getMappedName(child.getName());
                    if (!child.isTableRow()) {
                        final JMethod setMethod = definedInterface.method(JMod.NONE, codeModel.VOID,
                                getSetterName(fieldName));
                        setMethod.param(type, fieldName);
                    }
                    definedInterface.method(JMod.NONE, type, getGetterName(fieldName));
                    if (isIsDefinedMethodRequired(child, type)) {
                        definedInterface.method(JMod.NONE, codeModel.BOOLEAN, getIsDefinedName(fieldName));
                    }
                }
            }
        }

        // Add clone to interface
        definedInterface.method(JMod.NONE, definedInterface, "clone");

        addToOidClassMap(entity, definedInterface);
        return definedInterface;
    }

    private boolean isIsDefinedMethodRequired(final MibValueSymbol child, final JType type) {
        if (type == codeModel.INT || type == codeModel.LONG) {
            if (getFieldDefaultValue(child) instanceof NumberValue) {
                return true;
            }
        } else if (type == codeModel.ref(String.class) && getFieldDefaultValue(child) instanceof StringValue) {
            return true;
        }
        return false;
    }

    /**
     * Generate root entity collection.
     *
     * @param classes the classes
     * @return the collection
     */
    public final Collection<String> generateRootEntity(final Map<String, JDefinedClass> classes) {
        final JDefinedClass rootClass = createRootClass();
        final Map<String, JFieldVar> oidRootFieldMap = new HashMap<>();

        // importDependencies();

        for (final Entry<String, JDefinedClass> classEntry : classes.entrySet()) {

            final JDefinedClass topLevelClass = classEntry.getValue();

            // Table rows don't appear in the root entity (yet).
            final Boolean isTableRow = ((ClassMetadata) topLevelClass.metadata).isTableRow();
            if (!isTableRow) {
                // Add the field to the class.
                LOG.debug("class name:{} {}", topLevelClass.fullName(), topLevelClass.name());

                final String fieldName = getUniqueClassIdentifier(topLevelClass);
                final JFieldVar rootField = rootClass.field(JMod.PRIVATE, topLevelClass, fieldName);
                oidRootFieldMap.put(classEntry.getKey(), rootField);
                // Generate accessor methods, but no need for notify on root
                // entity
                generateAccessors(rootClass, rootField.type(), fieldName, -1);
            }
        }

        addStandardMethods(rootClass);

        generateGetRootsMethod(rootClass, oidRootFieldMap);

        generateClassRegistry();

        final Collection<String> classNames = new ArrayList<>();
        classNames.add(rootClass.fullName());
        for (final JDefinedClass clazz : classes.values()) {
            classNames.add(clazz.fullName());
        }
        return classNames;
    }

    private String getUniqueClassIdentifier(final JDefinedClass definedClass) {
        final String name = ((ClassMetadata) definedClass.metadata).getUniqueIdentifier();
        return decapitalizeFirstCharacter(name);
    }

    /**
     * Generate class with map, mapping OIDs to classes.
     */
    private void generateClassRegistry() {
        final JDefinedClass registryClass = createClass(packageName, GeneratedIdentifiers.OID_REGISTRY_CLASSNAME);

        // Create TreeMap, initialised by call to static function.
        JClass typeVar = codeModel.ref(TreeMap.class);
        typeVar = typeVar.narrow(OID.class)
                .narrow(codeModel.ref(Class.class).narrow(codeModel.ref(DeviceEntity.class).wildcard()));
        final JFieldVar registryMap = registryClass.field(PUBLIC_FINAL_STATIC, typeVar, "oidRegistry");

        final JMethod initMethod = registryClass.method(JMod.STATIC | JMod.PRIVATE, typeVar, "createMap");
        registryMap.init(JExpr.invoke(initMethod));

        // Instantiate the map.
        final JVar localVar = initMethod.body().decl(typeVar, "map");
        localVar.init(JExpr._new(typeVar));

        // Populate it
        final JClass oidTypeVar = codeModel.ref(OID.class);
        for (final Entry<String, JDefinedClass> entry : oidClasses.entrySet()) {

            // Generate a put of the form:
            // map.put(new OID("1.2.3.3"), Pojo.class);
            final JInvocation putInvocation = initMethod.body().invoke(JExpr.ref("map"), "put");

            final JInvocation newInvocation = JExpr._new(oidTypeVar);
            newInvocation.arg(entry.getKey());

            putInvocation.arg(newInvocation);
            putInvocation.arg(entry.getValue().dotclass());
        }

        // Return the map.
        initMethod.body()._return(JExpr.ref("map"));

    }

    public void importDependencies() {
        try {
            final ImmutableSet<ClassPath.ClassInfo> classes = ClassPath.from(this.getClass().getClassLoader())
                    .getAllClasses();
            for (ClassPath.ClassInfo clazz : classes) {
                if (clazz.getSimpleName().equals(GeneratedIdentifiers.OID_REGISTRY_CLASSNAME)) {
                    LOG.info("Adding OIDs from registry: {}", clazz.getName());
                    final Class loadedClass = this.getClass().getClassLoader().loadClass(clazz.getName());
                    final Field field = loadedClass.getDeclaredField("oidRegistry");
                    final TreeMap<OID, Class<? extends DeviceEntity>> map = (TreeMap<OID, Class<? extends DeviceEntity>>) field
                            .get(null);
                    for (Entry<OID, Class<? extends DeviceEntity>> entry : map.entrySet()) {
                        if (!oidClasses.containsKey(entry.getKey().toString())) {
                            final String fullClassName = getFullClassName(entry.getValue().getPackage().getName(),
                                    entry.getValue().getSimpleName());
                            if (codeModel._getClass(fullClassName) == null) {
                                final JDefinedClass importedClass = createClass(
                                        entry.getValue().getPackage().getName(), entry.getValue().getSimpleName());
                                final ClassMetadata metadata = new ClassMetadata(importedClass);
                                final boolean isTableRow = IIndexed.class.isAssignableFrom(entry.getValue());
                                metadata.setTableRow(isTableRow);
                                importedClass.metadata = metadata;
                                oidClasses.put(entry.getKey().toString(), importedClass);
                                IMPORTED_PACKAGES.add(entry.getValue().getPackage().getName());
                                IMPORTED_SYMBOLS.add(entry.getKey().toString());
                            } else {
                                oidClasses.put(entry.getKey().toString(), codeModel._getClass(fullClassName));
                            }
                        }
                    }
                }
            }
        } catch (Exception ex) {
            LOG.error("Exception adding classpath OIDS.", ex);
        }
    }

    private void generateGetRootsMethod(final JDefinedClass rootClass,
            final Map<String, JFieldVar> oidRootFieldMap) {
        final JMethod method = rootClass.method(JMod.PUBLIC, codeModel._ref(DeviceEntity[].class), "getRoots");

        final JArray objectArray = JExpr.newArray(codeModel._ref(DeviceEntity.class));
        List<Entry<String, JFieldVar>> entries = new ArrayList<>(oidRootFieldMap.entrySet());
        Collections.sort(entries, new Comparator<Entry<String, JFieldVar>>() {
            @Override
            public int compare(Entry<String, JFieldVar> o1, Entry<String, JFieldVar> o2) {
                return o1.getKey().compareTo(o2.getKey());
            }
        });
        for (final Entry<String, JFieldVar> entry : entries) {
            objectArray.add(entry.getValue());
        }

        method.body()._return(objectArray);
    }

    /**
     * Create the root class.
     * 
     * @return the root class
     */
    private JDefinedClass createRootClass() {
        final JDefinedClass rootClass = createClass(packageName, GeneratedIdentifiers.ROOT_CLASS_NAME);
        rootClass._extends(AbstractRootEntity.class);
        return rootClass;
    }

    /**
     * Gets entity classes.
     *
     * @return the list of full names of the entity classes generated from the
     *         MIB
     */
    public final Map<String, JDefinedClass> getEntityClasses() {
        return oidClasses;
    }

    private String createEntityPackageName(final String rootOid, final MibValueSymbol child) {
        final List<String> names = new ArrayList<>();
        MibValueSymbol parent = child;

        while (!rootOid.equals(getOidFromSymbol(parent)) && parent.getParent() != null) {
            parent = parent.getParent();
            names.add(parent.getName());
        }
        String classPackageName = packageName;
        int nameIndex = names.size();
        while (nameIndex-- > 0) {
            classPackageName = generateChildPackageName(classPackageName, names.get(nameIndex));
        }
        LOG.debug("packageName:{}", classPackageName);
        return classPackageName;
    }

    /**
     * Build a class associated with a Mib Symbol, recursively creating any
     * classes on which the class depends.
     * 
     * @param packageName
     * @param rootSymbol
     * @param parentClass
     * @return
     */
    private JDefinedClass buildClass(final String packageName, final MibValueSymbol rootSymbol,
            final JDefinedClass parentClass) {
        LOG.debug(">>> buildClass package:{} class:{}", packageName, rootSymbol.getName());

        // Create the class, constructor and annotate.
        final JDefinedClass definedClass = createClass(packageName, getClassName(rootSymbol.getName()));
        definedClass.constructor(JMod.PUBLIC);
        definedClass._implements(Serializable.class);
        definedClass._extends(DeviceEntity.class);

        final JDefinedClass abstractInterface = getInterfaceForSymbol(rootSymbol);
        if (abstractInterface != null) {
            LOG.debug("Got interface for {} {}", getOidFromSymbol(rootSymbol), abstractInterface);
            definedClass._implements(abstractInterface);
        }

        definedClass.metadata = createClassMetaData(definedClass, rootSymbol);

        // Create class fields, creating any nested classes as necessary
        final List<MibValueSymbol> children = createChildFields(packageName, rootSymbol, definedClass);

        if (doesObjectIncludeScalarTypes(children)) {
            definedClass._implements(IVariableBindingSetter.class);
            generateSetFromVariableBindingMethod(definedClass, rootSymbol, children);
        }

        if (rootSymbol.isTable()) {
            final MibValueSymbol entrySymbol = children.get(0);
            definedClass._implements(codeModel.ref(ITableAccess.class).narrow(getInterfaceFromOid(entrySymbol)));
            generateTableAccessMethods(definedClass, entrySymbol);
        }

        if (rootSymbol.isTableRow()) {
            createIndexMethod(rootSymbol, definedClass);
            createRelationshipToTable(definedClass, parentClass);
        }

        addStandardMethods(definedClass);

        addToOidClassMap(rootSymbol, definedClass);
        createClassPersistenceAnnotations(definedClass, rootSymbol);

        // Generate all the metadata the class will need.
        generateClassMetadata(rootSymbol, definedClass, children);

        return definedClass;
    }

    private Object createClassMetaData(final JDefinedClass definedClass, final MibValueSymbol rootSymbol) {
        final ClassMetadata metadata = new ClassMetadata(definedClass);
        metadata.setTableRow(rootSymbol.isTableRow());
        return metadata;
    }

    /**
     * Create the child fields for the object.
     * 
     * @param packageName
     * @param rootSymbol
     * @param parentClass
     * @return
     */
    private List<MibValueSymbol> createChildFields(final String packageName, final MibValueSymbol rootSymbol,
            final JDefinedClass parentClass) {
        final MibValueSymbol[] children = rootSymbol.getChildren();
        final List<MibValueSymbol> childEntities = new ArrayList<>();
        final IClassBuilder classBuilder = new IClassBuilder() {
            @Override
            public JClass buildCodeForClass(final String packageName, final MibValueSymbol entity) {
                return buildClass(packageName, entity, parentClass);
            }
        };

        for (final MibValueSymbol child : children) {
            if (child != null) {

                // Establish type of field - this will create a new class if
                // necessary.
                final JType type = getFieldType(generateChildPackageName(packageName, rootSymbol.getName()), child,
                        classBuilder);
                if (type == null) {
                    LOG.debug("Not generating empty entity:{}", child.getName());
                } else {
                    final String fieldName = getMappedName(child.getName());
                    createField(child, fieldName, type, parentClass);
                    childEntities.add(child);
                }
            }
        }
        return childEntities;
    }

    /**
     * Establishes the relationship from a table row entry to its parent table
     * 
     * @param definedClass
     * @param parentClass
     */
    private void createRelationshipToTable(final JDefinedClass definedClass, final JDefinedClass parentClass) {

        final JFieldVar tableVar = definedClass.field(JMod.PRIVATE, parentClass, PARENT_ENTITY_IDENTIFIER);

        // Define setTable method signature: public void _setTable(tableClass
        // table)
        final JMethod setMethod = definedClass.method(JMod.PUBLIC, codeModel.VOID,
                GeneratedIdentifiers.METHOD_SET_TABLE);
        final JVar tableParameter = setMethod.param(parentClass, "table");

        final JBlock body = setMethod.body();
        body.assign(JExpr._this().ref(tableVar), tableParameter);
    }

    /**
     * Creates index details if the symbol defines an indexed object (i.e. one
     * that resides in a table).
     * 
     * @param rootSymbol
     * @param definedClass
     */
    private void createIndexMethod(final MibValueSymbol rootSymbol, final JDefinedClass definedClass) {

        LOG.debug("createIndexMethod root:{} class:{}", rootSymbol, definedClass);

        // Get the index details retrieved from the MIB
        final ArrayList<SnmpIndex> indices = getIndexInformation(rootSymbol);

        // Make the class implement IIndexed, with a method to set its index
        // from an OID.
        definedClass._implements(IIndexed.class);

        // ==============================================================================================
        // Creates a public method to set the index variables of an entity,
        // given an OID of an
        // instance of any of the variables in the entity.
        //
        // Example:
        // If the index looks like: Index { intField1, stringField, intField2 }
        // and the OID of the entity is: 1.4.5.100.1.2
        // the body of the method would look something like:
        //
        // byte[] oidBytes = oid.toByteArray();
        // int[] oidInts = oid.toIntArray();
        // int index = 7; // the offset of the first sub-identifier of the index
        // int len; // length of String index
        //
        // // Get index field intField1
        // setIntField1(oidInts[n]); // See notes
        // n++;
        // // Get index field stringField
        // len = oidInts[n];
        // n++
        // setStringField(new String(oidBytes, n, len)); // See notes
        // n = n + len;
        // // Get index field intField1
        // setIntField2(oidInts[n]); // See notes
        // n++;
        //
        // So, presented with the OID: 1.4.5.100.1.2.1.17.3.65.66.67.18
        // it will assign:
        // 17 to intField1
        // ABC to stringField
        // 18 to intField2
        //
        // Notes
        // - we only need to call the set method for those indexes that are
        // local to the entry that is
        // being indexed.
        // ==============================================================================================

        // Define the method signature: public void _setIndex(OID oid)
        final JMethod setMethod = definedClass.method(JMod.PUBLIC, codeModel.VOID,
                GeneratedIdentifiers.METHOD_SET_INDEX);
        final JVar oidParam = setMethod.param(OID.class, "oid");

        // Establish the zero-based sub-identifier in the OID that defines the
        // first index value.
        // This will be the length of the OID for the containing object + 1
        // (allowing for the sub-identifier
        // that identifies the variable being updated by the OID.
        final int elementOffset = getOidPartCount(rootSymbol) + 1;

        final JBlock body = setMethod.body();

        // Declare local variables
        final JVar byteArrayVar = body.decl(codeModel._ref(byte[].class), "_oidBytes");
        byteArrayVar.init(JExpr.invoke(oidParam, "toByteArray"));

        final JVar intArrayVar = body.decl(codeModel._ref(int[].class), "_oidInts");
        intArrayVar.init(JExpr.invoke(oidParam, "toIntArray"));

        final JVar arrayOffsetVar = body.decl(codeModel.INT, "arrayOffset");
        arrayOffsetVar.init(JExpr.lit(elementOffset));
        final JVar lenVar = body.decl(codeModel.INT, "_len");

        // Create code to save off the index as a string.
        saveIndexAsString(definedClass, body, elementOffset, intArrayVar, oidParam);

        // Create an assignment for each part of the index.
        createIndexPartAssignments(rootSymbol, indices, body, byteArrayVar, intArrayVar, arrayOffsetVar, lenVar);

    }

    private void createIndexPartAssignments(final MibValueSymbol rootSymbol, final ArrayList<SnmpIndex> indices,
            final JBlock body, final JVar byteArrayVar, final JVar intArrayVar, final JVar arrayOffsetVar,
            final JVar lenVar) {
        for (final SnmpIndex index : indices) {

            // Get the child variable for this index
            String indexFieldName = null;
            MibValueSymbol indexField = getChildByOid(rootSymbol, (ObjectIdentifierValue) index.getValue());

            boolean isLocalIndex = false;
            // If the index field is not part of the entry itself, look for it
            // across all the mibs.
            if (indexField == null) {
                indexField = getSymbolByOid(rootSymbol, (ObjectIdentifierValue) index.getValue());
            } else {
                indexFieldName = indexField.getName();
                isLocalIndex = true;
            }

            if (indexField != null) {

                final JType indexType = mapObjectTypeToJavaType(indexField.getType());
                LOG.debug("index Field:{} type:{}", indexField, indexType);
                if (indexType == codeModel.INT) {
                    createIntegerIndexPartAssignment(body, intArrayVar, arrayOffsetVar, indexFieldName,
                            isLocalIndex);

                } else if (indexType == codeModel.ref(String.class)) {
                    createStringIndexPartAssignment(body, byteArrayVar, intArrayVar, arrayOffsetVar, lenVar, index,
                            indexFieldName, indexField, isLocalIndex);

                } else {
                    LOG.warn("Ignoring non-integer index:{} type:{}", index.getValue(), indexType);
                }
            } else {
                LOG.warn("Failed to find index field:{} referenced by:{}", index.getValue(),
                        getOidFromSymbol(rootSymbol));
            }
        }
    }

    @SuppressWarnings("checkstyle:parameternumber")
    private void createStringIndexPartAssignment(final JBlock body, final JVar byteArrayVar, final JVar intArrayVar,
            final JVar arrayOffsetVar, final JVar lenVar, final SnmpIndex index, final String indexFieldName,
            final MibValueSymbol indexField, final boolean isLocalIndex) {
        // Determine how to extract String values from indexfield
        final IIndexFieldConverter indexConverter = getIndexConverter(indexField);

        if (index.isImplied()) {
            if (isLocalIndex) {
                LOG.debug("implied length");
                // Implied length string.
                // Generate:
                // len = oidBytes.length - _index;
                // followed by one of:
                // o setIndexName(new String(oidBytes, _index, len));
                // o setIndexName(_getObjectIdentifier(oidInts, _index, len)); -
                // for an object identifier
                body.assign(lenVar, byteArrayVar.ref("length").minus(arrayOffsetVar));
                final JInvocation setterInvocation = body.invoke(getSetterName(indexFieldName));
                setterInvocation.arg(indexConverter.convert(byteArrayVar, intArrayVar, lenVar, arrayOffsetVar));
            }
        } else {
            final int stringLength = getIndexStringLength(indexField.getType());
            LOG.debug("stringLength {}", stringLength);
            if (stringLength == -1) {
                // Variable length string.
                // Generate:
                // len = oidBytes[_index++];
                // followed by one of:
                // o setIndexName(new String(oidBytes, _index, len));
                // o setIndexName(_getObjectIdentifier(oidInts, _index, len)); -
                // for an object identifier
                // followd by:
                // _index += len;
                body.assign(lenVar, intArrayVar.component(arrayOffsetVar.incr()));
                if (isLocalIndex) {
                    final JInvocation setterInvocation = body.invoke(getSetterName(indexFieldName));
                    setterInvocation.arg(indexConverter.convert(byteArrayVar, intArrayVar, lenVar, arrayOffsetVar));
                }
                body.assignPlus(arrayOffsetVar, lenVar);
            } else {
                // Fixed length string.
                // Generate:
                // setIndexName(new String(oidBytes, _index,
                // stringLength));
                // _index += stringLength;
                if (isLocalIndex) {
                    final JInvocation setterInvocation = body.invoke(getSetterName(indexField));
                    final JInvocation newString = JExpr._new(codeModel.ref(String.class));
                    newString.arg(byteArrayVar).arg(arrayOffsetVar).arg(JExpr.lit(stringLength));
                    setterInvocation.arg(indexConverter.convert(byteArrayVar, intArrayVar, JExpr.lit(stringLength),
                            arrayOffsetVar));
                }
                body.assignPlus(arrayOffsetVar, JExpr.lit(stringLength));
            }
        }
    }

    private void createIntegerIndexPartAssignment(final JBlock body, final JVar intArrayVar,
            final JVar arrayOffsetVar, final String indexFieldName, final boolean isLocalIndex) {
        // Call the setter for the index variable, for example:
        // setIndexName(oidBytes[_index]);
        // _index++;
        if (isLocalIndex) {
            final JInvocation setterInvocation = body.invoke(getSetterName(indexFieldName));
            setterInvocation.arg(intArrayVar.component(arrayOffsetVar));
        }
        body.assignPlus(arrayOffsetVar, JExpr.lit(1));
    }

    private void saveIndexAsString(final JDefinedClass definedClass, final JBlock body, final int elementOffset,
            final JVar intArrayVar, final JVar oidParam) {

        final JVar indexVar = definedClass.field(JMod.PRIVATE, codeModel.ref(String.class), "_index");

        // Generate statement of the form:
        // _index = new OID(intArray, 14, oid.size() - 14).toString()
        body.assign(indexVar, JExpr._new(codeModel.ref(OID.class)).arg(intArrayVar).arg(JExpr.lit(elementOffset))
                .arg(oidParam.invoke("size").minus(JExpr.lit(elementOffset))).invoke("toString"));

        // Create method to return the index.
        definedClass.method(JMod.PUBLIC, codeModel.ref(String.class), "_getIndex").body()._return(indexVar);
    }

    /**
     * Retrieve the index information associated with a managed object.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private ArrayList<SnmpIndex> getIndexInformation(final MibValueSymbol rootSymbol) {
        final SnmpObjectType objectType = (SnmpObjectType) rootSymbol.getType();
        final ArrayList indices = objectType.getIndex();

        LOG.debug("entry:{} count:{}", rootSymbol.getValue(), indices.size());
        return indices;
    }

    /**
     * Find the child of a parent, matching a specified OID.
     * 
     * @param rootSymbol
     * @param targetId
     * @return
     */
    private MibValueSymbol getChildByOid(final MibValueSymbol rootSymbol, final ObjectIdentifierValue targetId) {
        for (final MibValueSymbol child : rootSymbol.getChildren()) {
            if (child != null) {
                final ObjectIdentifierValue childId = (ObjectIdentifierValue) child.getValue();
                if (childId.compareTo(targetId) == 0) {
                    return child;
                }
            }
        }
        return null;
    }

    /**
     * Create field in a class.
     * 
     * @param child
     * @param fieldName
     * @param type
     * @param definedClass
     */
    private void createField(final MibValueSymbol child, final String fieldName, final JType type,
            final JDefinedClass definedClass) {

        LOG.debug("Add field {} to class {}", fieldName, definedClass);

        // Add the field to the class.
        final JFieldVar field = definedClass.field(JMod.PRIVATE, getFieldImplementationType(child, type),
                fieldName);

        // Initialise the field if it's a collection.
        if (child.isTableRow()) {
            final JClass typeVar = codeModel.ref(TreeMap.class);
            final JDefinedClass childClass = getInterfaceFromOid(child);
            field.init(JExpr._new(typeVar.narrow(codeModel.ref(String.class), childClass)));
        }

        generateAccessors(definedClass, child, type, fieldName);
    }

    /**
     * Determine the field implementation type. If a long/integer type has a
     * default value, use Integer/Long rather than the primitive type.
     * 
     * @param child
     * @param type
     * @return
     */
    private JType getFieldImplementationType(final MibValueSymbol child, final JType type) {
        if (fieldHasDefaultValue(child)) {
            if (type == codeModel.INT) {
                return codeModel.ref(Integer.class);
            }
            if (type == codeModel.LONG) {
                return codeModel.ref(Long.class);
            }
        }
        return type;
    }

    private void createClassPersistenceAnnotations(final JDefinedClass definedClass,
            final MibValueSymbol rootSymbol) {

        if (!rootSymbol.isTableRow()) {
            final JFieldVar parentField = definedClass.field(JMod.PRIVATE, AbstractRootEntity.class,
                    PARENT_ENTITY_IDENTIFIER);

            final JMethod setMethod = definedClass.method(JMod.PUBLIC, codeModel.VOID, "set_ParentEntity");
            final JVar parentParam = setMethod.param(AbstractRootEntity.class, "parent");

            setMethod.body().assign(parentField, parentParam);
        }
    }

    private boolean doesObjectIncludeScalarTypes(final List<MibValueSymbol> childEntities) {
        for (final MibValueSymbol child : childEntities) {
            // If it's a scalar or a table column...
            if (isSimpleType(child)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Generate methods to access table entries.
     * 
     * @param definedClass
     * @param entrySymbol
     */
    private void generateTableAccessMethods(final JDefinedClass definedClass, final MibValueSymbol entrySymbol) {

        final String entryName = getMappedName(entrySymbol.getName());
        final JDefinedClass entryClass = getClassFromOid(entrySymbol);
        final JDefinedClass interfaceClass = getInterfaceFromOid(entrySymbol);

        generateGetEntryMethod(definedClass, entryName, interfaceClass);

        generateSetEntryMethod(definedClass, entryName, interfaceClass, entryClass);

        generateGetEntriesMethod(definedClass, entryName, interfaceClass);

        generateCreateEntryMethod(definedClass, entryClass, getOidFromSymbol(entrySymbol));
    }

    private JDefinedClass getInterfaceForSymbol(final MibValueSymbol entrySymbol) {
        if (interfaceMap != null) {
            return interfaceMap.get(getOidFromSymbol(entrySymbol));
        }
        return null;
    }

    private void generateCreateEntryMethod(final JDefinedClass definedClass, final JDefinedClass entryClass,
            final String entryOid) {

        final JMethod createMethod = definedClass.method(JMod.PUBLIC, entryClass, "createEntry");
        final JVar entryIndex = createMethod.param(String.class, "entryIndex");
        final JBlock body = createMethod.body();

        // Method will look like:
        //
        // public T createEntry(String index) {
        // T newEntry = new T();
        // newEntry._setIndex(new OID(tableOID + ".0." + index));
        // setEntry(index, newEntry);
        // return newEntry;
        // }
        // Note that the index is set before the entry is added to the table.
        // This means that property change events will NOT be fired by any
        // changes made by
        // the _setIndex method.

        final JVar newEntry = body.decl(entryClass, "newEntry").init(JExpr._new(entryClass));
        body.invoke(newEntry, GeneratedIdentifiers.METHOD_SET_INDEX).arg(JExpr._new(codeModel.ref(OID.class))
                .arg(JExpr.lit(entryOid).plus(JExpr.lit(".0.").plus(entryIndex))));
        body.invoke("setEntry").arg(entryIndex).arg(newEntry);
        body._return(newEntry);
    }

    private void generateSetEntryMethod(final JDefinedClass definedClass, final String entryName,
            final JDefinedClass interfaceClass, final JDefinedClass entryClass) {
        final JMethod setMethod = definedClass.method(JMod.PUBLIC, codeModel.VOID, "setEntry");
        final JVar keyVar = setMethod.param(String.class, "key");
        final JVar valueVar = setMethod.param(interfaceClass, "value");

        setMethod.body().invoke(JExpr.ref(entryName), "put").arg(keyVar).arg(valueVar);
        setMethod.body().invoke(JExpr.cast(entryClass, valueVar), GeneratedIdentifiers.METHOD_SET_TABLE)
                .arg(JExpr._this());
        setMethod.body().invoke("addChild").arg(valueVar);
    }

    private void generateGetEntryMethod(final JDefinedClass definedClass, final String entryName,
            final JDefinedClass entryClass) {
        final JMethod getMethod = definedClass.method(JMod.PUBLIC, entryClass, "getEntry");
        final JVar keyVar = getMethod.param(String.class, "key");

        getMethod.body()._return(JExpr.invoke(JExpr.ref(entryName), "get").arg(keyVar));
    }

    private void generateGetEntriesMethod(final JDefinedClass definedClass, final String entryName,
            final JDefinedClass entryClass) {
        final JMethod getMethod = definedClass.method(JMod.PUBLIC, getTableCollectionType(entryClass),
                "getEntries");
        getMethod.body()._return(JExpr.ref(entryName));
    }

    private JDefinedClass getClassFromOid(final MibValueSymbol child) {
        return oidClasses.get(getOidFromSymbol(child));
    }

    private JDefinedClass getInterfaceFromOid(final MibValueSymbol entity) {
        JDefinedClass interfaceClass = getInterfaceForSymbol(entity);
        if (interfaceClass == null) {
            interfaceClass = getClassFromOid(entity);
        }
        return interfaceClass;
    }

    private void generateSetFromVariableBindingMethod(final JDefinedClass definedClass,
            final MibValueSymbol rootSymbol, final List<MibValueSymbol> childEntities) {
        final JMethod setMethod = definedClass.method(JMod.PUBLIC, codeModel.VOID, "set");
        final JVar binding = setMethod.param(VariableBinding.class, "binding");

        final JSwitch switchExpression = setMethod.body()
                ._switch(generateGetVariableIdentifier(JExpr.invoke(binding, "getOid"), rootSymbol));

        for (final MibValueSymbol child : childEntities) {
            // If it's scalar or a table column...
            if (isSimpleType(child)) {

                final int mibIndex = getMibIndex(child);

                final JCase caseExpression = switchExpression._case(JExpr.lit(mibIndex));
                final JInvocation setterInvocation = caseExpression.body().invoke(getSetterName(child));
                setterInvocation.arg(getValueFromVariableBinding(binding, child));
                caseExpression.body()._break();
            }
        }
    }

    private JExpression getValueFromVariableBinding(final JVar binding, final MibValueSymbol child) {
        final JInvocation getValueInvocation = JExpr.invoke(binding, "getVariable");
        if (mapObjectTypeToJavaType(child.getType()) == codeModel.INT) {
            return getValueInvocation.invoke("toInt");
        } else if (mapObjectTypeToJavaType(child.getType()) == codeModel.LONG) {
            return getValueInvocation.invoke("toLong");
        }
        return getValueInvocation.invoke("toString");
    }

    /**
     * Generate an expression to deliver the part of the OID that delivers the
     * type of the variable in the object.
     * 
     * @param oidVar
     * @param rootSymbol
     * @return
     */
    private JExpression generateGetVariableIdentifier(final JExpression oidVar, final MibValueSymbol rootSymbol) {
        final int partCount = getOidPartCount(rootSymbol);
        final JInvocation call = JExpr.invoke(oidVar, "get");
        call.arg(JExpr.lit(partCount));
        return call;
    }

    /**
     * Generate get and set methods for named field of specified type.
     * 
     * @param definedClass
     * @param type
     * @param field
     * @param fieldId
     */
    private void generateAccessors(final JDefinedClass definedClass, final JType type, final String field,
            final int fieldId) {
        generateGetAccessor(null, definedClass, type, field);
        generateSetAccessor(definedClass, type, field, fieldId, true);
    }

    /**
     * Establish Java field type that corresponds to a MibValueSymbol, creating
     * a class, if necessary. Null is returned if the field is an object that
     * has no variables in its tree.
     * 
     * @param extendPackageName
     * @param child
     * @param classBuilder
     * @return
     */
    private JType getFieldType(final String extendPackageName, final MibValueSymbol child,
            final IClassBuilder classBuilder) {

        // If it's scalar or a table column, expect a simple java type.
        if (isSimpleType(child)) {
            return mapObjectTypeToJavaType(child.getType());
        }

        // Otherwise, see if there is any point creating a class.
        if (child.isTableRow()) {
            final JClass type = classBuilder.buildCodeForClass(extendPackageName, child);

            // If the new class is part of a table, create a collection type for
            // it.
            if (child.isTableRow()) {
                final JDefinedClass resolvedType = getInterfaceFromOid(child);
                return getTableCollectionType(resolvedType);
            }
            return type;
        }
        return null;
    }

    /**
     * Create a Collection<type> java type.
     * 
     * @param clazz
     * @return
     */
    private JType getTableCollectionType(final JClass clazz) {
        final JClass typeVar = codeModel.ref(Map.class);
        return typeVar.narrow(codeModel.ref(String.class), clazz);
    }

    /**
     * Return length of fixed length String type, or -1 if it's not fixed.
     * 
     * @param type
     * @return
     */
    private int getIndexStringLength(final MibType type) {
        StringType objectType = null;
        if (((SnmpObjectType) type).getSyntax() instanceof ChoiceType) {
            objectType = (StringType) ((ChoiceType) ((SnmpObjectType) type).getSyntax()).getAllElements()[0]
                    .getType();
            LOG.debug("resolved : {}", objectType.getClass());
        } else if (((SnmpObjectType) type).getSyntax() instanceof StringType) {
            objectType = (StringType) ((SnmpObjectType) type).getSyntax();
        }

        if (objectType == null) {
            return -1;
        }

        final Constraint constraint = objectType.getConstraint();
        if (constraint instanceof SizeConstraint) {
            final SizeConstraint sizeConstraint = (SizeConstraint) constraint;
            if (sizeConstraint.getValues().size() == 1) {
                final Constraint valueConstraint = (Constraint) sizeConstraint.getValues().get(0);
                if (valueConstraint instanceof ValueConstraint) {
                    final MibValue value = ((ValueConstraint) valueConstraint).getValue();
                    if (value instanceof NumberValue) {
                        return ((Number) (value.toObject())).intValue();
                    }
                }
            }
        }
        return -1;
    }

    /**
     * Get a standard interface name from a Mib name.
     * 
     * @param name
     * @return
     */
    private String getInterfaceName(final String name) {
        return INTERFACE_PREFIX + getClassName(name);
    }

    /**
     * Deliver the name of the set method of a variable, given its MIB symbol
     * 
     * @param symbol
     * @return
     */
    private String getSetterName(final MibValueSymbol symbol) {
        return getSetterName(getMappedName(symbol.getName()));
    }

    /**
     * Decapitalise first character of the specified name.
     * 
     * @param name
     * @return
     */
    private String decapitalizeFirstCharacter(final String name) {
        final StringBuilder sb = new StringBuilder(name);
        final char firstChar = sb.charAt(0);
        sb.setCharAt(0, Character.toLowerCase(firstChar));
        return sb.toString();
    }

    /**
     * Map OID to class name.
     * 
     * @param rootSymbol
     * @param definedClass
     */
    private void addToOidClassMap(final MibValueSymbol rootSymbol, final JDefinedClass definedClass) {
        oidClasses.put(getOidFromSymbol(rootSymbol), definedClass);

        // Determine if this is a duplicate class name.
        final String className = definedClass.name();
        Integer count = classNameOccurrences.get(className);
        if (count == null) {
            classNameOccurrences.put(className, 0);
        } else {
            final int index = ++count;
            classNameOccurrences.put(className, index);
            ((ClassMetadata) definedClass.metadata)
                    .setUniqueIdentifier(definedClass.name() + Integer.toString(index));
        }
    }

    /**
     * Get the number of sub-identifiers that make up the OID.
     * 
     * @param symbol
     * @return
     */
    private int getOidPartCount(final MibValueSymbol symbol) {
        final ObjectIdentifierValue value = (ObjectIdentifierValue) symbol.getValue();
        return value.toString().split("\\.").length;
    }

    private IIndexFieldConverter getIndexConverter(final MibValueSymbol indexField) {
        final MibType type = ((SnmpObjectType) indexField.getType()).getSyntax();

        if (type instanceof ObjectIdentifierType) {
            return objectIdentifierConverter;
        }
        if (type.getReferenceSymbol() != null) {
            final String refSymbolName = type.getReferenceSymbol().getName();
            if ("InetAddress".equals(refSymbolName)) {
                return objectIdentifierConverter;
            }
            if ("MacAddress".equals(refSymbolName) || "PhysAddress".equals(refSymbolName)) {
                return macAddressConverter;
            }
        }

        return defaultConverter;
    }

    private interface IIndexFieldConverter {
        /**
         * Convert j expression.
         *
         * @param byteArray the byte array
         * @param intArray the int array
         * @param lenVar the len var
         * @param offsetVar the offset var
         * @return the j expression
         */
        JExpression convert(JVar byteArray, JVar intArray, JExpression lenVar, JExpression offsetVar);
    }

    private final IIndexFieldConverter objectIdentifierConverter = new IIndexFieldConverter() {
        @Override
        public JExpression convert(final JVar byteArray, final JVar intArray, final JExpression lenVar,
                final JExpression offsetVar) {
            return JExpr.invoke(GeneratedIdentifiers.METHOD_GET_OBJECT_IDENTIFIER).arg(intArray).arg(offsetVar)
                    .arg(lenVar);
        }
    };
    private final IIndexFieldConverter macAddressConverter = new IIndexFieldConverter() {
        @Override
        public JExpression convert(final JVar byteArray, final JVar intArray, final JExpression lenVar,
                final JExpression offsetVar) {
            return JExpr.invoke(GeneratedIdentifiers.METHOD_GET_MACADDRESS).arg(intArray).arg(offsetVar)
                    .arg(lenVar);
        }
    };
    private final IIndexFieldConverter defaultConverter = new IIndexFieldConverter() {
        @Override
        public JExpression convert(final JVar byteArray, final JVar intArray, final JExpression lenVar,
                final JExpression offsetVar) {
            return JExpr._new(codeModel.ref(String.class)).arg(byteArray).arg(offsetVar).arg(lenVar);
        }
    };

    private interface IClassBuilder {
        /**
         * Build code for class j class.
         *
         * @param packageName the package name
         * @param entity the entity
         * @return the j class
         */
        JClass buildCodeForClass(String packageName, MibValueSymbol entity);
    }

    private class ClassMetadata {
        private boolean isTableRow;

        /**
         * Used to disambiguate identifiers for classes with the same name.
         */
        private String uniqueIdentifier;

        /**
         * Is table row boolean.
         *
         * @return the boolean
         */
        public boolean isTableRow() {
            return isTableRow;
        }

        /**
         * Sets table row.
         *
         * @param isTableRow the is table row
         */
        public void setTableRow(final boolean isTableRow) {
            this.isTableRow = isTableRow;
        }

        /**
         * Gets unique identifier.
         *
         * @return the unique identifier
         */
        public String getUniqueIdentifier() {
            return uniqueIdentifier;
        }

        /**
         * Sets unique identifier.
         *
         * @param uniqueIdentifier the unique identifier
         */
        public void setUniqueIdentifier(final String uniqueIdentifier) {
            this.uniqueIdentifier = uniqueIdentifier;
        }

        /**
         * Instantiates a new Class metadata.
         *
         * @param definedClass the defined class
         */
        protected ClassMetadata(final JDefinedClass definedClass) {
            uniqueIdentifier = definedClass.name();
        }
    }
}