All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.squarespace.less.exec.ExtendMatcher Maven / Gradle / Ivy

There is a newer version: 2.0.5
Show newest version
/**
 * Copyright, 2015, Squarespace, 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.squarespace.less.exec;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.squarespace.less.core.HashPrefixTree.HPTMatch;
import com.squarespace.less.core.HashPrefixTree.HPTNode;
import com.squarespace.less.model.Selector;
import com.squarespace.less.model.SelectorPart;
import com.squarespace.less.model.Selectors;


/**
 * Matches selectors against the {@link ExtendIndex}. This also
 * cascades, ensuring that any selectors generated by the matcher
 * are themselves matched.
 *
 * Simple example:
 * 
 *   .a .b .c {
 *       content: 1;
 *   }
 *
 *   .x:extend(.b .c all) { }
 *   .d:extend(.c all) { }
 *   .replace:extend(.x) { }
 *   .e:extend(.d all) { }
 *   .f:extend(.a .b .e) { }
 *   .g:extend(.f) { }
 * 
* Result: *
 *   .a .b .c,
 *   .a .x,
 *   .a .replace,
 *   .a .b .d,
 *   .a .b .e,
 *   .f,
 *   .g {
 *     content: 1;
 *   }
 * 
*/ public class ExtendMatcher { /** * Collects all unique key ids seen during exact matching, ensuring we * can detect circular references. */ private final Set exactIndexIds = new HashSet<>(); /** * Collects all unique key ids seen during search/replace matching, * ensuring we can detect circular references. */ private final Set partialIndexIds = new HashSet<>(); /** * Scratch list of selectors we're currently working on. */ private final Deque selectorDeque = new ArrayDeque<>(); /** * Set to avoid generating duplicates which could cascade too long. * Example: * .e:extend(.e) {} * .e,.e:extend(.e) { } */ private final Set> dupeCheck = new HashSet<>(); /** * Extend the selector group against the given index. Collect the generated * selectors in the given list and return it. */ public List extend(ExtendIndex index, Selectors selectors, List collector) { // Match all of the selectors in the group, resetting the internal state for // each selector. dupeCheck.clear(); for (Selector selector : selectors.selectors()) { reset(); collector = extend(index, selector, collector); } return collector; } /** * Perform both an exact match and a search/replace on the given selector, and * then cascade to process all generated selectors. */ public List extend(ExtendIndex index, Selector selector, List collector) { exactMatch(index, selector); partialMatch(index, selector); return extendCascade(index, collector); } /** * Drain the deque of all selectors, matching each against the indexes. */ private List extendCascade(ExtendIndex index, List collector) { // Drain the queue until its empty. Once empty we're ensured that // all cascading selectors have been generated and captured. while (!selectorDeque.isEmpty()) { Selector selector = selectorDeque.pollFirst(); // Avoid generating duplicate selectors to reduce unnecessary queries. if (dupeCheck.contains(selector.parts())) { continue; } // Initialize the result collector and populate it. if (collector == null) { collector = new ArrayList<>(); } collector.add(selector); dupeCheck.add(selector.parts()); // Perform the matching process which will generate zero or more // new selectors and add them to the queue. exactMatch(index, selector); partialMatch(index, selector); } return collector; } /** * Find an exact match using the selector as query. */ private void exactMatch(ExtendIndex index, Selector selector) { HPTNode match = index.findExactMatch(selector); // Avoid processing a match more than once. if (match == null || exactIndexIds.contains(match.keyId())) { return; } for (Selector value : match.values()) { selectorDeque.add(value); } exactIndexIds.add(match.keyId()); } /** * Find a partial match using the selector as a query. This will search all subsequences * of the selector. */ private void partialMatch(ExtendIndex index, Selector selector) { List> partialMatches = index.findPartialMatch(selector, partialIndexIds); if (partialMatches != null) { for (HPTMatch match : partialMatches) { searchReplace(selector, match); } } } /** * Perform a search/replace on the given selector by doing a partial match * against the extend index. Returns the list of selectors generated by * the match, or null if none were matched. * * Based on the (start, end) range of each match found, we build a new * selector, replacing the match-covered range with the match values. * *
   * Selector: ["a", >, "b", >, "d", "e"]
   *    Match: start=1, end=3, values=[ ["x", +, "y", >, "z"] ]
   *   Result: ["a", >, "x", +, "y", >, "z", "d", "e"]
   * 
*/ private void searchReplace(Selector selector, HPTMatch match) { // Cache the key identifier for the match to ensure we don't // process it more than once. partialIndexIds.add(match.keyId()); List originalParts = selector.parts(); int size = selector.size(); int start = match.start(); int end = match.end(); for (Selector replacement : match.values()) { // Build a new selector with the search/replace patched in. Selector result = new Selector(); for (int i = 0; i < size; i++) { // When we reach the position in the selector where we need to // perform the search/replace, patch in the replacement parts. if (i == start) { for (SelectorPart part : replacement.parts()) { result.add(part); } continue; } // If we're outside the replacement range, just add the original // parts to the new selector. if (i < start || i >= end) { result.add(originalParts.get(i)); } } // We're done building the new selector, so add it to the queue. selectorDeque.add(result); } } /** * Resets the internal state of the matcher. */ private void reset() { exactIndexIds.clear(); partialIndexIds.clear(); selectorDeque.clear(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy