public class DisabledParallel extends AbstractParallel {
	
	/**
	 * Constructs a new instance of an implementation of {@link Parallel} that only generates serial commands.
	 * 
	 * @param parameters
	 *            the parameters that defined the instance as a {@link ScopedMap}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public DisabledParallel(ScopedMap parameters, Scope parent) {
		super(parameters, parent);
	}
	
	@Override
	public Boolean isParallel() {
		return false;
	}
	
	@Override
	public Integer getNumProcessors() {
		return 1;
	}
	
	@Override
	public String[] makeCommand(String executablePath, String logPath) {
		return Parallel.makeSerialCommand(executablePath, logPath);
	}
} public interface Parallel extends Scope {
	/**
	 * The name that {@link Logger Loggers} in instances of {@link Clear} should be created from.
	 */
	public static final String LOGGER_NAME = SIMULATION_LOGGER_ROOT + ".Parallel";
	
	/**
	 * @return {@code true} iff the command should be run in parallel
	 */
	@NamedScopeValue("is-parallel")
	public Boolean isParallel();
	
	/**
	 * @return the number of processors to use
	 */
	@NamedScopeValue("num-processors")
	public Integer getNumProcessors();
	
	/**
	 * Generates a shell command based on the information in the {@link Parallel} instance that can be passed to a {@link ProcessBuilder}.
	 * 
	 * @param executablePath
	 *            the path to the executable file (can be relative)
	 * @param logPath
	 *            the path to the log file (can be relative, doesn't need to exist)
	 * @return a shell command based on the information in the {@link Parallel} instance that can be passed to a {@link ProcessBuilder}
	 */
	public String[] makeCommand(String executablePath, String logPath);
	
