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 <<element>> 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 <<local type>> stereotype 080 * <li>The column types are converted to DataType instances named: <type 081 * name>[_<column size>[_<column precision>]]. 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}