All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.marshalling.reflect.SerializableField Maven / Gradle / Ivy

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.marshalling.reflect;

import static java.lang.System.getSecurityManager;
import static java.security.AccessController.doPrivileged;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import org.jboss.marshalling._private.GetUnsafeAction;
import org.jboss.marshalling.util.Kind;
import sun.misc.Unsafe;

/**
 * Reflection information about a field on a serializable class.
 */
public final class SerializableField {
    static final Unsafe unsafe = getSecurityManager() == null ? GetUnsafeAction.INSTANCE.run() : doPrivileged(GetUnsafeAction.INSTANCE);

    // the type of the field itself
    private final Class type;
    private final Field field;
    private final String name;
    private final boolean unshared;
    private final Kind kind;
    private final long fieldOffset;
    private final int recordComponentIndex;

    public SerializableField(Class type, String name, boolean unshared) {
        this(type, name, unshared, null, null);
    }

    SerializableField(Class type, String name, boolean unshared, final Field field, RecordComponent recordComponent) {
        assert field == null || (field.getModifiers() & Modifier.STATIC) == 0 && ! field.getDeclaringClass().isArray();
        this.type = type;
        this.name = name;
        this.unshared = unshared;
        this.field = field;
        fieldOffset = field == null || recordComponent != null? -1 : unsafe.objectFieldOffset(field);
        if (field != null) {
            // verify field information
            if (field.getType() != type) {
                throw new IllegalStateException("Constructed a serializable field with the wrong type (field type is " + field.getType() + ", our type is " + type + ")");
            }
            if (! field.getName().equals(name)) {
                throw new IllegalStateException("Constructed a serializable field with the wrong name (field name is " + field.getName() + ", our name is " + name + ")");
            }
        }
        if (recordComponent != null) {
            if (field == null) {
                throw new IllegalStateException("Record component field not passed for name " + recordComponent.getName());
            }
            if (recordComponent.getType() != type) {
                throw new IllegalStateException("Record component field with the wrong type (field type is " + recordComponent.getType() + ", our type is " + type + ")");
            }
            if (! recordComponent.getName().equals(name)) {
                throw new IllegalStateException("Record component field with the wrong name (field name is " + recordComponent.getName() + ", our name is " + name + ")");
            }
            this.recordComponentIndex = recordComponent.getIndex();
        } else {
            this.recordComponentIndex = -1;
        }
        // todo - see if a small Map is faster
        if (type == boolean.class) {
            kind = Kind.BOOLEAN;
        } else if (type == byte.class) {
            kind = Kind.BYTE;
        } else if (type == short.class) {
            kind = Kind.SHORT;
        } else if (type == int.class) {
            kind = Kind.INT;
        } else if (type == long.class) {
            kind = Kind.LONG;
        } else if (type == char.class) {
            kind = Kind.CHAR;
        } else if (type == float.class) {
            kind = Kind.FLOAT;
        } else if (type == double.class) {
            kind = Kind.DOUBLE;
        } else {
            kind = Kind.OBJECT;
        }
    }

    /**
     * Get the reflection {@code Field} for this serializable field.  The resultant field will be accessible.
     *
     * @return the reflection field
     * @deprecated As of Java 9, accessible fields are generally disallowed; use the {@code #setXXX(Object,value)} methods instead.
     */
    @Deprecated
    public Field getField() {
        return field;
    }

    /**
     * Determine if this object may be used to get or set an object field value.
     *
     * @return {@code true} if this object may be used to get or set an object field value, {@code false} otherwise
     */
    public boolean isAccessible() {
        return field != null;
    }

    /**
     * Get the name of the field.
     *
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * Determine whether this field is marked as "unshared".
     *
     * @return {@code true} if the field is unshared
     */
    public boolean isUnshared() {
        return unshared;
    }

    /**
     * Get the kind of field.
     *
     * @return the kind
     */
    public Kind getKind() {
        return kind;
    }

