/* * Copyright 2017-present Facebook, 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.facebook.buck.rules.coercer; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.model.Either; import com.facebook.buck.model.MacroFinder; import com.facebook.buck.model.MacroMatchResult; import com.facebook.buck.rules.CellPathResolver; import com.facebook.buck.rules.macros.Macro; import com.facebook.buck.rules.macros.StringWithMacros; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import java.nio.file.Path; import java.util.HashSet; public class StringWithMacrosTypeCoercer implements TypeCoercer<StringWithMacros> { private final ImmutableMap<String, Class<? extends Macro>> macros; private final ImmutableMap<Class<? extends Macro>, MacroTypeCoercer<? extends Macro>> coercers; private StringWithMacrosTypeCoercer( ImmutableMap<String, Class<? extends Macro>> macros, ImmutableMap<Class<? extends Macro>, MacroTypeCoercer<? extends Macro>> coercers) { Preconditions.checkArgument( Sets.difference(coercers.keySet(), new HashSet<>(macros.values())).isEmpty()); this.macros = macros; this.coercers = coercers; } static StringWithMacrosTypeCoercer from( ImmutableMap<String, Class<? extends Macro>> macros, ImmutableList<MacroTypeCoercer<? extends Macro>> coercers) { return new StringWithMacrosTypeCoercer( macros, Maps.uniqueIndex(coercers, MacroTypeCoercer::getOutputClass)); } @Override public Class<StringWithMacros> getOutputClass() { return StringWithMacros.class; } @Override public boolean hasElementClass(Class<?>... types) { for (MacroTypeCoercer<? extends Macro> coercer : coercers.values()) { if (coercer.hasElementClass(types)) { return true; } } return false; } private <M extends Macro> void traverse( MacroTypeCoercer<M> coercer, Macro macro, Traversal traversal) { coercer.traverse(coercer.getOutputClass().cast(macro), traversal); } @Override public void traverse(StringWithMacros stringWithMacros, Traversal traversal) { for (Macro macro : stringWithMacros.getMacros()) { MacroTypeCoercer<? extends Macro> coercer = Preconditions.checkNotNull(coercers.get(macro.getClass())); traverse(coercer, macro, traversal); } } private StringWithMacros parse( CellPathResolver cellRoots, ProjectFilesystem filesystem, Path pathRelativeToProjectRoot, String blob) throws CoerceFailedException { ImmutableList.Builder<Either<String, Macro>> parts = ImmutableList.builder(); // Iterate over all macros found in the string, expanding each found macro. int lastEnd = 0; MacroFinder.MacroFinderAutomaton matcher = new MacroFinder.MacroFinderAutomaton(blob); while (matcher.hasNext()) { MacroMatchResult matchResult = matcher.next(); // Add everything from the original string since the last match to this one. if (lastEnd < matchResult.getStartIndex()) { parts.add(Either.ofLeft(blob.substring(lastEnd, matchResult.getStartIndex()))); } // Look up the macro coercer that owns this macro name. String name = matchResult.getMacroType(); Class<? extends Macro> clazz = macros.get(name); if (clazz == null) { throw new CoerceFailedException( String.format( "expanding %s: no such macro \"%s\"", blob.substring(matchResult.getStartIndex(), matchResult.getEndIndex()), matchResult.getMacroType())); } MacroTypeCoercer<? extends Macro> coercer = Preconditions.checkNotNull(coercers.get(clazz)); ImmutableList<String> args = matchResult.getMacroInput(); // Delegate to the macro coercers to parse the macro.. Macro macro; try { macro = coercer.coerce(cellRoots, filesystem, pathRelativeToProjectRoot, args); } catch (CoerceFailedException e) { throw new CoerceFailedException(String.format("macro \"%s\": %s", name, e.getMessage()), e); } parts.add(Either.ofRight(macro)); lastEnd = matchResult.getEndIndex(); } // Append the remaining part of the original string after the last match. if (lastEnd < blob.length()) { parts.add(Either.ofLeft(blob.substring(lastEnd, blob.length()))); } return StringWithMacros.of(parts.build()); } @Override public StringWithMacros coerce( CellPathResolver cellRoots, ProjectFilesystem filesystem, Path pathRelativeToProjectRoot, Object object) throws CoerceFailedException { if (!(object instanceof String)) { throw CoerceFailedException.simple(object, getOutputClass()); } return parse(cellRoots, filesystem, pathRelativeToProjectRoot, (String) object); } }