Skip to content

Commit

Permalink
most of write finished
Browse files Browse the repository at this point in the history
  • Loading branch information
Greg Zoller committed Nov 8, 2023
1 parent 62ddc82 commit e48b358
Show file tree
Hide file tree
Showing 6 changed files with 363 additions and 119 deletions.
50 changes: 50 additions & 0 deletions src/main/java/co/blocke/scalajack/AESEncryptionDecryption.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package co.blocke.scalajack;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import java.io.UnsupportedEncodingException;
import javax.crypto.IllegalBlockSizeException;
import java.util.Arrays;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.BadPaddingException;

/**
* Java String Encryption Decryption Example
* @author Ramesh Fadatare
*
*/
public class AESEncryptionDecryption {
private static SecretKeySpec secretKey;
private static byte[] key;
private static final String ALGORITHM = "AES";

public void prepareSecreteKey(String myKey) throws NoSuchAlgorithmException {
MessageDigest sha = null;
key = myKey.getBytes(StandardCharsets.UTF_8);
sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
secretKey = new SecretKeySpec(key, ALGORITHM);
}

public String encrypt(String strToEncrypt, String secret)
throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException {
prepareSecreteKey(secret);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return Base64.getEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes("UTF-8")));
}

public String decrypt(String strToDecrypt, String secret) throws NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException, InvalidKeyException, IllegalBlockSizeException {
prepareSecreteKey(secret);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)));
}
}
8 changes: 8 additions & 0 deletions src/main/java/co/blocke/scalajack/TypeHint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package co.blocke.scalajack;

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeHint{}

216 changes: 150 additions & 66 deletions src/main/scala/co.blocke.scalajack/json/JsonWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package json

import scala.quoted.*
import co.blocke.scala_reflection.*
import co.blocke.scala_reflection.rtypes.{EnumRType, ScalaClassRType, TraitRType}
import co.blocke.scala_reflection.rtypes.{EnumRType, NonConstructorFieldInfo, ScalaClassRType, TraitRType}
import co.blocke.scala_reflection.reflect.{ReflectOnType, TypeSymbolMapper}
import co.blocke.scala_reflection.reflect.rtypeRefs.*
import scala.jdk.CollectionConverters.*
Expand All @@ -12,8 +12,8 @@ import scala.quoted.staging.*

/*
TODO:
[ ] - Scala non-case class
[ ] - Java class (Do I still want to support this???)
[*] - Scala non-case class
[*] - Java class (Do I still want to support this???)
[*] - Enum
[*] - Enumeration
[*] - Java Enum
Expand All @@ -25,16 +25,22 @@ import scala.quoted.staging.*
[*] - Object (How???)
[*] - Trait (How???)
[*] - Sealed trait
[ ] - sealed abstract class (handle like sealed trait....)
[*] - sealed abstract class (handle like sealed trait....)
[*] - SelfRef
[*] - Tuple
[*] - Unknown (throw exception)
[*] - Scala 2 (throw exception)
[*] - TypeSymbol (throw exception)
[*] - Value class
[ ] -- correct all the 'if $aE == null...'
[ ] -- type hint label mapping
[ ] -- type hint value mapping
[*] -- correct all the 'if $aE == null...'
[*] -- type hint label mapping
[*] -- type hint value mapping
[*] -- Discontinue use of inTermsOf "open" (non-sealed) trait support (?!)
[*] -- update runtime-size TraitRType handling to match new compile-time code
[ ] -- Streaming JSON write support
[ ] -- BigJSON support (eg. multi-gig file)
*/

object JsonWriter:
Expand All @@ -45,6 +51,7 @@ object JsonWriter:
def isOkToWrite(a: Any, cfg: JsonConfig) =
a match
case None if !cfg.noneAsNull => false
case o: java.util.Optional[?] if o.isEmpty && !cfg.noneAsNull => false
case Left(None) if !cfg.noneAsNull => false
case Right(None) if !cfg.noneAsNull => false
case Failure(_) if cfg.tryFailureHandling == TryOption.NO_WRITE => false
Expand All @@ -62,15 +69,16 @@ object JsonWriter:
ref match
case t: PrimitiveRef[?] if t.family == PrimFamily.Stringish => '{ if $aE == null then $sbE.append("null") else $sbE.append("\"" + $aE.toString + "\"") }
case t: PrimitiveRef[?] =>
val isNullable = Expr(t.isNullable)
if isMapKey then
'{
if $aE == null then $sbE.append("\"null\"")
if $isNullable && $aE == null then $sbE.append("\"null\"")
else
$sbE.append('"')
$sbE.append($aE.toString)
$sbE.append('"')
}
else '{ if $aE == null then $sbE.append("null") else $sbE.append($aE.toString) }
else '{ if $isNullable && $aE == null then $sbE.append("null") else $sbE.append($aE.toString) }

