/**
* 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.util;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.function.Function;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.junit.Test;
import com.asakusafw.operator.Callback;
import com.asakusafw.operator.OperatorCompilerTestRoot;
import com.asakusafw.operator.description.AnnotationDescription;
import com.asakusafw.operator.description.ArrayDescription;
import com.asakusafw.operator.description.BasicTypeDescription;
import com.asakusafw.operator.description.BasicTypeDescription.BasicTypeKind;
import com.asakusafw.operator.description.ClassDescription;
import com.asakusafw.operator.description.Descriptions;
import com.asakusafw.operator.description.ObjectDescription;
import com.asakusafw.operator.description.TypeDescription;
import com.asakusafw.operator.description.ValueDescription;
import com.asakusafw.operator.mock.MockMarker;
import com.asakusafw.operator.mock.MockNested;
import com.asakusafw.operator.mock.MockSingleElement;
import com.asakusafw.utils.java.model.syntax.Expression;
import com.asakusafw.utils.java.model.syntax.MethodDeclaration;
import com.asakusafw.utils.java.model.syntax.ModelFactory;
import com.asakusafw.utils.java.model.syntax.TypeDeclaration;
import com.asakusafw.utils.java.model.util.AttributeBuilder;
import com.asakusafw.utils.java.model.util.ImportBuilder;
import com.asakusafw.utils.java.model.util.Models;
import com.asakusafw.utils.java.model.util.TypeBuilder;
/**
* Test for {@link DescriptionHelper}.
*/
public class DescriptionHelperTest extends OperatorCompilerTestRoot {
/**
* object types to description.
*/
@Test
public void to_description_object() {
start(new Callback(true) {
@Override
protected void test() {
TypeMirror type = getType(String.class);
TypeDescription desc = DescriptionHelper.toDescription(env, type);
assertThat(desc, is((Object) Descriptions.typeOf(String.class)));
}
});
}
/**
* array types to description.
*/
@Test
public void to_description_array() {
start(new Callback() {
@Override
protected void test() {
TypeMirror type = types.getArrayType(getType(String.class));
TypeDescription desc = DescriptionHelper.toDescription(env, type);
assertThat(desc, is((Object) Descriptions.typeOf(String[].class)));
}
});
}
/**
* basic types to description.
*/
@Test
public void to_description_basic() {
start(new Callback(true) {
@Override
protected void test() {
for (BasicTypeKind kind : BasicTypeKind.values()) {
TypeMirror type;
if (kind == BasicTypeKind.VOID) {
type = types.getNoType(TypeKind.VOID);
} else {
type = types.getPrimitiveType(TypeKind.valueOf(kind.name()));
}
assertThat(
DescriptionHelper.toDescription(env, type),
is((TypeDescription) new BasicTypeDescription(kind)));
}
}
});
}
/**
* resolve basic type descriptions.
*/
@Test
public void resolve_basic() {
assertThat(type(Descriptions.typeOf(int.class)), equalTo((Object) int.class));
}
/**
* resolve object type descriptions.
*/
@Test
public void resolve_object() {
assertThat(type(Descriptions.typeOf(String.class)), equalTo((Object) String.class));
}
/**
* resolve array type descriptions.
*/
@Test
public void resolve_array() {
assertThat(type(Descriptions.typeOf(String[][].class)), equalTo((Object) String[][].class));
}
/**
* resolve immediate value.
*/
@Test
public void value_object() {
assertThat(value(ObjectDescription.of(Descriptions.classOf(BigDecimal.class), Descriptions.valueOf("3.14"))),
equalTo(new BigDecimal("3.14")));
}
/**
* resolve immediate value.
*/
@Test
public void value_object_factory() {
assertThat(
value(ObjectDescription.of(
Descriptions.classOf(Integer.class), "valueOf",
Descriptions.valueOf("1234"))),
equalTo(1234));
}
/**
* resolve enum constant value.
*/
@Test
public void value_array() {
assertThat(value(ArrayDescription.elementsOf(
Descriptions.classOf(BigDecimal.class),
ObjectDescription.of(Descriptions.classOf(BigDecimal.class), Descriptions.valueOf("1")),
ObjectDescription.of(Descriptions.classOf(BigDecimal.class), Descriptions.valueOf("2")),
ObjectDescription.of(Descriptions.classOf(BigDecimal.class), Descriptions.valueOf("3")))),
equalTo(new BigDecimal[] {
new BigDecimal("1"),
new BigDecimal("2"),
new BigDecimal("3"),
}));
}
/**
* resolve immediate value.
*/
@Test
public void constant_immediate() {
assertThat(constant(Descriptions.valueOf("Hello, world!")), equalTo((Object) "Hello, world!"));
}
/**
* resolve enum constant value.
*/
@Test
public void constant_enum() {
assertThat(constant(Descriptions.valueOf(ElementType.TYPE)), equalTo((Object) ElementType.TYPE));
}
/**
* resolve class literal.
*/
@Test
public void constant_type() {
assertThat(constant(Descriptions.valueOf(int.class)), equalTo((Object) int.class));
}
/**
* resolve array.
*/
@Test
public void constant_array() {
int[] array = { 1, 2, 3 };
assertThat(constant(Descriptions.valueOf(array)), equalTo((Object) array));
}
/**
* resolve null.
*/
@Test
public void constant_null() {
assertThat(constant(Descriptions.valueOf(null)), is(nullValue()));
}
/**
* resolve marker annotation.
*/
@Test
public void annotation_marker() {
checkAnnotation(new AnnotationDescription(Descriptions.classOf(MockMarker.class)));
}
/**
* resolve annotation with value.
*/
@Test
public void annotation_constant() {
checkAnnotation(new AnnotationDescription(
Descriptions.classOf(MockSingleElement.class),
Descriptions.valueOf("Hello, world!")));
}
/**
* resolve nesting annotation.
*/
@Test
public void annotation_nesting() {
ClassDescription single = Descriptions.classOf(MockSingleElement.class);
checkAnnotation(new AnnotationDescription(
Descriptions.classOf(MockNested.class),
ArrayDescription.elementsOf(single,
new AnnotationDescription(single, Descriptions.valueOf("A")),
new AnnotationDescription(single, Descriptions.valueOf("B")),
new AnnotationDescription(single, Descriptions.valueOf("C")))));
}
private Class<?> type(TypeDescription description) {
ModelFactory f = Models.getModelFactory();
return (Class<?>) expr(imports -> new TypeBuilder(f, DescriptionHelper.resolve(imports, description))
.dotClass()
.toExpression());
}
private Object value(ValueDescription description) {
return expr(imports -> DescriptionHelper.resolveValue(imports, description));
}
private Object constant(ValueDescription description) {
return expr(imports -> DescriptionHelper.resolveConstant(imports, description));
}
private Object expr(Function<ImportBuilder, Expression> resolver) {
ModelFactory f = Models.getModelFactory();
ClassLoader loader = start(new Callback(true) {
@Override
protected void test() throws IOException {
ImportBuilder imports = new ImportBuilder(
f,
f.newPackageDeclaration(Models.toName(f, "com.example.testing")),
ImportBuilder.Strategy.TOP_LEVEL);
Expression target = resolver.apply(imports);
MethodDeclaration method = f.newMethodDeclaration(
null,
new AttributeBuilder(f).Public().toAttributes(),
imports.toType(Object.class),
f.newSimpleName("call"),
Collections.emptyList(),
Collections.singletonList(f.newReturnStatement(target)));
TypeDeclaration type = f.newClassDeclaration(
null,
new AttributeBuilder(f).Public().toAttributes(),
f.newSimpleName("Work"),
null,
Collections.singletonList(new TypeBuilder(f, imports.toType(Callable.class))
.parameterize(imports.toType(Object.class))
.toType()),
Collections.singletonList(method));
env.emit(f.newCompilationUnit(
imports.getPackageDeclaration(),
imports.toImportDeclarations(),
Collections.singletonList(type)));
}
});
try {
return loader.loadClass("com.example.testing.Work")
.asSubclass(Callable.class)
.newInstance()
.call();
} catch (Exception e) {
throw new AssertionError(e);
}
}
private void checkAnnotation(AnnotationDescription description) {
Annotation annotation = annotation(description);
AnnotationDescription restored = AnnotationDescription.of(annotation);
assertThat(restored, is(description));
}
private Annotation annotation(AnnotationDescription description) {
ModelFactory f = Models.getModelFactory();
ClassLoader loader = start(new Callback(true) {
@Override
protected void test() throws IOException {
ImportBuilder imports = new ImportBuilder(
f,
f.newPackageDeclaration(Models.toName(f, "com.example.testing")),
ImportBuilder.Strategy.TOP_LEVEL);
TypeDeclaration type = f.newClassDeclaration(
null,
new AttributeBuilder(f)
.annotation(DescriptionHelper.resolveAnnotation(imports, description))
.Public()
.toAttributes(),
f.newSimpleName("Work"),
null,
Collections.emptyList(),
Collections.emptyList());
env.emit(f.newCompilationUnit(
imports.getPackageDeclaration(),
imports.toImportDeclarations(),
Collections.singletonList(type)));
}
});
try {
Class<? extends Annotation> annotationType = loader
.loadClass(description.getDeclaringClass().getBinaryName())
.asSubclass(Annotation.class);
return loader.loadClass("com.example.testing.Work").getAnnotation(annotationType);
} catch (Exception e) {
throw new AssertionError(e);
}
}
}