/* * Mibble MIB Parser (www.mibble.org) * * See LICENSE.txt for licensing information. * * Copyright (c) 2004-2017 Per Cederberg. All rights reserved. */ package net.percederberg.mibble.value; import java.util.ArrayList; import net.percederberg.mibble.Mib; import net.percederberg.mibble.MibException; import net.percederberg.mibble.MibFileRef; import net.percederberg.mibble.MibLoaderLog; import net.percederberg.mibble.MibType; import net.percederberg.mibble.MibValue; import net.percederberg.mibble.MibValueSymbol; /** * An object identifier value. This class stores the component * identifier values in a tree hierarchy. * * @author Per Cederberg * @version 2.10 * @since 2.0 */ public class ObjectIdentifierValue extends MibValue { /** * The declaration file location. This variable is only used when * resolving value references in order to present correct error * messages. After initialization it is set to null to minimize * memory impact. * * @since 2.10 */ private MibFileRef fileRef = null; /** * The component parent. */ private MibValue parent; /** * The component children. */ private ArrayList<ObjectIdentifierValue> children = new ArrayList<>(); /** * The object identifier component name. */ private String name; /** * The object identifier component value. */ private int value; /** * The MIB value symbol referenced by this object identifier. */ private MibValueSymbol symbol = null; /** * The cached numeric string representation of this value. This * variable is set when calling the toString() method the first * time and is used to optimize performance by avoiding any * subsequent recursive calls. * * @see #toString() */ private String cachedNumericValue = null; /** * Creates a new root object identifier value. * * @param name the component name, or null * @param value the component value */ public ObjectIdentifierValue(String name, int value) { super("OBJECT IDENTIFIER"); this.parent = null; this.name = name; this.value = value; } /** * Creates a new object identifier value. * * @param fileRef the definition MIB file reference * @param parent the component parent * @param name the component name, or null * @param value the component value * * @throws MibException if the object identifier parent already * had a child with the specified value */ public ObjectIdentifierValue(MibFileRef fileRef, ObjectIdentifierValue parent, String name, int value) throws MibException { super("OBJECT IDENTIFIER"); this.parent = parent; this.name = name; this.value = value; if (parent.getChildByValue(value) != null) { throw new MibException(fileRef, "cannot add duplicate OID " + "children with value " + value); } parent.addChild(null, fileRef, this); } /** * Creates a new object identifier value. * * @param fileRef the definition MIB file reference * @param parent the component parent * @param name the component name, or null * @param value the component value */ public ObjectIdentifierValue(MibFileRef fileRef, ValueReference parent, String name, int value) { super("OBJECT IDENTIFIER"); this.fileRef = fileRef; this.parent = parent; this.name = name; this.value = value; } /** * Initializes the MIB value. This will remove all levels of * indirection present, such as references to other values. No * value information is lost by this operation. This method may * modify this object as a side-effect, and will return the basic * value.<p> * * <strong>NOTE:</strong> This is an internal method that should * only be called by the MIB loader. * * @param log the MIB loader log * @param type the value type * * @return the basic MIB value * * @throws MibException if an error was encountered during the * initialization */ public MibValue initialize(MibLoaderLog log, MibType type) throws MibException { ValueReference ref = null; if (parent == null) { return this; } else if (parent instanceof ValueReference) { ref = (ValueReference) parent; } parent = parent.initialize(log, type); if (ref != null) { if (parent instanceof ObjectIdentifierValue) { ObjectIdentifierValue oid = (ObjectIdentifierValue) parent; oid.addChild(log, fileRef, this); } else { throw new MibException(ref.getFileRef(), "referenced value is not an " + "object identifier"); } } fileRef = null; cachedNumericValue = null; if (parent instanceof ObjectIdentifierValue) { return ((ObjectIdentifierValue) parent).getChildByValue(value); } else { return this; } } /** * Creates a value reference to this value. The value reference * is normally an identical value. Only certain values support * being referenced, and the default implementation of this * method throws an exception.<p> * * <strong>NOTE:</strong> This is an internal method that should * only be called by the MIB loader. * * @return the MIB value reference * * @since 2.2 */ public MibValue createReference() { return this; } /** * Clears and prepares this value for garbage collection. This * method will recursively clear any associated types or values, * making sure that no data structures references this object.<p> * * <strong>NOTE:</strong> This is an internal method that should * only be called by the MIB loader. */ protected void clear() { // Recursively clear all children in same MIB if (children != null) { Mib mib = getMib(); for (ObjectIdentifierValue child : new ArrayList<>(children)) { if (mib == null || mib == child.getMib()) { child.clear(); } } } // Remove parent reference if all children were cleared if (getChildCount() <= 0) { if (parent != null) { getParent().children.remove(this); parent = null; } children = new ArrayList<>(); } // Clear other value data symbol = null; super.clear(); } /** * Compares this object with the specified object for order. This * method will only attempt to compare each numerical OID part with * the other value, but may fall back to comparing the string * representations. * * @param obj the object to compare to * * @return less than zero if this object is less than the specified, * zero if the objects are equal, or * greater than zero otherwise * * @since 2.6 */ public int compareTo(Object obj) { if (obj instanceof ObjectIdentifierValue) { return compareToOid((ObjectIdentifierValue) obj); } else { return toString().compareTo(obj.toString()); } } /** * Compares this object with the specified OID for order. * * @param oid the OID to compare to * * @return less than zero if this OID is less than the specified, * zero if the OIDs are equal, or * greater than zero otherwise * * @since 2.10 */ private int compareToOid(ObjectIdentifierValue oid) { int[] one = getParentValues(); int[] two = oid.getParentValues(); for (int i = 0; i < one.length; i++) { if (i >= two.length) { return 1; } else if (one[i] != two[i]) { return one[i] - two[i]; } } return (one.length == two.length) ? 0 : -1; } /** * Checks if this object equals another object. This method will * compare the string representations for equality. * * @param obj the object to compare with * * @return true if the objects are equal, or * false otherwise */ public boolean equals(Object obj) { return toString().equals(obj.toString()); } /** * Returns a hash code for this object. * * @return a hash code for this object */ public int hashCode() { return toString().hashCode(); } /** * Returns the parent object identifier value. * * @return the parent object identifier value, or * null if no parent exists */ public ObjectIdentifierValue getParent() { if (parent instanceof ObjectIdentifierValue) { return (ObjectIdentifierValue) parent; } else { return null; } } /** * Returns an array of all the numeric values the OID chain. The root * ancestor value is placed at index zero. * * @return an array of the numeric OID values * * @since 2.10 */ public int[] getParentValues() { return getParentValuesInternal(1); } /** * Returns an array of all the numeric values the OID chain. The root * ancestor value is placed at index zero. * * @param length the minimum array length * * @return an array of the numeric OID values * * @since 2.10 */ private int[] getParentValuesInternal(int length) { int[] res; if (parent instanceof ObjectIdentifierValue) { res = ((ObjectIdentifierValue) parent).getParentValuesInternal(length + 1); } else { res = new int[length]; } res[res.length - length] = value; return res; } /** * Returns this object identifier component name. * * @return the object identifier component name, or * null if the component has no name */ public String getName() { return name; } /** * Returns this object identifier component value. * * @return the object identifier component value */ public int getValue() { return value; } /** * Returns the symbol connected to this object identifier. * * @return the symbol connected to this object identifier, or * null if no value symbol is connected */ public MibValueSymbol getSymbol() { return symbol; } /** * Sets the symbol connected to this object identifier.<p> * * <strong>NOTE:</strong> This is an internal method that should * only be called by the MIB loader. * * @param symbol the value symbol */ public void setSymbol(MibValueSymbol symbol) { if (name == null) { name = symbol.getName(); } this.symbol = symbol; } /** * Returns the MIB that this object identifier is connected to. * This method simply returns the symbol MIB. * * @return the symbol MIB, or * null if no symbol has been set * * @since 2.10 */ public Mib getMib() { return symbol == null ? null : symbol.getMib(); } /** * Returns the number of child object identifier values. * * @return the number of child object identifier values */ public int getChildCount() { return children == null ? 0 : children.size(); } /** * Returns a child object identifier value. The children are * ordered by their value, not necessarily in the order in which * they appear in the original MIB file. * * @param index the child position, starting from 0 * * @return the child object identifier value, or * null if not found */ public ObjectIdentifierValue getChild(int index) { return children.get(index); } /** * Returns a child object identifier value. The children are * searched by their component names. This method uses linear * search and therefore has time complexity O(n). Note that most * OID:s don't have a component name, but only an associated * symbol. * * @param name the child name * * @return the child object identifier value, or * null if not found * * @since 2.5 */ public ObjectIdentifierValue getChildByName(String name) { for (ObjectIdentifierValue child : children) { if (name.equals(child.getName())) { return child; } } return null; } /** * Returns a child object identifier value. The children are * searched by their numerical value. This method uses binary * search and therefore has time complexity O(log(n)) for the * worst case. Special handling of the common case (a child * array without numeric gaps), allow for O(1) performance most * of the time. * * @param value the child value * * @return the child object identifier value, or * null if not found * * @since 2.5 */ public ObjectIdentifierValue getChildByValue(int value) { if (value > 0 && value <= children.size()) { ObjectIdentifierValue child = children.get(value - 1); if (child.value == value) { return child; } } int low = 0; int high = children.size(); int pos = (low + high) / 2; while (low < high) { ObjectIdentifierValue child = children.get(pos); if (child.value == value) { return child; } else if (child.value < value) { low = pos + 1; } else { high = pos; } pos = (low + high) / 2; } return null; } /** * Returns an array of all child object identifier values. The * children are ordered by their value, not necessarily in the * order in which they appear in the original MIB file. * * @return the child object identifier values * * @since 2.3 */ public ObjectIdentifierValue[] getAllChildren() { ObjectIdentifierValue[] values; values = new ObjectIdentifierValue[children.size()]; children.toArray(values); return values; } /** * Searches the OID tree for the best match. The returned OID * value may be either an ancestor or a descendant node (or this * node itself). The search requires the full numeric OID value * (from the root). * * @param oid the numeric OID string to search for * * @return the best matching OID value, or * null if no partial match was found * * @since 2.10 */ public ObjectIdentifierValue find(String oid) { if (oid.startsWith(".")) { oid = oid.substring(1); } if (oid.length() > 0 && toString().startsWith(oid)) { return findAncestor(oid); } else if (oid.startsWith(toString())) { return findDescendant(oid); } else { return null; } } /** * Searches the OID tree for the best matching ancestor. The * returned OID will be an exact match of this node or one of its * parents. The search requires the full numeric OID value (from * the root). * * @param oid the numeric OID string to search for * * @return the matching ancestor OID value, or * null if no match was found * * @since 2.10 */ public ObjectIdentifierValue findAncestor(String oid) { if (oid.startsWith(".")) { oid = oid.substring(1); } ObjectIdentifierValue ancestor = this; while (ancestor != null && !ancestor.toString().equals(oid)) { ancestor = ancestor.getParent(); } return ancestor; } /** * Searches the OID tree for the best matching descendant. The * returned OID value will be the longest matching child node (or * this node itself), but doesn't have to be an exact match. The * search requires the full numeric OID value (from the root). * * @param oid the numeric OID string to search for * * @return the best matching descendant OID value, or * null if no match was found * * @since 2.10 */ public ObjectIdentifierValue findDescendant(String oid) { if (oid.startsWith(".")) { oid = oid.substring(1); } if (!oid.startsWith(toString())) { return null; } oid = oid.substring(toString().length()); if (oid.startsWith(".")) { oid = oid.substring(1); } ObjectIdentifierValue parent = this; ObjectIdentifierValue child = this; while (child != null && oid.length() > 0) { int value = -1; try { int pos = oid.indexOf('.'); if (pos > 0) { value = Integer.parseInt(oid.substring(0, pos)); oid = oid.substring(pos + 1); } else { value = Integer.parseInt(oid); oid = ""; } } catch (NumberFormatException ignore) { oid = ""; } parent = child; child = child.getChildByValue(value); } return (child == null) ? parent : child; } /** * Adds a child component. The children will be inserted in the * value order. If a child with the same value has already been * added, the new child will be merged with the previous one (if * possible) and the resulting child will be returned. * * @param log the MIB loader log * @param fileRef the definition MIB file reference * @param child the child component * * @return the child object identifier value added * * @throws MibException if an irrecoverable conflict between two * children occurred */ private ObjectIdentifierValue addChild(MibLoaderLog log, MibFileRef fileRef, ObjectIdentifierValue child) throws MibException { // Insert child in value order, searching backwards to // optimize the most common case (ordered insertion) int i = children.size(); while (i > 0) { ObjectIdentifierValue value = children.get(i - 1); if (value.getValue() == child.getValue()) { value = value.merge(log, fileRef, child); children.set(i - 1, value); return value; } else if (value.getValue() < child.getValue()) { break; } i--; } children.add(i, child); return child; } /** * Adds all the children from another object identifier value. * The children are not copied, but actually transfered from the * other value. If this value lacks a name component, it will be * set from other value. This operation thus corresponds to a * merge and thus can only be made under certain conditions. For * example, no child OID:s may have name conflicts. It is assumed * that the other OID has the same numerical value as this one. * * @param log the MIB loader log * @param fileRef the definition MIB file reference * @param parent the OID parent value for the children * * @throws MibException if an irrecoverable conflict between two * children occurred */ private void addChildren(MibLoaderLog log, MibFileRef fileRef, ObjectIdentifierValue parent) throws MibException { if (name == null) { name = parent.name; } else if (parent.name != null && !parent.name.equals(name)) { String msg = "OID component '" + parent.name + "' was previously " + "defined as '" + name + "'"; if (log == null) { throw new MibException(fileRef, msg); } else { log.addWarning(fileRef, msg); } } if (parent.symbol != null) { throw new MibException(fileRef, "INTERNAL ERROR: OID merge with " + "symbol reference already set"); } for (ObjectIdentifierValue child : parent.children) { child.parent = this; addChild(log, fileRef, child); } parent.children = new ArrayList<>(); } /** * Merges this object identifier value with another one. One of * the two objects will be discarded and the other will be used * as the merge destination and returned. Note that this * operation modifies both this value and the specified value. * The merge can only be made under certain conditions, for * example that no child OID:s have name conflicts. It is also * assumed that the two OID:s have the same numerical value. * * @param log the MIB loader log * @param fileRef the definition MIB file reference * @param value the OID value to merge with * * @return the merged object identifier value * * @throws MibException if the merge couldn't be performed due to * some conflict or invalid state */ private ObjectIdentifierValue merge(MibLoaderLog log, MibFileRef fileRef, ObjectIdentifierValue value) throws MibException { if (symbol != null || (value.symbol == null && children.size() > 0)) { addChildren(log, fileRef, value); return this; } else { value.addChildren(log, fileRef, this); return value; } } /** * Returns a string representation of this value. The string will * contain the full numeric object identifier value with each * component separated with a dot ('.'). * * @return a string representation of this value */ public Object toObject() { return toString(); } /** * Returns a string representation of this value. The string will * contain the full numeric object identifier value with each * component separated with a dot ('.'). * * @return a string representation of this value */ public String toString() { if (cachedNumericValue == null) { StringBuilder buffer = new StringBuilder(); if (parent != null) { buffer.append(parent.toString()); buffer.append("."); } buffer.append(value); cachedNumericValue = buffer.toString(); } return cachedNumericValue; } /** * Returns a detailed string representation of this value. The * string will contain the full numeric object identifier value * with optional names for each component. * * @return a detailed string representation of this value */ public String toDetailString() { StringBuilder buffer = new StringBuilder(); if (parent instanceof ObjectIdentifierValue) { buffer.append(((ObjectIdentifierValue) parent).toDetailString()); buffer.append("."); } if (name == null) { buffer.append(value); } else { buffer.append(name); buffer.append("("); buffer.append(value); buffer.append(")"); } return buffer.toString(); } /** * Returns an ASN.1 representation of this value. The string will * contain references to any parent OID value that can be found. * * @return an ASN.1 representation of this value * * @since 2.6 */ public String toAsn1String() { StringBuilder buffer = new StringBuilder(); if (parent instanceof ObjectIdentifierValue) { ObjectIdentifierValue ref = (ObjectIdentifierValue) parent; if (ref.getSymbol() == null) { buffer.append(ref.toAsn1String()); } else { buffer.append(ref.getSymbol().getName()); } buffer.append(" "); } if (name == null || getSymbol() != null) { buffer.append(value); } else { buffer.append(name); buffer.append("("); buffer.append(value); buffer.append(")"); } return buffer.toString(); } }