Java tutorial
/** * Copyright 2013 Julien Silland * * 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.soliton.shapeshifter; import com.google.common.base.Function; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.protobuf.Descriptors; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor.Type; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.concurrent.atomic.AtomicInteger; /** * This class stores a representation of the references between protobuf * message descriptors and implements Tarjan's algorithm to detect loops, * which are forbidden in {@link AutoSchema} instances. * * @author Julien Silland (julien@soliton.io) * @see <a href="http://en.wikipedia.org/wiki/Tarjan%E2%80%99s_strongly_connected_components_algorithm">Tarjan's algorithm</a> */ class ProtoDescriptorGraph { private final ImmutableMultimap<Descriptor, Descriptor> graph; private ProtoDescriptorGraph(ImmutableMultimap<Descriptor, Descriptor> graph) { this.graph = graph; } public static ProtoDescriptorGraph of(Descriptor descriptor) { Multimap<Descriptor, Descriptor> graphBuilder = HashMultimap.create(); populate(descriptor, graphBuilder); return new ProtoDescriptorGraph( ImmutableMultimap.<Descriptor, Descriptor>builder().putAll(graphBuilder).build()); } private static void populate(Descriptor descriptor, Multimap<Descriptor, Descriptor> graphBuilder) { if (graphBuilder.containsKey(descriptor)) { return; } for (FieldDescriptor field : descriptor.getFields()) { if (Type.MESSAGE.equals(field.getType())) { Descriptors.Descriptor subDescriptor = field.getMessageType(); graphBuilder.put(descriptor, subDescriptor); populate(subDescriptor, graphBuilder); } } } public boolean isLooping() { Map<Descriptor, Link> links = Maps.newHashMap(); for (Descriptor descriptor : graph.keySet()) { links.put(descriptor, new Link()); for (Descriptor child : graph.get(descriptor)) { if (child.equals(descriptor)) { return true; } if (!links.containsKey(child)) { links.put(child, new Link()); } } } Stack<Descriptor> stack = new Stack<Descriptor>(); List<List<Descriptor>> sccs = Lists.newArrayList(); AtomicInteger index = new AtomicInteger(0); for (Descriptor descriptor : graph.keySet()) { if (links.get(descriptor).index == -1) { strongConnect(descriptor, index, links, stack, sccs); } } return !sccs.isEmpty(); } private void strongConnect(Descriptor descriptor, AtomicInteger index, Map<Descriptor, Link> links, Stack<Descriptor> stack, List<List<Descriptor>> sccs) { links.get(descriptor).index = index.get(); links.get(descriptor).lowLink = index.get(); index.incrementAndGet(); stack.push(descriptor); for (Descriptor child : graph.get(descriptor)) { if (links.get(child).index == -1) { strongConnect(child, index, links, stack, sccs); links.get(descriptor).lowLink = Math.min(links.get(descriptor).lowLink, links.get(child).lowLink); } else if (stack.contains(child)) { links.get(descriptor).lowLink = Math.min(links.get(descriptor).lowLink, links.get(child).index); } } if (links.get(descriptor).index == links.get(descriptor).lowLink) { List<Descriptor> scc = Lists.newArrayList(); Descriptor component = null; do { component = stack.pop(); scc.add(component); } while (!component.equals(descriptor)); if (scc.size() != 1) { sccs.add(scc); } } } @Override public String toString() { Multimap<String, String> namedGraph = HashMultimap.create(); for (Map.Entry<Descriptor, Descriptor> entry : graph.entries()) { namedGraph.put(entry.getKey().getName(), entry.getValue().getName()); } return namedGraph.toString(); } private static final class Link { int index = -1; int lowLink = 0; } }