All Downloads are FREE. Search and download functionalities are using the official Maven repository.

core_pure_changetoken.diff_generation.pure Maven / Gradle / Ivy

The newest version!
// Copyright 2024 Goldman Sachs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import meta::json::*;
import meta::pure::changetoken::*;
import meta::pure::changetoken::cast_generation::expand::*;
import meta::pure::changetoken::diff_generation::*;
import meta::pure::functions::hash::*;
import meta::pure::metamodel::serialization::grammar::*;

function meta::pure::changetoken::diff_generation::generateDiffFromVersions(versions: Versions[1], propertyRenames: Map>[0..1], classRenames: Map[0..1], defaultValues: Map>[0..1], typeKeyName: String[1], versionKeyName: String[0..1]): Versions[1]
{
    $versions->generateDiffFromVersionsAndScopes('new::entities::vX_X_X', 'old::entities::vX_X_X', $propertyRenames, $classRenames, $defaultValues, $typeKeyName, $versionKeyName);
}

function meta::pure::changetoken::diff_generation::generateDiffFromVersionsAndScopes(versions: Versions[1], newScope: String[1], oldScope: String[1], propertyRenames: Map>[0..1], classRenames: Map[0..1], defaultValues: Map>[0..1], typeKeyName: String[1], versionKeyName: String[0..1]): Versions[1]
{
    let newClasses = getClasses($newScope);
    let oldClasses = getClasses($oldScope);
    generateDiffFromVersionsAndClasses($versions, $newClasses, $oldClasses, $propertyRenames, $classRenames, $defaultValues, $typeKeyName, $versionKeyName);
}

function <> meta::pure::changetoken::diff_generation::getClasses(scope: String[1]): Class[*]
{
    let entities = $scope->pathToPackage(true);
    $entities->packageClassesRecursive();
}

function meta::pure::changetoken::diff_generation::generateDiffFromVersionsAndClasses(versions: Versions[1], newClasses: Class[*], oldClasses: Class[*], propertyRenames: Map>[0..1], classRenames: Map[0..1], defaultValues: Map>[0..1], typeKeyName: String[1], versionKeyName: String[0..1]): Versions[1]
{
    let prevVersion = if($versions.versions->size() > 0, | $versions.versions->at($versions.versions->size() - 1).version, | []);
    let version = ^Version(
        version = getHash($prevVersion, getHashTokens($newClasses, $versionKeyName)),
        prevVersion = $prevVersion,
        changeTokens = getChangeTokens($newClasses, $oldClasses, $propertyRenames, $classRenames, $defaultValues, $typeKeyName, $versionKeyName)
    );
    ^Versions(versions = concatenate($versions.versions, [$version]));
}

function <> meta::pure::changetoken::diff_generation::getHash(prevVersion: String[0..1], tokens: String[*]): String[1]
{
    hash($prevVersion->concatenate($tokens)->joinStrings(), HashType.SHA1);
}

function meta::pure::changetoken::diff_generation::getHashTokens(classes: Class[*], versionKeyName: String[0..1]): String[*]
{
    $classes
    ->sort({x, y | stripedPaths($x, $y)})
    ->map(c | stripPath($c)->concatenate($c.properties
    ->filter(p | $p.name != $versionKeyName)
    ->sort({x, y | $x.name->toOne()->compare($y.name->toOne())})
    ->map(p | $p.name->toOne()->concatenate(stripVersionQualifier(printGenericType($p.genericType)) + '[' + printMultiplicity($p.multiplicity) + ']'))));
}

function <> meta::pure::changetoken::diff_generation::stripPath(element: PackageableElement[1]): String[1]
{
    stripVersionQualifier(elementToPath($element));
}

function <> meta::pure::changetoken::diff_generation::stripPaths(elements: PackageableElement[*]): String[*]
{
    $elements->map(m | $m->stripPath());
}

function <> meta::pure::changetoken::diff_generation::stripedPaths(x: PackageableElement[1], y: PackageableElement[1]): Integer[1]
{
    compare(stripPath($x), stripPath($y));
}

