blob: cf9e743bcabe977bc3a0a5993efd97e0c3c3d33a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.xml.axi.impl;
import java.util.ArrayList;
import java.util.List;
import org.netbeans.modules.xml.axi.AXIComponent;
import org.netbeans.modules.xml.axi.AXIComponent.ComponentType;
import org.netbeans.modules.xml.axi.AXIDocument;
import org.netbeans.modules.xml.axi.AXIType;
import org.netbeans.modules.xml.axi.AnyAttribute;
import org.netbeans.modules.xml.axi.AnyElement;
import org.netbeans.modules.xml.axi.Attribute;
import org.netbeans.modules.xml.axi.Compositor;
import org.netbeans.modules.xml.axi.Element;
import org.netbeans.modules.xml.axi.visitor.DeepAXITreeVisitor;
import org.netbeans.modules.xml.schema.model.AttributeReference;
import org.netbeans.modules.xml.schema.model.ElementReference;
import org.netbeans.modules.xml.schema.model.GlobalAttribute;
import org.netbeans.modules.xml.schema.model.GlobalElement;
import org.netbeans.modules.xml.schema.model.LocalAttribute;
import org.netbeans.modules.xml.schema.model.LocalElement;
import org.netbeans.modules.xml.schema.model.SchemaComponent;
import org.netbeans.modules.xml.axi.ContentModel;
/**
* AXIModelUpdater updates the AXIModel to keep it in sync
* with the SchemaModel. The sync alogirithm works as follows:
*
* For every component X it creates another component X' by using
* the peer in X.
* 1. For each child in X, checks its existence in X'.
* If not found, deleted from X.
* 2. For each child in X', checks its existence in X.
* If not found, added to X.
* 3. Repeat the steps for for all remaining child in X after step 1.
*
* @author Samaresh (Samaresh.Panda@Sun.Com)
*/
public class AXIModelUpdater extends DeepAXITreeVisitor {
/**
* Creates a new instance of AXIModelUpdater
*/
public AXIModelUpdater(AXIModelImpl model) {
this.model = model;
}
/**
* Keeps the AXIModel in sync with schema model.
* Returns true if success, false if failed.
*/
public boolean doSync() {
try {
syncCompleted = false;
//first sync the document
model.getRoot().accept(this);
//sync all global elements and attributes
for(AXIComponent child : model.getRoot().getChildren()) {
if(child instanceof ContentModel)
continue;
child.accept(this);
}
//finally sync all the content models
for(ContentModel contentModel : model.getRoot().getContentModels()) {
contentModel.accept(this);
}
syncCompleted = true;
} catch(Exception ex) {
//bad things happened
}
return syncCompleted;
}
/**
* Syncs only the specified component.
*/
public void syncOne(AXIComponent component) {
component.accept(this);
}
/**
* Syncs one component at a time. For each component, it creates
* the same component as if it was created from scratch and then merges
* the difference to the original one and then visits the modified list
* of children.
*/
protected void visitChildren(AXIComponent original) {
if(!original.canVisitChildren())
return;
//skip proxies if the original is from the same model
if(original.getComponentType() == ComponentType.PROXY &&
original.getModel() == original.getOriginal().getModel()) {
return;
}
AXIComponentCreator creator = new AXIComponentCreator(model);
AXIComponent altered = getAltered(original);
assert(altered != null);
List<AXIComponent> modifiedChildren = synchronize(original, altered);
if( (modifiedChildren == null) ||
(original instanceof AXIDocument) ) {
return;
}
//visit the children that were nethier removed nor added
for(AXIComponent child : modifiedChildren) {
child.accept(this);
}
}
/**
* We do not sync proxies, unless they represent items from other files.
* Hence, for a proxy, get the original, else create a new one.
*/
private AXIComponent getAltered(AXIComponent original) {
if(original.getComponentType() == ComponentType.PROXY) {
return original.getOriginal();
}
//create the same component from the original's peer
AXIComponentCreator creator = new AXIComponentCreator(model);
return creator.createNew(original.getPeer());
}
/**
* Step 1: From the original tree, delete the children that no longer
* exist in new tree. Remaining ones exist and we must sync them.
* Step 2: From the new tree, add the children that are new w.r.t. the
* original.
*/
private List<AXIComponent> synchronize(AXIComponent original, AXIComponent altered) {
//first remove the removed children
List<AXIComponent> modifiedChildren = removeRemovedChildren(original, altered);
//add new children
addNewChildren(original, altered);
return modifiedChildren;
}
/**
* Removes the list of children, that no longer exists in the altered tree.
*/
private List<AXIComponent> removeRemovedChildren(AXIComponent original, AXIComponent altered) {
List<AXIComponent> removedChildren = new ArrayList<AXIComponent>();
List<AXIComponent> dirtyChildren = new ArrayList<AXIComponent>();
for(AXIComponent oChild : original.getChildren()) {
int index = childExists(oChild, altered, true);
if( (index == -1) ||
oChild.getPeer().getParent() == null ||
oChild.getPeer().getModel() == null) {
removedChildren.add(oChild);
continue;
}
dirtyChildren.add(oChild);
}
for(AXIComponent child : removedChildren) {
original.removeChild(child);
}
return dirtyChildren;
}
/**
* Finds all newly added children and surgically inserts
* them into the original component at appropriate position.
*/
private void addNewChildren(AXIComponent original, AXIComponent altered) {
int size = altered.getChildren().size();
for(int index=0; index<size; index++) {
AXIComponent aChild = altered.getChildren().get(index);
int indexInOriginalTree = childExists(aChild, original, false);
//this is a new child, add it to the original tree
if(indexInOriginalTree == -1) {
if(original.getComponentType() == ComponentType.PROXY) {
AXIComponent proxy = model.getComponentFactory().createProxy(aChild);
original.addChildAtIndex(proxy, index); //items from other model/file.
} else {
if(aChild.getPeer() != null && aChild.getPeer().getModel() != null)
original.addChildAtIndex(aChild, index); //same model/file.
}
continue;
}
//if found, remove these transient objects as listeners.
if(aChild.getComponentType() == ComponentType.PROXY) {
aChild.getSharedComponent().removeListener(aChild);
}
}
}
/**
* Checks if the specified component exists as a child of the given parent.
* Returns a non negative index if found, -1 otherwise.
*/
private int childExists(AXIComponent child, AXIComponent parent, boolean checkOriginal) {
int size = parent.getChildren().size();
for(int index=0; index<size; index++) {
AXIComponent c = parent.getChildren().get(index);
if(c.getPeer() == child.getPeer()) {
if(checkOriginal) {
if(!validateOriginal(child, c))
return -1;
}
return index;
}
}
return -1;
}
/**
* Validates the original. First, it checks for a valid peer.
* Then some more sanity checks.
*/
private boolean validateOriginal(AXIComponent original, AXIComponent altered) {
//first pass: validate the peer
PeerValidator validator = new PeerValidator();
if(!validator.validate(original))
return false;
//altered child is a proxy where as original is not.
//possible that codegen creates a GCT(SEQ(LE))) and sets
//the peer of SEQ and LE to arbitrary AXI components.
//These components should be removed and then be added as proxies.
if(altered.getComponentType() == ComponentType.PROXY &&
original.getComponentType() != ComponentType.PROXY) {
return false;
}
return true;
}
/**
* Visit the AXIDocument.
*/
public void visit(AXIDocument document) {
Util.updateAXIDocument(document);
visitChildren(document);
}
/**
* Visit an element.
*/
public void visit(Element element) {
if(element instanceof ElementImpl)
visit((ElementImpl)element);
if(element instanceof ElementRef)
visit((ElementRef)element);
if(element instanceof ElementProxy)
((ElementProxy)element).forceFireEvent();
}
/**
* For an ElementImpl, add and remove child means that type
* changed, in which case we should recreate children,
* Same if the type changed.
*/
public void visit(ElementImpl element) {
SchemaComponent schemaComponent = element.getPeer();
if(schemaComponent instanceof LocalElement)
Util.updateLocalElement(element);
if(schemaComponent instanceof GlobalElement)
Util.updateGlobalElement(element);
//if type changed, update children
SchemaComponent newType = Util.getSchemaType(model, element.getPeer());
AXIType axiType = Util.getAXIType(element, newType);
if(element.getType() != axiType)
element.setType(axiType);
//sync children.
visitChildren(element);
}
public void visit(ElementRef elementRef) {
ElementReference ref = (ElementReference)elementRef.getPeer();
GlobalElement newGE = ref.getRef().get();
SchemaComponent originalGE = elementRef.getReferent().getPeer();
if(originalGE == newGE) {
Util.updateElementReference(elementRef);
elementRef.forceFireEvent();
visitChildren(elementRef);
return;
}
//the element ref now points to a different global element
AXIComponent newElement = Util.lookup(elementRef.getModel(), newGE);
if(newElement != null && newElement instanceof Element) {
elementRef.setRef((Element)newElement);
elementRef.forceFireEvent();
}
}
public void visit(Attribute attribute) {
if(attribute instanceof AttributeImpl)
visit((AttributeImpl)attribute);
if(attribute instanceof AttributeRef)
visit((AttributeRef)attribute);
if(attribute instanceof AttributeProxy)
((AttributeProxy)attribute).forceFireEvent();
}
public void visit(AttributeImpl attribute) {
SchemaComponent schemaComponent = attribute.getPeer();
if(schemaComponent instanceof LocalAttribute) {
Util.updateLocalAttribute(attribute);
AXIType type = Util.getDatatype(attribute.getModel(), (LocalAttribute)schemaComponent);
if(type != null)
attribute.setType(type);
}
if(schemaComponent instanceof GlobalAttribute) {
Util.updateGlobalAttribute(attribute);
AXIType type = Util.getDatatype(attribute.getModel(), (GlobalAttribute)schemaComponent);
if(type != null)
attribute.setType(type);
}
}
public void visit(AttributeRef attributeRef) {
AttributeReference ref = (AttributeReference)attributeRef.getPeer();
SchemaComponent originalGA = attributeRef.getReferent().getPeer();
GlobalAttribute newGA = ref.getRef().get();
if(originalGA == newGA) {
Util.updateAttributeReference(attributeRef);
attributeRef.forceFireEvent();
return;
}
//the attribute ref now points to a different global attribute
AXIComponent newAttr = Util.lookup(attributeRef.getModel(), newGA);
if(newAttr != null && newAttr instanceof Attribute) {
attributeRef.setRef((Attribute)newAttr);
attributeRef.forceFireEvent();
}
}
public void visit(Compositor compositor) {
Util.updateCompositor(compositor);
visitChildren(compositor);
}
public void visit(ContentModel contentModel) {
Util.updateContentModel(contentModel);
visitChildren(contentModel);
}
public void visit(AnyAttribute attribute) {
Util.updateAnyAttribute(attribute);
}
public void visit(AnyElement element) {
Util.updateAnyElement(element);
}
////////////////////////////////////////////////////////////////////
////////////////////////// member variables ////////////////////////
////////////////////////////////////////////////////////////////////
private AXIModelImpl model;
private boolean syncCompleted;
}