SimpleTypeRegistry.java

/*
 * Copyright 2015-2016 Providence Authors
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 net.morimekta.providence.types;

import net.morimekta.providence.descriptor.PContainer;
import net.morimekta.providence.descriptor.PDeclaredDescriptor;
import net.morimekta.providence.descriptor.PDescriptor;
import net.morimekta.providence.descriptor.PEnumDescriptor;
import net.morimekta.providence.descriptor.PField;
import net.morimekta.providence.descriptor.PMap;
import net.morimekta.providence.descriptor.PMessageDescriptor;
import net.morimekta.providence.descriptor.PService;
import net.morimekta.providence.descriptor.PServiceMethod;
import net.morimekta.providence.descriptor.PUnionDescriptor;
import net.morimekta.providence.descriptor.PValueProvider;
import net.morimekta.util.collect.UnmodifiableList;

import javax.annotation.Nonnull;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * Registry for declared types referenced in a flat space program context.
 * The registry itself does not have a context per se, as these may
 * reference each other recursively. Note that this registry does not
 * handle situations where two programs with the same name exist with
 * a type each with the same name.
 */
public class SimpleTypeRegistry extends WritableTypeRegistry {
    private final Set<String>                                knownPrograms;
    private final Map<TypeReference, PDeclaredDescriptor<?>> declaredTypes;
    private final Map<TypeReference, PService>               services;
    private final Map<TypeReference, TypeReference>          typedefs;
    private final Map<TypeReference, PValueProvider>         constants;

    public SimpleTypeRegistry() {
        this.knownPrograms = new HashSet<>();
        this.declaredTypes = new LinkedHashMap<>();
        this.services = new LinkedHashMap<>();
        this.typedefs = new LinkedHashMap<>();
        this.constants = new LinkedHashMap<>();
    }

    // TypeRegistry

    @Override
    public boolean isKnownProgram(@Nonnull String programName) {
        return knownPrograms.contains(programName);
    }

    @Override
    @Nonnull
    public Optional<TypeReference> getTypedef(@Nonnull TypeReference reference) {
        return Optional.ofNullable(typedefs.get(reference));
    }

    @Override
    public List<PDeclaredDescriptor<?>> getDeclaredTypes() {
        return UnmodifiableList.copyOf(declaredTypes.values());
    }

    @Nonnull
    @Override
    public Optional<PDeclaredDescriptor<?>> getDeclaredType(@Nonnull TypeReference reference) {
        return Optional.ofNullable(declaredTypes.get(reference));
    }

    @Nonnull
    @Override
    public Optional<PService> getService(@Nonnull TypeReference reference) {
        return Optional.ofNullable(services.get(reference));
    }

    @Nonnull
    @Override
    @SuppressWarnings("unchecked")
    public <T> Optional<T> getConstantValue(@Nonnull TypeReference reference) {
        return Optional.ofNullable((PValueProvider<T>) constants.get(reference))
                       .map(PValueProvider::get);
    }

    // WritableTypeRegistry

    @Override
    public void registerTypedef(@Nonnull TypeReference reference,
                                @Nonnull TypeReference target) {
        // Ignore if the type already exists.
        if (typedefs.containsKey(reference)) return;

        if (reference.isNativeType()) {
            throw new IllegalArgumentException("Registering typedef with native type name '" + reference + "'");
        }
        // Fail if we typedef back to self. Circular typedefs would be problematic if stored.
        if (reference.equals(finalTypeReference(target))) {
            throw new IllegalArgumentException("Typedef defining back to itself: " + reference);
        }
        knownPrograms.add(reference.programName);
        typedefs.put(reference, target);
    }

    @Override
    public void registerConstant(@Nonnull TypeReference reference, @Nonnull PValueProvider value) {
        if (constants.containsKey(reference)) return;
        if (reference.isNativeType()) {
            throw new IllegalArgumentException("Registering const with native type name '" + reference + "'");
        }
        knownPrograms.add(reference.programName);
        constants.put(reference, value);
    }

    @Override
    @SuppressWarnings("unchecked")
    public void registerService(@Nonnull PService service) {
        TypeReference reference = TypeReference.ref(service.getProgramName(),
                                                    service.getName());
        if (services.containsKey(reference)) {
            return;
        }

        knownPrograms.add(service.getProgramName());
        services.put(reference, service);
        for (PServiceMethod method : service.getMethods()) {
            PUnionDescriptor returnType = method.getResponseType();
            if (returnType != null) {
                registerType(returnType);
            }
            PMessageDescriptor requestType = method.getRequestType();
            registerType(requestType);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> void registerType(PDeclaredDescriptor<T> declaredType) {
        TypeReference reference = TypeReference.ref(declaredType.getProgramName(),
                                                    declaredType.getName());
        if (declaredTypes.containsKey(reference)) {
            return;
        }

        knownPrograms.add(reference.programName);
        declaredTypes.put(reference, declaredType);
        if (declaredType instanceof PMessageDescriptor) {
            PMessageDescriptor descriptor = (PMessageDescriptor) declaredType;
            for (PField field : descriptor.getFields()) {
                registerUntyped(field.getDescriptor());
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void registerUntyped(PDescriptor descriptor) {
        if (descriptor instanceof PEnumDescriptor ||
            descriptor instanceof PMessageDescriptor) {
            registerType((PDeclaredDescriptor) descriptor);
        } else if (descriptor instanceof PContainer) {
            registerUntyped(((PContainer) descriptor).itemDescriptor());
            if (descriptor instanceof PMap) {
                registerUntyped(((PMap) descriptor).keyDescriptor());
            }
        }
    }
}