function meta::pure::changetoken::diff_generation::stripVersionQualifier(path: String[1]): String[1]
{
    if($path->matches('.*v[^_]+_[^_]+_[^_]+.*'), | $path->split('::')->fold({s: String[1], r: String[*] | if($s->matches('^v[^_]+_[^_]+_[^_]+$')->and($r->size() >= 2), | $r->take($r->size() - 2), | $r->concatenate($s))}, [])->joinStrings('::'), | $path);
}

function <> meta::pure::changetoken::diff_generation::getChangeTokens(newClasses: Class[*], oldClasses: Class[*], propertyRenames: Map>[0..1], classRenames: Map[0..1], defaultValues: Map>[0..1], typeKeyName: String[1], versionKeyName: String[0..1]): ChangeToken[*]
{
    let classRenamesReverse = $classRenames->map(m | flipMap($m));
    let propertyRenamesReverse = $propertyRenames->map(m | flipNestedMaps($m));
    concatenate(mergeChangeTokens(getAddedClass($classRenamesReverse, $newClasses, $oldClasses), getAddField($propertyRenamesReverse, $classRenamesReverse, $defaultValues, $newClasses, $oldClasses, $typeKeyName, $versionKeyName)),
        mergeChangeTokens(getRemovedClass($classRenames, $newClasses, $oldClasses), getRemoveField($propertyRenames, $classRenames, $defaultValues, $newClasses, $oldClasses, $typeKeyName, $versionKeyName)))
    ->concatenate(getRenameField($propertyRenames, $oldClasses, $versionKeyName))
    ->concatenate(getRenamedClass($classRenames, $oldClasses));
}

function <> meta::pure::changetoken::diff_generation::flipMap(m: Map[1]): Map[1]
{
    newMap($m->keys()->map(k | pair($m->get($k)->toOne(), $k)));
}

function <> meta::pure::changetoken::diff_generation::flipNestedMaps(m: Map>[1]): Map>[1]
{
    newMap($m->keys()->map(k | pair($k, $m->get($k)->toOne()->flipMap())));
}

function <> meta::pure::changetoken::diff_generation::mergeChangeTokens(x: ChangeToken[*], y: ChangeToken[*]): ChangeToken[*]
{
    $x->concatenate($y)->sort({x, y | $x->cast(@ClassChangeToken).class->compare($y->cast(@ClassChangeToken).class)});
}

function <> meta::pure::changetoken::diff_generation::getAddedClass(classRenames: Map[0..1], newClasses: Class[*], oldClasses: Class[*]): ChangeToken[*]
{
    $newClasses
    ->filter(c | not(contains(stripPaths($oldClasses), lookupOrDefault($classRenames, stripPath($c)))))
    ->sort({x, y | stripedPaths($x, $y)})
    ->map(c | ^AddedClass(
            class = stripPath($c)
    ));
}

function <> meta::pure::changetoken::diff_generation::lookupOrDefault(m: Map[0..1], v: T[1]): T[1]
{
    if($m->isEmpty()->not()
    ->and($m->toOne()->keys()->contains($v)),
    | $m->toOne()->get($v)->toOne(),
    | $v);
}

function <> meta::pure::changetoken::diff_generation::lookupOrDefault(m: Map>[0..1], k: K[1], v: V[1]): V[1]
{
    if($m->isEmpty()->not()
    ->and($m->toOne()->keys()->contains($k))
    ->and($m->toOne()->get($k)->toOne()->keys()->contains($v)),
    | $m->toOne()->get($k)->toOne()->get($v)->toOne(),
    | $v);
}

function <> meta::pure::changetoken::diff_generation::lookupOrAny(m: Map>[0..1], k: K[1], v: V[1], a: Any[1], property: Property[1], typeKeyName: String[1], versionKeyName: String[0..1]): Any[1]
{
    if($m->isEmpty()->not()
    ->and($m->toOne()->keys()->contains($k))
    ->and($m->toOne()->get($k)->toOne()->keys()->contains($v)),
    | $m->toOne()->get($k)->toOne()->get($v)->serializeAny($property, $typeKeyName, $versionKeyName),
    | $a);
}

