/* * Copyright 2016 the original author or authors. * * 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 org.gradle.api.internal.attributes; import com.google.common.collect.Sets; import org.gradle.api.attributes.Attribute; import org.gradle.api.attributes.AttributeContainer; import org.gradle.internal.Cast; import java.util.Collections; import java.util.Comparator; import java.util.Map; import java.util.Set; import java.util.TreeMap; public final class ImmutableAttributes implements AttributeContainerInternal { public final static ImmutableAttributes EMPTY = new ImmutableAttributes(null); private static final Comparator<Attribute<?>> ATTRIBUTE_NAME_COMPARATOR = new Comparator<Attribute<?>>() { @Override public int compare(Attribute<?> o1, Attribute<?> o2) { return o1.getName().compareTo(o2.getName()); } }; final ImmutableAttributes parent; final Attribute<?> attribute; final Object value; private final int hashCode; private final int size; // the builder here is a performance optimization. It avoids trashing a lot // of object that would be otherwise built again and again final DefaultImmutableAttributesFactory.Builder builder; // cache keyset in case we need it again private Set<Attribute<?>> keySet; public static ImmutableAttributes of(AttributeContainer attributes) { return ((AttributeContainerInternal) attributes).asImmutable(); } ImmutableAttributes(ImmutableAttributesFactory owner) { this.builder = owner != null ? owner.builder(this) : null; this.parent = null; this.attribute = null; this.value = null; this.hashCode = 0; this.size = 0; } ImmutableAttributes(ImmutableAttributes parent, Attribute<?> key, Object value, ImmutableAttributesFactory owner) { this.parent = parent; this.attribute = key; this.value = value; this.builder = owner != null ? owner.builder(this) : null; int hashCode = parent.hashCode(); hashCode = 31 * hashCode + attribute.hashCode(); hashCode = 31 * hashCode + value.hashCode(); this.hashCode = hashCode; this.size = parent.size + 1; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ImmutableAttributes that = (ImmutableAttributes) o; if (size != that.size) { return false; } ImmutableAttributes cur = this; while (cur.value != null) { if (!cur.value.equals(that.getAttribute(cur.attribute))) { return false; } cur = cur.parent; } return true; } @Override public int hashCode() { return hashCode; } @Override public Set<Attribute<?>> keySet() { if (parent == null) { return Collections.emptySet(); } if (keySet == null) { keySet = Sets.union(Collections.singleton(attribute), parent.keySet()); } return keySet; } @Override public <T> AttributeContainer attribute(Attribute<T> key, T value) { throw new UnsupportedOperationException("Mutation of attributes is not allowed"); } @Override public <T> T getAttribute(Attribute<T> key) { if (key.equals(attribute)) { return Cast.uncheckedCast(value); } if (parent != null) { return parent.getAttribute(key); } return null; } @Override public boolean isEmpty() { return attribute == null; } @Override public boolean contains(Attribute<?> key) { return key.equals(attribute) || parent != null && parent.contains(key); } @Override public ImmutableAttributes asImmutable() { return this; } @Override public AttributeContainerInternal copy() { return this; } @Override public AttributeContainer getAttributes() { return this; } @Override public String toString() { Map<Attribute<?>, Object> sorted = new TreeMap<Attribute<?>, Object>(ATTRIBUTE_NAME_COMPARATOR); ImmutableAttributes node = this; while (node != null) { if (node.attribute != null) { sorted.put(node.attribute, node.value); } node = node.parent; } return sorted.toString(); } }