/* * Copyright 2014 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.model.internal.manage.projection; import com.google.common.base.Optional; import groovy.lang.Closure; import org.gradle.api.internal.ClosureBackedAction; import org.gradle.internal.Cast; import org.gradle.internal.typeconversion.TypeConverter; import org.gradle.model.internal.core.DefaultModelViewState; import org.gradle.model.internal.core.ModelPath; import org.gradle.model.internal.core.ModelView; import org.gradle.model.internal.core.MutableModelNode; import org.gradle.model.internal.core.TypeCompatibilityModelProjectionSupport; import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor; import org.gradle.model.internal.manage.binding.StructBindings; import org.gradle.model.internal.manage.instance.ManagedInstance; import org.gradle.model.internal.manage.instance.ManagedProxyFactory; import org.gradle.model.internal.manage.instance.ModelElementState; import org.gradle.model.internal.manage.schema.ManagedImplSchema; import org.gradle.model.internal.manage.schema.ModelProperty; import org.gradle.model.internal.manage.schema.ModelSchema; import org.gradle.model.internal.manage.schema.ScalarCollectionSchema; import org.gradle.model.internal.manage.schema.StructSchema; import org.gradle.model.internal.manage.schema.extract.ScalarCollectionModelView; import org.gradle.model.internal.type.ModelType; import java.util.Collection; import java.util.HashMap; import java.util.Map; import static org.gradle.internal.reflect.JavaReflectionUtil.hasDefaultToString; public class ManagedModelProjection<M> extends TypeCompatibilityModelProjectionSupport<M> { private static final ModelType<? extends Collection<?>> COLLECTION_MODEL_TYPE = new ModelType<Collection<?>>() { }; private final StructSchema<M> schema; private final StructBindings<?> bindings; private final ManagedProxyFactory proxyFactory; private final TypeConverter typeConverter; public ManagedModelProjection(StructSchema<M> schema, StructBindings<?> bindings, ManagedProxyFactory proxyFactory, TypeConverter typeConverter) { super(schema.getType()); this.schema = schema; this.bindings = bindings; this.proxyFactory = proxyFactory; this.typeConverter = typeConverter; } @Override protected ModelView<M> toView(final MutableModelNode modelNode, final ModelRuleDescriptor ruleDescriptor, final boolean writable) { final DefaultModelViewState state = new DefaultModelViewState(modelNode.getPath(), getType(), ruleDescriptor, writable, true); return new ModelView<M>() { private final Map<String, Object> propertyViews = new HashMap<String, Object>(); @Override public ModelPath getPath() { return modelNode.getPath(); } public ModelType<M> getType() { return ManagedModelProjection.this.getType(); } public M getInstance() { return proxyFactory.createProxy(new State(), schema, bindings, typeConverter); } public void close() { state.close(); } class State implements ModelElementState { @Override public MutableModelNode getBackingNode() { return modelNode; } @Override public String getDisplayName() { return getType().getDisplayName() + " '" + modelNode.getPath() + "'"; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj == null || obj.getClass() != getClass()) { return false; } State other = Cast.uncheckedCast(obj); return modelNode == other.getBackingNode(); } @Override public int hashCode() { return modelNode.hashCode(); } @Override public Object get(String name) { state.assertCanReadChildren(); if (propertyViews.containsKey(name)) { return propertyViews.get(name); } ModelProperty<?> property = schema.getProperty(name); Object value = doGet(property, name); propertyViews.put(name, value); return value; } private <T> T doGet(ModelProperty<T> property, String propertyName) { ModelType<T> propertyType = property.getType(); // TODO we are relying on the registration having established these links, we should be checking MutableModelNode propertyNode = modelNode.getLink(propertyName); propertyNode.ensureUsable(); ModelView<? extends T> modelView; if (writable) { modelView = propertyNode.asMutable(propertyType, ruleDescriptor); if (state.isClosed()) { modelView.close(); } } else { modelView = propertyNode.asImmutable(propertyType, ruleDescriptor); } return modelView.getInstance(); } @Override public void apply(String name, Closure<?> action) { state.assertCanMutate(); ClosureBackedAction.execute(get(name), action); } @Override public void set(String name, Object value) { state.assertCanMutate(); ModelProperty<?> property = schema.getProperty(name); value = doSet(name, value, property); propertyViews.put(name, value); } private <T> Object doSet(String name, Object value, ModelProperty<T> property) { ModelSchema<T> propertySchema = property.getSchema(); // TODO we are relying on the registration having established these links, we should be checking MutableModelNode propertyNode = modelNode.getLink(name); propertyNode.ensureUsable(); if (propertySchema instanceof ManagedImplSchema) { if (propertySchema instanceof ScalarCollectionSchema) { ModelView<? extends Collection<?>> modelView = propertyNode.asMutable(COLLECTION_MODEL_TYPE, ruleDescriptor); return ((ScalarCollectionModelView<?, ? extends Collection<?>>) modelView).setValue(value); } else if (value == null) { propertyNode.setTarget(null); } else if (value instanceof ManagedInstance) { ManagedInstance managedInstance = (ManagedInstance) value; MutableModelNode targetNode = managedInstance.getBackingNode(); propertyNode.setTarget(targetNode); } else { throw new IllegalArgumentException(String.format("Only managed model instances can be set as property '%s' of class '%s'", name, getType())); } } else { T castValue = Cast.uncheckedCast(value); propertyNode.setPrivateData(property.getType(), castValue); } return value; } } }; } @Override public Optional<String> getValueDescription(MutableModelNode modelNode) { Object instance = modelNode.asImmutable(ModelType.untyped(), null).getInstance(); if (instance == null || hasDefaultToString(instance)) { return Optional.absent(); } return Optional.of(toStringValueDescription(instance)); } }