case t: SeqRef[?] =>
if isMapKey then throw new JsonError("Seq instances cannot be map keys")
Expand Down Expand Up @@ -117,8 +125,44 @@ object JsonWriter:
else sb.setCharAt(sb.length() - 1, ']')
}

case t: ClassRef[?] =>
case t: ScalaClassRef[?] if t.isSealed && t.isAbstractClass =>
classesSeen.put(t.typedName, t)
if t.childrenAreObject then
// case object -> just write the simple name of the object
'{
if $aE == null then $sbE.append("null")
else
$sbE.append('"')
$sbE.append(lastPart($aE.getClass.getName))
$sbE.append('"')
}
else
// Wow! If this is a sealed trait, all the children already have correctly-typed parameters--no need for expensive inTermsOf()
val rt = t.expr.asInstanceOf[Expr[ScalaClassRType[T]]]
'{
if $aE == null then $sbE.append("null")
else
val className = $aE.getClass.getName
$rt.sealedChildren
.find(_.name == className)
.map(foundKid =>
val augmented = foundKid.asInstanceOf[ScalaClassRType[foundKid.T]].copy(renderTrait = Some($rt.name)).asInstanceOf[RType[foundKid.T]]
JsonWriterRT.refWriteRT[foundKid.T]($cfgE, augmented, $aE.asInstanceOf[foundKid.T], $sbE)(using scala.collection.mutable.Map.empty[TypedName, RType[?]])
)
.getOrElse(throw new JsonError(s"Unrecognized child $className of seald trait " + $rt.name))
}

case t: ScalaClassRef[?] if t.isValueClass =>
val theField = t.fields.head.fieldRef
theField.refType match
case '[e] =>
val fieldValue = Select.unique(aE.asTerm, t.fields.head.name).asExprOf[e]
refWrite[e](cfgE, theField.asInstanceOf[RTypeRef[e]], fieldValue, sbE)

