/* * 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; } }