io.soliton.shapeshifter.ProtoDescriptorGraph.java Source code

Java tutorial

Introduction

Here is the source code for io.soliton.shapeshifter.ProtoDescriptorGraph.java

Source

/**
 * 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;

    }
}