// Copyright 2014 The Bazel Authors. All rights reserved. // // 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.devtools.build.lib.packages; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.devtools.build.lib.events.Location; import java.util.Arrays; import javax.annotation.Nullable; /** * Provides attribute setting and retrieval for a Rule. Encapsulating attribute access * here means it can be passed around independently of the Rule itself. In particular, * it can be consumed by independent {@link AttributeMap} instances that can apply * varying kinds of logic for determining the "value" of an attribute. For example, * a configurable attribute's "value" may be a { config --> value } dictionary * or a configuration-bound lookup on that dictionary, depending on the context in * which it's requested. * * <p>This class provides the lowest-level access to attribute information. It is *not* * intended to be a robust public interface, but rather just an input to {@link AttributeMap} * instances. Use those instances for all domain-level attribute access. */ public class AttributeContainer { private final RuleClass ruleClass; // Attribute values, keyed by attribute index: private final Object[] attributeValues; // Holds two lists of attribute indices. // The first byte gives the length of the first list. // The first list records which attributes were set explicitly in the BUILD file. // The second list ends at the end of the array. // The second list records which attributes have Locations, in reverse order // from the attributeLocations array. // Between the lists there may be unused zero bytes (zeros are forbidden within each list). private byte[] state; // Attribute locations, packed: private Location[] attributeLocations; /** * Create a container for a rule of the given rule class. */ public AttributeContainer(RuleClass ruleClass) { this(ruleClass, EMPTY_LOCATIONS); } AttributeContainer(RuleClass ruleClass, Location[] locations) { int n = ruleClass.getAttributeCount(); if (n > 254) { // We reserve the zero byte as a hole/sentinel inside state[]. // If you hit this limit, replace byte with char and remove the masking with 0xff. throw new AssertionError("can't pack " + n + " rule indices into bytes"); } this.ruleClass = ruleClass; this.attributeValues = new Object[n]; this.state = EMPTY_STATE; this.attributeLocations = locations; } private static final byte[] EMPTY_STATE = {0}; private static final Location[] EMPTY_LOCATIONS = {}; /** * Returns an attribute value by name, or null on no match. */ @Nullable public Object getAttr(String attrName) { Integer idx = ruleClass.getAttributeIndex(attrName); return idx != null ? attributeValues[idx] : null; } /** * {@see #isAttributeValueExplicitlySpecified(String)} */ public boolean isAttributeValueExplicitlySpecified(Attribute attribute) { return isAttributeValueExplicitlySpecified(attribute.getName()); } /** * Returns true iff the value of the specified attribute is explicitly set in the BUILD file. * This returns true also if the value explicity specified in the BUILD file is the same as the * attribute's default value. In addition, this method return false if the rule has no attribute * with the given name. */ public boolean isAttributeValueExplicitlySpecified(String attributeName) { Integer idx = ruleClass.getAttributeIndex(attributeName); return idx != null && getExplicit(idx); } /** * Returns the number of elements of state[] currently used to store * indices of "explicitly set" attributes. */ private int explicitCount() { return 0xff & state[0]; } private boolean getExplicit(int index) { int n = explicitCount(); for (int i = 1; i <= n; ++i) { if ((0xff & state[i]) == index + 1) { return true; } } return false; } private int getLocationIndex(int index) { int n = explicitCount(); for (int i = state.length - 1; i > n; --i) { if ((0xff & state[i]) == index + 1) { return state.length - 1 - i; } } return -1; } private void setExplicit(int index) { if (getExplicit(index)) { return; } ensureSpace(); int n = explicitCount() + 1; state[0] = (byte) n; state[n] = (byte) (index + 1); } private int addLocationIndex(int index) { ensureSpace(); for (int i = state.length - 1; ; --i) { if (i <= explicitCount()) { throw new AssertionError("ensureSpace() did not insert a zero"); } if (state[i] == 0) { state[i] = (byte) (index + 1); return state.length - 1 - i; } } } /** * Ensures that the state[n] byte is equal to the sentinel value 0, so there is room for another * attribute's explicit bit to be set, or for an attribute's location to be set. */ private void ensureSpace() { int n = explicitCount() + 1; if (n < state.length && state[n] == 0) { return; } // Grow up to the next multiple of eight bytes, as the object will be // aligned to eight bytes anyway. Insert zeros between the two lists. byte[] newState = new byte[(state.length | 7) + 1]; // Copy stored explicit attributes to the beginning of the array. System.arraycopy(state, 0, newState, 0, n); // Copy stored attribute locations to the *end* of the array. int oldLocations = state.length - n; System.arraycopy(state, n, newState, newState.length - oldLocations, oldLocations); state = newState; } /** * Returns the location of the attribute definition for this rule, or null if not found. */ public Location getAttributeLocation(String attrName) { Integer idx = ruleClass.getAttributeIndex(attrName); int locationIndex = idx != null ? getLocationIndex(idx) : -1; return locationIndex >= 0 ? attributeLocations[locationIndex] : null; } Object getAttributeValue(int index) { return attributeValues[index]; } void setAttributeValue(Attribute attribute, Object value, boolean explicit) { String name = attribute.getName(); Integer index = ruleClass.getAttributeIndex(name); if (!explicit && getExplicit(index)) { throw new IllegalArgumentException("attribute " + name + " already explicitly set"); } attributeValues[index] = value; if (explicit) { setExplicit(index); } } // This sets the attribute "explicitly" as if it came from the BUILD file. // At present, the sole use of this is for the test_suite.$implicit_tests // attribute, which is synthesized during package loading. We do want to // consider that "explicitly set" so that it appears in query output. void setAttributeValueByName(String attrName, Object value) { setAttributeValue(ruleClass.getAttributeByName(attrName), value, true); } void setAttributeLocation(int attrIndex, Location location) { int locationIndex = getLocationIndex(attrIndex); if (locationIndex >= 0) { throw new IllegalArgumentException("already have a location for attribute " + ruleClass.getAttribute(attrIndex).getName() + ": " + attributeLocations[locationIndex]); } locationIndex = addLocationIndex(attrIndex); if (locationIndex >= attributeLocations.length) { // Grow by two references, as the object will be aligned to eight bytes anyway. attributeLocations = Arrays.copyOf(attributeLocations, attributeLocations.length + 2); } attributeLocations[locationIndex] = location; } @VisibleForTesting void setAttributeLocation(Attribute attribute, Location location) { Integer index = ruleClass.getAttributeIndex(attribute.getName()); setAttributeLocation(index, location); } public static final Function<RuleClass, AttributeContainer> ATTRIBUTE_CONTAINER_FACTORY = new Function<RuleClass, AttributeContainer>() { @Override public AttributeContainer apply(RuleClass ruleClass) { return new AttributeContainer(ruleClass); } }; }