blob: 61ee0c73342882882938510148508306f3ca3cde [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2011 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.jaxb.core.internal.context.java;
import java.util.Collection;
import java.util.List;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jpt.common.core.utility.TextRange;
import org.eclipse.jpt.common.utility.Filter;
import org.eclipse.jpt.common.utility.internal.CollectionTools;
import org.eclipse.jpt.common.utility.internal.Tools;
import org.eclipse.jpt.common.utility.internal.iterables.EmptyIterable;
import org.eclipse.jpt.jaxb.core.context.JaxbAttributeMapping;
import org.eclipse.jpt.jaxb.core.context.JaxbPersistentAttribute;
import org.eclipse.jpt.jaxb.core.context.JaxbPersistentClass;
import org.eclipse.jpt.jaxb.core.context.java.DefaultJavaAttributeMappingDefinition;
import org.eclipse.jpt.jaxb.core.context.java.JavaAttributeMappingDefinition;
import org.eclipse.jpt.jaxb.core.resource.java.JavaResourceAttribute;
import org.eclipse.wst.validation.internal.provisional.core.IMessage;
import org.eclipse.wst.validation.internal.provisional.core.IReporter;
public abstract class GenericJavaPersistentAttribute
extends AbstractJavaContextNode
implements JaxbPersistentAttribute {
protected JaxbAttributeMapping mapping; // never null
protected String defaultMappingKey;
protected GenericJavaPersistentAttribute(JaxbPersistentClass parent) {
super(parent);
}
@Override
public JaxbPersistentClass getParent() {
return (JaxbPersistentClass) super.getParent();
}
public boolean isInherited() {
return getParent().isInherited(this);
}
public String getInheritedJavaResourceAttributeOwningTypeName() {
return getParent().getJavaResourceAttributeOwningTypeName(this);
}
/**
* subclasses must call this method in their constructor
*/
protected void initializeMapping() {
// keep non-null at all times
this.mapping = this.buildMapping();
}
// ********** synchronize/update **********
@Override
public void synchronizeWithResourceModel() {
super.synchronizeWithResourceModel();
this.syncMapping();
}
@Override
public void update() {
super.update();
this.updateMapping();
}
// ********** name **********
public String getName() {
return this.getJavaResourceAttribute().getName();
}
// ********** mapping **********
public JaxbAttributeMapping getMapping() {
return this.mapping;
}
/**
* Clients do not set the mapping directly.
* @see #setMappingKey(String)
*/
protected void setMapping(JaxbAttributeMapping mapping) {
JaxbAttributeMapping old = this.mapping;
this.mapping = mapping;
this.firePropertyChanged(MAPPING_PROPERTY, old, mapping);
}
public String getMappingKey() {
return this.mapping.getKey();
}
/**
* Possible transitions:
* <table border>
* <th>
* <th>null mapping/default<br>
* <code>key = null</code>
* <th>specified mapping A<br>
* <code>key = "A"</code>
* <th>specified mapping B<br>
* <code>key = "B"</code>
* <tr>
* <th>[default] null mapping
* <td>do nothing
* <td>add annotation A<br>
* set new mapping A
* <td>add annotation B<br>
* set new mapping B
* <tr>
* <th>default mapping A
* <td>do nothing
* <td>add annotation A<br>
* <em>re-use</em> default mapping A
* <td>add annotation B<br>
* set new mapping B
* <tr>
* <th>specified mapping A
* <td>remove annotation A<br>
* set new default or null mapping
* <td>do nothing
* <td>remove annotation A<br>
* add annotation B<br>
* set new mapping B
* </table>
* The "do nothing" transitions are handled in this method.
*/
public JaxbAttributeMapping setMappingKey(String key) {
if (this.mapping.isDefault()) {
if (key == null) {
// leave the default mapping unchanged
} else {
this.setMappingKey_(key); // replace the default mapping
}
} else {
if (this.valuesAreEqual(key, this.mapping.getKey())) {
// leave the specified mapping unchanged
} else {
this.setMappingKey_(key); // replace the specified mapping
}
}
return this.mapping;
}
/**
* We have either:<ul>
* <li>a <em>default</em> mapping and a non-<code>null</code> key
* </ul>or<ul>
* <li>a <em>specified</em> mapping and a different (possibly
* <code>null</code>) key
* </ul>
*/
protected void setMappingKey_(String key) {
JavaAttributeMappingDefinition definition = this.getSpecifiedMappingDefinition(key);
if (definition == null) {
// our mapping is "specified" and the key is null;
// check for a default definition
definition = this.getDefaultMappingDefinition();
Iterable<String> supportingAnnotationNames = (definition != null) ? definition.getSupportingAnnotationNames() : EmptyIterable.<String>instance();
// clear any mapping annotation(s);
// leave the "default" mapping's supporting annotations;
// if there is no "default" mapping, clear all supporting annotations too(?)
this.setMappingAnnotation(null, supportingAnnotationNames);
} else {
this.setMappingAnnotation(definition);
}
// note: 'definition' can still be null (if the key is null and there is no "default" mapping)
this.setMapping(this.buildMapping(definition));
}
/**
* pre-condition: definition is not <code>null</code>
*/
protected void setMappingAnnotation(JavaAttributeMappingDefinition definition) {
this.setMappingAnnotation(definition.getAnnotationName(), definition.getSupportingAnnotationNames());
}
protected void setMappingAnnotation(String primaryAnnotationName, Iterable<String> supportingAnnotationNames) {
this.getJavaResourceAttribute().setPrimaryAnnotation(primaryAnnotationName, supportingAnnotationNames);
}
protected JaxbAttributeMapping buildMapping(JavaAttributeMappingDefinition definition) {
return (definition == null) ? this.buildNullMapping() : this.buildMapping_(definition);
}
protected JaxbAttributeMapping buildNullMapping() {
return this.getFactory().buildJavaNullAttributeMapping(this);
}
/**
* pre-condition: definition is not null
* <p>
* If we are converting a <em>default</em> mapping to its <em>specified</em>
* manifestation, we just keep the same mapping and create its annotation.
* We do <em>not</em> do the same thing when converting a <em>specified</em>
* mapping to its <em>default</em> manifestation. We rebuild the
* entire mapping, simplifying the clearing of all its state. We do this
* because we allow clients to modify a <em>default</em> mapping (or any of
* its components) directly,
* modifying its state and triggering a conversion to a <em>specified</em>
* mapping. The only way to convert a <em>specified</em> mapping to a
* <em>default</em> mapping is by {@link #setMappingKey(String) setting the
* mapping key} to <code>null</code>.
*/
protected JaxbAttributeMapping buildMapping_(JavaAttributeMappingDefinition definition) {
// 'mapping' is null during construction
if ((this.mapping != null) && this.mapping.isDefault() && Tools.valuesAreEqual(this.mapping.getKey(), definition.getKey())) {
this.mapping.updateDefault(); // since nothing here changes, we need to update the mapping's flag
return this.mapping;
}
return definition.buildMapping(this, this.getFactory());
}
/**
* We only look for a <em>specified</em> mapping here.
* We look for a default mapping during <em>update</em>.
*/
protected JaxbAttributeMapping buildMapping() {
return this.buildMapping(this.getSpecifiedMappingDefinition());
}
/**
* Look for a <em>specified</em> mapping and sync our mapping.
*/
protected void syncMapping() {
JavaAttributeMappingDefinition definition = this.getSpecifiedMappingDefinition();
if (definition == null) {
if (this.mapping.isDefault()) {
// null/default => null/default
this.mapping.synchronizeWithResourceModel();
} else {
// specified => null/default
definition = this.getDefaultMappingDefinition();
this.setMapping(this.buildMapping(definition));
}
} else {
if (this.mapping.isDefault()) {
// null/default => specified
this.setMapping(this.buildMapping(definition));
} else {
// specified => specified
if (this.valuesAreEqual(definition.getKey(), this.mapping.getKey())) {
this.mapping.synchronizeWithResourceModel();
} else {
this.setMapping(this.buildMapping(definition));
}
}
}
}
/**
* Return the "specified" mapping definition for the specified key.
*/
protected JavaAttributeMappingDefinition getSpecifiedMappingDefinition(String key) {
if (key == null) {
return null;
}
for (JavaAttributeMappingDefinition definition : this.getSpecifiedMappingDefinitions()) {
if (Tools.valuesAreEqual(definition.getKey(), key)) {
return definition;
}
}
throw new IllegalArgumentException("invalid mapping key: " + key); //$NON-NLS-1$
}
/**
* Return the mapping definition for the mapping currently specified in the
* source code.
*/
protected JavaAttributeMappingDefinition getSpecifiedMappingDefinition() {
for (JavaAttributeMappingDefinition definition : this.getSpecifiedMappingDefinitions()) {
if (definition.isSpecified(this)) {
return definition;
}
}
return null;
}
protected Iterable<JavaAttributeMappingDefinition> getSpecifiedMappingDefinitions() {
return this.getPlatform().getSpecifiedJavaAttributeMappingDefinitions();
}
// ********** default mapping **********
public String getDefaultMappingKey() {
return this.defaultMappingKey;
}
protected void setDefaultMappingKey(String mappingKey) {
String old = this.defaultMappingKey;
this.defaultMappingKey = mappingKey;
this.firePropertyChanged(DEFAULT_MAPPING_KEY_PROPERTY, old, mappingKey);
}
/**
* If a mapping annotation is specified, we would have already set a
* <em>specified</em> mapping in {@link #syncMapping()}. We need only check
* for changes to the <em>default</em> mapping.
*/
protected void updateMapping() {
JavaAttributeMappingDefinition definition = this.getDefaultMappingDefinition();
String newDefaultKey = (definition == null) ? null : definition.getKey();
if (this.mapping.isDefault() && Tools.valuesAreDifferent(this.mapping.getKey(), newDefaultKey)) {
this.setMapping(this.buildMapping(definition)); // the default mapping has changed
} else {
this.mapping.update();
}
this.setDefaultMappingKey(newDefaultKey);
}
protected JavaAttributeMappingDefinition getDefaultMappingDefinition() {
for (DefaultJavaAttributeMappingDefinition definition : this.getDefaultMappingDefinitions()) {
if (definition.isDefault(this)) {
return definition;
}
}
return null;
}
protected Iterable<DefaultJavaAttributeMappingDefinition> getDefaultMappingDefinitions() {
return this.getPlatform().getDefaultJavaAttributeMappingDefinitions();
}
@Override
public void toString(StringBuilder sb) {
sb.append(this.getName());
}
// **************** content assist ****************************************
@Override
public Iterable<String> getJavaCompletionProposals(int pos, Filter<String> filter, CompilationUnit astRoot) {
Iterable<String> result = super.getJavaCompletionProposals(pos, filter, astRoot);
if (! CollectionTools.isEmpty(result)) {
return result;
}
result = this.mapping.getJavaCompletionProposals(pos, filter, astRoot);
if (! CollectionTools.isEmpty(result)) {
return result;
}
return EmptyIterable.instance();
}
// **************** validation ********************************************
@Override
public TextRange getValidationTextRange(CompilationUnit astRoot) {
return this.getJavaResourceAttribute().getTextRange(astRoot);
}
@Override
public void validate(List<IMessage> messages, IReporter reporter, CompilationUnit astRoot) {
super.validate(messages, reporter, astRoot);
this.getMapping().validate(messages, reporter, astRoot);
}
//**************** static methods *****************
protected static String getJavaResourceAttributeType(JavaResourceAttribute attribute) {
if (attribute.typeIsSubTypeOf(COLLECTION_CLASS_NAME)) {
if (attribute.getTypeTypeArgumentNamesSize() == 1) {
return attribute.getTypeTypeArgumentName(0);
}
return null;
}
return attribute.getTypeName();
}
private static final String COLLECTION_CLASS_NAME = Collection.class.getName();
protected static boolean typeIsArray(JavaResourceAttribute attribute) {
return attribute.typeIsArray();
}
protected static boolean typeIsSubTypeOf(JavaResourceAttribute attribute, String typeName) {
return attribute.typeIsSubTypeOf(typeName);
}
}