case t: ScalaClassRef[?] =>
if t.isAbstractClass then throw new JsonError("Cannot serialize an abstract class")
classesSeen.put(t.typedName, t)
val isCase = Expr(t.isCaseClass)
'{
val sb = $sbE
if $aE == null then sb.append("null")
Expand All @@ -144,6 +188,26 @@ object JsonWriter:
}
}
}
if ! $isCase && $cfgE.writeNonConstructorFields then
${
t.nonConstructorFields.foldLeft('{ sb }) { (accE, f) =>
f.fieldRef.refType match
case '[e] =>
val fieldValue = Select.unique(aE.asTerm, f.getterLabel).asExprOf[e]
val name = Expr(f.name)
'{
val acc = $accE
if isOkToWrite($fieldValue, $cfgE) then
acc.append('"')
acc.append($name)
acc.append('"')
acc.append(':')
${ refWrite[e](cfgE, f.fieldRef.asInstanceOf[RTypeRef[e]], fieldValue, '{ acc }) }
acc.append(',')
else acc
}
}
}
if sbLen == sb.length then sb.append('}')
else sb.setCharAt(sb.length() - 1, '}')
}
Expand All @@ -154,45 +218,53 @@ object JsonWriter:
if t.childrenAreObject then
// case object -> just write the simple name of the object
'{
$sbE.append('"')
$sbE.append(lastPart($aE.getClass.getName))
$sbE.append('"')
if $aE == null then $sbE.append("null")
else
$sbE.append('"')
$sbE.append(lastPart($aE.getClass.getName))
$sbE.append('"')
}
else if t.isSealed then
// Wow! If this is a sealed trait, all the children already have correctly-typed parameters--no need for expensive inTermsOf()
'{
val className = $aE.getClass.getName
$rt.sealedChildren
.find(_.name == className)
.map(foundKid =>
val augmented = foundKid.asInstanceOf[ScalaClassRType[foundKid.T]].copy(renderTrait = Some($rt.name)).asInstanceOf[RType[foundKid.T]]
JsonWriterRT.refWriteRT[foundKid.T]($cfgE, augmented, $aE.asInstanceOf[foundKid.T], $sbE)(using scala.collection.mutable.Map.empty[TypedName, RType[?]])
)
.getOrElse(throw new JsonError(s"Unrecognized child $className of seald trait " + $rt.name))
}
else
'{
given Compiler = Compiler.make($aE.getClass.getClassLoader)
val fn = (q: Quotes) ?=> {
import q.reflect.*
val sb = $sbE
val classRType = RType.inTermsOf[T]($aE.getClass).asInstanceOf[ScalaClassRType[T]].copy(renderTrait = Some($rt.name)).asInstanceOf[RType[T]]
JsonWriterRT.refWriteRT[classRType.T]($cfgE, classRType, $aE.asInstanceOf[classRType.T], $sbE)(using scala.collection.mutable.Map.empty[TypedName, RType[?]])
Expr(1) // do-nothing... '{} requires Expr(something) be returned, so...
}
quoted.staging.run(fn)
$sbE
if $aE == null then $sbE.append("null")
else
val className = $aE.getClass.getName
$rt.sealedChildren
.find(_.name == className)
.map(foundKid =>
val augmented = foundKid.asInstanceOf[ScalaClassRType[foundKid.T]].copy(renderTrait = Some($rt.name)).asInstanceOf[RType[foundKid.T]]
JsonWriterRT.refWriteRT[foundKid.T]($cfgE, augmented, $aE.asInstanceOf[foundKid.T], $sbE)(using scala.collection.mutable.Map.empty[TypedName, RType[?]])
)
.getOrElse(throw new JsonError(s"Unrecognized child $className of seald trait " + $rt.name))
}
else throw new JsonError("non-sealed traits are not supported")
// '{
// if $aE == null then $sbE.append("null")
// else
// given Compiler = Compiler.make($aE.getClass.getClassLoader)
// val fn = (q: Quotes) ?=> {
// import q.reflect.*
// val sb = $sbE
// val classRType = RType.inTermsOf[T]($aE.getClass).asInstanceOf[ScalaClassRType[T]].copy(renderTrait = Some($rt.name)).asInstanceOf[RType[T]]
// JsonWriterRT.refWriteRT[classRType.T]($cfgE, classRType, $aE.asInstanceOf[classRType.T], $sbE)(using scala.collection.mutable.Map.empty[TypedName, RType[?]])
// Expr(1) // do-nothing... '{} requires Expr(something) be returned, so...
// }
// quoted.staging.run(fn)
// $sbE
// }

