night-config icon indicating copy to clipboard operation
night-config copied to clipboard

Create a deep copy of a configuration?

Open brainbytes42 opened this issue 1 year ago • 5 comments

hi again,

is there any (simple) way to create a deep copy of a configuration? Then one could compare the original and copied version, if one was changed later on? (equals seems to work properly for this case, comparing values and recursively including sub-configurations.)

This is slightly related to #118, as I was evaluating workarounds to this and to the question, if the config has been changed (to then manually / optionally save the config with user interaction). (Not only for autosave, but also for changes in config, a listener would be nice.)

But it seems, that in a copy only the 'simple' values are copied; sub-configurations remain linked and will be changed in both places:

Config config = Config.inMemory();
config.set("foo", "bar");
Config sub = config.createSubConfig();
sub.set("hello", "world");
config.set("sub", sub);
System.out.println("config = " + config);

System.out.println("--- COPY + CHANGE ---");

Config copy = Config.copy(config);
copy.set("foo", "bar-changed");
copy.<Config>get("sub")
    .set("hello", "world-changed");
System.out.println("config = " + config + "     <--  EXPECTED hello='world', BUT IS 'world-changed'! (no real deep copy!)");
System.out.println("copy = " + copy);

Maybe (if this behaviour isn't considered a bug), add Config.deepCopy(UnmodifiableConfig)?

brainbytes42 avatar Jan 16 '23 08:01 brainbytes42

Hi!

I've created #133 to track your feature request about modification tracking. Config#copy() is intended to be a shallow copy, in a similar way to clone(). A deepCopy() or deepClone() would be useful, though. Let's track this here.

For now, you can implement it yourself by calling copy, iterating on the entries and recursively copying sub-configurations, it shouldn't be too long to implement.

TheElectronWill avatar Jan 16 '23 09:01 TheElectronWill

I'm still wondering what the right interface is. One possibility is to add these to Config

	/**
	 * Puts a deep copy of the given configuration to the root of this config.
	 * <p>
	 * This methods calls {@code deepCopy} on every sub-configuration,
	 * manually copies lists of known type (including {@code ArrayList}),
	 * calls {@code clone()} on {@code Cloneable} values, and simply
	 * copy the reference of other values.
	 * <p>
	 * For more control over the deep copy, in particular if you have
	 * put nested mutable objects into the config, use {@link #putDeepCopy(Function)}.
	 */
	void putDeepCopy(UnmodifiableConfig configToCopy);

	/**
	 * Puts a deep copy of the given configuration to the root of this config.
	 * <p>
	 * This methods calls {@code deepCopy} on every sub-configuration
	 * and uses the {@code valueCopier} for other values (including lists).
	 *
	 * @param valueCopier a function that creates a deep copy of any object
	 */
	void putDeepCopy(UnmodifiableConfig configToCopy, Function<Object, Object> valueCopier);

But given that many options could be good to have, it may be better to have a dedicated class DeepConfigCopier.

TheElectronWill avatar Mar 08 '24 11:03 TheElectronWill

The Signature doesn't fit the JavaDoc's @return...

(And I'd prefer the described functionality in @return - so that I just get a new Config-Object from an existing one, without too much hassle...)

brainbytes42 avatar Mar 19 '24 12:03 brainbytes42

Ah that's right, I miscopied some code. The design choice here is: how do you choose the type of the new config. For simple configs that maps a HashMap and aren't thread-safe it's trivial, but for ConcurrentConfigs it's not (for instance, a subConfig of a SynchronizedConfig is not the same as a new SynchronizedConfig).

Thus, doing new MyNewConfig().putDeepCopy(config); works well. An alternative would be config.deepCopyTo(MyNewConfig::new);.

TheElectronWill avatar Mar 19 '24 13:03 TheElectronWill

Ah, I see - that makes sense...

From a usability / "discoverability" view, I'd prefer the latter, as I'd then come from an existing object and have all my methods there, with all their documentation / examples. Seems more fluent to me... But maybe that's a personal preference... :-)

brainbytes42 avatar Mar 19 '24 14:03 brainbytes42