/*
* Copyright 2003-2013 JetBrains s.r.o.
*
* 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 jetbrains.mps.lang.pattern.util;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.AttributeOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.logging.Logger;
import jetbrains.mps.util.EqualUtil;
import jetbrains.mps.util.IterableUtil;
import org.apache.log4j.LogManager;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.language.SDataType;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class MatchingUtil {
private static final Logger LOG = Logger.wrap(LogManager.getLogger(MatchingUtil.class));
public static boolean matchNodes(SNode node1, SNode node2) {
return matchNodes(node1, node2, IMatchModifier.DEFAULT, true);
}
public static boolean matchNodes(SNode node1, SNode node2, IMatchModifier matchModifier, boolean matchAttributes) {
if (node1 == node2) return true;
if (node1 == null) return false;
if (node2 == null) return false;
if (!node1.getConcept().equals(node2.getConcept())) return false;
//properties
final Set<SProperty> properties = new HashSet<SProperty>();
properties.addAll(IterableUtil.asSet(node1.getProperties()));
properties.addAll(IterableUtil.asSet(node2.getProperties()));
for (SProperty property : properties) {
// use of SNode.getProperty() directly (not SNodeAccessUtil.getProperty())
// as we are checking for structural equality. If there's e.g. a 'derived' property
// == getName() + getNodeId(), its values from SNodeAccessUtil would differ and nodes would not match
// (assuming NodeId is different and nodes otherwise match).
String stringValue1 = node1.getProperty(property);
String stringValue2 = node2.getProperty(property);
Object propertyValue1 = stringValue1;
Object propertyValue2 = stringValue2;
if (!IterableUtil.asCollection(node1.getConcept().getProperties()).contains(property)) {
SNode diagnosticsNode = stringValue1 != null ? node1 : node2;
LOG.warning("can't find a property declaration for property `" + property.getName() + "` in a concept " + diagnosticsNode.getConcept().getQualifiedName(), diagnosticsNode);
LOG.warning("try to compare just properties' internal values");
} else {
SDataType dataType = property.getType();
if (dataType != null) {
propertyValue1 = dataType.fromString(stringValue1);
propertyValue2 = dataType.fromString(stringValue2);
}
}
if (!EqualUtil.equals(propertyValue1, propertyValue2)) return false;
}
//-- matching references
Set<SReferenceLink> referenceRoles = new HashSet<SReferenceLink>();
for (SReference ref : node1.getReferences()) {
referenceRoles.add(ref.getLink());
}
for (SReference ref : node2.getReferences()) {
referenceRoles.add(ref.getLink());
}
for (SReferenceLink role : referenceRoles) {
SNode target1 = node1.getReferenceTarget(role);
SNode target2 = node2.getReferenceTarget(role);
if (matchModifier.accept(target1, target2)) {
matchModifier.performAction(target1, target2);
continue;
}
if (target2 != target1) return false;
}
// children
Set<SContainmentLink> childRoles = jetbrains.mps.util.SNodeOperations.getChildRoles(node1, matchAttributes);
childRoles.addAll(jetbrains.mps.util.SNodeOperations.getChildRoles(node2, matchAttributes));
for (SContainmentLink role : childRoles) {
List<SNode> children1 = IterableUtil.asList(node1.getChildren(role));
List<SNode> children2 = IterableUtil.asList(node2.getChildren(role));
if (matchModifier.acceptList(children1, children2)) {
matchModifier.performGroupAction(children1, children2);
continue;
}
Iterator<? extends SNode> childrenIterator = children1.iterator();
for (SNode child2 : children2.toArray(new SNode[children2.size()])) {
SNode child1 = childrenIterator.hasNext() ? childrenIterator.next() : null;
if (matchModifier.accept(child1, child2)) {
matchModifier.performAction(child1, child2);
continue;
}
if (!matchNodes(child1, child2, matchModifier, matchAttributes)) return false;
}
while (childrenIterator.hasNext()) {
SNode child1 = childrenIterator.next();
SNode child2 = null;
if (matchModifier.accept(child1, child2)) {
matchModifier.performAction(child1, child2);
continue;
}
if (!matchNodes(child1, child2, matchModifier, matchAttributes)) return false;
}
}
return true;
}
public static int hash(SNode node) {
int result = node.getConcept().hashCode();
for (SReference reference : node.getReferences()) {
SNode targetNode = jetbrains.mps.util.SNodeOperations.getTargetNodeSilently(reference);
if (targetNode != null) {
result = 31 * result + reference.getLink().hashCode();
result = 31 * result + targetNode.hashCode();
}
}
Map<String, String> properties = jetbrains.mps.util.SNodeOperations.getProperties(node);
for (String propertyName : properties.keySet()) {
result = 31 * result + propertyName.hashCode();
}
for (String propertyValue : properties.values()) {
result = 31 * result + propertyValue.hashCode();
}
for (SNode child : node.getChildren()) {
if (AttributeOperations.isAttribute(child)) continue;
result = 31 * result + child.getContainmentLink().hashCode();
result = 31 * result + hash(child);
}
return result;
}
}