org.chaston.oakfunds.storage.RecordTypeRegistryImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.chaston.oakfunds.storage.RecordTypeRegistryImpl.java

Source

/*
 * Copyright 2014 Miles Chaston
 *
 * 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 org.chaston.oakfunds.storage;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.inject.Inject;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * TODO(mchaston): write JavaDocs
 */
public class RecordTypeRegistryImpl implements RecordTypeRegistry {

    private final ImmutableMap<String, RecordType> recordTypes;
    private final ImmutableMap<String, RecordValidator> recordValidators;
    private final ImmutableMultimap<String, RecordType> assignableRecordTypes;

    @Inject
    RecordTypeRegistryImpl(Set<RecordType> recordTypes) {
        Map<String, RecordType> recordTypesBuilder = new HashMap<>();
        Map<String, RecordValidator> recordValidatorsBuilder = new HashMap<>();
        Multimap<String, RecordType> assignableRecordTypesBuilder = MultimapBuilder.hashKeys().hashSetValues()
                .build();
        for (RecordType recordType : recordTypes) {
            if (recordTypesBuilder.containsKey(recordType.getName())) {
                throw new IllegalStateException(
                        "RecordType " + recordType.getName() + " was bound more than once.");
            }
            recordTypesBuilder.put(recordType.getName(), recordType);
            recordValidatorsBuilder.put(recordType.getName(), new RecordValidator(recordType));
            assignableRecordTypesBuilder.put(recordType.getName(), recordType);
            RecordType parentType = recordType.getParentType();
            while (parentType != null) {
                assignableRecordTypesBuilder.put(parentType.getName(), recordType);
                parentType = parentType.getParentType();
            }
        }
        this.recordTypes = ImmutableMap.copyOf(recordTypesBuilder);
        this.recordValidators = ImmutableMap.copyOf(recordValidatorsBuilder);
        this.assignableRecordTypes = ImmutableMultimap.copyOf(assignableRecordTypesBuilder);
    }

    @Override
    public void validateRecordAttributes(RecordType<?> recordType, Map<String, Object> attributes, boolean create)
            throws StorageException {
        RecordValidator recordValidator = recordValidators.get(recordType.getName());
        if (recordValidator == null) {
            throw new IllegalStateException("RecordType " + recordType.getName() + " was not bound.");
        }
        recordValidator.validateAttributes(attributes, create);
    }

    @Override
    public <T extends Record> RecordType<T> getType(String name, RecordType<T> parentRecordType) {
        RecordType<?> recordType = recordTypes.get(name);
        if (recordType.isTypeOf(parentRecordType)) {
            return (RecordType<T>) recordType;
        }
        throw new IllegalArgumentException(
                "RecordType " + name + " is not a subtype of " + parentRecordType.getName() + ".");
    }

    @Override
    public Iterable<RecordType> getAssignableTypes(RecordType recordType) {
        return assignableRecordTypes.get(recordType.getName());
    }

    private static class RecordValidator {

        private static final ImmutableMap<Class<?>, Class<?>> NON_PRIMITIVES = ImmutableMap
                .<Class<?>, Class<?>>builder().put(Boolean.TYPE, Boolean.class).put(Character.TYPE, Character.class)
                .put(Byte.TYPE, Byte.class).put(Short.TYPE, Short.class).put(Integer.TYPE, Integer.class)
                .put(Long.TYPE, Long.class).put(Float.TYPE, Float.class).put(Double.TYPE, Double.class).build();

        private final RecordType recordType;
        private final Map<String, AttributeValidator> attributeValidators = new HashMap<>();

        public RecordValidator(RecordType<?> recordType) {
            this.recordType = recordType;
            extractAttributes(recordType);
        }

        private void extractAttributes(RecordType<?> recordType) {
            for (AttributeType attributeType : recordType.getAttributes().values()) {
                attributeValidators.put(attributeType.getName(), new AttributeValidator(attributeType.getName(),
                        attributeType.getType(), attributeType.isRequired()));
            }
            if (recordType.getParentType() != null) {
                extractAttributes(recordType.getParentType());
            }
        }

        public void validateAttributes(Map<String, Object> attributes, boolean create) throws StorageException {
            for (Map.Entry<String, Object> entry : attributes.entrySet()) {
                AttributeValidator attributeValidator = attributeValidators.get(entry.getKey());
                if (attributeValidator == null) {
                    throw new StorageException("Attribute " + entry.getKey()
                            + " is not a known attribute for RecordType " + recordType + ".");
                }
                attributeValidator.validate(entry.getValue());
            }
            for (Map.Entry<String, AttributeValidator> entry : attributeValidators.entrySet()) {
                if (create && entry.getValue().required) {
                    if (!attributes.containsKey(entry.getKey())) {
                        throw new StorageException(
                                "Attribute " + entry.getKey() + " on RecordType " + recordType + " is required.");
                    }
                }
            }
        }

        private class AttributeValidator {

            private final String attribute;
            private final Class<?> type;
            private final boolean required;

            private AttributeValidator(String attribute, Class<?> type, boolean required) {
                this.attribute = attribute;
                this.type = type.isPrimitive() ? NON_PRIMITIVES.get(type) : type;
                this.required = required;
            }

            public void validate(Object value) throws StorageException {
                if (value == null) {
                    if (required) {
                        throw new StorageException(
                                "Attribute " + attribute + " on RecordType " + recordType + " is required.");
                    }
                } else {
                    if (!type.isAssignableFrom(value.getClass())) {
                        throw new StorageException(
                                "Attribute " + attribute + " on RecordType " + recordType + " is of type " + type
                                        + ", but an object of type " + value.getClass() + " was provided.");
                    }
                }
            }
        }
    }
}