001/*
002 * Copyright c 2018 Rusi Popov, MDA Tools.net All rights reserved.
003 *
004 * This program and the accompanying materials are made available under the terms of the
005 * Eclipse Public License v2.0 which accompanies this distribution, and is available at
006 * http://www.eclipse.org/legal/epl-v20.html
007 */
008package net.mdatools.modelant.uml13.reverse;
009
010import java.io.File;
011import java.io.IOException;
012import java.net.URI;
013import java.net.URL;
014import java.util.IdentityHashMap;
015import java.util.Iterator;
016import java.util.Map;
017import java.util.StringTokenizer;
018import java.util.logging.Level;
019import java.util.logging.Logger;
020
021import javax.jmi.reflect.RefPackage;
022
023import org.omg.uml13.foundation.core.Attribute;
024import org.omg.uml13.foundation.core.Classifier;
025import org.omg.uml13.foundation.core.ModelElement;
026import org.omg.uml13.foundation.core.UmlAssociation;
027import org.omg.uml13.foundation.datatypes.ChangeableKindEnum;
028import org.omg.uml13.foundation.datatypes.ScopeKindEnum;
029import org.omg.uml13.foundation.datatypes.VisibilityKindEnum;
030import org.omg.uml13.modelmanagement.UmlPackage;
031import org.xml.sax.Attributes;
032import org.xml.sax.ContentHandler;
033import org.xml.sax.EntityResolver;
034import org.xml.sax.ErrorHandler;
035import org.xml.sax.InputSource;
036import org.xml.sax.Locator;
037import org.xml.sax.SAXException;
038import org.xml.sax.SAXParseException;
039
040import com.sun.xml.xsom.XSAttContainer;
041import com.sun.xml.xsom.XSAttGroupDecl;
042import com.sun.xml.xsom.XSAttributeDecl;
043import com.sun.xml.xsom.XSAttributeUse;
044import com.sun.xml.xsom.XSComplexType;
045import com.sun.xml.xsom.XSComponent;
046import com.sun.xml.xsom.XSContentType;
047import com.sun.xml.xsom.XSDeclaration;
048import com.sun.xml.xsom.XSElementDecl;
049import com.sun.xml.xsom.XSFacet;
050import com.sun.xml.xsom.XSListSimpleType;
051import com.sun.xml.xsom.XSModelGroup;
052import com.sun.xml.xsom.XSParticle;
053import com.sun.xml.xsom.XSRestrictionSimpleType;
054import com.sun.xml.xsom.XSSchema;
055import com.sun.xml.xsom.XSSchemaSet;
056import com.sun.xml.xsom.XSSimpleType;
057import com.sun.xml.xsom.XSTerm;
058import com.sun.xml.xsom.XSType;
059import com.sun.xml.xsom.XSUnionSimpleType;
060import com.sun.xml.xsom.parser.AnnotationContext;
061import com.sun.xml.xsom.parser.AnnotationParser;
062import com.sun.xml.xsom.parser.AnnotationParserFactory;
063import com.sun.xml.xsom.parser.XSOMParser;
064
065import net.mdatools.modelant.core.api.Function;
066import net.mdatools.modelant.core.util.key.GenerateUniqueName;
067import net.mdatools.modelant.repository.api.ModelFactory;
068import net.mdatools.modelant.repository.api.ModelRepository;
069
070/**
071 * Reverse engineer a XML schema and storing the results as UML
072 * 1.3 objects. The model produced is in fact a Platform Specific Model, which might need additional
073 * processing and tuning.
074 * <p>
075 * Conventions for the model produced: <ul>
076 * <li> The model elements (classes, association names) that represent elements in the output XML
077 *      are marked with &lt;&lt;element&gt;&gt; stereotype
078 * <li> For representation purposes local types (UML classes) could be introduced for XSD groups, unions,
079 *      local / inlined types. All of them are marked with &lt;&lt;local type&gt;&gt; stereotype
080 * <li>The column types are converted to DataType instances named: &lt;type
081 *     name&gt;[_&lt;column size&gt;[_&lt;column precision&gt;]]. Additionally as tagged values named
082 *     TAG_VALUE_DATA_TYPE_SIZE and TAG_VALUE_DATA_TYPE_PRECISION these values are bound to the concrete
083 *     data type.
084 * <li>The TAG_VALUE_DATA_TYPE_PRECISION tagged value is optional. When not provided, the precision
085 *     should be treated as 0
086 * <li>Any comments found while reverse engineering the XSD are bound as 'documentation' tagged
087 *     values. These tagged values are compatible with the Rose's approach to documentation. They are
088 *     optional.
089 * </ul>
090 * @author Rusi Popov (popovr@mdatools.net)
091 */
092public class ReverseXsdOperation implements Function<File,RefPackage> {
093
094  static final Logger LOGGER = Logger.getLogger( ReverseXsdOperation.class.getName() );
095
096  /**
097   * The <code>ANONYMOUS_CLASS_NAME_PREFIX</code> holds the common name
098   * of the anonymous types.
099   */
100  private static final String ANONYMOUS_CLASS_NAME_PREFIX = "AnonymousType";
101
102  /**
103   * The type name suffix to use when inferring the name of an originally anonymous type
104   */
105  private static final String INFERRED_CLASS_NAME_SUFFIX = "Type";
106  private static final String INFERRED_GROUP_NAME_SUFFIX = "Group";
107  private static final String INFERRED_UNION_NAME_SUFFIX = "United";
108  private static final String EMPTY_ENUM_NAME_PREFIX = "EMPTY";
109  private static final String ENUM_NAME_PREFIX = "ENUM_";
110
111  private final ModelRepository modelRepository;
112
113  /**
114   * Maps XSD types to corresponding UML classes
115   */
116  private final Map<XSComponent,Classifier> typeToClassMap = new IdentityHashMap<XSComponent, Classifier>();
117
118  /**
119   * Generates unique class names for anonymous XSD types
120   */
121  private final GenerateUniqueName uniqueNamesGenerator = new GenerateUniqueName();
122
123  /**
124   * The common type AnyType, string provided by the schema implementation
125   */
126  private XSType anyType;
127  private XSType stringType;
128  private Uml13ModelFactory factory;
129
130  /**
131   * @param modelRepository not null
132   */
133  public ReverseXsdOperation(ModelRepository modelRepository) {
134    assert modelRepository != null : "Expected non-nulll model repository";
135
136    this.modelRepository = modelRepository;
137  }
138
139  public RefPackage execute(File schemaFile) throws IllegalArgumentException {
140    RefPackage result;
141    Iterator<XSSchema> schemasIterator;
142    XSOMParser parser;
143    XSSchemaSet schemaSet;
144    XSSchema schema;
145    ModelFactory modelFactory;
146
147    modelFactory = modelRepository.loadMetamodel("UML13");
148    result = modelFactory.instantiate();
149
150    factory = new Uml13ModelFactory( result);
151
152    parser = constructSchemaParser();
153    try {
154      parser.parse( schemaFile );
155      schemaSet = parser.getResult();
156
157    } catch (SAXException|IOException ex) {
158      throw new IllegalArgumentException(ex);
159    }
160
161    if ( schemaSet != null ) {
162      anyType = schemaSet.getAnyType();
163      stringType = schemaSet.getType( "http://www.w3.org/2001/XMLSchema", "string" );
164
165      schemasIterator = schemaSet.iterateSchema();
166      while ( schemasIterator.hasNext() ) {
167        schema = schemasIterator.next();
168        describeSchema( schema );
169      }
170    } else {
171      throw new IllegalArgumentException( "No valid/complete schemas parsed");
172    }
173    return result;
174  }
175
176  private XSOMParser constructSchemaParser() {
177    XSOMParser parser;
178
179    parser = new XSOMParser();
180    parser.setErrorHandler( new ParserErrorListerner() );
181    parser.setEntityResolver( new SimpleEntityResolver() );
182    parser.setAnnotationParser( new SimpleAnnotationParserFactory() );
183
184    return parser;
185  }
186
187  /**
188   * Describes the contents of the schema in UML
189   *
190   * @param schema
191   */
192  private void describeSchema(XSSchema schema) {
193    XSType type;
194    XSElementDecl elementDecl;
195    Iterator<XSType> typeIterator;
196    Iterator<XSElementDecl> elementIterator;
197
198    declareTargetNamespacePackage( schema );
199
200    // declare/register all GLOBAL primitive & complex types as classes
201    typeIterator = schema.iterateTypes();
202    while ( typeIterator.hasNext() ) {
203      type = typeIterator.next();
204      declareType( type );
205    }
206
207    // describe the contents and associations all GLOBAL primitive & complex types as classes
208    typeIterator = schema.iterateTypes();
209    while ( typeIterator.hasNext() ) {
210      type = typeIterator.next();
211
212      defineType( type, locateType( type ) );
213    }
214
215    // describe all top-level elements as objects
216    elementIterator = schema.iterateElementDecls();
217    while ( elementIterator.hasNext() ) {
218      elementDecl = elementIterator.next();
219      createElement( elementDecl );
220    }
221  }
222
223  /**
224   * Declare the target namespace as a package and collect the top-level comments in it
225   * @param schema not null
226   */
227  private void declareTargetNamespacePackage(XSSchema schema) {
228    UmlPackage namespace;
229
230    namespace = factory.constructPackage( formatPackageName( schema.getTargetNamespace() ) );
231    assignDocumentation( namespace, schema );
232  }
233
234  /**
235   * @param type
236   * @return the UML class declared for the type. Returns NULL if the type
237   *         has not been declared.
238   */
239  private Classifier locateType(XSComponent type) {
240    Classifier result;
241
242    result = typeToClassMap.get( type );
243    return result;
244  }
245
246  /**
247   * Instantiate the UML class that represents the XSD type.
248   * Bind it with the representation of its XSD type.
249   * When representing a anonymous class, a unique name is generated.
250   * PRE-CONDITION:<ul>
251   * <li> the type has not been declared yet
252   * </ul>
253   * @param type is not null XSD type to represent
254   */
255  private Classifier declareType(XSDeclaration type) {
256    Classifier result;
257    UmlPackage namespace;
258    String simpleTypeName;
259
260    namespace = factory.constructPackage( formatPackageName( type.getTargetNamespace() ) );
261
262    simpleTypeName = formatClassName( type.getName() );
263
264    LOGGER.log( Level.FINE, "Declare type: {0}", simpleTypeName);
265
266    result = factory.constructClass( namespace, simpleTypeName );
267    bindType( type, result );
268
269    return result;
270  }
271
272  /**
273   * Declare the type as a local class within the class provided.
274   * This method is meaningful when processing anonymous element types
275   * @param type is a non-null class where locateType( type ) == null
276   * @param defaultName is the non-null name of the type to create if the type has no name
277   * @param umlClass is the class where the local class is declared in the XSD
278   */
279  private Classifier declareLocalType(XSType type, String defaultName, Classifier umlClass) {
280    Classifier result;
281    UmlPackage namespace;
282    String simpleTypeName;
283
284    if ( type.getName() != null && !type.getName().isEmpty() ) {
285      defaultName = type.getName();
286    }
287    // NOTE: even though the class could be local, we create it as top-level one
288    //       because Rose could not import its attributes otherwise.
289    simpleTypeName = formatClassName( defaultName );
290
291    LOGGER.log( Level.FINE, "Declare local type: {0}", simpleTypeName);
292
293    namespace = factory.constructPackage( formatPackageName( type.getTargetNamespace() ));
294    result = factory.constructClass( namespace, simpleTypeName );
295    result.setAbstract( true );
296    bindType( type, result );
297
298    factory.constructTagDocumentation( result, "An anonymous type in the XSD" );
299
300    return result;
301  }
302
303  /**
304   * This method binds the XML type to the UML class provided. Note that
305   * in some  cases of types declaration like
306   * <complexType>
307   *   <simpleContents>
308   *     <restriction base="...">
309   *     ...
310   *     </...>
311   *   </...>
312   * </...>
313   * The complex type and the simple contents (type) represent actually the
314   * same UML class.
315   * @param type is non-null type
316   * @param result is non-null UML class to bind
317   */
318  private void bindType(XSComponent type, Classifier result) {
319    typeToClassMap.put( type, result );
320
321    assignDocumentation( result, type );
322  }
323
324  /**
325   * Describe/convert to model the details of the XSD type provided
326   * PRE-CONDITION:<ul>
327   * <li> The type was declared, i.e. the corresponding UML class has
328   *      been assigned to the class
329   * </ul>
330   * @param type
331   * @param intoClass is the non-null UML class to describe the XSD type into
332   */
333  private Classifier defineType(XSType type, Classifier intoClass) {
334
335    LOGGER.log( Level.FINE, "Define type: {0}", type);
336
337    if ( type.isComplexType() ) {
338      processComplexType( type.asComplexType(), intoClass );
339    } else {
340      processSimpleType( type.asSimpleType(), intoClass );
341    }
342    return intoClass;
343  }
344
345  /**
346   * Describes a simple as a class
347   *
348   * @param type is the non-null simple type to describe
349   * @param intoClass is not-null
350   * @see com.sun.xml.xsom.visitor.XSContentTypeVisitor#simpleType(com.sun.xml.xsom.XSSimpleType)
351   */
352  private void processSimpleType(XSSimpleType type, Classifier intoClass) {
353
354    if ( type.isRestriction() ) {
355      assignSuperclass( type, intoClass );
356    }
357
358    // this splitting of the processing is needed because of a bug in XSOM,
359    // that wrongly identifies the supertype when XSD type is just a simple
360    // content of a complex type. Thus, we cannot leave processSimpleContent()
361    // to define the superclass - see the calling methods
362    processSimpleContent( type, intoClass );
363  }
364
365  /**
366   * Process this as the contents of a simple class. Note that it does not bind
367   * to the superclass. The reason is that when type is a simple contents of
368   * a complex type defined the superclass is just wrongly identified. Therefore,
369   * the processing of the simple class is split into two: binding to the superclass
370   * (here it is defined correctly) and processing of its contents.
371   * @param type
372   * @param intoClass is the non-null UML class to desccribe this XSD type into
373   */
374  private void processSimpleContent(XSSimpleType type, Classifier intoClass) {
375    Classifier superClass;
376    Classifier unitedClass;
377
378    Iterator<XSFacet> facetsItrator;
379    Iterator<XSSimpleType> unitedTypesItrator;
380    XSFacet facet;
381    XSSimpleType unitedType;
382    UmlAssociation assoc;
383
384    // describe restrictions
385    if ( type.isRestriction() ) {
386      LOGGER.log( Level.FINE, "Restriction");
387
388      // describe the restrictions as vegas known tagged values
389      facetsItrator = ((XSRestrictionSimpleType) type).iterateDeclaredFacets();
390      while ( facetsItrator.hasNext() ) {
391        facet = facetsItrator.next();
392
393        LOGGER.log( Level.FINE, "  Facet: {0}",facet);
394
395        if ( facet.getName().equals( XSFacet.FACET_LENGTH ) || facet.getName().equals( XSFacet.FACET_MAXLENGTH )
396             || facet.getName().equals( XSFacet.FACET_TOTALDIGITS ) ) {
397
398          factory.constructTagSize( intoClass, Integer.parseInt( facet.getValue().toString() ) );
399
400        } else if ( facet.getName().equals( XSFacet.FACET_FRACTIONDIGITS ) ) {
401          factory.constructTagFieldPrecision( intoClass, Integer.parseInt( facet.getValue().toString() ) );
402
403        } else if ( facet.getName().equals( XSFacet.FACET_ENUMERATION ) ) {
404          // create a constant for the enumerated value instead of a tag value
405          createConstant( facet, intoClass );
406
407        } else {
408          factory.constructTag( intoClass, facet.getName(), facet.getValue().toString() );
409        }
410      }
411    } else if ( type.isList() ) { // a list of simple type instances
412      LOGGER.log( Level.FINE, "List");
413
414      // make it a subclass of the AnyType
415      superClass = locateType( anyType );
416      factory.constructGeneralization( intoClass, superClass );
417
418      // bind this (List type) to the item class
419      unitedType = ((XSListSimpleType) type).getItemType();
420      unitedClass = locateType( unitedType );
421      if ( unitedClass == null ) { // a list of local class
422        unitedClass = declareLocalType( unitedType,
423                                        intoClass.getName()+INFERRED_CLASS_NAME_SUFFIX,
424                                        intoClass);
425        defineType( unitedType, unitedClass );
426      }
427      assoc = factory.constructAssociation( intoClass, "", 1, true, false,
428                                            unitedClass, "", net.mdatools.modelant.uml13.metamodel.Convention.UNLIMITED,
429                                            intoClass.getNamespace(),
430                                            "A list of "+ unitedType.getName() );
431      assignDocumentation( assoc, unitedType );
432
433      factory.constructTagDocumentation(intoClass, "This XSD type represents a list of " + unitedType.getName() );
434
435    } else if ( type.isUnion() ) { // this is a UNION of simple types, so this type
436
437      LOGGER.log( Level.FINE, "Union");
438
439      // make it a subclass of the the types it unites, thus allowing to be
440      // implementation of any of them
441
442      // describe all listed XSD types as superclasses of this
443      unitedTypesItrator = ((XSUnionSimpleType) type).iterator();
444      while ( unitedTypesItrator.hasNext() ) {
445        unitedType = unitedTypesItrator.next();
446
447        unitedClass = locateType( unitedType );
448        if ( unitedClass == null ) { // a list of local class
449          unitedClass = declareLocalType( unitedType,
450                                          type.getName()+INFERRED_UNION_NAME_SUFFIX,
451                                          intoClass );
452          defineType( unitedType, locateType( type ) );
453        }
454        factory.constructGeneralization( intoClass, unitedClass );
455      }
456      factory.constructTagDocumentation(intoClass, "This XSD type represents an XSD union of simple types inheriting from all of them" );
457
458    } else { // impossible, provided just for readability
459      LOGGER.log( Level.SEVERE, "Unkown type: {0}", type);
460    }
461  }
462
463  /**
464   * This method converts a complex XSD type to a UML class
465   * @param type is non-null complex type definition
466   * @param intoClass is non-null uml class to describe the XSD type into
467   */
468  private void processComplexType(XSComplexType type, Classifier intoClass) {
469    XSContentType contentType;
470
471    intoClass.setAbstract( type.isAbstract() );
472
473    assignSuperclass( type, intoClass );
474
475    // describe type as an XSAttContainer
476    processAttributeContainer( type, intoClass );
477
478    // describe the specific contents of this type
479    // NOTE: type.getExplicitContent() should be used but it actually misses some specific definitions
480    //       in in-lined types
481    contentType = type.getExplicitContent();
482    if ( contentType == null ) {
483      contentType = type.getContentType();
484    }
485    if ( contentType instanceof XSSimpleType ) {
486      // handle the restriction/extension as THIS type (the superclass identified here is the correct one)
487
488      processSimpleContent( contentType.asSimpleType(), intoClass );
489
490    } else if ( contentType instanceof XSParticle ) {
491
492      processParticleAsComplexTypeContent( contentType.asParticle(), intoClass );
493
494    }
495  }
496
497  /**
498   * @param type
499   * @param intoClass
500   */
501  private void assignSuperclass(XSType type, Classifier intoClass) {
502    Classifier superclass;
503
504    if ( type != anyType ) { // a real superclass exists
505      superclass = locateType( type.getBaseType() );
506
507      if ( superclass == null ) { // a subclass of a local class
508        // complete the declaration of this with the declaration of the superclass,
509        // no need of a separate superclass
510        defineType( type.getBaseType(), intoClass );
511
512      } else { // a public class - subclass it
513        factory.constructGeneralization( intoClass, superclass );
514      }
515    }
516  }
517
518  /**
519   * Describes type as a collection of attributes into umlClass
520   * @param type
521   * @param umlClass
522   */
523  private void processAttributeContainer(XSAttContainer type, Classifier umlClass) {
524    Attribute attribute;
525    Iterator<? extends XSAttributeUse> declaredAttributeUsesIterator;
526    XSAttributeUse attributeUse;
527    Iterator<? extends XSAttGroupDecl> groupsIterator;
528    XSAttGroupDecl group;
529
530    // process the directly declared attributes
531    declaredAttributeUsesIterator = type.iterateDeclaredAttributeUses();
532    while ( declaredAttributeUsesIterator.hasNext() ) {
533      attributeUse = declaredAttributeUsesIterator.next();
534
535      attribute = createAttribute( attributeUse.getDecl(), umlClass );
536
537      // collect the comments for the type when it is an attributes group, because
538      // it is in-lined
539      if ( !(type instanceof XSComplexType) ) {
540        assignDocumentation( attribute, type );
541      }
542    }
543
544    // add the attributes used in referred groups
545    groupsIterator = type.iterateAttGroups();
546    while ( groupsIterator.hasNext() ) {
547      group = groupsIterator.next();
548      processAttributeContainer( group, umlClass );
549    }
550    // type.getAttributeWildcard();
551  }
552
553  /**
554   * Processes a particle as a definition of elements of a complex type.
555   * The associations/compositions without role name at the part side represent
556   * model groups, the associations/compositions with a role represent
557   * XML elements.
558   * @param particle is the non-null particle that describes the class' contents
559   * @param intoClass is not-null class to put the elements in
560   */
561  private void processParticleAsComplexTypeContent(XSParticle particle, Classifier intoClass) {
562    Classifier otherClass;
563    UmlAssociation assoc;
564    UmlPackage namespace;
565    int otherEndUpper;
566    XSTerm term;
567    XSElementDecl elementDecl;
568    String modelGroupName;
569
570    // calculate multiplicity of the other end
571    if ( particle.isRepeated() ) {
572      otherEndUpper = particle.getMaxOccurs().subtract(particle.getMinOccurs()).intValue();
573    } else {
574      otherEndUpper = 1;
575    }
576    term = particle.getTerm();
577    if ( term.isElementDecl() ) { // just a single element's declaration. The element is represented as an association to the element's type
578      elementDecl = (XSElementDecl)term;
579
580      otherClass = locateType( elementDecl.getType() );
581      if ( otherClass == null ) { // a local class still not defined
582        otherClass = declareLocalType( elementDecl.getType(),
583                                       elementDecl.getName()+INFERRED_CLASS_NAME_SUFFIX,
584                                       intoClass );
585        defineType( elementDecl.getType(), otherClass );
586
587        factory.constructTagDocumentation( otherClass, "A type inlined in an <xsd:element> declaration" );
588      }
589
590      // instantiate a composition
591      assoc = factory.constructAssociation( intoClass, "", 1, true, false,
592                                            otherClass, elementDecl.getName(), otherEndUpper,
593                                            intoClass.getNamespace(),
594                                            "A declared element of the type" );
595      assignDocumentation( assoc, elementDecl );
596      factory.constructStereotypeElement( assoc );
597
598    } else if ( term.isModelGroup() ) { // an ad-hoc model group
599      if ( otherEndUpper == 1 ) {
600        inlineModelGroup( term.asModelGroup(),
601                          intoClass );
602
603      } else { // represent the local anonymous model group as a class and associate it
604
605        modelGroupName = formatClassName( intoClass.getName()+INFERRED_GROUP_NAME_SUFFIX );
606
607        LOGGER.log( Level.FINE, "Create anonymous model group: {0}",modelGroupName);
608
609        otherClass = factory.constructClass(modelGroupName);
610        bindType( term, otherClass );
611
612        // NOTE: even though the class could be local, we create it as top-level one
613        //       because Rose could not import its attributes.
614        otherClass.setNamespace( intoClass.getNamespace() );
615
616        inlineModelGroup( term.asModelGroup(),
617                          otherClass );
618        // instantiate a composition
619        assoc = factory.constructAssociation( intoClass, "", 1, true, false,
620                                              otherClass, "",
621                                              otherEndUpper,
622                                              intoClass.getNamespace(),
623                                              "A model element group - the elements are declared in its contents" );
624        assignDocumentation( assoc, term );
625        factory.constructTagDocumentation(otherClass, "A model group without any representation as <xsd:element>" );
626      }
627    } else if ( term.isModelGroupDecl() ) { // a model group declaration
628      if ( otherEndUpper == 1 ) {
629        inlineModelGroup( term.asModelGroupDecl().getModelGroup(),
630                          intoClass );
631      } else {
632        otherClass = locateType( term.asModelGroupDecl() );
633
634        if ( otherClass == null ) { // the group is still not declared
635          modelGroupName = formatClassName( term.asModelGroupDecl().getName()+INFERRED_GROUP_NAME_SUFFIX );
636
637          LOGGER.log( Level.FINE, "Create local model group: {0}",modelGroupName);
638
639          namespace = factory.constructPackage( formatPackageName( term.asModelGroupDecl().getTargetNamespace() ));
640          otherClass = factory.constructClass( namespace, modelGroupName );
641
642          bindType( term.asModelGroupDecl(), otherClass );
643          assignDocumentation( otherClass, term );
644
645          // NOTE: The group declaration is already put in the proper namespace
646
647          inlineModelGroup( term.asModelGroupDecl().getModelGroup(),
648                            otherClass );
649        }
650        // use the model group
651        assoc = factory.constructAssociation( intoClass, "", 1, true, false,
652                                              otherClass, "",
653                                              otherEndUpper,
654                                              intoClass.getNamespace(),
655                                              "An explicitly declared model element group - the elements are declared in its contents" );
656        assignDocumentation( assoc, term );
657        factory.constructTagDocumentation(otherClass, "A model group declaration without any representation as <xsd:element>" );
658      }
659    }
660  }
661
662  /**
663   * Creates a new attribute as of its declaration. Note that obviously
664   * the attribute use might redefine the attribute's default value and/or
665   * fixed value. The similarity in the code is just because of the stupid
666   * model where the methods are copied between interfaces instead of being
667   * just inherited.
668   * @param declaration
669   * @param umlClass
670   * @return a non-null declaration
671   */
672  private Attribute createAttribute(XSAttributeDecl declaration, Classifier umlClass) {
673    Attribute result;
674    Classifier attributeClass;
675
676    LOGGER.log( Level.FINE, "  Attribute: {0}",declaration);
677
678    result = factory.constructAttribute( declaration.getName() );
679    assignDocumentation( result, declaration );
680
681    attributeClass = locateType( declaration.getType() );
682    if ( attributeClass == null ) { // the type is still not defined
683      attributeClass = declareLocalType( declaration.getType(),
684                                         declaration.getName()+INFERRED_CLASS_NAME_SUFFIX,
685                                         umlClass );
686      defineType( declaration.getType(), attributeClass );
687    }
688    result.setOwner( umlClass );
689    result.setType( attributeClass );
690    result.setVisibility( VisibilityKindEnum.VK_PUBLIC );
691
692    // describe the rest of the attribute use
693    if ( declaration.getFixedValue() != null
694         && declaration.getFixedValue().toString() != null ) { // constant attribute
695
696      // make it a constant
697      result.setInitialValue( factory.constructExpression( declaration.getFixedValue().toString() ) );
698      result.setChangeability( ChangeableKindEnum.CK_FROZEN );
699      result.setTargetScope( ScopeKindEnum.SK_CLASSIFIER );
700
701    } else { // a regular attribute
702
703      if ( declaration.getDefaultValue() != null
704          && declaration.getDefaultValue().toString() != null ) {
705
706        result.setInitialValue( factory.constructExpression("\""+declaration.getDefaultValue()+"\"" ));
707      }
708      result.setChangeability( ChangeableKindEnum.CK_CHANGEABLE );
709      result.setTargetScope( ScopeKindEnum.SK_INSTANCE );
710    }
711    return result;
712  }
713
714  /**
715   * Creates a new constant attribute out of an ENUMERATION facet
716   * just inherited.
717   * @param declaration is an enumeration facet
718   * @param umlClass is the owner class
719   * @return a non-null declaration
720   */
721  private Attribute createConstant(XSFacet declaration, Classifier umlClass) {
722    Attribute result;
723    Classifier attributeClass;
724
725    result = factory.constructAttribute(formatConstant( declaration.getValue().value ));
726    assignDocumentation( result, declaration );
727
728    LOGGER.log( Level.FINE, "  Enumeration: {0}",result.getName());
729
730    attributeClass = locateType( stringType );
731    result.setOwner( umlClass );
732    result.setType( attributeClass );
733
734    // make it a constant
735    result.setInitialValue(factory.constructExpression(declaration.getValue().toString()));
736    result.setChangeability( ChangeableKindEnum.CK_FROZEN );
737    result.setTargetScope( ScopeKindEnum.SK_CLASSIFIER );
738    result.setVisibility( VisibilityKindEnum.VK_PUBLIC );
739
740    return result;
741  }
742
743  /**
744   * @param modelGroup is the non-null model group to inline
745   * @param result is the non-null UML class where to represent the group into
746   */
747  private void inlineModelGroup(XSModelGroup modelGroup, Classifier result) {
748    Iterator<XSParticle> particleIterator;
749    XSParticle particle;
750
751    // parse the particles
752    particleIterator = modelGroup.iterator();
753    while ( particleIterator.hasNext() ) {
754      particle = particleIterator.next();
755      processParticleAsComplexTypeContent( particle, result );
756    }
757    assignDocumentation( result, modelGroup );
758  }
759
760  /**
761   * Represents the element as a class with <<ELEMENT>> stereotype
762   * @param elementDeclaration is a non-null element
763   */
764  private void createElement(XSElementDecl elementDeclaration) {
765    Classifier umlType;
766    Classifier element;
767
768    element = declareType( elementDeclaration );
769
770    umlType = locateType( elementDeclaration.getType() );
771    if ( umlType == null ) { // a local type is referred
772      // create the type as the 'Type of this' element
773      umlType = declareLocalType( elementDeclaration.getType(),
774                                  elementDeclaration.getName()+INFERRED_CLASS_NAME_SUFFIX,
775                                  element );
776      defineType( elementDeclaration.getType(), umlType );
777    }
778    factory.constructGeneralization( element, umlType );
779    factory.constructStereotypeElement( element );
780  }
781
782  /**
783   * Assigns documentation, corresponding to the annotation provided,
784   * to the target model element.
785   * @param modelElement is non-null UML model element
786   * @param component is the XML schema component this model element represents
787   */
788  private void assignDocumentation(ModelElement modelElement, XSComponent component) {
789    String comments;
790
791    if ( component != null
792         && component.getAnnotation() != null
793         && component.getAnnotation().getAnnotation() instanceof String) {
794
795      comments = ((String) component.getAnnotation().getAnnotation()).trim();
796
797      factory.constructTagDocumentation(modelElement, comments );
798    }
799  }
800
801  /**
802   * @param defaultName is the suggested class name to generate. Might be null or empty
803   * @return an unique qualified class name that represents the taget namespace + default name
804   */
805  private String formatClassName(String defaultName) {
806    String result;
807    StringBuilder path;
808
809    path = new StringBuilder( 64 );
810
811    if ( defaultName == null || defaultName.isEmpty() ) {
812      path.append( ANONYMOUS_CLASS_NAME_PREFIX );
813    } else {
814      path.append( defaultName );
815    }
816    result = uniqueNamesGenerator.getUnique( path.toString() );
817    return result;
818  }
819
820  /**
821   * @param targetNamespace
822   * @return the package name treating all alphanumeric words from the target namespace as sub-package names
823   */
824  private String formatPackageName(String targetNamespace) {
825    StringBuilder result;
826    URI namespace;
827    StringTokenizer tokenizer;
828    String item;
829
830    result = new StringBuilder(256);
831
832    // define the package to store in
833    if ( targetNamespace != null && !targetNamespace.isEmpty() ) {
834      try {
835        namespace = new URI( targetNamespace );
836
837        // format the package name using the path from the namespace URL
838        if ( namespace.getPath() != null ) {
839          tokenizer = new StringTokenizer( namespace.getPath(), "/.-" );
840          while ( tokenizer.hasMoreElements() ) {
841            item = (String) tokenizer.nextElement();
842
843            if ( result.length() > 0 ) {
844              result.append(".");
845            }
846            result.append( item );
847          }
848        }
849      } catch (Exception ex) {
850        // DO NOTHING
851      }
852    }
853    return result.toString();
854  }
855
856  /**
857     * @param defaultName is the suggested class name to generate. Might be null or empty
858     * @return a java formatted constant name
859     */
860  private String formatConstant(String defaultName) {
861    String result;
862
863    if ( defaultName == null ) {
864      result = EMPTY_ENUM_NAME_PREFIX;
865    } else {
866      // construct identifier out of the value
867      result = defaultName.replaceAll( "[^A-Za-z0-9_$]+", "" );
868
869      if ( result.isEmpty() ) {
870        result = uniqueNamesGenerator.getUnique( EMPTY_ENUM_NAME_PREFIX );
871      } else {
872        if ( !Character.isJavaIdentifierStart( result.charAt( 0 ) ) ) { // not suitable as Java package name
873          result = ENUM_NAME_PREFIX+result;
874        }
875      }
876    }
877    return result;
878  }
879
880  /**
881     * The factory to provide the anntoation parser to the XSD parser
882     * @see AnnotationParserFactory
883     */
884    protected static class SimpleAnnotationParserFactory implements AnnotationParserFactory {
885      public AnnotationParser create() {
886        return new AnnotationParserImpl();
887      }
888    }
889
890  /**
891     * This annotation acts as an XML content handler that collects all texts of the
892     * elements it is fed. Then it returns the result annotation as a single string.
893     */
894    protected static class AnnotationParserImpl extends AnnotationParser implements ContentHandler {
895      /**
896       * The buffer where to collect the annotations
897       */
898      private final StringBuilder result = new StringBuilder(256);
899
900      /**
901       * @see com.sun.xml.xsom.parser.AnnotationParser#getContentHandler(com.sun.xml.xsom.parser.AnnotationContext, java.lang.String, org.xml.sax.ErrorHandler, org.xml.sax.EntityResolver)
902       */
903      public ContentHandler getContentHandler(AnnotationContext context, String parentElementName, ErrorHandler errorHandler, EntityResolver entityResolver) {
904        result.setLength( 0 );
905        return this;
906      }
907
908      /**
909       * @see com.sun.xml.xsom.parser.AnnotationParser#getResult(java.lang.Object)
910       */
911      public Object getResult(Object existing) {
912        return result.toString();
913      }
914
915      /**
916       * @see org.xml.sax.ContentHandler#characters(char[], int, int)
917       */
918      public void characters(char[] ch, int start, int length) throws SAXException {
919        result.append( new String( ch, start, length ).replaceAll( "[\t\r\n]", " "));
920      }
921
922      public void endDocument() throws SAXException {
923      }
924
925      public void endElement(String uri, String localName, String qName) throws SAXException {
926      }
927
928      public void endPrefixMapping(String prefix) throws SAXException {
929      }
930
931      public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
932      }
933
934      public void processingInstruction(String target, String data) throws SAXException {
935      }
936
937      public void setDocumentLocator(Locator locator) {
938      }
939
940      public void skippedEntity(String name) throws SAXException {
941      }
942
943      public void startDocument() throws SAXException {
944      }
945
946      public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
947      }
948
949      public void startPrefixMapping(String prefix, String uri) throws SAXException {
950      }
951    }
952
953  /**
954   * Reports any errors/warnings found during the schema parsing
955   */
956  final class ParserErrorListerner implements ErrorHandler {
957    public void error(SAXParseException arg0) {
958      LOGGER.log( Level.SEVERE, "",arg0);
959    }
960
961
962    public void fatalError(SAXParseException arg0) {
963      LOGGER.log( Level.SEVERE, "",arg0);
964    }
965
966
967    public void warning(SAXParseException arg0) {
968      LOGGER.log( Level.WARNING, "",arg0);
969    }
970  }
971
972  /**
973   * Resolves any entities just by returning their URL (when known)
974   */
975  final class SimpleEntityResolver implements EntityResolver {
976    /**
977     * The XSOM parser reads the <xsd:import namespace="...URI..." schemaLocation="...file..."> and
978     * provides the namespace URI as public ID and the schemaLocation as system ID
979     *
980     * @return a non-null InputSource for the file in the namespace URI
981     * @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String, java.lang.String)
982     */
983    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
984      InputSource result;
985      URL url;
986
987      if ( systemId == null ) {
988        if ( publicId != null ) {
989          url = new URL( publicId );
990
991          LOGGER.log(Level.INFO, "Resolving {0} remotely", url);
992        } else {
993          throw new IllegalArgumentException( "Received null system and null public ID of XSD to import");
994        }
995      } else { // prefer the system ID
996        url = new URL( systemId );
997
998        LOGGER.log(Level.INFO, "Resolving {0} locally", url);
999      }
1000      result = new InputSource( url.toString() );
1001
1002      return result;
1003    }
1004  }
1005}