METAGEN-19 Support @MapKeyClass overriding of the key class

This commit is contained in:
Hardy Ferentschik 2010-01-29 20:07:40 +00:00 committed by Strong Liu
parent 1f021a6fd2
commit f82b6a7d3c
12 changed files with 277 additions and 43 deletions

View File

@ -0,0 +1,35 @@
// $Id:$
/*
* JBoss, Home of Professional Open Source
* Copyright 2009, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.hibernate.jpamodelgen;
/**
* {@code RuntimeException} used for errors during meta model generation.
*
* @author Hardy Ferentschik
*/
public class MetaModelGenerationException extends RuntimeException {
public MetaModelGenerationException() {
super();
}
public MetaModelGenerationException(String message) {
super( message );
}
}

View File

@ -35,6 +35,7 @@ import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleTypeVisitor6;
import javax.persistence.Access;
import javax.persistence.AccessType;
@ -46,6 +47,7 @@ import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyClass;
import javax.persistence.MappedSuperclass;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
@ -57,6 +59,7 @@ import org.hibernate.jpamodelgen.ImportContext;
import org.hibernate.jpamodelgen.ImportContextImpl;
import org.hibernate.jpamodelgen.MetaAttribute;
import org.hibernate.jpamodelgen.MetaEntity;
import org.hibernate.jpamodelgen.MetaModelGenerationException;
import org.hibernate.jpamodelgen.util.TypeUtils;
/**
@ -369,18 +372,20 @@ public class AnnotationMetaEntity implements MetaEntity {
@Override
public AnnotationMetaAttribute visitDeclared(DeclaredType declaredType, Element element) {
//FIXME consider XML
if ( isPersistent( element ) ) {
TypeElement returnedElement = ( TypeElement ) context.getProcessingEnvironment()
.getTypeUtils()
.asElement( declaredType );
// WARNING: .toString() is necessary here since Name equals does not compare to String
String fqElementName = returnedElement.getQualifiedName().toString();
String collection = COLLECTIONS.get( fqElementName );
String fqNameOfReturnType = returnedElement.getQualifiedName().toString();
String collection = COLLECTIONS.get( fqNameOfReturnType );
String targetEntity = getTargetEntity( element.getAnnotationMirrors() );
if ( collection != null ) {
if ( TypeUtils.containsAnnotation( element, ElementCollection.class ) ) {
TypeMirror collectionElementType = getCollectionElementType( declaredType, fqElementName );
String explicitTargetEntity = getTargetEntity( element.getAnnotationMirrors() );
TypeMirror collectionElementType = getCollectionElementType(
declaredType, fqNameOfReturnType, explicitTargetEntity
);
final TypeElement collectionElement = ( TypeElement ) context.getProcessingEnvironment()
.getTypeUtils()
.asElement( collectionElementType );
@ -390,14 +395,7 @@ public class AnnotationMetaEntity implements MetaEntity {
);
}
if ( collection.equals( "javax.persistence.metamodel.MapAttribute" ) ) {
return new AnnotationMetaMap(
//FIXME support targetEntity for map's key @MapKeyClass
parent,
element,
collection,
getKeyType( declaredType ),
getElementType( declaredType, targetEntity )
);
return createAnnotationMetaAttributeForMap( declaredType, element, collection, targetEntity );
}
else {
return new AnnotationMetaCollection(
@ -423,25 +421,59 @@ public class AnnotationMetaEntity implements MetaEntity {
}
}
private TypeMirror getCollectionElementType(DeclaredType t, String fqElementName) {
private AnnotationMetaAttribute createAnnotationMetaAttributeForMap(DeclaredType declaredType, Element element, String collection, String targetEntity) {
String keyType;
if ( TypeUtils.containsAnnotation( element, MapKeyClass.class ) ) {
TypeMirror typeMirror = ( TypeMirror ) TypeUtils.getAnnotationValue(
TypeUtils.getAnnotationMirror(
element, MapKeyClass.class
), DEFAULT_ANNOTATION_PARAMETER_NAME
);
keyType = typeMirror.toString();
}
else {
keyType = getKeyType( declaredType );
}
return new AnnotationMetaMap(
parent,
element,
collection,
keyType,
getElementType( declaredType, targetEntity )
);
}
private TypeMirror getCollectionElementType(DeclaredType t, String fqNameOfReturnedType, String explicitTargetEntityName) {
TypeMirror collectionElementType;
if ( Map.class.getCanonicalName().equals( fqElementName ) ) {
if ( explicitTargetEntityName != null ) {
Elements elements = context.getProcessingEnvironment().getElementUtils();
TypeElement element = elements.getTypeElement( explicitTargetEntityName );
collectionElementType = element.asType();
}
else {
List<? extends TypeMirror> typeArguments = t.getTypeArguments();
if ( typeArguments.size() == 0 ) {
throw new MetaModelGenerationException( "Unable to determine collection type for property in " + getSimpleName() );
}
else if ( Map.class.getCanonicalName().equals( fqNameOfReturnedType ) ) {
collectionElementType = t.getTypeArguments().get( 1 );
}
else {
collectionElementType = t.getTypeArguments().get( 0 );
}
}
return collectionElementType;
}
@Override
public AnnotationMetaAttribute visitExecutable(ExecutableType t, Element p) {
if ( !p.getKind().equals( ElementKind.METHOD ) ) {
return null;
}
String string = p.getSimpleName().toString();
// TODO: implement proper property get/is/boolean detection
if ( string.startsWith( "get" ) || string.startsWith( "is" ) ) {
if ( string.startsWith( "get" ) || string.startsWith( "is" ) || string.startsWith( "has" ) ) {
TypeMirror returnType = t.getReturnType();
return returnType.accept( this, p );
}
else {

View File

@ -25,12 +25,15 @@ import javax.lang.model.element.Element;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import org.hibernate.jpamodelgen.Context;
import org.hibernate.jpamodelgen.ImportContext;
import org.hibernate.jpamodelgen.ImportContextImpl;
import org.hibernate.jpamodelgen.MetaAttribute;
import org.hibernate.jpamodelgen.MetaEntity;
import org.hibernate.jpamodelgen.MetaModelGenerationException;
import org.hibernate.jpamodelgen.util.TypeUtils;
import org.hibernate.jpamodelgen.xml.jaxb.Attributes;
import org.hibernate.jpamodelgen.xml.jaxb.Basic;
@ -142,8 +145,14 @@ public class XmlMetaEntity implements MetaEntity {
for ( Element elem : element.getEnclosedElements() ) {
if ( elem.getSimpleName().toString().equals( propertyName ) ) {
DeclaredType type = ( ( DeclaredType ) elem.asType() );
List<? extends TypeMirror> typeArguments = type.getTypeArguments();
if ( typeArguments.size() == 0 && explicitTargetEntity == null ) {
throw new MetaModelGenerationException( "Unable to determine target entity type for " + clazzName + "." + propertyName + "." );
}
if ( explicitTargetEntity == null ) {
types[0] = TypeUtils.extractClosestRealTypeAsString( type.getTypeArguments().get( 0 ), context );
types[0] = TypeUtils.extractClosestRealTypeAsString( typeArguments.get( 0 ), context );
}
else {
types[0] = explicitTargetEntity;
@ -240,25 +249,58 @@ public class XmlMetaEntity implements MetaEntity {
}
XmlMetaCollection metaCollection;
String[] types;
for ( ManyToMany manyToMany : attributes.getManyToMany() ) {
String[] types = getCollectionType( manyToMany.getName(), manyToMany.getTargetEntity() );
try {
types = getCollectionType( manyToMany.getName(), manyToMany.getTargetEntity() );
}
catch ( MetaModelGenerationException e ) {
logMetaModelException( manyToMany.getName(), e );
break;
}
metaCollection = new XmlMetaCollection( this, manyToMany.getName(), types[0], types[1] );
members.add( metaCollection );
}
for ( OneToMany oneToMany : attributes.getOneToMany() ) {
String[] types = getCollectionType( oneToMany.getName(), oneToMany.getTargetEntity() );
try {
types = getCollectionType( oneToMany.getName(), oneToMany.getTargetEntity() );
}
catch ( MetaModelGenerationException e ) {
logMetaModelException( oneToMany.getName(), e );
break;
}
metaCollection = new XmlMetaCollection( this, oneToMany.getName(), types[0], types[1] );
members.add( metaCollection );
}
for ( ElementCollection collection : attributes.getElementCollection() ) {
String[] types = getCollectionType( collection.getName(), collection.getTargetClass() );
try {
types = getCollectionType( collection.getName(), collection.getTargetClass() );
}
catch ( MetaModelGenerationException e ) {
logMetaModelException( collection.getName(), e );
break;
}
metaCollection = new XmlMetaCollection( this, collection.getName(), types[0], types[1] );
members.add( metaCollection );
}
}
private void logMetaModelException(String name, MetaModelGenerationException e) {
StringBuilder builder = new StringBuilder();
builder.append( "Error processing xml for " );
builder.append( clazzName );
builder.append( "." );
builder.append( name );
builder.append( ". Error message: " );
builder.append( e.getMessage() );
context.logMessage(
Diagnostic.Kind.WARNING,
builder.toString()
);
}
private void parseEmbeddableAttributes(EmbeddableAttributes attributes) {
XmlMetaSingleAttribute attribute;
for ( Basic basic : attributes.getBasic() ) {

View File

@ -23,7 +23,7 @@ import org.hibernate.jpamodelgen.test.util.CompilationTest;
import org.hibernate.jpamodelgen.test.util.TestUtil;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertAbsenceOfFieldInMetamodelFor;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertFieldTypeInMetaModelFor;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertAttributeTypeInMetaModelFor;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertPresenceOfFieldInMetamodelFor;
/**
@ -84,7 +84,7 @@ public class AccessTypeTest extends CompilationTest {
@Test
public void testMemberAccessType() {
assertPresenceOfFieldInMetamodelFor( Customer.class, "goodPayer", "access type overriding" );
assertFieldTypeInMetaModelFor( Customer.class, "goodPayer", Boolean.class, "access type overriding" );
assertAttributeTypeInMetaModelFor( Customer.class, "goodPayer", Boolean.class, "access type overriding" );
}
@Override

View File

@ -21,7 +21,7 @@ import org.testng.annotations.Test;
import org.hibernate.jpamodelgen.test.util.CompilationTest;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertFieldTypeInMetaModelFor;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertAttributeTypeInMetaModelFor;
/**
* @author Hardy Ferentschik
@ -33,7 +33,7 @@ public class ArrayTest extends CompilationTest {
*/
@Test
public void testPrimitiveArray() {
assertFieldTypeInMetaModelFor( Image.class, "data", byte[].class, "Wrong type for field." );
assertAttributeTypeInMetaModelFor( Image.class, "data", byte[].class, "Wrong type for field." );
}
/**
@ -41,7 +41,7 @@ public class ArrayTest extends CompilationTest {
*/
@Test
public void testIntegerArray() {
assertFieldTypeInMetaModelFor(
assertAttributeTypeInMetaModelFor(
TemperatureSamples.class, "samples", Integer[].class, "Wrong type for field."
);
}

View File

@ -0,0 +1,46 @@
// $Id$
/*
* JBoss, Home of Professional Open Source
* Copyright 2009, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.hibernate.jpamodelgen.test.elementcollection;
import javax.persistence.Entity;
/**
* @author Hardy Ferentschik
*/
@Entity
public class Cleaner {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -21,6 +21,7 @@ import org.testng.annotations.Test;
import org.hibernate.jpamodelgen.test.util.CompilationTest;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertMapAttributesInMetaModelFor;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertMetamodelClassGeneratedFor;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertNoSourceFileGeneratedFor;
@ -34,11 +35,26 @@ public class ElementCollectionTest extends CompilationTest {
@Test
public void testElementCollectionOnMap() {
assertMetamodelClassGeneratedFor( House.class );
assertMetamodelClassGeneratedFor( House.class );
assertMetamodelClassGeneratedFor( Room.class );
// side effect of METAGEN-8 was that a meta class for String was created!
assertNoSourceFileGeneratedFor( String.class );
}
/**
* METAGEN-19
*/
@Test
public void testMapKeyClass() {
assertMetamodelClassGeneratedFor( Hotel.class );
assertMapAttributesInMetaModelFor(
Hotel.class, "roomsByName", String.class, Room.class, "Wrong type in map attribute."
);
assertMapAttributesInMetaModelFor(
Hotel.class, "cleaners", Room.class, Cleaner.class, "Wrong type in map attribute."
);
}
@Override
protected String getTestPackage() {
return House.class.getPackage().getName();

View File

@ -0,0 +1,55 @@
// $Id$
/*
* JBoss, Home of Professional Open Source
* Copyright 2009, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.hibernate.jpamodelgen.test.elementcollection;
import java.util.Map;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.MapKeyClass;
import javax.persistence.OneToMany;
/**
* @author Hardy Ferentschik
*/
@Entity
public class Hotel {
private Map roomsByName;
private Map cleaners;
@ElementCollection(targetClass = Room.class)
@MapKeyClass(String.class)
public Map getRoomsByName() {
return roomsByName;
}
public void setRoomsByName(Map roomsByName) {
this.roomsByName = roomsByName;
}
@OneToMany(targetEntity = Cleaner.class)
@MapKeyClass(Room.class)
public Map getCleaners() {
return cleaners;
}
public void setCleaners(Map cleaners) {
this.cleaners = cleaners;
}
}

View File

@ -96,7 +96,7 @@ public class TestUtil {
Assert.assertTrue( hasFieldInMetamodelFor( clazz, fieldName ), errorString );
}
public static void assertFieldTypeInMetaModelFor(Class<?> clazz, String fieldName, Class expectedType, String errorString) {
public static void assertAttributeTypeInMetaModelFor(Class<?> clazz, String fieldName, Class<?> expectedType, String errorString) {
Field field = getFieldFromMetamodelFor( clazz, fieldName );
assertNotNull( field );
ParameterizedType type = ( ParameterizedType ) field.getGenericType();
@ -108,6 +108,18 @@ public class TestUtil {
assertEquals( actualType, expectedType, errorString );
}
public static void assertMapAttributesInMetaModelFor(Class<?> clazz, String fieldName, Class<?> expectedMapKey, Class<?> expectedMapValue, String errorString) {
Field field = getFieldFromMetamodelFor( clazz, fieldName );
assertNotNull( field );
ParameterizedType type = ( ParameterizedType ) field.getGenericType();
Type actualMapKeyType = type.getActualTypeArguments()[1];
assertEquals( actualMapKeyType, expectedMapKey, errorString );
Type actualMapKeyValue = type.getActualTypeArguments()[2];
assertEquals( actualMapKeyValue, expectedMapValue, errorString );
}
public static void assertSuperClassRelationShipInMetamodel(Class<?> entityClass, Class<?> superEntityClass) {
String entityModelClassName = entityClass.getName() + META_MODEL_CLASS_POSTFIX;
String superEntityModelClassName = superEntityClass.getName() + META_MODEL_CLASS_POSTFIX;

View File

@ -27,11 +27,7 @@ public class Boy {
private String name;
/*
* The mapping in boy.xml specifies as target-class for the element collection java.lang.Integer. This makes no
* sense from a mapping point, but makes it easy to test.
*/
private List<String> nickNames;
private List nickNames;
private Superhero favoriteSuperhero;

View File

@ -21,7 +21,7 @@ import org.testng.annotations.Test;
import org.hibernate.jpamodelgen.test.util.CompilationTest;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertFieldTypeInMetaModelFor;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertAttributeTypeInMetaModelFor;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertMetamodelClassGeneratedFor;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertPresenceOfFieldInMetamodelFor;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertSuperClassRelationShipInMetamodel;
@ -48,7 +48,7 @@ public class XmlMappingTest extends CompilationTest {
public void testTargetEntityOnOneToOne() {
assertMetamodelClassGeneratedFor( Boy.class );
assertPresenceOfFieldInMetamodelFor( Boy.class, "favoriteSuperhero", "favoriteSuperhero field should exist" );
assertFieldTypeInMetaModelFor(
assertAttributeTypeInMetaModelFor(
Boy.class, "favoriteSuperhero", FakeHero.class, "target entity overridden in xml"
);
}
@ -60,7 +60,7 @@ public class XmlMappingTest extends CompilationTest {
public void testTargetEntityOnOneToMany() {
assertMetamodelClassGeneratedFor( Boy.class );
assertPresenceOfFieldInMetamodelFor( Boy.class, "knowsHeros", "knowsHeros field should exist" );
assertFieldTypeInMetaModelFor(
assertAttributeTypeInMetaModelFor(
Boy.class, "knowsHeros", FakeHero.class, "target entity overridden in xml"
);
}
@ -72,7 +72,7 @@ public class XmlMappingTest extends CompilationTest {
public void testTargetEntityOnManyToMany() {
assertMetamodelClassGeneratedFor( Boy.class );
assertPresenceOfFieldInMetamodelFor( Boy.class, "savedBy", "savedBy field should exist" );
assertFieldTypeInMetaModelFor(
assertAttributeTypeInMetaModelFor(
Boy.class, "savedBy", FakeHero.class, "target entity overridden in xml"
);
}
@ -81,7 +81,7 @@ public class XmlMappingTest extends CompilationTest {
public void testXmlConfiguredElementCollection() {
assertMetamodelClassGeneratedFor( Boy.class );
assertPresenceOfFieldInMetamodelFor( Boy.class, "nickNames", "nickNames field should exist" );
assertFieldTypeInMetaModelFor( Boy.class, "nickNames", Integer.class, "target class overridden in xml" );
assertAttributeTypeInMetaModelFor( Boy.class, "nickNames", String.class, "target class overridden in xml" );
}

View File

@ -13,7 +13,7 @@
<one-to-many name="knowsHeros" target-entity="org.hibernate.jpamodelgen.test.xmlmapped.FakeHero"/>
<one-to-one name="favoriteSuperhero" target-entity="org.hibernate.jpamodelgen.test.xmlmapped.FakeHero"/>
<many-to-many name="savedBy" target-entity="org.hibernate.jpamodelgen.test.xmlmapped.FakeHero"/>
<element-collection name="nickNames" target-class="java.lang.Integer"/>
<element-collection name="nickNames" target-class="java.lang.String"/>
</attributes>
</entity>
</entity-mappings>