/*
* ModeShape (http://www.modeshape.org)
*
* 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.modeshape.sequencer.classfile.metadata;
import static org.hamcrest.core.AnyOf.anyOf;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.After;
import org.junit.Test;
import org.modeshape.common.util.HashCode;
import org.modeshape.sequencer.classfile.ClassFileSequencer;
import org.modeshape.sequencer.testdata.MockEnum;
public class ClassFileMetadataReaderTest {
private InputStream input;
@After
public void afterEach() throws Exception {
if (input != null) {
try {
input.close();
} finally {
input = null;
}
}
}
@Test
public void shouldReadJavaLangObject() throws Exception {
compareMetadataToClass(Object.class);
}
@Test
public void shouldReadJavaLangString() throws Exception {
compareMetadataToClass(String.class);
}
@Test
public void shouldReadOrgJbossDnaSequencerClassfileClassFileSequencer() throws Exception {
compareMetadataToClass(ClassFileSequencer.class);
}
@Test
public void shouldReadEnum() throws Exception {
String resourceName = "/" + MockEnum.class.getName().replace('.', '/') + ".class";
input = getClass().getResourceAsStream(resourceName);
ClassMetadata cmd = ClassFileMetadataReader.instance(input);
assertThat(cmd, instanceOf(EnumMetadata.class));
EnumMetadata emd = (EnumMetadata)cmd;
List<String> enumValues = Arrays.asList("VALUE_A", "VALUE_B", "VALUE_C");
assertThat(emd.getValues(), is(enumValues));
for (FieldMetadata fmd : emd.getFields()) {
assertThat(fmd.getName(), not(anyOf(is("VALUE_A"), is("VALUE_B"), is("VALUE_C"))));
}
}
@SuppressWarnings( "unchecked" )
private void compareMetadataToClass( Class<?> clazz ) throws Exception {
String resourceName = "/" + clazz.getName().replace('.', '/') + ".class";
input = getClass().getResourceAsStream(resourceName);
ClassMetadata cmd = ClassFileMetadataReader.instance(input);
assertThat(cmd.getClassName(), is(clazz.getName()));
if (clazz.getSuperclass() == null) {
assertThat(cmd.getSuperclassName(), is(nullValue()));
} else {
assertThat(cmd.getSuperclassName(), is(clazz.getSuperclass().getName()));
}
String[] clazzInterfaces = new String[clazz.getInterfaces().length];
for (int i = 0; i < clazz.getInterfaces().length; i++) {
clazzInterfaces[i] = clazz.getInterfaces()[i].getName();
}
assertThat(cmd.getInterfaces(), is(clazzInterfaces));
assertThat(cmd.isEnumeration(), is(clazz.isEnum()));
assertThat(cmd.getVisibility(), is(visibilityFor(clazz.getModifiers())));
assertThat(cmd.isInterface(), is(Modifier.isInterface(clazz.getModifiers())));
assertThat(cmd.isAbstract(), is(Modifier.isAbstract(clazz.getModifiers())));
assertThat(cmd.getAnnotations().size(), is(clazz.getDeclaredAnnotations().length));
for (AnnotationMetadata amd : cmd.getAnnotations()) {
Class<Annotation> annotationClass = (Class<Annotation>)Class.forName(amd.getAnnotationClassName());
Annotation annotation = clazz.getAnnotation(annotationClass);
assertThat(annotation, is(notNullValue()));
}
checkFields(cmd, clazz);
checkMethods(cmd, clazz);
checkConstructors(cmd, clazz);
}
@SuppressWarnings( {"unchecked", "synthetic-access"} )
private void checkFields( ClassMetadata cmd,
Class<?> clazz ) throws Exception {
Map<FieldKey, Field> clazzFields = new HashMap<FieldKey, Field>();
Map<FieldKey, FieldMetadata> metaFields = new HashMap<FieldKey, FieldMetadata>();
for (Field field : clazz.getDeclaredFields()) {
clazzFields.put(new FieldKey(field), field);
}
for (FieldMetadata field : cmd.getFields()) {
metaFields.put(new FieldKey(field), field);
}
assertThat(metaFields.size(), is(clazzFields.size()));
assertThat(metaFields.keySet(), is(clazzFields.keySet()));
for (Map.Entry<FieldKey, FieldMetadata> entry : metaFields.entrySet()) {
Field clazzField = clazzFields.get(entry.getKey());
FieldMetadata metaField = entry.getValue();
assert clazzField != null;
// getCanonicalName converts the $ to .
String metaName = metaField.getTypeName().replace('$', '.');
String clazzName = clazzField.getType().getCanonicalName();
assertThat(metaName, is(clazzName));
assertThat(metaField.getVisibility(), is(visibilityFor(clazzField.getModifiers())));
assertThat(metaField.isFinal(), is(Modifier.isFinal(clazzField.getModifiers())));
assertThat(metaField.isStatic(), is(Modifier.isStatic(clazzField.getModifiers())));
assertThat(metaField.getAnnotations().size(), is(clazzField.getDeclaredAnnotations().length));
for (AnnotationMetadata amd : metaField.getAnnotations()) {
Class<Annotation> annotationClass = (Class<Annotation>)Class.forName(amd.getAnnotationClassName());
Annotation annotation = clazz.getAnnotation(annotationClass);
assertThat(annotation, is(notNullValue()));
}
}
}
@SuppressWarnings( "synthetic-access" )
private void checkMethods( ClassMetadata cmd,
Class<?> clazz ) throws Exception {
Map<MethodKey, Method> clazzMethods = new HashMap<MethodKey, Method>();
Map<MethodKey, MethodMetadata> metaMethods = new HashMap<MethodKey, MethodMetadata>();
for (Method field : clazz.getDeclaredMethods()) {
clazzMethods.put(new MethodKey(field), field);
}
for (MethodMetadata field : cmd.getMethods()) {
metaMethods.put(new MethodKey(field), field);
}
assertThat(metaMethods.size(), is(clazzMethods.size()));
assertThat(metaMethods.keySet(), is(clazzMethods.keySet()));
for (Map.Entry<MethodKey, MethodMetadata> entry : metaMethods.entrySet()) {
/*
* We already know that the parameter types and name are equal, otherwise this would fail
*/
Method clazzMethod = clazzMethods.get(entry.getKey());
MethodMetadata metaMethod = entry.getValue();
assert clazzMethod != null;
// getCanonicalName converts the $ to .
String metaName = metaMethod.getReturnType().replace('$', '.');
String clazzName = clazzMethod.getReturnType().getCanonicalName();
assertThat(metaName, is(clazzName));
assertThat(metaMethod.getVisibility(), is(visibilityFor(clazzMethod.getModifiers())));
assertThat(metaMethod.isFinal(), is(Modifier.isFinal(clazzMethod.getModifiers())));
assertThat(metaMethod.isStatic(), is(Modifier.isStatic(clazzMethod.getModifiers())));
assertThat(metaMethod.getAnnotations().size(), is(clazzMethod.getDeclaredAnnotations().length));
// Can't really check this since some annotations are not runtime annotations
// for (AnnotationMetadata amd : metaMethod.getAnnotations()) {
// Class<Annotation> annotationClass = (Class<Annotation>)Class.forName(amd.getAnnotationClassName());
// Annotation annotation = clazz.getAnnotation(annotationClass);
//
// assertThat(annotation, is(notNullValue()));
// }
}
}
@SuppressWarnings( {"synthetic-access"} )
private void checkConstructors( ClassMetadata cmd,
Class<?> clazz ) throws Exception {
Map<MethodKey, Constructor<?>> clazzCtors = new HashMap<MethodKey, Constructor<?>>();
Map<MethodKey, MethodMetadata> metaCtors = new HashMap<MethodKey, MethodMetadata>();
for (Constructor<?> field : clazz.getDeclaredConstructors()) {
clazzCtors.put(new MethodKey(field), field);
}
for (MethodMetadata field : cmd.getConstructors()) {
metaCtors.put(new MethodKey(field), field);
}
assertThat(metaCtors.size(), is(clazzCtors.size()));
assertThat(metaCtors.keySet(), is(clazzCtors.keySet()));
for (Map.Entry<MethodKey, MethodMetadata> entry : metaCtors.entrySet()) {
/*
* We already know that the parameter types and name are equal, otherwise this would fail
*/
Constructor<?> clazzCtor = clazzCtors.get(entry.getKey());
MethodMetadata metaCtor = entry.getValue();
assert clazzCtor != null;
assertThat(metaCtor.getVisibility(), is(visibilityFor(clazzCtor.getModifiers())));
assertThat(metaCtor.isFinal(), is(Modifier.isFinal(clazzCtor.getModifiers())));
assertThat(metaCtor.isStatic(), is(Modifier.isStatic(clazzCtor.getModifiers())));
assertThat(metaCtor.getAnnotations().size(), is(clazzCtor.getDeclaredAnnotations().length));
// Can't really check this since some annotations are not runtime annotations
// for (AnnotationMetadata amd : metaMethod.getAnnotations()) {
// Class<Annotation> annotationClass = (Class<Annotation>)Class.forName(amd.getAnnotationClassName());
// Annotation annotation = clazz.getAnnotation(annotationClass);
//
// assertThat(annotation, is(notNullValue()));
// }
}
}
private Visibility visibilityFor( int modifier ) {
if (Modifier.isPublic(modifier)) return Visibility.PUBLIC;
if (Modifier.isProtected(modifier)) return Visibility.PROTECTED;
if (Modifier.isPrivate(modifier)) return Visibility.PRIVATE;
return Visibility.PACKAGE;
}
private class FieldKey {
private final String name;
private FieldKey( Field field ) {
this.name = field.getName();
}
private FieldKey( FieldMetadata field ) {
this.name = field.getName();
}
@Override
public boolean equals( Object obj ) {
if (!(obj instanceof FieldKey)) return false;
return name.equals(((FieldKey)obj).name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return name;
}
}
private class MethodKey {
private final String name;
private final List<String> parameters;
private MethodKey( Method method ) {
this.name = method.getName();
this.parameters = new ArrayList<String>();
for (Class<?> parameter : method.getParameterTypes()) {
parameters.add(parameter.getCanonicalName());
}
}
private MethodKey( Constructor<?> ctor ) {
this.name = ctor.getName();
this.parameters = new ArrayList<String>();
for (Class<?> parameter : ctor.getParameterTypes()) {
parameters.add(parameter.getCanonicalName());
}
}
private MethodKey( MethodMetadata method ) {
this.name = method.getName();
this.parameters = new ArrayList<String>();
for (String paramName : method.getParameters()) {
parameters.add(paramName.replaceAll("\\$", "."));
}
}
@Override
public boolean equals( Object obj ) {
if (!(obj instanceof MethodKey)) return false;
MethodKey other = (MethodKey)obj;
return name.equals(other.name) && parameters.equals(other.parameters);
}
@Override
public int hashCode() {
return HashCode.compute(name, parameters);
}
@Override
public String toString() {
return name + "(" + parameters + ")";
}
}
}