org.prebake.core.GlobSet.java Source code

Java tutorial

Introduction

Here is the source code for org.prebake.core.GlobSet.java

Source

// Copyright 2010, Mike Samuel
//
// 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 org.prebake.core;

import org.prebake.js.JsonSerializable;
import org.prebake.js.JsonSink;

import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;

/**
 * A set of {@link Glob}s that can be applied to an input path to
 * figure out the set of globs that match it.
 *
 * @author Mike Samuel <mikesamuel@gmail.com>
 */
@ParametersAreNonnullByDefault
public abstract class GlobSet implements Iterable<Glob>, JsonSerializable {
    private final PrefixTree prefixTree = new PrefixTree();

    GlobSet() {
        /* no-op */ }

    /** A copy of the given glob set. */
    GlobSet(GlobSet gset) {
        copy(gset.prefixTree, prefixTree);
    }

    private PrefixTree lookup(Glob glob, boolean create) {
        List<String> parts = glob.parts();
        PrefixTree t = prefixTree;
        for (int i = 0, n = parts.size(); i < n; ++i) {
            String s = parts.get(i);
            if ("/".equals(s)) {
                continue;
            }
            if (s.charAt(0) == '*') {
                break;
            }
            if (i + 1 < n && parts.get(i).charAt(0) == '*') {
                break;
            }
            PrefixTree child = t.children.get(s);
            if (child == null) {
                if (create) {
                    child = new PrefixTree(s, t);
                } else {
                    break;
                }
            }
            t = child;
        }
        return t;
    }

    /**
     * Adds a glob to the set so subsequent calls to {@link #matching} will
     * include it for paths that match glob.
     */
    protected void add(Glob glob) {
        lookup(glob, true).add(glob);
    }

    /**
     * Removes the given glob so that subsequent calls to {@link #matching} will
     * not include it.
     * @return true iff the glob changes.  I.e., true iff glob was added and not
     *     subsequently removed prior to this call.
     */
    protected boolean remove(Glob glob) {
        return lookup(glob, false).remove(glob);
    }

    /**
     * Returns a collection of globs grouped by path prefix.
     * A path prefix is a normalized path to a directory or file that is a
     * (non-strict) ancestor of all paths that match the glob.
     */
    public Multimap<String, Glob> getGlobsGroupedByPrefix() {
        ImmutableMultimap.Builder<String, Glob> b = ImmutableMultimap.builder();
        groupByPrefixInto(prefixTree, b, new StringBuilder());
        return b.build();
    }

    private void groupByPrefixInto(PrefixTree t, ImmutableMultimap.Builder<String, Glob> out,
            StringBuilder prefix) {
        Collection<Glob> globs = t.globsByExtension.values();
        if (!globs.isEmpty()) {
            out.putAll(prefix.toString(), globs);
        }
        if (!t.children.isEmpty()) {
            int len = prefix.length();
            for (Map.Entry<String, PrefixTree> child : t.children.entrySet()) {
                if (len != 0) {
                    prefix.append('/');
                }
                groupByPrefixInto(child.getValue(), out, prefix.append(child.getKey()));
                prefix.setLength(len);
            }
        }
    }

    /**
     * All component globs that match the given path in no-particular order.
     */
    public Iterable<Glob> matching(Path path) {
        PrefixTree t = prefixTree;
        for (Path p : path) {
            PrefixTree child = t.children.get(p.toString());
            if (child == null) {
                break;
            }
            t = child;
        }
        String pathStr = normPath(path);
        String extension = extensionFor(pathStr);
        if ("".equals(extension)) {
            extension = null;
        }
        ImmutableList.Builder<Glob> b = ImmutableList.builder();
        for (; t != null; t = t.parent) {
            if (extension != null) {
                for (Glob g : t.globsByExtension.get(extension)) {
                    if (g.match(pathStr)) {
                        b.add(g);
                    }
                }
            }
            for (Glob g : t.globsByExtension.get("")) {
                if (g.match(pathStr)) {
                    b.add(g);
                }
            }
        }
        return b.build();
    }

