/*
* Copyright (C) 2014 University of Freiburg
*
* This file is part of SMTInterpol.
*
* SMTInterpol 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.
*
* SMTInterpol 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 SMTInterpol. If not, see <http://www.gnu.org/licenses/>.
*/
package de.uni_freiburg.informatik.ultimate.smtinterpol.option;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
import de.uni_freiburg.informatik.ultimate.logic.QuotedObject;
import de.uni_freiburg.informatik.ultimate.logic.SMTLIBException;
import de.uni_freiburg.informatik.ultimate.smtinterpol.DefaultLogger;
import de.uni_freiburg.informatik.ultimate.smtinterpol.LogProxy;
import de.uni_freiburg.informatik.ultimate.smtinterpol.smtlib2.ParseEnvironment;
/**
* A map to handle all options supported by SMTInterpol. The map provides
* general methods to set and get values for options based on the options name.
* If the option is unknown, the methods will throw an
* UnsupportedOperationException.
*
* The options are group into front end options and solver options. The front
* end options contain all options only used by the {@link ParseEnvironment}.
* The solver options contain all options used by the solver whether created to
* be used from command line or through its API.
*
* The diagnostic output channel option has a special role. Since we are
* working with {@link LogProxy}s, we might not be able to change the logging
* destination. Thus, only if the logger to be used is a {@link DefaultLogger},
* we set up this option to change the log destination. If a different logger
* is used, we simply set up an option to print a warning if this option is
* changed.
*
* The front end options are not activated by default. All options are
* available, but the option <pre>:regular-output-channel</pre> will print a
* warning if its value is changed. Only in the activated state, this option
* actually changes the destination of the output. To activate the front end
* (if you actually use a {@link ParseEnvironment}), use the constructor
* {@link #OptionMap(LogProxy, boolean)} with the second parameter set to
* <code>true</code>.
*
* The map maintains a flag representing the current state of the solver. If
* this flag is turned on, all options that are not configured to be online
* modifiable cannot be modified anymore. Attempting to do so will throw a
* {@link SMTLIBException}.
* @author Juergen Christ
*/
public class OptionMap {
private final static String DIAG_OUTPUT_CHANNEL_NAME =
":diagnostic-output-channel";
private final static String DIAG_OUTPUT_CHANNEL_DESC =
"Where to print diagnostic output to. Use \"stdout\" for standard "
+ "output and \"stderr\" for standard error.";
/**
* When copying this map, the options stored in this map can be either stay
* unchanged or be reset to their default value. This is controlled by this
* enum. The names are pretty self-expanatory.
* @author Juergen Christ
*/
public enum CopyMode {
CURRENT_VALUE,
RESET_TO_DEFAULT,
/**
* Reset all options except for :regular-output-channel,
* :diagnostic-output-channel, and :verbosity.
*/
RESET_EXCEPT_CHANNELS
}
private final LinkedHashMap<String, Option> mOptions;
private final LinkedHashMap<String, String> mAliases;
private final SolverOptions mSolverOptions;
private final FrontEndOptions mFrontEndOptions;
private final LogProxy mLogger;
private boolean mOnline;
/**
* Convenience constructor for {@link #OptionMap(LogProxy, boolean)} where
* the front end is not activated by default. This constructor should be
* used when the Java API of SMTInterpol is used.
* @param logger The logger to be used by SMTInterpol.
*/
public OptionMap(LogProxy logger) {
this(logger, false);
}
/**
* Create a new option map and set up the solver options. If the logger
* given is a {@link DefaultLogger}, we also set up the option
* <code>:diagnostic-output-channel</code> to configure this logger.
* @param logger The logger to be used by SMTInterpol.
* @param activeFrontEnd Activate the front end options?
*/
public OptionMap(LogProxy logger, boolean activeFrontEnd) {
mOptions = new LinkedHashMap<String, Option>();
mAliases = new LinkedHashMap<String, String>();
mSolverOptions = new SolverOptions(this, logger);
mLogger = logger;
addOption(DIAG_OUTPUT_CHANNEL_NAME, new LoggerOption(
DIAG_OUTPUT_CHANNEL_DESC, logger));
mOnline = false;
mFrontEndOptions = new FrontEndOptions(this, activeFrontEnd);
}
private OptionMap(LogProxy logger, LinkedHashMap<String, Option> options,
LinkedHashMap<String, String> aliases) {
mOptions = options;
mAliases = aliases;
mSolverOptions = new SolverOptions(this);
mFrontEndOptions = new FrontEndOptions(this);
mLogger = logger;
mOnline = false;
}
public void started() {
for (final Option o : mOptions.values()) {
o.started();
}
}
/**
* Set the option map into online mode. From now on, all options that are
* not online modifiable cannot be modified anymore.
*/
public void setOnline() {
mOnline = true;
}
/**
* Get the logger used to construct this option map.
* @return The logger used to construct this option map.
*/
public final LogProxy getLogProxy() {
return mLogger;
}
public final SolverOptions getSolverOptions() {
return mSolverOptions;
}
public final FrontEndOptions getFrontEndOptions() {
return mFrontEndOptions;
}
public void addOption(String name, Option option) {
mOptions.put(name, option);
}
public void addAlias(String name, String alias) {
mAliases.put(name, alias);
}
/**
* Get the current value of an option. If the option is unknown to this
* option map, a <code>UnsupportedOperationException</code> will be thrown.
* @param option To option whose value should be retrieved.
* @return The current value of this option.
*/
public Object get(String option) {
if (mAliases.containsKey(option)) {
option = mAliases.get(option);
}
final Option o = mOptions.get(option);
if (o == null) {
throw new UnsupportedOperationException();
}
return o.get();
}
/**
* Set the value of an option. The option map relies on the caller of this
* function to correctly
* @param option
* @param value
*/
public void set(String option, Object value) {
if (mAliases.containsKey(option)) {
option = mAliases.get(option);
}
final Option o = mOptions.get(option);
if (o == null) {
throw new UnsupportedOperationException();
}
if (mOnline && !o.isOnlineModifiable()) {
throw new SMTLIBException("Option " + option
+ " can only be changed before setting the logic");
}
o.set(value);
}
/**
* Get all known option names.
* @return All known option names.
*/
public String[] getInfo() {
final String[] res = new String[mOptions.size() + mAliases.size()];
int pos = 0;
for (final String opt : mOptions.keySet()) {
res[pos++] = opt;
}
for (final String opt : mAliases.keySet()) {
res[pos++] = opt;
}
return res;
}
/**
* Get information about a specific option. The information contains the
* description, the default value, and the online modifiable state of this
* option. If the option is unknown, an
* <code>UnsupportedOperationException</code> will be thrown.
* @param option The option to get information for.
* @return Information for this option.
*/
public Object[] getInfo(String option) {
if (mAliases.containsKey(option)) {
option = mAliases.get(option);
}
final Option o = mOptions.get(option);
if (o == null) {
throw new UnsupportedOperationException();
}
final ArrayList<Object> result = new ArrayList<Object>();
result.add(":description");
result.add(new QuotedObject(o.getDescription()));
result.add(":default");
result.add(o.defaultValue());
if (o.isOnlineModifiable()) {
result.add(":online-modifiable");
}
return result.toArray();
}
/**
* Reset every option to its default value and set the map back to offline
* state.
*/
public void reset() {
mOnline = false;
for (final Option o : mOptions.values()) {
o.reset();
}
}
public OptionMap copy(CopyMode mode) {
final LinkedHashMap<String, Option> options = new LinkedHashMap<String, Option>();
for (final Map.Entry<String, Option> me : mOptions.entrySet()) {
final Option cpy = me.getValue().copy();
switch(mode) {
case CURRENT_VALUE:
break;
case RESET_EXCEPT_CHANNELS:
if (cpy instanceof VerbosityOption || cpy instanceof ChannelOption) {
break;
}
// FALLTHROUGH
default:
cpy.reset();
}
options.put(me.getKey(), cpy);
}
return new OptionMap(mLogger, options, new LinkedHashMap<String, String>(mAliases));
}
Option getOption(String key) {
return mOptions.get(key);
}
}