    /**
     * Get the field type.
     *
     * @return the field type
     */
    public Class getType() throws ClassNotFoundException {
        return type;
    }

    /**
     * Set the boolean value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @param value the value to set
     * @throws ClassCastException if {@code instance} or the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public void setBoolean(Object instance, boolean value) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != boolean.class) {
            throw new ClassCastException();
        }
        unsafe.putBoolean(instance, fieldOffset, value);
    }

    /**
     * Set the char value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @param value the value to set
     * @throws ClassCastException if {@code instance} or the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public void setChar(Object instance, char value) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != char.class) {
            throw new ClassCastException();
        }
        unsafe.putChar(instance, fieldOffset, value);
    }

    /**
     * Set the byte value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @param value the value to set
     * @throws ClassCastException if {@code instance} is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public void setByte(Object instance, byte value) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != byte.class) {
            throw new ClassCastException();
        }
        unsafe.putByte(instance, fieldOffset, value);
    }

    /**
     * Set the short value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @param value the value to set
     * @throws ClassCastException if {@code instance} or the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public void setShort(Object instance, short value) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != short.class) {
            throw new ClassCastException();
        }
        unsafe.putShort(instance, fieldOffset, value);
    }

    /**
     * Set the integer value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @param value the value to set
     * @throws ClassCastException if {@code instance} or the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public void setInt(Object instance, int value) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != int.class) {
            throw new ClassCastException();
        }
        unsafe.putInt(instance, fieldOffset, value);
    }

    /**
     * Set the long value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @param value the value to set
     * @throws ClassCastException if {@code instance} or the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public void setLong(Object instance, long value) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != long.class) {
            throw new ClassCastException();
        }
        unsafe.putLong(instance, fieldOffset, value);
    }

    /**
     * Set the float value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @param value the value to set
     * @throws ClassCastException if {@code instance} or the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public void setFloat(Object instance, float value) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != float.class) {
            throw new ClassCastException();
        }
        unsafe.putFloat(instance, fieldOffset, value);
    }

    /**
     * Set the double value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @param value the value to set
     * @throws ClassCastException if {@code instance} or the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public void setDouble(Object instance, double value) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != double.class) {
            throw new ClassCastException();
        }
        unsafe.putDouble(instance, fieldOffset, value);
    }

    /**
     * Set the object value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @param value the value to set
     * @throws ClassCastException if {@code instance} or the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public void setObject(Object instance, Object value) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        final Class fieldType = field.getType();
        if (fieldType.isPrimitive()) {
            throw new ClassCastException();
        }
        fieldType.cast(value);
        unsafe.putObject(instance, fieldOffset, value);
    }

    /**
     * Get the boolean value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @return the value of the field
     * @throws ClassCastException if the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public boolean getBoolean(Object instance) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != boolean.class) {
            throw new ClassCastException();
        }
        return unsafe.getBoolean(instance, fieldOffset);
    }

    /**
     * Get the char value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @return the value of the field
     * @throws ClassCastException if the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public char getChar(Object instance) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != char.class) {
            throw new ClassCastException();
        }
        return unsafe.getChar(instance, fieldOffset);
    }

    /**
     * Get the byte value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @return the value of the field
     * @throws ClassCastException if the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public byte getByte(Object instance) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != byte.class) {
            throw new ClassCastException();
        }
        return unsafe.getByte(instance, fieldOffset);
    }

    /**
     * Get the short value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @return the value of the field
     * @throws ClassCastException if the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public short getShort(Object instance) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != short.class) {
            throw new ClassCastException();
        }
        return unsafe.getShort(instance, fieldOffset);
    }

    /**
     * Get the integer value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @return the value of the field
     * @throws ClassCastException if the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public int getInt(Object instance) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != int.class) {
            throw new ClassCastException();
        }
        return unsafe.getInt(instance, fieldOffset);
    }

    /**
     * Get the long value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @return the value of the field
     * @throws ClassCastException if the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public long getLong(Object instance) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != long.class) {
            throw new ClassCastException();
        }
        return unsafe.getLong(instance, fieldOffset);
    }

    /**
     * Get the float value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @return the value of the field
     * @throws ClassCastException if the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public float getFloat(Object instance) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != float.class) {
            throw new ClassCastException();
        }
        return unsafe.getFloat(instance, fieldOffset);
    }

    /**
     * Get the double value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @return the value of the field
     * @throws ClassCastException if the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public double getDouble(Object instance) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType() != double.class) {
            throw new ClassCastException();
        }
        return unsafe.getDouble(instance, fieldOffset);
    }

    /**
     * Get the object value of this field on the given object instance.
     *
     * @param instance the object instance (must not be {@code null}, must be of the correct type)
     * @return the value of the field
     * @throws ClassCastException if the field is not of the correct type
     * @throws IllegalArgumentException if this instance has no reflection field set on it
     */
    public Object getObject(Object instance) throws ClassCastException, IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("instance is null");
        }
        if (field == null) {
            throw new IllegalArgumentException();
        }
        field.getDeclaringClass().cast(instance);
        if (field.getType().isPrimitive()) {
            throw new ClassCastException();
        }
        return unsafe.getObject(instance, fieldOffset);
    }

    /**
     * Read the field value from the stream.
     *
     * @param instance the instance
     * @param input the source stream
     * @throws IOException if an I/O error occurs
     * @throws ClassNotFoundException if a class could not be loaded
     */
    public void readFrom(Object instance, ObjectInput input) throws IOException, ClassNotFoundException {
        switch (kind) {
            case BOOLEAN:
                setBoolean(instance, input.readBoolean());
                break;
            case BYTE:
                setByte(instance, input.readByte());
                break;
            case CHAR:
                setChar(instance, input.readChar());
                break;
            case DOUBLE:
                setDouble(instance, input.readDouble());
                break;
            case FLOAT:
                setFloat(instance, input.readFloat());
                break;
            case INT:
                setInt(instance, input.readInt());
                break;
            case LONG:
                setLong(instance, input.readLong());
                break;
            case SHORT:
                setShort(instance, input.readShort());
                break;
            case OBJECT:
                setObject(instance, input.readObject());
                break;
            default:
                throw new IllegalStateException();
        }
    }

    public void writeTo(Object instance, ObjectOutput output) throws IOException {
        switch (kind) {
            case BOOLEAN:
                output.writeBoolean(getBoolean(instance));
                break;
            case BYTE:
                output.writeByte(getByte(instance));
                break;
            case CHAR:
                output.writeChar(getChar(instance));
                break;
            case DOUBLE:
                output.writeDouble(getDouble(instance));
                break;
            case FLOAT:
                output.writeFloat(getFloat(instance));
                break;
            case INT:
                output.writeInt(getInt(instance));
                break;
            case LONG:
                output.writeLong(getLong(instance));
                break;
            case SHORT:
                output.writeShort(getShort(instance));
                break;
            case OBJECT:
                output.writeObject(getObject(instance));
                break;
            default:
                throw new IllegalStateException();
        }
    }

    /**
     * Getter for the record component value.
     * @param obj The object to obtain the record component value
     * @return The record component value for this obj
     */
    public Object getRecordComponentValue(Object obj) {
        if (recordComponentIndex == -1) {
            return null;
        }
        return JDKSpecific.getRecordComponentValue(obj, name, type);
    }

    /**
     * Getter for the record component index.
     * @return The record component index or -1
     */
    public int getRecordComponentIndex() {
        return recordComponentIndex;
    }

    /**
     * A record component, which has a name, type and index.
     */
    static final class RecordComponent {

        private final String name;
        private final Class type;
        private final int index;

        RecordComponent(String name, Class type, int index) {
            this.name = name;
            this.type = type;
            this.index = index;
        }

        public String getName() {
            return name;
        }

        public Class getType() {
            return type;
        }

        public int getIndex() {
            return index;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy