Skip to content

Commit

Permalink
@SchemaSwaps annotation can now be repeated (WIP). fixes fabric8io#4350
Browse files Browse the repository at this point in the history
  • Loading branch information
xRodney committed Aug 25, 2022
1 parent 16f4521 commit 2403614
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 57 deletions.
Expand Up @@ -17,6 +17,7 @@

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;

import io.fabric8.crd.generator.annotation.SchemaSwap;
import io.fabric8.crd.generator.utils.Types;
import io.fabric8.kubernetes.api.model.Duration;
Expand Down Expand Up @@ -81,6 +82,7 @@ public abstract class AbstractJsonSchema<T, B> {
public static final String ANNOTATION_NOT_NULL = "javax.validation.constraints.NotNull";
public static final String ANNOTATION_SCHEMA_FROM = "io.fabric8.crd.generator.annotation.SchemaFrom";
public static final String ANNOTATION_SCHEMA_SWAP = "io.fabric8.crd.generator.annotation.SchemaSwap";
public static final String ANNOTATION_SCHEMA_SWAPS = "io.fabric8.crd.generator.annotation.SchemaSwaps";

public static final String JSON_NODE_TYPE = "com.fasterxml.jackson.databind.JsonNode";

Expand Down Expand Up @@ -119,9 +121,9 @@ public static String getSchemaTypeFor(TypeRef typeRef) {
* @return The schema.
*/
protected T internalFrom(TypeDef definition, String... ignore) {
List<InternalSchemaSwap> schemaSwaps = new ArrayList<>();
InternalSchemaSwaps schemaSwaps = new InternalSchemaSwaps();
T ret = internalFromImpl(definition, new HashSet<>(), schemaSwaps, ignore);
validateRemainingSchemaSwaps("unmatched class", schemaSwaps);
validateRemainingSchemaSwaps(schemaSwaps);
return ret;
}

Expand Down Expand Up @@ -187,25 +189,51 @@ private static ClassRef extractClassRef(Object type) {
}
}

private InternalSchemaSwap extractSchemaSwap(AnnotationRef annotation) {
private void extractSchemaSwaps(ClassRef definitionType, AnnotationRef annotation, InternalSchemaSwaps schemaSwaps) {
String fullyQualifiedName = annotation.getClassRef().getFullyQualifiedName();
switch (fullyQualifiedName) {
case ANNOTATION_SCHEMA_SWAP:
extractSchemaSwap(definitionType, annotation, schemaSwaps);
break;
case ANNOTATION_SCHEMA_SWAPS:
Map<String, Object> params = annotation.getParameters();
Object[] values = (Object[]) params.get("value");
if (values instanceof SchemaSwap[]) {
for (SchemaSwap value : (SchemaSwap[]) values) {
extractSchemaSwap(definitionType, value, schemaSwaps);
}
}
// for (AnnotationRef value : values) {
// extractSchemaSwap(definitionType, value, schemaSwaps);
// }
break;
}
}

private void extractSchemaSwap(ClassRef definitionType, AnnotationRef annotation, InternalSchemaSwaps schemaSwaps) {
Map<String, Object> params = annotation.getParameters();
return new InternalSchemaSwap(
extractClassRef(params.get("originalType")),
(String) params.get("fieldName"),
extractClassRef(params.get("targetType")));
schemaSwaps.registerSwap(definitionType,
extractClassRef(params.get("originalType")),
(String) params.get("fieldName"),
extractClassRef(params.get("targetType")));
}
private void extractSchemaSwap(ClassRef definitionType, SchemaSwap annotation, InternalSchemaSwaps schemaSwaps) {
schemaSwaps.registerSwap(definitionType,
extractClassRef(annotation.originalType()),
annotation.fieldName(),
extractClassRef(annotation.targetType()));
}

private void validateRemainingSchemaSwaps(String error, List<InternalSchemaSwap> schemaSwaps) {
if (!schemaSwaps.isEmpty()) {
String umatchedSchemaSwaps = schemaSwaps
.stream()
.map(InternalSchemaSwap::toString)
.collect(Collectors.joining(",", "[", "]"));
throw new IllegalArgumentException("SchemaSwap annotation error " + error + ": " + umatchedSchemaSwaps);
private void validateRemainingSchemaSwaps(InternalSchemaSwaps schemaSwaps) {
String unmatchedSchemaSwaps = schemaSwaps.getUnusedSwaps()
.map(Object::toString)
.collect(Collectors.joining(","));
if (!unmatchedSchemaSwaps.isEmpty()) {
throw new IllegalArgumentException("Unmatched SchemaSwaps: " + unmatchedSchemaSwaps);
}
}

private T internalFromImpl(TypeDef definition, Set<String> visited, List<InternalSchemaSwap> schemaSwaps, String... ignore) {
private T internalFromImpl(TypeDef definition, Set<String> visited, InternalSchemaSwaps schemaSwaps, String... ignore) {
final B builder = newBuilder();
Set<String> ignores = ignore.length > 0 ? new LinkedHashSet<>(Arrays.asList(ignore))
: Collections
Expand All @@ -215,19 +243,7 @@ private T internalFromImpl(TypeDef definition, Set<String> visited, List<Interna
boolean preserveUnknownFields = (definition.getFullyQualifiedName() != null &&
definition.getFullyQualifiedName().equals(JSON_NODE_TYPE));

List<InternalSchemaSwap> newSchemaSwaps = definition
.getAnnotations()
.stream()
.filter(a -> a.getClassRef().getFullyQualifiedName().equals(ANNOTATION_SCHEMA_SWAP))
.map(this::extractSchemaSwap)
.collect(Collectors.toList());

schemaSwaps.addAll(newSchemaSwaps);

final Set<InternalSchemaSwap> currentSchemaSwaps = schemaSwaps
.stream()
.filter(iss -> iss.getOriginalType().getFullyQualifiedName().equals(definition.getFullyQualifiedName()))
.collect(Collectors.toSet());
definition.getAnnotations().forEach(annotation -> extractSchemaSwaps(definition.toReference(), annotation, schemaSwaps));

// index potential accessors by name for faster lookup
final Map<String, Method> accessors = indexPotentialAccessors(definition);
Expand All @@ -239,11 +255,9 @@ private T internalFromImpl(TypeDef definition, Set<String> visited, List<Interna
continue;
}

final PropertyFacade facade = new PropertyFacade(property, accessors, currentSchemaSwaps);
ClassRef potentialSchemaSwap = schemaSwaps.lookupAndMark(definition.toReference(), name).orElse(null);
final PropertyFacade facade = new PropertyFacade(property, accessors, potentialSchemaSwap);
final Property possiblyRenamedProperty = facade.process();
final Set<InternalSchemaSwap> matchedSchemaSwaps = facade.getMatchedSchemaSwaps();
currentSchemaSwaps.removeAll(matchedSchemaSwaps);
schemaSwaps.removeAll(matchedSchemaSwaps);
name = possiblyRenamedProperty.getName();

if (facade.required) {
Expand All @@ -267,7 +281,6 @@ private T internalFromImpl(TypeDef definition, Set<String> visited, List<Interna
addProperty(possiblyRenamedProperty, builder, possiblyUpdatedSchema);
}

validateRemainingSchemaSwaps("unmatched field", currentSchemaSwaps.stream().collect(Collectors.toList()));
return build(builder, required, preserveUnknownFields);
}

Expand Down Expand Up @@ -383,8 +396,6 @@ public String toString() {

private static class PropertyFacade {
private final List<PropertyOrAccessor> propertyOrAccessors = new ArrayList<>(4);
private final Set<InternalSchemaSwap> schemaSwaps;
private final Set<InternalSchemaSwap> matchedSchemaSwaps;
private String renamedTo;
private String description;
private boolean required;
Expand All @@ -395,10 +406,8 @@ private static class PropertyFacade {
private String descriptionContributedBy;
private TypeRef schemaFrom;

public PropertyFacade(Property property, Map<String, Method> potentialAccessors, Set<InternalSchemaSwap> schemaSwaps) {
public PropertyFacade(Property property, Map<String, Method> potentialAccessors, ClassRef schemaSwap) {
original = property;
this.schemaSwaps = schemaSwaps;
this.matchedSchemaSwaps = new HashSet<>();
final String capitalized = property.getNameCapitalized();
final String name = property.getName();
propertyOrAccessors.add(PropertyOrAccessor.fromProperty(property));
Expand All @@ -414,21 +423,12 @@ public PropertyFacade(Property property, Map<String, Method> potentialAccessors,
if (method != null) {
propertyOrAccessors.add(PropertyOrAccessor.fromMethod(method, name));
}
schemaFrom = schemaSwap;
}

public Property process() {
final String name = original.getName();

Optional<InternalSchemaSwap> currentSchemaSwap = schemaSwaps
.stream()
.filter(iss -> iss.getFieldName().equals(name))
.findFirst();

currentSchemaSwap.ifPresent(iss -> {
schemaFrom = iss.targetType;
matchedSchemaSwaps.add(iss);
});

propertyOrAccessors.forEach(p -> {
p.process();
final String contributorName = p.toString();
Expand Down Expand Up @@ -471,10 +471,6 @@ public Property process() {
return new Property(original.getAnnotations(), typeRef, finalName,
original.getComments(), original.getModifiers(), original.getAttributes());
}

public Set<InternalSchemaSwap> getMatchedSchemaSwaps() {
return this.matchedSchemaSwaps;
}
}

private boolean isPotentialAccessor(Method method) {
Expand Down Expand Up @@ -547,10 +543,10 @@ private String extractUpdatedNameFromJacksonPropertyIfPresent(Property property)
* @return the structural schema associated with the specified property
*/
public T internalFrom(String name, TypeRef typeRef) {
return internalFromImpl(name, typeRef, new HashSet<>(), new ArrayList<>());
return internalFromImpl(name, typeRef, new HashSet<>(), new InternalSchemaSwaps());
}

private T internalFromImpl(String name, TypeRef typeRef, Set<String> visited, List<InternalSchemaSwap> schemaSwaps) {
private T internalFromImpl(String name, TypeRef typeRef, Set<String> visited, InternalSchemaSwaps schemaSwaps) {
// Note that ordering of the checks here is meaningful: we need to check for complex types last
// in case some "complex" types are handled specifically
if (typeRef.getDimensions() > 0 || io.sundr.model.utils.Collections.isCollection(typeRef)) { // Handle Collections & Arrays
Expand Down Expand Up @@ -611,7 +607,7 @@ private T internalFromImpl(String name, TypeRef typeRef, Set<String> visited, Li
// Flag to detect cycles
private boolean resolving = false;

private T resolveNestedClass(String name, TypeDef def, Set<String> visited, List<InternalSchemaSwap> schemaSwaps) {
private T resolveNestedClass(String name, TypeDef def, Set<String> visited, InternalSchemaSwaps schemaSwaps) {
if (!resolving) {
visited.clear();
resolving = true;
Expand Down
@@ -0,0 +1,118 @@
package io.fabric8.crd.generator;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.stream.Stream;

import io.sundr.model.ClassRef;

public class InternalSchemaSwaps {
private final Map<Key, Value> swaps = new HashMap<>();

public void registerSwap(ClassRef definitionType, ClassRef originalType, String fieldName, ClassRef targetType) {
Value value = new Value(definitionType, originalType, fieldName, targetType);
swaps.put(new Key(originalType, fieldName), value);
}

public Stream<Value> getUnusedSwaps() {
return swaps.values().stream().filter(value -> !value.used);
}

public Optional<ClassRef> lookupAndMark(ClassRef originalType, String name) {
Value value = swaps.get(new Key(originalType, name));
if (value != null) {
value.markUsed();
return Optional.of(value.getTargetType());
} else {
return Optional.empty();
}
}

private static class Key {
private final ClassRef originalType;
private final String fieldName;


public Key(ClassRef originalType, String fieldName) {
this.originalType = originalType;
this.fieldName = fieldName;
}

public ClassRef getOriginalType() {
return originalType;
}

public String getFieldName() {
return fieldName;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Key key = (Key) o;
return Objects.equals(originalType, key.originalType) && Objects.equals(fieldName, key.fieldName);
}

@Override
public int hashCode() {
return Objects.hash(originalType, fieldName);
}

@Override
public String toString() {
return new StringJoiner(", ", Key.class.getSimpleName() + "[", "]")
.add("originalType=" + originalType)
.add("fieldName='" + fieldName + "'")
.toString();
}
}

public static class Value {
private final ClassRef originalType;
private final String fieldName;
private final ClassRef targetType;
private boolean used;
private final ClassRef definitionType;

public Value(ClassRef definitionType, ClassRef originalType, String fieldName, ClassRef targetType) {
this.definitionType = definitionType;
this.originalType = originalType;
this.fieldName = fieldName;
this.targetType = targetType;
this.used = false;
}

private void markUsed() {
this.used = true;
}

public ClassRef getOriginalType() {
return originalType;
}

public String getFieldName() {
return fieldName;
}

public ClassRef getTargetType() {
return targetType;
}

public boolean isUsed() {
return used;
}

@Override
public String toString() {
return "@SchemaSwap(originalType=" + originalType + ", fieldName=\"" + fieldName + "\", targetType="+targetType + ") on " + definitionType;
}
}
}
@@ -0,0 +1,25 @@
/**
* Copyright (C) 2015 Red Hat, Inc.
*
* 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.
*/
package io.fabric8.crd.example.extraction;

import io.fabric8.crd.generator.annotation.SchemaSwap;
import io.fabric8.kubernetes.client.CustomResource;

@SchemaSwap(originalType = SchemaSwapSpec.SomeObject.class, fieldName = "shouldBeString", targetType = String.class)
@SchemaSwap(originalType = SchemaSwapSpec.AnotherObject.class, fieldName = "shouldBeInt", targetType = Integer.class)
public class MultipleSchemaSwaps extends CustomResource<SchemaSwapSpec, Void> {

}
@@ -0,0 +1,30 @@
/**
* Copyright (C) 2015 Red Hat, Inc.
*
* 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.
*/
package io.fabric8.crd.example.extraction;

public class SchemaSwapSpec {
private SomeObject first;
private SomeObject second;
private AnotherObject third;

static class SomeObject {
private int shouldBeString;
}

static class AnotherObject {
private String shouldBeInt;
}
}

0 comments on commit 2403614

Please sign in to comment.