Skip to content

Commit

Permalink
Documented BridgeTransform
Browse files Browse the repository at this point in the history
  • Loading branch information
Pyknic committed Aug 6, 2015
1 parent 1e7c20c commit 530f4d0
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 36 deletions.
143 changes: 109 additions & 34 deletions src/main/java/com/speedment/codegen/base/BridgeTransform.java
Expand Up @@ -18,62 +18,77 @@

import java.util.ArrayList;
import java.util.List;
import static java.util.Objects.requireNonNull;
import java.util.Optional;
import java.util.stream.Stream;

/**
*
* A transform that uses a series of transforms to complete the transformation.
* For an example, if you have two transforms A -> B and A -> C you can define
* a <code>BridgeTransform</code> for A -> C.
*
* @author Emil Forslund
*/
public class BridgeTransform<A, B> implements Transform<A, B> {

private final List<Transform<?, ?>> steps;
private final Class<A> from;
private final Class<B> to;
private final TransformFactory installer;
private final TransformFactory factory;
private Class<?> end;

public BridgeTransform(Class<A> from, Class<B> to, TransformFactory installer) {
/**
* Constructs a new transform from one model class to another. The bridge
* requires a factory to create the intermediate steps.
*
* @param from the type to transform from
* @param to the type to transform to
* @param factory a factory with all the required steps installed
*/
private BridgeTransform(Class<A> from, Class<B> to, TransformFactory factory) {
this.from = requireNonNull(from);
this.to = requireNonNull(to);
this.factory = requireNonNull(factory);

this.steps = new ArrayList<>();
this.from = from;
this.to = to;
this.end = from;
this.installer = installer;
this.end = requireNonNull(from);
}

/**
* Creates a shallow copy of a bridge.
*
* @param prototype the prototype
*/
private BridgeTransform(BridgeTransform<A, B> prototype) {
steps = new ArrayList<>(prototype.steps);
from = prototype.from;
to = prototype.to;
end = prototype.end;
installer = prototype.installer;
steps = new ArrayList<>(prototype.steps);
from = prototype.from;
to = prototype.to;
end = prototype.end;
factory = prototype.factory;
}

public <A2, B2> boolean addStep(Class<A2> from, Class<B2> to, Transform<A2, B2> step) {
if (end == null || from.equals(end)) {
if (steps.contains(step)) {
return false;
} else {
steps.add(step);
end = to;
return true;
}
} else {
throw new IllegalArgumentException("Transform " + step + " has a different entry class (" + from + ") than the last class in the current build (" + end + ").");
}
}

/**
* Transforms the specified model using this transform. A code generator is
* supplied so that the transform can initiate new generation processes to
* resolve dependencies.
*
* @param gen the current code generator
* @param model the model to transform
* @return the transformation result or empty if any of the steps
* returned empty
*/
@Override
@SuppressWarnings("unchecked")
public Optional<B> transform(Generator gen, A model) {
Object o = model;

for (final Transform<?, ?> step : steps) {
if (o == null) {
return Optional.empty();
} else {
@SuppressWarnings("unchecked")
final Transform<Object, ?> step2 = (Transform<Object, ?>) step;
o = gen.transform(step2, o, installer)

o = gen.transform(step2, o, factory)
.map(m -> m.getResult())
.orElse(null);
}
Expand All @@ -82,36 +97,96 @@ public Optional<B> transform(Generator gen, A model) {
return Optional.ofNullable((B) o);
}

public static <A, B, T extends Transform<A, B>> Stream<T> create(TransformFactory installer, Class<A> from, Class<B> to) {
return create(installer, new BridgeTransform<>(from, to, installer));
/**
* Creates a bridge from one model type to another. A factory is supplied so
* that intermediate steps can be resolved.
*
* @param <A> the initial type of a model to transform
* @param <B> the final type of a model after transformation
* @param <T> the type of a transform between A and B
* @param factory a factory with all intermediate steps installed
* @param from the initial class of a model to transform
* @param to the final class of a model after transformation
* @return a <code>Stream</code> of all unique paths between A and B
*/
public static <A, B, T extends Transform<A, B>> Stream<T> create(TransformFactory factory, Class<A> from, Class<B> to) {
return create(factory, new BridgeTransform<>(from, to, factory));
}

@SuppressWarnings("unchecked")
public static <A, B, T extends Transform<A, B>> Stream<T> create(TransformFactory installer, BridgeTransform<A, B> bridge) {
/**
* Takes a bridge and completes it if it is not finished. Returns all valid
* paths through the graph as a <code>Stream</code>.
*
* @param <A> the initial type of a model to transform
* @param <B> the final type of a model after transformation
* @param <T> the type of a transform between A and B
* @param factory a factory with all intermediate steps installed
* @param bridge the incomplete bridge to finish
* @return a <code>Stream</code> of all unique paths between A and B
*/
private static <A, B, T extends Transform<A, B>> Stream<T> create(TransformFactory factory, BridgeTransform<A, B> bridge) {
if (bridge.end.equals(bridge.to)) {
return Stream.of((T) bridge);
} else {
final List<Stream<T>> bridges = new ArrayList<>();

installer.allFrom(bridge.end).stream().forEachOrdered(e -> {
factory.allFrom(bridge.end).stream().forEachOrdered(e -> {

final BridgeTransform<A, B> br = new BridgeTransform<>(bridge);

@SuppressWarnings("unchecked")
Class<Object> a = (Class<Object>) bridge.end;

@SuppressWarnings("unchecked")
Class<Object> b = (Class<Object>) e.getKey();

@SuppressWarnings("unchecked")
Transform<Object, Object> transform = (Transform<Object, Object>) e.getValue();

if (br.addStep(a, b, transform)) {
bridges.add(create(installer, br));
bridges.add(create(factory, br));
}
});

return bridges.stream().flatMap(i -> i);
}
}

/**
* Returns true if this transform is or contains the specified
* transformer. This is used internally by the code generator to avoid
* circular paths.
*
* @param transformer the type of the transformer to check
* @return true if this transform is or contains the input
*/
@Override
public boolean is(Class<? extends Transform<?, ?>> transformer) {
return steps.stream().anyMatch(t -> t.is(transformer));
}

/**
* Attempts to add a new step to the bridge. If the step is already part of
* the bridge, it will not be added. Returns true if the step was added.
*
* @param <A2> the initial type of the step
* @param <B2> the outputed type of the step
* @param from the initial type of the step
* @param to the outputed type of the step
* @param step the step to attempt to add
* @return true if the step was added
*/
private <A2, B2> boolean addStep(Class<A2> from, Class<B2> to, Transform<A2, B2> step) {
if (end == null || from.equals(end)) {
if (steps.contains(step)) {
return false;
} else {
steps.add(step);
end = to;
return true;
}
} else {
throw new IllegalArgumentException("Transform " + step + " has a different entry class (" + from + ") than the last class in the current build (" + end + ").");
}
}
}
24 changes: 22 additions & 2 deletions src/main/java/com/speedment/codegen/base/Transform.java
Expand Up @@ -23,12 +23,32 @@
* be instantiated dynamically.
*
* @author Emil Forslund
* @param <F> The model to generate from.
* @param <T> The resulting model.
* @param <F> the model to generate from
* @param <T> the resulting model
*/
public interface Transform<F, T> {

/**
* Transforms a model from one type to another. A reference to the current
* code generator is supplied so that intermediate generation processes can
* be initiated to resolve dependencies. The transform can choose not to
* accept a particular input and therefore return <code>empty</code>.
*
* @param gen a reference to the generator being used
* @param model the model to transform
* @return the transformed model or empty if the transformation could
* not be done for that input
*/
Optional<T> transform(Generator gen, F model);

/**
* Returns true if this transform is or contains the specified
* transformer. This is used internally by the code generator to avoid
* circular paths.
*
* @param transformer the type of the transformer to check
* @return true if this transform is or contains the input
*/
default boolean is(Class<? extends Transform<?, ?>> transformer) {
return transformer.isAssignableFrom(getClass());
}
Expand Down

0 comments on commit 530f4d0

Please sign in to comment.