blob: c3cb67a6eb093edd25e69424b78b8ff348a4f3e5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2009 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0, which accompanies this distribution
* and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.core.internal.context.java;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jpt.core.JpaStructureNode;
import org.eclipse.jpt.core.MappingKeys;
import org.eclipse.jpt.core.context.AccessType;
import org.eclipse.jpt.core.context.Embeddable;
import org.eclipse.jpt.core.context.PersistentType;
import org.eclipse.jpt.core.context.TypeMapping;
import org.eclipse.jpt.core.context.java.JavaAttributeMapping;
import org.eclipse.jpt.core.context.java.JavaPersistentAttribute;
import org.eclipse.jpt.core.context.java.JavaStructureNodes;
import org.eclipse.jpt.core.internal.validation.DefaultJpaValidationMessages;
import org.eclipse.jpt.core.internal.validation.JpaValidationMessages;
import org.eclipse.jpt.core.resource.java.Annotation;
import org.eclipse.jpt.core.resource.java.JavaResourcePersistentAttribute;
import org.eclipse.jpt.core.utility.TextRange;
import org.eclipse.jpt.utility.Filter;
import org.eclipse.jpt.utility.internal.ClassTools;
import org.eclipse.jpt.utility.internal.CollectionTools;
import org.eclipse.jpt.utility.internal.HashBag;
import org.eclipse.jpt.utility.internal.iterators.EmptyIterator;
import org.eclipse.wst.validation.internal.provisional.core.IMessage;
import org.eclipse.wst.validation.internal.provisional.core.IReporter;
/**
* common state/behavior between Generic and EclipseLink persistent attributes
*/
public abstract class AbstractJavaPersistentAttribute
extends AbstractJavaJpaContextNode
implements JavaPersistentAttribute
{
protected String name;
protected JavaAttributeMapping defaultMapping;
protected JavaAttributeMapping specifiedMapping;
protected JavaResourcePersistentAttribute resourcePersistentAttribute;
protected AbstractJavaPersistentAttribute(PersistentType parent, JavaResourcePersistentAttribute resourcePersistentAttribute) {
super(parent);
this.resourcePersistentAttribute = resourcePersistentAttribute;
this.name = this.buildName();
this.defaultMapping = this.buildDefaultMapping();
this.specifiedMapping = this.buildJavaAttributeMappingFromAnnotation(this.getJavaMappingAnnotationName());
}
// ********** JpaStructureNode implementation **********
public String getId() {
return JavaStructureNodes.PERSISTENT_ATTRIBUTE_ID;
}
public IContentType getContentType() {
return this.getPersistentType().getContentType();
}
public JpaStructureNode getStructureNode(int textOffset) {
return this;
}
public TextRange getSelectionTextRange() {
return this.getSelectionTextRange(this.buildASTRoot());
}
protected TextRange getSelectionTextRange(CompilationUnit astRoot) {
return this.resourcePersistentAttribute.getNameTextRange(astRoot);
}
protected CompilationUnit buildASTRoot() {
return this.resourcePersistentAttribute.getJavaResourceCompilationUnit().buildASTRoot();
}
public void dispose() {
//nothing to dispose
}
// ********** AccessHolder implementation **********
public AccessType getAccess() {
AccessType access = this.getSpecifiedAccess();
return (access != null) ? access : this.getDefaultAccess();
}
public AccessType getDefaultAccess() {
return this.resourcePersistentAttribute.isField() ? AccessType.FIELD : AccessType.PROPERTY;
}
// ********** [Java]PersistentAttribute implementation **********
public JavaResourcePersistentAttribute getResourcePersistentAttribute() {
return this.resourcePersistentAttribute;
}
public PersistentType getPersistentType() {
return this.getParent();
}
public TypeMapping getTypeMapping() {
return this.getPersistentType().getMapping();
}
public String getPrimaryKeyColumnName() {
return this.getMapping().getPrimaryKeyColumnName();
}
public boolean isOverridableAttribute() {
return this.getMapping().isOverridableAttributeMapping();
}
public boolean isOverridableAssociation() {
return this.getMapping().isOverridableAssociationMapping();
}
public boolean isIdAttribute() {
return this.getMapping().isIdMapping();
}
public boolean isVirtual() {
return false;
}
public boolean contains(int offset, CompilationUnit astRoot) {
TextRange fullTextRange = this.getFullTextRange(astRoot);
// 'fullTextRange' will be null if the attribute no longer exists in the java;
// the context model can be out of synch with the resource model
// when a selection event occurs before the context model has a
// chance to synch with the resource model via the update thread
return (fullTextRange == null) ? false : fullTextRange.includes(offset);
}
protected TextRange getFullTextRange(CompilationUnit astRoot) {
return this.resourcePersistentAttribute.getTextRange(astRoot);
}
public Embeddable getEmbeddable() {
String typeName = this.resourcePersistentAttribute.getTypeName();
return (typeName == null) ? null : this.getPersistenceUnit().getEmbeddable(typeName);
}
public boolean isField() {
return this.resourcePersistentAttribute.isField();
}
public boolean isProperty() {
return this.resourcePersistentAttribute.isProperty();
}
public boolean isPublic() {
return Modifier.isPublic(this.resourcePersistentAttribute.getModifiers());
}
public boolean isFinal() {
return Modifier.isFinal(this.resourcePersistentAttribute.getModifiers());
}
// ***** Basic defaults
/**
* From the JPA spec, when the basic mapping applies:
* If the type of the attribute (field or property) is one of the following
* it must be mapped as @Basic:
* byte[]
* java.lang.Byte[]
* char[]
* java.lang.Character[]
* primitive types (except 'void')
* primitive wrappers (except 'java.lang.Void')
* java.lang.String
* java.math.BigInteger
* java.math.BigDecimal
* java.util.Date
* java.util.Calendar
* java.sql.Date
* java.sql.Time
* java.sql.Timestamp
* enums
* any other type that implements java.io.Serializable
*/
public boolean typeIsBasic() {
// 'typeName' may include array brackets but not generic type arguments
String typeName = this.resourcePersistentAttribute.getTypeName();
if (typeName == null) {
return false;
}
int arrayDepth = ClassTools.arrayDepthForTypeDeclaration(typeName);
if (arrayDepth > 1) {
return false; // multi-dimensional arrays are not supported
}
if (arrayDepth == 1) {
String elementTypeName = ClassTools.elementTypeNameForTypeDeclaration(typeName, 1);
return this.elementTypeIsValidForBasicArray(elementTypeName);
}
// arrayDepth == 0
if (ClassTools.classNamedIsVariablePrimitive(typeName)) {
return true; // any primitive but 'void'
}
if (ClassTools.classNamedIsVariablePrimitiveWrapperClass(typeName)) {
return true; // any primitive wrapper but 'java.lang.Void'
}
if (this.typeIsOtherValidBasicType(typeName)) {
return true;
}
if (this.resourcePersistentAttribute.typeIsEnum()) {
return true;
}
if (this.resourcePersistentAttribute.typeIsSubTypeOf(SERIALIZABLE_TYPE_NAME)) {
return true;
}
return false;
}
/**
* Return whether the specified type is a valid element type for
* a one-dimensional array that can default to a Basic mapping:
* byte
* char
* java.lang.Byte
* java.lang.Character
*/
protected boolean elementTypeIsValidForBasicArray(String elementTypeName) {
return CollectionTools.contains(VALID_BASIC_ARRAY_ELEMENT_TYPE_NAMES, elementTypeName);
}
protected static final String[] VALID_BASIC_ARRAY_ELEMENT_TYPE_NAMES = {
byte.class.getName(),
char.class.getName(),
java.lang.Byte.class.getName(),
java.lang.Character.class.getName()
};
/**
* Return whether the specified type is among the various "other" types
* that can default to a Basic mapping.
*/
protected boolean typeIsOtherValidBasicType(String typeName) {
return CollectionTools.contains(OTHER_VALID_BASIC_TYPE_NAMES, typeName);
}
protected static final String[] OTHER_VALID_BASIC_TYPE_NAMES = {
java.lang.String.class.getName(),
java.math.BigInteger.class.getName(),
java.math.BigDecimal.class.getName(),
java.util.Date.class.getName(),
java.util.Calendar.class.getName(),
java.sql.Date.class.getName(),
java.sql.Time.class.getName(),
java.sql.Timestamp.class.getName(),
};
protected static final String SERIALIZABLE_TYPE_NAME = java.io.Serializable.class.getName();
// ***** reference entities
public String getSingleReferenceEntityTypeName() {
return this.buildSingleReferenceEntityTypeName(this.resourcePersistentAttribute.getTypeName());
}
public String getMultiReferenceEntityTypeName() {
// 'typeName' may include array brackets but not generic type arguments
String typeName = this.resourcePersistentAttribute.getTypeName();
if (typeName == null) {
return null;
}
if (ClassTools.arrayDepthForTypeDeclaration(typeName) != 0) {
return null; // arrays cannot hold entities
}
switch (this.resourcePersistentAttribute.typeTypeArgumentNamesSize()) {
case 0:
return null;
case 1:
return this.typeIsCollection(typeName) ? this.resourcePersistentAttribute.getTypeTypeArgumentName(0) : null;
case 2:
return this.typeIsMap(typeName) ? this.resourcePersistentAttribute.getTypeTypeArgumentName(1) : null;
default:
return null;
}
}
/**
* 'typeName' may include array brackets ("[]")
* but not generic type arguments (e.g. "<java.lang.String>")
*/
protected String buildSingleReferenceEntityTypeName(String typeName) {
if (typeName == null) {
return null;
}
if (ClassTools.arrayDepthForTypeDeclaration(typeName) != 0) {
return null; // arrays cannot be entities
}
if (this.typeIsContainer(typeName)) {
return null; // "containers" cannot be entities
}
return typeName;
}
/**
* return whether the specified type is one of the collection
* types allowed by the JPA spec
*/
protected boolean typeIsCollection(String typeName) {
return CollectionTools.contains(COLLECTION_TYPE_NAMES, typeName);
}
protected static final String[] COLLECTION_TYPE_NAMES = {
java.util.Collection.class.getName(),
java.util.Set.class.getName(),
java.util.List.class.getName()
};
/**
* return whether the specified type is one of the map
* types allowed by the JPA spec
*/
protected boolean typeIsMap(String typeName) {
return CollectionTools.contains(MAP_TYPE_NAMES, typeName);
}
protected static final String[] MAP_TYPE_NAMES = {
java.util.Map.class.getName()
};
/**
* return whether the specified type is one of the container
* types allowed by the JPA spec
*/
protected boolean typeIsContainer(String typeName) {
return CollectionTools.contains(CONTAINER_TYPE_NAMES, typeName);
}
protected static final String[] CONTAINER_TYPE_NAMES = CollectionTools.concatenate(
COLLECTION_TYPE_NAMES,
MAP_TYPE_NAMES
);
// ***** name
public String getName() {
return this.name;
}
protected void setName(String name) {
String old = this.name;
this.name = name;
this.firePropertyChanged(NAME_PROPERTY, old, name);
}
protected String buildName() {
return this.resourcePersistentAttribute.getName();
}
// ***** mapping
public JavaAttributeMapping getMapping() {
return (this.specifiedMapping != null) ? this.specifiedMapping : this.defaultMapping;
}
public String getMappingKey() {
return this.getMapping().getKey();
}
// ***** default mapping
public JavaAttributeMapping getDefaultMapping() {
return this.defaultMapping;
}
/**
* clients do not set the "default" mapping
*/
protected void setDefaultMapping(JavaAttributeMapping defaultMapping) {
JavaAttributeMapping old = this.defaultMapping;
this.defaultMapping = defaultMapping;
this.firePropertyChanged(DEFAULT_MAPPING_PROPERTY, old, defaultMapping);
}
protected JavaAttributeMapping buildDefaultMapping() {
JavaAttributeMapping mapping = this.getJpaPlatform().buildDefaultJavaAttributeMapping(this);
Annotation annotation = this.resourcePersistentAttribute.getNullMappingAnnotation(mapping.getAnnotationName());
mapping.initialize(annotation);
return mapping;
}
/**
* return null if there is no "default" mapping for the attribute
*/
public String getDefaultMappingKey() {
return this.defaultMapping.getKey();
}
/**
* the mapping might be "default", but it still might be a "null" mapping...
*/
public boolean mappingIsDefault(JavaAttributeMapping mapping) {
return this.defaultMapping == mapping;
}
// ***** specified mapping
public JavaAttributeMapping getSpecifiedMapping() {
return this.specifiedMapping;
}
/**
* clients do not set the "specified" mapping;
* @see #setSpecifiedMappingKey(String)
*/
protected void setSpecifiedMapping(JavaAttributeMapping specifiedMapping) {
JavaAttributeMapping old = this.specifiedMapping;
this.specifiedMapping = specifiedMapping;
this.firePropertyChanged(SPECIFIED_MAPPING_PROPERTY, old, specifiedMapping);
}
/**
* return null if there is no "specified" mapping for the attribute
*/
protected String getSpecifiedMappingKey() {
return (this.specifiedMapping == null) ? null : this.specifiedMapping.getKey();
}
// TODO support morphing mappings, i.e. copying common settings over
// to the new mapping; this can't be done in the same was as XmlAttributeMapping
// since we don't know all the possible mapping types
public void setSpecifiedMappingKey(String key) {
if (key == this.getSpecifiedMappingKey()) {
return;
}
JavaAttributeMapping oldMapping = this.specifiedMapping;
JavaAttributeMapping newMapping = this.buildMappingFromMappingKey(key);
this.specifiedMapping = newMapping;
this.resourcePersistentAttribute.setMappingAnnotation((newMapping == null) ? null : newMapping.getAnnotationName());
this.firePropertyChanged(SPECIFIED_MAPPING_PROPERTY, oldMapping, newMapping);
// remove "supporting annotations" that do not "support" to the new mapping
if (oldMapping != null) {
HashBag<String> annotationsToRemove = CollectionTools.collection(oldMapping.supportingAnnotationNames());
CollectionTools.removeAll(annotationsToRemove, this.supportingAnnotationNames());
for (String annotationName : annotationsToRemove) {
this.resourcePersistentAttribute.removeSupportingAnnotation(annotationName);
}
}
}
protected JavaAttributeMapping buildMappingFromMappingKey(String key) {
if (key == MappingKeys.NULL_ATTRIBUTE_MAPPING_KEY) {
return null;
}
JavaAttributeMapping mapping = this.getJpaPlatform().buildJavaAttributeMappingFromMappingKey(key, this);
//no mapping.initialize(JavaResourcePersistentAttribute) call here
//we do not yet have a mapping annotation so we can't call initialize
return mapping;
}
protected Iterator<String> supportingAnnotationNames() {
JavaAttributeMapping mapping = this.getMapping();
return (mapping != null) ? mapping.supportingAnnotationNames() : EmptyIterator.<String>instance();
}
// ********** updating **********
public void update() {
this.setName(this.buildName());
this.updateDefaultMapping();
this.updateSpecifiedMapping();
}
protected void updateDefaultMapping() {
String defaultMappingKey = this.getJpaPlatform().getDefaultJavaAttributeMappingKey(this);
if (this.defaultMapping.getKey() == defaultMappingKey) {
this.defaultMapping.update(this.resourcePersistentAttribute.getNullMappingAnnotation(this.defaultMapping.getAnnotationName()));
} else {
JavaAttributeMapping old = this.defaultMapping;
this.defaultMapping = this.buildDefaultMapping();
this.firePropertyChanged(DEFAULT_MAPPING_PROPERTY, old, this.defaultMapping);
}
}
protected void updateSpecifiedMapping() {
String javaMappingAnnotationName = this.getJavaMappingAnnotationName();
if (javaMappingAnnotationName == this.getSpecifiedMappingAnnotationName()) {
if (this.specifiedMapping != null) {
this.specifiedMapping.update(this.resourcePersistentAttribute.getMappingAnnotation(javaMappingAnnotationName));
}
} else {
this.setSpecifiedMapping(this.buildJavaAttributeMappingFromAnnotation(javaMappingAnnotationName));
}
}
protected String getJavaMappingAnnotationName() {
Annotation mappingAnnotation = this.resourcePersistentAttribute.getMappingAnnotation();
return (mappingAnnotation == null) ? null : mappingAnnotation.getAnnotationName();
}
protected String getSpecifiedMappingAnnotationName() {
return (this.specifiedMapping == null) ? null : this.specifiedMapping.getAnnotationName();
}
protected JavaAttributeMapping buildJavaAttributeMappingFromAnnotation(String annotationName) {
if (annotationName == null) {
return null;
}
JavaAttributeMapping mapping = getJpaPlatform().buildJavaAttributeMappingFromAnnotation(annotationName, this);
mapping.initialize(this.resourcePersistentAttribute.getMappingAnnotation(annotationName));
return mapping;
}
// ********** validation **********
public TextRange getValidationTextRange(CompilationUnit astRoot) {
return this.getSelectionTextRange(astRoot);
}
@Override
public void validate(List<IMessage> messages, IReporter reporter, CompilationUnit astRoot) {
super.validate(messages, reporter, astRoot);
this.validateModifiers(messages, astRoot);
if (this.specifiedMapping != null) {
this.specifiedMapping.validate(messages, reporter, astRoot);
}
else if (this.defaultMapping != null) {
this.defaultMapping.validate(messages, reporter, astRoot);
}
}
protected void validateModifiers(List<IMessage> messages, CompilationUnit astRoot) {
if (this.getMappingKey() == MappingKeys.TRANSIENT_ATTRIBUTE_MAPPING_KEY) {
return;
}
if (this.isField()) {
if (this.isFinal()) {
messages.add(this.buildAttributeMessage(JpaValidationMessages.PERSISTENT_ATTRIBUTE_FINAL_FIELD, astRoot));
}
if (this.isPublic()) {
messages.add(this.buildAttributeMessage(JpaValidationMessages.PERSISTENT_ATTRIBUTE_PUBLIC_FIELD, astRoot));
}
}
}
protected IMessage buildAttributeMessage(String msgID, CompilationUnit astRoot) {
return DefaultJpaValidationMessages.buildMessage(
IMessage.HIGH_SEVERITY,
msgID,
new String[] {getName()},
this,
this.getValidationTextRange(astRoot)
);
}
// ********** Java completion proposals **********
@Override
public Iterator<String> javaCompletionProposals(int pos, Filter<String> filter, CompilationUnit astRoot) {
Iterator<String> result = super.javaCompletionProposals(pos, filter, astRoot);
if (result != null) {
return result;
}
return this.getMapping().javaCompletionProposals(pos, filter, astRoot);
}
// ********** misc overrides **********
@Override
public PersistentType getParent() {
return (PersistentType) super.getParent();
}
@Override
public void toString(StringBuilder sb) {
super.toString(sb);
sb.append(this.name);
}
}