/* * Initial version copyright 2008 Lockheed Martin Corporation, except * as stated in the file entitled Licensing-Information. * * All modifications copyright 2009-2015 Data Access Technologies, Inc. * (Model Driven Solutions). Licensed under the Academic Free License version 3.0 * (http://www.opensource.org/licenses/afl-3.0.php), except as stated * in the file entitled Licensing-Information. * * Contributors: * MDS - initial API and implementation * */ package org.modeldriven.fuml.assembly; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; import javax.xml.namespace.QName; import javax.xml.stream.Location; import javax.xml.stream.events.Attribute; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.modeldriven.fuml.FumlObject; import org.modeldriven.fuml.common.lang.JavaKeyWords; import org.modeldriven.fuml.common.reflect.ReflectionUtils; import org.modeldriven.fuml.config.FumlConfiguration; import org.modeldriven.fuml.config.ImportAdapter; import org.modeldriven.fuml.config.ImportAdapterType; import org.modeldriven.fuml.config.NamespaceDomain; import org.modeldriven.fuml.config.NamespaceMapping; import org.modeldriven.fuml.config.ReferenceMappingType; import org.modeldriven.fuml.config.ValidationExemption; import org.modeldriven.fuml.config.ValidationExemptionType; import org.modeldriven.fuml.environment.Environment; import org.modeldriven.fuml.library.Library; import org.modeldriven.fuml.library.libraryclass.ImplementationObject; import org.modeldriven.fuml.repository.Class_; import org.modeldriven.fuml.repository.Classifier; import org.modeldriven.fuml.repository.Repository; import org.modeldriven.fuml.repository.Property; import org.modeldriven.fuml.xmi.ModelSupport; import org.modeldriven.fuml.xmi.XmiExternalReferenceElement; import org.modeldriven.fuml.xmi.XmiIdentity; import org.modeldriven.fuml.xmi.XmiMappedReference; import org.modeldriven.fuml.xmi.XmiNode; import org.modeldriven.fuml.xmi.XmiReference; import org.modeldriven.fuml.xmi.XmiReferenceAttribute; import org.modeldriven.fuml.xmi.stream.StreamNode; import org.modeldriven.fuml.xmi.validation.ErrorCode; import org.modeldriven.fuml.xmi.validation.ErrorSeverity; import org.modeldriven.fuml.xmi.validation.ValidationError; import org.modeldriven.fuml.xmi.validation.ValidationException; import UMLPrimitiveTypes.UnlimitedNatural; import fUML.Semantics.CommonBehaviors.BasicBehaviors.OpaqueBehaviorExecution; import fUML.Syntax.Classes.Kernel.Comment; import fUML.Syntax.Classes.Kernel.DataType; import fUML.Syntax.Classes.Kernel.Element; import fUML.Syntax.Classes.Kernel.Generalization; import fUML.Syntax.Classes.Kernel.Enumeration; import fUML.Syntax.Classes.Kernel.PrimitiveType; import fUML.Syntax.Classes.Kernel.Type; public class ElementAssembler extends AssemblerNode implements XmiIdentity, Assembler { private static Log log = LogFactory.getLog(ElementAssembler.class); private Repository metadata = Repository.INSTANCE; private List<XmiReference> references; private Map<String, ElementAssembler> assemblerMap; private ModelSupport modelSupport; private boolean assembleExternalReferences = true; private ElementAssembler parentAssembler; // FIXME: re-factor ASAP!! // Generalizations deferred until element linking is complete, keyed to the // specific classifier. private List<Generalization> deferredGeneralizations; /** XMI source */ private XmiNode source; private XmiNode parent; private Attribute xmiId; private Attribute xmiHref; /** namespace source */ private String xmiNamespace; /** UML meta-class */ private Class_ prototype; /** fUML class target(s) */ private Element target; private Comment targetComment; @SuppressWarnings("unused") private ElementAssembler() { // nope } public ElementAssembler(XmiNode source, XmiNode parent, Class_ prototype, Map<String, ElementAssembler> assemblerMap) { super(source); this.source = source; this.parent = parent; this.prototype = prototype; this.assemblerMap = assemblerMap; modelSupport = new ModelSupport(); StreamNode eventNode = (StreamNode) this.source; QName idName = new QName(eventNode.getContext().getXmiNamespace().getNamespaceURI(), "id"); this.xmiId = eventNode.getAttribute(idName); QName hrefName = new QName("href"); this.xmiHref = eventNode.getStartElementEvent().asStartElement().getAttributeByName(hrefName); this.xmiNamespace = eventNode.getNamespaceURI(); if (this.xmiNamespace == null) throw new AssemblyException("could not find namespace for node '" + eventNode.getLocalName() + "'"); } public ElementAssembler getParentAssembler() { return parentAssembler; } public void setParentAssembler(ElementAssembler parentAssembler) { this.parentAssembler = parentAssembler; } public List<Generalization> getDeferredGeneralizations() { return this.deferredGeneralizations; } public void addDeferredGeneralization(Generalization generalization) { // Record deferred generalizations only in root assemblers. ElementAssembler parent = this.getParentAssembler(); if (parent == null) { if (this.deferredGeneralizations == null) { this.deferredGeneralizations = new ArrayList<Generalization>(); } this.deferredGeneralizations.add(generalization); } else { parent.addDeferredGeneralization(generalization); } } public void assembleElementClass() { try { String packageName = metadata.getJavaPackageNameForClass(this.prototype); String instancecClassName = this.prototype.getDelegate().name; // TODO: We have a keyword map, but unclear whether upper-case 'Class' should be mapped. Definately 'class' is. if ("Class".equals(instancecClassName)) instancecClassName = instancecClassName + "_"; String qualifiedName = packageName + "." + instancecClassName; FumlObject object = null; ImportAdapter importAdapter = FumlConfiguration.getInstance().findImportAdapter( instancecClassName); if (importAdapter == null || importAdapter.getType().ordinal() != ImportAdapterType.ASSEMBLY.ordinal()) { object = (FumlObject)ReflectionUtils.instanceForName(qualifiedName); } else { AssemblyAdapter adapter = (AssemblyAdapter) ReflectionUtils.instanceForName(importAdapter .getAdapterClassName()); object = adapter.assembleElement((StreamNode) this.source); } if (this.xmiId != null) object.setXmiId(this.xmiId.getValue()); else object.setXmiId(UUID.randomUUID().toString()); // synthesize one if (this.xmiHref != null) object.setHref(this.xmiHref.getValue()); if (this.xmiId == null && this.xmiHref == null) log.warn("found (" + object.getClass().getName() + ") element with no xmi:id or href"); object.setXmiNamespace(this.xmiNamespace); if (object instanceof Element) { if (object instanceof PrimitiveType && object.getHref() != null) { // FIXME; gotta be a better way! URI resolver?? // Note: Path map variables allow for portability of URIs. The actual location // indicated by a URI depends on the run-time binding of the path variable. Thus, different // environments can work with the same resource URIs even though the // resources are stored in different physical locations. int idx = object.getHref().lastIndexOf("#"); String suffix = object.getHref().substring(idx+1); if (suffix.equals("Integer")) this.target = Environment.getInstance().getInteger(); else if (suffix.equals("String")) this.target = Environment.getInstance().getString(); else if (suffix.equals("Boolean")) this.target = Environment.getInstance().getBoolean(); else if (suffix.equals("Real")) this.target = Environment.getInstance().getReal(); else if (suffix.equals("UnlimitedNatural")) this.target = Environment.getInstance().getUnlimitedNatural(); else throw new AssemblyException("unknown type, " + object.getHref()); //this.target = (Element) object; if (this.target == null) throw new AssemblyException("could not determine target object for prototype, " + this.prototype.getXmiNamespace() + "#" + this.prototype.getName()); } else { this.target = (Element) object; if (this.target == null) throw new AssemblyException("could not determine target object for prototype, " + this.prototype.getXmiNamespace() + "#" + this.prototype.getName()); if (log.isDebugEnabled()) log.debug("constructing class " + this.target.getClass().getName()); } } else if (object instanceof Comment) { this.targetComment = (Comment) object; if (this.targetComment == null) throw new AssemblyException("could not determine target object for prototype, " + this.prototype.getXmiNamespace() + "#" + this.prototype.getName()); } else throw new AssemblyException("unknown instance, " + object.getClass().getName()); } catch (ClassNotFoundException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (NoSuchMethodException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (InvocationTargetException e) { log.error(e.getCause().getMessage(), e.getCause()); throw new AssemblyException(e.getCause()); } catch (IllegalAccessException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (InstantiationException e) { if (e.getCause() != null) log.error(e.getCause().getMessage(), e.getCause()); else log.error(e.getMessage(), e); throw new AssemblyException(e); } } // TODO: move this to library.Library setting up the locus // as elements are registered. Current;y can't do this as opposite // properties are not being set. I.e. a packagedElement does not // know it's package (parent). We are relying on the assembler // hierarchy to do this (below), which is unnecessary/hacky. The metadata // will // need to be enhanced to find opposite properties using associations. // public void registerElement() { try { if (!(this.target instanceof Type)) return; Type targetType = (Type) this.target; ElementAssembler assembler = this; // FIXME: generalize this hack using associations in metamodel. For incremental // assembly where we remove chunks of XMI from the parent (packagedElement), this // scheme breaks because it relies on a continuous graph. String qualifiedPackageName = ""; fUML.Syntax.Classes.Kernel.Package parentPackage = null; if (assembler.getParentAssembler() != null && assembler.getParentAssembler().getTarget() instanceof fUML.Syntax.Classes.Kernel.Package) { parentPackage = (fUML.Syntax.Classes.Kernel.Package)assembler.getParentAssembler().getTarget(); } for (int i = 0; (assembler = assembler.getParentAssembler()) != null; i++) { if (assembler.getTarget() instanceof fUML.Syntax.Classes.Kernel.Package) { fUML.Syntax.Classes.Kernel.Package pckg = (fUML.Syntax.Classes.Kernel.Package) assembler.getTarget(); String name = pckg.name; if (i > 0) qualifiedPackageName = name + "." + qualifiedPackageName; else qualifiedPackageName = name; } else break; } // FIXME: generalize this hack using associations in metamodel String libraryObjectClassName = null; if (qualifiedPackageName != null && qualifiedPackageName.trim().length() > 0) libraryObjectClassName = qualifiedPackageName + "." + targetType.name; else libraryObjectClassName = targetType.name; targetType.qualifiedName = libraryObjectClassName; if (parentPackage != null) targetType.package_ = parentPackage; if (!(targetType instanceof fUML.Syntax.Classes.Kernel.Class_)) return; fUML.Syntax.Classes.Kernel.Class_ targetClass = (fUML.Syntax.Classes.Kernel.Class_)targetType; String implObjectClassName = FumlConfiguration.getInstance().findExecutionClassName( libraryObjectClassName); if (implObjectClassName == null) return; // not mapped - we're not interested Classifier implClassifier = metadata.getClassifierByQualifiedName(implObjectClassName); if (implClassifier == null) { if (log.isDebugEnabled()) log.debug("(expected) no classifier found for mapped library class '" + implObjectClassName + "' for library class, " + libraryObjectClassName); } if (log.isDebugEnabled()) log.debug("mapped " + targetType.name + " to " + implObjectClassName); Object object = ReflectionUtils.instanceForName(implObjectClassName); if (object instanceof ImplementationObject) { ImplementationObject execution = (ImplementationObject) object; execution.types.add(targetClass); log.info("adding to locus: " + execution.getClass().getName()); Environment.getInstance().locus.add(execution); } else if (object instanceof OpaqueBehaviorExecution) { OpaqueBehaviorExecution execution = (OpaqueBehaviorExecution) object; execution.types.add(targetClass); Environment.getInstance().locus.factory.addPrimitiveBehaviorPrototype(execution); } else log.warn("unknown instance, " + object.getClass().getName()); } catch (ClassNotFoundException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (NoSuchMethodException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (InvocationTargetException e) { log.error(e.getCause().getMessage(), e.getCause()); throw new AssemblyException(e.getCause()); } catch (IllegalAccessException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (InstantiationException e) { if (e.getCause() != null) log.error(e.getCause().getMessage(), e.getCause()); else log.error(e.getMessage(), e); throw new AssemblyException(e); } } // TODO: find the "opposite" property using metadata and value // that as well. Metadata will need to be enhanced to use associations. public void associateElement(ElementAssembler other) { try { Property property = other.getPrototype().findProperty(this.getSource().getLocalName()); if (property == null) return; // we validate this elsewhere // If the target property is "generalization", then defer associating it with the source. // This is necessary to ensure that the general classifier is fully assembled before // it is added as a generalization of the specific classifier. Otherwise, the // inherited members of the specific classifier will not be set properly. if ("generalization".equals(this.source.getLocalName())) { this.addDeferredGeneralization((Generalization)this.getTargetObject()); return; } if (!property.isSingular()) { if (log.isDebugEnabled()) log.debug("linking collection property: " + other.getPrototype().getName() + "." + this.getSource().getLocalName() + " with: " + this.getPrototype().getName()); try { String methodName = "add" + this.getSource().getLocalName().substring(0, 1).toUpperCase() + this.getSource().getLocalName().substring(1); Method adder = ReflectionUtils.getMethod(other.getTargetClass(), methodName, this .getTargetClass()); Object[] args = { this.getTargetObject() }; adder.invoke(other.getTarget(), args); } catch (NoSuchMethodException e) { // try to get and add to the list field if exists try { Field field = other.getTargetClass().getField( this.getSource().getLocalName()); Object list = field.get(other.getTargetObject()); Method adder = ReflectionUtils.getMethod(list.getClass(), "add", this.getTargetClass()); Object[] args = { this.getTargetObject() }; adder.invoke(list, args); } catch (NoSuchFieldException e2) { log.warn("no 'add' or 'List.add' method found for property, " + other.getPrototype().getName() + "." + this.getSource().getLocalName()); } } } else { if (log.isDebugEnabled()) log.debug("linking singular property: " + other.getPrototype().getName() + "." + this.getSource().getLocalName() + " with: " + this.getPrototype().getName()); try { String methodName = "set" + this.getSource().getLocalName().substring(0, 1).toUpperCase() + this.getSource().getLocalName().substring(1); Method setter = ReflectionUtils.getMethod(other.getTargetClass(), methodName, this .getTargetClass()); Object[] args = { this.getTargetObject() }; setter.invoke(other.getTarget(), args); } catch (NoSuchMethodException e) { // try to get and add to the list field if exists try { Field field = other.getTargetClass().getField( this.getSource().getLocalName()); field.set(other.getTargetObject(), this.getTargetObject()); } catch (NoSuchFieldException e2) { log.warn("no 'set' method or public field found for property, " + other.getPrototype().getName() + "." + this.getSource().getLocalName()); } } } } catch (NoSuchMethodException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (InvocationTargetException e) { log.error(e.getCause().getMessage(), e.getCause()); throw new AssemblyException(e.getCause()); } catch (IllegalAccessException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } } public void associateDeferredGeneralizations() { List<Generalization> deferredGeneralizations = this.getDeferredGeneralizations(); if (deferredGeneralizations != null) { while (!deferredGeneralizations.isEmpty()) { int i = 0; while (i < deferredGeneralizations.size()) { Generalization deferredGeneralization = deferredGeneralizations.get(i); // Only associate a deferred generalization with its specific classifier // if there are no generalizations still deferred for the general // classifier. for (Generalization generalization: deferredGeneralizations) { if (generalization.specific == deferredGeneralization.general) { i++; continue; } } deferredGeneralization.specific.addGeneralization(deferredGeneralization); deferredGeneralizations.remove(deferredGeneralization); } } } } public void assembleFeatures() { try { NamespaceDomain domain = null; // only lookup as needed StreamNode eventNode = (StreamNode) source; Location loc = eventNode.getLocation(); if (log.isDebugEnabled()) log.debug("element line/column: " + loc.getLineNumber() + ":" + loc.getColumnNumber()); // look at XML attributes Iterator<Attribute> attributes = eventNode.getAttributes(); while (attributes != null && attributes.hasNext()) { Attribute xmlAttrib = attributes.next(); QName name = xmlAttrib.getName(); String prefix = name.getPrefix(); if (prefix != null && prefix.length() > 0) continue; // not applicable to current // element/association-end. if ("href".equals(name.getLocalPart())) continue; // FIXME: find/write URI resolver Property property = this.prototype.findProperty(name.getLocalPart()); if (property == null) { if (domain == null) domain = FumlConfiguration.getInstance().findNamespaceDomain(source.getNamespaceURI()); ValidationExemption exemption = FumlConfiguration.getInstance().findValidationExemptionByProperty(ValidationExemptionType.UNDEFINED_PROPERTY, this.prototype, name.getLocalPart(), source.getNamespaceURI(), domain); if (exemption == null) { throw new ValidationException(new ValidationError(eventNode, name .getLocalPart(), ErrorCode.UNDEFINED_PROPERTY, ErrorSeverity.FATAL)); } else { if (log.isDebugEnabled()) log.debug("undefined property exemption found within domain '" + exemption.getDomain().toString() + "' for property '" + this.prototype.getName() + "." + name.getLocalPart() + "' - ignoring error"); continue; // ignore attrib } } Classifier type = property.getType(); if (this.modelSupport.isReferenceAttribute(property)) { XmiReferenceAttribute reference = new XmiReferenceAttribute(source, xmlAttrib, this.getPrototype()); this.addReference(reference); continue; } String value = xmlAttrib.getValue(); if (value == null || value.length() == 0) { String defaultValue = property.findPropertyDefault(); if (defaultValue != null) { value = defaultValue; if (log.isDebugEnabled()) log.debug("using default '" + String.valueOf(value) + "' for enumeration feature <" + type.getName() + "> " + this.getPrototype().getName() + "." + property.getName()); } } this.assembleNonReferenceFeature(property, value, type); } // look at model properties not found in above attribute set List<Property> properties = this.prototype.getNamedProperties(); for (Property property : properties) { QName name = new QName(property.getName()); String value = eventNode.getAttributeValue(name); if (value != null && value.trim().length() > 0) continue; // handled above XmiNode childNode = eventNode.findChildByName(property.getName()); if (childNode != null) { if (!this.modelSupport.isReferenceAttribute(property)) { this.assembleNonReferenceFeature(property, childNode.getData(), property.getType()); } continue; } String defaultValue = property.findPropertyDefault(); if (defaultValue != null) { Classifier type = property.getType(); if (log.isDebugEnabled()) log.debug("using default: '" + String.valueOf(defaultValue) + "' for enumeration feature <" + type.getName() + "> " + this.getPrototype().getName() + "." + property.getName()); this.assembleNonReferenceFeature(property, defaultValue, type); continue; } if (!property.isRequired()) continue; // don't bother digging further for a value if (this.modelSupport.isReferenceAttribute(property) && FumlConfiguration.getInstance().hasReferenceMapping(this.prototype, property)) { ReferenceMappingType mappingType = FumlConfiguration.getInstance() .getReferenceMappingType(this.prototype, property); if (mappingType == ReferenceMappingType.PARENT) { if (parent != null && parent.getXmiId() != null && parent.getXmiId().length() > 0) { XmiMappedReference reference = new XmiMappedReference(source, property.getName(), new String[] { parent.getXmiId() }, this.prototype); this.addReference(reference); continue; } else log.warn("no parent XMI id found, ignoring mapping for, " + this.prototype.getName() + "." + property.getName()); } else log.warn("unrecognized mapping type, " + mappingType.value() + " ignoring mapping for, " + this.prototype.getName() + "." + property.getName()); } if (!property.isDerived()) { if (domain == null) domain = FumlConfiguration.getInstance().findNamespaceDomain(source.getNamespaceURI()); ValidationExemption exemption = FumlConfiguration.getInstance().findValidationExemptionByProperty(ValidationExemptionType.REQUIRED_PROPERTY, this.prototype, name.getLocalPart(), source.getNamespaceURI(), domain); if (exemption == null) { if (log.isDebugEnabled()) log.debug("throwing " + ErrorCode.PROPERTY_REQUIRED.toString() + " error for " + this.prototype.getName() + "." + property.getName()); throw new ValidationException(new ValidationError(eventNode, property.getName(), ErrorCode.PROPERTY_REQUIRED, ErrorSeverity.FATAL)); } else { if (log.isDebugEnabled()) log.debug("required property exemption found within domain '" + exemption.getDomain().toString() + "' for property '" + this.prototype.getName() + "." + name.getLocalPart() + "' - ignoring error"); continue; // ignore property } } } } catch (ClassNotFoundException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (NoSuchMethodException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (InvocationTargetException e) { log.error(e.getCause().getMessage(), e.getCause()); throw new AssemblyException(e.getCause()); } catch (IllegalAccessException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (InstantiationException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (NoSuchFieldException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } } public void assembleReferenceFeatures() { try { if (this.references == null) return; Iterator<XmiReference> iter = this.references.iterator(); while (iter.hasNext()) { XmiReference reference = iter.next(); this.assembleReferenceFeature(reference); } } catch (ClassNotFoundException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (NoSuchMethodException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (InvocationTargetException e) { log.error(e.getCause().getMessage(), e.getCause()); throw new AssemblyException(e.getCause()); } catch (IllegalAccessException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (InstantiationException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } catch (NoSuchFieldException e) { log.error(e.getMessage(), e); throw new AssemblyException(e); } } @SuppressWarnings("rawtypes") private void assembleNonReferenceFeature(Property property, String stringValue, Classifier type) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException { if (type.getDelegate() instanceof Enumeration) { if (log.isDebugEnabled()) log.debug("assembling enum feature <" + type.getName() + "> " + this.getPrototype().getName() + "." + property.getName() + " = " + stringValue); Object value = toEnumerationValue(stringValue, type); assembleEnumerationFeature(property, value, type); } else if (type.getDelegate() instanceof DataType) { Class javaType = toPrimitiveJavaClass((DataType) type.getDelegate()); Object value = toPrimitiveJavaValue(stringValue, (DataType) type.getDelegate(), javaType); if (log.isDebugEnabled()) log.debug("assembling primitive feature <" + javaType.getName() + "> " + this.getPrototype().getName() + "." + property.getName() + " = " + stringValue); if (property.isSingular()) assembleSingularPrimitiveFeature(property, value, javaType); else assembleCollectionPrimitiveFeature(property, value, javaType); } else if (type.getDelegate() instanceof fUML.Syntax.Classes.Kernel.Class_) { if (UnlimitedNatural.class.getSimpleName().equals(type.getName())) { UnlimitedNatural value = new UnlimitedNatural(); if ("*".equals(stringValue)) value.naturalValue = -1; else try { value.naturalValue = Integer.parseInt(stringValue); } catch (NumberFormatException e) { //log.error("could not parse string value '" + stringValue + "'", e); throw new AssemblyException(e); } if (log.isDebugEnabled()) log.debug("assembling primitive feature <" + UnlimitedNatural.class.getSimpleName() + "> " + this.getPrototype().getName() + "." + property.getName() + " = " + stringValue); if (property.isSingular()) assembleSingularPrimitiveFeature(property, value, UnlimitedNatural.class); else assembleCollectionPrimitiveFeature(property, value, UnlimitedNatural.class); } else throw new AssemblyException("unexpected Class_, " + type.getName()); } else throw new AssemblyException("unexpected instance, " + type.getClass().getName()); } @SuppressWarnings("rawtypes") private void assembleReferenceFeature(XmiReference reference) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException { StreamNode eventNode = (StreamNode) this.getSource(); // NamespaceDomain domain = null; // only lookup as needed Property property = this.prototype.getProperty(reference.getLocalName()); Classifier type = property.getType(); if (type.getDelegate() instanceof Enumeration || type.getDelegate() instanceof DataType) return; // handled these in pre-assembly String instancecClassName = type.getName(); // TODO: We have a keyword map, but unclear whether upper-case 'Class' should be mapped. Definately 'class' is. if ("Class".equals(instancecClassName)) instancecClassName = instancecClassName + "_"; String packageName = metadata.getJavaPackageNameForClass(type); String qualifiedName = packageName + "." + instancecClassName; Class fClass_ = Class.forName(qualifiedName); if (!Element.class.isAssignableFrom(fClass_)) { log.warn("ignoring non-element feature <" + type.getName() + "> " + this.getPrototype().getName() + "." + property.getName()); return; } if (property.isSingular()) { if (log.isDebugEnabled()) log.debug("assembling singular reference feature <" + type.getName() + "> " + this.getPrototype().getName() + "." + property.getName()); if (reference.getReferenceCount() != 1) log.warn("expected single reference, not " + String.valueOf(reference.getReferenceCount())); String id = reference.getXmiIds().next(); // expect only one for // singular prop if (reference instanceof XmiExternalReferenceElement) { if (assembleExternalReferences) { if (log.isDebugEnabled()) log.debug("assembling singular external reference feature <" + type.getName() + "> " + this.getPrototype().getName() + "." + property.getName()); Element referent = this.resolveExternalReference(eventNode, reference, id); if (referent != null) this.assembleSingularReferenceFeature(referent, property, type); } } else { FumlObject target = null; ElementAssembler referencedAssembler = this.assemblerMap.get(id); if (referencedAssembler != null) target = referencedAssembler.getTarget(); else target = Environment.getInstance().findElementById(id); if (target != null) this.assembleSingularReferenceFeature(target, property, type); else throw new ValidationException(new ValidationError(reference, id, ErrorCode.INVALID_REFERENCE, ErrorSeverity.FATAL)); } } else { if (log.isDebugEnabled()) log.debug("assembling collection reference feature <" + type.getName() + "> " + this.getPrototype().getName() + "." + property.getName()); Iterator<String> iter = reference.getXmiIds(); while (iter.hasNext()) { String id = iter.next(); if (reference instanceof XmiExternalReferenceElement) { if (assembleExternalReferences) { Element referent = this.resolveExternalReference(eventNode, reference, id); if (referent != null) this.assembleCollectionReferenceFeature(referent, property, type); } } else { FumlObject target = null; ElementAssembler referencedAssembler = this.assemblerMap.get(id); if (referencedAssembler != null) target = referencedAssembler.getTargetObject(); else target = Environment.getInstance().findElementById(id); if (target != null) this.assembleCollectionReferenceFeature(target, property, type); else throw new ValidationException(new ValidationError(reference, id, ErrorCode.INVALID_REFERENCE, ErrorSeverity.FATAL)); } } } } /** * Resolves a single external reference id by first resolving the pathmap if exists, then checking for * a valid library reference, then a valid qualified element reference. If neither a library or * element reference are found, a validation error is thrown. * @param eventNode the stream event node * @param reference the Xmi reference * @param id the id * @throws ValidationException when no mapping is found for a pathmap or when no library or qualified element * is found for the given id. * @return the resolved element */ private Element resolveExternalReference(StreamNode eventNode, XmiReference reference, String id) { NamespaceDomain domain = null; // only lookup as needed if (id == null) { throw new ValidationException(new ValidationError(reference, "", ErrorCode.INVALID_EXTERNAL_REFERENCE, ErrorSeverity.FATAL)); } String mappedId = id; if (mappedId.toUpperCase().startsWith("PATHMAP:")) { int idx = id.lastIndexOf("#"); String pathmap = id.substring(0, idx); NamespaceMapping mapping = FumlConfiguration.getInstance().findPathmap(pathmap); if (mapping == null) throw new ValidationException(new ValidationError(reference, mappedId, ErrorCode.INVALID_EXTERNAL_REFERENCE, ErrorSeverity.FATAL)); mappedId = mapping.getTarget() + id.substring(idx); if (log.isDebugEnabled()) log.debug("resolved external pathmap reference '" + id + "' to '" + mappedId + "'"); } Element referent = Library.getInstance().lookup(mappedId); if (referent == null) { org.modeldriven.fuml.repository.Element elem = Repository.INSTANCE.findElementByQualifiedName(mappedId); if (elem != null) referent = elem.getDelegate(); } if (referent == null) { if (domain == null) domain = FumlConfiguration.getInstance().findNamespaceDomain(eventNode.getNamespaceURI()); ValidationExemption exemption = FumlConfiguration.getInstance().findValidationExemptionByReference(ValidationExemptionType.EXTERNAL_REFERENCE, reference.getClassifier(), id, eventNode.getNamespaceURI(), domain); if (exemption == null) exemption = FumlConfiguration.getInstance().findValidationExemptionByReference(ValidationExemptionType.EXTERNAL_REFERENCE, reference.getClassifier(), mappedId, eventNode.getNamespaceURI(), domain); // if external reference not specifically exempted from validation if (exemption == null) { throw new ValidationException(new ValidationError(reference, mappedId, ErrorCode.INVALID_EXTERNAL_REFERENCE, ErrorSeverity.FATAL)); } } return referent; } @SuppressWarnings("rawtypes") private void assembleEnumerationFeature(Property property, Object value, Classifier type) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { try { Class[] types = { value.getClass() }; String methodName = "set" + property.getName().substring(0, 1).toUpperCase() + property.getName().substring(1); Method setter = this.getTarget().getClass().getMethod(methodName, types); Object[] args = { value }; setter.invoke(this.getTarget(), args); } catch (NoSuchMethodException e) { try { Field field = this.getTargetClass().getField(property.getName()); field.set(this.getTargetObject(), value); } catch (NoSuchFieldException e2) { String msg = "no fUML (" + this.getTargetObject().getClass().getName() + ") setter method or public field found for enumeration feature " + "<" + type.getName() + "> " + this.getPrototype().getName() + "." + property.getName(); log.warn(msg); } } } @SuppressWarnings({ "unchecked", "rawtypes" }) private void assembleSingularPrimitiveFeature(Property property, Object value, Class javaType) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException { String methodName = "set" + property.getName().substring(0, 1).toUpperCase() + property.getName().substring(1); Object[] args = { value }; try { Method setter = this.getTargetClass().getMethod(methodName, new Class[] { javaType }); setter.invoke(this.getTargetObject(), args); } catch (NoSuchMethodException e) { try { Object targetObject = this.getTargetObject(); Class targetClass = targetObject.getClass(); Field field = targetClass.getField(property.getName()); field.set(targetObject, value); } catch (NoSuchFieldException e2) { String msg = "no fUML (" + this.getTargetObject().getClass().getName() + ") setter method named '"+methodName+"' or public field found for primitive feature " + "<" + javaType.getName() + "> " + this.getPrototype().getName() + "." + property.getName(); log.warn(msg); } } } @SuppressWarnings({ "unchecked", "rawtypes" }) private void assembleCollectionPrimitiveFeature(Property property, Object value, Class javaType) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException { String methodName = "add" + property.getName().substring(0, 1).toUpperCase() + property.getName().substring(1); try { Object[] args = { value }; Method adder = this.getTargetClass().getMethod(methodName, new Class[] { javaType }); adder.invoke(this.getTargetObject(), args); } catch (NoSuchMethodException e) { try { Field field = this.getTargetClass().getField(property.getName()); Object list = field.get(this.getTargetObject()); Method adder = ReflectionUtils.getMethod(list.getClass(), "add", value.getClass()); Object[] args = { value }; adder.invoke(list, args); } catch (NoSuchMethodException e2) { String msg = "no fUML (" + this.getTargetObject().getClass().getName() + ") add method or public field found for primitive collection property " + "<" + javaType.getName() + "> " + this.getPrototype().getName() + "." + property.getName(); log.warn(msg); } catch (NoSuchFieldException e2) { String msg = "no fUML (" + this.getTargetObject().getClass().getName() + ") add method or public field found for primitive collection property " + "<" + javaType.getName() + "> " + this.getPrototype().getName() + "." + property.getName(); log.warn(msg); } } } private void assembleSingularReferenceFeature(FumlObject target, Property property, Classifier type) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { try { String methodName = "set" + property.getName().substring(0, 1).toUpperCase() + property.getName().substring(1); Method setter = ReflectionUtils.getMethod(this.getTarget().getClass(), methodName, target.getClass()); Object[] args = { target }; setter.invoke(this.getTarget(), args); } catch (NoSuchMethodException e) { try { Field field = this.getTargetClass().getField(property.getName()); field.set(this.getTargetObject(), target); } catch (NoSuchFieldException e2) { String msg = "no fUML (" + this.getTargetObject().getClass().getName() + ") setter method or public field found for singular property " + "<" + type.getName() + "> " + this.getPrototype().getName() + "." + property.getName(); log.warn(msg); } } } private void assembleCollectionReferenceFeature(FumlObject target, Property property, Classifier type) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException { try { String methodName = "add" + property.getName().substring(0, 1).toUpperCase() + property.getName().substring(1); Method adder = ReflectionUtils.getMethod(this.getTargetObject().getClass(), methodName, target .getClass()); Object[] args = { target }; adder.invoke(this.getTarget(), args); } catch (NoSuchMethodException e) { try { Field field = this.getTargetClass().getField(property.getName()); Object list = field.get(this.getTargetObject()); Method adder = ReflectionUtils.findMethod(list.getClass(), "add", target.getClass()); Object[] args = { target }; adder.invoke(list, args); } catch (NoSuchMethodException e2) { String msg = "no fUML (" + this.getTargetObject().getClass().getName() + ") add method or public field found for collection property " + "<" + type.getName() + "> " + this.getPrototype().getName() + "." + property.getName(); log.warn(msg); } } } /** * Returns the Java class associated with the given primitive type. * * Note: A primitive type defines * a predefined data type, without any relevant substructure (i.e., it has no defined parts in * the UML/MOF Infrastructure). So a Property who's datatype is a PrimitiveType cannot be treated as * a reference Property, e.g. reference to UnlimitedNatural, because the internal structure of UnlimitedNatural * is not defined. * * @param dataType the dataType to convert. * @return the Java Class */ @SuppressWarnings("rawtypes") private Class toPrimitiveJavaClass(DataType dataType) { if (PrimitiveType.class.isAssignableFrom(dataType.getClass())) { if (dataType.name != null && dataType.name.trim().length() > 0) { if (String.class.getSimpleName().equals(dataType.name)) return java.lang.String.class; else if (Integer.class.getSimpleName().equals(dataType.name)) return int.class; else if (Boolean.class.getSimpleName().equals(dataType.name)) return boolean.class; else if (UnlimitedNatural.class.getSimpleName().equals(dataType.name)) return int.class; else if ("Real".equals(dataType.name)) return float.class; else throw new AssemblyException("unknown dataType (" + dataType.getClass().getName() + ") name: '" + dataType.name + "'"); } else if (dataType.getHref() != null) { if (dataType.getHref().endsWith(String.class.getSimpleName())) return java.lang.String.class; else if (dataType.getHref().endsWith(Integer.class.getSimpleName())) return int.class; else if (dataType.getHref().endsWith(Boolean.class.getSimpleName())) return boolean.class; else if (dataType.getHref().endsWith(UnlimitedNatural.class.getSimpleName())) return int.class; else if (dataType.getHref().endsWith("Real")) return int.class; else throw new AssemblyException("unknown dataType (" + dataType.getClass().getName() + ") href: '" + dataType.getHref() + "'"); } else throw new AssemblyException("expected name or href for primitive type, " + dataType.getClass().getName()); } else { throw new AssemblyException("expected primitive type not (" + dataType.getClass().getName() + ") name: '" + dataType.name + "'"); } } /** * Returns the Java value associated with the given String value in the context of the given * primitive type and Java type. * * Note: A primitive type defines * a predefined data type, without any relevant substructure (i.e., it has no defined parts in * the UML/MOF Infrastructure). So a Property who's datatype is a PrimitiveType cannot be treated as * a reference Property, e.g. reference to UnlimitedNatural, because the internal structure of UnlimitedNatural * is not defined. * * @param value the value to convert. * @param dataType the data type under which to evaluate the the String value. * @param javaType the Java type under which to evaluate the the String value. * @return the value */ @SuppressWarnings("rawtypes") private Object toPrimitiveJavaValue(String value, DataType dataType, Class javaType) { if (javaType.equals(java.lang.String.class)) return value; else if (javaType.equals(java.lang.Integer.class)) try { return Integer.valueOf(value); } catch (NumberFormatException e) { if (value == null || value.length() == 0) return new Integer(0); else if (UnlimitedNatural.class.getSimpleName().equals(dataType.name) && "*".equals(value)) return new Integer(-1); else throw e; } else if (javaType.equals(int.class)) try { return Integer.valueOf(value).intValue(); } catch (NumberFormatException e) { if (value == null || value.length() == 0) return 0; else if (UnlimitedNatural.class.getSimpleName().equals(dataType.name) && "*".equals(value)) return -1; else throw e; } else if (javaType.equals(java.lang.Boolean.class)) return Boolean.valueOf(value); else if (javaType.equals(boolean.class)) return (boolean) Boolean.valueOf(value).booleanValue(); else if (javaType.equals(float.class)) { return (float)Float.valueOf(value).floatValue(); } else return value; } @SuppressWarnings({ "rawtypes", "unchecked" }) private Object toEnumerationValue(String value, Classifier type) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { String pkg = metadata.getJavaPackageNameForClass(type); String qualifiedName = pkg + "." + type.getName(); Class enumClass = Class.forName(qualifiedName); if (!enumClass.isEnum()) throw new AssemblyException("expected class as enum, " + enumClass.getName()); Method valueOf = enumClass.getMethod("valueOf", new Class[] { String.class }); if (JavaKeyWords.getInstance().isKeyWord(value)) value = value + "_"; Object enumValue = valueOf.invoke(enumClass, value); return enumValue; } public String getXmiId() { return this.getTargetObject().getXmiId(); } public Element getTarget() { return target; } public FumlObject getTargetObject() { if (target != null) return target; else return targetComment; } @SuppressWarnings("rawtypes") public Class getTargetClass() { if (target != null) return target.getClass(); else return targetComment.getClass(); } public Comment getTargetComment() { return targetComment; } public Class_ getPrototype() { return prototype; } public XmiNode getSource() { return source; } public XmiNode getParent() { return parent; } public void addReference(XmiReference ref) { if (references == null) references = new ArrayList<XmiReference>(); references.add(ref); } public boolean isAssembleExternalReferences() { return assembleExternalReferences; } public void setAssembleExternalReferences(boolean assembleExternalReferences) { this.assembleExternalReferences = assembleExternalReferences; } }