/*
* Copyright 2015 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.core;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.gradle.api.GradleException;
import org.gradle.model.ModelMap;
import org.gradle.model.ModelSet;
import org.gradle.model.internal.manage.schema.CollectionSchema;
import org.gradle.model.internal.manage.schema.ModelSchemaStore;
import org.gradle.model.internal.type.ModelType;
import java.util.List;
import java.util.Set;
/**
* Thrown when a NodeInitializer can not be found for a given type or when the type is not managed and can not be constructed.
*/
public class ModelTypeInitializationException extends GradleException {
private static final String MANAGED_TYPE_DESCRIPTION = "A managed type (annotated with @Managed)";
private static final String UNMANAGED_PROPERTY_DESCRIPTION = "An unmanaged property (i.e. annotated with @Unmanaged)";
public ModelTypeInitializationException(NodeInitializerContext<?> context,
ModelSchemaStore schemaStore,
Iterable<? extends ModelType<?>> scalarTypes,
Iterable<? extends ModelType<?>> constructibleTypes) {
super(toMessage(context, schemaStore, scalarTypes, constructibleTypes));
}
private static <T> String toMessage(NodeInitializerContext<T> context,
ModelSchemaStore schemaStore,
Iterable<? extends ModelType<?>> scalarTypes,
Iterable<? extends ModelType<?>> constructibleTypes) {
Optional<? extends NodeInitializerContext.PropertyContext> propertyContextOptional = context.getPropertyContextOptional();
StringBuilder s = new StringBuilder();
if (propertyContextOptional.isPresent()) {
NodeInitializerContext.PropertyContext propertyContext = propertyContextOptional.get();
s.append(String.format("A model element of type: '%s' can not be constructed.%n", propertyContext.getDeclaringType().getName()));
String propertyName = propertyContext.getName();
ModelType<?> propertyType = propertyContext.getType();
if (isManagedCollection(propertyType)) {
s.append(String.format("Its property '%s %s' is not a valid managed collection%n", propertyType.getName(), propertyName));
CollectionSchema<?, ?> schema = (CollectionSchema) schemaStore.getSchema(propertyType);
s.append(String.format("A managed collection can not contain '%s's%n", schema.getElementType()));
appendManagedCollections(s, 1, constructibleTypes);
} else if (isScalarCollection(propertyType, propertyContext.isDeclaredAsHavingUnmanagedType())) {
ModelType<?> innerType = propertyType.getTypeVariables().get(0);
s.append(String.format("Its property '%s %s' is not a valid scalar collection%n", propertyType.getName(), propertyName));
s.append(String.format("A scalar collection can not contain '%s's%n", innerType));
s.append(explainScalarCollections(scalarTypes));
} else {
s.append(String.format("Its property '%s %s' can not be constructed%n", propertyType.getName(), propertyName));
s.append(String.format("It must be one of:%n"));
s.append(String.format(" - %s%n", MANAGED_TYPE_DESCRIPTION));
s.append(" - A managed collection. ");
appendManagedCollections(s, 1, constructibleTypes);
s.append(String.format("%n - A scalar collection. %s%n - %s", explainScalarCollections(scalarTypes), UNMANAGED_PROPERTY_DESCRIPTION));
maybeAppendConstructibles(s, constructibleTypes, 1);
}
} else {
s.append(String.format("A model element of type: '%s' can not be constructed.%n", context.getModelType().getName()));
s.append(String.format("It must be one of:%n"));
s.append(String.format(" - %s", MANAGED_TYPE_DESCRIPTION));
maybeAppendConstructibles(s, constructibleTypes, 1);
}
return s.toString();
}
private static String explainScalarCollections(Iterable<? extends ModelType<?>> scalarTypes) {
return String.format("A valid scalar collection takes the form of List<T> or Set<T> where 'T' is one of (%s)", describe(scalarTypes));
}
private static String appendManagedCollections(StringBuilder s, int pad, Iterable<? extends ModelType<?>> constructibleTypes) {
s.append(String.format("A valid managed collection takes the form of ModelSet<T> or ModelMap<T> where 'T' is:%n - %s", MANAGED_TYPE_DESCRIPTION));
maybeAppendConstructibles(s, constructibleTypes, pad + 1);
return s.toString();
}
private static void maybeAppendConstructibles(StringBuilder s, Iterable<? extends ModelType<?>> constructibleTypes, int pad) {
if (!Iterables.isEmpty(constructibleTypes)) {
String padding = pad(pad);
s.append(String.format("%n%s- or a type which Gradle is capable of constructing:", padding));
for (ModelType<?> modelType : constructibleTypes) {
s.append(String.format("%n %s- %s", padding, modelType.getName()));
}
}
}
private static String pad(int padding) {
return Strings.padStart("", padding * 4, ' ');
}
private static boolean isScalarCollection(ModelType<?> propertyType, boolean declaredAsHavingUnmanagedType) {
Class<?> concreteClass = propertyType.getConcreteClass();
return (concreteClass.equals(List.class) || concreteClass.equals(Set.class))
&& !declaredAsHavingUnmanagedType;
}
private static String describe(Iterable<? extends ModelType<?>> types) {
return Joiner.on(", ").join(ImmutableSet.copyOf(Iterables.transform(types, new Function<ModelType<?>, String>() {
@Override
public String apply(ModelType<?> input) {
return input.getDisplayName();
}
})));
}
private static boolean isManagedCollection(ModelType<?> type) {
Class<?> concreteClass = type.getConcreteClass();
return concreteClass.equals(ModelMap.class) || concreteClass.equals(ModelSet.class);
}
}