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

org.gradle.api.internal.provider.DefaultMapProperty Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2018 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.provider;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.gradle.api.Action;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Provider;
import org.gradle.internal.Cast;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class DefaultMapProperty extends AbstractProperty, MapSupplier> implements MapProperty, MapProviderInternal {
    private static final String NULL_KEY_FORBIDDEN_MESSAGE = String.format("Cannot add an entry with a null key to a property of type %s.", Map.class.getSimpleName());
    private static final String NULL_VALUE_FORBIDDEN_MESSAGE = String.format("Cannot add an entry with a null value to a property of type %s.", Map.class.getSimpleName());

    private static final MapSupplier NO_VALUE = new NoValueSupplier<>(Value.missing());

    private final Class keyType;
    private final Class valueType;
    private final ValueCollector keyCollector;
    private final MapEntryCollector entryCollector;
    private MapSupplier defaultValue = emptySupplier();

    public DefaultMapProperty(PropertyHost propertyHost, Class keyType, Class valueType) {
        super(propertyHost);
        this.keyType = keyType;
        this.valueType = valueType;
        keyCollector = new ValidatingValueCollector<>(Set.class, keyType, ValueSanitizers.forType(keyType));
        entryCollector = new ValidatingMapEntryCollector<>(keyType, valueType, ValueSanitizers.forType(keyType), ValueSanitizers.forType(valueType));
        init(defaultValue, noValueSupplier());
    }

    private MapSupplier emptySupplier() {
        return new EmptySupplier();
    }

    private MapSupplier noValueSupplier() {
        return Cast.uncheckedCast(NO_VALUE);
    }

    @Nullable
    @Override
    @SuppressWarnings("unchecked")
    public Class> getType() {
        return (Class) Map.class;
    }

    @Override
    public Class getKeyType() {
        return keyType;
    }

    @Override
    public Class getValueType() {
        return valueType;
    }

    @Override
    public Class publicType() {
        return MapProperty.class;
    }

    @Override
    public int getFactoryId() {
        return ManagedFactories.MapPropertyManagedFactory.FACTORY_ID;
    }

    @Override
    public Provider getting(final K key) {
        return new EntryProvider(key);
    }

    @Override
    @SuppressWarnings("unchecked")
    public MapProperty empty() {
        setSupplier(emptySupplier());
        return this;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void setFromAnyValue(@Nullable Object object) {
        if (object == null || object instanceof Map) {
            set((Map) object);
        } else if (object instanceof Provider) {
            set((Provider) object);
        } else {
            throw new IllegalArgumentException(String.format(
                "Cannot set the value of a property of type %s using an instance of type %s.", Map.class.getName(), object.getClass().getName()));
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void set(@Nullable Map entries) {
        if (entries == null) {
            discardValue();
            defaultValue = noValueSupplier();
        } else {
            setSupplier(new CollectingSupplier(new MapCollectors.EntriesFromMap<>(entries)));
        }
    }

    @Override
    public void set(Provider> provider) {
        ProviderInternal> p = checkMapProvider(provider);
        setSupplier(new CollectingSupplier(new MapCollectors.EntriesFromMapProvider<>(p)));
    }

    @Override
    public MapProperty value(@Nullable Map entries) {
        set(entries);
        return this;
    }

    @Override
    public MapProperty value(Provider> provider) {
        set(provider);
        return this;
    }

    @Override
    public void put(K key, V value) {
        Preconditions.checkNotNull(key, NULL_KEY_FORBIDDEN_MESSAGE);
        Preconditions.checkNotNull(value, NULL_VALUE_FORBIDDEN_MESSAGE);
        addCollector(new MapCollectors.SingleEntry<>(key, value));
    }

    @Override
    public void put(K key, Provider providerOfValue) {
        Preconditions.checkNotNull(key, NULL_KEY_FORBIDDEN_MESSAGE);
        Preconditions.checkNotNull(providerOfValue, NULL_VALUE_FORBIDDEN_MESSAGE);
        ProviderInternal p = Providers.internal(providerOfValue);
        if (p.getType() != null && !valueType.isAssignableFrom(p.getType())) {
            throw new IllegalArgumentException(String.format("Cannot add an entry to a property of type %s with values of type %s using a provider of type %s.",
                Map.class.getName(), valueType.getName(), p.getType().getName()));
        }
        addCollector(new MapCollectors.EntryWithValueFromProvider<>(key, p));
    }

    @Override
    public void putAll(Map entries) {
        addCollector(new MapCollectors.EntriesFromMap<>(entries));
    }

    @Override
    public void putAll(Provider> provider) {
        ProviderInternal> p = checkMapProvider(provider);
        addCollector(new MapCollectors.EntriesFromMapProvider<>(p));
    }

    private void addCollector(MapCollector collector) {
        assertCanMutate();
        setSupplier(getExplicitValue(defaultValue).plus(collector));
    }

    @SuppressWarnings("unchecked")
    private ProviderInternal> checkMapProvider(@Nullable Provider> provider) {
        if (provider == null) {
            throw new IllegalArgumentException("Cannot set the value of a property using a null provider.");
        }
        ProviderInternal> p = Providers.internal(provider);
        if (p.getType() != null && !Map.class.isAssignableFrom(p.getType())) {
            throw new IllegalArgumentException(String.format("Cannot set the value of a property of type %s using a provider of type %s.",
                Map.class.getName(), p.getType().getName()));
        }
        if (p instanceof MapProviderInternal) {
            Class providerKeyType = ((MapProviderInternal) p).getKeyType();
            Class providerValueType = ((MapProviderInternal) p).getValueType();
            if (!keyType.isAssignableFrom(providerKeyType) || !valueType.isAssignableFrom(providerValueType)) {
                throw new IllegalArgumentException(String.format("Cannot set the value of a property of type %s with key type %s and value type %s " +
                        "using a provider with key type %s and value type %s.", Map.class.getName(), keyType.getName(), valueType.getName(),
                    providerKeyType.getName(), providerValueType.getName()));
            }
        }
        return p;
    }

    @Override
    public MapProperty convention(@Nullable Map value) {
        if (value == null) {
            setConvention(noValueSupplier());
        } else {
            setConvention(new CollectingSupplier(new MapCollectors.EntriesFromMap<>(value)));
        }
        return this;
    }

    @Override
    public MapProperty convention(Provider> valueProvider) {
        setConvention(new CollectingSupplier(new MapCollectors.EntriesFromMapProvider<>(Providers.internal(valueProvider))));
        return this;
    }

    public void fromState(ExecutionTimeValue> value) {
        if (value.isMissing()) {
            setSupplier(noValueSupplier());
        } else if (value.isFixedValue()) {
            setSupplier(new FixedSuppler<>(Cast.uncheckedNonnullCast(value.getFixedValue())));
        } else {
            setSupplier(new CollectingSupplier(new MapCollectors.EntriesFromMapProvider<>(value.getChangingValue())));
        }
    }

    @Override
    public Provider> keySet() {
        return new KeySetProvider();
    }

    @Override
    protected String describeContents() {
        return String.format("Map(%s->%s, %s)", keyType.getSimpleName().toLowerCase(), valueType.getSimpleName(), getSupplier().toString());
    }

    @Override
    protected Value> calculateValueFrom(MapSupplier value, ValueConsumer consumer) {
        return value.calculateValue(consumer);
    }

    @Override
    protected MapSupplier finalValue(MapSupplier value, ValueConsumer consumer) {
        Value> result = value.calculateValue(consumer);
        if (!result.isMissing()) {
            Map entries = result.get();
            return new FixedSuppler<>(entries);
        } else if (result.getPathToOrigin().isEmpty()) {
            return noValueSupplier();
        } else {
            return new NoValueSupplier<>(result);
        }
    }

    @Override
    protected ExecutionTimeValue> calculateOwnExecutionTimeValue(MapSupplier value) {
        return value.calculateOwnExecutionTimeValue();
    }

    private class EntryProvider extends AbstractMinimalProvider {
        private final K key;

        public EntryProvider(K key) {
            this.key = key;
        }

        @Nullable
        @Override
        public Class getType() {
            return valueType;
        }

        @Override
        protected Value calculateOwnValue(ValueConsumer consumer) {
            Value> result = DefaultMapProperty.this.calculateOwnValue(consumer);
            if (result.isMissing()) {
                return result.asType();
            }
            return Value.ofNullable(result.get().get(key));
        }
    }

    private class KeySetProvider extends AbstractMinimalProvider> {
        @Nullable
        @Override
        @SuppressWarnings("unchecked")
        public Class> getType() {
            return (Class) Set.class;
        }

        @Override
        protected Value> calculateOwnValue(ValueConsumer consumer) {
            beforeRead(consumer);
            return getSupplier().calculateKeys(consumer);
        }
    }

    private static class NoValueSupplier implements MapSupplier {
        private final Value> value;

        public NoValueSupplier(Value> value) {
            this.value = value.asType();
            assert value.isMissing();
        }

        @Override
        public boolean calculatePresence(ValueConsumer consumer) {
            return false;
        }

        @Override
        public Value> calculateValue(ValueConsumer consumer) {
            return value;
        }

        @Override
        public Value> calculateKeys(ValueConsumer consumer) {
            return value.asType();
        }

        @Override
        public MapSupplier plus(MapCollector collector) {
            // nothing + something = nothing
            return this;
        }

        @Override
        public ExecutionTimeValue> calculateOwnExecutionTimeValue() {
            return ExecutionTimeValue.missing();
        }

        @Override
        public ValueProducer getProducer() {
            return ValueProducer.unknown();
        }
    }

    private class EmptySupplier implements MapSupplier {
        @Override
        public boolean calculatePresence(ValueConsumer consumer) {
            return true;
        }

        @Override
        public Value> calculateValue(ValueConsumer consumer) {
            return Value.of(ImmutableMap.of());
        }

        @Override
        public Value> calculateKeys(ValueConsumer consumer) {
            return Value.of(ImmutableSet.of());
        }

        @Override
        public MapSupplier plus(MapCollector collector) {
            // empty + something = something
            return new CollectingSupplier(collector);
        }

        @Override
        public ExecutionTimeValue> calculateOwnExecutionTimeValue() {
            return ExecutionTimeValue.fixedValue(ImmutableMap.of());
        }

        @Override
        public ValueProducer getProducer() {
            return ValueProducer.noProducer();
        }
    }

    private static class FixedSuppler implements MapSupplier {
        private final Map entries;

        public FixedSuppler(Map entries) {
            this.entries = entries;
        }

        @Override
        public boolean calculatePresence(ValueConsumer consumer) {
            return true;
        }

        @Override
        public Value> calculateValue(ValueConsumer consumer) {
            return Value.of(entries);
        }

        @Override
        public Value> calculateKeys(ValueConsumer consumer) {
            return Value.of(entries.keySet());
        }

        @Override
        public MapSupplier plus(MapCollector collector) {
            throw new UnsupportedOperationException();
        }

        @Override
        public ExecutionTimeValue> calculateOwnExecutionTimeValue() {
            return ExecutionTimeValue.fixedValue(entries);
        }

        @Override
        public ValueProducer getProducer() {
            return ValueProducer.unknown();
        }
    }

    private class CollectingSupplier implements MapSupplier {
        private final MapCollector collector;

        public CollectingSupplier(MapCollector collector) {
            this.collector = collector;
        }

        @Override
        public boolean calculatePresence(ValueConsumer consumer) {
            return collector.calculatePresence(consumer);
        }

        @Override
        public Value> calculateKeys(ValueConsumer consumer) {
            // TODO - don't make a copy when the collector already produces an immutable collection
            ImmutableSet.Builder builder = ImmutableSet.builder();
            Value result = collector.collectKeys(consumer, keyCollector, builder);
            if (result.isMissing()) {
                return result.asType();
            }
            return Value.of(ImmutableSet.copyOf(builder.build()));
        }

        @Override
        public Value> calculateValue(ValueConsumer consumer) {
            // TODO - don't make a copy when the collector already produces an immutable collection
            // Cannot use ImmutableMap.Builder here, as it does not allow multiple entries with the same key, however the contract
            // for MapProperty allows a provider to override the entries of earlier providers and so there can be multiple entries
            // with the same key
            Map entries = new LinkedHashMap<>();
            Value result = collector.collectEntries(consumer, entryCollector, entries);
            if (result.isMissing()) {
                return result.asType();
            }
            return Value.of(ImmutableMap.copyOf(entries));
        }

        @Override
        public MapSupplier plus(MapCollector collector) {
            return new CollectingSupplier(new PlusCollector<>(this.collector, collector));
        }

        @Override
        public ExecutionTimeValue> calculateOwnExecutionTimeValue() {
            List>> values = new ArrayList<>();
            collector.calculateExecutionTimeValue(values::add);
            boolean fixed = true;
            boolean changingContent = false;
            for (ExecutionTimeValue> value : values) {
                if (value.isMissing()) {
                    return ExecutionTimeValue.missing();
                }
                if (value.isChangingValue()) {
                    fixed = false;
                } else if (value.hasChangingContent()) {
                    changingContent = true;
                }
            }
            if (fixed) {
                Map entries = new LinkedHashMap<>();
                for (ExecutionTimeValue> value : values) {
                    entries.putAll(value.getFixedValue());
                }
                ExecutionTimeValue> value = ExecutionTimeValue.fixedValue(ImmutableMap.copyOf(entries));
                if (changingContent) {
                    return value.withChangingContent();
                } else {
                    return value;
                }
            }
            List>> providers = new ArrayList<>();
            for (ExecutionTimeValue> value : values) {
                providers.add(value.toProvider());
            }
            return ExecutionTimeValue.changingValue(new CollectingProvider(providers));
        }

        @Override
        public ValueProducer getProducer() {
            return collector.getProducer();
        }
    }

    private static class CollectingProvider extends AbstractMinimalProvider> {
        private final List>> providers;

        public CollectingProvider(List>> providers) {
            this.providers = providers;
        }

        @Nullable
        @Override
        public Class> getType() {
            return Cast.uncheckedCast(Map.class);
        }

        @Override
        protected Value> calculateOwnValue(ValueConsumer consumer) {
            Map entries = new LinkedHashMap<>();
            for (ProviderInternal> provider : providers) {
                Value> value = provider.calculateValue(consumer);
                if (value.isMissing()) {
                    return Value.missing();
                }
                entries.putAll(value.get());
            }
            return Value.of(ImmutableMap.copyOf(entries));
        }
    }

    private static class PlusCollector implements MapCollector {
        private final MapCollector left;
        private final MapCollector right;

        public PlusCollector(MapCollector left, MapCollector right) {
            this.left = left;
            this.right = right;
        }

        @Override
        public boolean calculatePresence(ValueConsumer consumer) {
            return left.calculatePresence(consumer) && right.calculatePresence(consumer);
        }

        @Override
        public Value collectEntries(ValueConsumer consumer, MapEntryCollector collector, Map dest) {
            Value result = left.collectEntries(consumer, collector, dest);
            if (result.isMissing()) {
                return result;
            }
            return right.collectEntries(consumer, collector, dest);
        }

        @Override
        public Value collectKeys(ValueConsumer consumer, ValueCollector collector, ImmutableCollection.Builder dest) {
            Value result = left.collectKeys(consumer, collector, dest);
            if (result.isMissing()) {
                return result;
            }
            return right.collectKeys(consumer, collector, dest);
        }

        @Override
        public void calculateExecutionTimeValue(Action>> visitor) {
            left.calculateExecutionTimeValue(visitor);
            right.calculateExecutionTimeValue(visitor);
        }

        @Override
        public ValueProducer getProducer() {
            return left.getProducer().plus(right.getProducer());
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy