com.google.common.css.compiler.ast.DefaultVisitController Maven / Gradle / Ivy
Show all versions of closure-stylesheets Show documentation
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.css.compiler.ast;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Queues;
import com.google.common.css.compiler.ast.CssCompositeValueNode.Operator;
import java.util.Deque;
import java.util.List;
/**
* Default implementation of the MutatingVisitController. The controller is
* mutating or not depending on a flag passed as a parameter to the constructor.
*
* @author [email protected] (Oana Florescu)
*/
class DefaultVisitController implements MutatingVisitController {
/** The (sub)tree to be visited. */
private final CssNode subtree;
/** Whether mutations of the tree are allowed or not. */
private final boolean allowMutating;
/** The visitor of the tree. */
@VisibleForTesting
CssTreeVisitor visitor;
/** The stack of states for the controller. */
private final StateStack stateStack = new StateStack();
/** Whether the visit was required to stop. */
@SuppressWarnings("unused")
private boolean stopVisitCalled = false;
@SuppressWarnings("serial")
private static class StopVisitRequestedException extends RuntimeException {}
/**
* Interface for CSS AST visit states. Visit states are used to track which
* node and what type of node is currently visited, perform tree modifications
* and take care of visit state transitions. Visit states should only allow
* valid state transitions - this can be used as AST structure validation.
*
* @param type of the children CSS nodes that can be used as a replacement
* for currently visited block node
*/
@VisibleForTesting
interface VisitState {
/**
* Performs the visit by calling appropriate methods of the visitor
* (enter and leave).
*/
void doVisit();
/**
* Transitions to next state by putting a new one onto the stack or popping
* the old one off the state stack. Some implementations can handle children
* nodes changes and omit visits of children nodes (effectively a subtree).
*/
void transitionToNextState();
/**
* Notifies the state, that the stop tree visit has been requested.
*
*
NOTE(dgajda): Practically unused, remove?
*/
void stopVisitCalled();
/**
* Notifies the state that removal of current node is requested.
* Performs the removal by passing the control to the state
* below current one.
*/
void removeCurrentNodeCalled();
/** Removes currently visited child node. */
void removeCurrentChild();
/**
* Used to notify current state and allow the state below of the top state
* remove the current node with a list of replacement nodes.
*
*
NOTE(dgajda): This method probably does not need to be in VisitState.
*
* @param type od replacement nodes
* @param replacementNodes nodes used to replace current block
* @param visitTheReplacementNodes whether new nodes should also be visited
*/
void replaceCurrentBlockChildWithCalled(
List replacementNodes,
boolean visitTheReplacementNodes);
/**
* Replaces current node with given replacement nodes.
* Called by {@link #replaceCurrentBlockChildWithCalled(List, boolean)}.
*
* @param replacementNodes nodes used to replace current block
* @param visitTheReplacementNodes whether new nodes should also be visited
*/
void replaceCurrentBlockChildWith(List replacementNodes,
boolean visitTheReplacementNodes);
}
/**
* Base implementation of AST visit state.
*
* @param type of the children CSS nodes that can be used as a replacement
* for currently visited block node
*/
@VisibleForTesting
abstract class BaseVisitState
implements VisitState {
@Override
public void stopVisitCalled() {}
@Override
public void removeCurrentChild() {
// Assume that by default this cannot happen.
throw new AssertionError("Current child removal is not supported by " +
this.getClass().getName() + " VisitState class.");
}
@Override
public void removeCurrentNodeCalled() {
stateStack.pop();
stateStack.getTop().removeCurrentChild();
}
@Override
public void replaceCurrentBlockChildWithCalled(
List replacementNodes, boolean visitTheReplacementNodes) {
stateStack.pop();
@SuppressWarnings("unchecked")
VisitState topState = (VisitState) stateStack.getTop();
topState.replaceCurrentBlockChildWith(
replacementNodes, visitTheReplacementNodes);
}
@Override
public void replaceCurrentBlockChildWith(
List replacementNodes, boolean visitTheReplacementNodes) {
// Assume that by default this cannot happen.
// assert false;
}
public VisitState extends CssNode> createFallbackState(N child) {
return null;
}
}
/**
* Base class for VisitStates which control visits of {@link CssNodesListNode}
* children.
*
* @param type of the children CSS nodes that can be used as a replacement
* for currently visited block node
*/
abstract class VisitChildrenState
extends BaseVisitState {
private final CssNodesListNode block;
private int currentIndex = -1;
VisitChildrenState(CssNodesListNode block) {
this.block = block;
}
@Override
public void transitionToNextState() {
if (currentIndex == block.numChildren() - 1) {
stateStack.pop();
return;
}
// Remain in this state to finish visiting all the children
currentIndex++;
stateStack.push(getVisitState(block.getChildAt(currentIndex)));
return;
}
/**
* Returns a visit state for a given child node.
*
* @param node child node to create visit state for
* @return new visit state
*/
abstract VisitState getVisitState(T node);
/** {@inheritDoc} */
@Override
public void doVisit() {
// Does nothing.
}
}
/**
* Base class for VisitStates which control visits of {@link CssNodesListNode}
* children and can replace currently visited node with replacement nodes.
*
* @param type of the children CSS nodes that can be used as a replacement
* for currently visited block node
*/
abstract class VisitReplaceChildrenState
extends BaseVisitState {
protected int currentIndex = -1;
protected boolean doNotIncreaseIndex = false;
protected final CssNodesListNode node;
VisitReplaceChildrenState(CssNodesListNode node) {
this.node = node;
}
@Override
public void doVisit() {
// Does nothing.
}
@Override
public void removeCurrentChild() {
node.removeChildAt(currentIndex);
doNotIncreaseIndex = true;
}
@Override
public void replaceCurrentBlockChildWith(
List replacementNodes, boolean visitTheReplacementNodes) {
node.replaceChildAt(currentIndex, replacementNodes);
if (visitTheReplacementNodes) {
doNotIncreaseIndex = true;
} else {
currentIndex += replacementNodes.size() - 1;
}
}
@Override
public void transitionToNextState() {
// We get out of this state if we are on the last child and we are allowed
// to increment the index, which means we arrived here with no special
// case, or we have just removed the last element.
if ((currentIndex == node.numChildren() - 1 && !doNotIncreaseIndex)
|| currentIndex == node.numChildren()) {
stateStack.pop();
return;
}
// Remain in this state to finish visiting all the children.
if (!doNotIncreaseIndex) {
currentIndex++;
} else {
doNotIncreaseIndex = false;
}
VisitState extends CssNode> state =
createVisitState(node.getChildAt(currentIndex), this);
if (state != null) {
stateStack.push(state);
}
}
}
/**
* Unfinished base class for VisitStates which share the same code to
* optionally visit child nodes.
*
* @param type of the children CSS nodes that can be used as a replacement
* for currently visited block node
*/
abstract class VisitChildrenOptionalState
extends BaseVisitState {
// TODO(user): move the common code here or delete this
// useless complexity.
}
@VisibleForTesting
class RootVisitBeforeChildrenState extends BaseVisitState {
private final CssRootNode root;
RootVisitBeforeChildrenState(CssRootNode root) {
Preconditions.checkNotNull(root);
this.root = root;
}
@Override
public void doVisit() {
visitor.enterTree(root);
}
@Override
public void transitionToNextState() {
stateStack.transitionTo(
new RootVisitCharsetState(root, root.getCharsetRule()));
}
}
@VisibleForTesting
class RootVisitAfterChildrenState extends BaseVisitState {
private final CssRootNode root;
RootVisitAfterChildrenState(CssRootNode root) {
Preconditions.checkNotNull(root);
this.root = root;
}
@Override
public void doVisit() {
visitor.leaveTree(root);
}
@Override
public void transitionToNextState() {
stateStack.pop();
// assert stateStack.isEmpty();
}
}
@VisibleForTesting
class RootVisitCharsetState extends BaseVisitState {
private final CssRootNode root;
private final CssAtRuleNode charsetRule;
RootVisitCharsetState(CssRootNode root, CssAtRuleNode charsetRule) {
this.root = root;
this.charsetRule = charsetRule;
}
@Override
public void doVisit() {
if (charsetRule == null) {
// Nothing left to do.
return;
}
}
@Override
public void removeCurrentNodeCalled() {
root.setCharsetRule(null);
}
@Override
public void transitionToNextState() {
stateStack.transitionTo(new RootVisitImportBlockState(root,
root.getImportRules()));
}
}
@VisibleForTesting
class RootVisitImportBlockState extends BaseVisitState {
private final CssRootNode root;
private final CssImportBlockNode block;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
RootVisitImportBlockState(CssRootNode root, CssImportBlockNode block) {
this.root = root;
this.block = block;
}
@Override
public void doVisit() {
if (!visitedChildren) {
shouldVisitChildren = visitor.enterImportBlock(block);
} else {
visitor.leaveImportBlock(block);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren && shouldVisitChildren) {
stateStack.push(new VisitImportBlockChildrenState(block));
visitedChildren = true;
} else {
stateStack.transitionTo(
new RootVisitBodyState(root, root.getBody()));
}
}
}
@VisibleForTesting
class VisitImportBlockChildrenState
extends VisitChildrenState {
VisitImportBlockChildrenState(CssImportBlockNode block) {
super(block);
}
@Override
VisitState getVisitState(CssImportRuleNode node) {
return new VisitImportRuleState(node);
}
}
@VisibleForTesting
class VisitImportRuleState extends BaseVisitState {
private final CssImportRuleNode node;
VisitImportRuleState(CssImportRuleNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (visitor.enterImportRule(node)) {
visitor.leaveImportRule(node);
}
}
@Override
public void transitionToNextState() {
stateStack.pop();
}
}
@VisibleForTesting
class VisitProvideState extends BaseVisitState {
private final CssProvideNode node;
VisitProvideState(CssProvideNode node) {
this.node = node;
}
@Override
public void doVisit() {
visitor.enterProvideNode(node);
visitor.leaveProvideNode(node);
}
@Override
public void transitionToNextState() {
stateStack.pop();
}
}
@VisibleForTesting
class VisitRequireState extends BaseVisitState {
private final CssRequireNode node;
VisitRequireState(CssRequireNode node) {
this.node = node;
}
@Override
public void doVisit() {
visitor.enterRequireNode(node);
visitor.leaveRequireNode(node);
}
@Override
public void transitionToNextState() {
stateStack.pop();
}
}
@VisibleForTesting
class RootVisitBodyState extends VisitChildrenOptionalState {
private final CssRootNode root;
private final CssBlockNode body;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
RootVisitBodyState(CssRootNode root, CssBlockNode body) {
this.root = root;
this.body = body;
}
@Override
public void doVisit() {
if (!visitedChildren) {
shouldVisitChildren = visitor.enterBlock(body);
} else {
visitor.leaveBlock(body);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren && shouldVisitChildren) {
stateStack.push(new VisitBlockChildrenState(body));
visitedChildren = true;
} else {
stateStack.transitionTo(new RootVisitAfterChildrenState(root));
}
}
}
@VisibleForTesting
class VisitBlockChildrenState extends VisitReplaceChildrenState {
VisitBlockChildrenState(CssBlockNode block) {
super(block);
}
}
@VisibleForTesting
class VisitDefinitionState extends VisitChildrenOptionalState {
private final CssDefinitionNode node;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
VisitDefinitionState(CssDefinitionNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
shouldVisitChildren = visitor.enterDefinition(node);
} else {
visitor.leaveDefinition(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren && shouldVisitChildren) {
stateStack.push(new VisitDefinitionParametersState(node));
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitDefinitionParametersState
extends VisitReplaceChildrenState {
VisitDefinitionParametersState(CssDefinitionNode def) {
super(def);
}
@Override
public VisitState createFallbackState(CssValueNode child) {
return new VisitValueNodeState(child);
}
}
@VisibleForTesting
class VisitMediaRuleState extends VisitReplaceChildrenState {
private final CssMediaRuleNode node;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
VisitMediaRuleState(CssMediaRuleNode node) {
super(node);
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren && currentIndex == -1) {
shouldVisitChildren = visitor.enterMediaRule(node);
} else if (visitedChildren) {
visitor.leaveMediaRule(node);
}
}
@Override
public void replaceCurrentBlockChildWith(
List replacementNodes, boolean visitTheReplacementNodes) {
// If we're replacing the current property with a composite value
// separated by the space, we really just want to graft those nodes at
// the current child's location.
if (replacementNodes.size() == 1
&& replacementNodes.get(0) instanceof CssCompositeValueNode) {
CssCompositeValueNode compositeValueNode =
(CssCompositeValueNode) replacementNodes.get(0);
if (compositeValueNode.getOperator() == Operator.SPACE) {
replacementNodes = compositeValueNode.getValues();
}
}
super.replaceCurrentBlockChildWith(
replacementNodes, visitTheReplacementNodes);
}
@Override
public void transitionToNextState() {
if (visitedChildren || !shouldVisitChildren) {
stateStack.pop();
return;
}
if (!doNotIncreaseIndex) {
currentIndex++;
} else {
doNotIncreaseIndex = false;
}
final int parametersCount = node.getParameters().size();
if (currentIndex < parametersCount) {
if (currentIndex < parametersCount - 1) {
stateStack.push(new VisitMediaTypeListDelimiterState(node));
}
stateStack.push(getVisitState(node.getParameters().get(currentIndex)));
} else {
if (node.getType().hasBlock()) {
stateStack.push(new VisitUnknownAtRuleBlockState(node.getBlock()));
}
visitedChildren = true;
}
}
public VisitState extends CssNode> getVisitState(CssValueNode node) {
if (node instanceof CssCompositeValueNode) {
return new VisitCompositeValueState((CssCompositeValueNode) node);
} else {
return new VisitValueNodeState(node);
}
}
}
private class VisitMediaTypeListDelimiterState
extends BaseVisitState {
private final CssNodesListNode extends CssNode> node;
public VisitMediaTypeListDelimiterState(
CssNodesListNode extends CssNode> node) {
this.node = node;
}
@Override public void doVisit() {
visitor.enterMediaTypeListDelimiter(node);
visitor.leaveMediaTypeListDelimiter(node);
}
@Override public void transitionToNextState() {
stateStack.pop();
}
}
@VisibleForTesting
class VisitPageRuleState extends VisitChildrenOptionalState {
private final CssPageRuleNode node;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
VisitPageRuleState(CssPageRuleNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
shouldVisitChildren = visitor.enterPageRule(node);
} else {
visitor.leavePageRule(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren && shouldVisitChildren) {
stateStack.push(new VisitUnknownAtRuleBlockState(node.getBlock()));
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitPageSelectorState extends VisitChildrenOptionalState {
private final CssPageSelectorNode node;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
VisitPageSelectorState(CssPageSelectorNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
shouldVisitChildren = visitor.enterPageSelector(node);
} else {
visitor.leavePageSelector(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren && shouldVisitChildren) {
stateStack.push(new VisitUnknownAtRuleBlockState(node.getBlock()));
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitFontFaceState extends VisitChildrenOptionalState {
private final CssFontFaceNode node;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
VisitFontFaceState(CssFontFaceNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
shouldVisitChildren = visitor.enterFontFace(node);
} else {
visitor.leaveFontFace(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren && shouldVisitChildren) {
stateStack.push(new VisitUnknownAtRuleBlockState(node.getBlock()));
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitConditionalBlockState extends BaseVisitState {
private final CssConditionalBlockNode block;
private boolean visitedChildren = false;
VisitConditionalBlockState(CssConditionalBlockNode block) {
this.block = block;
}
@Override
public void doVisit() {
if (!visitedChildren) {
visitor.enterConditionalBlock(block);
} else {
visitor.leaveConditionalBlock(block);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren) {
stateStack.push(
new VisitConditionalBlockChildrenState(block));
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitConditionalBlockChildrenState
extends VisitChildrenState {
VisitConditionalBlockChildrenState(CssConditionalBlockNode block) {
super(block);
}
@Override
VisitState getVisitState(CssConditionalRuleNode node) {
return new VisitConditionalRuleState(node);
}
}
@VisibleForTesting
class VisitConditionalRuleState extends VisitChildrenOptionalState {
private final CssConditionalRuleNode node;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
VisitConditionalRuleState(CssConditionalRuleNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
shouldVisitChildren = visitor.enterConditionalRule(node);
} else {
visitor.leaveConditionalRule(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren && shouldVisitChildren) {
stateStack.push(
new VisitConditionalRuleChildrenState(node.getBlock()));
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitConditionalRuleChildrenState extends VisitReplaceChildrenState {
VisitConditionalRuleChildrenState(CssBlockNode block) {
super(block);
}
}
@VisibleForTesting
class VisitRulesetState extends VisitChildrenOptionalState {
private final CssRulesetNode node;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
VisitRulesetState(CssRulesetNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
shouldVisitChildren = visitor.enterRuleset(node);
} else {
visitor.leaveRuleset(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren && shouldVisitChildren) {
stateStack.push(new VisitSelectorBlockState(node, node.getSelectors()));
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitKeyframeRulesetState extends VisitChildrenOptionalState {
private final CssKeyframeRulesetNode node;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
VisitKeyframeRulesetState(CssKeyframeRulesetNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
shouldVisitChildren = visitor.enterKeyframeRuleset(node);
} else {
visitor.leaveKeyframeRuleset(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren && shouldVisitChildren) {
stateStack.push(new VisitKeyBlockState(node, node.getKeys()));
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitSelectorBlockState extends BaseVisitState {
private final CssSelectorListNode block;
private final CssRulesetNode ruleset;
private boolean visitedChildren = false;
VisitSelectorBlockState(CssRulesetNode ruleset,
CssSelectorListNode block) {
this.ruleset = ruleset;
this.block = block;
}
@Override
public void doVisit() {
if (!visitedChildren) {
visitor.enterSelectorBlock(block);
} else {
visitor.leaveSelectorBlock(block);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren) {
stateStack.push(
new VisitSelectorBlockChildrenState(block));
visitedChildren = true;
} else {
stateStack.transitionTo(
new VisitDeclarationBlockState(ruleset.getDeclarations()));
}
}
}
@VisibleForTesting
class VisitSelectorBlockChildrenState
extends VisitChildrenState {
VisitSelectorBlockChildrenState(CssSelectorListNode block) {
super(block);
}
@Override
VisitState getVisitState(CssSelectorNode node) {
return new VisitSelectorState(node);
}
}
@VisibleForTesting
class VisitKeyBlockState extends BaseVisitState {
private final CssKeyListNode block;
private final CssKeyframeRulesetNode ruleset;
private boolean visitedChildren = false;
VisitKeyBlockState(CssKeyframeRulesetNode ruleset,
CssKeyListNode block) {
this.ruleset = ruleset;
this.block = block;
}
@Override
public void doVisit() {
if (!visitedChildren) {
visitor.enterKeyBlock(block);
} else {
visitor.leaveKeyBlock(block);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren) {
stateStack.push(
new VisitKeyBlockChildrenState(block));
visitedChildren = true;
} else {
stateStack.transitionTo(
new VisitDeclarationBlockState(ruleset.getDeclarations()));
}
}
}
@VisibleForTesting
class VisitKeyBlockChildrenState
extends VisitChildrenState {
VisitKeyBlockChildrenState(CssKeyListNode block) {
super(block);
}
@Override
VisitState getVisitState(CssKeyNode node) {
return new VisitKeyState(node);
}
}
@VisibleForTesting
class VisitSelectorState extends BaseVisitState {
private final CssSelectorNode node;
private boolean visitedChildren = false;
VisitSelectorState(CssSelectorNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
visitor.enterSelector(node);
} else {
visitor.leaveSelector(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren) {
// We need to prepare the stack such that the refiners are visited first
// and then the combinator if there is one.
if (node.getCombinator() != null) {
stateStack.push(new VisitCombinatorState(node.getCombinator()));
}
stateStack.push(new VisitRefinerListState(node.getRefiners()));
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitKeyState extends BaseVisitState {
private final CssKeyNode node;
private boolean visitedChildren = false;
VisitKeyState(CssKeyNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
visitor.enterKey(node);
} else {
visitor.leaveKey(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren) {
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitRefinerListState extends VisitReplaceChildrenState {
VisitRefinerListState(CssRefinerListNode node) {
super(node);
}
}
@VisibleForTesting
class VisitRefinerNodeState extends BaseVisitState {
private final CssRefinerNode node;
private boolean visitedChildren = false;
VisitRefinerNodeState(CssRefinerNode node) {
this.node = node;
}
@Override
public void doVisit() {
// TODO(fbenz): Actually each of these nodes should have its own state
// here but this adds a bunch of similar code that is not really
// necessary. The problem is the design of the visit controller. I'm
// going to refactor it so that it doesn't make sense to add all the
// states.
if (!visitedChildren) {
if (node instanceof CssClassSelectorNode) {
visitor.enterClassSelector((CssClassSelectorNode) node);
} else if (node instanceof CssIdSelectorNode) {
visitor.enterIdSelector((CssIdSelectorNode) node);
} else if (node instanceof CssPseudoClassNode) {
visitor.enterPseudoClass((CssPseudoClassNode) node);
} else if (node instanceof CssPseudoElementNode) {
visitor.enterPseudoElement((CssPseudoElementNode) node);
} else if (node instanceof CssAttributeSelectorNode) {
visitor.enterAttributeSelector((CssAttributeSelectorNode) node);
}
} else {
if (node instanceof CssClassSelectorNode) {
visitor.leaveClassSelector((CssClassSelectorNode) node);
} else if (node instanceof CssIdSelectorNode) {
visitor.leaveIdSelector((CssIdSelectorNode) node);
} else if (node instanceof CssPseudoClassNode) {
visitor.leavePseudoClass((CssPseudoClassNode) node);
} else if (node instanceof CssPseudoElementNode) {
visitor.leavePseudoElement((CssPseudoElementNode) node);
} else if (node instanceof CssAttributeSelectorNode) {
visitor.leaveAttributeSelector((CssAttributeSelectorNode) node);
}
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren) {
if (node instanceof CssPseudoClassNode) {
CssPseudoClassNode pseudoClass = (CssPseudoClassNode) node;
if (pseudoClass.getNotSelector() != null) {
stateStack.push(new VisitSelectorState(
pseudoClass.getNotSelector()));
}
}
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitCombinatorState extends BaseVisitState {
private final CssCombinatorNode node;
private boolean visitedChildren = false;
VisitCombinatorState(CssCombinatorNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
visitor.enterCombinator(node);
} else {
visitor.leaveCombinator(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren) {
stateStack.push(new VisitSelectorState(node.getSelector()));
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitDeclarationBlockState extends BaseVisitState {
private final CssDeclarationBlockNode node;
private boolean startedVisitingChildren = false;
private boolean finishedVisitingChildren = false;
@VisibleForTesting
int currentIndex = -1;
private boolean doNotIncreaseIndex = false;
VisitDeclarationBlockState(CssDeclarationBlockNode block) {
this.node = block;
}
@Override
public void doVisit() {
if (!startedVisitingChildren) {
visitor.enterDeclarationBlock(node);
startedVisitingChildren = true;
} else if (finishedVisitingChildren) {
visitor.leaveDeclarationBlock(node);
}
}
@Override
public void removeCurrentChild() {
node.removeChildAt(currentIndex);
doNotIncreaseIndex = true;
}
@Override
public void replaceCurrentBlockChildWith(
List replacementNodes,
boolean visitTheReplacementNodes) {
node.replaceChildAt(currentIndex, replacementNodes);
if (visitTheReplacementNodes) {
doNotIncreaseIndex = true;
} else {
currentIndex += replacementNodes.size() - 1;
}
}
@Override
public void transitionToNextState() {
if (finishedVisitingChildren) {
stateStack.pop();
return;
}
// We finish visiting this state if we are on the last child and we are
// allowed to increment the index, which means we arrived here with no
// special case, or we have just removed the last element.
if ((currentIndex == node.numChildren() - 1 && !doNotIncreaseIndex)
|| currentIndex == node.numChildren()) {
finishedVisitingChildren = true;
return;
}
// Remain in this state to finish visiting all the children.
if (!doNotIncreaseIndex) {
currentIndex++;
} else {
doNotIncreaseIndex = false;
}
VisitState extends CssNode> state =
createVisitState(node.getChildAt(currentIndex), this);
if (state != null) {
stateStack.push(state);
}
}
}
@VisibleForTesting
class VisitDeclarationState extends BaseVisitState {
private final CssDeclarationNode node;
private boolean visitedChildren = false;
VisitDeclarationState(CssDeclarationNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
visitor.enterDeclaration(node);
} else {
visitor.leaveDeclaration(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren) {
stateStack.push(new VisitPropertyValueState(node.getPropertyValue()));
visitedChildren = true;
} else {
stateStack.pop();
}
}
@Override
public void removeCurrentChild() {
stateStack.pop();
stateStack.getTop().removeCurrentChild();
}
}
@VisibleForTesting
class VisitMixinState extends BaseVisitState {
private final CssMixinNode node;
private boolean visitedChildren = false;
VisitMixinState(CssMixinNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
visitor.enterMixin(node);
} else {
visitor.leaveMixin(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren) {
stateStack.push(
new VisitFunctionArgumentsNodeState(node.getArguments()));
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitPropertyValueState extends BaseVisitState {
private final CssPropertyValueNode node;
private boolean visitedChildren = false;
private boolean visitingChildren = false;
private int currentIndex = -1;
private boolean doNotIncreaseIndex = false;
VisitPropertyValueState(CssPropertyValueNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitingChildren) {
if (!visitedChildren) {
visitor.enterPropertyValue(node);
} else {
visitor.leavePropertyValue(node);
}
}
}
@Override
public void removeCurrentChild() {
node.removeChildAt(currentIndex);
doNotIncreaseIndex = true;
}
@Override
public void replaceCurrentBlockChildWith(
List replacementNodes, boolean visitTheReplacementNodes) {
// If we're replacing the current property with a composite value
// separated by spaces, we really just want to graft those nodes at the
// current child's location.
if (replacementNodes.size() == 1
&& replacementNodes.get(0) instanceof CssCompositeValueNode) {
CssCompositeValueNode compositeValueNode =
(CssCompositeValueNode) replacementNodes.get(0);
if (compositeValueNode.getOperator() == Operator.SPACE) {
replacementNodes = compositeValueNode.getValues();
}
}
node.replaceChildAt(currentIndex, replacementNodes);
if (visitTheReplacementNodes) {
doNotIncreaseIndex = true;
} else {
currentIndex += replacementNodes.size() - 1;
}
}
@Override
public void transitionToNextState() {
// We get out of this state if we are on the last child and we are allowed
// to increment the index, which means we arrived here with no special
// case, or we have just removed the last element.
if ((currentIndex == node.numChildren() - 1 && !doNotIncreaseIndex)
|| currentIndex == node.numChildren()) {
if (visitedChildren) {
stateStack.pop();
} else {
visitingChildren = false;
visitedChildren = true;
}
return;
}
// Remain in this state to finish visiting all the children.
visitingChildren = true;
if (!doNotIncreaseIndex) {
currentIndex++;
} else {
doNotIncreaseIndex = false;
}
VisitState extends CssNode> state =
createVisitState(node.getChildAt(currentIndex), this);
if (state != null) {
stateStack.push(state);
}
}
@Override
public VisitState extends CssNode> createFallbackState(CssValueNode child) {
return new VisitValueNodeState(child);
}
}
@VisibleForTesting
class VisitValueNodeState extends BaseVisitState {
private final CssValueNode node;
VisitValueNodeState(CssValueNode node) {
this.node = node;
}
@Override
public void doVisit() {
visitor.enterValueNode(node);
visitor.leaveValueNode(node);
}
@Override
public void transitionToNextState() {
stateStack.pop();
}
}
@VisibleForTesting
class VisitCompositeValueState extends BaseVisitState {
private final CssCompositeValueNode node;
private final List children;
private int currentIndex = -1;
private boolean doNotIncreaseIndex = false;
private boolean visitChildren = true;
private boolean intervalueStateIsNext = false;
VisitCompositeValueState(CssCompositeValueNode node) {
this.node = node;
this.children = node.getValues();
}
@Override
public void transitionToNextState() {
if (currentIndex == children.size() - 1) {
stateStack.pop();
return;
}
if (visitChildren == false) {
currentIndex = children.size() - 1;
return;
}
// Remain in this state to finish visiting all the children
if (intervalueStateIsNext) {
stateStack.push(new IntervalueState(node));
intervalueStateIsNext = false;
return;
}
if (!doNotIncreaseIndex) {
currentIndex++;
} else {
doNotIncreaseIndex = false;
}
stateStack.push(createVisitState(children.get(currentIndex), this));
intervalueStateIsNext = true;
return;
}
@Override
public void removeCurrentChild() {
children.remove(currentIndex);
intervalueStateIsNext = false;
doNotIncreaseIndex = true;
if (currentIndex == children.size()) {
stateStack.pop();
}
}
@Override
public VisitState createFallbackState(CssValueNode child) {
return new VisitValueNodeState(child);
}
/** {@inheritDoc} */
@Override
public void doVisit() {
if (currentIndex < 0) {
visitChildren = visitor.enterCompositeValueNode(node);
} else if (currentIndex == children.size() - 1) {
visitor.leaveCompositeValueNode(node);
}
}
@Override
public void replaceCurrentBlockChildWith(
List replacementNodes,
boolean visitTheReplacementNodes) {
children.remove(currentIndex);
// If we're replacing the current property with a composite value
// separated by the same operator, we really just want to graft those
// nodes at the current child's location.
if (replacementNodes.size() == 1
&& replacementNodes.get(0) instanceof CssCompositeValueNode) {
CssCompositeValueNode compositeValueNode =
(CssCompositeValueNode) replacementNodes.get(0);
if (compositeValueNode.getOperator() == node.getOperator()) {
replacementNodes = compositeValueNode.getValues();
}
}
children.addAll(currentIndex, replacementNodes);
if (!visitTheReplacementNodes) {
currentIndex += replacementNodes.size() - 1;
} else {
doNotIncreaseIndex = true;
}
}
}
class IntervalueState extends BaseVisitState {
private final CssCompositeValueNode parent;
IntervalueState(CssCompositeValueNode parent) {
this.parent = parent;
}
@Override
public void doVisit() {
visitor.enterCompositeValueNodeOperator(parent);
visitor.leaveCompositeValueNodeOperator(parent);
}
@Override
public void transitionToNextState() {
stateStack.pop();
}
}
@VisibleForTesting
class VisitFunctionNodeState extends BaseVisitState {
private final CssFunctionNode node;
private boolean visitedChildren = false;
VisitFunctionNodeState(CssFunctionNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
visitor.enterFunctionNode(node);
} else {
visitor.leaveFunctionNode(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren) {
stateStack.push(
new VisitFunctionArgumentsNodeState(node.getArguments()));
visitedChildren = true;
} else {
stateStack.pop();
}
}
@Override
public void removeCurrentChild() {
node.setArguments(new CssFunctionArgumentsNode());
}
@Override
@SuppressWarnings("unchecked")
public void removeCurrentNodeCalled() {
// If the function is a singleton, remove the nearest declaration that
// contains it.
popToNonDegenerateState();
stateStack.getTop().removeCurrentChild();
}
/**
* Exit states until we reach the nearest ancestor that will not
* be made degenerate by the removal of its current child.
*
* E.g., if this node's parent's role is to represent a collection of
* children, and this node has no siblings, then we want to remove
* the parent, and the transitive closure. So for example if we have
* div {
* background: url('http://google.com/logo')
* }
* and we remove the url function node, then we should not leave
* div {
* background:
* }
* but rather should remove background as well.
*/
private void popToNonDegenerateState() {
for (CssNode child = node; true; child = child.getParent()) {
// will removing the child leave the tree in a bad state?
boolean otherSiblingsExist;
if (child instanceof CssDeclarationNode) {
// it's just too hard, so stop removing ancestors if we
// get this high.
otherSiblingsExist = true;
} else if (child instanceof CssNodesListNode) {
otherSiblingsExist = ((CssNodesListNode) child).numChildren() > 1;
} else if (child instanceof CssCompositeValueNode) {
otherSiblingsExist =
((CssCompositeValueNode) child).getValues().size() > 1;
} else if (child instanceof CssDeclarationNode) {
// there's always just one CssPropertyValueNode
otherSiblingsExist = false;
} else if (child instanceof CssDeclarationNode) {
otherSiblingsExist = false;
} else if (child instanceof CssFunctionNode) {
otherSiblingsExist = false;
} else {
break;
}
if (otherSiblingsExist) {
break;
}
// TODO(user): refactor the preceding giant conditional branch to
// use dynamic dispatch. Maybe each node state should just have a
// locally-sane predicate to verify that its own properties are in
// good shape. Then we could just remove and pop our way up to
// sanity.
// TODO(user): verify that the stateStack.getTop() corresponds to
// node. I think the only VisitState implementation whose ctor does
// not demand a corresponding CssNode is the IntervalueState, so we
// can add a method to get the node and then bail on this loop if
// the result is either null or inconsistent with the current child.
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitFunctionArgumentsNodeState
extends VisitReplaceChildrenState {
VisitFunctionArgumentsNodeState(CssFunctionArgumentsNode node) {
super(node);
}
@Override
public void replaceCurrentBlockChildWith(
List replacementNodes, boolean visitTheReplacementNodes) {
// If we're replacing the current property with a composite value
// separated by the space, we really just want to graft those nodes at
// the current child's location.
if (replacementNodes.size() == 1
&& replacementNodes.get(0) instanceof CssCompositeValueNode) {
CssCompositeValueNode compositeValueNode =
(CssCompositeValueNode) replacementNodes.get(0);
if (compositeValueNode.getOperator() == Operator.SPACE) {
replacementNodes = compositeValueNode.getValues();
}
}
super.replaceCurrentBlockChildWith(
replacementNodes, visitTheReplacementNodes);
}
@Override
public VisitState createFallbackState(CssValueNode child) {
return new VisitFunctionArgumentNodeState(child);
}
}
@VisibleForTesting
class VisitFunctionArgumentNodeState extends BaseVisitState {
private final CssValueNode node;
VisitFunctionArgumentNodeState(CssValueNode node) {
this.node = node;
}
@Override
public void doVisit() {
visitor.enterArgumentNode(node);
visitor.leaveArgumentNode(node);
}
@Override
public void transitionToNextState() {
stateStack.pop();
}
}
@VisibleForTesting
class VisitComponentState extends VisitChildrenOptionalState {
private final CssComponentNode node;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
VisitComponentState(CssComponentNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
shouldVisitChildren = visitor.enterComponent(node);
} else {
visitor.leaveComponent(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren && shouldVisitChildren) {
stateStack.push(new VisitComponentChildrenState(node.getBlock()));
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitComponentChildrenState extends VisitReplaceChildrenState {
VisitComponentChildrenState(CssBlockNode block) {
super(block);
}
}
@VisibleForTesting
class VisitForLoopRuleState extends BaseVisitState {
private final CssForLoopRuleNode node;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
VisitForLoopRuleState(CssForLoopRuleNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
shouldVisitChildren = visitor.enterForLoop(node);
} else {
visitor.leaveForLoop(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren && shouldVisitChildren) {
stateStack.push(new VisitBlockChildrenState(node.getBlock()));
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitUnknownAtRuleState extends VisitChildrenOptionalState {
private final CssUnknownAtRuleNode node;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
private int currentIndex = -1;
VisitUnknownAtRuleState(CssUnknownAtRuleNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren && currentIndex == -1) {
shouldVisitChildren = visitor.enterUnknownAtRule(node);
} else if (visitedChildren) {
visitor.leaveUnknownAtRule(node);
}
}
@Override
public void transitionToNextState() {
if (visitedChildren || !shouldVisitChildren) {
stateStack.pop();
return;
}
++currentIndex;
final int parametersCount = node.getParameters().size();
if (currentIndex < parametersCount) {
if (currentIndex < parametersCount - 1) {
stateStack.push(new VisitMediaTypeListDelimiterState(node));
}
stateStack.push(getVisitState(node.getParameters().get(currentIndex)));
} else {
if (node.getType().hasBlock()) {
stateStack.push(new VisitUnknownAtRuleBlockState(node.getBlock()));
}
visitedChildren = true;
}
}
/**
* Returns a visit state for a given child node.
*
* @param node child node for which to create visit state
* @return new visit state
*/
public VisitState extends CssNode> getVisitState(CssValueNode node) {
if (node instanceof CssCompositeValueNode) {
return new VisitCompositeValueState((CssCompositeValueNode) node);
} else {
return new VisitValueNodeState(node);
}
}
}
@VisibleForTesting
class VisitUnknownAtRuleBlockState extends VisitChildrenOptionalState {
private final CssAbstractBlockNode body;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
VisitUnknownAtRuleBlockState(CssAbstractBlockNode body) {
this.body = body;
}
@Override
public void doVisit() {
if (!visitedChildren) {
if (body instanceof CssBlockNode) {
shouldVisitChildren = visitor.enterBlock((CssBlockNode) body);
}
} else {
if (body instanceof CssBlockNode) {
visitor.leaveBlock((CssBlockNode) body);
}
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren && shouldVisitChildren) {
if (body instanceof CssBlockNode) {
stateStack.push(new VisitBlockChildrenState((CssBlockNode) body));
} else if (body instanceof CssDeclarationBlockNode) {
stateStack.push(
new VisitDeclarationBlockState((CssDeclarationBlockNode) body));
}
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitWebkitKeyframesState extends VisitChildrenOptionalState {
private final CssKeyframesNode node;
private boolean visitedChildren = false;
private boolean shouldVisitChildren = true;
VisitWebkitKeyframesState(CssKeyframesNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
shouldVisitChildren = visitor.enterKeyframesRule(node);
} else {
visitor.leaveKeyframesRule(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren && shouldVisitChildren) {
if (node.getType().hasBlock()) {
stateStack.push(new VisitUnknownAtRuleBlockState(node.getBlock()));
}
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
@VisibleForTesting
class VisitMixinDefinitionState extends BaseVisitState {
private final CssMixinDefinitionNode node;
private boolean visitedChildren = false;
VisitMixinDefinitionState(CssMixinDefinitionNode node) {
this.node = node;
}
@Override
public void doVisit() {
if (!visitedChildren) {
visitor.enterMixinDefinition(node);
} else {
visitor.leaveMixinDefinition(node);
}
}
@Override
public void transitionToNextState() {
if (!visitedChildren) {
stateStack.push(new VisitDeclarationBlockState(node.getBlock()));
visitedChildren = true;
} else {
stateStack.pop();
}
}
}
// TODO(oana): Maybe add a generic utility class for Stack than can be used in
// CssTreeBuilder too.
@VisibleForTesting
static class StateStack {
Deque> stack = Queues.newArrayDeque();
VisitState extends CssNode> getTop() {
return stack.peek();
}
void push(VisitState extends CssNode> state) {
Preconditions.checkNotNull(state);
stack.addFirst(state);
}
void pop() {
stack.removeFirst();
}
void transitionTo(VisitState extends CssNode> state) {
pop();
push(state);
}
int size() {
return stack.size();
}
boolean isEmpty() {
return stack.isEmpty();
}
}
public DefaultVisitController(CssNode subtree, boolean allowMutating) {
Preconditions.checkNotNull(subtree);
this.subtree = subtree;
this.allowMutating = allowMutating;
}
public DefaultVisitController(CssTree tree, boolean allowMutating) {
this(tree.getRoot(), allowMutating);
}
public StateStack getStateStack() {
return stateStack;
}
@Override
public void removeCurrentNode() {
Preconditions.checkState(allowMutating);
stateStack.getTop().removeCurrentNodeCalled();
}
@Override
public void replaceCurrentBlockChildWith(
List replacementNodes,
boolean visitTheReplacementNodes) {
Preconditions.checkState(allowMutating);
@SuppressWarnings("unchecked")
VisitState stackTop = (VisitState) stateStack.getTop();
stackTop.replaceCurrentBlockChildWithCalled(
replacementNodes, visitTheReplacementNodes);
}
@Override
public void startVisit(CssTreeVisitor treeVisitor) {
Preconditions.checkNotNull(treeVisitor);
this.visitor = treeVisitor;
stateStack.push(createVisitStateInternal(subtree));
while (!stateStack.isEmpty()) {
try {
stateStack.getTop().doVisit();
stateStack.getTop().transitionToNextState();
} catch (StopVisitRequestedException e) {
// We stop visiting.
// assert stopVisitCalled;
break;
}
}
}
@Override
public void stopVisit() {
stopVisitCalled = true;
stateStack.getTop().stopVisitCalled();
throw new StopVisitRequestedException();
}
/**
* Factory method to create visit state for a child node. Class of the visit
* state depends on the child node class.
*
* @param child node type
* @param child node for which the visit state is created
* @param fallbackStateSource object which will create the fallback visit
* state if the factory cannot create the state for a given child node
* @return created visit state or {@code null} if state cannot be created by
* the factory or the fallback factory
*/
private VisitState extends CssNode> createVisitState(
T child, BaseVisitState fallbackStateSource) {
VisitState extends CssNode> state = createVisitStateInternal(child);
return (state == null) ?
fallbackStateSource.createFallbackState(child) : state;
}
private VisitState extends CssNode> createVisitStateInternal(CssNode child) {
// VisitProvideState
if (child instanceof CssProvideNode) {
return new VisitProvideState((CssProvideNode) child);
}
// VisitRequireState
if (child instanceof CssRequireNode) {
return new VisitRequireState((CssRequireNode) child);
}
// VisitUnknownAtRuleBlockState
if (child instanceof CssMediaRuleNode) {
return new VisitMediaRuleState((CssMediaRuleNode) child);
}
// VisitUnknownAtRuleBlockState
if (child instanceof CssPageRuleNode) {
return new VisitPageRuleState((CssPageRuleNode) child);
}
// VisitUnknownAtRuleBlockState
if (child instanceof CssPageSelectorNode) {
return new VisitPageSelectorState((CssPageSelectorNode) child);
}
// VisitUnknownAtRuleBlockState
if (child instanceof CssFontFaceNode) {
return new VisitFontFaceState((CssFontFaceNode) child);
}
// VisitUnknownAtRuleBlockState
if (child instanceof CssImportRuleNode) {
return new VisitImportRuleState((CssImportRuleNode) child);
}
if (child instanceof CssComponentNode) {
return new VisitComponentState((CssComponentNode) child);
}
// VisitRefinerListState
if (child instanceof CssRefinerNode) {
return new VisitRefinerNodeState((CssRefinerNode) child);
}
// VisitDeclarationBlockState
if (child instanceof CssDeclarationNode) {
return new VisitDeclarationState((CssDeclarationNode) child);
}
// VisitDeclarationBlockState
if (child instanceof CssMixinNode) {
return new VisitMixinState((CssMixinNode) child);
}
if (child instanceof CssForLoopRuleNode) {
return new VisitForLoopRuleState((CssForLoopRuleNode) child);
}
// VisitBlockChildrenState
// VisitComponentChildrenState, VisitUnknownAtRuleChildrenState
if (child instanceof CssUnknownAtRuleNode) {
return new VisitUnknownAtRuleState((CssUnknownAtRuleNode) child);
}
// VisitUnknownAtRuleBlockState
if (child instanceof CssKeyframesNode) {
return new VisitWebkitKeyframesState((CssKeyframesNode) child);
}
// VisitKeyBlockState
if (child instanceof CssKeyframeRulesetNode) {
return new VisitKeyframeRulesetState((CssKeyframeRulesetNode) child);
}
// VisitComponentChildrenState, VisitUnknownAtRuleChildrenState
// VisitBlockChildrenState, VisitConditionalRuleChildrenState
if (child instanceof CssConditionalBlockNode) {
return new VisitConditionalBlockState((CssConditionalBlockNode) child);
}
if (child instanceof CssRulesetNode) {
return new VisitRulesetState((CssRulesetNode) child);
}
if (child instanceof CssDefinitionNode) {
return new VisitDefinitionState((CssDefinitionNode) child);
}
// VisitDefinitionParametersState, VisitPropertyValueState, VisitFunctionArgumentsNodeState
if (child instanceof CssFunctionNode) {
return new VisitFunctionNodeState((CssFunctionNode) child);
}
if (child instanceof CssMixinDefinitionNode) {
return new VisitMixinDefinitionState((CssMixinDefinitionNode) child);
}
if (child instanceof CssCompositeValueNode) {
return new VisitCompositeValueState((CssCompositeValueNode) child);
}
if (child instanceof CssPropertyValueNode) {
return new VisitPropertyValueState((CssPropertyValueNode) child);
}
if (child instanceof CssRootNode) {
return new RootVisitBeforeChildrenState((CssRootNode) child);
}
return null;
}
}