    /** True iff any glob in the set matches the given normalized path. */
    public boolean matches(Path path) {
        PrefixTree t = prefixTree;
        for (Path p : path) {
            PrefixTree child = t.children.get(p.toString());
            if (child == null) {
                break;
            }
            t = child;
        }
        String pathStr = normPath(path);
        String extension = extensionFor(pathStr);
        if ("".equals(extension)) {
            extension = null;
        }
        for (; t != null; t = t.parent) {
            if (extension != null) {
                for (Glob g : t.globsByExtension.get(extension)) {
                    if (g.match(pathStr)) {
                        return true;
                    }
                }
            }
            for (Glob g : t.globsByExtension.get("")) {
                if (g.match(pathStr)) {
                    return true;
                }
            }
        }
        return false;
    }

    private static final Supplier<List<Glob>> GLOB_LIST_SUPPLIER = new Supplier<List<Glob>>() {
        public List<Glob> get() {
            return Lists.newArrayList();
        }
    };

    private static void copy(PrefixTree from, PrefixTree to) {
        for (Map.Entry<String, PrefixTree> e : from.children.entrySet()) {
            String s = e.getKey();
            PrefixTree child = to.children.get(s);
            if (child == null) {
                child = new PrefixTree(s, to);
            }
            copy(e.getValue(), child);
        }
        to.globsByExtension.putAll(from.globsByExtension);
    }

    /** A node in a prefix or suffix tree. */
    @ParametersAreNonnullByDefault
    private static final class PrefixTree {
        final Map<String, PrefixTree> children = Maps.newHashMap();
        final Multimap<String, Glob> globsByExtension = Multimaps
                .newListMultimap(Maps.<String, Collection<Glob>>newHashMap(), GLOB_LIST_SUPPLIER);
        final String prefix;
        final @Nullable PrefixTree parent;

        PrefixTree() {
            this.prefix = "";
            this.parent = null;
        }

        PrefixTree(String prefix, PrefixTree parent) {
            this.prefix = prefix;
            this.parent = parent;
            parent.children.put(prefix, this);
        }

        void add(Glob glob) {
            globsByExtension.put(extensionFor(glob), glob);
        }

        boolean remove(Glob glob) {
            boolean changed = globsByExtension.remove(extensionFor(glob), glob);
            for (PrefixTree t = this; t.parent != null; t = t.parent) {
                if (t.globsByExtension.isEmpty() && t.children.isEmpty()) {
                    t.parent.children.remove(t.prefix);
                } else {
                    break;
                }
            }
            return changed;
        }
    }

    private static String extensionFor(Glob glob) {
        List<String> parts = glob.parts();
        String lastPart = parts.get(parts.size() - 1);
        if (lastPart.charAt(0) == '*') {
            return "";
        }
        return extensionFor(lastPart);
    }

    private static String extensionFor(String lastPart) {
        int dot = lastPart.lastIndexOf('.');
        return dot < 0 ? "" : lastPart.substring(dot);
    }

    private static String normPath(Path p) {
        String sep = p.getFileSystem().getSeparator();
        String pathStr = p.toString();
        if (!"/".equals(sep)) {
            pathStr = pathStr.replace(sep, "/");
        }
        return pathStr;
    }

    public Iterator<Glob> iterator() {
        return snapshot().iterator();
    }

    protected ImmutableList<Glob> snapshot() {
        ImmutableList.Builder<Glob> items = ImmutableList.builder();
        iterateOnto(prefixTree, items);
        return items.build();
    }

    public void toJson(JsonSink sink) throws IOException {
        sink.write("[");
        Iterator<Glob> it = iterator();
        if (it.hasNext()) {
            it.next().toJson(sink);
            while (it.hasNext()) {
                sink.write(",");
                it.next().toJson(sink);
            }
        }
        sink.write("]");
    }

    private static void iterateOnto(PrefixTree t, ImmutableList.Builder<? super Glob> out) {
        out.addAll(t.globsByExtension.values());
        for (PrefixTree child : t.children.values()) {
            iterateOnto(child, out);
        }
    }
}