blob: 2f91a3b8b20d377733ecc0b3b0c3bac33c9281dd [file] [log] [blame]
/*
* Copyright 2013-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.parser.BuildTargetParser;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;
/**
* Coerces a type to either type, trying the left type before the right.
*/
public class EitherTypeCoercer<Left, Right> implements TypeCoercer<Either<Left, Right>> {
private final TypeCoercer<Left> leftTypeCoercer;
private final TypeCoercer<Right> rightTypeCoercer;
public EitherTypeCoercer(TypeCoercer<Left> leftTypeCoercer, TypeCoercer<Right> rightTypeCoercer) {
this.leftTypeCoercer = leftTypeCoercer;
this.rightTypeCoercer = rightTypeCoercer;
// disallow either of eithers, it doesn't work well with traversals
Preconditions.checkState(
!(leftTypeCoercer instanceof EitherTypeCoercer ||
rightTypeCoercer instanceof EitherTypeCoercer),
"Either of Eithers is not allowed");
}
@SuppressWarnings("unchecked")
@Override
public Class<Either<Left, Right>> getOutputClass() {
return (Class<Either<Left, Right>>) (Class<?>) Either.class;
}
@Override
public boolean hasElementClass(Class<?>... types) {
return leftTypeCoercer.hasElementClass(types) || rightTypeCoercer.hasElementClass(types);
}
@Override
public Optional<Either<Left, Right>> getOptionalValue() {
return Optional.absent();
}
@Override
public void traverse(Either<Left, Right> object, Traversal traversal) {
if (object.isLeft()) {
leftTypeCoercer.traverse(object.getLeft(), traversal);
} else {
rightTypeCoercer.traverse(object.getRight(), traversal);
}
}
// Classifications for the "type" of object/coercer. We use this to unambiguously
// choose which "side" of the coercion fork we want to choose, rather than relying
// on catching `CoerceFailedException` exceptions, which can lead to really unhelpful
// error messages hitting the user.
private enum Type {
DEFAULT,
COLLECTION,
MAP,
}
private static <T> Type getCoercerType(TypeCoercer<T> coercer) {
if (coercer instanceof MapTypeCoercer) {
return Type.MAP;
} else if (coercer instanceof CollectionTypeCoercer) {
return Type.COLLECTION;
} else {
return Type.DEFAULT;
}
}
private static Type getObjectType(Object object) {
if (object instanceof Map) {
return Type.MAP;
} else if (object instanceof Collection) {
return Type.COLLECTION;
} else {
return Type.DEFAULT;
}
}
@Override
public Either<Left, Right> coerce(
BuildTargetParser buildTargetParser,
ProjectFilesystem filesystem,
Path pathRelativeToProjectRoot,
Object object) throws CoerceFailedException {
// Determine the "type" of the object we're coercing and our left and right coercers.
Type objectType = getObjectType(object);
Type leftCoercerType = getCoercerType(leftTypeCoercer);
Type rightCoercerType = getCoercerType(rightTypeCoercer);
// If both coercers match, try the left one first, and if it fails try the right
// side. If neither work, throw an exception combining the two errors. Long term,
// we probably should require some way to "choose" a side without relying on failures,
// as this would make errors reported to the user much more clear.
if (leftCoercerType == objectType && rightCoercerType == objectType) {
try {
return Either.ofLeft(leftTypeCoercer.coerce(
buildTargetParser,
filesystem,
pathRelativeToProjectRoot,
object));
} catch (CoerceFailedException eLeft) {
try {
return Either.ofRight(rightTypeCoercer.coerce(
buildTargetParser,
filesystem,
pathRelativeToProjectRoot,
object));
} catch (CoerceFailedException eRight) {
throw new CoerceFailedException(String.format(
"%s, or %s",
eLeft.getMessage(),
eRight.getMessage()));
}
}
}
// Only the left coercer matches, so use that to parse the input and let any inner
// exceptions propagate up.
if (leftCoercerType == objectType) {
return Either.ofLeft(leftTypeCoercer.coerce(
buildTargetParser,
filesystem,
pathRelativeToProjectRoot,
object));
}
// Only the right coercer matches, so use that to parse the input and let any inner
// exceptions propagate up.
if (rightCoercerType == objectType) {
return Either.ofRight(rightTypeCoercer.coerce(
buildTargetParser,
filesystem,
pathRelativeToProjectRoot,
object));
}
// None of our coercers matched the "type" of the object, so throw the generic
// error message.
throw new CoerceFailedException(String.format("cannot parse %s", object));
}
}