| /* |
| * Copyright 2014 The Kythe Authors. All rights reserved. |
| * |
| * 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.devtools.kythe.extractors.shared; |
| |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.devtools.kythe.proto.Storage.VName; |
| import com.google.gson.Gson; |
| import com.google.gson.GsonBuilder; |
| import com.google.gson.JsonDeserializationContext; |
| import com.google.gson.JsonDeserializer; |
| import com.google.gson.JsonElement; |
| import com.google.gson.reflect.TypeToken; |
| import java.io.IOException; |
| import java.lang.reflect.Type; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Stack; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Utility for configuring base {@link VName}s to use for particular file paths. Useful for |
| * populating the {@link VName}s for each required input in a {@link CompilationUnit}. |
| * |
| * <p>JSON format: |
| * |
| * <pre>[ |
| * { |
| * "pattern": "pathRegex", |
| * "vname": { |
| * "corpus": "corpusTemplate", // optional |
| * "root": "rootTemplate" // optional |
| * } |
| * }, ... |
| * ]</pre> |
| * |
| * The {@link VName} template values can contain markers such as @1@ or @2@ that will be replaced by |
| * the first or second regex groups in the pathRegex. |
| * |
| * <p>NOTE: regex syntax is RE2: https://code.google.com/p/re2/wiki/Syntax |
| */ |
| public class FileVNames { |
| private static final Gson GSON = |
| new GsonBuilder().registerTypeAdapter(Pattern.class, new PatternDeserializer()).create(); |
| private static final Type CONFIG_TYPE = new TypeToken<List<BaseFileVName>>() {}.getType(); |
| |
| private final List<BaseFileVName> baseVNames; |
| |
| private FileVNames(List<BaseFileVName> baseVNames) { |
| Preconditions.checkNotNull(baseVNames); |
| for (BaseFileVName b : baseVNames) { |
| Preconditions.checkNotNull(b.pattern, "pattern == null for base VName: %s", b.vname); |
| Preconditions.checkNotNull(b.vname, "vname template == null for pattern: %s", b.pattern); |
| } |
| this.baseVNames = baseVNames; |
| } |
| |
| /** |
| * Returns a {@link FileVNames} that yields a static corpus-populated {@link VName} for each |
| * {@link #lookupBaseVName(String)}. |
| */ |
| public static FileVNames staticCorpus(String corpus) { |
| return new FileVNames( |
| Arrays.asList( |
| new BaseFileVName(Pattern.compile(".*"), new VNameTemplate(corpus, null, null)))); |
| } |
| |
| public static FileVNames fromFile(String configFile) throws IOException { |
| return fromFile(Paths.get(configFile)); |
| } |
| |
| public static FileVNames fromFile(Path configFile) throws IOException { |
| return new FileVNames( |
| GSON.<List<BaseFileVName>>fromJson( |
| Files.newBufferedReader(configFile, UTF_8), CONFIG_TYPE)); |
| } |
| |
| public static FileVNames fromJson(String json) { |
| return new FileVNames(GSON.<List<BaseFileVName>>fromJson(json, CONFIG_TYPE)); |
| } |
| |
| /** |
| * Returns a base {@link VName} for the given file path. If none is configured, return {@link |
| * VName#getDefaultInstance()}. |
| */ |
| public VName lookupBaseVName(String path) { |
| if (path != null) { |
| for (BaseFileVName b : baseVNames) { |
| Matcher matcher = b.pattern.matcher(path); |
| if (matcher.matches()) { |
| return b.vname.fillInWith(matcher); |
| } |
| } |
| } |
| return VName.getDefaultInstance(); |
| } |
| |
| /** Base {@link VName} to use for files matching {@code pattern}. */ |
| private static class BaseFileVName { |
| public final Pattern pattern; |
| public final VNameTemplate vname; |
| |
| public BaseFileVName(Pattern pattern, VNameTemplate vname) { |
| this.pattern = pattern; |
| this.vname = vname; |
| } |
| |
| // GSON-required no-args constructor. |
| private BaseFileVName() { |
| this(null, null); |
| } |
| } |
| |
| /** Subset of a {@link VName} with built-in templating '@<num>@' markers. */ |
| private static class VNameTemplate { |
| private final String corpus; |
| private final String root; |
| private final String path; |
| |
| public VNameTemplate(String corpus, String root, String path) { |
| this.corpus = corpus; |
| this.root = root; |
| this.path = path; |
| } |
| |
| // GSON-required no-args constructor. |
| private VNameTemplate() { |
| this(null, null, null); |
| } |
| |
| /** |
| * Returns a {@link VName} by filling in its corpus/root/path with regex groups in the given |
| * {@link Matcher}. |
| */ |
| public VName fillInWith(Matcher m) { |
| VName.Builder b = VName.newBuilder(); |
| if (corpus != null) { |
| b.setCorpus(fillIn(corpus, m)); |
| } |
| if (root != null) { |
| b.setRoot(fillIn(root, m)); |
| } |
| if (path != null) { |
| b.setPath(fillIn(path, m)); |
| } |
| return b.build(); |
| } |
| |
| private static final Pattern replacerMatcher = Pattern.compile("@(\\d+)@"); |
| |
| private static String fillIn(String tmpl, Matcher m) { |
| Matcher replacers = replacerMatcher.matcher(tmpl); |
| Stack<ReplacementMarker> matches = new Stack<>(); |
| while (replacers.find()) { |
| matches.push( |
| new ReplacementMarker( |
| replacers.start(), replacers.end(), Integer.parseInt(replacers.group(1)))); |
| } |
| StringBuilder builder = new StringBuilder(tmpl); |
| while (!matches.isEmpty()) { |
| ReplacementMarker res = matches.pop(); |
| builder.replace(res.start, res.end, m.group(res.grp)); |
| } |
| return builder.toString(); |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("{corpus: %s, root: %s, path: %s}", corpus, root, path); |
| } |
| } |
| |
| /** Representation of a '@n@' marker's span and integer parse of 'n'. */ |
| private static class ReplacementMarker { |
| final int start; |
| final int end; |
| final int grp; |
| |
| ReplacementMarker(int start, int end, int grp) { |
| this.start = start; |
| this.end = end; |
| this.grp = grp; |
| } |
| } |
| |
| private static class PatternDeserializer implements JsonDeserializer<Pattern> { |
| @Override |
| public Pattern deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { |
| return Pattern.compile(json.getAsJsonPrimitive().getAsString()); |
| } |
| } |
| } |