blob: ac42543fe7ddb61c613b7de7768e40698c488ded [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;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.parser.BuildTargetParser;
import com.facebook.buck.rules.coercer.CoerceFailedException;
import com.facebook.buck.rules.coercer.TypeCoercer;
import com.facebook.buck.rules.coercer.TypeCoercerFactory;
import com.facebook.buck.util.Types;
import com.google.common.base.CaseFormat;
import com.google.common.base.Optional;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.Map;
import javax.annotation.Nullable;
class ParamInfo<T> implements Comparable<ParamInfo<T>> {
private final TypeCoercer<?> typeCoercer;
private final boolean isOptional;
private final String name;
private final String pythonName;
private final boolean isDep;
private final Field field;
public ParamInfo(TypeCoercerFactory typeCoercerFactory, Field field) {
this.field = field;
this.name = field.getName();
Hint hint = field.getAnnotation(Hint.class);
this.pythonName = determinePythonName(this.name, hint);
this.isDep = hint != null ? hint.isDep() : Hint.DEFAULT_IS_DEP;
isOptional = Optional.class.isAssignableFrom(field.getType());
this.typeCoercer = typeCoercerFactory.typeCoercerForType(Types.getFirstNonOptionalType(field));
}
public String getName() {
return name;
}
public boolean isOptional() {
return isOptional;
}
public String getPythonName() {
return pythonName;
}
public boolean isDep() {
return isDep;
}
/**
* Returns the type that input values will be coerced to.
* Return the type parameter of Optional if wrapped in Optional.
*/
public Class<?> getResultClass() {
return typeCoercer.getOutputClass();
}
public void traverse(Traversal traversal, T dto) {
traverseHelper(typeCoercer, traversal, dto);
}
@SuppressWarnings("unchecked")
private <U> void traverseHelper(TypeCoercer<U> typeCoercer, Traversal traversal, T dto) {
U object;
try {
if (isOptional) {
Optional<U> optional = (Optional<U>) field.get(dto);
object = optional.orNull();
} else {
object = (U) field.get(dto);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
if (object != null) {
typeCoercer.traverse(object, traversal);
}
}
public boolean hasElementTypes(final Class<?>... types) {
return typeCoercer.hasElementClass(types);
}
public void setFromParams(
ProjectFilesystem filesystem,
BuildRuleFactoryParams params,
Object arg,
Map<String, ?> instance
) throws ParamInfoException {
set(
params.buildTargetParser,
filesystem,
params.target.getBasePath(),
arg,
instance.get(name));
}
/**
* Sets a single property of the {@code dto}, coercing types as necessary.
* @param filesystem {@link com.facebook.buck.io.ProjectFilesystem} used to ensure
* {@link java.nio.file.Path}s exist.
* @param pathRelativeToProjectRoot The path relative to the project root that this DTO is for.
* @param dto The constructor DTO on which the value should be set.
* @param value The value, which may be coerced depending on the type on {@code dto}.
*/
public void set(
BuildTargetParser buildTargetParser,
ProjectFilesystem filesystem,
Path pathRelativeToProjectRoot,
Object dto,
@Nullable Object value) throws ParamInfoException {
Object result;
if (value == null) {
if (isOptional) {
result = typeCoercer.getOptionalValue();
} else if (Number.class.isAssignableFrom(typeCoercer.getOutputClass())) {
result = 0;
} else if (Boolean.class.isAssignableFrom(typeCoercer.getOutputClass())) {
result = false;
} else {
throw new ParamInfoException(name, "field cannot be null");
}
} else {
try {
result = typeCoercer.coerce(
buildTargetParser,
filesystem,
pathRelativeToProjectRoot,
value);
} catch (CoerceFailedException e) {
throw new ParamInfoException(name, e.getMessage(), e);
}
if (isOptional) {
result = Optional.of(result);
}
}
try {
field.set(dto, result);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
/**
* Only valid when comparing {@link ParamInfo} instances from the same description.
*/
@Override
public int compareTo(ParamInfo<T> that) {
return this.name.compareTo(that.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ParamInfo)) {
return false;
}
ParamInfo<?> that = (ParamInfo<?>) obj;
return name.equals(that.getName());
}
private String determinePythonName(String javaName, @Nullable Hint hint) {
if (hint != null && !Hint.DEFAULT_NAME.equals(hint.name())) {
return hint.name();
}
return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, javaName);
}
public interface Traversal extends TypeCoercer.Traversal {}
}