/**
* Copyright 2012 Red Hat, Inc. and/or its affiliates.
*
* 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 org.kie.workbench.common.screens.datamodeller.client;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.kie.workbench.common.screens.datamodeller.model.maindomain.MainDomainAnnotations;
import org.kie.workbench.common.screens.datamodeller.client.util.AnnotationValueHandler;
import org.kie.workbench.common.screens.datamodeller.client.util.DataModelerUtils;
import org.kie.workbench.common.screens.datamodeller.events.DataModelerEvent;
import org.kie.workbench.common.screens.datamodeller.events.DataModelerValueChangeEvent;
import org.kie.workbench.common.services.datamodeller.core.PropertyType;
import org.kie.workbench.common.services.datamodeller.core.DataModel;
import org.kie.workbench.common.services.datamodeller.core.DataObject;
import org.kie.workbench.common.services.datamodeller.core.ObjectProperty;
import static org.kie.workbench.common.screens.datamodeller.client.util.DataModelerUtils.*;
public class DataModelHelper {
private DataModel dataModel;
// Map linking Objects with the Objects they are being referenced by (e.g. x.y.A --> {u.v.B} means B holds a reference to A)
private Map<String, Set<String>> referencedBy = new HashMap<String, Set<String>>(10);
// Map linking DataObjects with the Objects they are referencing by (e.g. u.v.B --> {x.y.A} means B references A)
private Map<String, Set<String>> references = new HashMap<String, Set<String>>(10);
// Map that keeps track of the offspring a parent class has.
private Map<String, Set<String>> offspringMap = new HashMap<String, Set<String>>(10);
// Map of all class names and their corresponding labels (if any) that coexist within a project
private Map<String, String> classNames = new HashMap<String, String>(10);
// Map of all labelled class names that coexist within a project
private Map<String, String> labelledClassNames = new TreeMap<String, String>();
Map <String, PropertyType> orderedBaseTypes = new TreeMap<String, PropertyType>();
Map <String, PropertyType> baseTypesByClassName = new HashMap<String, PropertyType>();
String contextId;
public DataModelHelper( String contextId ) {
this.contextId = contextId;
}
public Collection<String> getDataObjectReferences( String className ) {
Collection<String> c = new TreeSet<String>();
Set<String> s = referencedBy.get( className );
if ( s != null ) {
c.addAll( s );
}
s = offspringMap.get( className );
if ( s != null ) {
c.addAll( s );
}
return c;
}
public String getObjectLabelByClassName(String className) {
return classNames.get(className);
}
public List<String> getClassList() {
List<String> l = new ArrayList<String>(classNames.size());
l.addAll(classNames.keySet());
return Collections.unmodifiableList(l);
}
public Map<String, String> getLabelledClassMap() {
return Collections.unmodifiableMap(labelledClassNames);
}
public Map <String, PropertyType> getOrderedBaseTypes() {
return orderedBaseTypes;
}
// DataModelHelper methods
public void dataModelChanged(DataModelerValueChangeEvent changeEvent) {
if (changeEvent.isFromContext( contextId )) {
if (DataModelerEvent.DATA_OBJECT_EDITOR.equalsIgnoreCase(changeEvent.getSource())) {
// If any object referenced the object whose name or package just changed, we need to adjust those internally
if ("name".equals(changeEvent.getValueName())) nameChanged( changeEvent.getCurrentDataObject(),
(String) changeEvent.getOldValue(),
(String) changeEvent.getNewValue());
else if ("packageName".equals(changeEvent.getValueName())) packageChanged( changeEvent.getCurrentDataObject(),
(String) changeEvent.getOldValue(),
(String) changeEvent.getNewValue());
}
reset();
}
}
private void nameChanged(DataObject object, String oldName, String newName) {
adjustDataObjects( assembleClassName(object.getPackageName(), oldName),
assembleClassName(object.getPackageName(), newName) );
}
private void packageChanged(DataObject object, String oldPackage, String newPackage) {
adjustDataObjects( assembleClassName(oldPackage, object.getName()),
assembleClassName(newPackage, object.getName()) );
}
private void adjustDataObjects(String oldClassName, String newClassname) {
//TODO, WM, this method seems to be no longer needed after we changed to 1:1 edition
/*
Set<String> s = referencedBy.get(oldClassName);
if ( s != null && s.size() != 0 ) {
// Get the object referencing the modified object
for (String refHolderClassName : s) {
// Go get the referencing object (in case of a 'self' reference, find it through the new name!)
DataObjectTO refHolder = dataModel.getDataObjectByClassName(oldClassName.equals(refHolderClassName) ? newClassname : refHolderClassName);
for (ObjectPropertyTO prop : refHolder.getProperties()) {
if (oldClassName.equals(prop.getClassName())) {
prop.setClassName(newClassname);
}
}
}
}
s = offspringMap.get(oldClassName);
if ( s != null && s.size() != 0 ) {
for (String offspringClassName : s) {
DataObjectTO offspring = dataModel.getDataObjectByClassName(offspringClassName);
offspring.setSuperClassName(newClassname);
}
}
*/
}
public void dataObjectReferenced(String objectClassName, String subjectClassName) {
objectReferenced(objectClassName, subjectClassName);
}
public void dataObjectUnReferenced(String objectClassName, String subjectClassName) {
objectUnReferenced(objectClassName, subjectClassName);
}
public void dataObjectExtended(String parentClassName, String offspringClassName, Boolean _extends) {
objectExtended(parentClassName, offspringClassName, _extends);
}
public void dataObjectDeleted(String objectClassName) {
reset();
}
public void dataObjectCreated(String objectClassName) {
reset();
}
public void dataObjectSelected(String objectClassName) {
}
public void dataObjectUnSelected(String objectClassName) {
}
public Boolean isBaseType(String type) {
return baseTypesByClassName.containsKey(type);
}
public Boolean isPrimitiveType(String type) {
PropertyType propertyType;
return ( propertyType = baseTypesByClassName.get( type ) ) != null && propertyType.isPrimitive();
}
/**
* Evaluate if an object can safely extend another one (at least as far as the extension hierarchy is concerned).
* @param offspringCandidate The class name of the extending object
* @param parentCandidate The class name of the extended object
* @return True if the extension does not provoke a conflict with the existing extension hierarchy.
*/
public Boolean isAssignableFrom(String offspringCandidate, String parentCandidate) {
if (offspringCandidate == null || offspringCandidate.length() == 0 ||
parentCandidate == null || parentCandidate.length() == 0 ||
offspringCandidate.equals(parentCandidate)) return false;
Set<String> candidatesOffspring = offspringMap.get(offspringCandidate);
boolean candidateHasOffspring = candidatesOffspring != null && candidatesOffspring.size() > 0;
if (candidateHasOffspring) {
if (candidatesOffspring.contains(parentCandidate)) return false;
for (String newOffspringCandidate : candidatesOffspring) {
if (!isAssignableFrom(newOffspringCandidate, parentCandidate)) return false;
}
}
return true;
}
public void setDataModel(DataModel dataModel) {
this.dataModel = dataModel;
reset();
}
public void setBaseTypes(List<PropertyType> baseTypes) {
if (baseTypes != null) {
for (PropertyType type : baseTypes) {
orderedBaseTypes.put(type.getName(), type);
baseTypesByClassName.put(type.getClassName(), type);
}
}
}
// Todo can be improved if required for performance reasons
// Brute force recalculate all
private void reset() {
referencedBy.clear();
references.clear();
classNames.clear();
labelledClassNames.clear();
offspringMap.clear();
if (dataModel != null) {
for (DataObject extClassName : dataModel.getExternalClasses()) {
classNames.put(extClassName.getClassName(), null);
labelledClassNames.put(extClassName.getClassName(), extClassName.getClassName());
}
for (DataObject object : dataModel.getDataObjects()) {
String className = object.getClassName();
classNames.put(className, AnnotationValueHandler.getStringValue( object, MainDomainAnnotations.LABEL_ANNOTATION ));
labelledClassNames.put(DataModelerUtils.getDataObjectFullLabel(object), className);
String superClassName = object.getSuperClassName();
if (superClassName != null && !"".equals(superClassName)) objectExtended(superClassName, className, true);
for (ObjectProperty prop : object.getProperties()) {
if (!prop.isBaseType()) {
objectReferenced(prop.getClassName(), object.getClassName());
}
}
}
}
}
private void objectReferenced(String objectClassName, String subjectClassName) {
Set<String> refs = referencedBy.get(objectClassName);
if (refs == null) refs = new HashSet<String>();
refs.add(subjectClassName);
referencedBy.put(objectClassName, refs);
refs = references.get(subjectClassName);
if (refs == null) refs = new HashSet<String>();
refs.add(objectClassName);
references.put(subjectClassName, refs);
}
private void objectUnReferenced(String objectClassName, String subjectClassName) {
Set<String> refs = referencedBy.get(objectClassName);
if (refs != null && refs.size() > 0) {
refs.remove(subjectClassName);
}
// else ("Error de-referencing data object (referenced object)."));
refs = references.get(subjectClassName);
if (refs != null && refs.size() > 0) {
refs.remove(objectClassName);
}
// else ("Error de-referencing data object (referring object)."));
}
private void objectExtended(String parentClassName, String offspringClassName, Boolean _extends) {
Set<String> _offspring = offspringMap.get(parentClassName);
if (_extends) {
if (_offspring != null ) {
_offspring.add( offspringClassName );
} else {
_offspring = new HashSet<String>();
_offspring.add(offspringClassName);
offspringMap.put(parentClassName, _offspring);
}
} else {
if (_offspring != null ) {
if ( _offspring.size() > 0) {
_offspring.remove(offspringClassName);
}
if (_offspring.size() == 0) {
offspringMap.remove(parentClassName);
}
// else ("Superclass referencing error"));
}
}
}
}