	/**
	 * Generates a shell command for serial processes that can be passed to a {@link ProcessBuilder}.
	 * 
	 * @param executablePath
	 *            the path to the executable file (can be relative)
	 * @param logPath
	 *            the path to the log file (can be relative, doesn't need to exist)
	 * @return a shell command for serial processes that can be passed to a {@link ProcessBuilder}
	 */
	public static String[] makeSerialCommand(String executablePath, String logPath) {
		return new String[]{executablePath, "2>&1", "|", "tee", logPath};
	}
} public class StandardParallel extends AbstractParallel {
	private Boolean dmpar, bootLAM;
	private Integer numProcessors;
	
	/**
	 * Initializes a new instance of {@link StandardParallel} with a {@link Logger} derived from {@link Parallel#LOGGER_NAME}.
	 * 
	 * @param parameters
	 *            the parameters that define the instance as a {@link ScopedMap}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public StandardParallel(ScopedMap parameters, Scope parent) {
		super(parameters, parent);
		dmpar = null;
		bootLAM = null;
		numProcessors = null;
	}
	
	@Override
	@NamedScopeValue({"is-dmpar", "is-parallel"})
	public Boolean isParallel() {
		if (dmpar != null)
			return dmpar;
		synchronized (this) {
			if (dmpar == null) {
				dmpar = getParameters().containsKey("is-dmpar") ? evaluateToType(getParameters().get("is-dmpar"), "is-dmpar", Boolean.class)
						: (getParent() instanceof Parallel ? ((Parallel) getParent()).isParallel() : false);
			}
		}
		return dmpar;
	}
	
	/**
	 * @return {@code true} iff the "boot-lam" field was set to {@code true}
	 */
	@NamedScopeValue("boot-lam")
	public Boolean isBootLAM() {
		if (bootLAM != null)
			return bootLAM;
		synchronized (this) {
			if (bootLAM == null) {
				bootLAM = getParameters().containsKey("boot-lam") ? evaluateToType(getParameters().get("boot-lam"), "boot-lam", Boolean.class)
						: (getParent() instanceof StandardParallel ? ((StandardParallel) getParent()).isBootLAM() : false);
			}
		}
		return bootLAM;
	}
	
	@Override
	@NamedScopeValue({"processors", "num-processors"})
	public Integer getNumProcessors() {
		if (numProcessors != null)
			return numProcessors;
		synchronized (this) {
			if (numProcessors == null) {
				numProcessors = getParameters().containsKey("processors") ? evaluateToNumber(getParameters().get("processors"), "processors").intValue()
						: (getParent() instanceof Parallel ? ((Parallel) getParent()).getNumProcessors() : 2);
			}
		}
		return numProcessors;
	}
	
	@Override
	public String[] makeCommand(String executablePath, String logPath) {
		if (isParallel()) {
			if (isBootLAM())
				return new String[]{"mpiexec", "-boot", "-np", getNumProcessors().toString(), executablePath, "2>&1", "|", "tee", logPath};
			else
				return new String[]{"mpiexec", "-np", getNumProcessors().toString(), executablePath, "2>&1", "|", "tee", logPath};
		}
		return new String[]{executablePath, "2>&1", "|", "tee", logPath};
	}
	
} public class ComputedTiming extends ScopedComponent<Scope> implements Timing {
	private volatile Calendar base, start, end;
	private volatile Offset offset;
	private volatile Round round;
	private volatile Duration duration;
	private volatile Clear clear;
	private boolean appliedClear;
	
	/**
	 * Constructs a {@link ComputedTiming} instance without an explicitly defined base {@link Calendar}. This is the general use constructor.
	 * 
	 * @param parameters
	 *            the parameters that define the {@link ComputedTiming} instance as a {@link ScopedMap}
	 * @param parent
	 *            the {@link ComputedTiming} instance's parent {@link Scope}
	 */
	public ComputedTiming(ScopedMap parameters, Scope parent) {
		this(parameters, null, parent);
	}
	
	/**
	 * Constructs a {@link ComputedTiming} instance with an (optionally) explicitly defined base {@link Calendar}. This is generally only used by
	 * {@link Simulation} during the initialization process.
	 * 
	 * @param parameters
	 *            the parameters that define the {@link ComputedTiming} instance as a {@link ScopedMap}
	 * @param base
	 *            the base {@link Calendar} for the {@link ComputedTiming} instance
	 * @param parent
	 *            the {@link ComputedTiming} instance's parent {@link Scope}
	 */
	public ComputedTiming(ScopedMap parameters, Calendar base, Scope parent) {
		super(parameters, parent);
		this.base = parent instanceof Timing ? base : Objects.requireNonNull(base, "The base Calendar cannot be null if the parent is not an instance of Timing");
		start = end = null;
		offset = null;
		round = null;
		duration = null;
		clear = null;
		appliedClear = false;
	}
	
	@Override
	public Calendar getBase() {
		if (base == null) {
			synchronized (this) {
				if (base == null) { //Have to re-check for synchronization
					base = getClear().apply((getParent() instanceof Timing) ? ((Timing) getParent()).getBase() : null);
					appliedClear = true;
				}
			}
		}
		if (!appliedClear) {
			synchronized (this) {
				if (!appliedClear) { //Have to re-check for synchronization
					base = getClear().apply(base);
					appliedClear = true;
				}
			}
		}
		return base;
	}
	
	@Override
	public Calendar getStart() {
		if (start != null)
			return start;
		synchronized (this) {
			if (start == null)
				start = getOffset().apply(getRound().apply(getBase()));
		}
		return start;
	}
	
	@Override
	public Calendar getEnd() {
		if (end != null)
			return end;
		synchronized (this) {
			if (end == null)
				end = getDuration().apply(getStart());
		}
		return end;
	}
	
	@Override
	public Offset getOffset() {
		if (offset != null)
			return offset;
		synchronized (this) {
			if (offset == null) {
				if (getParameters().get("offset") instanceof ScopedList)
					offset = new ListOffset((ScopedList) getParameters().get("offset"), (getParent() instanceof Timing) ? ((Timing) getParent()).getOffset() : this);
				else
					offset = WRFRunnerComponentFactory.generateComponent(Offset.class, (ScopedMap) getParameters().get("offset"),
							(getParent() instanceof Timing) ? ((Timing) getParent()).getOffset() : this);
			}
		}
		return offset;
	}
	
	@Override
	public Round getRound() {
		if (round != null)
			return round;
		synchronized (this) {
			if (round == null) {
				if (getParameters().get("round") instanceof ScopedList)
					round = new ListRound((ScopedList) getParameters().get("round"), (getParent() instanceof Timing) ? ((Timing) getParent()).getRound() : this);
				else
					round = WRFRunnerComponentFactory.generateComponent(Round.class, (ScopedMap) getParameters().get("round"),
							(getParent() instanceof Timing) ? ((Timing) getParent()).getRound() : this);
			}
		}
		return round;
	}
	
	@Override
	public Duration getDuration() {
		if (duration != null)
			return duration;
		synchronized (this) {
			if (duration == null) {
				if (getParameters().get("duration") instanceof ScopedList)
					duration = new ListDuration((ScopedList) getParameters().get("duration"), (getParent() instanceof Timing) ? ((Timing) getParent()).getDuration() : this);
				else
					duration = WRFRunnerComponentFactory.generateComponent(Duration.class, (ScopedMap) getParameters().get("duration"),
							(getParent() instanceof Timing) ? ((Timing) getParent()).getDuration() : this);
			}
		}
		return duration;
	}
	
	@Override
	public Clear getClear() {
		if (clear != null)
			return clear;
		synchronized (this) {
			if (clear == null) {
				if (getParameters().get("clear") instanceof ScopedList)
					clear = new ListClear((ScopedList) getParameters().get("clear"), (getParent() instanceof Timing) ? ((Timing) getParent()).getClear() : this);
				else
					clear = WRFRunnerComponentFactory.generateComponent(Clear.class, (ScopedMap) getParameters().get("clear"),
							(getParent() instanceof Timing) ? ((Timing) getParent()).getClear() : this);
			}
		}
		return clear;
	}
} public class DisabledTiming extends AbstractScope<Scope> implements Timing {
	
	/**
	 * This constructor should <i>not</i> be called - it will just throw an {@link UnsupportedOperationException}
	 * 
	 * @param parameters
	 *            ignored - here for compatibility only
	 * @param parent
	 *            ignored - here for compatibility only
	 * @throws UnsupportedOperationException
	 *             always - if this constructor has been called, something went wrong.
	 */
	public DisabledTiming(ScopedMap parameters, Scope parent) throws UnsupportedOperationException {
		super(parent);
		throw new UnsupportedOperationException("Cannot have a DisabledTiming Object");
	}
	
	@Override
	public Calendar getBase() {
		return null;
	}
	
	@Override
	public Calendar getStart() {
		return null;
	}
	
	@Override
	public Calendar getEnd() {
		return null;
	}
	
	@Override
	public Offset getOffset() {
		return null;
	}
	
	@Override
	public Round getRound() {
		return null;
	}
	
	@Override
	public Duration getDuration() {
		return null;
	}
	
	@Override
	public Clear getClear() {
		return null;
	}
} public interface Timing extends Scope {
	
	/**
	 * @return the {@link Calendar} from which {@link #getStart() start} and {@link #getEnd() end} are derived
	 */
	public Calendar getBase();
	
	/**
	 * @return a {@link ScopedMap} generated from the {@link Calendar} returned by {@link #getBase()} via
	 *         {@link ScopeUtils#makeScopeFromCalendar(Calendar, Scope)}
	 */
	@NamedScopeValue("base")
	public default ScopedMap getScopedBase() {
		return ScopeUtils.makeScopeFromCalendar(getBase(), this);
	}
	
	/**
	 * @return the {@link Calendar} denoting the time that the {@link Simulation} will start
	 */
	public Calendar getStart();
	
	/**
	 * @return a {@link ScopedMap} generated from the {@link Calendar} returned by {@link #getStart()} via
	 *         {@link ScopeUtils#makeScopeFromCalendar(Calendar, Scope)}
	 */
	@NamedScopeValue("start")
	public default ScopedMap getScopedStart() {
		return ScopeUtils.makeScopeFromCalendar(getStart(), this);
	}
	
	/**
	 * @return the {@link Calendar} denoting the time that the {@link Simulation} will end
	 */
	public Calendar getEnd();
	
	/**
	 * @return a {@link ScopedMap} generated from the {@link Calendar} returned by {@link #getEnd()} via
	 *         {@link ScopeUtils#makeScopeFromCalendar(Calendar, Scope)}
	 */
	@NamedScopeValue("end")
	public default ScopedMap getScopedEnd() {
		return ScopeUtils.makeScopeFromCalendar(getEnd(), this);
	}
	
	/**
	 * @return the {@link Offset} added to the rounded {@link #getBase() base} to derive {@link #getStart() start}
	 */
	@NamedScopeValue("offset")
	public Offset getOffset();
	
	/**
	 * @return the {@link Round} applied to {@link #getBase() base} to derive {@link #getStart() start}
	 */
	@NamedScopeValue("round")
	public Round getRound();
	
	/**
	 * @return the {@link Duration} of the {@link Simulation}
	 */
	@NamedScopeValue("duration")
	public Duration getDuration();
	
	/**
	 * @return the {@link Clear} of the {@link Simulation}
	 */
	@NamedScopeValue("clear")
	public Clear getClear();
} public interface Clear extends Scope, TimingComponent {
	/**
	 * The name that {@link Logger Loggers} in instances of {@link Clear} should be created from.
	 */
	public static final String LOGGER_NAME = SIMULATION_LOGGER_ROOT + ".Clear";
} public class DisabledClear extends AbstractClear {
	
	/**
	 * Constructs a new instance of an implementation of {@link Clear} that does not perform any actions.
	 * 
	 * @param parameters
	 *            the parameters that defined the instance as a {@link ScopedMap}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public DisabledClear(ScopedMap parameters, Scope parent) {
		super(parameters, parent);
	}
	
	@Override
	protected Calendar doApply(Calendar base) {
		return base;
	}
	
	@Override
	protected void compute() {/* Nothing to do here */}
} public class ListClear extends ScopedTimingComponentList<Clear> implements Clear {
	
	/**
	 * Initializes a new {@link ListClear} wrapper around an existing {@link List} of {@link Clear} instances.
	 * 
	 * @param backing
	 *            the {@link List} of {@link Clear} instances
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public ListClear(List<Clear> backing, Scope parent) {
		super(backing, parent);
	}
	
	/**
	 * Initializes a new {@link ListClear} wrapper around a {@link List} of {@link Clear} instances derived from the "items" field of the provided
	 * {@link ScopedMap}. This is for compatibility with {@link WRFRunnerComponentFactory}.
	 * 
	 * @param parameters
	 *            a {@link ScopedMap} with "items" mapping to a {@link ScopedList}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public ListClear(ScopedMap parameters, Scope parent) {
		this((ScopedList) parameters.get("items"), parent);
	}
	
	/**
	 * Initializes a new {@link ListClear} wrapper around a {@link List} of {@link Clear} instances derived from the elements of the given
	 * {@link ScopedList}.
	 * 
	 * @param items
	 *            a {@link ScopedList} exclusively containing {@link ScopedMap ScopedMaps} that define {@link Clear} instances
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public ListClear(ScopedList items, Scope parent) {
		this(new ArrayList<>(), parent);
		for (Object o : items)
			add(WRFRunnerComponentFactory.generateComponent(Clear.class, (ScopedMap) o, WRFRunnerComponentFactory.willInherit((ScopedMap) o) ? parent : this));
	}
} public class StandardClear extends AbstractClear {
	private final int[] values;
	private int keep;
	
	/**
	 * Initializes a new instance of {@link StandardClear} with a {@link Logger} derived from {@link Clear#LOGGER_NAME}.
	 * 
	 * @param parameters
	 *            the parameters that define the implementation as a {@link ScopedMap}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public StandardClear(ScopedMap parameters, Scope parent) {
		super(parameters, parent);
		values = new int[TIMING_FIELD_NAMES.size()];
		Arrays.fill(values, -1);
	}
	
	@Override
	protected Calendar doApply(Calendar base) {
		for (int i = 0; i < keep; i++)
			base.set(TIMING_FIELD_IDS.get(i), values[i] == -1 ? base.getActualMinimum(TIMING_FIELD_IDS.get(i)) : values[i]);
		return base;
	}
	
	@Override
	protected void compute() {
		for (int i = 0; i < TIMING_FIELD_NAMES.size(); i++)
			if (getParameters().containsKey(TIMING_FIELD_NAMES.get(i)))
				values[i] = ((Number) getParameters().get(TIMING_FIELD_NAMES.get(i))).intValue();
		String name = (String) getParameters().get("keep");
		keep = TIMING_FIELD_NAMES.indexOf(name);
		if (keep < 0)
			throw new IllegalArgumentException(name + " is not a valid timing field name");
	}
} public class DisabledDuration extends AbstractDuration {
	
	/**
	 * Constructs a new instance of an implementation of {@link Duration} that does not perform any actions.
	 * 
	 * @param parameters
	 *            the parameters that defined the instance as a {@link ScopedMap}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public DisabledDuration(ScopedMap parameters, Scope parent) {
		super(parameters, parent);
	}

	@Override
	protected Calendar doApply(Calendar base) {
		return base;
	}

	@Override
	protected void compute() {/* Nothing to do here */}
} public interface Duration extends Scope, TimingComponent {
	/**
	 * The name that {@link Logger Loggers} in instances of {@link Duration} should be created from.
	 */
	public static final String LOGGER_NAME = SIMULATION_LOGGER_ROOT + ".Duration";
} public class ListDuration extends ScopedTimingComponentList<Duration> implements Duration {
	
	/**
	 * Initializes a new {@link ListDuration} wrapper around an existing {@link List} of {@link Duration} instances.
	 * 
	 * @param backing
	 *            the {@link List} of {@link Duration} instances
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public ListDuration(List<Duration> backing, Scope parent) {
		super(backing, parent);
	}
	
	/**
	 * Initializes a new {@link ListDuration} wrapper around a {@link List} of {@link Duration} instances derived from the "items" field of the provided
	 * {@link ScopedMap}. This is for compatibility with {@link WRFRunnerComponentFactory}.
	 * 
	 * @param parameters
	 *            a {@link ScopedMap} with "items" mapping to a {@link ScopedList}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public ListDuration(ScopedMap parameters, Scope parent) {
		this((ScopedList) parameters.get("items"), parent);
	}
	
	/**
	 * Initializes a new {@link ListDuration} wrapper around a {@link List} of {@link Duration} instances derived from the elements of the given
	 * {@link ScopedList}.
	 * 
	 * @param items
	 *            a {@link ScopedList} exclusively containing {@link ScopedMap ScopedMaps} that define {@link Duration} instances
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public ListDuration(ScopedList items, Scope parent) {
		this(new ArrayList<>(), parent);
		for (Object o : items)
			add(WRFRunnerComponentFactory.generateComponent(Duration.class, (ScopedMap) o, WRFRunnerComponentFactory.willInherit((ScopedMap) o) ? parent : this));
	}
} public class StandardDuration extends AbstractDuration {
	private int[] duration;
	
	/**
	 * Initializes a new instance of {@link StandardDuration} with a {@link Logger} derived from {@link Duration#LOGGER_NAME}.
	 * 
	 * @param parameters
	 *            the parameters that define the implementation as a {@link ScopedMap}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public StandardDuration(ScopedMap parameters, Scope parent) {
		super(parameters, parent);
	}
	
	@Override
	protected Calendar doApply(Calendar base) {
		for (int i = 0; i < duration.length; i++)
			base.add(TIMING_FIELD_IDS.get(i), duration[i]);
		return base;
	}
	
	@Override
	protected void compute() {
		duration = new int[TIMING_FIELD_NAMES.size()];
		for (int i = 0; i < duration.length; i++)
			if (getParameters().containsKey(TIMING_FIELD_NAMES.get(i))) {
				Object val = getParameters().get(TIMING_FIELD_NAMES.get(i));
				if (val instanceof String)
					val = ScopedFormulaProcessor.process((String) val, this, TIMING_FIELD_NAMES.get(i));
				if (val instanceof Number)
					duration[i] = ((Number) val).intValue();
				else
					throw new IllegalArgumentException(
							"The value of the " + TIMING_FIELD_NAMES.get(i) + " field must be an instance of Number or evaluate to an instance of Number after inheritance is processed.");
			}
	}
} public class DisabledOffset extends AbstractOffset {
	
	/**
	 * Constructs a new instance of an implementation of {@link Offset} that does not perform any actions.
	 * 
	 * @param parameters
	 *            the parameters that defined the instance as a {@link ScopedMap}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public DisabledOffset(ScopedMap parameters, Scope parent) {
		super(parameters, parent);
	}
	
	@Override
	protected void compute() {/* Nothing to do here */};

	@Override
	protected Calendar doApply(Calendar base) {
		return base;
	}

	@Override
	public boolean doesWrap() {
		return true;
	}
} public class ListOffset extends ScopedTimingComponentList<Offset> implements Offset {
	
	/**
	 * Initializes a new {@link ListOffset} wrapper around an existing {@link List} of {@link Offset} instances.
	 * 
	 * @param backing
	 *            the {@link List} of {@link Offset} instances
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public ListOffset(List<Offset> backing, Scope parent) {
		super(backing, parent);
	}
	
	/**
	 * Initializes a new {@link ListOffset} wrapper around a {@link List} of {@link Offset} instances derived from the "items" field of the provided
	 * {@link ScopedMap}. This is for compatibility with {@link WRFRunnerComponentFactory}.
	 * 
	 * @param parameters
	 *            a {@link ScopedMap} with "items" mapping to a {@link ScopedList}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public ListOffset(ScopedMap parameters, Scope parent) {
		this((ScopedList) parameters.get("items"), parent);
	}
	
	/**
	 * Initializes a new {@link ListOffset} wrapper around a {@link List} of {@link Offset} instances derived from the elements of the given
	 * {@link ScopedList}.
	 * 
	 * @param items
	 *            a {@link ScopedList} exclusively containing {@link ScopedMap ScopedMaps} that define {@link Offset} instances
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public ListOffset(ScopedList items, Scope parent) {
		this(new ArrayList<>(), parent);
		for (Object o : items)
			add(WRFRunnerComponentFactory.generateComponent(Offset.class, (ScopedMap) o, WRFRunnerComponentFactory.willInherit((ScopedMap) o) ? parent : this));
	}

	@Override
	public boolean doesWrap() {
		return get(0).doesWrap();
	}
} public interface Offset extends Scope, TimingComponent {
	/**
	 * The name that {@link Logger Loggers} in instances of {@link Offset} should be created from.
	 */
	public static final String LOGGER_NAME = SIMULATION_LOGGER_ROOT + ".Offset";
	
	/**
	 * @return {@code true} iff offsets should wrap according to the {@link Calendar Calendar's} model
	 */
	@NamedScopeValue("does-wrap")
	public boolean doesWrap();
	
	/**
	 * Performs the steps necessary to apply the modifications specified by the {@link Offset} to the given {@link Calendar}.
	 * 
	 * @param base
	 *            the {@link Calendar} to modify with the {@link Offset}
	 * @param inPlace
	 *            whether the modification should be performed in place (if {@code false} then the {@link Calendar} is cloned before being modified)
	 * @return {@code base} (or a copy thereof if {@code inPlace} is {@code false}) with the {@link Offset} applied
	 */
	@Override
	public Calendar apply(Calendar base, boolean inPlace);
} public class StandardOffset extends AbstractOffset {
	private int[] offsets;
	private Boolean wrap;
	
	/**
	 * Initializes a new instance of {@link StandardOffset} with a {@link Logger} derived from {@link Offset#LOGGER_NAME}.
	 * 
	 * @param parameters
	 *            the parameters that define the implementation as a {@link ScopedMap}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public StandardOffset(ScopedMap parameters, Scope parent) {
		super(parameters, parent);
		wrap = null;
	}
	
	@Override
	protected void compute() {
		Collection<String> enabled = parseEnabled();
		offsets = new int[TIMING_FIELD_NAMES.size()];
		for (int i = 0; i < offsets.length; i++)
			if (enabled.contains(TIMING_FIELD_NAMES.get(i)) && getParameters().containsKey(TIMING_FIELD_NAMES.get(i)))
				offsets[i] = ((Number) getParameters().get(TIMING_FIELD_NAMES.get(i))).intValue();
	}
	
	@Override
	protected Calendar doApply(Calendar base) {
		if (doesWrap())
			for (int i = 0; i < offsets.length; i++)
				base.add(TIMING_FIELD_IDS.get(i), offsets[i]);
		else
			for (int i = 0; i < offsets.length; i++)
				base.set(TIMING_FIELD_IDS.get(i), base.get(TIMING_FIELD_IDS.get(i)) + offsets[i]);
		return base;
	}
	
	@Override
	public boolean doesWrap() {
		if (wrap == null)
			wrap = getParameters().containsKey("wrap") ? ((Boolean) getParameters().get("wrap")) : ((getParent() instanceof Offset) ? ((Offset) getParent()).doesWrap() : true);
		return wrap;
	}
} public class BucketRound extends AbstractRound {
	private Map<Integer, Function<Integer, Integer>> roundingActions;
	
	/**
	 * Initializes a new instance of {@link BucketRound} described by the given {@code parameters} with a {@link Logger} derived from
	 * {@link Round#LOGGER_NAME} and the given parent {@link Scope}.
	 * 
	 * @param parameters
	 *            the parameters that describe the implementation as a {@link ScopedMap}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public BucketRound(ScopedMap parameters, Scope parent) {
		super(parameters, parent);
	}
	
	private final Map<Integer, Function<Integer, Integer>> parseRoundingParameters(Map<?, ?> parameters) {
		Collection<String> keep = parseEnabled();
		Map<Integer, Function<Integer, Integer>> roundingFunctions = new HashMap<>(); //We use a map because the key is the TIMING_FIELD_ID
		Map<?, ?> arguments = parameters.containsKey("arguments") ? (Map<?, ?>) parameters.get("arguments") : parameters; //Having the arguments sub-object was pointless
		String name;
		Object value;
		RoundingMode globalRM = roundingModeFromField(arguments.get("rounding-mode"), RoundingMode.FLOOR);
		for (int i = 0; i < TIMING_FIELD_NAMES.size(); i++) {
			name = TIMING_FIELD_NAMES.get(i);
			if (!keep.contains(name))
				continue;
			value = arguments.get(name); //If arguments does not contain name, arguments.get(name) returns null
			final RoundingMode rm = roundingModeFromField(arguments.get(name + "-rounding-mode"), globalRM);
			if (value == null && arguments.containsKey(name + "-step")) { //Step-offset
				String field = name + "-step";
				final int step = evaluateToNumber(arguments.get(field), field).intValue();
				
				field = name + "-offset";
				final int offset = evaluateToNumber(arguments.containsKey(field) ? arguments.get(field) : 0, field).intValue();
				roundingFunctions.put(TIMING_FIELD_IDS.get(i), stepOffsetProcessor(rm, step, offset));
			}
			else if (value instanceof List) { //Explicit buckets
				roundingFunctions.put(TIMING_FIELD_IDS.get(i), explicitBucketsProcessor(rm, name, (List<?>) value));
			}
			else if (value instanceof Map) {
				Map<?, ?> temp = (Map<?, ?>) value;
				final RoundingMode orm = roundingModeFromField(temp.get("rounding-mode"), rm);
				if (temp.containsKey("buckets"))
					roundingFunctions.put(TIMING_FIELD_IDS.get(i), explicitBucketsProcessor(orm, name + ".buckets", evaluateToType(temp.get("buckets"), name + ".buckets", List.class)));
				else
					roundingFunctions.put(TIMING_FIELD_IDS.get(i), stepOffsetProcessor(orm, evaluateToNumber(temp.get("step"), name + ".step").intValue(),
							evaluateToNumber(temp.containsKey("offset") ? arguments.get("offset") : 0, name + ".offset").intValue()));
			}
			else if (value instanceof Number) { //Step-offset without the -step tag
				String field = name + "-offset";
				roundingFunctions.put(TIMING_FIELD_IDS.get(i),
						stepOffsetProcessor(rm, ((Number) value).intValue(), evaluateToNumber(arguments.containsKey(field) ? arguments.get(field) : 0, field).intValue()));
			}
			else if (value != null) {
				throw new IllegalArgumentException("The value of " + name + " in BucketRound must be either undefined, null, or an instance of List, Map, or Number.");
			}
		}
		return roundingFunctions;
	}
	
	private Function<Integer, Integer> stepOffsetProcessor(RoundingMode rm, int step, int offset) {
		return inp -> Numbers.bucketRounding(inp, rm, step, offset);
	}
	
	private Function<Integer, Integer> explicitBucketsProcessor(RoundingMode rm, String name, List<?> buckets) {
		final int[] bucketsArr = new int[buckets.size()];
		for (int b = 0; b < bucketsArr.length; b++)
			bucketsArr[b] = evaluateToNumber(buckets.get(b), name + "[" + b + "]").intValue();
		return inp -> Numbers.bucketRounding(inp, rm, bucketsArr);
	}
	
	private RoundingMode roundingModeFromField(Object value, RoundingMode defaultRM) {
		if (value == null)
			return defaultRM;
		if (value instanceof String) {
			try {
				return RoundingMode.valueOf(((String) value).toUpperCase());
			}
			catch (IllegalArgumentException e) {/*Nothing to do here - we're just trying to get the Enum value from the String.*/}
		}
		return RoundingMode.valueOf(evaluateToNumber(value, "rounding-mode").intValue());
	}
	
	@Override
	protected Calendar doApply(Calendar base) {
		for (Entry<Integer, Function<Integer, Integer>> e : roundingActions.entrySet())
			base.set(e.getKey(), e.getValue().apply(base.get(e.getKey())));
		return base;
	}
	
	@Override
	protected void compute() {
		if (getParameters() == null)
			roundingActions = getParent() instanceof BucketRound ? parseRoundingParameters(((BucketRound) getParent()).getParameters()) : new HashMap<>();
		else
			roundingActions = parseRoundingParameters(getParameters());
	}
} public class DisabledRound extends AbstractRound {
	
	/**
	 * Constructs a new instance of an implementation of {@link Round} that does not perform any actions.
	 * 
	 * @param parameters
	 *            the parameters that defined the instance as a {@link ScopedMap}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public DisabledRound(ScopedMap parameters, Scope parent) {
		super(parameters, parent);
	}
	
	@Override
	protected Calendar doApply(Calendar base) {
		return base;
	}

	@Override
	protected void compute() {/* Nothing to do here */}
} public class FractionalRound extends AbstractRound {
	private int roundingPoint;
	private double fraction;
	private String diff;
	
	/**
	 * Initializes a new instance of {@link FractionalRound} based on the given {@code parameters} with a {@link Logger} derived from
	 * {@link Round#LOGGER_NAME} and the given parent {@link Scope}.
	 * 
	 * @param parameters
	 *            the parameters that define the implementation as a {@link ScopedMap}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public FractionalRound(ScopedMap parameters, Scope parent) {
		super(parameters, parent);
	}
	
	@Override
	protected Calendar doApply(Calendar base) {
		//First, we handle the diff on the field that the user is rounding on
		int rp = roundingPoint;
		if (diff.equals("next"))
			base.add(TIMING_FIELD_IDS.get(rp), 1);
		else if (diff.equals("previous"))
			base.add(TIMING_FIELD_IDS.get(rp), -1);
		if (fraction < 1.0) { //If the fraction is less than 1.0, then some portion of the field after the one specified in magnitude should be kept.
			--rp;
			int offset = 1 - base.getActualMinimum(TIMING_FIELD_IDS.get(rp));
			base.set(TIMING_FIELD_IDS.get(rp), (int) Numbers.semifloor(base.getActualMaximum(TIMING_FIELD_IDS.get(rp)) + offset, fraction, base.get(TIMING_FIELD_IDS.get(rp))));
		}
		for (int i = 0; i < rp; i++) //The logic here is that if we are rounding to something, then we want to set everything before it to 0.
			base.set(TIMING_FIELD_IDS.get(i), base.getActualMinimum(TIMING_FIELD_IDS.get(i)));
		return base;
	}
	
	@Override
	protected void compute() {
		String diff = getParameters().get("diff") instanceof String ? ((String) getParameters().get("diff")).toLowerCase() : "none";
		if (!diff.equals("next") && !diff.equals("previous") && !diff.equals("none") && !diff.equals("prev"))
			diff = (String) ScopedFormulaProcessor.process(diff, this, "diff");
		if (diff.equals("prev")) //This allows previous to have a 4-character variant
			diff = "previous";
		if (!diff.equals("next") && !diff.equals("previous") && !diff.equals("none"))
			throw new IllegalArgumentException("The diff field in fractional rounding must equal, or evaluate to, \"next\", \"previous\", or \"none\". \"" + diff.toString() + "\" is not a valid value.");
		this.diff = diff;
		
		Object frac = getParameters().containsKey("fraction") ? getParameters().get("fraction") : (Double) 1.0;
		if (frac instanceof String)
			frac = ScopedFormulaProcessor.process((String) frac, this, "fraction");
		if (!(frac instanceof Number))
			throw new IllegalArgumentException("The fraction field in fractional rounding must be, or evaluate to, an instance of Number if it exists.");
		double fraction = ((Number) frac).doubleValue();
		if (fraction <= 0.0 || fraction > 1.0) {
			getLogger().log(Level.WARNING, "The fraction field in round is outside of its valid range of (0.0, 1.0].  Assuming 1.0.");
			this.fraction = 1.0;
		}
		else {
			this.fraction = fraction;
		}
		
		Object mag = getParameters().get("magnitude");
		if (!(mag instanceof String))
			throw new IllegalArgumentException("The magnitude field in fractional rounding must exist and be, or evaluate to, a String.");
		String magnitude = ((String) mag).toLowerCase();
		int roundingPoint = TIMING_FIELD_NAMES.indexOf(magnitude);
		if (roundingPoint == -1) //If it is invalid, we can try inheritance
			mag = ScopedFormulaProcessor.process((String) mag, this, "magnitude");
		if (!(mag instanceof String))
			throw new IllegalArgumentException("The magnitude field in fractional rounding must exist and be, or evaluate to, a String.");
		magnitude = ((String) mag).toLowerCase();
		if (roundingPoint == -1)
			throw new IllegalArgumentException(magnitude + " is not a valid timing field name.");
		this.roundingPoint = roundingPoint;
	}
} public class FunctionRound extends AbstractRound {
	private ConsCell[] functions;
	
	/**
	 * Initializes a new instance of {@link FunctionRound} described by the given {@code parameters} with a {@link Logger} derived from
	 * {@link Round#LOGGER_NAME} and the given parent {@link Scope}.
	 * 
	 * @param parameters
	 *            the parameters that describe the implementation as a {@link ScopedMap}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public FunctionRound(ScopedMap parameters, Scope parent) {
		super(parameters, parent);
		functions = null;
	}
	
	@Override
	protected Calendar doApply(Calendar base) {
		ScopedMap manufacturedScope = new ScopedMap(this);
		for (int i = 0; i < functions.length; i++)
			manufacturedScope.put(TIMING_FIELD_NAMES.get(i), base.get(TIMING_FIELD_IDS.get(i)));
		for (int i = 0; i < functions.length; i++)
			base.set(TIMING_FIELD_IDS.get(i), evaluateToNumber(process(functions[i], manufacturedScope, TIMING_FIELD_NAMES.get(i)).getCar(), TIMING_FIELD_NAMES.get(i)).intValue());
		return base;
	}
	
	@Override
	protected void compute() {
		Collection<String> enabled = parseEnabled();
		functions = new ConsCell[TIMING_FIELD_NAMES.size()];
		for (int i = 0; i < functions.length; i++)
			functions[i] = preProcess(enabled.contains(TIMING_FIELD_NAMES.get(i)) && getParameters().containsKey(TIMING_FIELD_NAMES.get(i)) ? getParameters().get(TIMING_FIELD_NAMES.get(i)).toString()
					: TIMING_FIELD_NAMES.get(i));
	}
} public class ListRound extends ScopedTimingComponentList<Round> implements Round {
	
	/**
	 * Initializes a new {@link ListRound} wrapper around an existing {@link List} of {@link Round} instances.
	 * 
	 * @param backing
	 *            the {@link List} of {@link Round} instances
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public ListRound(List<Round> backing, Scope parent) {
		super(backing, parent);
	}
	
	/**
	 * Initializes a new {@link ListRound} wrapper around a {@link List} of {@link Round} instances derived from the "items" field of the provided
	 * {@link ScopedMap}. This is for compatibility with {@link WRFRunnerComponentFactory}.
	 * 
	 * @param parameters
	 *            a {@link ScopedMap} with "items" mapping to a {@link ScopedList}
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public ListRound(ScopedMap parameters, Scope parent) {
		this((ScopedList) parameters.get("items"), parent);
	}
	
	/**
	 * Initializes a new {@link ListRound} wrapper around a {@link List} of {@link Round} instances derived from the elements of the given
	 * {@link ScopedList}.
	 * 
	 * @param items
	 *            a {@link ScopedList} exclusively containing {@link ScopedMap ScopedMaps} that define {@link Round} instances
	 * @param parent
	 *            the parent {@link Scope}
	 */
	public ListRound(ScopedList items, Scope parent) {
		this(new ArrayList<>(), parent);
		for (Object o : items)
			add(WRFRunnerComponentFactory.generateComponent(Round.class, (ScopedMap) o, WRFRunnerComponentFactory.willInherit((ScopedMap) o) ? parent : this));
	}
} public interface Round extends Scope, TimingComponent {
	/**
	 * The name that {@link Logger Loggers} in instances of {@link Round} should be created from.
	 */
	public static final String LOGGER_NAME = SIMULATION_LOGGER_ROOT + ".Round";
} public class UpgradeWarning {
	private final String id;
	private final String text;
	
	/**
	 * Constructs a new {@link UpgradeWarning} with the given elements.
	 * 
	 * @param id
	 *            the first element
	 * @param text
	 *            the second element
	 */
	public UpgradeWarning(String id, String text) {
		this.id = id;
		this.text = text;
	}
	
	/**
	 * @return the ID of the {@link UpgradeProblemHandler} that created the {@link UpgradeWarning}
	 */
	public String getID() {
		return id;
	}
	
	/**
	 * @return the text of the warning that the {@link UpgradeWarning} wraps
	 */
	public String getText() {
		return text;
	}
	
	/**
	 * @return a {@link String} containing: "&lt;ID, text&gt;"
	 */
	@Override
	public String toString() {
		return "<" + getID() + ", " + getText() + ">";
	}
	
	@Override
	public int hashCode() {
		return Objects.hash(getID(), getText());
	}
	
	@Override
	public boolean equals(Object o) {
		if (!(o instanceof UpgradeWarning))
			return false;
		UpgradeWarning uw = (UpgradeWarning) o;
		return (getID() == uw.getID() || (getID() != null && getID().equals(uw.getID()))) && (getText() == uw.getText() || (getText() != null && getText().equals(uw.getText())));
	}
	
} public class ConfigurationUpgrader extends JSONUpgrader {
	private static final VersionNumber MINIMUM_VERSION = new VersionNumber("1.0.0");
	
	@Override
	protected VersionNumber getVersion(JSONObject root) {
		return root.containsKey("version") && root.get("version").value() instanceof String ? new VersionNumber((String) root.get("version").value()) : MINIMUM_VERSION;
	}
	
	@Override
	protected void setVersion(JSONObject root, VersionNumber version) {
		root.put("version", version.toString());
	}
} public class FormulaUpgradeProblemHandler extends ConfigurationUpgradeProblemHandler {
	
	/**
	 * Creates a new {@link FormulaUpgradeProblemHandler} for use with a {@link ConfigurationUpgrader}.
	 */
	public FormulaUpgradeProblemHandler() {
		super("formula", new VersionNumber("4.0.0"));
	}
	
	@Override
	public String generateWarning(String path, JSONData<?> value) {
		StringBuilder warning = new StringBuilder();
		warning.append("The value of ").append(path).append(" starts with ");
		if (((String) value.value()).charAt(0) == '=')
			warning.append("an '='.  It will therefore be treated as a formula.");
		else //Then it starts with \=
			warning.append("\"\\=\".  It will therefore be treated as a literal String equal to \"" + ((String) value.value()).substring(1) + "\".");
		warning.append("\nIf this is not correct, add a '\\' to the start of the value.");
		warning.append("\nSee https://github.com/Toberumono/WRF-Runner/wiki/Configuration-Formula-System for more information.");
		return warning.toString();
	}
	
	@Override
	public boolean fieldHasProblem(JSONData<?> value) {
		return (value instanceof JSONString) && (((String) value.value()).charAt(0) == '=' || ((String) value.value()).startsWith("\\="));
	}
	
	@Override
	public JSONData<?> interactiveFix(String path, JSONData<?> oldValue, Supplier<String> input, Consumer<String> output) {
		if (promptForValueCorrectness(path, oldValue, input, output))
			return oldValue;
		oldValue = new JSONString("\\" + oldValue.value());
		output.accept("Is \"" + oldValue.value() + "\" correct (a preceeding '\\' was added)? [Y/n] ");
		if (!input.get().toLowerCase().startsWith("n"))
			return oldValue;
		output.accept("Please enter the correct value here (it must be on a single line):\n");
		return stringToValue(oldValue, input.get());
	}
	
	@Override
	protected JSONData<?> stringToValue(JSONData<?> oldValue, String newValue) {
		return new JSONString(newValue);
	}
} public static final List<String> TIMING_FIELD_NAMES = Collections.unmodifiableList(Arrays.asList("milliseconds", "seconds", "minutes", "hours", "days", "months", "years"));
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Scanner;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import toberumono.json.JSONArray;
import toberumono.json.JSONBoolean;
import toberumono.json.JSONData;
import toberumono.json.JSONNumber;
import toberumono.json.JSONObject;
import toberumono.json.JSONString;
import toberumono.json.JSONSystem;
import toberumono.namelist.parser.Namelist;
import toberumono.structures.SortingMethods;
import toberumono.structures.collections.lists.SortedList;
import toberumono.structures.tuples.Pair;
import toberumono.utils.files.RecursiveEraser;
import toberumono.wrf.components.parallel.DisabledParallel;
import toberumono.wrf.components.parallel.Parallel;
import toberumono.wrf.components.parallel.StandardParallel;
import toberumono.wrf.timing.ComputedTiming;
import toberumono.wrf.timing.DisabledTiming;
import toberumono.wrf.timing.Timing;
import toberumono.wrf.timing.clear.Clear;
import toberumono.wrf.timing.clear.DisabledClear;
import toberumono.wrf.timing.clear.ListClear;
import toberumono.wrf.timing.clear.StandardClear;
import toberumono.wrf.timing.duration.DisabledDuration;
import toberumono.wrf.timing.duration.Duration;
import toberumono.wrf.timing.duration.ListDuration;
import toberumono.wrf.timing.duration.StandardDuration;
import toberumono.wrf.timing.offset.DisabledOffset;
import toberumono.wrf.timing.offset.ListOffset;
import toberumono.wrf.timing.offset.Offset;
import toberumono.wrf.timing.offset.StandardOffset;
import toberumono.wrf.timing.round.BucketRound;
import toberumono.wrf.timing.round.DisabledRound;
import toberumono.wrf.timing.round.FractionalRound;
import toberumono.wrf.timing.round.FunctionRound;
import toberumono.wrf.timing.round.ListRound;
import toberumono.wrf.timing.round.Round;
import toberumono.wrf.upgrader.UpgradeWarning;
import toberumono.wrf.upgrader.configuration.ConfigurationUpgrader;
import toberumono.wrf.upgrader.configuration.FormulaUpgradeProblemHandler;
import static toberumono.wrf.SimulationConstants.TIMING_FIELD_NAMES;
import static toberumono.json.JSONBoolean.TRUE;
import static toberumono.json.JSONBoolean.FALSE;
		upgrader.addUpgradeAction("4.0.0", root -> {
			JSONObject general = (JSONObject) root.get("general"), timing = (JSONObject) root.get("timing");
			JSONObject modules = (JSONObject) root.computeIfAbsent("module", k -> new JSONObject());
			JSONSystem.transferField("logging-level", new JSONString("INFO"), general);
			//Build the module section
			if (general.containsKey("features")) {
				JSONObject features = (JSONObject) general.get("features");
				JSONSystem.renameField(features, TRUE, "wget", "grib");
				for (int i = 0; i < DEFAULT_MODULE_NAMES.length; i++) {
					if (!modules.containsKey(DEFAULT_MODULE_NAMES[i]))
						modules.put(DEFAULT_MODULE_NAMES[i], buildModuleSubsection(features.getOrDefault(DEFAULT_MODULE_NAMES[i], TRUE),
								DEFAULT_MODULE_CLASSES[i], DEFAULT_MODULE_NAMELISTS[i], DEFAULT_MODULE_DEPENDENCIES[i]));
				}
			}
			else {
				for (int i = 0; i < DEFAULT_MODULE_NAMES.length; i++)
					if (!modules.containsKey(DEFAULT_MODULE_NAMES[i]))
						modules.put(DEFAULT_MODULE_NAMES[i], buildModuleSubsection(TRUE, DEFAULT_MODULE_CLASSES[i], DEFAULT_MODULE_NAMELISTS[i], DEFAULT_MODULE_DEPENDENCIES[i]));
			}
			//Build the timing->global subsection
			if (!timing.containsKey("global")) {
				JSONObject rounding = timing.get("rounding") instanceof JSONObject ? (JSONObject) timing.remove("rounding") : new JSONObject();
				JSONObject global = new JSONObject();
				if (timing.containsKey("duration"))
					global.put("duration", timing.remove("duration")); //Duration has remained unchanged
				global.put("offset", timing.remove("offset")); //Offset has remained unchanged
				JSONObject clear = new JSONObject();
				
				if (rounding.containsKey("magnitude")) {
					String magnitude = (String) rounding.get("magnitude").value();
					if (!magnitude.endsWith("s")) {
						magnitude = magnitude + "s";
						rounding.put("magnitude", magnitude);
					}
					//Build the timing->global->clear subsection
					clear.put("keep", TIMING_FIELD_NAMES.get(TIMING_FIELD_NAMES.indexOf(magnitude) - 1));
				}
				clear.put("enabled", rounding.get("enabled"));
				global.put("clear", clear);
				//Build the timing->global->rounding subsection
				rounding.put("type", "fractional");
				if (!rounding.containsKey("fraction")) {
					rounding.put("fraction", 1.0);
					rounding.put("enabled", FALSE);
				}
				if (!rounding.containsKey("diff") || rounding.get("diff").equals("current"))
					rounding.put("diff", "none");
				global.put("rounding", rounding);
				timing.put("global", global);
			}
			if (((JSONObject) root.get("grib")).containsKey("wrap-timestep")) //If grib contains wrap-timestep, transfer it to grib->timestep
				JSONSystem.transferField("wrap-timestep", TRUE, (JSONObject) root.get("grib"), (JSONObject) ((JSONObject) root.get("grib")).get("timestep"));
			if (((JSONObject) ((JSONObject) root.get("grib")).get("timestep")).containsKey("wrap-timestep"))
				JSONSystem.renameField((JSONObject) ((JSONObject) root.get("grib")).get("timestep"), TRUE, "wrap-timestep", "wrap");
		});
		upgrader.addUpgradeAction("4.0.0", root -> {
			JSONObject modules = (JSONObject) root.get("module");
			if (modules.containsKey("execution-order")) {
				JSONArray eo = (JSONArray) modules.remove("execution-order");
				for (int i = 1; i < eo.size(); i++) {
					JSONObject module = (JSONObject) modules.get(eo.get(i).value());
					if (!module.containsKey("dependencies")) {
						JSONArray deps = new JSONArray();
						deps.add(eo.get(i - 1));
						module.put("dependencies", deps);
					}
				}
			}
		});
		
		upgrader.addUpgradeAction("5.0.0", root -> {
			//Rename all uses of type = json in Timing subsections to type = computed
			JSONObject timing = (JSONObject) root.get("timing"), temp;
			for (JSONData<?> val : timing.values()) { //All timing subsections except for grib are single-depth.  Therefore, this will take care of most of the appropriate renaming
				if (val instanceof JSONObject) {
					temp = (JSONObject) val;
					if (temp.containsKey("type") && temp.get("type").value().equals("json"))
						temp.put("type", "computed");
				}
			}
			if (timing.get("grib") instanceof JSONObject) {
				for (JSONData<?> val : ((JSONObject) timing.get("grib")).values()) { //This takes care of the remaining type renames.
					if (val instanceof JSONObject) {
						temp = (JSONObject) val;
						if (temp.containsKey("type") && temp.get("type").value().equals("json"))
							temp.put("type", "computed");
					}
				}
			}
			//Rename rounding to round
			ConfigurationUpgrader.recursiveRenameField(timing, "rounding", "round"); //Rounding should only be renamed within timing
		});
		
		upgrader.addUpgradeAction("5.1.0", root -> ((JSONObject) root.get("general")).putIfAbsent("serial-module-execution", FALSE));
		
		upgrader.addUpgradeAction("5.2.0", root -> {
			JSONObject general = (JSONObject) root.get("general");
			JSONObject wrf = (JSONObject) root.computeIfAbsent("wrf", k -> new JSONObject());
			if (!wrf.containsKey("parallel")) {
				if (general.containsKey("parallel"))
					wrf.put("parallel", general.get("parallel"));
				else {
					JSONObject defaultParallel = new JSONObject();
					defaultParallel.put("is-dmpar", FALSE);
					defaultParallel.put("boot-lam", FALSE);
					defaultParallel.put("processors", new JSONNumber<>(2));
					wrf.put("parallel", defaultParallel);
				}
			} else {
				JSONSystem.transferField("always-suffix", FALSE, (JSONObject) wrf.get("parallel"), general);
			}
			general.remove("parallel");
		});
		upgrader.addUpgradeAction("5.3.2", root -> JSONSystem.transferField("use-computed-times", TRUE, (JSONObject) root.get("timing"), (JSONObject) root.get("general")));
		upgrader.addUpgradeAction("6.0.2", root -> JSONSystem.renameField((JSONObject) root.get("general"), FALSE, "serial-module-execution", "force-serial-module-execution"));
		upgrader.addUpgradeAction("6.1.0", root -> JSONSystem.renameField((JSONObject) root.get("grib"), new JSONNumber<>(8), "max-concurrent-downloads"));
		upgrader.addUpgradeAction("7.4.0", root -> JSONSystem.renameField((JSONObject) root.get("grib"), FALSE, "use-increment-duration"));
	}
	
	protected void initConfigurationUpgradeProblemHandlers(ConfigurationUpgrader upgrader) {
		upgrader.addUpgradeProblemHandler(new FormulaUpgradeProblemHandler());