1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.schemagen.xml.impl;
7   
8   import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
9   import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
10  import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
11  import gov.nist.secauto.metaschema.core.model.IDefinition;
12  import gov.nist.secauto.metaschema.core.model.IFieldDefinition;
13  import gov.nist.secauto.metaschema.core.model.IFlagDefinition;
14  import gov.nist.secauto.metaschema.core.model.IModelElement;
15  import gov.nist.secauto.metaschema.core.model.IModule;
16  import gov.nist.secauto.metaschema.core.model.IValuedDefinition;
17  import gov.nist.secauto.metaschema.core.model.constraint.IAllowedValue;
18  import gov.nist.secauto.metaschema.core.util.AutoCloser;
19  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
20  import gov.nist.secauto.metaschema.schemagen.AbstractGenerationState;
21  import gov.nist.secauto.metaschema.schemagen.SchemaGenerationException;
22  import gov.nist.secauto.metaschema.schemagen.SchemaGenerationFeature;
23  import gov.nist.secauto.metaschema.schemagen.xml.datatype.XmlDatatypeManager;
24  import gov.nist.secauto.metaschema.schemagen.xml.schematype.IXmlComplexType;
25  import gov.nist.secauto.metaschema.schemagen.xml.schematype.IXmlSimpleType;
26  import gov.nist.secauto.metaschema.schemagen.xml.schematype.IXmlType;
27  import gov.nist.secauto.metaschema.schemagen.xml.schematype.XmlComplexTypeAssemblyDefinition;
28  import gov.nist.secauto.metaschema.schemagen.xml.schematype.XmlComplexTypeFieldDefinition;
29  import gov.nist.secauto.metaschema.schemagen.xml.schematype.XmlSimpleTypeDataTypeReference;
30  import gov.nist.secauto.metaschema.schemagen.xml.schematype.XmlSimpleTypeDataTypeRestriction;
31  import gov.nist.secauto.metaschema.schemagen.xml.schematype.XmlSimpleTypeUnion;
32  
33  import org.codehaus.stax2.XMLStreamWriter2;
34  
35  import java.io.IOException;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.concurrent.ConcurrentHashMap;
39  
40  import javax.xml.namespace.QName;
41  import javax.xml.stream.XMLStreamException;
42  
43  import edu.umd.cs.findbugs.annotations.NonNull;
44  import edu.umd.cs.findbugs.annotations.Nullable;
45  
46  public class XmlGenerationState
47      extends AbstractGenerationState<AutoCloser<XMLStreamWriter2, SchemaGenerationException>, XmlDatatypeManager> {
48    @NonNull
49    private final String defaultNS;
50    @NonNull
51    private final Map<String, String> namespaceToPrefixMap = new ConcurrentHashMap<>();
52    @NonNull
53    private final Map<IDataTypeAdapter<?>, IXmlSimpleType> dataTypeToSimpleTypeMap = new ConcurrentHashMap<>();
54    @NonNull
55    private final Map<IValuedDefinition, IXmlSimpleType> definitionToSimpleTypeMap = new ConcurrentHashMap<>();
56    @NonNull
57    private final Map<IDefinition, IXmlType> definitionToTypeMap = new ConcurrentHashMap<>();
58  
59    private int prefixNum; // 0
60  
61    public XmlGenerationState(
62        @NonNull IModule module,
63        @NonNull AutoCloser<XMLStreamWriter2, SchemaGenerationException> writer,
64        @NonNull IConfiguration<SchemaGenerationFeature<?>> configuration) {
65      super(module, writer, configuration, new XmlDatatypeManager());
66      this.defaultNS = ObjectUtils.notNull(module.getXmlNamespace().toASCIIString());
67    }
68  
69    @SuppressWarnings("resource")
70    @NonNull
71    public XMLStreamWriter2 getXMLStreamWriter() {
72      return getWriter().getResource();
73    }
74  
75    @NonNull
76    public String getDefaultNS() {
77      return defaultNS;
78    }
79  
80    @NonNull
81    public String getDatatypeNS() {
82      return getDefaultNS();
83    }
84  
85    @SuppressWarnings("null")
86    @NonNull
87    public String getNS(@NonNull IModelElement modelElement) {
88      return modelElement.getContainingModule().getXmlNamespace().toASCIIString();
89    }
90  
91    public String getNSPrefix(String namespace) {
92      String retval = null;
93      if (!getDefaultNS().equals(namespace)) {
94        synchronized (this) {
95          retval = namespaceToPrefixMap.get(namespace);
96          if (retval == null) {
97            retval = String.format("ns%d", ++prefixNum);
98            namespaceToPrefixMap.put(namespace, retval);
99          }
100       }
101     }
102     return retval;
103   }
104 
105   @NonNull
106   protected QName newQName(
107       @NonNull String localName,
108       @NonNull String namespace) {
109     String prefix = null;
110     if (!getDefaultNS().equals(namespace)) {
111       prefix = getNSPrefix(namespace);
112     }
113 
114     return ObjectUtils.notNull(
115         prefix == null ? new QName(namespace, localName) : new QName(namespace, localName, prefix));
116   }
117 
118   @NonNull
119   protected QName newQName(
120       @NonNull IDefinition definition,
121       @Nullable String suffix) {
122     return newQName(
123         getTypeNameForDefinition(definition, suffix),
124         getNS(definition));
125   }
126 
127   public IXmlType getXmlForDefinition(@NonNull IDefinition definition) {
128     IXmlType retval = definitionToTypeMap.get(definition);
129     if (retval == null) {
130       switch (definition.getModelType()) {
131       case FIELD: {
132         IFieldDefinition field = (IFieldDefinition) definition;
133         if (field.getFlagInstances().isEmpty()) {
134           retval = getSimpleType(field);
135         } else {
136           retval = newComplexType(field);
137         }
138         break;
139       }
140       case ASSEMBLY: {
141         retval = newComplexType((IAssemblyDefinition) definition);
142         break;
143       }
144       case FLAG:
145         retval = getSimpleType((IFlagDefinition) definition);
146         break;
147       case CHOICE_GROUP:
148       case CHOICE:
149       default:
150         throw new UnsupportedOperationException(definition.getModelType().toString());
151       }
152       definitionToTypeMap.put(definition, retval);
153     }
154     return retval;
155   }
156 
157   @NonNull
158   public IXmlSimpleType getSimpleType(@NonNull IDataTypeAdapter<?> dataType) {
159     IXmlSimpleType type = dataTypeToSimpleTypeMap.get(dataType);
160     if (type == null) {
161       // lazy initialize and cache the type
162       QName qname = newQName(
163           getDatatypeManager().getTypeNameForDatatype(dataType),
164           getDatatypeNS());
165       type = new XmlSimpleTypeDataTypeReference(qname, dataType);
166       dataTypeToSimpleTypeMap.put(dataType, type);
167     }
168     return type;
169   }
170 
171   @NonNull
172   public IXmlSimpleType getSimpleType(@NonNull IValuedDefinition definition) {
173     IXmlSimpleType simpleType = definitionToSimpleTypeMap.get(definition);
174     if (simpleType == null) {
175       AllowedValueCollection allowedValuesCollection = getContextIndependentEnumeratedValues(definition);
176       List<IAllowedValue> allowedValues = allowedValuesCollection.getValues();
177 
178       IDataTypeAdapter<?> dataType = definition.getJavaTypeAdapter();
179       if (allowedValues.isEmpty()) {
180         // just use the built-in type
181         simpleType = getSimpleType(dataType);
182       } else {
183 
184         // generate a restriction on the built-in type for the enumerated values
185         simpleType = new XmlSimpleTypeDataTypeRestriction(
186             newQName(definition, null),
187             definition,
188             allowedValuesCollection);
189 
190         if (!allowedValuesCollection.isClosed()) {
191           // if other values are allowed, we need to make a union of the restriction type
192           // and the base
193           // built-in type
194           simpleType = new XmlSimpleTypeUnion(
195               newQName(definition, "Union"),
196               definition,
197               getSimpleType(dataType),
198               simpleType);
199         }
200       }
201 
202       definitionToSimpleTypeMap.put(definition, simpleType);
203     }
204     return simpleType;
205   }
206 
207   @NonNull
208   protected IXmlComplexType newComplexType(@NonNull IFieldDefinition definition) {
209     QName qname = newQName(definition, null);
210     return new XmlComplexTypeFieldDefinition(qname, definition);
211   }
212 
213   @NonNull
214   protected IXmlComplexType newComplexType(@NonNull IAssemblyDefinition definition) {
215     QName qname = newQName(definition, null);
216     return new XmlComplexTypeAssemblyDefinition(qname, definition);
217   }
218 
219   public void generateXmlTypes() throws XMLStreamException {
220 
221     for (IXmlType type : definitionToTypeMap.values()) {
222       if (!type.isInline(this) && type.isGeneratedType(this) && type.isReferenced(this)) {
223         type.generate(this);
224       } else {
225         assert !type.isGeneratedType(this) || type.isInline(this) || !type.isReferenced(this);
226       }
227     }
228     getDatatypeManager().generateDatatypes(getXMLStreamWriter());
229   }
230 
231   public void writeAttribute(@NonNull String localName, @NonNull String value) throws XMLStreamException {
232     getXMLStreamWriter().writeAttribute(localName, value);
233   }
234 
235   public void writeStartElement(@NonNull String namespaceUri, @NonNull String localName) throws XMLStreamException {
236     getXMLStreamWriter().writeStartElement(namespaceUri, localName);
237   }
238 
239   public void writeStartElement(
240       @NonNull String prefix,
241       @NonNull String localName,
242       @NonNull String namespaceUri) throws XMLStreamException {
243     getXMLStreamWriter().writeStartElement(prefix, localName, namespaceUri);
244 
245   }
246 
247   public void writeEndElement() throws XMLStreamException {
248     getXMLStreamWriter().writeEndElement();
249   }
250 
251   public void writeCharacters(@NonNull String text) throws XMLStreamException {
252     getXMLStreamWriter().writeCharacters(text);
253   }
254 
255   public void writeNamespace(String prefix, String namespaceUri) throws XMLStreamException {
256     getXMLStreamWriter().writeNamespace(prefix, namespaceUri);
257   }
258 
259   @Override
260   public void flushWriter() throws IOException {
261     try {
262       getWriter().getResource().flush();
263     } catch (XMLStreamException ex) {
264       throw new IOException(ex);
265     }
266   }
267 }