RegexWalkFilter.java
package zserio.runtime.walker;
import java.lang.reflect.Array;
import java.util.Stack;
import java.util.regex.Pattern;
import zserio.runtime.typeinfo.FieldInfo;
import zserio.runtime.typeinfo.TypeInfoUtil;
/**
* Walk filter which allows to walk only paths matching the given regex.
*
* The path is constructed from field names within the root object, thus the root object
* itself is not part of the path.
*
* Array elements have the index appended to the path so that e.g. "compound.arrayField[0]" will match
* only the first element in the array "arrayField".
*/
public final class RegexWalkFilter implements WalkFilter
{
/**
* Constructor.
*
* @param pathRegex Path regex to use for filtering.
*/
public RegexWalkFilter(String pathRegex)
{
this.pathRegex = pathRegex;
this.currentPath = new Stack<String>();
}
@Override
public boolean beforeArray(Object array, FieldInfo fieldInfo)
{
currentPath.push(fieldInfo.getSchemaName());
if (Pattern.matches(pathRegex, getCurrentPath()))
return true; // the array itself matches
// try to find match in each element and continue into the array only if some match is found
final int length = Array.getLength(array);
for (int i = 0; i < length; ++i)
{
currentPath.set(currentPath.size() - 1, fieldInfo.getSchemaName() + "[" + i + "]");
final Object element = Array.get(array, i);
if (matchSubtree(element, fieldInfo))
return true;
}
currentPath.set(currentPath.size() - 1, fieldInfo.getSchemaName());
return false;
}
@Override
public boolean afterArray(Object array, FieldInfo fieldInfo)
{
currentPath.pop();
return true;
}
@Override
public boolean beforeCompound(Object compound, FieldInfo fieldInfo, int elementIndex)
{
appendPath(fieldInfo, elementIndex);
if (Pattern.matches(pathRegex, getCurrentPath()))
return true; // the compound itself matches
return matchSubtree(compound, fieldInfo);
}
@Override
public boolean afterCompound(Object compound, FieldInfo fieldInfo, int elementIndex)
{
popPath(fieldInfo, elementIndex);
return true;
}
@Override
public boolean beforeValue(Object value, FieldInfo fieldInfo, int elementIndex)
{
appendPath(fieldInfo, elementIndex);
return matchSubtree(value, fieldInfo);
}
@Override
public boolean afterValue(Object value, FieldInfo fieldInfo, int elementIndex)
{
popPath(fieldInfo, elementIndex);
return true;
}
boolean matchSubtree(Object member, FieldInfo fieldInfo)
{
if (member != null && TypeInfoUtil.isCompound(fieldInfo.getTypeInfo().getSchemaType()))
{
// is a not null compound, try to find match within its subtree
final DefaultWalkObserver defaultObserver = new DefaultWalkObserver();
final SubtreeRegexWalkFilter subtreeFilter = new SubtreeRegexWalkFilter(currentPath, pathRegex);
final Walker walker = new Walker(defaultObserver, subtreeFilter);
walker.walk(member);
return subtreeFilter.matches();
}
else
{
// try to match a simple value or null compound
return Pattern.matches(pathRegex, getCurrentPath());
}
}
private String getCurrentPath()
{
return getCurrentPathImpl(currentPath);
}
private void appendPath(FieldInfo fieldInfo, int elementIndex)
{
appendPathImpl(currentPath, fieldInfo, elementIndex);
}
private void popPath(FieldInfo fieldInfo, int elementIndex)
{
popPathImpl(currentPath, fieldInfo, elementIndex);
}
private static String getCurrentPathImpl(Stack<String> currentPath)
{
return String.join(".", currentPath);
}
private static void appendPathImpl(Stack<String> currentPath, FieldInfo fieldInfo, int elementIndex)
{
if (elementIndex == WalkerConst.NOT_ELEMENT)
currentPath.push(fieldInfo.getSchemaName());
else
currentPath.set(currentPath.size() - 1, fieldInfo.getSchemaName() + "[" + elementIndex + "]");
}
private static void popPathImpl(Stack<String> currentPath, FieldInfo fieldInfo, int elementIndex)
{
if (elementIndex == WalkerConst.NOT_ELEMENT)
currentPath.pop();
else
currentPath.set(currentPath.size() - 1, fieldInfo.getSchemaName());
}
/**
* Walks whole subtree and in case of match stops walking. Used to check whether any path
* within the subtree matches given regex.
*/
private static class SubtreeRegexWalkFilter implements WalkFilter
{
public SubtreeRegexWalkFilter(Stack<String> currentPath, String pathRegex)
{
this.currentPath = currentPath;
this.pathRegex = pathRegex;
matches = false;
}
public boolean matches()
{
return matches;
}
@Override
public boolean beforeArray(Object array, FieldInfo fieldInfo)
{
currentPath.push(fieldInfo.getSchemaName());
matches = Pattern.matches(pathRegex, getCurrentPath());
// terminate when the match is already found (note that array is never None here)
return !matches;
}
@Override
public boolean afterArray(Object array, FieldInfo fieldInfo)
{
currentPath.pop();
// terminate when the match is already found
return !matches;
}
@Override
public boolean beforeCompound(Object compound, FieldInfo fieldInfo, int elementIndex)
{
appendPath(fieldInfo, elementIndex);
matches = Pattern.matches(pathRegex, getCurrentPath());
// terminate when the match is already found (note that compound is never None here)
return !matches;
}
@Override
public boolean afterCompound(Object compound, FieldInfo fieldInfo, int elementIndex)
{
popPath(fieldInfo, elementIndex);
// terminate when the match is already found
return !matches;
}
@Override
public boolean beforeValue(Object value, FieldInfo fieldInfo, int elementIndex)
{
appendPath(fieldInfo, elementIndex);
matches = Pattern.matches(pathRegex, getCurrentPath());
// terminate when the match is already found
return !matches;
}
@Override
public boolean afterValue(Object value, FieldInfo fieldInfo, int elementIndex)
{
popPath(fieldInfo, elementIndex);
// terminate when the match is already found
return !matches;
}
private String getCurrentPath()
{
return RegexWalkFilter.getCurrentPathImpl(currentPath);
}
private void appendPath(FieldInfo fieldInfo, int elementIndex)
{
RegexWalkFilter.appendPathImpl(currentPath, fieldInfo, elementIndex);
}
private void popPath(FieldInfo fieldInfo, int elementIndex)
{
RegexWalkFilter.popPathImpl(currentPath, fieldInfo, elementIndex);
}
private final Stack<String> currentPath;
private final String pathRegex;
private boolean matches;
};
private final String pathRegex;
private final Stack<String> currentPath;
};