function <> meta::pure::changetoken::diff_generation::getAddField(renamedProperties: Map>[0..1], classRenames: Map[0..1], defaultValues: Map>[0..1], newClasses: Class[*], oldClasses: Class[*], typeKeyName: String[1], versionKeyName: String[0..1]): ChangeToken[*]
{
    $newClasses
    ->map(c | $c.properties->map(p | pair($c, $p)))
    ->filter(cp | $oldClasses->map(c | $c.properties->map(p | pair(stripPath($c), $p.name->toOne())))->contains(pair(lookupOrDefault($classRenames, stripPath($cp.first)),
    lookupOrDefault($renamedProperties, lookupOrDefault($classRenames, stripPath($cp.first)), $cp.second.name->toOne())))->not())
    ->filter(cp | $cp.second.name != $versionKeyName)
    ->sort({x, y | compare($x.second.name->toOne(), $y.second.name->toOne())})
    ->sort({x, y | stripedPaths($x.first, $y.first)})
    ->map(cp | ^AddField(
            class = stripPath($cp.first),
            fieldName = $cp.second.name->toOne(),
            fieldType = stripVersionQualifier(printGenericType($cp.second.genericType)) + '[' + printMultiplicity($cp.second.multiplicity) + ']',
            safeCast = true,
            defaultValue = ^ConstValue(value = lookupOrAny($defaultValues, stripPath($cp.first), $cp.second.name->toOne(), if($cp.second.defaultValue->isEmpty(), | generateDefault($cp.second, $typeKeyName, $versionKeyName), | $cp.second.defaultValue.functionDefinition.expressionSequence->evaluateAndDeactivate()->serializeAny($cp.second, $typeKeyName, $versionKeyName)), $cp.second, $typeKeyName, $versionKeyName))
    ));
}

function <> meta::pure::changetoken::diff_generation::getRemovedClass(classRenames: Map[0..1], newClasses: Class[*], oldClasses: Class[*]): ChangeToken[*]
{
    $oldClasses
    ->filter(c | not(contains(stripPaths($newClasses), lookupOrDefault($classRenames, stripPath($c)))))
    ->sort({x, y | stripedPaths($x, $y)})
    ->map(c | ^RemovedClass(
            class = stripPath($c)
    ));
}

function <> meta::pure::changetoken::diff_generation::getRemoveField(renamedProperties: Map>[0..1], classRenames: Map[0..1], defaultValues: Map>[0..1], newClasses: Class[*], oldClasses: Class[*], typeKeyName: String[1], versionKeyName: String[0..1]): ChangeToken[*]
{
    $oldClasses
    ->map(c | $c.properties->map(p | pair($c, $p)))
    ->filter(cp | $newClasses->map(c | $c.properties->map(p | pair(stripPath($c), $p.name->toOne())))->contains(pair(lookupOrDefault($classRenames, stripPath($cp.first)),
    lookupOrDefault($renamedProperties, stripPath($cp.first), $cp.second.name->toOne())))->not())
    ->filter(cp | $cp.second.name != $versionKeyName)
    ->sort({x, y | compare($x.second.name->toOne(), $y.second.name->toOne())})
    ->sort({x, y | stripedPaths($x.first, $y.first)})
    ->map(cp | ^RemoveField(
            class = stripPath($cp.first),
            fieldName = $cp.second.name->toOne(),
            fieldType = stripVersionQualifier(printGenericType($cp.second.genericType)) + '[' + printMultiplicity($cp.second.multiplicity) + ']',
            safeCast = true,
            defaultValue = ^ConstValue(value = lookupOrAny($defaultValues, stripPath($cp.first), $cp.second.name->toOne(), if($cp.second.defaultValue->isEmpty(), | generateDefault($cp.second, $typeKeyName, $versionKeyName), | $cp.second.defaultValue.functionDefinition->toOne()->cast(@Function<{->Any[1]}>)->eval()->serializeAny($cp.second, $typeKeyName, $versionKeyName)), $cp.second, $typeKeyName, $versionKeyName))
    ));
}

