/** * * Copyright (c) 2006-2017, Speedment, Inc. All Rights Reserved. * * 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 com.speedment.common.codegen.internal; import com.speedment.common.codegen.Generator; import com.speedment.common.codegen.Meta; import com.speedment.common.codegen.Transform; import com.speedment.common.codegen.TransformFactory; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Stream; import static java.util.Objects.requireNonNull; /** * 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 * @param <A> the from type * @param <B> the to type */ 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 factory; private Class<?> end; /** * 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.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; factory = prototype.factory; } /** * 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 public Optional<B> transform(Generator gen, A model) { requireNonNull(gen); requireNonNull(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, factory) .map(Meta::getResult) .orElse(null); } } if (o == null) { return Optional.empty(); } else { if (to.isAssignableFrom(o.getClass())) { @SuppressWarnings("unchecked") final B result = (B) o; return Optional.of(result); } else { throw new IllegalStateException( "The bridge between '" + from.getSimpleName() + "' to '" + to.getSimpleName() + "' is not complete." ); } } } /** * 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)); } /** * 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) { requireNonNull(factory); requireNonNull(bridge); if (bridge.end.equals(bridge.to)) { @SuppressWarnings("unchecked") final T result = (T) bridge; return Stream.of(result); } else { final List<Stream<T>> bridges = new ArrayList<>(); 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(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(requireNonNull(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 output type of the step * @param from the initial type of the step * @param to the output 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) { requireNonNull(from); requireNonNull(to); requireNonNull(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 + ")."); } } }