case t: OptionRef[?] =>
if isMapKey then throw new JsonError("Option valuess cannot be map keys")
t.optionParamType.refType match
case '[e] =>
'{
$aE match
case None => $sbE.append("null")
case Some(v) =>
${ refWrite[e](cfgE, t.optionParamType.asInstanceOf[RTypeRef[e]], '{ v }.asInstanceOf[Expr[e]], sbE) }
if $aE == null then $sbE.append("null")
else
$aE match
case None => $sbE.append("null")
case Some(v) =>
${ refWrite[e](cfgE, t.optionParamType.asInstanceOf[RTypeRef[e]], '{ v }.asInstanceOf[Expr[e]], sbE) }
}

case t: MapRef[?] =>
Expand Down Expand Up @@ -258,17 +330,19 @@ object JsonWriter:
t.tryRef.refType match
case '[e] =>
'{
$aE match
case Success(v) =>
${ refWrite[e](cfgE, t.tryRef.asInstanceOf[RTypeRef[e]], '{ v }.asInstanceOf[Expr[e]], sbE) }
case Failure(_) if $cfgE.tryFailureHandling == TryOption.AS_NULL => $sbE.append("null")
case Failure(_) if $cfgE.tryFailureHandling == TryOption.AS_NULL => $sbE.append("null")
case Failure(f) if $cfgE.tryFailureHandling == TryOption.THROW_EXCEPTION =>
throw new JsonError("A try value was Failure with message: " + f.getMessage())
case Failure(v) =>
$sbE.append("\"Failure(")
$sbE.append(v.getMessage)
$sbE.append(")\"")
if $aE == null then $sbE.append("null")
else
$aE match
case Success(v) =>
${ refWrite[e](cfgE, t.tryRef.asInstanceOf[RTypeRef[e]], '{ v }.asInstanceOf[Expr[e]], sbE) }
case Failure(_) if $cfgE.tryFailureHandling == TryOption.AS_NULL => $sbE.append("null")
case Failure(_) if $cfgE.tryFailureHandling == TryOption.AS_NULL => $sbE.append("null")
case Failure(f) if $cfgE.tryFailureHandling == TryOption.THROW_EXCEPTION =>
throw new JsonError("A try value was Failure with message: " + f.getMessage())
case Failure(v) =>
$sbE.append("\"Failure(")
$sbE.append(v.getMessage)
$sbE.append(")\"")
}

case t: TupleRef[?] =>
Expand Down Expand Up @@ -355,24 +429,34 @@ object JsonWriter:
val enumE = t.expr
val isMapKeyE = Expr(isMapKey)
'{
val enumRT = $enumE.asInstanceOf[EnumRType[T]]
val enumAsId = $cfgE.enumsAsIds match
case '*' => true
case aList: List[String] if aList.contains(enumRT.name) => true
case _ => false
if enumAsId then
val enumVal = enumRT.ordinal($aE.toString).getOrElse(throw new JsonError("Value " + $aE.toString + s" is not a valid enum value for ${enumRT.name}"))
if $isMapKeyE then
if $aE == null then $sbE.append("null")
else
val enumRT = $enumE.asInstanceOf[EnumRType[T]]
val enumAsId = $cfgE.enumsAsIds match
case '*' => true
case aList: List[String] if aList.contains(enumRT.name) => true
case _ => false
if enumAsId then
val enumVal = enumRT.ordinal($aE.toString).getOrElse(throw new JsonError("Value " + $aE.toString + s" is not a valid enum value for ${enumRT.name}"))
if $isMapKeyE then
$sbE.append('"')
$sbE.append(enumVal.toString)
$sbE.append('"')
else $sbE.append(enumVal.toString)
else
$sbE.append('"')
$sbE.append(enumVal.toString)
$sbE.append($aE.toString)
$sbE.append('"')
else $sbE.append(enumVal.toString)
else
$sbE.append('"')
$sbE.append($aE.toString)
$sbE.append('"')
}

// Just handle Java classes 100% runtime since we need to leverage Java reflection entirely anyway
case t: JavaClassRef[?] =>
t.refType match
case '[e] =>
'{
JsonWriterRT.refWriteRT[e]($cfgE, ${ t.expr }.asInstanceOf[RType[e]], $aE.asInstanceOf[e], $sbE)(using scala.collection.mutable.Map.empty[TypedName, RType[?]])
}

case t: ObjectRef =>
val tname = Expr(t.name)
'{
Expand All @@ -385,7 +469,7 @@ object JsonWriter:
$cfgE.undefinedFieldHandling match
case UndefinedValueOption.AS_NULL => $sbE.append("null")
case UndefinedValueOption.AS_SYMBOL => $sbE.append("\"" + $tname + "\"")
case UndefinedValueOption.THROW_EXCEPTION => throw new JsonError("Value " + $aE.toString + " is of some unknown/unsupported Scala 2 type " + $tname)
case UndefinedValueOption.THROW_EXCEPTION => throw new JsonError("Unknown/unsupported Scala 2 type " + $tname)
}

case t: UnknownRef[?] =>
Expand All @@ -394,7 +478,7 @@ object JsonWriter:
$cfgE.undefinedFieldHandling match
case UndefinedValueOption.AS_NULL => $sbE.append("null")
case UndefinedValueOption.AS_SYMBOL => $sbE.append("\"" + $tname + "\"")
case UndefinedValueOption.THROW_EXCEPTION => throw new JsonError("Value " + $aE.toString + " is of some unknown/unsupported type " + $tname)
case UndefinedValueOption.THROW_EXCEPTION => throw new JsonError("Unknown/unsupported type " + $tname)
}

case t: TypeSymbolRef =>
Expand All @@ -403,5 +487,5 @@ object JsonWriter:
$cfgE.undefinedFieldHandling match
case UndefinedValueOption.AS_NULL => $sbE.append("null")
case UndefinedValueOption.AS_SYMBOL => $sbE.append("\"" + $tname + "\"")
case UndefinedValueOption.THROW_EXCEPTION => throw new JsonError("Value " + $aE.toString + " is of some unknown/unsupported type " + $tname + ". (Class didn't fully define all its type parameters.)")
case UndefinedValueOption.THROW_EXCEPTION => throw new JsonError("Unknown/unsupported type " + $tname + ". (Class didn't fully define all its type parameters.)")
}
Loading

0 comments on commit e48b358

Please sign in to comment.