gt:         objects = (ArrayList) getValue(getChildAt(node, 1), 0);
context: /*
   * MibAnalyzer.java
   *
   * This work 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 2 of the License,
   * or (at your option) any later version.
   *
   * This work 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.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
   * USA
   *
   * Copyright (c) 2004-2008 Per Cederberg. All rights reserved.
   */
  
  package net.percederberg.mibble;
  
  import java.io.File;
  import java.math.BigInteger;
  import java.util.ArrayList;
  import java.util.BitSet;
  
  import net.percederberg.grammatica.parser.Node;
  import net.percederberg.grammatica.parser.ParseException;
  import net.percederberg.grammatica.parser.Production;
  import net.percederberg.grammatica.parser.Token;
  import net.percederberg.mibble.asn1.Asn1Analyzer;
  import net.percederberg.mibble.asn1.Asn1Constants;
  import net.percederberg.mibble.snmp.SnmpAccess;
  import net.percederberg.mibble.snmp.SnmpAgentCapabilities;
  import net.percederberg.mibble.snmp.SnmpCompliance;
  import net.percederberg.mibble.snmp.SnmpIndex;
  import net.percederberg.mibble.snmp.SnmpModule;
  import net.percederberg.mibble.snmp.SnmpModuleCompliance;
  import net.percederberg.mibble.snmp.SnmpModuleIdentity;
  import net.percederberg.mibble.snmp.SnmpModuleSupport;
  import net.percederberg.mibble.snmp.SnmpNotificationGroup;
  import net.percederberg.mibble.snmp.SnmpNotificationType;
  import net.percederberg.mibble.snmp.SnmpObjectGroup;
  import net.percederberg.mibble.snmp.SnmpObjectIdentity;
  import net.percederberg.mibble.snmp.SnmpObjectType;
  import net.percederberg.mibble.snmp.SnmpRevision;
  import net.percederberg.mibble.snmp.SnmpStatus;
  import net.percederberg.mibble.snmp.SnmpTextualConvention;
  import net.percederberg.mibble.snmp.SnmpTrapType;
  import net.percederberg.mibble.snmp.SnmpVariation;
  import net.percederberg.mibble.type.BitSetType;
  import net.percederberg.mibble.type.BooleanType;
  import net.percederberg.mibble.type.ChoiceType;
  import net.percederberg.mibble.type.CompoundConstraint;
  import net.percederberg.mibble.type.Constraint;
  import net.percederberg.mibble.type.ElementType;
  import net.percederberg.mibble.type.IntegerType;
  import net.percederberg.mibble.type.NullType;
  import net.percederberg.mibble.type.ObjectIdentifierType;
  import net.percederberg.mibble.type.RealType;
  import net.percederberg.mibble.type.SequenceOfType;
  import net.percederberg.mibble.type.SequenceType;
  import net.percederberg.mibble.type.SizeConstraint;
  import net.percederberg.mibble.type.StringType;
  import net.percederberg.mibble.type.TypeReference;
  import net.percederberg.mibble.type.ValueConstraint;
  import net.percederberg.mibble.type.ValueRangeConstraint;
  import net.percederberg.mibble.value.BinaryNumberValue;
  import net.percederberg.mibble.value.BitSetValue;
  import net.percederberg.mibble.value.BooleanValue;
  import net.percederberg.mibble.value.HexNumberValue;
  import net.percederberg.mibble.value.NullValue;
  import net.percederberg.mibble.value.NumberValue;
  import net.percederberg.mibble.value.ObjectIdentifierValue;
  import net.percederberg.mibble.value.StringValue;
  import net.percederberg.mibble.value.ValueReference;
  
  /**
   * A MIB file analyzer. This class analyzes the MIB file parse tree,
   * and creates appropriate MIB modules with the right symbols. This
   * analyzer handles imports by adding them to the MIB loader queue.
   * As the imported MIB symbols aren't available during the analysis,
   * type and value references will be created whenever an identifier
   * is encountered.
   *
   * @author   Per Cederberg, <per at percederberg dot net>
   * @version  2.9
   * @since    2.0
   */
  class MibAnalyzer extends Asn1Analyzer {
  
      /**
       * The list of MIB modules found.
       */
      private ArrayList mibs = new ArrayList();
  
      /**
       * The MIB file being analyzed.
       */
      private File file;
  
      /**
       * The MIB loader using this analyzer.
       */
      private MibLoader loader;
  
      /**
       * The MIB loader log.
       */
      private MibLoaderLog log;
  
      /**
       * The current MIB module being analyzed.
       */
      private Mib currentMib = null;
  
      /**
       * The base MIB symbol context. This context will be extended
       * when parsing the import list.
       */
      private MibContext baseContext = null;
  
      /**
       * The MIB context stack. This stack is modified during the
       * parsing to add type or import contexts as necessary. The top
       * context on the stack is returned by the getContext() method.
       *
       * @see #getContext()
       */
      private ArrayList contextStack = new ArrayList();
  
      /**
       * The implicit tags flag.
       */
      private boolean implicitTags = true;
  
      /**
       * Creates a new MIB file analyzer.
       *
       * @param file           the MIB file being analyzed
       * @param loader         the MIB loader using this analyzer
       * @param log            the MIB loader log to use
       */
      public MibAnalyzer(File file, MibLoader loader, MibLoaderLog log) {
          this.file = file;
          this.loader = loader;
          this.log = log;
      }
  
      /**
       * Resets this analyzer. This method is mostly used to release
       * all references to parsed data.
       */
      public void reset() {
          mibs = new ArrayList();
          currentMib = null;
          baseContext = null;
          contextStack.clear();
          implicitTags = true;
      }
  
      /**
       * Returns the list of MIB modules found during analysis.
       *  
       * @return a list of MIB modules
       */
      public ArrayList getMibs() {
          return mibs;
      }
  
      /**
       * Adds the binary number as a node value. This method will
       * convert the binary string to either an Integer, a Long, or a
       * BigInteger.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitBinaryString(Token node) {
          String  str = node.getImage();
          Number  value;
  
          str = str.substring(1, str.length() - 2);
          if (str.length() == 0) {
              value = new Integer(0);
          } else if (str.length() < 32) {
              value = new Integer(Integer.parseInt(str, 2));
          } else if (str.length() < 64) {
              value = new Long(Long.parseLong(str, 2));
          } else {
              value = new BigInteger(str, 2);
          }
          node.addValue(value);
          node.addValue(str);
          return node;
      }
  
      /**
       * Adds the hexadecimal number as a node value. This method will
       * convert the hexadecimal string to either an Integer, a Long,
       * or a BigInteger.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitHexadecimalString(Token node) {
          String  str = node.getImage();
          Number  value;
  
          str = str.substring(1, str.length() - 2);
          if (str.length() == 0) {
              value = new Integer(0);
          } else if (str.length() < 8) {
              value = new Integer(Integer.parseInt(str, 16));
          } else if (str.length() < 16) {
              value = new Long(Long.parseLong(str, 16));
          } else {
              value = new BigInteger(str, 16);
          }
          node.addValue(value);
          node.addValue(str);
          return node;
      }
  
      /**
       * Adds the quoted string as a node value. This method will
       * remove the quotation marks and replace any double marks inside
       * the string with a single mark.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitQuotedString(Token node) {
          String  str = node.getImage();
          int     pos;
  
          str = str.substring(1, str.length() - 1);
          do {
              pos = str.indexOf("\"\"");
              if (pos >= 0) {
                  str = str.substring(0, pos) + '"' + str.substring(pos + 2);
              }
          } while (pos >= 0);
          node.addValue(str);
          return node;
      }
  
      /**
       * Adds the identifier string as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitIdentifierString(Token node) {
          node.addValue(node.getImage());
          return node;
      }
  
      /**
       * Adds the number as a node value. This method will convert the
       * number string to either an Integer, a Long, or a BigInteger.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitNumberString(Token node) {
          String  str = node.getImage();
          Number  value;
  
          if (str.length() < 10) {
              value = new Integer(str);
          } else if (str.length() < 19) {
              value = new Long(str);
          } else {
              value = new BigInteger(str);
          }
          node.addValue(value);
          return node;
      }
  
      /**
       * Stores any MIB tail comments if available.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitStart(Production node) {
          String  comment = MibAnalyzerUtil.getCommentsFooter(node);
  
          if (currentMib != null) {
              currentMib.setFooterComment(comment);
          }
          return null;
      }
  
      /**
       * Creates the current MIB module container and the base context.
       *
       * @param node           the node being entered
       */
      protected void enterModuleDefinition(Production node) {
          currentMib = new Mib(file, loader, log);
          baseContext = loader.getDefaultContext();
          baseContext = new CompoundContext(currentMib, baseContext);
          pushContext(baseContext);
      }
  
      /**
       * Sets the MIB name to the module identifier string value. Also
       * removes this node from the parse tree.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitModuleDefinition(Production node)
          throws ParseException {
  
          currentMib.setName(getStringValue(getChildAt(node, 0), 0));
          currentMib.setHeaderComment(MibAnalyzerUtil.getComments(node));
          mibs.add(currentMib);
          return node;
      }
  
      /**
       * Adds the module identifier string as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitModuleIdentifier(Production node)
          throws ParseException {
  
          node.addValue(getStringValue(getChildAt(node, 0), 0));
          return node;
      }
  
      /**
       * Adds the module identifier string as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitModuleReference(Production node)
          throws ParseException {
  
          node.addValue(getStringValue(getChildAt(node, 0), 0));
          return node;
      }
  
      /**
       * Sets the implicit tags flag.
       *
       * @param node           the node being exited
       *
       * @return null to remove the node from the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitTagDefault(Production node)
          throws ParseException {
  
          Node  child;
  
          child = getChildAt(node, 0);
          if (child.getId() == Asn1Constants.EXPLICIT) {
              implicitTags = false;
          } else {
              implicitTags = true;
          }
          return null;
      }
  
      /**
       * Adds all imported MIB files to the MIB context. Also removes
       * this node from the parse tree.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitImportList(Production node) {
          ArrayList   imports = getChildValues(node);
          MibImport   imp;
          MibContext  current = loader.getDefaultContext();
          boolean     addMissingSmi = true;
  
          for (int i = 0; i < imports.size(); i++) {
              imp = (MibImport) imports.get(i);
              if (imp.getName().startsWith("RFC1065-SMI") ||
                  imp.getName().startsWith("RFC1155-SMI") ||
                  imp.getName().startsWith("SNMPv2-SMI")) {
  
                  addMissingSmi = false;
              }
          }
          if (addMissingSmi) {
              // TODO: Ugly hack that adds a "hidden" SNMPv1 SMI as the last
              //       import, but without any named symbols (triggering
              //       warnings for each symbol used).
              imp = new MibImport(loader, getLocation(node), "RFC1155-SMI", new ArrayList());
              loader.scheduleLoad(imp.getName());
              currentMib.addImport(imp);
              imports.add(imp);
          }
          for (int i = imports.size() - 1; i >= 0; i--) {
              imp = (MibImport) imports.get(i);
              current = new CompoundContext(imp, current);
          }
          baseContext = new CompoundContext(currentMib, current);
          popContext();
          pushContext(baseContext);
          return null;
      }
  
      /**
       * Schedules the imported MIB file for loading. Also adds a MIB
       * reference as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitSymbolsFromModule(Production node)
          throws ParseException {
  
          MibImport  imp;
          String     module;
          ArrayList  symbols;
          Node       child;
  
          // Create MIB reference
          child = getChildAt(node, 0);
          symbols = child.getAllValues();
          if (symbols == null) {
              symbols = new ArrayList();
          }
          child = getChildAt(node, 2);
          module = getStringValue(child, 0);
          imp = new MibImport(loader, getLocation(child), module, symbols);
  
          // Schedule MIB loading
          loader.scheduleLoad(module);
  
          // Add reference to MIB and node
          currentMib.addImport(imp);
          node.addValue(imp);
          return node;
      }
  
      /**
       * Adds all symbol identifiers as node values.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitSymbolList(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Adds the identifier string as a node value. If the symbol name
       * is not an identifier, no node value will be added.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitSymbol(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Creates a macro symbol and adds it to the MIB. Also removes
       * this node from the parse tree.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitMacroDefinition(Production node)
          throws ParseException {
          
          String          name;
          MibMacroSymbol  symbol;
  
          // Check macro name
          name = getStringValue(getChildAt(node, 0), 0);
          if (currentMib.getSymbol(name) != null) {
              throw new ParseException(
                  ParseException.ANALYSIS_ERROR,
                  "a symbol '" + name + "' already present in the MIB",
                  node.getStartLine(),
                  node.getStartColumn());
          }
  
          // Create macro symbol
          symbol = new MibMacroSymbol(getLocation(node),
                                      currentMib,
                                      name);
          symbol.setComment(MibAnalyzerUtil.getComments(node));
  
          return null;
      }
  
      /**
       * Adds the macro name as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitMacroReference(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Creates a type symbol and adds it to the MIB. Also removes
       * this node from the parse tree.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitTypeAssignment(Production node)
          throws ParseException {
  
          String         name;
          MibType        type;
          MibTypeSymbol  symbol;
  
          // Check type name
          name = getStringValue(getChildAt(node, 0), 0);
          if (currentMib.getSymbol(name) != null) {
              throw new ParseException(
                  ParseException.ANALYSIS_ERROR,
                  "a symbol '" + name + "' already present in the MIB",
                  node.getStartLine(),
                  node.getStartColumn());
          }
          if (!Character.isUpperCase(name.charAt(0))) {
              log.addWarning(getLocation(node),
                             "type identifier '" + name + "' doesn't " +
                             "start with an uppercase character");
          }
  
          // Create type symbol
          type = (MibType) getValue(getChildAt(node, 2), 0);
          symbol = new MibTypeSymbol(getLocation(node),
                                     currentMib,
                                     name,
                                     type);
          symbol.setComment(MibAnalyzerUtil.getComments(node));
  
          return null;
      }
  
      /**
       * Adds a MIB type as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitType(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Adds a type reference as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitDefinedType(Production node)
          throws ParseException {
  
          MibContext     local = getContext();
          String         name = null;
          Object         value = null;
          FileLocation   loc = getLocation(node);
          Node           child;
  
          for (int i = 0; i < node.getChildCount(); i++) {
              child = node.getChildAt(i);
              switch (child.getId()) {
              case Asn1Constants.MODULE_REFERENCE:
                  name = getStringValue(child, 0);
                  local = currentMib.getImport(name);
                  if (local == null) {
                      throw new ParseException(
                          ParseException.ANALYSIS_ERROR,
                          "referenced module not imported '" + name + "'",
                          child.getStartLine(),
                          child.getStartColumn());
                  }
                  break;
              case Asn1Constants.IDENTIFIER_STRING:
                  name = getStringValue(child, 0);
                  break;
              case Asn1Constants.VALUE_OR_CONSTRAINT_LIST:
                  value = getValue(child, 0);
              }
          }
          if (value instanceof Constraint) {
              value = new TypeReference(loc, local, name, (Constraint) value);
          } else if (value instanceof ArrayList) {
              value = new TypeReference(loc, local, name, (ArrayList) value);
          } else {
              value = new TypeReference(loc, local, name);
          }
          node.addValue(value);
          return node;
      }
  
      /**
       * Adds a MIB type as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitBuiltinType(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Adds a null type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitNullType(Production node) {
          node.addValue(new NullType());
          return node;
      }
  
      /**
       * Adds a boolean type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitBooleanType(Production node) {
          node.addValue(new BooleanType());
          return node;
      }
  
      /**
       * Adds a real type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitRealType(Production node) {
          node.addValue(new RealType());
          return node;
      }
  
      /**
       * Adds an integer type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitIntegerType(Production node) {
          IntegerType  type;
          ArrayList    values;
          Object       obj;
  
          values = getChildValues(node);
          if (values.size() == 0) {
              type = new IntegerType();
          } else {
              obj = values.get(0);
              if (obj instanceof ArrayList) {
                  type = new IntegerType((ArrayList) obj);
              } else {
                  type = new IntegerType((Constraint) obj);
              }
          }
          node.addValue(type);
          return node;
      }
  
      /**
       * Adds an object identifier type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitObjectIdentifierType(Production node) {
          node.addValue(new ObjectIdentifierType());
          return node;
      }
  
      /**
       * Adds a string type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitStringType(Production node) {
          StringType  type;
          ArrayList   values;
  
          values = getChildValues(node);
          if (values.size() == 0) {
              type = new StringType();
          } else {
              type = new StringType((Constraint) values.get(0));
          }
          node.addValue(type);
          return node;
      }
  
      /**
       * Adds a bit set type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitBitStringType(Production node) {
          BitSetType  type;
          ArrayList   values;
          Object      obj;
  
          values = getChildValues(node);
          if (values.size() == 0) {
              type = new BitSetType();
          } else {
              obj = values.get(0);
              if (obj instanceof ArrayList) {
                  type = new BitSetType((ArrayList) obj);
              } else {
                  type = new BitSetType((Constraint) obj);
              }
          }
          node.addValue(type);
          return node;
      }
  
      /**
       * Adds a bit set type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitBitsType(Production node) {
          return exitBitStringType(node);
      }
  
      /**
       * Adds a MIB sequence type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitSequenceType(Production node) {
          ArrayList  elements = getChildValues(node);
  
          node.addValue(new SequenceType(elements));
          return node;
      }
  
      /**
       * Adds a sequence of MIB type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitSequenceOfType(Production node)
          throws ParseException {
  
          MibType     type;
          Constraint  c = null;
          Node        child;
  
          child = getChildAt(node, node.getChildCount() - 1);
          type = (MibType) getValue(child, 0);
          if (node.getChildCount() == 4) {
              child = getChildAt(node, 1);
              c = (Constraint) getValue(child, 0);
          }
          node.addValue(new SequenceOfType(type, c));
          return node;
      }
  
      /**
       * Adds a null type as a node value. This method also prints an
       * error about this construct being unsupported.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitSetType(Production node) {
          // TODO: implement set type support
          log.addError(getLocation(node),
                       "SET type currently unsupported");
          node.addValue(new NullType());
          return node;
      }
  
      /**
       * Adds a null type as a node value. This method also prints an
       * error about this construct being unsupported.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitSetOfType(Production node) {
          // TODO: implement set of type support
          log.addError(getLocation(node),
                       "SET OF type currently unsupported");
          node.addValue(new NullType());
          return node;
      }
  
      /**
       * Adds a MIB choice type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitChoiceType(Production node) {
          node.addValue(new ChoiceType(getChildValues(node)));
          return node;
      }
  
      /**
       * Adds a null type as a node value. This method also prints an
       * error about this construct being unsupported.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitEnumeratedType(Production node) {
          // TODO: implement enumerated type support
          log.addError(getLocation(node),
                       "ENUMERATED type currently unsupported");
          node.addValue(new NullType());
          return node;
      }
  
      /**
       * Adds a null type as a node value. This method also prints an
       * error about this construct being unsupported.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitSelectionType(Production node) {
          // TODO: implement selection type support
          log.addError(getLocation(node),
                       "selection type currently unsupported");
          node.addValue(new NullType());
          return node;
      }
  
      /**
       * Adds the tagged type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitTaggedType(Production node)
          throws ParseException {
  
          MibType     type;
          MibTypeTag  tag;
          boolean     implicit = implicitTags;
          Node        child;
  
          child = getChildAt(node, 0);
          tag = (MibTypeTag) getValue(child, 0);
          child = getChildAt(node, 1);
          if (child.getId() == Asn1Constants.EXPLICIT_OR_IMPLICIT_TAG) {
              implicit = ((Boolean) getValue(child, 0)).booleanValue();
          }
          child = getChildAt(node, node.getChildCount() - 1);
          type = (MibType) getValue(child, 0);
          type.setTag(implicit, tag);
          node.addValue(type);
          return node;
      }
  
      /**
       * Called when exiting a parse tree node.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree, or
       *         null if no parse tree should be created
       */
      protected Node exitTag(Production node) {
          ArrayList  values = getChildValues(node);
          int        category = MibTypeTag.CONTEXT_SPECIFIC_CATEGORY;
          int        value;
  
          if (values.size() == 1) {
              value = ((Number) values.get(0)).intValue();
          } else {
              category = ((Integer) values.get(0)).intValue();
              value = ((Number) values.get(1)).intValue();
          }
          node.addValue(new MibTypeTag(category, value));
          return node;
      }
  
      /**
       * Adds the type tag category value as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitClass(Production node) throws ParseException {
          Node  child = getChildAt(node, 0);
          int   category;
  
          if (child.getId() == Asn1Constants.UNIVERSAL) {
              category = MibTypeTag.UNIVERSAL_CATEGORY;
          } else if (child.getId() == Asn1Constants.APPLICATION) {
              category = MibTypeTag.APPLICATION_CATEGORY;
          } else if (child.getId() == Asn1Constants.PRIVATE) {
              category = MibTypeTag.PRIVATE_CATEGORY;
          } else {
              category = MibTypeTag.CONTEXT_SPECIFIC_CATEGORY;
          }
          node.addValue(new Integer(category));
          return node;
      }
  
      /**
       * Adds the implicit boolean flag as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitExplicitOrImplicitTag(Production node)
          throws ParseException {
  
          Node  child = getChildAt(node, 0);
  
          if (child.getId() == Asn1Constants.EXPLICIT) {
              node.addValue(Boolean.FALSE);
          } else {
              node.addValue(Boolean.TRUE);
          }
          return node;
      }
  
      /**
       * Adds a null type as a node value. This method also prints an
       * error about this construct being unsupported.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitAnyType(Production node) {
          // TODO: implement any type support
          log.addError(getLocation(node),
                       "ANY type currently unsupported");
          node.addValue(new NullType());
          return node;
      }
  
      /**
       * Adds all element types as a node values.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitElementTypeList(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Adds an element type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitElementType(Production node)
          throws ParseException {
  
          String   name = null;
          MibType  type;
          Node     child;
  
          child = getChildAt(node, 0);
          if (child.getId() == Asn1Constants.IDENTIFIER_STRING) {
              name = getStringValue(child, 0);
              child = getChildAt(node, 1);
          }
          if (child.getId() != Asn1Constants.TYPE) {
              throw new ParseException(
                  ParseException.ANALYSIS_ERROR,
                  "referencing components is currently unsupported",
                  child.getStartLine(),
                  child.getStartColumn());
          }
          type = new ElementType(name, (MibType) getValue(child, 0));
          type.setComment(MibAnalyzerUtil.getComments(node));
          node.addValue(type);
          return node;
      }
  
      /**
       * Prints an error about this construct being unsupported.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitOptionalOrDefaultElement(Production node) {
          // TODO: implement this method?
          log.addError(getLocation(node),
                       "optional and default elements are currently " +
                       "unsupported");
          return null;
      }
  
      /**
       * Adds an array list with symbols or a constraint as the node
       * value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitValueOrConstraintList(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Adds an array list with symbols as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitNamedNumberList(Production node) {
          MibValueSymbol  symbol;
          Node            child;
  
          for (int i = 0; i < node.getChildCount(); i++) {
              child = node.getChildAt(i);
              if (child.getId() == Asn1Constants.NAMED_NUMBER) {
                  symbol = (MibValueSymbol) child.getValue(0);
                  symbol.setComment(MibAnalyzerUtil.getComments(child));
              }
          }
          node.addValue(getChildValues(node));
          return node;
      }
  
      /**
       * Adds a value symbol as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitNamedNumber(Production node)
          throws ParseException {
  
          MibValueSymbol  symbol;
          String          name;
          MibValue        value;
  
          name = getStringValue(getChildAt(node, 0), 0);
          value = (MibValue) getValue(getChildAt(node, 2), 0);
          symbol = new MibValueSymbol(getLocation(node),
                                      null,
                                      name,
                                      null,
                                      value);
          node.addValue(symbol);
          return node;
      }
  
      /**
       * Adds a MIB value as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitNumber(Production node) throws ParseException {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Adds a MIB type constraint as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitConstraintList(Production node) {
          Constraint  result = null;
          ArrayList   values;
          Constraint  c;
  
          values = getChildValues(node);
          for (int i = values.size() - 1; i >= 0; i--) {
              c = (Constraint) values.get(i);
              if (result == null) {
                  result = c;
              } else {
                  result = new CompoundConstraint(c, result);
              }
          }
          node.addValue(result);
          return node;
      }
  
      /**
       * Adds a MIB type constraint as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitConstraint(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Adds a MIB type constraint as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitValueConstraintList(Production node) {
          return exitConstraintList(node);
      }
  
      /**
       * Adds a MIB type value or value range constraint as a node
       * value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitValueConstraint(Production node)
          throws ParseException {
  
          ArrayList  list = getChildValues(node);
          MibValue   lower = null;
          MibValue   upper = null;
          Boolean    strictLower = null;
          Boolean    strictUpper = null;
          Object     obj;
  
          if (list.size() == 0) {
              throw new ParseException(
                  ParseException.ANALYSIS_ERROR,
                  "no value specified in constraint",
                  node.getStartLine(),
                  node.getStartColumn());
          } else if (list.size() == 1) {
              lower = (MibValue) list.get(0);
              obj = new ValueConstraint(getLocation(node), lower);
          } else {
              for (int i = 0; i < list.size(); i++) {
                  obj = list.get(i);
                  if (obj instanceof Boolean && strictLower == null) {
                      strictLower = (Boolean) obj;
                  } else if (obj instanceof Boolean) {
                      strictUpper = (Boolean) obj;
                  } else if (strictLower == null) {
                      lower = (MibValue) obj;
                  } else {
                      upper = (MibValue) obj;
                  }
              }
              if (strictLower == null) {
                  strictLower = Boolean.FALSE;
              }
              if (strictUpper == null) {
                  strictUpper = Boolean.FALSE;
              }
              obj = new ValueRangeConstraint(getLocation(node),
                                             lower,
                                             strictLower.booleanValue(),
                                             upper,
                                             strictUpper.booleanValue());
          }
          node.addValue(obj);
          return node;
      }
  
      /**
       * Adds the upper end point and strict inequality flags as node
       * values.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitValueRange(Production node)
          throws ParseException {
  
          Node  child;
  
          // Check for strict lower end point
          child = getChildAt(node, 0);
          if (child.getId() == Asn1Constants.LESS_THAN) {
              node.addValue(Boolean.TRUE);
          } else {
              node.addValue(Boolean.FALSE);
          }
  
          // Add upper end point (or null)
          child = getChildAt(node, node.getChildCount() - 1);
          node.addValue(child.getValue(0));
  
          // Check for strict upper end point
          child = getChildAt(node, node.getChildCount() - 2);
          if (child.getId() == Asn1Constants.LESS_THAN) {
              node.addValue(Boolean.TRUE);
          } else {
              node.addValue(Boolean.FALSE);
          }
  
          return node;
      }
  
      /**
       * Adds a MIB value or null as a node value. The null value is
       * used to represent a minimum value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitLowerEndPoint(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Adds a MIB value or null as a node value. The null value is
       * used to represent a maximum value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitUpperEndPoint(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Adds a MIB type size constraint as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitSizeConstraint(Production node)
          throws ParseException {
  
          Constraint  c;
  
          c = (Constraint) getValue(getChildAt(node, 1), 0);
          node.addValue(new SizeConstraint(getLocation(node), c));
          return node;
      }
  
      /**
       * Removes this node from the parse tree, and prints an error
       * about this construct being unsupported.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitAlphabetConstraint(Production node) {
          // TODO: implement alphabet constraints
          log.addError(getLocation(node),
                       "FROM constraints are currently unsupported");
          return null;
      }
  
      /**
       * Removes this node from the parse tree, and prints an error
       * about this construct being unsupported.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitContainedTypeConstraint(Production node) {
          // TODO: implement contained type constraints
          log.addError(getLocation(node),
                       "INCLUDES constraints are currently unsupported");
          return null;
      }
  
      /**
       * Removes this node from the parse tree, and prints an error
       * about this construct being unsupported.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitInnerTypeConstraint(Production node) {
          // TODO: implement inner type constraints
          log.addError(getLocation(node),
                       "WITH COMPONENT(S) constraints are currently " +
                       "unsupported");
          return null;
      }
  
      /**
       * Creates a value symbol and adds it to the MIB. Also removes
       * this node from the parse tree.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitValueAssignment(Production node)
          throws ParseException {
  
          String          name;
          MibType         type;
          MibValue        value;
          MibValueSymbol  symbol;
  
          // Check value name
          name = getStringValue(getChildAt(node, 0), 0);
          if (currentMib.getSymbol(name) != null) {
              throw new ParseException(
                  ParseException.ANALYSIS_ERROR,
                  "a symbol '" + name + "' already present in the MIB",
                  node.getStartLine(),
                  node.getStartColumn());
          }
          if (!Character.isLowerCase(name.charAt(0))) {
              log.addWarning(getLocation(node),
                             "value identifier '" + name + "' doesn't " +
                             "start with a lowercase character");
          }
  
          // Create value symbol
          type = (MibType) getValue(getChildAt(node, 1), 0);
          value = (MibValue) getValue(getChildAt(node, 3), 0);
          symbol = new MibValueSymbol(getLocation(node),
                                      currentMib,
                                      name,
                                      type,
                                      value);
          symbol.setComment(MibAnalyzerUtil.getComments(node));
  
          return null;
      }
  
      /**
       * Adds a MIB value as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitValue(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Adds a value reference as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitDefinedValue(Production node)
          throws ParseException {
  
          ValueReference  ref;
          MibContext      local = getContext();
          String          name;
          Node            child;
  
          // Check for module reference
          child = getChildAt(node, 0);
          if (child.getId() == Asn1Constants.MODULE_REFERENCE) {
              name = getStringValue(child, 0);
              local = currentMib.getImport(name);
              if (local == null) {
                  throw new ParseException(
                      ParseException.ANALYSIS_ERROR,
                      "referenced module not imported '" + name + "'",
                      child.getStartLine(),
                      child.getStartColumn());
              }
              child = getChildAt(node, 1);
          }
  
          // Create value reference
          name = getStringValue(child, 0);
          ref = new ValueReference(getLocation(node), local, name);
          node.addValue(ref);
          return node;
      }
  
      /**
       * Adds a MIB value as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitBuiltinValue(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Adds a MIB null value as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitNullValue(Production node) {
          node.addValue(NullValue.NULL);
          return node;
      }
  
      /**
       * Adds a MIB boolean value as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitBooleanValue(Production node)
          throws ParseException {
  
          Node  child = getChildAt(node, 0);
  
          if (child.getId() == Asn1Constants.TRUE) {
              node.addValue(BooleanValue.TRUE);
          } else {
              node.addValue(BooleanValue.FALSE);
          }
          return node;
      }
  
      /**
       * Adds a MIB number value as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitSpecialRealValue(Production node)
          throws ParseException {
  
          Number  number;
  
          if (getChildAt(node, 0).getId() == Asn1Constants.PLUS_INFINITY) {
              number = new Float(Float.POSITIVE_INFINITY);
          } else {
              number = new Float(Float.NEGATIVE_INFINITY);
          }
          node.addValue(new NumberValue(number));
          return node;
      }
  
      /**
       * Adds a MIB number value as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitNumberValue(Production node)
          throws ParseException {
  
          Number  number;
  
          if (getChildAt(node, 0).getId() == Asn1Constants.MINUS) {
              number = (Number) getValue(getChildAt(node, 1), 0);
              if (number instanceof Integer) {
                  number = new Integer(-number.intValue());
              } else if (number instanceof Long) {
                  number = new Long(-number.longValue());
              } else {
                  number = ((BigInteger) number).negate();
              }
          } else {
              number = (Number) getValue(getChildAt(node, 0), 0);
          }
          node.addValue(new NumberValue(number));
          return node;
      }
  
      /**
       * Adds a MIB number value as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitBinaryValue(Production node)
          throws ParseException {
  
          Node    child;
          Number  number;
          String  text;
  
          child = getChildAt(node, 0);
          number = (Number) child.getValue(0);
          text = (String) child.getValue(1);
          node.addValue(new BinaryNumberValue(number, text.length()));
          return node;
      }
  
      /**
       * Adds a MIB number value as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitHexadecimalValue(Production node)
          throws ParseException {
  
          Node    child;
          Number  number;
          String  text;
  
          child = getChildAt(node, 0);
          number = (Number) child.getValue(0);
          text = (String) child.getValue(1);
          node.addValue(new HexNumberValue(number, text.length()));
          return node;
      }
  
      /**
       * Adds a MIB string value as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitStringValue(Production node)
          throws ParseException {
  
          String  str;
  
          str = getStringValue(getChildAt(node, 0), 0);
          node.addValue(new StringValue(str));
          return node;
      }
  
      /**
       * Adds a MIB value as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitBitOrObjectIdentifierValue(Production node)
          throws ParseException {
  
          if (MibAnalyzerUtil.isBitValue(node)) {
              return exitBitValue(node);
          } else {
              return exitObjectIdentifierValue(node);
          }
      }
  
      /**
       * Adds a MIB bit set value as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitBitValue(Production node) throws ParseException {
          ArrayList     components = getChildValues(node);
          BitSet        bits = new BitSet();
          ArrayList     values = new ArrayList();
          NamedNumber   number;
  
          for (int i = 0; i < components.size(); i++) {
              number = (NamedNumber) components.get(i);
              if (number.hasNumber()) {
                  bits.set(number.getNumber().intValue());
              } else {
                  values.add(number.getReference());
              }
          }
          node.addValue(new BitSetValue(bits, values));
          return node;
      }
  
      /**
       * Adds a MIB object identifier value as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitObjectIdentifierValue(Production node)
          throws ParseException {
  
          ArrayList     components = getChildValues(node);
          MibValue      parent = null;
          NamedNumber   number;
          int           value;
  
          // Check for minimum number of components
          if (components.size() < 1) {
              throw new ParseException(
                  ParseException.ANALYSIS_ERROR,
                  "object identifier must contain at least one component",
                  node.getStartLine(),
                  node.getStartColumn());
          }
  
          // Analyze components
          for (int i = 0; i < components.size(); i++) {
              number = (NamedNumber) components.get(i);
              if (number.hasNumber()) {
                  value = number.getNumber().intValue();
                  if (parent == null && value == 0) {
                      parent = new ValueReference(getLocation(node),
                                                  getContext(),
                                                  DefaultContext.CCITT);
                  } else if (parent == null && value == 1) {
                      parent = new ValueReference(getLocation(node),
                                                  getContext(),
                                                  DefaultContext.ISO);
                  } else if (parent == null && value == 2) {
                      parent = new ValueReference(getLocation(node),
                                                  getContext(),
                                                  DefaultContext.JOINT_ISO_CCITT);
                  } else if (parent instanceof ObjectIdentifierValue) {
                      try {
                          parent = new ObjectIdentifierValue(
                                          getLocation(node),
                                          (ObjectIdentifierValue) parent,
                                          number.getName(),
                                          value);
                      } catch (MibException e) {
                          log.addError(e.getLocation(), e.getMessage());
                          parent = null;
                      }
                  } else {
                      parent = new ObjectIdentifierValue(
                                          getLocation(node),
                                          (ValueReference) parent,
                                          number.getName(),
                                          value);
                  }
              } else if (parent != null) {
                  throw new ParseException(
                      ParseException.ANALYSIS_ERROR,
                      "object identifier component '" + number.getName() +
                      "' has been previously defined, remove any " +
                      "components to the left",
                      node.getStartLine(),
                      node.getStartColumn());
              } else {
                  parent = number.getReference();
              }
          }
  
          // Set node value
          node.addValue(parent);
          return node;
      }
  
      /**
       * Adds all the named numbers as the node values.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitNameValueList(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Adds a named number as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitNameValueComponent(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
  
      /**
       * Adds a named number as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitNameOrNumber(Production node)
          throws ParseException {
  
          NamedNumber     value;
          Object          obj;
          ValueReference  ref;
  
          obj = getValue(getChildAt(node, 0), 0);
          if (obj instanceof Number) {
              value = new NamedNumber((Number) obj);
          } else if (obj instanceof String) {
              ref = new ValueReference(getLocation(node),
                                       getContext(),
                                       (String) obj);
              value = new NamedNumber((String) obj, ref);
          } else {
              value = (NamedNumber) obj;
          }
          node.addValue(value);
          return node;
      }
  
      /**
       * Adds a named number as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitNameAndNumber(Production node)
          throws ParseException {
  
          NamedNumber  value;
          String       name;
          Object       obj;
  
          name = getStringValue(getChildAt(node, 0), 0);
          obj = getValue(getChildAt(node, 2), 0);
          if (obj instanceof Number) {
              value = new NamedNumber(name, (Number) obj);
          } else {
              value = new NamedNumber(name, (ValueReference) obj);
          }
          node.addValue(value);
          return node;
      }
  
      /**
       * Adds an SNMP type as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitDefinedMacroType(Production node) {
          node.addValues(getChildValues(node));
          return node;
      }
  
      /**
       * Adds the defined macro name as the node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       */
      protected Node exitDefinedMacroName(Production node) {
          node.addValue(((Token) node.getChildAt(0)).getImage());
          return node;
      }
  
      /**
       * Adds an SNMP module identity as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitSnmpModuleIdentityMacroType(Production node)
          throws ParseException {
  
          String     update;
          String     org;
          String     contact;
          String     desc;
          ArrayList  revisions = new ArrayList();
  
          currentMib.setSmiVersion(2);
          update = getStringValue(getChildAt(node, 1), 0);
          org = getStringValue(getChildAt(node, 2), 0);
          contact = getStringValue(getChildAt(node, 3), 0);
          desc = getStringValue(getChildAt(node, 4), 0);
          for (int i = 5; i < node.getChildCount(); i++) {
              revisions.add(getValue(getChildAt(node, i), 0));
          }
          node.addValue(new SnmpModuleIdentity(update,
                                               org,
                                               contact,
                                               desc,
                                               revisions));
          return node;
      }
  
      /**
       * Called when exiting a parse tree node.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitSnmpObjectIdentityMacroType(Production node)
          throws ParseException {
  
          SnmpStatus  status;
          String      desc;
          String      ref;
  
          currentMib.setSmiVersion(2);
          status = (SnmpStatus) getValue(getChildAt(node, 1), 0);
          desc = getStringValue(getChildAt(node, 2), 0);
          if (node.getChildCount() <= 3) {
              ref = null;
          } else {
              ref = getStringValue(getChildAt(node, 3), 0);
          }
          node.addValue(new SnmpObjectIdentity(status, desc, ref));
          return node;
      }
  
      /**
       * Adds the syntax type to the MIB context stack if possible.
       *
       * @param node           the parent node
       * @param child          the child node, or null
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected void childSnmpObjectTypeMacroType(Production node, Node child)
          throws ParseException {
  
          MibType  type;
  
          if (child.getId() == Asn1Constants.SNMP_SYNTAX_PART) {
              type = (MibType) getValue(child, 0);
              if (type instanceof MibContext) {
                  pushContextExtension((MibContext) type);
              }
          }
          node.addChild(child);
      }
  
      /**
       * Adds an SNMP object type as a node value. This method also
       * removes any syntax type from the MIB context stack if needed.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitSnmpObjectTypeMacroType(Production node)
          throws ParseException {
  
          SnmpObjectType  type;
          MibType         syntax = null;
          String          units = null;
          SnmpAccess      access = null;
          SnmpStatus      status = null;
          String          desc = null;
          String          ref = null;
          Object          index = null;
          MibValue        defVal = null;
          Node            child;
  
          for (int i = 0; i < node.getChildCount(); i++) {
              child = node.getChildAt(i);
              switch (child.getId()) {
              case Asn1Constants.SNMP_SYNTAX_PART:
                  syntax = (MibType) getValue(child, 0);
                  if (syntax instanceof MibContext) {
                      popContext();
                  }
                  syntax.setComment(MibAnalyzerUtil.getComments(child));
                  break;
              case Asn1Constants.SNMP_UNITS_PART:
                  units = getStringValue(child, 0);
                  break;
              case Asn1Constants.SNMP_ACCESS_PART:
                  access = (SnmpAccess) getValue(child, 0);
                  break;
              case Asn1Constants.SNMP_STATUS_PART:
                  status = (SnmpStatus) getValue(child, 0);
                  break;
              case Asn1Constants.SNMP_DESCR_PART:
                  desc = getStringValue(child, 0);
                  break;
              case Asn1Constants.SNMP_REFER_PART:
                  ref = getStringValue(child, 0);
                  break;
              case Asn1Constants.SNMP_INDEX_PART:
                  index = getValue(child, 0);
                  break;
              case Asn1Constants.SNMP_DEF_VAL_PART:
                  defVal = (MibValue) getValue(child, 0);
                  break;
              }
          }
          if (index instanceof ArrayList) {
              type = new SnmpObjectType(syntax,
                                        units,
                                        access,
                                        status,
                                        desc,
                                        ref,
                                        (ArrayList) index,
                                        defVal);
          } else {
              type = new SnmpObjectType(syntax,
                                        units,
                                        access,
                                        status,
                                        desc,
                                        ref,
                                        (MibValue) index,
                                        defVal);
          }
          node.addValue(type);
          return node;
      }
  
      /**
       * Adds an SNMP notification type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitSnmpNotificationTypeMacroType(Production node)
          throws ParseException {
  
          ArrayList   objects = new ArrayList();
          SnmpStatus  status = null;
          String      desc = null;
          String      ref = null;
          Node        child;
  
          currentMib.setSmiVersion(2);
          for (int i = 0; i < node.getChildCount(); i++) {
              child = node.getChildAt(i);
              switch (child.getId()) {
              case Asn1Constants.SNMP_OBJECTS_PART:
                  objects = (ArrayList) getValue(child, 0);
                  break;
              case Asn1Constants.SNMP_STATUS_PART:
                  status = (SnmpStatus) getValue(child, 0);
                  break;
              case Asn1Constants.SNMP_DESCR_PART:
                  desc = getStringValue(child, 0);
                  break;
              case Asn1Constants.SNMP_REFER_PART:
                  ref = getStringValue(child, 0);
                  break;
              }
          }
          node.addValue(new SnmpNotificationType(objects,
                                                 status,
                                                 desc,
                                                 ref));
          return node;
      }
  
      /**
       * Adds an SNMP trap type as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitSnmpTrapTypeMacroType(Production node)
          throws ParseException {
  
          MibValue   enterprise = null;
          ArrayList  vars = new ArrayList();
          String     desc = null;
          String     ref = null;
          Node       child;
  
          for (int i = 0; i < node.getChildCount(); i++) {
              child = node.getChildAt(i);
              switch (child.getId()) {
              case Asn1Constants.SNMP_ENTERPRISE_PART:
                  enterprise = (MibValue) getValue(child, 0);
                  break;
              case Asn1Constants.SNMP_VAR_PART:
                  vars = child.getAllValues();
                  break;
              case Asn1Constants.SNMP_DESCR_PART:
                  desc = getStringValue(child, 0);
                  break;
              case Asn1Constants.SNMP_REFER_PART:
                  ref = getStringValue(child, 0);
                  break;
              }
          }
          node.addValue(new SnmpTrapType(enterprise, vars, desc, ref));
          return node;
      }
  
      /**
       * Adds an SNMP textual convention as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitSnmpTextualConventionMacroType(Production node)
          throws ParseException {
  
          String      display = null;
          SnmpStatus  status = null;
          String      desc = null;
          String      ref = null;
          MibType     syntax = null;
          Node        child;
  
          currentMib.setSmiVersion(2);
          for (int i = 0; i < node.getChildCount(); i++) {
              child = node.getChildAt(i);
              switch (child.getId()) {
              case Asn1Constants.SNMP_DISPLAY_PART:
                  display = getStringValue(child, 0);
                  break;
              case Asn1Constants.SNMP_STATUS_PART:
                  status = (SnmpStatus) getValue(child, 0);
                  break;
              case Asn1Constants.SNMP_DESCR_PART:
                  desc = getStringValue(child, 0);
                  break;
              case Asn1Constants.SNMP_REFER_PART:
                  ref = getStringValue(child, 0);
                  break;
              case Asn1Constants.SNMP_SYNTAX_PART:
                  syntax = (MibType) getValue(child, 0);
                  syntax.setComment(MibAnalyzerUtil.getComments(child));
                  break;
              }
          }
          node.addValue(new SnmpTextualConvention(display,
                                                  status,
                                                  desc,
                                                  ref,
                                                  syntax));
          return node;
      }
  
      /**
       * Adds an SNMP object group as a node value.
       *
       * @param node           the node being exited
       *
       * @return the node to add to the parse tree
       *
       * @throws ParseException if the node analysis discovered errors
       */
      protected Node exitSnmpObjectGroupMacroType(Production node)
          throws ParseException {
  
          ArrayList   objects;
          SnmpStatus  status;
          String      desc;
          String      ref;
  
          currentMib.setSmiVersion(2);
  