SnmpRelativeOid.java

package eu.linuxengineering.snmp;

import java.util.Arrays;

import org.opennms.protocols.snmp.SnmpObjectId;

import eu.javaexperience.parse.ParsePrimitive;
import eu.javaexperience.regex.RegexTools;
import net.sf.snmpadaptor4j.object.SnmpOid;

public class SnmpRelativeOid
{
	/**
	 * -3: `from the root` element (eg: /)
	 * -2: current element (eg.: ./)
	 * -1: backward (eg.: ../)
	 * 0 and greater: valid oid elements
	 * */
	protected int[] path;
	
	public SnmpRelativeOid(int[] path)
	{
		this.path = path;
	}
	
	public int[] getPathComponents()
	{
		return path;
	}
	
	public SnmpOid resolve(SnmpOid oid)
	{
		if(isStatic())
		{
			return SnmpOid.newInstance(Arrays.copyOfRange(path, 1, path.length));
		}
		
		int[] p = oid.getOid();
		int ep = p.length;
		p = Arrays.copyOf(p, ep+path.length);
		
		for(int i=0;i<path.length;++i)
		{
			int el = path[i];
			if(PATH_CURRENT_ELEMENT == el)
			{
				continue;
			}
			else if(PATH_BACKWARD == el)
			{
				--ep;
			}
			else
			{
				p[ep++] = el;
			}
		}
		
		return SnmpOid.newInstance(Arrays.copyOf(p, ep));
	}
	
	public boolean isStatic()
	{
		if(path[0] != PATH_FROM_ROOT)
		{
			return false;
		}
		
		for(int i=0;i<path.length;++i)
		{
			if(path[i] == PATH_BACKWARD)
			{
				return false;
			}
		}
		return true;
	}
	
	public static final int PATH_FROM_ROOT = -3;
	public static final int PATH_CURRENT_ELEMENT = -2;
	public static final int PATH_BACKWARD = -1;
	
	/**
	 * OID as file path expression looks like:
	 *  ram free on linux: /1/3/6/1/4/1/2021/4/11/0
	 * 
	 * this may contain back steps like:
	 * 	/1/3/6/10/../1
	 * in this case the 10 is wiped out becuse of back step likely in the
	 * 	filesystem operations
	 * 
	 * it may contain back steps like:
	 * ./../10
	 * which can be resolved only relatively to a given node
	 * (in filesystem analogy for extample relatively to the current working
	 * 	directory)
	 * */
	public static int[] parseAsFilePathExpression(String path)
	{
		path = path.trim();
		
		if("/".equals(path))
		{
			return new int[]{PATH_FROM_ROOT};
		}
		
		String[] components = RegexTools.SLASHES.split(path);
		
		int[] ret = new int[components.length+1];
		int ep = 0;
		
		for(int i=0;i<components.length;++i)
		{
			String comp = components[i];
			if(0 == i && "".equals(comp))
			{
				ret[ep++] = PATH_FROM_ROOT;
			}
			else if(".".equals(comp))
			{
				if(0 == i)
				{
					ret[ep++] = PATH_CURRENT_ELEMENT;
				}
				//skip intermediate . elements like: 1/./././4 => 1/4
			}
			else if("..".equals(comp))
			{
				//pop out the previous non relative expression
				if(ep > 0)
				{
					//eg "/../../../../../" this resolves to "/"
					if(PATH_FROM_ROOT == ret[ep-1])//only occures when 0 == ep
					{
						continue;
					}
					else if(ret[ep-1] >= 0)
					{
						--ep;
						continue;
					}
					else if(ret[ep-1] == PATH_CURRENT_ELEMENT)
					{
						--ep;
					}
				}
				
				ret[ep++] = PATH_BACKWARD;
			}
			else
			{
				int val = ParsePrimitive.tryParseInt(comp, Integer.MIN_VALUE);
				if(val == Integer.MIN_VALUE || val < 0)
				{
					throw new RuntimeException("Illegal path element `"+comp+"` at location `"+i+"` in path expression: `"+path+"`");
				}
				ret[ep++] = val;
			}
		}
		
		return Arrays.copyOf(ret, ep);
	}
	
	public String renderPath()
	{
		StringBuilder sb = new StringBuilder();
		for(int i=0;i<path.length;++i)
		{
			int el = path[i];
			
			if(sb.length() > 0 || (1 == i && PATH_FROM_ROOT == path[0]))
			{
				sb.append("/");
			}
			
			if(PATH_FROM_ROOT == el)
			{
				continue;
			}
			
			if(PATH_CURRENT_ELEMENT == el)
			{
				sb.append(".");
			}
			else if(PATH_BACKWARD == el)
			{
				sb.append("..");
			}
			else
			{
				sb.append(el);
			}
		}
		return sb.toString();
	}
	
	@Override
	public String toString()
	{
		return "SnmpRelativeOid: "+renderPath();
	}
	
	public static SnmpRelativeOid parse(String path)
	{
		return new SnmpRelativeOid(parseAsFilePathExpression(path));
	}
}