| /* |
| * Copyright 2014 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.gwt.resources.gss; |
| |
| import com.google.gwt.resources.gss.ast.CssRuntimeConditionalRuleNode; |
| import com.google.gwt.thirdparty.common.css.compiler.ast.CssAtRuleNode.Type; |
| import com.google.gwt.thirdparty.common.css.compiler.ast.CssBooleanExpressionNode; |
| import com.google.gwt.thirdparty.common.css.compiler.ast.CssCompilerPass; |
| import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalBlockNode; |
| import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalRuleNode; |
| import com.google.gwt.thirdparty.common.css.compiler.ast.MutatingVisitController; |
| import com.google.gwt.thirdparty.common.css.compiler.passes.BooleanExpressionEvaluator; |
| import com.google.gwt.thirdparty.common.css.compiler.passes.EliminateConditionalNodes; |
| import com.google.gwt.thirdparty.guava.common.collect.Lists; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * A compiler pass that eliminates the conditional blocks for which the boolean |
| * expression does not evaluate to true. |
| * <p>This compiler pass does not deal with conditional nodes that need to be evaluated at runtime. |
| */ |
| public class ExtendedEliminateConditionalNodes extends EliminateConditionalNodes |
| implements CssCompilerPass { |
| |
| private final MutatingVisitController visitController; |
| private final Set<String> trueConditions; |
| private final Set<CssConditionalBlockNode> runtimeConditionalNodes; |
| private final Set<CssConditionalBlockNode> alreadyTreatedNode = |
| new HashSet<CssConditionalBlockNode>(); |
| |
| public ExtendedEliminateConditionalNodes(MutatingVisitController visitController, |
| Set<String> trueConditions, Set<CssConditionalBlockNode> runtimeConditionalNodes) { |
| super(visitController, trueConditions); |
| |
| this.visitController = visitController; |
| this.trueConditions = trueConditions; |
| this.runtimeConditionalNodes = runtimeConditionalNodes; |
| } |
| |
| @Override |
| public boolean enterConditionalBlock(CssConditionalBlockNode block) { |
| if (alreadyTreatedNode.contains(block)) { |
| // don't visit this block again but visit its children |
| return true; |
| } |
| |
| if (runtimeConditionalNodes.contains(block)) { |
| return enterRuntimeConditionalBlock(block); |
| } else { |
| // block without any runtime condition. |
| return super.enterConditionalBlock(block); |
| } |
| } |
| |
| private boolean enterRuntimeConditionalBlock(CssConditionalBlockNode block) { |
| boolean runtimeEvaluationNodeFound = false; |
| List<CssConditionalRuleNode> newChildren = |
| new ArrayList<CssConditionalRuleNode>(block.numChildren()); |
| |
| for (CssConditionalRuleNode currentConditional : block.childIterable()) { |
| if (currentConditional.getType() == Type.ELSE) { |
| newChildren.add(currentConditional); |
| break; |
| } |
| |
| if (currentConditional instanceof CssRuntimeConditionalRuleNode) { |
| runtimeEvaluationNodeFound = true; |
| newChildren.add(currentConditional); |
| continue; |
| } |
| |
| // The node can be evaluated at compile time |
| BooleanExpressionEvaluator evaluator = new BooleanExpressionEvaluator( |
| currentConditional.getCondition(), trueConditions); |
| |
| CssBooleanExpressionNode result = evaluator.evaluate(); |
| boolean isTrue = CssBooleanExpressionNode.Type.TRUE_CONSTANT.equals(result.getValue()); |
| |
| if (!isTrue) { |
| // any node evaluated to false can be removed |
| } else if (!runtimeEvaluationNodeFound) { |
| // node evaluated to true before the runtime condition, replace the conditional block by the |
| // children of this current conditional node. |
| visitController.replaceCurrentBlockChildWith(currentConditional.getBlock().getChildren(), |
| true); |
| return true; |
| } else { |
| // node evaluated to true before the runtime condition, transform this node to an else node |
| CssConditionalRuleNode newNode = new CssConditionalRuleNode(Type.ELSE, |
| currentConditional.getName(), null, currentConditional.getBlock()); |
| |
| newChildren.add(newNode); |
| break; |
| } |
| } |
| |
| CssConditionalBlockNode newNode = new CssConditionalBlockNode(); |
| for (CssConditionalRuleNode child : newChildren) { |
| newNode.addChildToBack(child); |
| } |
| |
| visitController.replaceCurrentBlockChildWith(Lists.newArrayList(newNode), true); |
| |
| // mark this node as already visited. |
| alreadyTreatedNode.add(newNode); |
| |
| return true; |
| } |
| |
| @Override |
| public void runPass() { |
| alreadyTreatedNode.clear(); |
| |
| visitController.startVisit(this); |
| } |
| } |