/* * Copyright 2016-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.versions; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetPattern; import com.facebook.buck.model.Pair; import com.facebook.buck.parser.BuildTargetPatternParser; import com.facebook.buck.rules.CellPathResolver; import com.facebook.buck.rules.DefaultBuildTargetSourcePath; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourceWithFlags; import com.facebook.buck.rules.TargetNode; import com.facebook.buck.rules.coercer.CoercedTypeCache; import com.facebook.buck.rules.coercer.ParamInfo; import com.facebook.buck.rules.coercer.TypeCoercerFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedSet; import java.util.Map; import java.util.Optional; import java.util.function.Function; /** * A helper class which uses reflection to translate {@link BuildTarget}s in {@link TargetNode}s. * The API methods use an {@link Optional} for their return types, so that {@link Optional#empty()} * can be used to signify a translation was not needed. This may allow some translation functions to * avoid copying or creating unnecessary new objects. */ public abstract class TargetNodeTranslator { private final TypeCoercerFactory typeCoercerFactory; // Translators registered for various types. private final ImmutableList<TargetTranslator<?>> translators; public TargetNodeTranslator( TypeCoercerFactory typeCoercerFactory, ImmutableList<TargetTranslator<?>> translators) { this.typeCoercerFactory = typeCoercerFactory; this.translators = translators; } public abstract Optional<BuildTarget> translateBuildTarget(BuildTarget target); public abstract Optional<ImmutableMap<BuildTarget, Version>> getSelectedVersions( BuildTarget target); public <A> Optional<Optional<A>> translateOptional( CellPathResolver cellPathResolver, BuildTargetPatternParser<BuildTargetPattern> pattern, Optional<A> val) { if (!val.isPresent()) { return Optional.empty(); } Optional<A> inner = translate(cellPathResolver, pattern, val.get()); if (!inner.isPresent()) { return Optional.empty(); } return Optional.of(inner); } public <A> Optional<ImmutableList<A>> translateList( CellPathResolver cellPathResolver, BuildTargetPatternParser<BuildTargetPattern> pattern, ImmutableList<A> val) { boolean modified = false; ImmutableList.Builder<A> builder = ImmutableList.builder(); for (A a : val) { Optional<A> item = translate(cellPathResolver, pattern, a); modified = modified || item.isPresent(); builder.add(item.orElse(a)); } return modified ? Optional.of(builder.build()) : Optional.empty(); } public <A> Optional<ImmutableSet<A>> translateSet( CellPathResolver cellPathResolver, BuildTargetPatternParser<BuildTargetPattern> pattern, ImmutableSet<A> val) { boolean modified = false; ImmutableSet.Builder<A> builder = ImmutableSet.builder(); for (A a : val) { Optional<A> item = translate(cellPathResolver, pattern, a); modified = modified || item.isPresent(); builder.add(item.orElse(a)); } return modified ? Optional.of(builder.build()) : Optional.empty(); } public <A extends Comparable<?>> Optional<ImmutableSortedSet<A>> translateSortedSet( CellPathResolver cellPathResolver, BuildTargetPatternParser<BuildTargetPattern> pattern, ImmutableSortedSet<A> val) { boolean modified = false; ImmutableSortedSet.Builder<A> builder = ImmutableSortedSet.naturalOrder(); for (A a : val) { Optional<A> item = translate(cellPathResolver, pattern, a); modified = modified || item.isPresent(); builder.add(item.orElse(a)); } return modified ? Optional.of(builder.build()) : Optional.empty(); } public <A extends Comparable<?>, B> Optional<ImmutableMap<A, B>> translateMap( CellPathResolver cellPathResolver, BuildTargetPatternParser<BuildTargetPattern> pattern, ImmutableMap<A, B> val) { boolean modified = false; ImmutableMap.Builder<A, B> builder = ImmutableMap.builder(); for (Map.Entry<A, B> ent : val.entrySet()) { Optional<A> key = translate(cellPathResolver, pattern, ent.getKey()); Optional<B> value = translate(cellPathResolver, pattern, ent.getValue()); modified = modified || key.isPresent() || value.isPresent(); builder.put(key.orElse(ent.getKey()), value.orElse(ent.getValue())); } return modified ? Optional.of(builder.build()) : Optional.empty(); } public <A extends Comparable<?>, B> Optional<ImmutableSortedMap<A, B>> translateSortedMap( CellPathResolver cellPathResolver, BuildTargetPatternParser<BuildTargetPattern> pattern, ImmutableSortedMap<A, B> val) { boolean modified = false; ImmutableSortedMap.Builder<A, B> builder = ImmutableSortedMap.naturalOrder(); for (Map.Entry<A, B> ent : val.entrySet()) { Optional<A> key = translate(cellPathResolver, pattern, ent.getKey()); Optional<B> value = translate(cellPathResolver, pattern, ent.getValue()); modified = modified || key.isPresent() || value.isPresent(); builder.put(key.orElse(ent.getKey()), value.orElse(ent.getValue())); } return modified ? Optional.of(builder.build()) : Optional.empty(); } public <A, B> Optional<Pair<A, B>> translatePair( CellPathResolver cellPathResolver, BuildTargetPatternParser<BuildTargetPattern> pattern, Pair<A, B> val) { Optional<A> first = translate(cellPathResolver, pattern, val.getFirst()); Optional<B> second = translate(cellPathResolver, pattern, val.getSecond()); if (!first.isPresent() && !second.isPresent()) { return Optional.empty(); } return Optional.of(new Pair<>(first.orElse(val.getFirst()), second.orElse(val.getSecond()))); } public Optional<DefaultBuildTargetSourcePath> translateBuildTargetSourcePath( CellPathResolver cellPathResolver, BuildTargetPatternParser<BuildTargetPattern> pattern, DefaultBuildTargetSourcePath val) { BuildTarget target = val.getTarget(); Optional<BuildTarget> translatedTarget = translate(cellPathResolver, pattern, target); return translatedTarget.isPresent() ? Optional.of(new DefaultBuildTargetSourcePath(translatedTarget.get())) : Optional.empty(); } public Optional<SourceWithFlags> translateSourceWithFlags( CellPathResolver cellPathResolver, BuildTargetPatternParser<BuildTargetPattern> pattern, SourceWithFlags val) { Optional<SourcePath> translatedSourcePath = translate(cellPathResolver, pattern, val.getSourcePath()); return translatedSourcePath.isPresent() ? Optional.of(SourceWithFlags.of(translatedSourcePath.get(), val.getFlags())) : Optional.empty(); } @SuppressWarnings("unchecked") private <A, T> Optional<Optional<T>> tryTranslate( CellPathResolver cellPathResolver, BuildTargetPatternParser<BuildTargetPattern> pattern, TargetTranslator<A> translator, T object) { Class<A> clazz = translator.getTranslatableClass(); if (!clazz.isAssignableFrom(object.getClass())) { return Optional.empty(); } return Optional.of( (Optional<T>) translator.translateTargets(cellPathResolver, pattern, this, (A) object)); } @SuppressWarnings("unchecked") public <A> Optional<A> translate( CellPathResolver cellPathResolver, BuildTargetPatternParser<BuildTargetPattern> pattern, A object) { // `null`s require no translating. if (object == null) { return Optional.empty(); } // First try all registered translators. for (TargetTranslator<?> translator : translators) { if (translator.getTranslatableClass().isAssignableFrom(object.getClass())) { Optional<Optional<A>> translated = tryTranslate(cellPathResolver, pattern, translator, object); if (translated.isPresent()) { return translated.get(); } } } if (object instanceof Optional) { return (Optional<A>) translateOptional(cellPathResolver, pattern, (Optional<?>) object); } else if (object instanceof ImmutableList) { return (Optional<A>) translateList(cellPathResolver, pattern, (ImmutableList<?>) object); } else if (object instanceof ImmutableSortedSet) { return (Optional<A>) translateSortedSet( cellPathResolver, pattern, (ImmutableSortedSet<? extends Comparable<?>>) object); } else if (object instanceof ImmutableSet) { return (Optional<A>) translateSet(cellPathResolver, pattern, (ImmutableSet<?>) object); } else if (object instanceof ImmutableSortedMap) { return (Optional<A>) translateSortedMap( cellPathResolver, pattern, (ImmutableSortedMap<? extends Comparable<?>, ?>) object); } else if (object instanceof ImmutableMap) { return (Optional<A>) translateMap( cellPathResolver, pattern, (ImmutableMap<? extends Comparable<?>, ?>) object); } else if (object instanceof Pair) { return (Optional<A>) translatePair(cellPathResolver, pattern, (Pair<?, ?>) object); } else if (object instanceof DefaultBuildTargetSourcePath) { return (Optional<A>) translateBuildTargetSourcePath( cellPathResolver, pattern, (DefaultBuildTargetSourcePath) object); } else if (object instanceof SourceWithFlags) { return (Optional<A>) translateSourceWithFlags(cellPathResolver, pattern, (SourceWithFlags) object); } else if (object instanceof BuildTarget) { return (Optional<A>) translateBuildTarget((BuildTarget) object); } else if (object instanceof TargetTranslatable) { TargetTranslatable<A> targetTranslatable = (TargetTranslatable<A>) object; Optional<A> res = targetTranslatable.translateTargets(cellPathResolver, pattern, this); return res; } else { return Optional.empty(); } } public boolean translateConstructorArg( CellPathResolver cellPathResolver, BuildTargetPatternParser<BuildTargetPattern> pattern, Object constructorArg, Object newConstructorArgOrBuilder) { boolean modified = false; for (ParamInfo param : CoercedTypeCache.INSTANCE .getAllParamInfo(typeCoercerFactory, constructorArg.getClass()) .values()) { Object value = param.get(constructorArg); Optional<Object> newValue = translate(cellPathResolver, pattern, value); modified |= newValue.isPresent(); param.setCoercedValue(newConstructorArgOrBuilder, newValue.orElse(value)); } return modified; } @SuppressWarnings("unchecked") private <A> Optional<A> translateConstructorArg( CellPathResolver cellPathResolver, BuildTargetPatternParser<BuildTargetPattern> pattern, TargetNode<A, ?> node) { A constructorArg = node.getConstructorArg(); if (node.getDescription() instanceof TargetTranslatorOverridingDescription) { return ((TargetTranslatorOverridingDescription<A>) node.getDescription()) .translateConstructorArg( node.getBuildTarget(), node.getCellNames(), this, constructorArg); } else { Pair<Object, Function<Object, A>> newArgAndBuild = CoercedTypeCache.instantiateSkeleton( node.getDescription().getConstructorArgType(), node.getBuildTarget()); boolean modified = translateConstructorArg( cellPathResolver, pattern, constructorArg, newArgAndBuild.getFirst()); if (!modified) { return Optional.empty(); } return Optional.of(newArgAndBuild.getSecond().apply(newArgAndBuild.getFirst())); } } /** * @return a copy of the given {@link TargetNode} with all found {@link BuildTarget}s translated, * or {@link Optional#empty()} if the node requires no translation. */ public <A> Optional<TargetNode<A, ?>> translateNode(TargetNode<A, ?> node) { CellPathResolver cellPathResolver = node.getCellNames(); BuildTargetPatternParser<BuildTargetPattern> pattern = BuildTargetPatternParser.forBaseName(node.getBuildTarget().getBaseName()); Optional<BuildTarget> target = translateBuildTarget(node.getBuildTarget()); Optional<A> constructorArg = translateConstructorArg(cellPathResolver, pattern, node); Optional<ImmutableSet<BuildTarget>> declaredDeps = translateSet(cellPathResolver, pattern, node.getDeclaredDeps()); Optional<ImmutableSet<BuildTarget>> extraDeps = translateSet(cellPathResolver, pattern, node.getExtraDeps()); Optional<ImmutableSet<BuildTarget>> targetGraphOnlyDeps = translateSet(cellPathResolver, pattern, node.getTargetGraphOnlyDeps()); Optional<ImmutableMap<BuildTarget, Version>> newSelectedVersions = getSelectedVersions(node.getBuildTarget()); Optional<ImmutableMap<BuildTarget, Version>> oldSelectedVersions = node.getSelectedVersions(); Optional<Optional<ImmutableMap<BuildTarget, Version>>> selectedVersions = oldSelectedVersions.equals(newSelectedVersions) ? Optional.empty() : Optional.of(newSelectedVersions); // If nothing has changed, don't generate a new node. if (!target.isPresent() && !constructorArg.isPresent() && !declaredDeps.isPresent() && !extraDeps.isPresent() && !targetGraphOnlyDeps.isPresent() && !selectedVersions.isPresent()) { return Optional.empty(); } return Optional.of( node.withTargetConstructorArgDepsAndSelectedVerisons( target.orElse(node.getBuildTarget()), constructorArg.orElse(node.getConstructorArg()), declaredDeps.orElse(node.getDeclaredDeps()), extraDeps.orElse(node.getExtraDeps()), targetGraphOnlyDeps.orElse(node.getTargetGraphOnlyDeps()), selectedVersions.orElse(oldSelectedVersions))); } }