/*
* 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;
import java.io.File;
import java.util.HashSet;
import net.percederberg.grammatica.parser.Node;
import net.percederberg.grammatica.parser.Production;
import net.percederberg.grammatica.parser.Token;
import net.percederberg.mibble.asn1.Asn1Constants;
/**
* Helper and utility functions for the MIB file analyzer.
*
* @author Per Cederberg
* @version 2.10
* @since 2.9
*/
class MibAnalyzerUtil {
/**
* Checks if a node corresponds to a bit value. This method is
* used to distinguish between bit values and object identifier
* values during the analysis.
*
* @param node the parse tree node to check
*
* @return true if the node contains a bit value, or
* false otherwise
*/
static boolean isBitValue(Node node) {
if (node.getId() == Asn1Constants.COMMA) {
return true;
} else if (node.getId() == Asn1Constants.NAME_VALUE_LIST
&& node.getChildCount() < 4) {
return true;
} else {
for (int i = 0; i < node.getChildCount(); i++) {
if (isBitValue(node.getChildAt(i))) {
return true;
}
}
return false;
}
}
/**
* Returns the MIB file reference for a production node.
*
* @param file the MIB file
* @param node the production or token node
*
* @return the MIB file reference
*/
static MibFileRef getFileRef(File file, Node node) {
MibFileRef ref = new MibFileRef(file,
node.getStartLine(),
node.getStartColumn());
Token comment = findCommentTokenBefore(node, null);
if (comment != null) {
ref.lineCommentStart = comment.getStartLine();
}
ref.lineEnd = node.getEndLine();
return ref;
}
/**
* Returns a string containing the raw input text for a node.
* This is created by concatenating the sequence of tokens that
* makes up the production.
*
* @param node the production or token node
*
* @return the input string
*
* @since 2.10
*/
static String getText(Node node) {
StringBuilder buffer = new StringBuilder();
Token token = findCommentTokenBefore(node, null);
if (token == null) {
token = findFirstToken(node);
}
Token lastToken = findLastToken(node);
while (token != null && token != lastToken) {
buffer.append(token.getImage());
token = token.getNextToken();
}
if (lastToken != null) {
buffer.append(lastToken.getImage());
}
return buffer.toString();
}
/**
* Returns all the comments associated with a specified node. If
* there are multiple comment lines, these will be concatenated
* into a single string. This method handles comments before,
* inside and after (starting on the same line) as the specified
* node. A set of previously processed tokens must be specified
* to avoid duplicates.
*
* @param node the production or token node
* @param marked the processed token set (modified)
*
* @return the comment string, or
* null if no comments were found
*/
static String getComments(Node node, HashSet<Token> marked) {
String comment = "";
Token token = findCommentTokenBefore(node, marked);
String str = processComments(token, marked);
if (str != null) {
comment = str;
}
str = processCommentsInside(node, marked);
if (str != null) {
if (comment.length() > 0) {
comment += "\n\n";
}
comment += str;
}
token = findCommentTokenAfter(node, true);
str = processComments(token, marked);
if (str != null) {
if (comment.length() > 0) {
comment += "\n\n";
}
comment += str;
}
return comment.length() <= 0 ? null : comment;
}
/**
* Returns all the footer comments after the specified node. A
* set of previously processed tokens must be specified to avoid
* duplicates.
*
* @param node the production or token node
* @param marked the processed token set (modified)
*
* @return the comment string, or
* null if no comments were found
*/
static String getCommentsFooter(Node node, HashSet<Token> marked) {
return processComments(findCommentTokenAfter(node, false), marked);
}
/**
* Reads comment tokens (and whitespace). Reading is stopped on
* the first non-comment or whitespace token, or if a token is
* marked as already consumed.
*
* @param token the starting comment token
* @param marked the processed token set (modified)
*
* @return the comment text (without '--' prefixes), or
* null if no comment text remained after trimming
*/
private static String processComments(Token token, HashSet<Token> marked) {
StringBuilder buffer = new StringBuilder();
while (token != null && !marked.contains(token)) {
if (token.getId() == Asn1Constants.COMMENT) {
marked.add(token);
buffer.append(token.getImage().substring(2).trim());
} else if (token.getId() == Asn1Constants.WHITESPACE) {
buffer.append(getLineBreaks(token.getImage()));
} else {
break;
}
token = token.getNextToken();
}
String res = buffer.toString().trim();
return res.length() <= 0 ? null : res;
}
/**
* Reads all unprocessed comment tokens inside the specified node.
* Note that only comment tokens not in the processed set will be
* returned by this method.
*
* @param node the production or token node
* @param marked the processed token set (modified)
*
* @return the comment text (without '--' prefixes), or
* null if no comments were found
*/
private static String processCommentsInside(Node node, HashSet<Token> marked) {
StringBuilder buffer = new StringBuilder();
Token token = findFirstToken(node);
Token last = findLastToken(node);
while (token != null && token != last) {
if (token.getId() == Asn1Constants.COMMENT && !marked.contains(token)) {
marked.add(token);
buffer.append(token.getImage().substring(2).trim());
buffer.append("\n");
}
token = token.getNextToken();
}
String res = buffer.toString().trim();
return res.length() <= 0 ? null : res;
}
/**
* Returns the first token in a production.
*
* @param node the production or token node
*
* @return the first token in the production, or
* null if none was found
*/
private static Token findFirstToken(Node node) {
while (node instanceof Production) {
node = node.getChildAt(0);
}
return (Token) node;
}
/**
* Returns the last token in a production.
*
* @param node the production or token node
*
* @return the last token in the production, or
* null if none was found
*/
private static Token findLastToken(Node node) {
while (node instanceof Production) {
node = node.getChildAt(node.getChildCount() - 1);
}
return (Token) node;
}
/**
* Returns the first comment token before the specified node.
* Optionally, a set of tokens to skip may be provided to stop
* the search if found.
*
* @param node the production node
* @param skip the set of tokens to skip, or null
*
* @return the first comment token found, or
* null if no comment token found
*/
private static Token findCommentTokenBefore(Node node, HashSet<Token> skip) {
Token comment = null;
Token token = findFirstToken(node);
if (token == null) {
return null;
}
token = token.getPreviousToken();
while (token != null && (skip == null || !skip.contains(token))) {
if (token.getId() == Asn1Constants.COMMENT) {
comment = token;
} else if (token.getId() == Asn1Constants.WHITESPACE) {
// Ignore token, check next
} else {
break;
}
token = token.getPreviousToken();
}
return comment;
}
/**
* Returns the first comment token after the specified node.
* Optionally, only tokens on the same line are checked.
*
* @param node the production node
* @param sameline the same line number flag
*
* @return the first comment token found, or
* null if no comment token found
*/
private static Token findCommentTokenAfter(Node node, boolean sameline) {
Token token = findLastToken(node);
if (token == null) {
return null;
}
int lineNo = token.getEndLine();
token = token.getNextToken();
while (token != null) {
if (sameline && lineNo != token.getStartLine()) {
return null;
} else if (token.getId() == Asn1Constants.COMMENT) {
return token;
} else if (token.getId() == Asn1Constants.WHITESPACE ||
token.getId() == Asn1Constants.COMMA) {
// Ignore token, check next
} else {
return null;
}
token = token.getNextToken();
}
return null;
}
/**
* Returns a string containing the line breaks of an input
* string.
*
* @param str the input string
*
* @return a string containing zero or more line breaks
*/
private static String getLineBreaks(String str) {
if (str == null) {
return null;
}
StringBuilder res = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == '\n') {
res.append('\n');
}
}
return res.toString();
}
}