package org.dcache.services.info.base;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import static org.junit.Assert.assertTrue;
/**
* The VerifyingVisitor allows the verification that the visitor infrastructure is
* working as expected.
*
* Since the order sibling StateComponents are encountered is not guaranteed, we
* must describe the structure we expect to encounter rather than simply serialise
* the output (e.g., as a long String).
*/
public class VerifyingVisitor implements StateVisitor {
/**
* An Exception that indicates the operation has violated the expected behaviour of
* the visitor.
*/
private static class UnexpectedVisitDataException extends Exception {
private static final long serialVersionUID = -5609901746889782083L;
UnexpectedVisitDataException( String reason) {
super( reason);
}
}
/**
* A ComponentInfo holds all information about either a branch (StateComposite)
* or a metric (any subclass of StateValue).
*/
static class ComponentInfo {
Map<String,ComponentInfo> _children = new HashMap<>();
/** Have we visited this metric? (only used for metrics) */
private boolean _haveSeen;
/** Which PreDescends we have seen?: only used when this._type == BRANCH */
private final Set<String> _preDescend = new HashSet<>();
/** Which PostDescends we have seen?: only used when this._type == BRANCH */
private final Set<String> _postDescend = new HashSet<>();
/** Which PreSkipDescend we have seen?: only used when this._type == BRANCH */
private String _preSkipDescend;
/** Which PostSkipDescend we have seen?: only used when this._type == BRANCH */
private String _postSkipDescend;
/** Number of children that have _type StateComponentType.BRANCH */
private int _branchChildren;
/** Visited children */
private int _visitedChildren;
public static enum StateComponentType {
BRANCH( "StateComposite"), STRING( "StringStateValue"), INTEGER("IntegerStateValue"), BOOLEAN("BooleanStateValue"), FLOATINGPOINT("FloatingPointStateValue");
private final Class<?> _stateComponent;
StateComponentType( String stateComponentName) {
String fullName ="org.dcache.services.info.base."+stateComponentName;
Class<?> component = null;
try {
component = Class.forName( fullName);
} catch (ClassNotFoundException e) {
System.out.println( "Class " + fullName + " not found");
component = null;
} finally {
_stateComponent = component;
}
}
/**
* Identify which enum value is appropriate for a given StateComponent.
* @param component
* @return
* @throws IllegalArgumentException if the component isn't one of the known types.
*/
static StateComponentType identify( StateComponent component) {
for( StateComponentType type : StateComponentType.values()) {
if( type._stateComponent != null &&
type._stateComponent.isInstance( component)) {
return type;
}
}
throw new IllegalArgumentException( "Unable to identify argument");
}
}
private final String _value;
protected final StateComponentType _type;
/**
* Create a new BRANCH ComponentInfo
*/
protected ComponentInfo() {
_type = StateComponentType.BRANCH;
_value = null;
}
/**
* Create a new ComponentInfo to store a metric.
* @param type
* @param value
*/
private ComponentInfo( StateComponentType type, String value) {
assert( type != StateComponentType.BRANCH);
_type = type;
_value = value;
}
/**
* Get the branch with the given name. If it doesn't exist, create it.
* @param name
* @return
*/
ComponentInfo getOrCreateBranch( String name) {
ComponentInfo info;
try {
info = getChild( name);
assert( info._type == StateComponentType.BRANCH);
} catch( UnexpectedVisitDataException e) {
info = new ComponentInfo();
_children.put( name, info);
_branchChildren++;
}
return info;
}
/**
* Try to obtain the ComponentInfo of a child element.
* @param name the name the child
* @return the ComponentInfo corresponding to the child element
* @throws UnexpectedVisitDataException if no child element exists with name
*/
ComponentInfo getChild( String name) throws UnexpectedVisitDataException {
if( !_children.containsKey( name)) {
throw new UnexpectedVisitDataException("no child " + name);
}
return _children.get( name);
}
/**
* Add an expected StringStateValue metric as a child of this branch
* @param name
* @param value
*/
void addMetric( String metricName, StateComponentType metricType, String metricValue) {
assert( _type == StateComponentType.BRANCH);
_children.put( metricName, new ComponentInfo( metricType, metricValue));
}
/**
* Mark a metric (immediate child of this branch) as found.
* @param path path to metric.
* @param metricType actual type of metric
* @param metricValue actual value of metric
* @throws BadPathException if no metric exists at this path
* @throws BadMetricException if there is a problem with metric at this point.
*/
void markMetric( String metricName, StateComponentType metricType, String metricValue) throws UnexpectedVisitDataException {
if( !_children.containsKey( metricName)) {
throw new UnexpectedVisitDataException("received unexpected metric: " + metricName);
}
ComponentInfo metricInfo = _children.get( metricName);
if( metricInfo._type != metricType) {
throw new UnexpectedVisitDataException("expected type " + metricInfo
._type.toString() + ", got " + metricType
.toString());
}
if( !metricInfo._value.equals( metricValue)) {
throw new UnexpectedVisitDataException("expected value " + metricInfo._value + ", got " + metricValue);
}
if( metricInfo._haveSeen == true) {
throw new UnexpectedVisitDataException("already seen this metric");
}
_visitedChildren++;
metricInfo._haveSeen = true;
}
/**
* Mark a PreDescend
* @param childName
*/
void markPreDescend( String childName) throws UnexpectedVisitDataException {
if( !_children.containsKey( childName)) {
throw new UnexpectedVisitDataException("preDescend for unknown " + childName);
}
if( _type != StateComponentType.BRANCH) {
throw new UnexpectedVisitDataException("preDescend for non-branch " + childName);
}
if( _preDescend.size() >= _branchChildren) {
throw new UnexpectedVisitDataException("preDescend for " + childName + " when " + Integer
.toString(_preDescend
.size()) + " (of " + Integer
.toString(_children
.size()) + ") have been seen");
}
if( _preDescend.contains( childName)) {
throw new UnexpectedVisitDataException("duplicate preDescend for " + childName);
}
_visitedChildren++;
_preDescend.add( childName);
}
void markPostDescend( String childName) throws UnexpectedVisitDataException {
if( _type != StateComponentType.BRANCH) {
throw new UnexpectedVisitDataException("postDescend for non-branch " + childName);
}
if( !_children.containsKey( childName)) {
throw new UnexpectedVisitDataException("postDescend for unknown " + childName);
}
if( _postDescend.size() >= _branchChildren) {
throw new UnexpectedVisitDataException("postDescend for " + childName + " when all children have registered");
}
if( _postDescend.contains( childName)) {
throw new UnexpectedVisitDataException("duplicate postDescend for " + childName);
}
_postDescend.add( childName);
}
/**
* Whether this StateComponent is satisfied after running the visitor.
* @param myPath the name for this path element: only used to enhance error messages.
* @return
*/
boolean isGood( StatePath path) {
if( _type == StateComponentType.BRANCH) {
return isGoodBranch( path);
} else {
if( !_haveSeen) {
System.out.println( "["+path.toString() + "] missing metric");
return false;
}
}
return true;
}
/**
* Given the current element is a branch, are we satisfied with the visit?
*
* There are four cases to consider:
* 1. There is no skip, or skip and path are equal, or path is a child of skip,
* 2. path is parent of skip path,
* 3. path is an ancestor of skip path but not parent,
* 4. path is outside skip path.
*
* What we expect to see:
* 1. visiting all children
* 2. visiting the child that's the next path element,
* 3. preSkip and postSkip the child that's the next path element,
* 4. no sub-elements are visited.
*
* @param path
* @param skip
* @param emitError
* @return
*/
boolean isGoodBranch( StatePath path) {
String pathName = path != null ? path.toString() : "(root)";
if( _preDescend.size() != _branchChildren) {
System.out.println( "["+pathName + "] missing children in preDescend: expected " + _children.size() + ", got " + _preDescend.size());
return false;
}
if( _postDescend.size() != _branchChildren) {
System.out.println( "["+pathName + "] missing children in postDescend: expected " + _children.size() + ", got " + _postDescend.size());
return false;
}
if( _preSkipDescend != null) {
System.out.println( "["+pathName + "] preSkip unexpectedly not null: " + _preSkipDescend);
return false;
}
if( _postSkipDescend != null) {
System.out.println( "["+pathName + "] postSkip unexpectedly not null: " + _preSkipDescend);
return false;
}
if( _visitedChildren != _children.size()) {
System.out.println( "["+pathName + "] missing visited children, expected "+ Integer.toString( _children.size())+", got "+Integer.toString( _visitedChildren));
return false;
}
return true;
}
/**
* Reset this StateComponent to a preVisit state, ready for another visit.
*/
void reset() {
_haveSeen = false;
_visitedChildren = 0;
_preDescend.clear();
_postDescend.clear();
_preSkipDescend = null;
_postSkipDescend = null;
}
}
/** The root of our tree */
private final ComponentInfo _info = new ComponentInfo();
private boolean _encounteredException;
private boolean _seenRootPre, _seenRootPost;
/**
* Create an new VerifyingVisitor that expects to find nothing.
*/
public VerifyingVisitor() {}
/**
* Create a new VerifyingVisitor that expects a StateComposite (branch).
* This is equivalent to
* <code>
* VerifyingVisitor visitor = new VerifyingVisitor();
* visitor.addExpectedBranch( branchPath);
* </code>
* @param branchPath the StatePath of the expected branch.
*/
protected VerifyingVisitor(StatePath branchPath) {
addExpectedBranch( branchPath);
}
/**
* Create a new VerifyingVisitor that expects a StateValue (a metric).
* This is equivalent to:
* <code>
* VerifyingVisitor visitor = new VerifyingVisitor();
* visitor.addExpectedMetric( metricPath, metricValue);
* </code>
* @param metricPath
* @param metricValue
*/
protected VerifyingVisitor( StatePath metricPath, StateValue metricValue) {
addExpectedMetric( metricPath, metricValue);
}
/**
* Add information that we expect to see a certain branch
* @param branchPathStr
*/
public void addExpectedBranch( StatePath path) {
getOrCreateBranch( path);
}
public void addExpectedMetric( StatePath path, StateValue metricValue) {
ComponentInfo.StateComponentType type = ComponentInfo.StateComponentType.identify( metricValue);
ComponentInfo branchInfo = getOrCreateBranch( path.parentPath());
branchInfo.addMetric( path.getLastElement(), type, metricValue.toString());
}
/**
* Reset all values so the visitor may be used again.
*/
public void reset() {
_encounteredException = false;
_seenRootPre = false;
_seenRootPost = false;
resetThisAndChildren( _info);
}
/**
* Reset the visitor, visit the structure and assert the visitor is satisfied with the result.
* @param msg
* @param root
* @param skip
*/
protected void assertSatisfied( String msg, StateComposite root) {
reset();
root.acceptVisitor( null, this);
assertTrue( msg, satisfied());
}
/**
* Reset the visitor, visit the structure with supplied transition and assert the visitor is
* satisfied with the result.
* @param msg
* @param visitor
* @param root
* @param skip
*/
protected void assertSatisfiedTransition( String msg, StateComposite root, StateTransition transition) {
reset();
root.acceptVisitor( transition, null, this);
assertTrue( msg, satisfied());
}
/**
* Did we visit without encountering any exception and did we satisfy all expected elements?
* @return
*/
public boolean satisfied() {
if( !_seenRootPre) {
System.out.println( "Missing root pre descend");
return false;
}
if( !_seenRootPost) {
System.out.println( "Missing root post descend");
return false;
}
if( _encounteredException) {
return false;
}
return isGood( _info, null);
}
/**
*
* METHODS TO SATISFY VISITOR PATTERN
*
* Metrics:
*/
@Override
public void visitString( StatePath path, StringStateValue value) {
markMetric( path, ComponentInfo.StateComponentType.STRING, value.toString());
}
@Override
public void visitInteger( StatePath path, IntegerStateValue value) {
markMetric( path, ComponentInfo.StateComponentType.INTEGER, value.toString());
}
@Override
public void visitBoolean( StatePath path, BooleanStateValue value) {
markMetric( path, ComponentInfo.StateComponentType.BOOLEAN, value.toString());
}
@Override
public void visitFloatingPoint( StatePath path, FloatingPointStateValue value) {
markMetric( path, ComponentInfo.StateComponentType.FLOATINGPOINT, value.toString());
}
/**
* ... and branches:
*/
@Override
public void visitCompositePreDescend( StatePath path, Map<String,String> metadata) {
ComponentInfo thisBranch;
if( path == null) {
_seenRootPre = true;
return;
}
try {
thisBranch = getBranch( path.parentPath());
thisBranch.markPreDescend( path.getLastElement());
} catch (UnexpectedVisitDataException e) {
System.out.println( e.getMessage());
_encounteredException = true;
}
}
@Override
public void visitCompositePostDescend( StatePath path, Map<String,String> metadata) {
ComponentInfo thisBranch;
if( path == null) {
_seenRootPost = true;
return;
}
try {
thisBranch = getBranch(path.parentPath());
thisBranch.markPostDescend(path.getLastElement());
} catch (UnexpectedVisitDataException e) {
System.out.println( e.getMessage());
_encounteredException = true;
}
}
@Override
public boolean isVisitable( StatePath path) {
// TODO Auto-generated method stub
return true;
}
/**
* P R I V A T E M E T H O D S
*/
/**
* Obtain the ComponentInfo corresponding to the given path; if path is null
* then the root branch (_info) is returned.
* @param path the path to the branch to obtain
* @return the ComponentInfo for this branch
* @throws UnexpectedVisitDataException if any part of the path doesn't exist
*/
private ComponentInfo getBranch( StatePath path) throws UnexpectedVisitDataException {
ComponentInfo thisComponent = _info;
for(; path != null; path = path.childPath()) {
thisComponent = thisComponent
.getChild(path.getFirstElement());
}
return thisComponent;
}
/**
* Given a StatePath, return the ComponentInfo for this branch.
* @param branchPath the dot-separated path.
* @return the corresponding ComponentInfo for this branch.
*/
private ComponentInfo getOrCreateBranch( StatePath path) {
ComponentInfo thisComponent = _info;
for(; path != null; path = path.childPath()) {
thisComponent = thisComponent
.getOrCreateBranch(path.getFirstElement());
}
return thisComponent;
}
/**
* Is the supplied ComponentInfo and all its children "good"?
* @param info the top branch of the hierarchy
* @param path the path to info (used to decorate any error messages)
* @return true if this ComponentInfo and all children are "good", false otherwise.
*/
private boolean isGood( ComponentInfo info, StatePath path) {
if( !info.isGood( path)) {
return false;
}
for( Entry<String,ComponentInfo> e : info._children.entrySet()) {
String childName = e.getKey();
ComponentInfo childComponentInfo = e.getValue();
StatePath childPath = path != null ? path.newChild( childName) : new StatePath( childName);
if( !isGood( childComponentInfo, childPath)) {
return false;
}
}
return true;
}
/**
* Reset the given ComponentInfo to a pre-visited state and all
* of the children.
* @param info the ComponentInfo root of the tree to reset
*/
private void resetThisAndChildren( ComponentInfo info) {
info.reset();
for( ComponentInfo childInfo : info._children.values()) {
resetThisAndChildren(childInfo);
}
}
/**
* Mark a metric as found, verifying it matches the expected value.
* @param path the path to the metric
* @param type the metric type
* @param value the encountered metric value
*/
private void markMetric( StatePath path, ComponentInfo.StateComponentType type, String value) {
try {
ComponentInfo metricBranchInfo = getBranch( path.parentPath());
metricBranchInfo.markMetric( path.getLastElement(), type, value);
} catch( UnexpectedVisitDataException e) {
System.out.println( "["+path.toString() + "] " + e.getMessage());
_encounteredException = true;
}
}
}