All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.java.se.symbolicvalues.BinaryRelation Maven / Gradle / Ivy

The newest version!
/*
 * SonarQube Java
 * Copyright (C) 2012-2016 SonarSource SA
 * mailto:contact AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.java.se.symbolicvalues;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import org.sonar.java.se.symbolicvalues.RelationalSymbolicValue.Kind;

import javax.annotation.CheckForNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

public abstract class BinaryRelation {


  public static BinaryRelation binaryRelation(Kind kind, SymbolicValue leftOp, SymbolicValue rightOp) {
    BinaryRelation relation;
    switch (kind) {
      case EQUAL:
        relation = new EqualRelation(leftOp, rightOp);
        relation.inverse = new NotEqualRelation(leftOp, rightOp);
        relation.inverse.symmetric = new NotEqualRelation(rightOp, leftOp);
        relation.inverse.inverse = relation;
        relation.symmetric = new EqualRelation(rightOp, leftOp);
        relation.symmetric.symmetric = relation;
        relation.symmetric.inverse = relation.inverse.symmetric;
        break;
      case NOT_EQUAL:
        relation = binaryRelation(Kind.EQUAL, leftOp, rightOp).inverse;
        break;
      case LESS_THAN:
        relation = new LessThanRelation(leftOp, rightOp);
        break;
      case LESS_THAN_OR_EQUAL:
        relation = new LessThanOrEqualRelation(leftOp, rightOp);
        break;
      case GREATER_THAN:
        relation = new GreaterThanRelation(leftOp, rightOp);
        break;
      case GREATER_THAN_OR_EQUAL:
        relation = new GreaterThanOrEqualRelation(leftOp, rightOp);
        break;
      case METHOD_EQUALS:
        relation = new MethodEqualsRelation(leftOp, rightOp);
        break;
      case NOT_METHOD_EQUALS:
        relation = new NotMethodEqualsRelation(leftOp, rightOp);
        break;
      default:
        throw new IllegalStateException("Creation of relation of kind " + kind + " is missing!");
    }
    return relation;


  }


  public static class TransitiveRelationExceededException extends RuntimeException {
    public TransitiveRelationExceededException() {
      super("Number of transitive relations exceeded!");
    }
  }

  protected final Kind kind;
  protected final SymbolicValue leftOp;
  protected final SymbolicValue rightOp;
  protected BinaryRelation symmetric;
  protected BinaryRelation inverse;
  private final int hashcode;

  protected BinaryRelation(Kind kind, SymbolicValue v1, SymbolicValue v2) {
    this.kind = kind;
    leftOp = v1;
    rightOp = v2;
    hashcode = Objects.hash(kind, leftOp, rightOp);
  }

  @Override
  public int hashCode() {
    return hashcode;
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof BinaryRelation) {
      return equalsRelation((BinaryRelation) obj);
    }
    return false;
  }

  private boolean equalsRelation(BinaryRelation rel) {
    if (kind.equals(rel.kind)) {
      return leftOp.id() == rel.leftOp.id() && rightOp.id() == rel.rightOp.id();
    }
    return false;
  }

  @Override
  public String toString() {
    StringBuilder buffer = new StringBuilder();
    buffer.append(leftOp);
    buffer.append(kind.operand);
    buffer.append(rightOp);
    return buffer.toString();
  }

  protected RelationState resolveState(Collection knownRelations) {
    return resolveState(knownRelations, new HashSet());
  }

  @CheckForNull
  protected RelationState resolveState(Collection knownRelations, Set usedRelations) {
    //relation on same operand
    if(leftOp.equals(rightOp)) {
      return relationStateForSameOperand();
    }
    if (knownRelations.isEmpty()) {
      return RelationState.UNDETERMINED;
    }
    if (usedRelations.size() > 200) {
      throw new TransitiveRelationExceededException();
    }
    for (BinaryRelation relation : knownRelations) {
      RelationState result = relation.implies(this);
      if (result.isDetermined()) {
        return result;
      }
      usedRelations.add(relation);
      usedRelations.add(relation.symmetric());
    }
    Collection transitiveReduction = transitiveReduction(knownRelations, usedRelations);
    if (transitiveReduction.isEmpty()) {
      // If no new combination, try with the symmetric (because transitive reduction only checks the operand on the left side.
      transitiveReduction = symmetric().transitiveReduction(knownRelations, usedRelations);
    }
    return resolveState(transitiveReduction, usedRelations);
  }

  private RelationState relationStateForSameOperand() {
    switch (kind) {
      case EQUAL:
      case GREATER_THAN_OR_EQUAL:
      case LESS_THAN_OR_EQUAL:
      case METHOD_EQUALS:
        return RelationState.FULFILLED;
      case NOT_EQUAL:
      case GREATER_THAN:
      case LESS_THAN:
      case NOT_METHOD_EQUALS:
        return RelationState.UNFULFILLED;
      default:
        throw new IllegalStateException("Binary relation kind unsupported" + kind);
    }
  }

  /**
   * Returns a new list of relations, built by combining one of the known relations that can be combined transitively with the receiver
   * 
   * @param knownRelations the list of relations known so far
   * @param usedRelations the list of relation that have been used in past recursive checks
   * @return a list of relations containing at least one new relation; empty if no new combination was found.
   */
  private Collection transitiveReduction(Collection knownRelations, Set usedRelations) {
    boolean changed = false;
    List result = new ArrayList<>();
    for (BinaryRelation relation : knownRelations) {
      boolean used = false;
      if (leftOp.equals(relation.leftOp)) {
        used = result.addAll(relation.combinedRelations(knownRelations, usedRelations));
      } else if (leftOp.equals(relation.rightOp)) {
        used = result.addAll(relation.symmetric().combinedRelations(knownRelations, usedRelations));
      }
      if (used) {
        changed = true;
      } else {
        result.add(relation);
      }
    }
    return changed ? result : Collections.emptyList();
  }

  private Collection combinedRelations(Collection relations, Set usedRelations) {
    List result = new ArrayList<>();
    for (BinaryRelation relation : relations) {
      if (!this.equals(relation)) {
        BinaryRelation combined = combineUnordered(relation);
        if (combined != null && !usedRelations.contains(combined)) {
          result.add(combined);
        }
      }
    }
    return result;
  }

  /**
   * Create a new relation, if any, that is a transitive combination of the receiver with the supplied relation.
   * @param relation another SymbolicValueRelation
   * @return a SymbolicValueRelation or null if the receiver and the supplied relation cannot be combined
   */
  @VisibleForTesting
  @CheckForNull
  BinaryRelation combineUnordered(BinaryRelation relation) {
    BinaryRelation combined = null;
    if (rightOp.equals(relation.leftOp)) {
      combined = relation.combineOrdered(this);
    } else if (rightOp.equals(relation.rightOp)) {
      combined = relation.symmetric().combineOrdered(this);
    }
    return combined;
  }

  /**
   * Create a new relation, if any, that is a transitive combination of the receiver with the supplied relation.
   * Schema aSb & bRc => aTc
   * where aRb is the relation described by the receiver, bSc that of the supplied relation, aTc is the returned relation.
   * 
   * @param relation another SymbolicValueRelation that can be combined transitively with the receiver.
   * @return a SymbolicValueRelation or null if the receiver and the supplied relation cannot be combined
   */
  @CheckForNull
  private BinaryRelation combineOrdered(BinaryRelation relation) {
    Preconditions.checkArgument(leftOp.equals(relation.rightOp), "Transitive condition not matched!");
    if (rightOp.equals(relation.leftOp)) {
      return conjunction(relation.symmetric());
    }
    return combinedAfter(relation);
  }

  /**
   * Returns a new relation resulting of the conjunction of the receiver with the supplied relation.
   * Schema aRb & aSb => aTb
   * where aRb is the relation described by the receiver, bSc that of the supplied relation, aTc is the returned relation.
   * 
   * @param relation another relation bearing on the same operands as those of the receiver
   * @return the resulting relation or null if the conjunction of both relations does not bring any new information
   */

  @CheckForNull
  protected BinaryRelation conjunction(BinaryRelation relation) {
    Preconditions.checkArgument(leftOp.equals(relation.leftOp) && rightOp.equals(relation.rightOp), "Conjunction condition not matched!");
    return null;
  }

  /**
   * @return a new relation, the negation of the receiver
   */
  public BinaryRelation inverse() {
    if (inverse == null) {
      inverse = binaryRelation(kind.inverse(), leftOp, rightOp);
    }
    return inverse;
  }

  /**
   * @return a new relation, equivalent to the receiver with inverted parameters
   */
  protected BinaryRelation symmetric() {
    if (symmetric == null) {
      symmetric = binaryRelation(kind.symmetric(), rightOp, leftOp);
    }
    return symmetric;
  }

  /**
   * @param relation a relation between symbolic values
   * @return a RelationState
    *
  • FULFILLED if the receiver implies that the supplied relation is true
  • *
  • UNFULFILLED if the receiver implies that the supplied relation is false
  • *
  • UNDETERMINED otherwise
  • *
* @see RelationState */ protected RelationState implies(BinaryRelation relation) { if (leftOp.equals(relation.leftOp) && rightOp.equals(relation.rightOp)) { return relation.isImpliedBy(this); } else if (leftOp.equals(relation.rightOp) && rightOp.equals(relation.leftOp)) { return relation.symmetric().isImpliedBy(this); } return RelationState.UNDETERMINED; } protected abstract RelationState isImpliedBy(BinaryRelation relation); protected abstract RelationState impliesEqual(); protected abstract RelationState impliesNotEqual(); protected abstract RelationState impliesMethodEquals(); protected abstract RelationState impliesNotMethodEquals(); protected abstract RelationState impliesGreaterThan(); protected abstract RelationState impliesGreaterThanOrEqual(); protected abstract RelationState impliesLessThan(); protected abstract RelationState impliesLessThanOrEqual(); /** * Returns a new relation resulting of the transitive combination of the receiver with the supplied relation. * Schema aRb & bSc => aTc * where aRb is the relation described by the receiver, bSc that of the supplied relation, aTc is the returned relation. * * @param relation another relation whose left operand is the same as the receiver's right operand * @return the resulting relation or null if the conjunction of both relations does not bring any new information */ @CheckForNull protected abstract BinaryRelation combinedAfter(BinaryRelation relation); @CheckForNull protected abstract BinaryRelation combinedWithEqual(EqualRelation relation); @CheckForNull protected abstract BinaryRelation combinedWithNotEqual(NotEqualRelation relation); @CheckForNull protected abstract BinaryRelation combinedWithMethodEquals(MethodEqualsRelation relation); @CheckForNull protected abstract BinaryRelation combinedWithNotMethodEquals(NotMethodEqualsRelation relation); @CheckForNull protected abstract BinaryRelation combinedWithGreaterThan(GreaterThanRelation relation); @CheckForNull protected abstract BinaryRelation combinedWithGreaterThanOrEqual(GreaterThanOrEqualRelation relation); @CheckForNull protected abstract BinaryRelation combinedWithLessThan(LessThanRelation relation); @CheckForNull protected abstract BinaryRelation combinedWithLessThanOrEqual(LessThanOrEqualRelation relation); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy