Skip to content

Commit

Permalink
fix fabric8io#5233: allowing schemaswap to have a configurable level
Browse files Browse the repository at this point in the history
  • Loading branch information
shawkins committed Jun 15, 2023
1 parent ba5a9d8 commit 4cd90d6
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#### Bugs

#### Improvements
* Fix #5233: Generalized SchemaSwap to allow for cycle expansion

#### Dependency Upgrade

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,15 +244,15 @@ private void extractSchemaSwap(ClassRef definitionType, Object annotation, Inter
schemaSwaps.registerSwap(definitionType,
extractClassRef(schemaSwap.originalType()),
schemaSwap.fieldName(),
extractClassRef(schemaSwap.targetType()));
extractClassRef(schemaSwap.targetType()), schemaSwap.depth());

} else if (annotation instanceof AnnotationRef
&& ((AnnotationRef) annotation).getClassRef().getFullyQualifiedName().equals(ANNOTATION_SCHEMA_SWAP)) {
Map<String, Object> params = ((AnnotationRef) annotation).getParameters();
schemaSwaps.registerSwap(definitionType,
extractClassRef(params.get("originalType")),
(String) params.get("fieldName"),
extractClassRef(params.getOrDefault("targetType", void.class)));
extractClassRef(params.getOrDefault("targetType", void.class)), (Integer) params.getOrDefault("depth", 0));

} else {
throw new IllegalArgumentException("Unmanaged annotation type passed to the SchemaSwaps: " + annotation);
Expand All @@ -273,19 +273,25 @@ private T internalFromImpl(TypeDef definition, Set<String> visited, InternalSche

boolean preserveUnknownFields = isJsonNode;

definition.getAnnotations().forEach(annotation -> extractSchemaSwaps(definition.toReference(), annotation, schemaSwaps));
schemaSwaps = schemaSwaps.branchAnnotations();
final InternalSchemaSwaps swaps = schemaSwaps;
definition.getAnnotations().forEach(annotation -> extractSchemaSwaps(definition.toReference(), annotation, swaps));

// index potential accessors by name for faster lookup
final Map<String, Method> accessors = indexPotentialAccessors(definition);

for (Property property : definition.getProperties()) {
if (isJsonNode) {
break;
}
String name = property.getName();
if (property.isStatic() || ignores.contains(name)) {
LOGGER.debug("Ignoring property {}", name);
continue;
}

ClassRef potentialSchemaSwap = schemaSwaps.lookupAndMark(definition.toReference(), name).orElse(null);
schemaSwaps = schemaSwaps.branchDepths();
ClassRef potentialSchemaSwap = schemaSwaps.lookupAndMark(definition.toReference(), name, visited::clear);
final PropertyFacade facade = new PropertyFacade(property, accessors, potentialSchemaSwap);
final Property possiblyRenamedProperty = facade.process();
name = possiblyRenamedProperty.getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,69 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.stream.Collectors;

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

public void registerSwap(ClassRef definitionType, ClassRef originalType, String fieldName, ClassRef targetType) {
Value value = new Value(definitionType, originalType, fieldName, targetType);
public InternalSchemaSwaps() {
this(new HashMap<>(), new HashMap<>(), new HashMap<>());
}

private InternalSchemaSwaps(Map<Key, Value> swaps, Map<Key, Integer> swapDepths, Map<Key, Value> allSwaps) {
this.allSwaps = allSwaps;
this.swaps = swaps;
this.swapDepths = swapDepths;
}

public InternalSchemaSwaps branchDepths() {
InternalSchemaSwaps result = new InternalSchemaSwaps(this.swaps, new HashMap<>(), this.allSwaps);
result.swapDepths.putAll(this.swapDepths);
return result;
}

public InternalSchemaSwaps branchAnnotations() {
InternalSchemaSwaps result = new InternalSchemaSwaps(new HashMap<>(), this.swapDepths, this.allSwaps);
result.swaps.putAll(this.swaps);
return result;
}

public void registerSwap(ClassRef definitionType, ClassRef originalType, String fieldName, ClassRef targetType,
int depth) {
Value value = new Value(definitionType, originalType, fieldName, targetType, depth);
swaps.put(new Key(originalType, fieldName), value);
if (allSwaps.put(new Key(originalType, fieldName), value) != null) {
// it's simplest for now to just disallow this
throw new IllegalArgumentException("Nested SchemaSwap: " + value);
}
}

public Optional<ClassRef> lookupAndMark(ClassRef originalType, String name) {
Value value = swaps.get(new Key(originalType, name));
public ClassRef lookupAndMark(ClassRef originalType, String name, Runnable swapApplicableAction) {
Key key = new Key(originalType, name);
Value value = swaps.get(key);
if (value != null) {
if (value.depth > 0) {
swapApplicableAction.run();
}
int depth = swapDepths.compute(key, (k, v) -> {
if (v == null) {
return 1;
}
return v + 1;
});
value.markUsed();
return Optional.of(value.getTargetType());
} else {
return Optional.empty();
if (depth > value.depth) {
return value.getTargetType();
}
}
return null;
}

public void throwIfUnmatchedSwaps() {
String unmatchedSchemaSwaps = swaps.values().stream().filter(value -> !value.used)
String unmatchedSchemaSwaps = allSwaps.values().stream().filter(value -> !value.used)
.map(Object::toString)
.collect(Collectors.joining(", "));
if (!unmatchedSchemaSwaps.isEmpty()) {
Expand Down Expand Up @@ -100,12 +139,14 @@ private static class Value {
private final ClassRef targetType;
private boolean used;
private final ClassRef definitionType;
private final int depth;

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
*/
package io.fabric8.crd.generator.annotation;

import java.lang.annotation.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation that allows replacing a nested schema with one from another class.
Expand Down Expand Up @@ -52,4 +56,12 @@
* The default value of {@code void.class} causes the field to be skipped
*/
Class<?> targetType() default void.class;

/**
* For fields that may include a reference cycle, how many expansions to include in the output before including the
* {@link #targetType()}
* <p>
* The default value of 0 replaces the field with the {@link #targetType()} without any expansion
*/
int depth() default 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* 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.api.model.AnyType;
import io.fabric8.kubernetes.client.CustomResource;

import java.util.List;

@SchemaSwap(originalType = CollectionCyclicSchemaSwap.Level.class, fieldName = "levels", targetType = AnyType.class, depth = 2)
public class CollectionCyclicSchemaSwap extends CustomResource<CollectionCyclicSchemaSwap.Spec, Void> {

public static class Spec {
private MyObject myObject;
private List<Level> levels;
}

public static class Level {
private MyObject myObject;
private List<Level> levels;
}

public static class MyObject {
private int value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* 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;

import java.util.List;

@SchemaSwap(originalType = CyclicSchemaSwap.Level.class, fieldName = "level", depth = 0)
public class CyclicSchemaSwap extends CustomResource<CyclicSchemaSwap.Spec, Void> {

public static class Spec {
private MyObject myObject;
private Level level;
private List<Level> levels; // should not interfere with the rendering depth of level of its sibling
}

public static class Level {
private MyObject myObject;
private Level level;
}

public static class MyObject {
private int value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* 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;

public class NestedSchemaSwap extends CustomResource<NestedSchemaSwap.Spec, Void> {

@SchemaSwap(originalType = End.class, fieldName = "value", targetType = String.class)
public static class Spec {
private Intermediate one;
private Intermediate another;
}

@SchemaSwap(originalType = End.class, fieldName = "value", targetType = Void.class)
public static class Intermediate {
private End one;
}

public static class End {
private int value;
}
}
Loading

0 comments on commit 4cd90d6

Please sign in to comment.