Package com.github.sommeri.less4j.core.compiler.stages

Source Code of com.github.sommeri.less4j.core.compiler.stages.ParentChainIterator

package com.github.sommeri.less4j.core.compiler.stages;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.github.sommeri.less4j.core.ast.ASTCssNode;
import com.github.sommeri.less4j.core.ast.ASTCssNodeType;
import com.github.sommeri.less4j.core.ast.Body;
import com.github.sommeri.less4j.core.ast.BodyOwner;
import com.github.sommeri.less4j.core.ast.GeneralBody;
import com.github.sommeri.less4j.core.ast.Media;
import com.github.sommeri.less4j.core.ast.StyleSheet;
import com.github.sommeri.less4j.core.problems.ProblemsHandler;

/**
* Bubbles media at-rules on top of stylesheet and merges their media queries. It
* assumes that all media already bubbled on top of rulesets.
*
*/
public class MediaBubblerAndMerger {

  private ASTManipulator astManipulator = new ASTManipulator();
  private ProblemsHandler problemsHandler;

  public MediaBubblerAndMerger(ProblemsHandler problemsHandler) {
    super();
    this.problemsHandler = problemsHandler;
  }

  public void bubbleAndMergeMedia(StyleSheet node) {
    bubbleUp(node);
    mergeTopLevelMedias(node);
  }

  private void mergeTopLevelMedias(StyleSheet node) {
    NestedMediaCollector nestedMediaCollector = new NestedMediaCollector(problemsHandler);

    List<? extends ASTCssNode> childs = new ArrayList<ASTCssNode>(node.getChilds());
    for (ASTCssNode kid : childs) {
      switch (kid.getType()) {
      case MEDIA: {
        List<Media> nestedMedia = nestedMediaCollector.collectMedia((Media) kid);
        astManipulator.addIntoBody(nestedMedia, kid);
        break;
      }
      default:
        //nothing is needed
      }
    }

  }

  private void bubbleUp(ASTCssNode node) {
    switch (node.getType()) {
    case MEDIA: {
      bubbleUp((Media) node);
      break;
    }
    case RULE_SET: {
      // media are supposed to be bubble over rulesets in previous step. There is no
      // reason to go deeper
      return ;
    }
    default: {
      List<? extends ASTCssNode> childs = new ArrayList<ASTCssNode>(node.getChilds());
      for (ASTCssNode kid : childs) {
        bubbleUp(kid);
      }
    }
    }

  }

  private void bubbleUp(Media media) {
    ParentChainIterator parentChainIterator = new ParentChainIterator(media);
    if (parentChainIterator.finished())
      return;

    astManipulator.removeFromBody(media);
    BodiesStorage bodiesStorage = new BodiesStorage();

    //move all kids of media into the empty clone. It is wasteful, they are going to be cloned but does not need to.
    Body oldBody = parentChainIterator.getParentAsBody();
    ASTCssNode currentNode = parentChainIterator.getCurrentNode();
    parentChainIterator.moveUpToNextBody();

    Body emptyClone = bodiesStorage.storeAndReplaceBySingleMemberClone(oldBody, null);
    astManipulator.moveMembersBetweenBodies(media.getBody(), emptyClone);

    while (!parentChainIterator.finished()) {
      //store current node and
      oldBody = parentChainIterator.getParentAsBody();
      currentNode = parentChainIterator.getCurrentNode();
      //move up
      parentChainIterator.moveUpToNextBody();

      bodiesStorage.storeAndReplaceBySingleMemberClone(oldBody, currentNode);
    }

    //clone whole parental chain
    currentNode = parentChainIterator.getCurrentNode();
    ASTCssNode currentNodeClone = currentNode.clone();

    //make it media child
    media.getBody().addMember(currentNodeClone);
    currentNodeClone.setParent(media.getBody());

    //restore bodies and add media
    bodiesStorage.restore();
    astManipulator.addIntoBody(media, currentNode);
  }

}

class BodiesStorage {
  private List<Body> originalBodies = new ArrayList<Body>();
  private List<ASTCssNode> keepChilds = new ArrayList<ASTCssNode>();
  private List<BodyOwner<Body>> originalBodiesParents = new ArrayList<BodyOwner<Body>>();

  private void store(Body body, BodyOwner<Body> parent) {
    originalBodies.add(body);
    originalBodiesParents.add(parent);
  }

  private void replaceBody(BodyOwner<Body> bodyOwner, Body body) {
    bodyOwner.getBody().setParent(null);
    bodyOwner.setBody(body);
    body.setParent((ASTCssNode) bodyOwner);
  }

  public Body storeAndReplaceBySingleMemberClone(Body body, ASTCssNode keepChild) {
    Body newBody = body.emptyClone();
    @SuppressWarnings("unchecked")
    BodyOwner<Body> bodyOwner = (BodyOwner<Body>) body.getParent();

    store(body, bodyOwner);
    replaceBody(bodyOwner, newBody);

    //add keep child node into faked body
    keepChilds.add(keepChild);
    moveToBody(newBody, keepChild);

    return newBody;
  }

  private void moveToBody(Body body, ASTCssNode child) {
    if (child != null) {
      // we only reparented the child, we did not removed it from the previous parent
      if (!body.getChilds().contains(child))
        body.addMember(child);
      child.setParent(body);
    }
  }

  public void restore() {
    Iterator<Body> bodiesIterator = originalBodies.iterator();
    Iterator<BodyOwner<Body>> parentsIterator = originalBodiesParents.iterator();
    Iterator<ASTCssNode> keepChildsIterator = keepChilds.iterator();

    while (bodiesIterator.hasNext()) {
      BodyOwner<Body> bodyOwner = parentsIterator.next();
      Body body = bodiesIterator.next();
      ASTCssNode keepChild = keepChildsIterator.next();

      bodyOwner.getBody().setParent(null);
      replaceBody(bodyOwner, body);
      moveToBody(body, keepChild);
    }

  }

}

class ParentChainIterator {

  private ASTCssNode currentNode;
  private ASTCssNode currentNodeParent;

  public ParentChainIterator(Media media) {
    currentNode = media;
    currentNodeParent = currentNode.getParent();
  }

  public ASTCssNode getCurrentNode() {
    return currentNode;
  }

  public Body getParentAsBody() {
    return (Body) currentNodeParent;
  }

  public void moveUpToNextBody() {
    moveOnParent();

    while (!finished() && !onNextBody()) {
      moveOnParent();
    }
  }

  private boolean onNextBody() {
    return currentNodeParent instanceof Body;
  }

  private void moveOnParent() {
    currentNode = currentNodeParent;
    currentNodeParent = currentNode.getParent();
  }

  public boolean finished() {
    return isStopParent(currentNodeParent);
  }

  private boolean isStopParent(ASTCssNode parent) {
    if (parent == null)
      return true;

    switch (parent.getType()) {
    case STYLE_SHEET: {
      return true;
    }
    case GENERAL_BODY: {
      GeneralBody body = (GeneralBody) parent;
      ASTCssNode bodyParent = body.getParent();
      return bodyParent == null || bodyParent.getType() == ASTCssNodeType.MEDIA;
    }
    default:
      //nothing is needed
    }

    return false;
  }

}
TOP

Related Classes of com.github.sommeri.less4j.core.compiler.stages.ParentChainIterator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.