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

com.google.apphosting.utils.glob.GlobIntersector Maven / Gradle / Ivy

There is a newer version: 2.0.27
Show newest version
/*
 * Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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.apphosting.utils.glob;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * Converts a set of unordered and overlapping {@link Glob} objects
 * into an ordered list of {@link Glob} objects where each contains
 * all of the properties from all of the matching input {@link Glob}
 * objects.
 *
 * 

For example, if two input globs are specified: *

    *
  • /admin/* (security=admin) *
  • /*.png (type=static) *
      * The output will be: *
        *
      1. /admin/*.png (security=admin, type=static) *
      2. /admin/* (security=admin) *
      3. /*.png (type=static) *
      * *

      This class is not responsible for resolving properties. * Instead, it just tracks the input {@link Glob} objects that * resulted in each output {@link Glob}. A custom {@link * ConflictResolver} can be provided when looking up property values * to resolve conflicts between properties. * */ public class GlobIntersector { private final Collection inputGlobs; public GlobIntersector() { inputGlobs = new ArrayList(); } public void addGlobs(Glob... globs) { for (Glob glob : globs) { inputGlobs.add(glob); } } public void addGlob(Glob glob) { inputGlobs.add(glob); } /** * Return an ordered list of {@link Glob} objects that matches the * same set of strings as the input globs, but in the appropriate * order and with the appropriate blending of properties from the * input globs to output globs. */ public List getIntersection() { // Use a LinkedHashSet so that the resulting order will be stable // across JDK releases. HashSet results = new LinkedHashSet(); doIntersect(inputGlobs, results); List resultList = new ArrayList(results); reorderAndBlendChildren(resultList); removeExactDuplicates(resultList); return resultList; } private void removeExactDuplicates(Collection results) { Set newResults = new HashSet(); Iterator i = results.iterator(); while (i.hasNext()) { Glob glob = i.next(); String pattern = glob.getPattern(); if (newResults.contains(pattern)) { i.remove(); } else { newResults.add(pattern); } } } private void reorderAndBlendChildren(List globs) { int size = globs.size(); for (int i = 0; i < size; i++) { for (int j = i + 1; j < size; j++) { // We're interested in a partial ordering of globs here. The // only case we care about is if a glob (j) comes after a // glob (i) that matches all of the same strings that it does. // In this case we need to flip them around. if (globs.get(i).matchesAll(globs.get(j))) { Glob tmp = globs.get(i); globs.set(i, globs.get(j)); globs.set(j, tmp); } // If a glob (i) comes before a glob (j) that matches all of // the same strings that it does, then we need to blend its // children up from j to i. This ensures that a pattern like // a*b will include all of the properties of the a* and b* // patterns that come after it. if (globs.get(j).matchesAll(globs.get(i))) { if (globs.get(i) instanceof LeafGlob) { globs.set(i, GlobFactory.convertToBranch(globs.get(i))); } ((BranchGlob) globs.get(i)).addChild(globs.get(j)); } } } } private void doIntersect(Collection inputGlobs, HashSet outputGlobs) { if (inputGlobs.size() <= 1) { outputGlobs.addAll(inputGlobs); } else if (inputGlobs.size() == 2) { Iterator i = inputGlobs.iterator(); doIntersectTwo(i.next(), i.next(), outputGlobs); } else { Set globs = new LinkedHashSet(); Iterator i = inputGlobs.iterator(); globs.add(i.next()); // It's too hard to do all of these at one time, so we divide // and conquer and merge the supplied patterns in one at a time. while (i.hasNext()) { Glob inputGlob = i.next(); HashSet newGlobs = new LinkedHashSet(); for (Glob glob : globs) { doIntersectTwo(inputGlob, glob, newGlobs); } globs = newGlobs; } outputGlobs.addAll(globs); } } private void doIntersectTwo(Glob inputGlob1, Glob inputGlob2, HashSet outputGlobs) { String pattern1 = inputGlob1.getPattern(); String pattern2 = inputGlob2.getPattern(); // To simplify the work we do below, we first check to see if the // supplied globs share a common prefix excluding wildcards. // // For example: // ab*d becomes: a | b*d // a*cd a | *cd String prefix = extractPrefix(pattern1, pattern2); if (prefix != null) { int prefixLength = prefix.length(); // The prefix is guaranteed to exclude wildcards, so if they // don't match then we're done here. if (!prefix.equals(pattern2.substring(0, prefixLength))) { outputGlobs.add(inputGlob1); outputGlobs.add(inputGlob2); } else { // Strip off the prefix and recurse on the non-prefixed version. HashSet newGlobs = new LinkedHashSet(); doIntersectTwo( GlobFactory.createChildGlob(pattern1.substring(prefixLength), inputGlob1), GlobFactory.createChildGlob(pattern2.substring(prefixLength), inputGlob2), newGlobs); // Now put the prefix back onto all of the returned globs. for (Glob newGlob : newGlobs) { outputGlobs.add(GlobFactory.createChildGlob(prefix + newGlob.getPattern(), newGlob)); } } return; } // Now we do the same thing we did for prefixes with suffixes. // // For example: // b*d becomes: b*d | d // *cd *cd | d String suffix = extractSuffix(pattern1, pattern2); if (suffix != null) { int suffixLength = suffix.length(); // The suffix is guaranteed to exclude wildcards, so if they // don't match then we're done here. if (!suffix.equals(pattern2.substring(pattern2.length() - suffixLength))) { outputGlobs.add(inputGlob1); outputGlobs.add(inputGlob2); } else { // Strip off the suffix and recurse on the non-suffixed version. HashSet newGlobs = new LinkedHashSet(); doIntersectTwo(GlobFactory.createChildGlob( pattern1.substring(0, pattern1.length() - suffixLength), inputGlob1), GlobFactory.createChildGlob( pattern2.substring(0, pattern2.length() - suffixLength), inputGlob2), newGlobs); // Now put the suffix back onto all of the returned globs. for (Glob newGlob : newGlobs) { outputGlobs.add(GlobFactory.createChildGlob(newGlob.getPattern() + suffix, newGlob)); } } return; } // For example: // abc*xyz adds: abc*ghi*xyz // *ghi* if (pattern1.length() > 1 && pattern1.startsWith("*") && pattern1.endsWith("*")) { for (int star = pattern2.indexOf("*"); star != -1; star = pattern2.indexOf("*", star + 1)) { outputGlobs.add(GlobFactory.createChildGlob( pattern2.substring(0, star) + pattern1 + pattern2.substring(star + 1), inputGlob1, inputGlob2)); } } // This is just the opposite of what we just did because we can't // be sure about the order. // // For example: // *ghi* adds: abc*ghi*xyz // abc*xyz if (pattern2.length() > 1 && pattern2.startsWith("*") && pattern2.endsWith("*")) { for (int star = pattern1.indexOf("*"); star != -1; star = pattern1.indexOf("*", star + 1)) { outputGlobs.add(GlobFactory.createChildGlob( pattern1.substring(0, star) + pattern2 + pattern1.substring(star + 1), inputGlob1, inputGlob2)); } } // For example: // abc* adds: abc*xyz // *xyz if (pattern1.length() > 1 && pattern2.length() > 1 && pattern1.endsWith("*") && pattern2.startsWith("*")) { String trimmedPattern1 = pattern1.substring(0, pattern1.length() - 1); outputGlobs.add(GlobFactory.createChildGlob(trimmedPattern1 + pattern2, inputGlob1, inputGlob2)); // For example: // abc* adds: abc // *c if (inputGlob2.matchesAll(trimmedPattern1)) { outputGlobs.add(GlobFactory.createChildGlob(trimmedPattern1, inputGlob1, inputGlob2)); } } // This is just the opposite of what we just did because we can't // be sure about the order. // // For example: // *xyz // abc* adds: abc*xyz if (pattern1.length() > 1 && pattern2.length() > 1 && pattern1.startsWith("*") && pattern2.endsWith("*")) { String trimmedPattern2 = pattern2.substring(0, pattern2.length() - 1); outputGlobs.add(GlobFactory.createChildGlob(trimmedPattern2 + pattern1, inputGlob1, inputGlob2)); // For example: // *c adds: abc // abc* if (inputGlob1.matchesAll(trimmedPattern2)) { outputGlobs.add(GlobFactory.createChildGlob(trimmedPattern2, inputGlob1, inputGlob2)); } } // Now include the two original patterns. outputGlobs.add(inputGlob1); outputGlobs.add(inputGlob2); } private String extractPrefix(String pattern1, String pattern2) { int firstStar1 = pattern1.indexOf("*"); int firstStar2 = pattern2.indexOf("*"); if (firstStar1 != -1 || firstStar2 != -1) { if (firstStar1 == -1) { firstStar1 = pattern1.length(); } if (firstStar2 == -1) { firstStar2 = pattern2.length(); } int minStar = Math.min(firstStar1, firstStar2); if (minStar > 0) { return pattern1.substring(0, minStar); } } return null; } private String extractSuffix(String pattern1, String pattern2) { int lastStar1 = pattern1.lastIndexOf("*"); int lastStar2 = pattern2.lastIndexOf("*"); if (lastStar1 != -1 || lastStar2 != -1) { if (lastStar1 == -1) { lastStar1 = 0; } if (lastStar2 == -1) { lastStar2 = 0; } int fromEnd = Math.min(pattern1.length() - lastStar1 - 1, pattern2.length() - lastStar2 - 1); if (fromEnd > 0) { return pattern1.substring(pattern1.length() - fromEnd); } } return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy