/*
* Copyright (C) 2016 Daniel Dietsch (dietsch@informatik.uni-freiburg.de)
* Copyright (C) 2016 University of Freiburg
*
* This file is part of the ULTIMATE ModelCheckerUtils Library.
*
* The ULTIMATE ModelCheckerUtils Library 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.
*
* The ULTIMATE ModelCheckerUtils Library 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 the ULTIMATE ModelCheckerUtils Library. If not, see .
*
* Additional permission under GNU GPL version 3 section 7:
* If you modify the ULTIMATE ModelCheckerUtils Library, or any covered work, by linking
* or combining it with Eclipse RCP (or a modified version of Eclipse RCP),
* containing parts covered by the terms of the Eclipse Public License, the
* licensors of the ULTIMATE ModelCheckerUtils Library grant you additional permission
* to convey the resulting work.
*/
package de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import de.uni_freiburg.informatik.ultimate.core.lib.models.BasePayloadContainer;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IIcfg;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IcfgEdge;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IcfgLocation;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.debugidentifiers.DebugIdentifier;
/**
*
* Basic implementation of {@link IIcfg}.
*
* @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de)
*
* @param
* The type of location.
*/
public class BasicIcfg extends BasePayloadContainer implements IIcfg {
private static final long serialVersionUID = 1L;
private final Map> mProgramPoints;
private final Map mEntryNodes;
private final Map mExitNodes;
private final Map> mErrorNodes;
private final Set mLoopLocations;
private CfgSmtToolkit mCfgSmtToolkit;
private final Set mInitialNodes;
private final String mIdentifier;
private final Class mLocationClass;
/**
* Create an empty {@link IIcfg}.
*
* @param identifier
* A human-readable debug identifier.
* @param cfgSmtToolkit
* A {@link CfgSmtToolkit} instance that has to contain all procedures and variables that may occur in
* this {@link IIcfg}.
*/
public BasicIcfg(final String identifier, final CfgSmtToolkit cfgSmtToolkit, final Class locClazz) {
mLocationClass = Objects.requireNonNull(locClazz);
mIdentifier = Objects.requireNonNull(identifier);
mCfgSmtToolkit = Objects.requireNonNull(cfgSmtToolkit);
mInitialNodes = new LinkedHashSet<>();
mLoopLocations = new LinkedHashSet<>();
mProgramPoints = new LinkedHashMap<>();
mEntryNodes = new LinkedHashMap<>();
mExitNodes = new LinkedHashMap<>();
mErrorNodes = new LinkedHashMap<>();
// initialize all maps with the known procedures
for (final String proc : mCfgSmtToolkit.getProcedures()) {
mProgramPoints.put(proc, new LinkedHashMap<>());
mErrorNodes.put(proc, new LinkedHashSet<>());
}
}
/**
* TODO: Documentation
*/
public void addProcedure(final String proc) {
mProgramPoints.put(proc, new LinkedHashMap<>());
mErrorNodes.put(proc, new LinkedHashSet<>());
}
/**
* Add a new location to this {@link IIcfg}. The location has to have a procedure that is already known by the
* {@link IIcfg}. Known procedures can be obtained from {@link CfgSmtToolkit#getProcedures()} via
* {@link #getCfgSmtToolkit()}.
*
* @param loc
* The location that should be added.
* @param isInitial
* true if it is an initial location.
* @param isError
* true if it is an error location for its procedure.
* @param isProcEntry
* true if it is the entry location for its procedure (it cannot overwrite existing procedure entry
* locations).
* @param isProcExit
* true if it is the exit location for its procedure (it cannot overwrite existing procedure exit
* locations).
* @param isLoopLocation
* true if it is a loop head.
*/
public void addLocation(final LOC loc, final boolean isInitial, final boolean isError, final boolean isProcEntry,
final boolean isProcExit, final boolean isLoopLocation) {
if (loc == null) {
throw new IllegalArgumentException("Cannot add null location");
}
assert getLocationClass()
.isAssignableFrom(loc.getClass()) : "Incompatible location types. Should be subclass of "
+ getLocationClass() + " but is " + loc.getClass();
final String proc = getProcedure(loc);
final Map name2Loc = mProgramPoints.get(proc);
assert name2Loc != null : "Unknown procedure";
final LOC old = name2Loc.put(loc.getDebugIdentifier(), loc);
if (loc.equals(old)) {
// is already added
return;
}
assert old == null : "Duplicate debug identifier for loc " + loc;
if (isInitial) {
mInitialNodes.add(loc);
}
if (isError) {
final Set procErrors = mErrorNodes.get(proc);
assert procErrors != null : "Unknown procedure";
procErrors.add(loc);
}
if (isProcEntry) {
final LOC oldEntry = mEntryNodes.put(proc, loc);
assert oldEntry == null || loc.equals(
oldEntry) : "Do not overwrite the procedure entry node by mistake! Remove the old one first";
}
if (isProcExit) {
final LOC oldExit = mExitNodes.put(proc, loc);
assert oldExit == null || loc
.equals(oldExit) : "Do not overwrite the procedure exit node by mistake! Remove the old one first";
}
if (isLoopLocation) {
mLoopLocations.add(loc);
}
}
/**
* Convenience method for {@link #addLocation(IcfgLocation, boolean, boolean, boolean, boolean, boolean)} without
* any true.
*
* TODO 2018-09-23 Matthias: I think methods like these are bad practice because the increase the overall complexity
* of the code
* more methods == higher complexity of this class
* method only used once
* method introduces new terminology (what is an "ordinary" location)
* if you use this method you have to read and understand the addLocation method anyway
*
* @param loc
* The location to add.
*/
public void addOrdinaryLocation(final LOC loc) {
addLocation(loc, false, false, false, false, false);
}
/**
* Remove the location loc
from this {@link IIcfg} by removing it from all the maps. Note that this
* does not disconnect the location (i.e., loc itself remains unchanged)
*
* @param loc
* The location to remove.
* @return True if the location was previously part of the IICfg, false otherwise.
*/
public boolean removeLocation(final IcfgLocation loc) {
if (loc == null) {
return false;
}
final String proc = getProcedure(loc);
final Map name2loc = mProgramPoints.get(proc);
if (name2loc == null) {
return false;
}
final LOC removedLoc = name2loc.remove(loc.getDebugIdentifier());
if (!loc.equals(removedLoc)) {
assert removedLoc == null : "Multiple nodes with identical debug identifier!";
return false;
}
final LOC entryLoc = mEntryNodes.get(proc);
if (loc.equals(entryLoc)) {
mEntryNodes.remove(proc);
}
final LOC exitLoc = mExitNodes.get(proc);
if (loc.equals(exitLoc)) {
mExitNodes.remove(proc);
}
final Set errorLocs = mErrorNodes.get(proc);
errorLocs.remove(loc);
mLoopLocations.remove(loc);
mInitialNodes.remove(loc);
return true;
}
private static String getProcedure(final IcfgLocation loc) {
final String proc = loc.getProcedure();
assert proc != null : "Location " + loc + " does not have a procedure";
return proc;
}
@Override
public Map> getProgramPoints() {
return Collections.unmodifiableMap(mProgramPoints);
}
@Override
public Map getProcedureEntryNodes() {
return Collections.unmodifiableMap(mEntryNodes);
}
@Override
public Map getProcedureExitNodes() {
return Collections.unmodifiableMap(mExitNodes);
}
@Override
public Map> getProcedureErrorNodes() {
return Collections.unmodifiableMap(mErrorNodes);
}
@Override
public Set getLoopLocations() {
return Collections.unmodifiableSet(mLoopLocations);
}
@Override
public CfgSmtToolkit getCfgSmtToolkit() {
return mCfgSmtToolkit;
}
public void setCfgSmtToolkit(final CfgSmtToolkit cfgSmtToolkit) {
mCfgSmtToolkit = cfgSmtToolkit;
}
@Override
public Set getInitialNodes() {
return Collections.unmodifiableSet(mInitialNodes);
}
public Set getInitialOutgoingEdges() {
return getInitialNodes().stream().flatMap(a -> a.getOutgoingEdges().stream()).collect(Collectors.toSet());
}
@Override
public String getIdentifier() {
return mIdentifier;
}
@Override
public Class getLocationClass() {
return mLocationClass;
}
@Override
public String toString() {
return graphStructureToString();
}
}