microsoft / microsoft/thrifty

Reflective extensions code dump

Open
#293 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Kotlin
Stars
542
Forks
101
Avg merge
1d 12h
Merged PRs (30d)
2

Description

Talked offline, here's a dump of reflective extensions we currently employ because 🙃

package com.uber.thriftyextensions

import com.microsoft.thrifty.schema.Field
import com.microsoft.thrifty.schema.Location
import com.microsoft.thrifty.schema.Requiredness
import com.microsoft.thrifty.schema.UserElement
import com.microsoft.thrifty.schema.UserType
import com.microsoft.thrifty.schema.parser.ConstValueElement
import com.microsoft.thrifty.schema.parser.FieldElement
import java.lang.reflect.Modifier
import java.util.UUID

/*
 * Unsafe reflection-based extensions to Thrifty's APIs. Necessary because there's no APIs available
 * for adjusting [FieldElement] APIs.
 */

/**
 * Creates a new copy of this [Field] with `required` [Field.required]ness reflectively set via its
 * internal [FieldElement] field.
 *
 * @receiver the source [Field] to use. A defensive copy will be made.
 * @return the new [Field] instance.
 */
fun Field.makeRequired(): Field {
  val newElement = (elementField.get(this) as FieldElement).copy(requiredness = Requiredness.REQUIRED)
  return toBuilder().build()
      .apply {
        elementField.set(this, newElement)
      }
}

/**
 * Creates a new copy of this [Field] with `optional` [Field.required]ness reflectively set via its
 * internal [FieldElement] field.
 *
 * @receiver the source [Field] to use. A defensive copy will be made.
 * @return the new [Field] instance.
 */
fun Field.makeOptional(): Field {
  val newElement = (elementField.get(this) as FieldElement).copy(requiredness = Requiredness.OPTIONAL)
  return toBuilder().build()
      .apply {
        elementField.set(this, newElement)
      }
}

/**
 * Creates a new copy of this [Field] with the specified [value] default value reflectively set via
 * its internal [FieldElement] field.
 *
 * @receiver the source [Field] to use. A defensive copy will be made.
 * @return the new [Field] instance.
 */
fun Field.withDefaultValue(value: ConstValueElement): Field {
  val newElement = (elementField.get(this) as FieldElement).copy(constValue = value)
  return toBuilder().build()
      .apply {
        elementField.set(this, newElement)
      }
}

/**
 * Creates a new copy of this [Field] with the specified [newId] reflectively set via its internal
 * [FieldElement] field.
 *
 * @param newId the new id to set.
 * @receiver the source [Field] to use. A defensive copy will be made.
 * @return the new [Field] instance.
 */
fun Field.newId(newId: Int): Field {
  val newElement = (elementField.get(this) as FieldElement).copy(fieldId = newId)
  return toBuilder().build()
      .apply {
        elementField.set(this, newElement)
      }
}

/**
 * Ensures [this] list of [Field]s are valid (such as ensuring they've got unique field IDs).
 *
 * @param ensuredLocation the [Location] of the element holding this list to ensure fields match it.
 * @receiver the source list to check. A defensive new copy will be made.
 * @return the new fields list with the new field appended to the end.
 */
fun List<Field>.asSafeFields(ensuredLocation: Location): List<Field> {
  if (distinctBy { it.id }.size == size) {
    // They're all distinct, just return them with locations ensured
    return map {
      it.toBuilder()
          .location(it.location.matchTo(ensuredLocation))
          .build()
    }
  }
  val safeFields = fold(emptyList<Field>()) { cur, next ->
    cur.safeAddField(next, ensuredLocation)
  }
  check(safeFields.size == size) {
    "Data lost!"
  }
  return safeFields
}

/**
 * Safely adds a new [Field] to this collection while ensuring that it does not conflict with the
 * existing field ids.
 *
 * @param newField the new [Field] to add.
 * @param ensuredLocation the [Location] of the element holding this list to ensure fields match it.
 * @receiver the source list to add this to. A defensive new copy will be made.
 * @return the new fields list with the new field appended to the end.
 */
fun List<Field>.safeAddField(newField: Field, ensuredLocation: Location): List<Field> {
  val defensiveCopy = newField.toBuilder()
      .location(newField.location.matchTo(ensuredLocation))
      .build()
  if (none { it.id == newField.id }) {
    return plus(defensiveCopy)
  }
  val finalField = map(Field::id).max()?.let { maxId ->
    defensiveCopy.newId(maxId + 1)
  } ?: defensiveCopy
  return plus(finalField)
}

private fun Location.matchTo(source: Location): Location {
  return if (base == source.base && path == source.path) {
    this
  } else {
    Location.get(source.base, source.path)
  }
}

/**
 * Creates a new copy of this [Field] with the specified [newUUID] reflectively set via its internal
 * [FieldElement] field.
 *
 * @param newUUID the new UUID to use. Defaults to a randomly generated one.
 * @receiver the source [Field] to use. A defensive copy will be made.
 * @return the new [Field] instance.
 */
fun <T : UserElement> T.newUuid(newUUID: UUID = UUID.randomUUID()): T {
  if (this is UserType) {
    UserType::class.java.getDeclaredField("mixin")
  } else {
    javaClass.getDeclaredField("mixin")
  }
      .apply {
        isAccessible = true
        val modifiersField = java.lang.reflect.Field::class.java
            .getDeclaredField("modifiers")
        modifiersField.isAccessible = true
        modifiersField.setInt(this, modifiers and Modifier.FINAL.inv())
      }
      .get(this)
      .let { mixin ->
        // UserElementMixin
        mixin.javaClass.getDeclaredField("uuid")
            .apply {
              isAccessible = true
              val modifiersField = java.lang.reflect.Field::class.java
                  .getDeclaredField("modifiers")
              modifiersField.isAccessible = true
              modifiersField.setInt(this, modifiers and Modifier.FINAL.inv())
            }
            .apply {
              val olduuid = uuid
              set(mixin, newUUID)
              check(olduuid != uuid) {
                "New UUID setting failed!"
              }
            }
      }
  return this
}

/**
 * Sets a new default value for a given [Field].
 *
 * @param const the new [ConstValueElement] to set
 * @return this same [Field] instance.
 */
fun Field.setDefaultValue(const: ConstValueElement): Field {
  val newElement = (elementField.get(this) as FieldElement).copy(constValue = const)
  elementField.set(this, newElement)
  return this
}

private val elementField = Field::class.java.getDeclaredField("element")
    .apply {
      isAccessible = true
      val modifiersField = java.lang.reflect.Field::class.java
          .getDeclaredField("modifiers")
      modifiersField.isAccessible = true
      modifiersField.setInt(this, modifiers and Modifier.FINAL.inv())
    }

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

No repository files or tests are named. Start by reviewing the public Field, FieldElement, UserElement, and UserType APIs referenced in the dump, then determine whether supported APIs should replace these reflection-based extensions; completion criteria are not defined in the issue.

Written by the indexing model from the issue text.

Assessment

Tech stack
kotlin
Domain
tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
15/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.