/* * 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.thirdparty.common.css.compiler.ast.CssCompilerPass; import com.google.gwt.thirdparty.common.css.compiler.ast.CssCompositeValueNode; import com.google.gwt.thirdparty.common.css.compiler.ast.CssDeclarationNode; import com.google.gwt.thirdparty.common.css.compiler.ast.CssFunctionNode; import com.google.gwt.thirdparty.common.css.compiler.ast.CssNode; import com.google.gwt.thirdparty.common.css.compiler.ast.CssNumericNode; import com.google.gwt.thirdparty.common.css.compiler.ast.CssPropertyNode; import com.google.gwt.thirdparty.common.css.compiler.ast.CssPropertyValueNode; import com.google.gwt.thirdparty.common.css.compiler.ast.CssTreeVisitor; import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode; import com.google.gwt.thirdparty.common.css.compiler.ast.DefaultTreeVisitor; import com.google.gwt.thirdparty.common.css.compiler.ast.MutatingVisitController; import com.google.gwt.thirdparty.common.css.compiler.passes.BiDiFlipper; import com.google.gwt.thirdparty.guava.common.base.Objects; import java.util.List; /** * Compiler pass that BiDi flips all the flippable nodes and records if nodes have been flipped. */ public class RecordingBidiFlipper extends DefaultTreeVisitor implements CssCompilerPass { /** * This {@link MutatingVisitController} will record if an effective mutation is done. */ private static class RecordingMutatingVisitController implements MutatingVisitController { private MutatingVisitController delegate; private CssDeclarationNode visitingDeclarationNode; private boolean hasMutation; private RecordingMutatingVisitController(MutatingVisitController delegate) { this.delegate = delegate; } @Override public void removeCurrentNode() { delegate.removeCurrentNode(); } @Override public <T extends CssNode> void replaceCurrentBlockChildWith(List<T> replacementNodes, boolean visitTheReplacementNodes) { // In our case, the list of replacement node should contain only one CssDeclarationNode if (!hasMutation && visitingDeclarationNode != null && replacementNodes.size() == 1 && replacementNodes.get(0) instanceof CssDeclarationNode) { CssDeclarationNode newDeclarationNode = (CssDeclarationNode) replacementNodes.get(0); hasMutation |= isNotEqual(visitingDeclarationNode, newDeclarationNode); } delegate.replaceCurrentBlockChildWith(replacementNodes, visitTheReplacementNodes); } public void setVisitingDeclarationNode(CssDeclarationNode visitingDeclarationNode) { this.visitingDeclarationNode = visitingDeclarationNode; } @Override public void startVisit(CssTreeVisitor visitor) { delegate.startVisit(visitor); } @Override public void stopVisit() { delegate.stopVisit(); } private boolean compositeValueEqual(CssCompositeValueNode first, CssCompositeValueNode second) { return valueNodeListEqual(first.getValues(), second.getValues()); } private boolean functionNodeEqual(CssFunctionNode first, CssFunctionNode second) { return valueNodeListEqual(first.getArguments().getChildren(), second.getArguments() .getChildren()); } private boolean isNotEqual(CssDeclarationNode first, CssDeclarationNode second) { return !propertyNameEqual(first.getPropertyName(), second.getPropertyName()) || !propertyValuesEqual(first.getPropertyValue(), second.getPropertyValue()); } private boolean numericNodeEqual(CssNumericNode first, CssNumericNode second) { return Objects.equal(first.getNumericPart(), second.getNumericPart()) && Objects.equal(first.getUnit(), second.getUnit()); } private boolean propertyNameEqual(CssPropertyNode first, CssPropertyNode second) { return Objects.equal(first.getPropertyName(), second.getPropertyName()); } private boolean propertyValuesEqual(CssPropertyValueNode first, CssPropertyValueNode second) { return valueNodeListEqual(first.getChildren(), second.getChildren()); } private boolean valueEqual(CssValueNode first, CssValueNode second) { if (first.getClass() != second.getClass()) { return false; } if (first instanceof CssCompositeValueNode) { return compositeValueEqual((CssCompositeValueNode) first, (CssCompositeValueNode) second); } else if (first instanceof CssFunctionNode) { return functionNodeEqual((CssFunctionNode) first, (CssFunctionNode) second); } else if (first instanceof CssNumericNode) { return numericNodeEqual((CssNumericNode) first, (CssNumericNode) second); } else { return Objects.equal(first.getValue(), second.getValue()); } } private boolean valueNodeListEqual(List<CssValueNode> firstValues, List<CssValueNode> secondValues) { if (firstValues.size() != secondValues.size()) { return false; } for (int i = 0; i < firstValues.size(); i++) { CssValueNode firstNode = firstValues.get(i); CssValueNode secondNode = secondValues.get(i); if (!valueEqual(firstNode, secondNode)) { return false; } } return true; } } private BiDiFlipper delegate; private RecordingMutatingVisitController mutatingVisitController; public RecordingBidiFlipper(MutatingVisitController visitController, boolean swapLtrRtlInUrl, boolean swapLeftRightInUrl, boolean shouldFlipConstantReferences) { this.mutatingVisitController = new RecordingMutatingVisitController(visitController); this.delegate = new BiDiFlipper(mutatingVisitController, swapLtrRtlInUrl, swapLeftRightInUrl, shouldFlipConstantReferences); } @Override public boolean enterDeclaration(CssDeclarationNode declaration) { mutatingVisitController.setVisitingDeclarationNode(declaration); return delegate.enterDeclaration(declaration); } /** * return true if at least one node was flipped, false otherwise. */ public boolean nodeFlipped() { return mutatingVisitController.hasMutation; } @Override public void runPass() { mutatingVisitController.startVisit(this); } }