com.gilecode.yagson.refs.impl.PlaceholderUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.gilecode.yagson.refs.impl.PlaceholderUtils.java

Source

/*
 * Copyright (C) 2016 Andrey Mogilev
 *
 * 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.gilecode.yagson.refs.impl;

import com.gilecode.yagson.refs.HashReferencePlaceholder;
import com.gilecode.yagson.refs.PlaceholderUse;
import com.gilecode.yagson.refs.ReferencePlaceholder;
import com.google.gson.JsonSyntaxException;
import com.gilecode.yagson.adapters.HasField;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Utility functions related to {@link ReferencePlaceholder}s
 *
 * @author Andrey Mogilev
 */
public class PlaceholderUtils {

    /**
     * Finds hash and fields placeholders in the specified list of placeholders and resolve each of them, either
     * now (if no other placeholders prevent immediate resolution), or after other placeholders are resolved.
     *
     * @param instance the instance which contains the field placeholders
     * @param placeholders all placeholders for the fields of the instance object, either hash or non-hash
     * @param fieldsByName provider of fields by theirs (serialization) names
     *
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public static <T> void applyOrDeferHashAndFieldPlaceholders(final T instance,
            final Map<Field, ReferencePlaceholder> placeholders, Map<String, ? extends HasField> fieldsByName)
            throws IOException {

        // as FieldReferencePlaceholder may actually reference another placeholder, they may form a DAG, ending with
        //  either HashRef, other Ref or value. Use recursion to process DAGs from the end, and for each placeholder
        //  either resolve it (apply actual object if known), or move to the 'hash references' set, or to the
        //  'deferred references' set
        Set<Field> unresolvedFields = new HashSet<Field>(placeholders.keySet());
        final Set<Field> hashFields = new HashSet<Field>(placeholders.size());
        final Set<Field> deferredReferencesFields = new HashSet<Field>(placeholders.size());

        while (!unresolvedFields.isEmpty()) {
            Iterator<Field> it = unresolvedFields.iterator();
            Field fieldToResolve = it.next();
            it.remove();
            tryResolveFieldPlaceholder(fieldToResolve, placeholders, fieldsByName, instance, unresolvedFields,
                    hashFields, deferredReferencesFields);
        }

        // at this moment, all unresolved references are either 'hash' or 'deferred' placeholders (which are to be
        //  resolved elsewhere, or are chained).
        // here we need to process or chain the hash placeholders
        if (hashFields.isEmpty()) {
            return;
        }
        if (deferredReferencesFields.isEmpty()) {
            // no deferred non-hash placeholders, can calculate hash now
            int hashCode = instance.hashCode();
            for (Field f : hashFields) {
                ReferencePlaceholder p = placeholders.get(f);
                p.applyActualObject(hashCode);
            }
        } else {
            // apply hash when all non-hash references are resolved
            for (Field f : deferredReferencesFields) {
                ReferencePlaceholder p = placeholders.get(f);
                final AtomicBoolean isResolved = new AtomicBoolean();
                p.registerUse(new PlaceholderUse<Object>() {
                    public void applyActualObject(Object actualObject) throws IOException {
                        if (isResolved.get()) {
                            return;
                        }
                        for (Field checkedField : deferredReferencesFields) {
                            ReferencePlaceholder checkedPlaceholder = placeholders.get(checkedField);
                            if (checkedPlaceholder.getActualObject() == null) {
                                // not all placeholders are resolved yet
                                return;
                            }
                            isResolved.set(true);
                            int hashCode = instance.hashCode();
                            for (Field hashField : hashFields) {
                                ReferencePlaceholder hp = placeholders.get(hashField);
                                hp.applyActualObject(hashCode);
                            }
                        }
                    }
                });
            }
        }
    }

    @SuppressWarnings("unchecked")
    private static <T> void tryResolveFieldPlaceholder(Field fieldToResolve,
            Map<Field, ReferencePlaceholder> placeholders, Map<String, ? extends HasField> fieldsByName, T instance,
            Set<Field> unresolvedFields, Set<Field> hashFields, Set<Field> deferredReferencesFields)
            throws IOException {
        final ReferencePlaceholder placeholder = placeholders.get(fieldToResolve);
        assert placeholder != null;

        if (placeholder instanceof HashReferencePlaceholder) {
            hashFields.add(fieldToResolve);
        } else if (placeholder instanceof FieldReferencePlaceholder) {
            FieldReferencePlaceholder fieldRefPlaceholder = (FieldReferencePlaceholder) placeholder;
            String referencedFieldName = fieldRefPlaceholder.getReferencedFieldName();
            if (!fieldRefPlaceholder.isResolved()) {
                HasField fieldProvider = fieldsByName.get(referencedFieldName);
                if (fieldProvider == null) {
                    throw new JsonSyntaxException(
                            "No field found for the serialization name '" + referencedFieldName + "'");
                }
                fieldRefPlaceholder.setReferencedField(fieldProvider.getField());
            }

            Field referencedField = fieldRefPlaceholder.getReferencedField();

            // if there are no unresolved placeholders at the referenced field, we can use its value now
            ReferencePlaceholder chainedPlaceholder = placeholders.get(referencedField);
            Object referencedValue;
            if (chainedPlaceholder != null) {
                if (chainedPlaceholder.getActualObject() == null) {
                    // not resolved yet, check if met in processed sets
                    if (unresolvedFields.remove(referencedField)) {
                        // recursively process referenced placeholder
                        tryResolveFieldPlaceholder(fieldToResolve, placeholders, fieldsByName, instance,
                                unresolvedFields, hashFields, deferredReferencesFields);
                    }
                    if (chainedPlaceholder.getActualObject() == null) {
                        // still not resolved - must be either hash or deferred
                        if (hashFields.contains(referencedField)) {
                            // treat as hash reference
                            hashFields.add(referencedField);
                        } else if (deferredReferencesFields.contains(referencedField)) {
                            // chain to the deferred placeholder
                            deferredReferencesFields.add(referencedField);
                            chainedPlaceholder.registerUse(new PlaceholderUse() {
                                public void applyActualObject(Object actualObject) throws IOException {
                                    placeholder.applyActualObject(actualObject);
                                }
                            });
                        } else {
                            throw new IllegalStateException(
                                    "The placeholder is expected to be already processed: " + chainedPlaceholder);
                        }
                        return;
                    }
                }
                referencedValue = chainedPlaceholder.getActualObject();
                assert referencedValue != null;
            } else {
                try {
                    referencedValue = referencedField.get(instance);
                } catch (IllegalAccessException e) {
                    throw new JsonSyntaxException(
                            "Failed to get the referenced reflective field value; field=" + referencedField);
                }
            }
            fieldRefPlaceholder.applyActualObject(referencedValue);
        } else {
            deferredReferencesFields.add(fieldToResolve);
        }
    }
}