function <> meta::pure::changetoken::diff_generation::getRenameField(renamedProperties: Map>[0..1], oldClasses: Class[*], versionKeyName: String[0..1]): ChangeToken[*]
{
    if($renamedProperties->isEmpty(), | [], |
    $oldClasses
    ->map(c | $c.properties->map(p | pair($c, $p)))
    ->filter(cp | $renamedProperties->toOne()->keys()->contains(stripPath($cp.first))->and($renamedProperties->toOne()->get(stripPath($cp.first))->toOne()->keys()->contains($cp.second.name->toOne())))
    ->filter(cp | $cp.second.name != $versionKeyName)
    ->sort({x, y | compare($x.second.name->toOne(), $y.second.name->toOne())})
    ->sort({x, y | stripedPaths($x.first, $y.first)})
    ->map(cp | ^RenameField(
            class = stripPath($cp.first),
            oldFieldName = $cp.second.name->toOne(),
            newFieldName = $renamedProperties->toOne()->get(stripPath($cp.first))->toOne()->get($cp.second.name->toOne())->toOne()
    )));
}

function <> meta::pure::changetoken::diff_generation::getRenamedClass(renamedClasses: Map[0..1], oldClasses: Class[*]): ChangeToken[*]
{
    if($renamedClasses->isEmpty(), | [], |
    $oldClasses
    ->filter(c | $renamedClasses->toOne()->keys()->contains(stripPath($c)))
    ->sort({x, y | stripedPaths($x, $y)})
    ->map(c | ^RenamedClass(
        class = stripPath($c),
        newName = $renamedClasses->toOne()->get(stripPath($c))->toOne()
    )));
}

function <> meta::pure::changetoken::diff_generation::serializeAny(any: Any[*], property: Property[0..1], typeKeyName: String[1], versionKeyName: String[0..1]): JSONElement[1]
{
    let values = $any->map(i | $i->match([
        i: InstanceValue[1] | serializeAny($i.values, [], $typeKeyName, $versionKeyName),
        s: SimpleFunctionExpression[1] | $s.genericType.rawType->match([
          p: PrimitiveType[1] | if($s.functionName != 'minus', | fail('Not supported: ' + $s.functionName->toOne())->cast(@JSONElement), | serializeAny(minus($s.parametersValues->at(0)->cast(@InstanceValue).values->cast(@Number)), [], $typeKeyName, $versionKeyName)),
          en: Enumeration[1] | if($s.functionName != 'extractEnumValue', | fail('Not supported: ' + $s.functionName->toOne())->cast(@JSONElement), | let e = $en->enumValues()->filter(v:Enum[1]| $v->id()==$s.parametersValues->at(1)->cast(@InstanceValue).values->cast(@String))->toOne()->cast(@Enum); ^JSONString(value = stripVersionQualifier($e->type()->toOne()->elementToPath() + '.' + $e->id()));),
          c: Class[1] | if($s.functionName != 'new', | fail('Not supported: ' + $s.functionName->toOne())->cast(@JSONElement), | newJSONObject([newJSONKeyValue($typeKeyName, ^JSONString(value = stripVersionQualifier($c->elementToPath()->toString())))]
          ->concatenate($s.parametersValues->at(2)->cast(@InstanceValue).values->cast(@KeyExpression)->map(x | newJSONKeyValue($x.key->cast(@InstanceValue).values->toOne()->toString(), serializeAny($x.expression, $c.properties->filter(p | $p.name == $x.key->cast(@InstanceValue).values->toOne()->toString())->toOne()->cast(@Property), $typeKeyName, $versionKeyName))))
          ->concatenate($c.properties->filter(p | $p.name != $versionKeyName)
            ->filter(p | $s.parametersValues->at(2)->cast(@InstanceValue).values->cast(@KeyExpression)->map(x | $x.key->cast(@InstanceValue).values->toOne()->toString())->contains($p.name->toOne())->not())
            ->map(p | newJSONKeyValue($p.name->toOne(), generateDefault($p, $typeKeyName, $versionKeyName))))));
        ]),
        s: String[1] | ^JSONString(value = $s),
        d: Date[1] | ^JSONString(value = $d->toString()),
        d: DateTime[1] | ^JSONString(value = $d->toString()),
        d: StrictDate[1] | ^JSONString(value = $d->toString()),
        b: Boolean[1] | ^JSONBoolean(value = $b),
        i: Integer[1] | ^JSONNumber(value = $i),
        f: Float[1] | ^JSONNumber(value = $f),
        d: Decimal[1] | ^JSONNumber(value = $d),
        n: Number[1] | ^JSONNumber(value = $n),
        e: Enum[1] | ^JSONString(value = stripVersionQualifier($e->type()->toOne()->elementToPath() + '.' + $e->id())),
        m: Map[1] | $m->expandValue($typeKeyName),
        l: List[1] | $l->expandValue($typeKeyName)->cast(@JSONArray).values,
        a: Any[1] | let c = $a->class(); newJSONObject([newJSONKeyValue($typeKeyName, ^JSONString(value = stripVersionQualifier($c->elementToPath()->toString())))]->concatenate($c.properties->filter(p | $p.name != $versionKeyName)->map(p | newJSONKeyValue($p.name->toOne(), serializeAny($p->eval($a), $p, $typeKeyName, $versionKeyName)))));
    ]));
    let ret = if($property != [], | if($property.multiplicity == PureOne || $property.multiplicity == PureZero || $property.multiplicity == ZeroOne, | if($property.multiplicity == PureZero || $property.multiplicity == ZeroOne && $values->size() == 0, | ^JSONNull(), | $values->toOne('Expecting one for property')),
     | if($values->size() == 1 && $values->toOne()->instanceOf(JSONArray), | $values, | ^JSONArray(values = $values))),
     | if($values->size() == 1, | $values->toOne(), | ^JSONArray(values = $values)));
    $ret->toOne();
}

