Java tutorial
/* * Copyright 2014-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.model.UnflavoredBuildTarget; import com.facebook.buck.rules.coercer.BuildConfigFields.Field; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.immutables.BuckStyleImmutable; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.immutables.value.Value; import java.util.Iterator; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * List of fields to add to a generated {@code BuildConfig.java} file. Each field knows its Java * type, variable name, and value. */ @Value.Enclosing @Value.Immutable @BuckStyleImmutable abstract class AbstractBuildConfigFields implements Iterable<Field> { /** An individual field in a {@link BuildConfigFields}. */ @Value.Immutable abstract static class AbstractField { @Value.Parameter public abstract String getType(); @Value.Parameter public abstract String getName(); @Value.Parameter public abstract String getValue(); /** * @return a string that could be passed to * {@link BuildConfigFields#fromFieldDeclarations(Iterable)} such that it could be parsed * to return a {@link Field} equal to this object. */ @Override public String toString() { return String.format("%s %s = %s", getType(), getName(), getValue()); } } private static final Pattern VARIABLE_DEFINITION_PATTERN = Pattern .compile("(?<type>[a-zA-Z_$][a-zA-Z0-9_.<>]+(" + Pattern.quote("[]") + ")?)" + "\\s+" + "(?<name>[a-zA-Z_$][a-zA-Z0-9_$]+)" + "\\s*=\\s*" + "(?<value>.+)"); private static final ImmutableSet<String> PRIMITIVE_NUMERIC_TYPE_NAMES = ImmutableSet.of("byte", "char", "double", "float", "int", "long", "short"); private static final Function<String, Field> TRANSFORM = input -> { Matcher matcher = VARIABLE_DEFINITION_PATTERN.matcher(input); if (matcher.matches()) { return Field.builder().setType(matcher.group("type")).setName(matcher.group("name")) .setValue(matcher.group("value")).build(); } else { throw new HumanReadableException("Not a valid BuildConfig variable declaration: %s", input); } }; private static final BuildConfigFields EMPTY = BuildConfigFields.of(ImmutableMap.of()); @Value.Parameter protected abstract Map<String, Field> getNameToField(); public static BuildConfigFields fromFieldDeclarations(Iterable<String> declarations) { return fromFields(FluentIterable.from(declarations).transform(TRANSFORM)); } /** @return a {@link BuildConfigFields} with no fields */ public static BuildConfigFields empty() { return EMPTY; } /** @return a {@link BuildConfigFields} that contains the specified fields in iteration order. */ public static BuildConfigFields fromFields(Iterable<Field> fields) { ImmutableMap<String, Field> entries = FluentIterable.from(fields).uniqueIndex(Field::getName); return BuildConfigFields.builder().putAllNameToField(entries).build(); } /** * @return A new {@link BuildConfigFields} with all of the fields from this object, combined with * all of the fields from the specified {@code fields}. If both objects have fields with the * same name, the entry from the {@code fields} parameter wins. */ public BuildConfigFields putAll(BuildConfigFields fields) { ImmutableMap.Builder<String, Field> nameToFieldBuilder = ImmutableMap.builder(); nameToFieldBuilder.putAll(fields.getNameToField()); for (Field field : this.getNameToField().values()) { if (!fields.getNameToField().containsKey(field.getName())) { nameToFieldBuilder.put(field.getName(), field); } } return BuildConfigFields.of(nameToFieldBuilder.build()); } /** * Creates the Java code for a {@code BuildConfig.java} file in the specified {@code javaPackage}. * @param source The build target of the rule that is responsible for generating this * BuildConfig.java file. * @param javaPackage The Java package for the generated file. * @param useConstantExpressions Whether the value of each field in the generated Java code should * be the literal value from the {@link Field} (i.e., a constant expression) or a * non-constant-expression that is guaranteed to evaluate to the literal value. */ public String generateBuildConfigDotJava(UnflavoredBuildTarget source, String javaPackage, boolean useConstantExpressions) { StringBuilder builder = new StringBuilder(); // By design, we drop the flavor from the BuildTarget (if present), so this debug text makes // more sense to users. builder.append(String.format("// Generated by %s. DO NOT MODIFY.\n", source)); builder.append("package ").append(javaPackage).append(";\n"); builder.append("public class BuildConfig {\n"); builder.append(" private BuildConfig() {}\n"); final String prefix = " public static final "; for (Field field : getNameToField().values()) { String type = field.getType(); if ("boolean".equals(type)) { // type is a non-numeric primitive. boolean isTrue = "true".equals(field.getValue()); if (!(isTrue || "false".equals(field.getValue()))) { throw new HumanReadableException("expected boolean literal but was: %s", field.getValue()); } String value; if (useConstantExpressions) { value = String.valueOf(isTrue); } else { value = "Boolean.parseBoolean(null)"; if (isTrue) { value = "!" + value; } } builder.append(prefix + "boolean " + field.getName() + " = " + value + ";\n"); } else { String typeSafeZero = PRIMITIVE_NUMERIC_TYPE_NAMES.contains(type) ? "0" : "null"; String defaultValue = field.getValue(); if (!useConstantExpressions) { defaultValue = "!Boolean.parseBoolean(null) ? " + defaultValue + " : " + typeSafeZero; } builder.append(prefix + type + " " + field.getName() + " = " + defaultValue + ";\n"); } } builder.append("}\n"); return builder.toString(); } /** * @return iterator that enumerates the fields used to construct this {@link BuildConfigFields}. * The {@link Iterator#remove()} method of the return value is not supported. */ @Override public Iterator<Field> iterator() { return getNameToField().values().iterator(); } /** * @return value that represents the data stored in this object such that it can be used to * represent this object in a {@link com.facebook.buck.rules.RuleKey}. */ @Override public String toString() { return Joiner.on(';').join(getNameToField().values()); } }