/*
* 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.manage.schema.extract;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.gradle.api.Action;
import org.gradle.internal.reflect.PropertyAccessorType;
import org.gradle.model.internal.manage.schema.ModelProperty;
import org.gradle.model.internal.manage.schema.ModelSchema;
import org.gradle.model.internal.method.WeaklyTypeReferencingMethod;
import org.gradle.model.internal.type.ModelType;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
public abstract class StructSchemaExtractionStrategySupport implements ModelSchemaExtractionStrategy {
private final ModelSchemaAspectExtractor aspectExtractor;
protected StructSchemaExtractionStrategySupport(ModelSchemaAspectExtractor aspectExtractor) {
this.aspectExtractor = aspectExtractor;
}
@Override
public <R> void extract(final ModelSchemaExtractionContext<R> extractionContext) {
ModelType<R> type = extractionContext.getType();
if (!isTarget(type)) {
return;
}
CandidateMethods candidateMethods = ModelSchemaUtils.getCandidateMethods(type.getRawClass());
Iterable<ModelPropertyExtractionContext> candidateProperties = selectProperties(extractionContext, candidateMethods);
List<ModelPropertyExtractionResult<?>> extractedProperties = extractProperties(candidateProperties);
List<ModelSchemaAspect> aspects = aspectExtractor.extract(extractionContext, extractedProperties);
Set<WeaklyTypeReferencingMethod<?, ?>> nonPropertyMethods = getNonPropertyMethods(candidateMethods, extractedProperties);
Iterable<ModelProperty<?>> properties = Iterables.transform(extractedProperties, new Function<ModelPropertyExtractionResult<?>, ModelProperty<?>>() {
@Override
public ModelProperty<?> apply(ModelPropertyExtractionResult<?> propertyResult) {
return propertyResult.getProperty();
}
});
ModelSchema<R> schema = createSchema(extractionContext, properties, nonPropertyMethods, aspects);
for (ModelPropertyExtractionResult<?> propertyResult : extractedProperties) {
toPropertyExtractionContext(extractionContext, propertyResult);
}
extractionContext.found(schema);
}
private Set<WeaklyTypeReferencingMethod<?, ?>> getNonPropertyMethods(CandidateMethods candidateMethods, List<ModelPropertyExtractionResult<?>> extractedProperties) {
Set<Method> nonPropertyMethods = Sets.newLinkedHashSet(Iterables.transform(candidateMethods.allMethods().keySet(), new Function<Wrapper<Method>, Method>() {
@Override
public Method apply(Wrapper<Method> method) {
return method.get();
}
}));
for (ModelPropertyExtractionResult<?> extractedProperty : extractedProperties) {
for (PropertyAccessorExtractionContext accessor : extractedProperty.getAccessors()) {
nonPropertyMethods.removeAll(accessor.getDeclaringMethods());
}
}
return Sets.newLinkedHashSet(Iterables.transform(nonPropertyMethods, new Function<Method, WeaklyTypeReferencingMethod<?, ?>>() {
@Override
public WeaklyTypeReferencingMethod<?, ?> apply(Method method) {
return WeaklyTypeReferencingMethod.of(method);
}
}));
}
protected abstract boolean isTarget(ModelType<?> type);
private Iterable<ModelPropertyExtractionContext> selectProperties(final ModelSchemaExtractionContext<?> context, CandidateMethods candidateMethods) {
Map<String, ModelPropertyExtractionContext> propertiesMap = Maps.newTreeMap();
for (Map.Entry<Wrapper<Method>, Collection<Method>> entry : candidateMethods.allMethods().entrySet()) {
Method method = entry.getKey().get();
PropertyAccessorType propertyAccessorType = PropertyAccessorType.of(method);
Collection<Method> methodsWithEqualSignature = entry.getValue();
if (propertyAccessorType != null) {
String propertyName = propertyAccessorType.propertyNameFor(method);
ModelPropertyExtractionContext propertyContext = propertiesMap.get(propertyName);
if (propertyContext == null) {
propertyContext = new ModelPropertyExtractionContext(propertyName);
propertiesMap.put(propertyName, propertyContext);
}
propertyContext.addAccessor(new PropertyAccessorExtractionContext(propertyAccessorType, methodsWithEqualSignature));
}
}
return Collections2.filter(propertiesMap.values(), new Predicate<ModelPropertyExtractionContext>() {
@Override
public boolean apply(ModelPropertyExtractionContext property) {
return property.isReadable();
}
});
}
private static List<ModelPropertyExtractionResult<?>> extractProperties(Iterable<ModelPropertyExtractionContext> properties) {
ImmutableList.Builder<ModelPropertyExtractionResult<?>> builder = ImmutableList.builder();
for (ModelPropertyExtractionContext propertyContext : properties) {
builder.add(extractProperty(propertyContext));
}
return builder.build();
}
private static ModelPropertyExtractionResult<?> extractProperty(ModelPropertyExtractionContext property) {
ModelType<?> propertyType = determinePropertyType(property.getAccessor(PropertyAccessorType.GET_GETTER));
if (propertyType == null) {
propertyType = determinePropertyType(property.getAccessor(PropertyAccessorType.IS_GETTER));
}
if (propertyType == null) {
propertyType = determinePropertyType(property.getAccessor(PropertyAccessorType.SETTER));
}
return createProperty(propertyType, property);
}
private static ModelType<?> determinePropertyType(PropertyAccessorExtractionContext accessor) {
return accessor == null ? null : ModelType.of(accessor.getAccessorType().propertyTypeFor(accessor.getMostSpecificDeclaration()));
}
private static <P> ModelPropertyExtractionResult<P> createProperty(ModelType<P> propertyType, ModelPropertyExtractionContext propertyContext) {
ImmutableMap.Builder<PropertyAccessorType, WeaklyTypeReferencingMethod<?, ?>> accessors = ImmutableMap.builder();
for (PropertyAccessorExtractionContext accessor : propertyContext.getAccessors()) {
WeaklyTypeReferencingMethod<?, ?> accessorMethod = WeaklyTypeReferencingMethod.of(accessor.getMostSpecificDeclaration());
accessors.put(accessor.getAccessorType(), accessorMethod);
}
ModelProperty<P> property = new ModelProperty<P>(
propertyType,
propertyContext.getPropertyName(),
propertyContext.getDeclaredBy(),
accessors.build()
);
return new ModelPropertyExtractionResult<P>(property, propertyContext.getAccessors());
}
private static <R, P> void toPropertyExtractionContext(ModelSchemaExtractionContext<R> parentContext, ModelPropertyExtractionResult<P> propertyResult) {
ModelProperty<P> property = propertyResult.getProperty();
String propertyDescription = propertyDescription(parentContext, property);
parentContext.child(property.getType(), propertyDescription, attachSchema(property));
}
private static <P> Action<? super ModelSchema<P>> attachSchema(final ModelProperty<P> property) {
return new Action<ModelSchema<P>>() {
@Override
public void execute(ModelSchema<P> propertySchema) {
property.setSchema(propertySchema);
}
};
}
private static String propertyDescription(ModelSchemaExtractionContext<?> parentContext, ModelProperty<?> property) {
if (property.getDeclaredBy().size() == 1 && property.getDeclaredBy().contains(parentContext.getType())) {
return String.format("property '%s'", property.getName());
} else {
ImmutableSortedSet<String> declaredBy = ImmutableSortedSet.copyOf(Iterables.transform(property.getDeclaredBy(), Functions.toStringFunction()));
return String.format("property '%s' declared by %s", property.getName(), Joiner.on(", ").join(declaredBy));
}
}
protected abstract <R> ModelSchema<R> createSchema(ModelSchemaExtractionContext<R> extractionContext, Iterable<ModelProperty<?>> properties, Set<WeaklyTypeReferencingMethod<?, ?>> nonPropertyMethods, Iterable<ModelSchemaAspect> aspects);
}