@Override
	public void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,
	                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,
	                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {
		String ownerName = initialClassState.getName();
		AggregatedMappings mappings = context.getMappings();

		// Extract metadata
		KtClass ktClass = KotlinMetadata.extractKtModel(initialClassState);
		if (ktClass == null) {
			// Check if there is @JvmName as a fallback. The annotation is similar to SourceFileAttribute.
			initialClassState.getClassReader().accept(new SkippingClassVisitor() {
				@Override
				public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
					if (!descriptor.equals("Lkotlin/jvm/JvmName;"))
						return null;
					return new AnnotationVisitor(RecafConstants.getAsmVersion()) {
						private static final Pattern NAME_PATTERN = RegexUtil.pattern("\\w{1, 50}");

						@Override
						public void visit(String name, Object value) {
							if ("name".equals(name) && value instanceof String sourceName && NAME_PATTERN.matches(sourceName)) {
								mappings.addClass(ownerName, initialClassState.getPackageName() + '/' + sourceName);
							}
						}
					};
				}
			}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE);
			return;
		}

		// The @MetaData class name is the full class descriptor. Add it as-is to the mappings.
		if (ktClass.getName() != null)
			mappings.addClass(ownerName, ktClass.getName());

		// Sadly, the kotlin meta-data is unordered, so we can only be sure about name mappings
		// when there are only EXACT descriptor matches.
		for (KtProperty property : ktClass.getProperties()) {
			if (property.getName() == null)
				continue;
			String descriptor = Objects.requireNonNull(mappings.applyReverseMappings(KtType.toDescriptor(property)));
			List<FieldMember> descriptorMatchedProperties = initialClassState.fieldStream()
					.filter(f -> matchDescriptor(f, descriptor))
					.toList();
			if (descriptorMatchedProperties.size() == 1) {
				FieldMember field = descriptorMatchedProperties.getFirst();
				mappings.addField(ownerName, field.getDescriptor(), field.getName(), property.getName());
			}
		}
		for (KtFunction function : ktClass.getFunctions()) {
			if (function.getName() == null)
				continue;
			String descriptor = Objects.requireNonNull(mappings.applyReverseMappings(KtType.toDescriptor(function)));
			List<MethodMember> descriptorMatchedFunctions = initialClassState.methodStream()
					.filter(m -> matchDescriptor(m, descriptor))
					.toList();
			if (descriptorMatchedFunctions.size() == 1) {
				MethodMember method = descriptorMatchedFunctions.getFirst();
				mappings.addMethod(ownerName, method.getDescriptor(), method.getName(), function.getName());
			}
		}
	}
A transformer that renames classes and members based on Kotlin metadata.

@author Matt Coley
recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/KotlinNameRestorationTransformer.java