function <> meta::pure::changetoken::diff_generation::generateDefault(property: Property[1], typeKeyName: String[1], versionKeyName: String[0..1]): JSONElement[1]
{
    if($property.multiplicity->getLowerBound() == 0, | if($property.multiplicity == PureZero || $property.multiplicity == ZeroOne, | ^JSONNull(), | ^JSONArray()), | meta::pure::changetoken::diff_generation::multiply(
        $property.genericType.rawType->match([
            p: PrimitiveType[1] | $p->primitiveType(),
            en: Enumeration[1] | if($en->enumValues()->size() == 0, | fail($en->toString() + ' has 0 values'); ^JSONNull();, | let e = $en->enumValues()->at(0); let value = $e->type()->toOne()->elementToPath() + '.' + $e->id(); ^JSONString(value = stripVersionQualifier($value));),
            c: Class[1] | newJSONObject([newJSONKeyValue($typeKeyName, ^JSONString(value = stripVersionQualifier($c->elementToPath()->toString())))]->concatenate($c.properties->filter(p | $p.name != $versionKeyName)->map(p | newJSONKeyValue($p.name->toOne(), generateDefault($p, $typeKeyName, $versionKeyName)))))
        ]), $property.multiplicity->getLowerBound(), $property.multiplicity == PureOne));
}

function <> meta::pure::changetoken::diff_generation::primitiveType(type: PrimitiveType[1]): JSONElement[1]
{
    [
        pair(String, ^JSONString(value = '')),
        pair(Date, ^JSONString(value = toString(%1970-01-01T00:00:00.000+0000))),
        pair(DateTime, ^JSONString(value = 0->fromEpochValue()->toString())),
        pair(StrictDate, ^JSONString(value = toString(%1970-01-01))),
        pair(Boolean, ^JSONBoolean(value = false)),
        pair(Integer, ^JSONNumber(value = 0)),
        pair(Float, ^JSONNumber(value = 0.0)),
        pair(Decimal, ^JSONNumber(value = 0d)),
        pair(Number, ^JSONNumber(value = 0))
    ]->filter(t | $t.first == $type).second->toOne('Cannot find type match for primitive type: ' + $type.name->toOne());
}

function <> meta::pure::changetoken::diff_generation::multiply(instance: JSONElement[1], instances: Integer[1], single: Boolean[1]): JSONElement[1]
{
    if($single, | $instance, | ^JSONArray(values = $instances->range()->map(i | $instance)));
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy