/******************************************************************************
* Copyright (c) 2016 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:
* Konstantin Komissarchik - initial implementation and ongoing maintenance
* Ling Hao - [338605] The include directive not handled in XML Schema parsing (regression)
* [337232] Certain schema causes elements to be out of order in corresponding xml files
******************************************************************************/
package org.eclipse.sapphire.modeling.xml.schema;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.xml.namespace.QName;
/**
* @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a>
*/
public final class XmlDocumentSchema
{
private String namespace;
private String schemaLocation;
private final Map<String,String> importedNamespaces;
private final Map<String,XmlContentModel> contentModels;
private final Map<String,XmlElementDefinition> topLevelElements;
private XmlDocumentSchema( final String namespace,
final String schemaLocation,
final Map<String,String> importedNamespaces,
final Map<String,XmlContentModel.Factory> contentModels,
final List<XmlElementDefinition.Factory> topLevelElements )
{
this.namespace = namespace;
this.schemaLocation = schemaLocation;
this.importedNamespaces = new TreeMap<String,String>( importedNamespaces );
this.contentModels = new TreeMap<String,XmlContentModel>();
for( Map.Entry<String,XmlContentModel.Factory> entry : contentModels.entrySet() )
{
final XmlContentModel.Factory factory = entry.getValue();
if( factory != null )
{
this.contentModels.put( entry.getKey(), optimize( factory.create( this ) ) );
}
}
this.topLevelElements = new TreeMap<String,XmlElementDefinition>();
for( XmlElementDefinition.Factory factory : topLevelElements )
{
final XmlElementDefinition def = (XmlElementDefinition) optimize( factory.create( this ) );
this.topLevelElements.put( def.getName().getLocalPart(), def );
}
for ( Map.Entry<String, XmlElementDefinition> map : this.topLevelElements.entrySet() ) {
final XmlElementDefinition definition = map.getValue();
if (definition.isAbstract()) {
List<XmlElementDefinition> substitutionList = new ArrayList<XmlElementDefinition>();
for ( Map.Entry<String, XmlElementDefinition> map2 : this.topLevelElements.entrySet() ) {
final XmlElementDefinition definition2 = map2.getValue();
if (definition.getName().equals(definition2.getSubstitutionGroup())) {
XmlElementDefinition.Factory def = new XmlElementDefinitionByReference.Factory();
def.setName(definition2.getName());
def.setMinOccur(definition2.getMinOccur());
def.setMaxOccur(definition2.getMaxOccur());
substitutionList.add((XmlElementDefinition)def.create(this));
}
}
definition.setSubstitutionList(substitutionList);
}
}
}
public String getNamespace()
{
return this.namespace;
}
public String getSchemaLocation()
{
return this.schemaLocation;
}
public String getSchemaLocation( final String namespace )
{
String res;
if( namespace.equals( this.namespace ) )
{
res = this.schemaLocation;
}
else
{
res = this.importedNamespaces.get( namespace );
if( res == null )
{
res = namespace;
}
}
return res;
}
public Map<String,String> getSchemaLocations()
{
final Map<String,String> schemaLocations = new TreeMap<String,String>();
if( this.namespace != null && this.schemaLocation != null )
{
schemaLocations.put( this.namespace, this.schemaLocation );
}
schemaLocations.putAll( this.importedNamespaces );
return Collections.unmodifiableMap( schemaLocations );
}
public XmlElementDefinition getElement( final String name )
{
return this.topLevelElements.get( name );
}
public XmlContentModel getContentModel( final String name )
{
return this.contentModels.get( name );
}
@Override
public String toString()
{
final StringBuilder buf = new StringBuilder();
for( XmlElementDefinition xmlElementSchema : this.topLevelElements.values() )
{
xmlElementSchema.toString( buf, "" );
buf.append( "\n\n" );
}
for( Map.Entry<String,XmlContentModel> entry : this.contentModels.entrySet() )
{
buf.append( entry.getKey() );
buf.append( " = " );
entry.getValue().toString( buf, "" );
buf.append( "\n\n" );
}
return buf.toString();
}
private XmlContentModel optimize( final XmlContentModel contentModel )
{
if( contentModel instanceof XmlSequenceGroup )
{
final XmlSequenceGroup sequenceContentModel = (XmlSequenceGroup) contentModel;
final List<XmlContentModel> nestedContent = new ArrayList<XmlContentModel>();
boolean optimized = false;
for( XmlContentModel child : sequenceContentModel.getNestedContent() )
{
boolean handled = false;
if( child instanceof XmlSequenceGroup )
{
final XmlSequenceGroup cs = (XmlSequenceGroup) child;
if( cs.getMinOccur() == 1 && cs.getMaxOccur() == 1 )
{
for( XmlContentModel nested : cs.getNestedContent() )
{
nestedContent.add( optimize( nested ) );
}
handled = true;
optimized = true;
}
}
if( ! handled )
{
final XmlContentModel optimizedChild = optimize( child );
if( optimizedChild != child )
{
optimized = true;
}
nestedContent.add( optimizedChild );
}
}
if( optimized )
{
return new XmlSequenceGroup( sequenceContentModel.getSchema(), sequenceContentModel.getMinOccur(), sequenceContentModel.getMaxOccur(), nestedContent );
}
}
else if( contentModel instanceof XmlChoiceGroup )
{
final XmlChoiceGroup choiceContentModel = (XmlChoiceGroup) contentModel;
final List<XmlContentModel> nestedContent = new ArrayList<XmlContentModel>();
boolean optimized = false;
for( XmlContentModel child : choiceContentModel.getNestedContent() )
{
final XmlContentModel optimizedChild = optimize( child );
if( optimizedChild != child )
{
optimized = true;
}
nestedContent.add( optimizedChild );
}
if( optimized )
{
return new XmlChoiceGroup( choiceContentModel.getSchema(), choiceContentModel.getMinOccur(), choiceContentModel.getMaxOccur(), nestedContent );
}
}
return contentModel;
}
public static final class Factory
{
private String namespace;
private String schemaLocation;
private final Map<String,String> importedNamespaces = new TreeMap<String,String>();
private final Map<String,XmlContentModel.Factory> contentModels = new TreeMap<String,XmlContentModel.Factory>();
private final List<XmlElementDefinition.Factory> topLevelElements = new ArrayList<XmlElementDefinition.Factory>();
public String getNamespace()
{
return this.namespace;
}
public void setNamespace( final String namespace )
{
this.namespace = namespace;
}
public String getSchemaLocation()
{
return this.schemaLocation;
}
public void setSchemaLocation( final String schemaLocation )
{
this.schemaLocation = schemaLocation;
}
public Map<String,String> getImportedNamespaces()
{
return Collections.unmodifiableMap( this.importedNamespaces );
}
public void addImportedNamespace( final String namespace,
final String schemaLocation )
{
this.importedNamespaces.put( namespace, schemaLocation );
}
public Map<String,XmlContentModel.Factory> getContentModels()
{
return Collections.unmodifiableMap( this.contentModels );
}
public XmlContentModel.Factory getContentModel( final String name )
{
return this.contentModels.get( name );
}
public void addContentModel( final String name,
final XmlContentModel.Factory contentModel )
{
this.contentModels.put( name, contentModel );
}
public void removeContentModel( final String name )
{
this.contentModels.remove( name );
}
public QName createContentModelName()
{
int counter = 1;
String contentModelName = null;
do
{
contentModelName = "##@" + String.valueOf( counter );
counter++;
}
while( this.contentModels.containsKey( contentModelName ) );
this.contentModels.put( contentModelName, null );
return new QName( this.namespace, contentModelName );
}
public List<XmlElementDefinition.Factory> getTopLevelElements()
{
return Collections.unmodifiableList( this.topLevelElements );
}
public void addTopLevelElement( final XmlElementDefinition.Factory topLevelElement )
{
this.topLevelElements.add( topLevelElement );
}
public XmlDocumentSchema create()
{
return new XmlDocumentSchema( this.namespace, this.schemaLocation, this.importedNamespaces, this.contentModels, this.topLevelElements );
}
}
}