All Downloads are FREE. Search and download functionalities are using the official Maven repository.

shade.polaris.com.google.auto.value.processor.BuilderRequiredProperties Maven / Gradle / Ivy

There is a newer version: 2.0.0.0
Show newest version
/*
 * Copyright 2022 Google LLC
 *
 * 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.google.auto.value.processor;

import static com.google.auto.common.MoreStreams.toImmutableList;
import static com.google.auto.common.MoreStreams.toImmutableMap;
import static java.lang.Math.min;
import static java.util.stream.Collectors.joining;

import com.google.auto.value.processor.AutoValueishProcessor.Property;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * Code generation to track which properties have been set in a builder.
 *
 * 

Every property in an {@code @AutoValue} or {@code @AutoBuilder} builder must be set before the * {@code build()} method is called, with a few exceptions like {@code @Nullable} and {@code * Optional} properties. That means we must keep track of which ones have in fact been set. We do * that in two ways: for reference (non-primitive) types, we use {@code null} to indicate that the * value has not been set, while for primitive types we use a bitmask where each bit indicates * whether a certain primitive property has been set. * *

Additionally, for Kotlin constructors with default parameters, we track exactly which * properties have been set so we can invoke the constructor thas has a bitmask indicating the * properties to be defaulted. * *

The public methods in this class are accessed reflectively from the {@code builder.vm} * template. In that template, {@code $builderRequiredProperties} references an instance of this * class corresponding to the builder being generated. A reference like {@code * $builderRequiredProperties.markAsSet($p)} calls the method {@link #markAsSet} with the given * parameter. A reference like {@code $builderRequiredProperties.requiredProperties} is shorthand * for {@link #getRequiredProperties() $builderRequiredProperties.getProperties()}. */ public abstract class BuilderRequiredProperties { static final BuilderRequiredProperties EMPTY = of(ImmutableSet.of(), ImmutableSet.of()); // Bitmasks are a bit fiddly because we use them in a couple of ways. The first way is where // we are just using the bitmasks to track which primitive properties have been set. Then if // we have three primitive properties we can just check that the bitmask is (1 << 3) - 1, the // all-ones bitmask, to see that they have all been set. The second way is when we are also // handling optional Kotlin parameters. Then the bitmasks are different: we have one bit for every // property, primitive or not, optional or not. To check that the required primitive properties // have been set, we need to check specific bits. For example if properties 1 and 3 are primitive // then we need to check (~set$0 & ((1 << 1) | (1 << 3))) == 0. That tests that bits 1 and 3 are // set, since if either of them is 0 then it will be 1 in ~set$0 and will survive the AND. We can // also isolate the bits representing optional Kotlin parameters similarly, and pass those to the // special Kotlin constructor that handles default parameters. Kotlin uses bitmasks for that too: // they have one bit per parameter, optional or not, but only the bits for optional parameters // matter. We isolate those bits with `&` operations similar to what was described for primitive // properties. We also need the all-ones bitmask to implement a "copy constructor" builder, which // starts out with all properties set. /** All required properties. */ final ImmutableSet requiredProperties; /** * The bit index for each tracked property. Properties are tracked if they are primitive, or if * this is a Kotlin constructor with default parameters. Non-tracked properties do not appear in * this map. */ final ImmutableMap trackedPropertyToIndex; /** * The integer fields that store the bitmask. In the usual case, where there are ≤32 tracked * properties, we can pack the bitmask into one integer field. Its type is the smallest one that * fits the required number of bits, for example {@code byte} if there are ≤8 tracked properties. * *

If there are {@literal >32} tracked properties, we will pack them into as few integer fields * as possible. For example if there are 75 tracked properties (this can happen) then we will put * numbers 0 to 31 in an {@code int}, 32 to 63 in a second {@code int}, and 64 to 75 in a {@code * short}. * *

When there are {@literal >32} tracked properties, we could potentially pack them better if * we used {@code long}. But sometimes AutoValue code gets translated into JavaScript, which * doesn't handle long values natively. By the time you have that many properties you are probably * not going to notice the difference between 5 ints or 2 longs plus an int. */ final ImmutableList bitmaskFields; /** * Represents a field in which we will record which tracked properties from a certain set have * been given a value. */ private static class BitmaskField { final Class type; final String name; /** * The source representation of the value this field has when all properties have been given a * value. */ final String allSetBitmask; /** * The source representation of the value this field has when all required properties have been * given a value. */ final String allRequiredBitmask; BitmaskField(Class type, String name, String allSetBitmask, String allRequiredBitmask) { this.type = type; this.name = name; this.allSetBitmask = allSetBitmask; this.allRequiredBitmask = allRequiredBitmask; } } static BuilderRequiredProperties of( ImmutableSet allProperties, ImmutableSet requiredProperties) { boolean hasDefaults = allProperties.stream().anyMatch(Property::hasDefault); return hasDefaults ? new WithDefaults(allProperties, requiredProperties) : new NoDefaults(requiredProperties); } private BuilderRequiredProperties( ImmutableSet requiredProperties, ImmutableList trackedProperties) { this.requiredProperties = requiredProperties; int trackedCount = trackedProperties.size(); this.trackedPropertyToIndex = IntStream.range(0, trackedCount) .boxed() .collect(toImmutableMap(trackedProperties::get, i -> i)); this.bitmaskFields = IntStream.range(0, (trackedCount + 31) / 32) .mapToObj( i -> { int bitBase = i * 32; int remainingBits = trackedCount - bitBase; Class type = classForBits(remainingBits); String name = "set$" + i; String allSetBitmask = (remainingBits >= 32) ? "-1" : hex((1 << remainingBits) - 1); String allRequiredBitmask = allRequiredBitmask(trackedProperties, bitBase, remainingBits); return new BitmaskField(type, name, allSetBitmask, allRequiredBitmask); }) .collect(toImmutableList()); } abstract String allRequiredBitmask( ImmutableList trackedProperties, int bitBase, int remainingBits); public ImmutableSet getRequiredProperties() { return requiredProperties; } /** * Returns code to declare any fields needed to track which properties have been set. Each line in * the returned list should appear on a line of its own. */ public ImmutableList getFieldDeclarations() { return bitmaskFields.stream() .map(field -> "private " + field.type + " " + field.name + ";") .collect(toImmutableList()); } /** * Returns code to indicate that all tracked properties have received a value. This is needed in * the {@code toBuilder()} constructor, since it assigns to the corresponding fields directly * without going through their setters. */ public ImmutableList getInitToAllSet() { return bitmaskFields.stream() .map(field -> field.name + " = " + cast(field.type, field.allSetBitmask) + ";") .collect(toImmutableList()); } /** * Returns code to indicate that the given property has been set, if assigning to the property * field is not enough. For reference (non-primitive) properties, assignment is enough, but * for primitive properties we also need to set a bit in the bitmask. */ public String markAsSet(Property p) { Integer index = trackedPropertyToIndex.get(p); if (index == null) { return ""; } BitmaskField field = bitmaskFields.get(index / 32); // This use-case is why Java reduces int shift amounts mod 32. :-) return field.name + " |= " + cast(field.type, hex(1 << index)) + ";"; } /** * Returns an expression that is true if the given property is required but has not been set. * Returns null if the property is not required. */ public String missingRequiredProperty(Property p) { return requiredProperties.contains(p) ? propertyNotSet(p) : null; } /** * Returns an expression that is true if the given property has not been given a value. That's * only different from {@link #missingRequiredProperty} if the property has a Kotlin default. If * so, we don't require it to be set at build time (because Kotlin will supply the default), but * we do require it to be set if it is accessed with a getter on the builder. We don't have access * to Kotlin parameter defaults so we can't arrange for the builder field to have the same default * value. Rather than returning a bogus zero value we say the value is unset. */ public String noValueToGet(Property p) { return (requiredProperties.contains(p) || p.hasDefault()) ? propertyNotSet(p) : null; } private String propertyNotSet(Property p) { Integer index = trackedPropertyToIndex.get(p); if (index == null) { return "this." + p + " == null"; } BitmaskField field = bitmaskFields.get(index / 32); return "(" + field.name + " & " + hex(1 << index) + ") == 0"; } /** * Returns an expression that is true if any required properties have not been set. Should not be * called if there are no required properties. */ public abstract String getAnyMissing(); /** * Returns additional constructor parameters to indicate what properties have been defaulted, or * an empty string if there are none. */ public abstract String getDefaultedBitmaskParameters(); /** * The smallest primitive integer type that has at least this many bits, or {@code int} if the * number of bits is more than 32. */ private static Class classForBits(int bits) { return bits <= 8 ? byte.class : bits <= 16 ? short.class : int.class; } private static String cast(Class type, String number) { return (type == int.class) ? number : ("(" + type + ") " + number); } @VisibleForTesting static String hex(int number) { if (number >= 0) { if (number < 10) { return Integer.toHexString(number); } if (number <= 0xffff) { return "0x" + Integer.toHexString(number); } } // It's harder to tell 0x7fffffff from 0x7ffffff than to tell 0x7fff_ffff from 0x7ff_ffff. String lowNybble = Integer.toHexString(number & 0xffff); String pad = "000".substring(lowNybble.length() - 1); return "0x" + Integer.toHexString(number >>> 16) + "_" + pad + lowNybble; } /** Subclass for when there are no Kotlin default properties. */ private static final class NoDefaults extends BuilderRequiredProperties { NoDefaults(ImmutableSet requiredProperties) { super(requiredProperties, primitivePropertiesIn(requiredProperties)); } private static ImmutableList primitivePropertiesIn( ImmutableSet properties) { return properties.stream().filter(p -> p.getKind().isPrimitive()).collect(toImmutableList()); } @Override String allRequiredBitmask( ImmutableList trackedProperties, int bitBase, int remainingBits) { // We have to be a bit careful with sign-extension. If we're using a byte and // the mask is 0xff, then we'll write -1 instead. The comparison set$0 == 0xff // would always fail since the byte value gets sign-extended to 0xffff_ffff. // We should also write -1 if this is not the last field. boolean minusOne = remainingBits >= 32 || remainingBits == 16 || remainingBits == 8; return minusOne ? "-1" : hex((1 << remainingBits) - 1); } /** * {@inheritDoc} * *

We check the bitmask for primitive properties, and null checks for non-primitive ones. */ @Override public String getAnyMissing() { Stream primitiveConditions = bitmaskFields.stream().map(field -> field.name + " != " + field.allRequiredBitmask); Stream nonPrimitiveConditions = requiredProperties.stream() .filter(p -> !trackedPropertyToIndex.containsKey(p)) .map(this::missingRequiredProperty); return Stream.concat(primitiveConditions, nonPrimitiveConditions).collect(joining("\n|| ")); } @Override public String getDefaultedBitmaskParameters() { return ""; } } /** Subclass for when there are Kotlin default properties. */ private static final class WithDefaults extends BuilderRequiredProperties { private final ImmutableList allProperties; WithDefaults(ImmutableSet allProperties, ImmutableSet requiredProperties) { super(requiredProperties, allProperties.asList()); this.allProperties = allProperties.asList(); } @Override String allRequiredBitmask( ImmutableList trackedProperties, int bitBase, int remainingBits) { int requiredBits = 0; for (int bit = 0; bit < remainingBits; bit++) { Property p = trackedProperties.get(bitBase + bit); if (requiredProperties.contains(p)) { requiredBits |= 1 << bit; } } return hex(requiredBits); } /** * {@inheritDoc} * *

Everything can be checked with bitmask operations. If bit i represents a required * property then it must be 1 in the bitmask field. So if we invert it we must get 0, and if we * do that for the field as a whole and AND with a bitmask selecting only required properties we * should get 0. */ @Override public String getAnyMissing() { return bitmaskFields.stream() .filter(field -> !field.allRequiredBitmask.equals("0")) .map(field -> "(~" + field.name + " & " + field.allRequiredBitmask + ") != 0") .collect(joining("\n|| ")); } /** * {@inheritDoc} * *

When there are default parameters, we're calling the special constructor that has one or * more bitmask parameters at the end. Bit i is set if parameter i (zero-origin) * has its default value, and then the actual value passed for that parameter is ignored. Our * bitmask field has a 1 for any parameter that has been set, meaning it has a 0 for any * parameter that has been defaulted. So we need to invert it, and we also want to AND it with a * bitmask that selects just the bits for parameters with defaults. (The AND probably isn't * strictly necessary, since the constructor code doesn't actually look at those other bits, but * it seems cleaner.) If the bitmask for parameters with defaults is 0 then we can just use 0 * for that bitmask, and if it is ~0 (all 1 bits) then we can skip the AND. * *

That special constructor has an additional dummy parameter of type {@code * DefaultConstructorMarker}. We just pass {@code null} to that parameter. */ @Override public String getDefaultedBitmaskParameters() { ImmutableList.Builder defaultedBitmasksBuilder = ImmutableList.builder(); for (int bitBase = 0; bitBase < allProperties.size(); bitBase += 32) { int bitCount = min(32, allProperties.size() - bitBase); int defaultedBitmask = 0; for (int i = 0; i < bitCount; i++) { if (allProperties.get(bitBase + i).hasDefault()) { defaultedBitmask |= 1 << i; } } defaultedBitmasksBuilder.add(defaultedBitmask); } ImmutableList defaultedBitmasks = defaultedBitmasksBuilder.build(); return IntStream.range(0, bitmaskFields.size()) .mapToObj( i -> { int defaultedBitmask = defaultedBitmasks.get(i); switch (defaultedBitmask) { case 0: return "0"; case ~0: return "~" + bitmaskFields.get(i).name; default: return "~" + bitmaskFields.get(i).name + " & " + hex(defaultedBitmask); } }) .collect(joining(",\n", ",\n", ",\nnull")); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy