private void save() {
		// Pull data from path.
		JvmClassInfo info = path.getValue().asJvmClass();
		Workspace workspace = path.getValueOfType(Workspace.class);
		if (workspace == null)
			throw new IllegalStateException("Workspace missing from class path node");
		JvmClassBundle bundle = (JvmClassBundle) path.getValueOfType(Bundle.class);
		if (bundle == null)
			throw new IllegalStateException("Bundle missing from class path node");

		// Clear old errors emitted by compilation.
		problemTracking.removeByPhase(ProblemPhase.BUILD);

		// Invoke compiler with data.
		String infoName = info.getName();
		CompletableFuture.supplyAsync(() -> {
			boolean debug = javacDebug.getValue();
			JavacArgumentsBuilder builder = new JavacArgumentsBuilder()
					.withVersionTarget(useConfiguredVersion(info))
					.withDownsampleTarget(javacDownsampleTarget.getValue())
					.withDebugVariables(debug)
					.withDebugSourceName(debug)
					.withDebugLineNumbers(debug)
					.withClassSource(editor.getText())
					.withClassName(infoName);
			return javac.compile(builder.build(), workspace, null);
		}, compilePool).completeOnTimeout(null, 2, TimeUnit.SECONDS).whenCompleteAsync((result, throwable) -> {
			// Handle results.
			//  - Success --> Update content in the containing bundle
			//  - Failure --> Show error + diagnostics to user
			if (result != null && result.wasSuccess()) {
				// Renaming is not allowed. Tell the user to use mapping operations.
				// This should usually be caught by javac, but we're double-checking here.
				// We *could* have some hacky code to work around the rename being done outside the dedicated API,
				// but it would be ugly. Find the new name for the class and any inners, copy over properties from
				// the old names, apply mapping operations to patch broken references, etc.
				CompileMap compilations = result.getCompilations();

				// Check if the target name still exists.
				if (!compilations.containsKey(infoName)) {
					logger.warn("Please only rename classes via mapping operations.");
					return;
				}

				// Check if any non-external-reference inner class entry no longer exists.
				//  - Removal/updating/insertion is OK, renaming is not.
				//  - Because inners may have other inners we need to recursively collect inner classes
				Map<String, InnerClassInfo> realInners = info.getInnerClasses().stream()
						.filter(inner -> !inner.isExternalReference())
						.collect(Collectors.toMap(InnerClassInfo::getInnerClassName, Function.identity()));
				Set<String> names = new HashSet<>();
				boolean recurseAddInners;
				do {
					// Reset the recurse flag each iteration. We'll enable it only if we need to.
					recurseAddInners = false;
					for (String type : new HashSet<>(realInners.keySet())) {
						// Skip if we already checked this type for further inner classes.
						if (!names.add(type)) continue;

						// Lookup inner class in workspace and add its inner classes to the map.
						ClassPathNode typePath = workspace.findClass(type);
						if (typePath != null) {
							List<InnerClassInfo> innerClasses = typePath.getValue().getInnerClasses();
							for (InnerClassInfo inner : innerClasses) {
								if (inner.isExternalReference()) continue;

								// Enable another recursive pass if a new inner class was found.
								recurseAddInners |= realInners.putIfAbsent(inner.getInnerClassName(), inner) == null;
							}
						}
					}
				} while (recurseAddInners);

				// Ensure all names in the compilation exist in the previous inner classes info
				if (!realInners.isEmpty() && compilations.keySet().stream().anyMatch(name -> !name.equals(infoName) && !realInners.containsKey(name))) {
					logger.warn("Please only rename inner classes via mapping operations.");
					Animations.animateWarn(this, 1000);
					return;
				}

				// Compilation map has contents, update the workspace.
				Animations.animateSuccess(this, 1000);
				updateLock.set(true);
				compilations.forEach((name, bytecode) -> {
					JvmClassInfo newInfo;
					if (infoName.equals(name)) {
						// Adapt from existing.
						newInfo = info.toJvmClassBuilder()
								.adaptFrom(bytecode)
								.build();
					} else {
						// Handle inner classes.
						JvmClassInfo originalClass = bundle.get(name);
						if (originalClass != null) {
							// Adapt from existing.
							newInfo = originalClass
									.toJvmClassBuilder()
									.adaptFrom(bytecode)
									.build();
						} else {
							// Class is new.
							newInfo = new JvmClassInfoBuilder(bytecode).build();
						}
					}

					// Update cached decompile property to current editor text.
					// If the class is opened later, we can use the code we compiled with.
					JvmDecompiler currentDecompiler = decompiler.getValue();
					int configHash = currentDecompiler.getConfig().getHash();
					CachedDecompileProperty.set(newInfo, currentDecompiler,
							new DecompileResult(editor.getText(), configHash));

					// Update the class in the bundle.
					bundle.put(newInfo);
				});
				updateLock.set(false);
			} else {
				// Handle compile-result failure, or uncaught thrown exception.
				if (result != null) {
					if (result.getDiagnostics().isEmpty() && result.getException() != null)
						problemTracking.add(new Problem(-1, -1, 0,
								ProblemLevel.ERROR, ProblemPhase.BUILD, result.getException().toString()));

					for (CompilerDiagnostic diagnostic : result.getDiagnostics())
						problemTracking.add(Problem.fromDiagnostic(diagnostic));

					// For first-timers, tell them you cannot save with errors.
					if (!config.getAcknowledgedSaveWithErrors().getValue())
						showFirstTimeSaveWithErrors();
				} else {
					logger.error("Compilation encountered an error on class '{}'", infoName, throwable);
				}
				Animations.animateFailure(this, 1000);
			}

			// Redraw paragraph graphics to update things like in-line problem graphics.
			editor.redrawParagraphGraphics();
		}, FxThreadUtil.executor());
	}
Called when {@link KeybindingConfig#getSave()} is pressed.
<br>
Compiles the current Java code in the {@link #editor} and updates the workspace
with the newly compiled {@link JvmClassInfo}.
recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/JvmDecompilerPane.java