/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* 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 com.asakusafw.operator.dmdl;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import com.asakusafw.operator.CompileEnvironment;
import com.asakusafw.operator.Constants;
import com.asakusafw.operator.DataModelMirrorRepository;
import com.asakusafw.operator.description.ClassDescription;
import com.asakusafw.operator.model.ConcreteDataModelMirror;
import com.asakusafw.operator.model.DataModelMirror;
import com.asakusafw.operator.model.JavaName;
import com.asakusafw.operator.model.PartialDataModelMirror;
import com.asakusafw.operator.model.PropertyMirror;
import com.asakusafw.operator.model.PropertyMirrorCollector;
import com.asakusafw.operator.util.AnnotationHelper;
/**
* Implementation of {@link DataModelMirrorRepository} for DMDL.
*/
public class DmdlDataModelMirrorRepository implements DataModelMirrorRepository, PropertyMirrorCollector {
private static final ClassDescription TYPE_DATA_MODEL =
new ClassDescription("com.asakusafw.runtime.model.DataModel"); //$NON-NLS-1$
private static final ClassDescription TYPE_DATA_MODEL_KIND =
new ClassDescription("com.asakusafw.runtime.model.DataModelKind"); //$NON-NLS-1$
private static final String MEMBER_DATA_MODEL_KIND = "value"; //$NON-NLS-1$
private static final String SYMBOL_KIND = "DMDL"; //$NON-NLS-1$
@Override
public DataModelMirror load(CompileEnvironment environment, TypeMirror type) {
Objects.requireNonNull(environment, "environment must not be null"); //$NON-NLS-1$
Objects.requireNonNull(type, "type must not be null"); //$NON-NLS-1$
if (isConcrete(environment, type)) {
return new ConcreteDataModelMirror(environment, (DeclaredType) type, this);
} else if (isPartial(environment, type)) {
return new PartialDataModelMirror(environment, (TypeVariable) type, this);
}
return null;
}
private boolean isConcrete(CompileEnvironment environment, TypeMirror type) {
assert environment != null;
assert type != null;
if (type.getKind() != TypeKind.DECLARED) {
return false;
}
if (isKindMatched(environment, type) == false) {
return false;
}
TypeMirror datamodel = environment.findDeclaredType(TYPE_DATA_MODEL);
return environment.getProcessingEnvironment().getTypeUtils().isSubtype(type, datamodel);
}
private boolean isPartial(CompileEnvironment environment, TypeMirror type) {
assert environment != null;
assert type != null;
if (type.getKind() != TypeKind.TYPEVAR) {
return false;
}
TypeVariable var = (TypeVariable) type;
TypeParameterElement parameter = (TypeParameterElement) var.asElement();
if (hasKindMatched(environment, parameter) == false) {
return false;
}
Element parent = parameter.getEnclosingElement();
// MEMO Eclipse JDT returns null for "TypeParameterElement.enclosingElement."
if (parent == null) {
return true;
}
return isOperatorSource(environment, parent);
}
private boolean isKindMatched(CompileEnvironment environment, TypeMirror type) {
assert environment != null;
assert type != null;
TypeElement element = (TypeElement) environment.getProcessingEnvironment().getTypeUtils().asElement(type);
TypeElement annotation = environment.findTypeElement(TYPE_DATA_MODEL_KIND);
AnnotationMirror mirror = AnnotationHelper.findAnnotation(environment, annotation, element);
if (mirror == null) {
return false;
}
AnnotationValue value = AnnotationHelper.getValue(environment, mirror, MEMBER_DATA_MODEL_KIND);
return value.getValue().equals(SYMBOL_KIND);
}
private boolean hasKindMatched(CompileEnvironment environment, TypeParameterElement parameter) {
assert environment != null;
assert parameter != null;
for (TypeMirror bound : parameter.getBounds()) {
if (isKindMatched(environment, bound)) {
return true;
}
}
return false;
}
private boolean isOperatorSource(CompileEnvironment environment, Element element) {
assert environment != null;
assert element != null;
if (element.getKind() == ElementKind.METHOD) {
return true;
}
if (element.getKind() == ElementKind.CLASS) {
Types typeUtils = environment.getProcessingEnvironment().getTypeUtils();
DeclaredType type = typeUtils.getDeclaredType((TypeElement) element);
DeclaredType desc = environment.findDeclaredType(Constants.TYPE_FLOW_DESCRIPTION);
return typeUtils.isSubtype(type, desc);
}
return false;
}
@Override
public Set<PropertyMirror> collect(
CompileEnvironment environment,
TypeMirror targetType,
List<TypeElement> upperBounds) {
Objects.requireNonNull(environment, "environment must not be null"); //$NON-NLS-1$
Objects.requireNonNull(targetType, "targetType must not be null"); //$NON-NLS-1$
Objects.requireNonNull(upperBounds, "upperBounds must not be null"); //$NON-NLS-1$
Set<PropertyMirror> results = new LinkedHashSet<>();
Elements elements = environment.getProcessingEnvironment().getElementUtils();
for (TypeElement element : upperBounds) {
for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(element))) {
PropertyMirror property = toProperty(method);
if (property != null) {
results.add(property);
}
}
}
return results;
}
private PropertyMirror toProperty(ExecutableElement element) {
assert element != null;
JavaName name = JavaName.of(element.getSimpleName().toString());
List<String> segments = name.getSegments();
if (segments.size() <= 2) {
return null;
}
String first = segments.get(0);
String last = segments.get(segments.size() - 1);
if (first.equals("get") == false || last.equals("option") == false) { //$NON-NLS-1$ //$NON-NLS-2$
return null;
}
name.removeLast();
name.removeFirst();
String propertyName = name.toMemberName();
return new PropertyMirror(propertyName, element.getReturnType());
}
}