/**
* Copyright 2004-2016 Riccardo Solmi. All rights reserved.
* This file is part of the Whole Platform.
*
* The Whole Platform is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Whole Platform is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the Whole Platform. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whole.lang.xsd.templates;
import org.whole.lang.bindings.BindingManagerFactory;
import org.whole.lang.bindings.IBindingManager;
import org.whole.lang.builders.IBuilderOperation;
import org.whole.lang.commons.builders.ICommonsBuilder;
import org.whole.lang.commons.reflect.CommonsEntityDescriptorEnum;
import org.whole.lang.commons.reflect.CommonsFeatureDescriptorEnum;
import org.whole.lang.commons.reflect.CommonsLanguageKit;
import org.whole.lang.model.IEntity;
import org.whole.lang.parsers.DataTypeParsers;
import org.whole.lang.parsers.IDataTypeParser;
import org.whole.lang.reflect.DataKinds;
import org.whole.lang.reflect.EntityDescriptor;
import org.whole.lang.reflect.FeatureDescriptor;
import org.whole.lang.templates.ITemplate;
import org.whole.lang.templates.ModelTemplate;
import org.whole.lang.util.EntityUtils;
import org.whole.lang.util.FreshNameGenerator;
import org.whole.lang.util.WholeMessages;
import org.whole.lang.visitors.AbstractVisitor;
import org.whole.lang.xml.builders.IXmlBuilder;
import org.whole.lang.xml.builders.XmlGenericBuilderAdapter;
import org.whole.lang.xml.reflect.XmlLanguageKit;
import org.whole.lang.xsd.codebase.IMappingStrategy;
import org.whole.lang.xsd.mapping.util.MappingStrategyUtils;
import org.whole.lang.xsd.util.NamespaceUtils;
/**
* @author Enrico Persiani, Riccardo Solmi
*/
public class XsiModelTemplate extends AbstractVisitor implements ITemplate {
protected IEntity model;
protected IXmlBuilder builder;
protected String encoding;
protected IBindingManager nsPrefixes;
protected FreshNameGenerator fnGen;
public XsiModelTemplate(IEntity model) {
this(model, "UTF-8");
}
public XsiModelTemplate(IEntity model, String encoding) {
this.model = model;
this.encoding = encoding;
nsPrefixes = BindingManagerFactory.instance.createBindingManager();
fnGen = new FreshNameGenerator();
}
protected void putNamespacePrefix(String uri, String prefix) {
nsPrefixes.wDefValue(uri, prefix);
}
protected String getNamespacePrefix(IEntity entity, String uri) {
if (!hasNamespacePrefix(entity, uri))
putNamespacePrefix(uri, fnGen.nextFreshName("ns"));//FIXME add a preferred prefix registry
return nsPrefixes.wStringValue(uri);
}
protected boolean hasNamespacePrefix(IEntity entity, String uri) {
return nsPrefixes.wIsSet(uri);
}
protected void addNamespaceDeclaration(IEntity entity, String languageURI) {
if (!NamespaceUtils.isInternalNamespace(languageURI))
createNamespaceDeclaration(getNamespacePrefix(entity, languageURI), languageURI);
}
protected void addSchemaLocationDeclaration(IEntity entity, String languageURI) {
if (NamespaceUtils.isInternalNamespace(languageURI))
createNoNamespaceSchemalLocation(entity, languageURI);
else
createSchemalLocation(entity, languageURI);
}
protected boolean needSchemaLocationDeclaration(IMappingStrategy strategy, EntityDescriptor<?> context, EntityDescriptor<?> ed, FeatureDescriptor fd) {
//TODO check for imported or explicitly referenced namespaces when any types are implemented
// by now testing for a parent RootFragment will suffice
return CommonsEntityDescriptorEnum.RootFragment.equals(context);
}
public void apply(IBuilderOperation operation) {
IXmlBuilder builder = (IXmlBuilder) operation.wGetBuilder(XmlLanguageKit.URI);
ICommonsBuilder cb = (ICommonsBuilder) operation.wGetBuilder(CommonsLanguageKit.URI);
if (EntityUtils.hasParent(model) && MappingStrategyUtils.hasElementNCName(model)) {
builder.Document_();
builder.Prolog_();
builder.XMLDecl_();
builder.Version("1.0");
builder.Encoding(encoding);
cb.Resolver();
builder._XMLDecl();
cb.Resolver();
cb.Resolver();
builder._Prolog();
apply(builder);
builder._Document();
} else
apply(builder);
}
public void apply(IXmlBuilder builder) {
this.builder = builder;
visit(model);
}
public void visit(IEntity entity) {
IEntity adaptee = entity.wGetAdaptee(false);
EntityDescriptor<?> adapteeEd = adaptee.wGetEntityDescriptor();
if (adapteeEd.getLanguageKit().getURI().equals(CommonsLanguageKit.URI)) {
switch (adapteeEd.getOrdinal()) {
case CommonsEntityDescriptorEnum.Resolver_ord:
return;
case CommonsEntityDescriptorEnum.SameStageFragment_ord:
case CommonsEntityDescriptorEnum.RootFragment_ord:
case CommonsEntityDescriptorEnum.StageDownFragment_ord:
case CommonsEntityDescriptorEnum.StageUpFragment_ord:
// String lang = entity.wGetLanguageKit().getURI();
IEntity root = entity.wGetRoot();
if (XmlLanguageKit.URI.equals(root.wGetLanguageKit().getURI())) {
ModelTemplate template = new ModelTemplate(root);
template.apply(new XmlGenericBuilderAdapter(builder));
} else
visit(root);
return;
}
}
EntityDescriptor<?> ed = entity.wGetEntityDescriptor();
EntityDescriptor<?> context;
FeatureDescriptor fd;
String languageURI;
IEntity parent = entity.wGetParent();
if (EntityUtils.isNull(parent) ||
CommonsEntityDescriptorEnum.RootFragment.equals(parent.wGetEntityDescriptor()) ||
CommonsEntityDescriptorEnum.StageUpFragment.equals(parent.wGetEntityDescriptor())) {
context = CommonsEntityDescriptorEnum.RootFragment;
fd = CommonsFeatureDescriptorEnum.rootEntity;
// get languageURI from parent entity descriptor but from RootFragment
languageURI = ed.getLanguageKit().getURI();
} else {
context = parent.wGetEntityDescriptor();
// always skip SameStageFragment parent
if (CommonsEntityDescriptorEnum.SameStageFragment.equals(context)) {
IEntity parentParent = parent.wGetParent();
context = parentParent.wGetEntityDescriptor();
fd = parentParent.wGetFeatureDescriptor(parent);
languageURI = context.getLanguageKit().getURI();
} else {
fd = parent.wGetFeatureDescriptor(entity);
languageURI = parent.wGetEntityDescriptor().getLanguageKit().getURI();
}
}
IMappingStrategy strategy = getXsiMappingStrategy(languageURI);
//FIXME workaround
// if (!hasNCName(strategy, context, ed, fd)) {
if (getElementNCName(entity) == null) {
if (isMixedType(strategy, context, ed))
builder.CharData(toContentValue(entity, strategy));
else {
final int size = entity.wSize();
for (int i=0; i<size; i++)
visit(entity.wGet(i));
}
} else {
nsPrefixes.wEnterScope();
boolean needNamespacePrefix = (strategy.isElementsFormQualified() /*TODO or we are the root of an anyType mapping */) && !NamespaceUtils.isInternalNamespace(languageURI);
boolean needNamespaceDeclaration = needNamespacePrefix && !hasNamespacePrefix(entity, strategy.getNamespace());
builder.Element_();
String tagName = getElementNCName(entity);
if (needNamespacePrefix) {
String prefix = getNamespacePrefix(entity, languageURI);
builder.QualifiedName_();
builder.NameSpace(prefix);
builder.Name(tagName);
builder._QualifiedName();
} else
builder.Name(tagName);
builder.Attributes_();
if (needNamespaceDeclaration)
addNamespaceDeclaration(entity, languageURI);
if (needSchemaLocationDeclaration(strategy, context, ed, fd))
addSchemaLocationDeclaration(entity, languageURI);
if (entity.wGetEntityKind().isData()) {
builder._Attributes();
builder.CharData(toContentValue(entity, strategy));
} else {
applyAttributes(entity, 0);
builder._Attributes();
builder.Content_();
final int size = entity.wSize();
for (int i=0; i<size; i++) {
context = entity.wGetEntityDescriptor();
fd = context.getEntityFeatureDescriptor(i);
IEntity child = entity.wGet(i);
ed = child.wGetEntityDescriptor();
if (!isAttributeMapping(strategy, context, ed, fd)) {
if (isContentMapping(strategy, context, ed, fd))
builder.CharData(toContentValue(child, strategy));
else
visit(child);
}
}
builder._Content();
}
builder._Element();
nsPrefixes.wExitScope();
}
}
protected void applyAttributes(IEntity entity, int fromIndex) {
final int size = entity.wSize();
for (int i=fromIndex; i<size; i++) {
EntityDescriptor<?> context = entity.wGetEntityDescriptor();
FeatureDescriptor fd = entity.wGetFeatureDescriptor(i);
IEntity child = entity.wGet(i);
EntityDescriptor<?> ed = child.wGetEntityDescriptor();
String languageURI = ed.getLanguageKit().getURI();
//FIXME workaround for resolvers and other adapted entities
if (CommonsLanguageKit.URI.equals(ed.getLanguageKit().getURI())) {
ed = child.wGetParent().wGetEntityDescriptor();
languageURI = ed.getLanguageKit().getURI();
}
IMappingStrategy strategy = getXsiMappingStrategy(languageURI);
if (isAttributeMapping(strategy, context, ed, fd)) {
if (!EntityUtils.isResolver(child) || !fd.isOptional()) {
builder.Attribute_();
String attrName = getAttributeNCName(strategy, context, ed, fd);
//TODO add any attribute support
boolean needNamespacePrefix = strategy.isAttributesFormQualified() && !NamespaceUtils.isInternalNamespace(languageURI);
if (needNamespacePrefix) {
String prefix = getNamespacePrefix(child, languageURI);
builder.QualifiedName_();
builder.NameSpace(prefix);
builder.Name(attrName);
builder._QualifiedName();
} else
builder.Name(attrName);
builder.Value(toAttributeValue(child, strategy));
builder._Attribute();
}
}
}
}
protected String getAttributeNCName(IMappingStrategy strategy, EntityDescriptor<?> context,
EntityDescriptor<?> ed, FeatureDescriptor fd) {
return strategy.getAttributeNCName(context, ed, fd);
}
protected String getElementNCName(IEntity entity) {
return MappingStrategyUtils.getElementNCName(entity);
}
protected boolean hasNCName(IMappingStrategy strategy, EntityDescriptor<?> context,
EntityDescriptor<?> ed, FeatureDescriptor fd) {
return isElementMapping(strategy, context, ed, fd) || isAttributeMapping(strategy, context, ed, fd);
}
protected boolean isElementMapping(IMappingStrategy strategy, EntityDescriptor<?> context,
EntityDescriptor<?> ed, FeatureDescriptor fd) {
return strategy.isElementMapping(context, ed, fd);
}
protected boolean isAttributeMapping(IMappingStrategy strategy, EntityDescriptor<?> context,
EntityDescriptor<?> ed, FeatureDescriptor fd) {
return strategy.isAttributeMapping(context, ed, fd);
}
protected boolean isContentMapping(IMappingStrategy strategy, EntityDescriptor<?> context,
EntityDescriptor<?> ed, FeatureDescriptor fd) {
return strategy.isContentMapping(context, ed, fd);
}
protected boolean isMixedStructuralMapping(IMappingStrategy strategy, EntityDescriptor<?> context,
EntityDescriptor<?> ed, FeatureDescriptor fd) {
return strategy.isMixedStructuralMapping(context, ed, fd);
}
protected boolean isMixedType(IMappingStrategy strategy, EntityDescriptor<?> context,
EntityDescriptor<?> ed) {
return strategy.isMixedType(context) && ed.equals(strategy.getMixedDataType());
}
protected String toAttributeValue(IEntity entity, IMappingStrategy strategy) {
switch (entity.wGetEntityKind()) {
case DATA:
return toContentValue(entity, strategy);
case COMPOSITE:
StringBuilder sb = new StringBuilder();
final int size = entity.wSize();
for (int i=0; i<size; i++) {
if (i>0)
sb.append(' ');
sb.append(toContentValue(entity.wGet(i), strategy));
}
return sb.toString();
default:
throw new IllegalStateException("Wrong entity kind");
}
}
protected String toContentValue(IEntity entity, IMappingStrategy strategy) {
EntityDescriptor<?> ed = entity.wGetEntityDescriptor();
DataKinds dataKind = ed.getDataKind();
if (!dataKind.isNotAData()) {
IDataTypeParser dataTypeParser = ed.getLanguageKit().getDataTypeParser(DataTypeParsers.PERSISTENCE);
switch (dataKind) {
case BOOLEAN:
return dataTypeParser.unparseBoolean(ed, entity.wBooleanValue());
case BYTE:
return dataTypeParser.unparseByte(ed, entity.wByteValue());
case SHORT:
return dataTypeParser.unparseShort(ed, entity.wShortValue());
case INT:
return dataTypeParser.unparseInt(ed, entity.wIntValue());
case LONG:
return dataTypeParser.unparseLong(ed, entity.wLongValue());
case DOUBLE:
return dataTypeParser.unparseDouble(ed, entity.wDoubleValue());
case FLOAT:
return dataTypeParser.unparseFloat(ed, entity.wFloatValue());
case STRING:
return dataTypeParser.unparseString(ed, entity.wStringValue());
case OBJECT:
return dataTypeParser.unparseObject(ed, entity.wGetValue());
case ENUM_VALUE:
return dataTypeParser.unparseEnumValue(ed, entity.wEnumValue());
case DATE:
case CHAR:
}
}
throw new IllegalStateException(WholeMessages.no_data_type);
}
protected IMappingStrategy getXsiMappingStrategy(String uri) {
IMappingStrategy strategy = MappingStrategyUtils.getMappingStrategy(uri);
if (strategy == null)
throw new IllegalArgumentException("cannot find a mapping strategy for "+uri);
return strategy;
}
protected void createNamespaceDeclaration(String prefix, String languageURI) {
// never "xml" namespace declarations
if (NamespaceUtils.isXmlNamespace(languageURI))
return;
builder.Attribute_();
if (NamespaceUtils.isDefaultNamespacePrefix(prefix))
builder.Name("xmlns");
else {
builder.QualifiedName_();
builder.NameSpace("xmlns");
builder.Name(prefix);
builder._QualifiedName();
}
builder.Value(languageURI);
builder._Attribute();
}
protected void createXsiDirective(IEntity entity, String directive, String value) {
if (!hasNamespacePrefix(entity, NamespaceUtils.XSI_NAMESPACE_URI)) {
createNamespaceDeclaration("xsi", NamespaceUtils.XSI_NAMESPACE_URI);
putNamespacePrefix(NamespaceUtils.XSI_NAMESPACE_URI, "xsi");
}
builder.Attribute_();
builder.QualifiedName_();
builder.NameSpace("xsi");
builder.Name(directive);
builder._QualifiedName();
builder.Value(value);
builder._Attribute();
}
protected void createSchemalLocation(IEntity entity, String languageURI) {
String schemaLocationURI = getXsiMappingStrategy(languageURI).getSchemaLocation();
if (schemaLocationURI != null && schemaLocationURI.length() > 0)
createXsiDirective(entity, "schemaLocation", languageURI+' '+schemaLocationURI);
}
protected void createNoNamespaceSchemalLocation(IEntity entity, String languageURI) {
String schemaLocationURI = getXsiMappingStrategy(languageURI).getSchemaLocation();
if (schemaLocationURI != null && schemaLocationURI.length() > 0) {
createXsiDirective(entity, "noNamespaceSchemaLocation", schemaLocationURI);
putNamespacePrefix(languageURI, "noNamespace");
}
}
}