/*
* Copyright (c) 2010-2016 Evolveum
*
* Licensed 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 com.evolveum.midpoint.prism;
import com.evolveum.midpoint.prism.path.IdItemPathSegment;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.ItemPathSegment;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
import com.evolveum.midpoint.prism.path.ObjectReferencePathSegment;
import com.evolveum.midpoint.prism.path.ParentPathSegment;
import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.PrettyPrinter;
import org.jetbrains.annotations.NotNull;
import com.evolveum.midpoint.util.QNameUtil;
import org.apache.commons.lang.StringUtils;
import java.util.*;
import javax.xml.namespace.QName;
/**
* TODO
*
* @author Radovan Semancik
*
*/
public class ComplexTypeDefinitionImpl extends TypeDefinitionImpl implements ComplexTypeDefinition {
private static final long serialVersionUID = 2655797837209175037L;
@NotNull private final List<ItemDefinition> itemDefinitions = new ArrayList<>();
private boolean containerMarker;
private boolean objectMarker;
private boolean xsdAnyMarker;
private boolean listMarker;
private QName extensionForType;
private String defaultNamespace;
@NotNull private List<String> ignoredNamespaces = new ArrayList<>();
public ComplexTypeDefinitionImpl(@NotNull QName typeName, @NotNull PrismContext prismContext) {
super(typeName, prismContext);
}
//region Trivia
protected String getSchemaNamespace() {
return getTypeName().getNamespaceURI();
}
/**
* Returns set of item definitions.
*
* The set contains all item definitions of all types that were parsed.
* Order of definitions is insignificant.
*
* @return set of definitions
*/
@NotNull
@Override
public List<? extends ItemDefinition> getDefinitions() {
return Collections.unmodifiableList(itemDefinitions);
}
public void add(ItemDefinition<?> definition) {
itemDefinitions.add(definition);
}
@Override
public QName getExtensionForType() {
return extensionForType;
}
public void setExtensionForType(QName extensionForType) {
this.extensionForType = extensionForType;
}
@Override
public boolean isContainerMarker() {
return containerMarker;
}
public void setContainerMarker(boolean containerMarker) {
this.containerMarker = containerMarker;
}
@Override
public boolean isObjectMarker() {
return objectMarker;
}
@Override
public boolean isXsdAnyMarker() {
return xsdAnyMarker;
}
public void setXsdAnyMarker(boolean xsdAnyMarker) {
this.xsdAnyMarker = xsdAnyMarker;
}
public boolean isListMarker() {
return listMarker;
}
public void setListMarker(boolean listMarker) {
this.listMarker = listMarker;
}
@Override
public String getDefaultNamespace() {
return defaultNamespace;
}
public void setDefaultNamespace(String defaultNamespace) {
this.defaultNamespace = defaultNamespace;
}
@Override
@NotNull
public List<String> getIgnoredNamespaces() {
return ignoredNamespaces;
}
public void setIgnoredNamespaces(@NotNull List<String> ignoredNamespaces) {
this.ignoredNamespaces = ignoredNamespaces;
}
public void setObjectMarker(boolean objectMarker) {
this.objectMarker = objectMarker;
}
//endregion
//region Creating definitions
public PrismPropertyDefinitionImpl createPropertyDefinition(QName name, QName typeName) {
PrismPropertyDefinitionImpl propDef = new PrismPropertyDefinitionImpl(name, typeName, prismContext);
itemDefinitions.add(propDef);
return propDef;
}
// Creates reference to other schema
// TODO: maybe check if the name is in different namespace
// TODO: maybe create entirely new concept of property reference?
public PrismPropertyDefinition createPropertyDefinition(QName name) {
PrismPropertyDefinition propDef = new PrismPropertyDefinitionImpl(name, null, prismContext);
itemDefinitions.add(propDef);
return propDef;
}
public PrismPropertyDefinitionImpl createPropertyDefinition(String localName, QName typeName) {
QName name = new QName(getSchemaNamespace(), localName);
return createPropertyDefinition(name, typeName);
}
public PrismPropertyDefinition createPropertyDefinition(String localName, String localTypeName) {
QName name = new QName(getSchemaNamespace(), localName);
QName typeName = new QName(getSchemaNamespace(), localTypeName);
return createPropertyDefinition(name, typeName);
}
//endregion
//region Finding definitions
// TODO deduplicate w.r.t. findNamedItemDefinition
@Override
public <T extends ItemDefinition> T findItemDefinition(@NotNull QName name, @NotNull Class<T> clazz, boolean caseInsensitive) {
for (ItemDefinition def : getDefinitions()) {
if (def.isValidFor(name, clazz, caseInsensitive)) {
return (T) def;
}
}
return null;
}
@Override
public <ID extends ItemDefinition> ID findItemDefinition(@NotNull ItemPath path, @NotNull Class<ID> clazz) {
for (;;) {
if (path.isEmpty()) {
throw new IllegalArgumentException("Cannot resolve empty path on complex type definition "+this);
}
ItemPathSegment first = path.first();
if (first instanceof NameItemPathSegment) {
QName firstName = ((NameItemPathSegment)first).getName();
return findNamedItemDefinition(firstName, path.rest(), clazz);
} else if (first instanceof IdItemPathSegment) {
path = path.rest();
} else if (first instanceof ParentPathSegment) {
ItemPath rest = path.rest();
ComplexTypeDefinition parent = getSchemaRegistry().determineParentDefinition(this, rest);
if (rest.isEmpty()) {
// requires that the parent is defined as an item (container, object)
return (ID) getSchemaRegistry().findItemDefinitionByType(parent.getTypeName());
} else {
return parent.findItemDefinition(rest, clazz);
}
} else if (first instanceof ObjectReferencePathSegment) {
throw new IllegalStateException("Couldn't use '@' path segment in this context. CTD=" + getTypeName() + ", path=" + path);
} else {
throw new IllegalStateException("Unexpected path segment: " + first + " in " + path);
}
}
}
// path starts with NamedItemPathSegment
public <ID extends ItemDefinition> ID findNamedItemDefinition(@NotNull QName firstName, @NotNull ItemPath rest, @NotNull Class<ID> clazz) {
ID found = null;
for (ItemDefinition def : getDefinitions()) {
if (def.isValidFor(firstName, clazz, false)) {
if (found != null) {
throw new IllegalStateException("More definitions found for " + firstName + "/" + rest + " in " + this);
}
found = (ID) def.findItemDefinition(rest, clazz);
if (QNameUtil.hasNamespace(firstName)) {
return found; // if qualified then there's no risk of matching more entries
}
}
}
return found;
}
//endregion
/**
* Merge provided definition into this definition.
*/
@Override
public void merge(ComplexTypeDefinition otherComplexTypeDef) {
for (ItemDefinition otherItemDef: otherComplexTypeDef.getDefinitions()) {
add(otherItemDef.clone());
}
}
@Override
public void revive(PrismContext prismContext) {
if (this.prismContext != null) {
return;
}
this.prismContext = prismContext;
for (ItemDefinition def: itemDefinitions) {
def.revive(prismContext);
}
}
@Override
public boolean isEmpty() {
return itemDefinitions.isEmpty();
}
@NotNull
@Override
public ComplexTypeDefinitionImpl clone() {
ComplexTypeDefinitionImpl clone = new ComplexTypeDefinitionImpl(this.typeName, prismContext);
copyDefinitionData(clone);
return clone;
}
public ComplexTypeDefinition deepClone() {
return deepClone(new HashMap<>());
}
@NotNull
@Override
public ComplexTypeDefinition deepClone(Map<QName, ComplexTypeDefinition> ctdMap) {
if (ctdMap != null) {
ComplexTypeDefinition clone = ctdMap.get(this.getTypeName());
if (clone != null) {
return clone; // already cloned
}
}
ComplexTypeDefinitionImpl clone = clone(); // shallow
if (ctdMap != null) {
ctdMap.put(this.getTypeName(), clone);
}
clone.itemDefinitions.clear();
for (ItemDefinition itemDef: this.itemDefinitions) {
clone.itemDefinitions.add(itemDef.deepClone(ctdMap));
}
return clone;
}
protected void copyDefinitionData(ComplexTypeDefinitionImpl clone) {
super.copyDefinitionData(clone);
clone.containerMarker = this.containerMarker;
clone.objectMarker = this.objectMarker;
clone.xsdAnyMarker = this.xsdAnyMarker;
clone.extensionForType = this.extensionForType;
clone.defaultNamespace = this.defaultNamespace;
clone.ignoredNamespaces = this.ignoredNamespaces;
clone.itemDefinitions.addAll(this.itemDefinitions);
}
public void replaceDefinition(QName propertyName, ItemDefinition newDefinition) {
for (int i=0; i<itemDefinitions.size(); i++) {
ItemDefinition itemDef = itemDefinitions.get(i);
if (itemDef.getName().equals(propertyName)) {
if (!itemDef.getClass().isAssignableFrom(newDefinition.getClass())) {
throw new IllegalArgumentException("The provided definition of class "+newDefinition.getClass().getName()+" does not match existing definition of class "+itemDef.getClass().getName());
}
if (!itemDef.getName().equals(newDefinition.getName())) {
newDefinition = newDefinition.clone();
((ItemDefinitionImpl) newDefinition).setName(propertyName);
}
// Make sure this is set, not add. set will keep correct ordering
itemDefinitions.set(i, newDefinition);
return;
}
}
throw new IllegalArgumentException("The definition with name "+propertyName+" was not found in complex type "+getTypeName());
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + (containerMarker ? 1231 : 1237);
result = prime * result + ((extensionForType == null) ? 0 : extensionForType.hashCode());
result = prime * result + ((itemDefinitions == null) ? 0 : itemDefinitions.hashCode());
result = prime * result + (objectMarker ? 1231 : 1237);
result = prime * result + (xsdAnyMarker ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ComplexTypeDefinitionImpl other = (ComplexTypeDefinitionImpl) obj;
if (containerMarker != other.containerMarker) {
return false;
}
if (extensionForType == null) {
if (other.extensionForType != null) {
return false;
}
} else if (!extensionForType.equals(other.extensionForType)) {
return false;
}
if (!itemDefinitions.equals(other.itemDefinitions)) {
return false;
}
if (objectMarker != other.objectMarker) {
return false;
}
if (xsdAnyMarker != other.xsdAnyMarker) {
return false;
}
// TODO ignored and default namespaces
return true;
}
@Override
public String debugDump(int indent) {
StringBuilder sb = new StringBuilder();
for (int i=0; i<indent; i++) {
sb.append(DebugDumpable.INDENT_STRING);
}
sb.append(toString());
if (extensionForType != null) {
sb.append(",ext:");
sb.append(PrettyPrinter.prettyPrint(extensionForType));
}
if (ignored) {
sb.append(",ignored");
}
if (containerMarker) {
sb.append(",Mc");
}
if (objectMarker) {
sb.append(",Mo");
}
if (xsdAnyMarker) {
sb.append(",Ma");
}
extendDumpHeader(sb);
for (ItemDefinition def : getDefinitions()) {
sb.append("\n");
sb.append(def.debugDump(indent+1));
extendDumpDefinition(sb, def);
}
return sb.toString();
}
protected void extendDumpHeader(StringBuilder sb) {
// Do nothing
}
protected void extendDumpDefinition(StringBuilder sb, ItemDefinition<?> def) {
// Do nothing
}
/**
* Return a human readable name of this class suitable for logs.
*/
@Override
protected String getDebugDumpClassName() {
return "CTD";
}
@Override
public String getDocClassName() {
return "complex type";
}
// @Override
// public void accept(Visitor visitor) {
// super.accept(visitor);
// itemDefinitions.forEach(def -> def.accept(visitor));
// }
}