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

org.jooq.impl.AbstractRecord Maven / Gradle / Ivy

There is a newer version: 3.19.9
Show newest version
/*
 * 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.
 *
 * Other licenses:
 * -----------------------------------------------------------------------------
 * Commercial licenses for this work are available. These replace the above
 * ASL 2.0 and offer limited warranties, support, maintenance, and commercial
 * database integrations.
 *
 * For more information, please visit: http://www.jooq.org/licenses
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package org.jooq.impl;

import static java.util.Arrays.asList;
import static org.jooq.conf.SettingsTools.updatablePrimaryKeys;
import static org.jooq.impl.Tools.indexOrFail;
import static org.jooq.impl.Tools.resetChangedOnNotNull;
import static org.jooq.impl.Tools.settings;
import static org.jooq.impl.Tools.ThreadGuard.Guard.RECORD_TOSTRING;

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import org.jooq.Attachable;
import org.jooq.Converter;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.JSONFormat;
import org.jooq.Name;
import org.jooq.Record;
import org.jooq.Record1;
import org.jooq.Record10;
import org.jooq.Record11;
import org.jooq.Record12;
import org.jooq.Record13;
import org.jooq.Record14;
import org.jooq.Record15;
import org.jooq.Record16;
import org.jooq.Record17;
import org.jooq.Record18;
import org.jooq.Record19;
import org.jooq.Record2;
import org.jooq.Record20;
import org.jooq.Record21;
import org.jooq.Record22;
import org.jooq.Record3;
import org.jooq.Record4;
import org.jooq.Record5;
import org.jooq.Record6;
import org.jooq.Record7;
import org.jooq.Record8;
import org.jooq.Record9;
import org.jooq.RecordMapper;
import org.jooq.Result;
import org.jooq.Table;
import org.jooq.UniqueKey;
import org.jooq.XMLFormat;
import org.jooq.exception.IOException;
import org.jooq.exception.InvalidResultException;
import org.jooq.exception.MappingException;
import org.jooq.impl.Tools.ThreadGuard;
import org.jooq.impl.Tools.ThreadGuard.GuardedOperation;
import org.jooq.tools.Convert;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;

/**
 * A general base class for all {@link Record} types
 *
 * @author Lukas Eder
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
abstract class AbstractRecord extends AbstractStore implements Record {

    /**
     * Generated UID
     */
    private static final long       serialVersionUID = -6052512608911220404L;
    private static final JooqLogger log              = JooqLogger.getLogger(AbstractRecord.class);

    final RowImpl                   fields;
    final Object[]                  values;
    final Object[]                  originals;
    final BitSet                    changed;
    boolean                         fetched;

    AbstractRecord(Collection> fields) {
        this(new RowImpl(fields));
    }

    AbstractRecord(Field... fields) {
        this(new RowImpl(fields));
    }

    AbstractRecord(RowImpl fields) {
        int size = fields.size();

        this.fields = fields;
        this.values = new Object[size];
        this.originals = new Object[size];
        this.changed = new BitSet(size);
    }

    // ------------------------------------------------------------------------
    // XXX: Attachable API
    // ------------------------------------------------------------------------

    @Override
    final List getAttachables() {
        List result = null;

        int size = size();
        for (int i = 0; i < size; i++) {
            if (values[i] instanceof Attachable) {
                if (result == null)
                    result = new ArrayList();

                result.add((Attachable) values[i]);
            }
        }

        return result == null ? Collections.emptyList() : result;
    }

    // ------------------------------------------------------------------------
    // XXX: FieldProvider API
    // ------------------------------------------------------------------------

    @Override
    public final  Field field(Field field) {
        return fieldsRow().field(field);
    }

    @Override
    public final Field field(String name) {
        return fieldsRow().field(name);
    }

    @Override
    public final Field field(Name name) {
        return fieldsRow().field(name);
    }

    @Override
    public final Field field(int index) {
        return index >= 0 && index < fields.size() ? fields.field(index) : null;
    }

    @Override
    public final Field[] fields() {
        return fields.fields();
    }

    @Override
    public final Field[] fields(Field... f) {
        return fields.fields(f);
    }

    @Override
    public final Field[] fields(String... fieldNames) {
        return fields.fields(fieldNames);
    }

    @Override
    public final Field[] fields(Name... fieldNames) {
        return fields.fields(fieldNames);
    }

    @Override
    public final Field[] fields(int... fieldIndexes) {
        return fields.fields(fieldIndexes);
    }

    // ------------------------------------------------------------------------
    // XXX: Record API
    // ------------------------------------------------------------------------

    @Override
    public final int size() {
        return fields.size();
    }

    @Override
    public final  T get(Field field) {
        return (T) get(indexOrFail(fieldsRow(), field));
    }

    @Override
    public final  T get(Field field, Class type) {
        return Convert.convert(get(field), type);
    }

    @Override
    public final  U get(Field field, Converter converter) {
        return converter.from(get(field));
    }

    @Override
    public final Object get(int index) {
        return values[safeIndex(index)];
    }

    @Override
    public final  T get(int index, Class type) {
        return Convert.convert(get(index), type);
    }

    @Override
    public final  U get(int index, Converter converter) {
        return Convert.convert(get(index), converter);
    }

    @Override
    public final Object get(String fieldName) {
        return get(indexOrFail(fieldsRow(), fieldName));
    }

    @Override
    public final  T get(String fieldName, Class type) {
        return Convert.convert(get(fieldName), type);
    }

    @Override
    public final  U get(String fieldName, Converter converter) {
        return Convert.convert(get(fieldName), converter);
    }

    @Override
    public final Object get(Name fieldName) {
        return get(indexOrFail(fieldsRow(), fieldName));
    }

    @Override
    public final  T get(Name fieldName, Class type) {
        return Convert.convert(get(fieldName), type);
    }

    @Override
    public final  U get(Name fieldName, Converter converter) {
        return Convert.convert(get(fieldName), converter);
    }

    /**
     * Subclasses may type-unsafely set a value to a record index. This method
     * takes care of converting the value to the appropriate type.
     *
     * @deprecated - Use {@link AbstractRecord#set(int, Object)} instead
     */
    @Deprecated
    protected final void setValue(int index, Object value) {
        set(index, value);
    }

    protected final void set(int index, Object value) {
        set(index, (Field) field(index), value);
    }

    @Override
    public final  void set(Field field, T value) {
        set(indexOrFail(fields, field), field, value);
    }

    final  void set(int index, Field field, T value) {
        // Relevant issues documenting this method's behaviour:
        // [#945] Avoid bugs resulting from setting the same value twice
        // [#948] To allow for controlling the number of hard-parses
        //        To allow for explicitly overriding default values
        // [#979] Avoid modifying chnaged flag on unchanged primary key values

        UniqueKey key = getPrimaryKey();

        // Normal fields' changed flag is always set to true
        if (key == null || !key.getFields().contains(field)) {
            changed.set(index);
        }

        // The primary key's changed flag might've been set previously
        else if (changed.get(index)) {
            changed.set(index);
        }

        // [#2764] Users may override updatability of primary key values
        else if (updatablePrimaryKeys(settings(this))) {
            changed.set(index);
        }

        // [#2698] If the primary key has not yet been set
        else if (originals[index] == null) {
            changed.set(index);
        }

        // [#979] If the primary key is being changed, all other fields' flags
        // need to be set to true for in case this record is stored again, an
        // INSERT statement will thus be issued
        else {

            // [#945] Be sure that changed is never reset to false
            changed.set(index, changed.get(index) || !StringUtils.equals(values[index], value));

            if (changed.get(index)) {
                changed(true);
            }
        }

        values[index] = value;
    }

    @Override
    public final  void set(Field field, U value, Converter converter) {
        set(field, converter.to(value));
    }

    @Override
    public /* non-final */  Record with(Field field, T value) {
        set(field, value);
        return this;
    }

    @Override
    public  Record with(Field field, U value, Converter converter) {
        set(field, value, converter);
        return this;
    }

    final void setValues(Field[] fields, AbstractRecord record) {
        fetched = record.fetched;

        for (Field field : fields) {
            int targetIndex = indexOrFail(fieldsRow(), field);
            int sourceIndex = indexOrFail(record.fieldsRow(), field);

            values[targetIndex] = record.get(sourceIndex);
            originals[targetIndex] = record.original(sourceIndex);
            changed.set(targetIndex, record.changed(sourceIndex));
        }
    }

    final void intern0(int fieldIndex) {
        safeIndex(fieldIndex);

        if (field(fieldIndex).getType() == String.class) {
            values[fieldIndex] = intern((String) values[fieldIndex]);
            originals[fieldIndex] = intern((String) originals[fieldIndex]);
        }
    }

    final int safeIndex(int index) {
        if (index >= 0 && index < values.length)
            return index;

        throw new IllegalArgumentException("No field at index " + index + " in Record type " + fieldsRow());
    }

    final String intern(String string) {
        return string == null ? null : string.intern();
    }

    /**
     * Subclasses may override this
     */
    UniqueKey getPrimaryKey() {
        return null;
    }

    /*
     * This method is overridden covariantly by TableRecordImpl
     */
    @Override
    public Record original() {
        return Tools.newRecord(fetched, (Class) getClass(), fields.fields.fields, configuration())
                    .operate(new RecordOperation() {

            @Override
            public AbstractRecord operate(AbstractRecord record) throws RuntimeException {
                for (int i = 0; i < originals.length; i++) {
                    record.values[i] = originals[i];
                    record.originals[i] = originals[i];
                }

                return record;
            }
        });
    }

    @Override
    public final  T original(Field field) {
        return (T) original(indexOrFail(fieldsRow(), field));
    }

    @Override
    public final Object original(int fieldIndex) {
        return originals[safeIndex(fieldIndex)];
    }

    @Override
    public final Object original(String fieldName) {
        return original(indexOrFail(fieldsRow(), fieldName));
    }

    @Override
    public final Object original(Name fieldName) {
        return original(indexOrFail(fieldsRow(), fieldName));
    }

    @Override
    public final boolean changed() {
        return !changed.isEmpty();
    }

    @Override
    public final boolean changed(Field field) {
        return changed(indexOrFail(fieldsRow(), field));
    }

    @Override
    public final boolean changed(int fieldIndex) {
        return changed.get(safeIndex(fieldIndex));
    }

    @Override
    public final boolean changed(String fieldName) {
        return changed(indexOrFail(fieldsRow(), fieldName));
    }

    @Override
    public final boolean changed(Name fieldName) {
        return changed(indexOrFail(fieldsRow(), fieldName));
    }

    @Override
    public final void changed(boolean c) {
        changed.set(0, values.length, c);

        // [#1995] If a value is meant to be "unchanged", the "original" should
        // match the supposedly "unchanged" value.
        if (!c) {
            System.arraycopy(values, 0, originals, 0, values.length);
        }
    }

    @Override
    public final void changed(Field field, boolean c) {
        changed(indexOrFail(fieldsRow(), field), c);
    }

    @Override
    public final void changed(int fieldIndex, boolean c) {
        safeIndex(fieldIndex);

        changed.set(fieldIndex, c);

        // [#1995] If a value is meant to be "unchanged", the "original" should
        // match the supposedly "unchanged" value.
        if (!c)
            originals[fieldIndex] = values[fieldIndex];
    }

    @Override
    public final void changed(String fieldName, boolean c) {
        changed(indexOrFail(fieldsRow(), fieldName), c);
    }

    @Override
    public final void changed(Name fieldName, boolean c) {
        changed(indexOrFail(fieldsRow(), fieldName), c);
    }

    @Override
    public final void reset() {
        changed.clear();

        System.arraycopy(originals, 0, values, 0, originals.length);
    }

    @Override
    public final void reset(Field field) {
        reset(indexOrFail(fieldsRow(), field));
    }

    @Override
    public final void reset(int fieldIndex) {
        safeIndex(fieldIndex);

        changed.clear(fieldIndex);
        values[fieldIndex] = originals[fieldIndex];
    }

    @Override
    public final void reset(String fieldName) {
        reset(indexOrFail(fieldsRow(), fieldName));
    }

    @Override
    public final void reset(Name fieldName) {
        reset(indexOrFail(fieldsRow(), fieldName));
    }

    @Override
    public final Object[] intoArray() {
        return into(Object[].class);
    }

    @Override
    public final List intoList() {
        return Arrays.asList(intoArray());
    }


    @Override
    public final Stream intoStream() {
        return into(Stream.class);
    }


    @Override
    public final Map intoMap() {
        Map map = new LinkedHashMap();

        int size = fields.size();
        for (int i = 0; i < size; i++) {
            Field field = fields.field(i);

            if (map.put(field.getName(), get(i)) != null) {
                throw new InvalidResultException("Field " + field.getName() + " is not unique in Record : " + this);
            }
        }

        return map;
    }

    @Override
    public final Record into(Field... f) {
        return Tools.newRecord(fetched, Record.class, f, configuration()).operate(new TransferRecordState(f));
    }

    // [jooq-tools] START [into-fields]

    @Override
    public final  Record1 into(Field field1) {
        return (Record1) into(new Field[] { field1 });
    }

    @Override
    public final  Record2 into(Field field1, Field field2) {
        return (Record2) into(new Field[] { field1, field2 });
    }

    @Override
    public final  Record3 into(Field field1, Field field2, Field field3) {
        return (Record3) into(new Field[] { field1, field2, field3 });
    }

    @Override
    public final  Record4 into(Field field1, Field field2, Field field3, Field field4) {
        return (Record4) into(new Field[] { field1, field2, field3, field4 });
    }

    @Override
    public final  Record5 into(Field field1, Field field2, Field field3, Field field4, Field field5) {
        return (Record5) into(new Field[] { field1, field2, field3, field4, field5 });
    }

    @Override
    public final  Record6 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6) {
        return (Record6) into(new Field[] { field1, field2, field3, field4, field5, field6 });
    }

    @Override
    public final  Record7 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7) {
        return (Record7) into(new Field[] { field1, field2, field3, field4, field5, field6, field7 });
    }

    @Override
    public final  Record8 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8) {
        return (Record8) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8 });
    }

    @Override
    public final  Record9 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8, Field field9) {
        return (Record9) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8, field9 });
    }

    @Override
    public final  Record10 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8, Field field9, Field field10) {
        return (Record10) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8, field9, field10 });
    }

    @Override
    public final  Record11 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8, Field field9, Field field10, Field field11) {
        return (Record11) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11 });
    }

    @Override
    public final  Record12 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8, Field field9, Field field10, Field field11, Field field12) {
        return (Record12) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11, field12 });
    }

    @Override
    public final  Record13 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8, Field field9, Field field10, Field field11, Field field12, Field field13) {
        return (Record13) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11, field12, field13 });
    }

    @Override
    public final  Record14 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8, Field field9, Field field10, Field field11, Field field12, Field field13, Field field14) {
        return (Record14) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11, field12, field13, field14 });
    }

    @Override
    public final  Record15 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8, Field field9, Field field10, Field field11, Field field12, Field field13, Field field14, Field field15) {
        return (Record15) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11, field12, field13, field14, field15 });
    }

    @Override
    public final  Record16 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8, Field field9, Field field10, Field field11, Field field12, Field field13, Field field14, Field field15, Field field16) {
        return (Record16) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11, field12, field13, field14, field15, field16 });
    }

    @Override
    public final  Record17 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8, Field field9, Field field10, Field field11, Field field12, Field field13, Field field14, Field field15, Field field16, Field field17) {
        return (Record17) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11, field12, field13, field14, field15, field16, field17 });
    }

    @Override
    public final  Record18 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8, Field field9, Field field10, Field field11, Field field12, Field field13, Field field14, Field field15, Field field16, Field field17, Field field18) {
        return (Record18) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11, field12, field13, field14, field15, field16, field17, field18 });
    }

    @Override
    public final  Record19 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8, Field field9, Field field10, Field field11, Field field12, Field field13, Field field14, Field field15, Field field16, Field field17, Field field18, Field field19) {
        return (Record19) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11, field12, field13, field14, field15, field16, field17, field18, field19 });
    }

    @Override
    public final  Record20 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8, Field field9, Field field10, Field field11, Field field12, Field field13, Field field14, Field field15, Field field16, Field field17, Field field18, Field field19, Field field20) {
        return (Record20) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11, field12, field13, field14, field15, field16, field17, field18, field19, field20 });
    }

    @Override
    public final  Record21 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8, Field field9, Field field10, Field field11, Field field12, Field field13, Field field14, Field field15, Field field16, Field field17, Field field18, Field field19, Field field20, Field field21) {
        return (Record21) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11, field12, field13, field14, field15, field16, field17, field18, field19, field20, field21 });
    }

    @Override
    public final  Record22 into(Field field1, Field field2, Field field3, Field field4, Field field5, Field field6, Field field7, Field field8, Field field9, Field field10, Field field11, Field field12, Field field13, Field field14, Field field15, Field field16, Field field17, Field field18, Field field19, Field field20, Field field21, Field field22) {
        return (Record22) into(new Field[] { field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11, field12, field13, field14, field15, field16, field17, field18, field19, field20, field21, field22 });
    }

// [jooq-tools] END [into-fields]

    @Override
    public final  E into(Class type) {
        return (E) Tools.configuration(this).recordMapperProvider().provide(fields.fields, type).map(this);
    }

    @Override
    public final  E into(E object) {
        if (object == null) {
            throw new NullPointerException("Cannot copy Record into null");
        }

        Class type = (Class) object.getClass();

        try {
            return new DefaultRecordMapper(fields.fields, type, object, configuration()).map(this);
        }

        // Pass MappingExceptions on to client code
        catch (MappingException e) {
            throw e;
        }

        // All other reflection exceptions are intercepted
        catch (Exception e) {
            throw new MappingException("An error ocurred when mapping record to " + type, e);
        }
    }

    @Override
    public final  R into(Table table) {
        return Tools.newRecord(fetched, table, configuration()).operate(new TransferRecordState(table.fields()));
    }

    final  R intoRecord(Class type) {
        return Tools.newRecord(fetched, type, fields(), configuration()).operate(new TransferRecordState(null));
    }

    private class TransferRecordState implements RecordOperation {

        private final Field[] targetFields;

        TransferRecordState(Field[] targetFields) {
            this.targetFields = targetFields;
        }

        @Override
        public R operate(R target) throws MappingException {
            AbstractRecord source = AbstractRecord.this;

            try {

                // [#1522] [#2989] If possible the complete state of this record should be copied onto the other record
                if (target instanceof AbstractRecord) {
                    AbstractRecord t = (AbstractRecord) target;

                    // Iterate over target fields, to avoid ambiguities when two source fields share the same name.
                    // [#3634] If external targetFields are provided, use those instead of the target record's fields.
                    //         The record doesn't know about aliased tables, for instance.
                    for (int targetIndex = 0; targetIndex < (targetFields != null ? targetFields.length : t.size()); targetIndex++) {
                        Field targetField = (targetFields != null ? targetFields[targetIndex] : t.field(targetIndex));
                        int sourceIndex = fields.indexOf(targetField);

                        if (sourceIndex >= 0) {
                            DataType targetType = targetField.getDataType();

                            t.values[targetIndex] = targetType.convert(values[sourceIndex]);
                            t.originals[targetIndex] = targetType.convert(originals[sourceIndex]);
                            t.changed.set(targetIndex, changed.get(sourceIndex));
                        }
                    }
                }

                else {
                    for (Field targetField : target.fields()) {
                        Field sourceField = field(targetField);

                        if (sourceField != null) {
                            Tools.setValue(target, targetField, source, sourceField);
                        }
                    }
                }

                return target;
            }

            // All reflection exceptions are intercepted
            catch (Exception e) {
                throw new MappingException("An error ocurred when mapping record to " + target, e);
            }
        }
    }

    @Override
    public final ResultSet intoResultSet() {
        ResultImpl result = new ResultImpl(configuration(), fields.fields.fields);
        result.add(this);
        return result.intoResultSet();
    }

    @Override
    public final  E map(RecordMapper mapper) {
        return mapper.map(this);
    }

    private final void from0(Object source, Fields f) {
        if (source == null) return;

        // [#2520] TODO: Benchmark this from() method. There's probably a better implementation
        from(Tools.configuration(this).recordUnmapperProvider().provide(source.getClass(), f).unmap(prepareArrayForUnmap(source, f)));

        // [#2700] [#3582] If a POJO attribute is NULL, but the column is NOT NULL
        // then we should let the database apply DEFAULT values
        resetChangedOnNotNull(this);
    }

    private final Object prepareArrayForUnmap(Object source, Fields f) {
        if (source instanceof Object[]) {
            Object[] array = (Object[]) source;

            if (array.length != f.size()) {
                Object[] result = new Object[f.size()];

                for (int i = 0; i < result.length; i++) {
                    int index = fields.indexOf(f.field(i));
                    result[i] = index >= 0 && index < array.length ? array[index] : null;
                }

                return result;
            }
            else
                return source;
        }
        else
            return source;
    }

    @Override
    public final void from(Object source) {
        from0(source, fields.fields);
    }

    @Override
    public final void from(Object source, Field... f) {
        from0(source, new Fields(f));
    }

    @Override
    public final void from(Object source, String... fieldNames) {
        from(source, fields(fieldNames));
    }

    @Override
    public final void from(Object source, Name... fieldNames) {
        from(source, fields(fieldNames));
    }

    @Override
    public final void from(Object source, int... fieldIndexes) {
        from(source, fields(fieldIndexes));
    }

    @Override
    public final void fromMap(Map map) {
        from(map, fields());
    }

    @Override
    public final void fromMap(Map map, Field... f) {
        from0(map, new Fields(f));
    }

    @Override
    public final void fromMap(Map map, String... fieldNames) {
        fromMap(map, fields(fieldNames));
    }

    @Override
    public final void fromMap(Map map, Name... fieldNames) {
        fromMap(map, fields(fieldNames));
    }

    @Override
    public final void fromMap(Map map, int... fieldIndexes) {
        fromMap(map, fields(fieldIndexes));
    }

    @Override
    public final void fromArray(Object... array) {
        fromArray(array, fields());
    }

    @Override
    public final void fromArray(Object[] array, Field... f) {
        from0(array, new Fields(f));
    }

    @Override
    public final void fromArray(Object[] array, String... fieldNames) {
        fromArray(array, fields(fieldNames));
    }

    @Override
    public final void fromArray(Object[] array, Name... fieldNames) {
        fromArray(array, fields(fieldNames));
    }

    @Override
    public final void fromArray(Object[] array, int... fieldIndexes) {
        fromArray(array, fields(fieldIndexes));
    }

    /**
     * This method was implemented with [#799]. It may be useful to make it
     * public for broader use...?
     */
    protected final void from(Record source) {
        for (Field field : fields.fields.fields) {
            Field sourceField = source.field(field);

            if (sourceField != null && source.changed(sourceField))
                Tools.setValue(this, field, source, sourceField);
        }
    }

    // -------------------------------------------------------------------------
    // Formatting methods
    // -------------------------------------------------------------------------

    @Override
    public final String formatJSON() {
        StringWriter writer = new StringWriter();
        formatJSON(writer);
        return writer.toString();
    }

    @Override
    public final String formatJSON(JSONFormat format) {
        StringWriter writer = new StringWriter();
        formatJSON(writer, format);
        return writer.toString();
    }

    @Override
    public final void formatJSON(OutputStream stream) {
        formatJSON(new OutputStreamWriter(stream));
    }

    @Override
    public final void formatJSON(OutputStream stream, JSONFormat format) {
        formatJSON(new OutputStreamWriter(stream), format);
    }

    @Override
    public final void formatJSON(Writer writer) {
        formatJSON(writer, JSONFormat.DEFAULT_FOR_RECORDS);
    }

    @Override
    public final void formatJSON(Writer writer, JSONFormat format) {
        if (format.header())
            log.debug("JSONFormat.header currently not supported for Record.formatJSON()");

        try {
            switch (format.recordFormat()) {
                case ARRAY:
                    ResultImpl.formatJSONArray0(this, fields.fields, format, 0, writer);
                    break;
                case OBJECT:
                    ResultImpl.formatJSONMap0(this, fields.fields, format, 0, writer);
                    break;
                default:
                    throw new IllegalArgumentException("Format not supported: " + format);
            }
        }
        catch (java.io.IOException e) {
            throw new IOException("Exception while writing JSON", e);
        }
    }

    @Override
    public final String formatXML() {
        return formatXML(XMLFormat.DEFAULT_FOR_RECORDS);
    }

    @Override
    public final String formatXML(XMLFormat format) {
        StringWriter writer = new StringWriter();
        formatXML(writer, format);
        return writer.toString();
    }

    @Override
    public final void formatXML(OutputStream stream) {
        formatXML(stream, XMLFormat.DEFAULT_FOR_RECORDS);
    }

    @Override
    public final void formatXML(OutputStream stream, XMLFormat format) {
        formatXML(new OutputStreamWriter(stream), format);
    }

    @Override
    public final void formatXML(Writer writer) {
        formatXML(writer, XMLFormat.DEFAULT_FOR_RECORDS);
    }

    @Override
    public final void formatXML(Writer writer, XMLFormat format) {
        if (format.header())
            log.debug("XMLFormat.header currently not supported for Record.formatXML()");

        try {
            ResultImpl.formatXMLRecord(writer, format, 0, this, fields.fields);
        }
        catch (java.io.IOException e) {
            throw new IOException("Exception while writing XML", e);
        }
    }

    // ------------------------------------------------------------------------
    // XXX: Object and Comparable API
    // ------------------------------------------------------------------------

    @Override
    public String toString() {
        // [#3900] Nested records should generate different toString() behaviour
        return ThreadGuard.run(RECORD_TOSTRING, new GuardedOperation() {
            @Override
            public String unguarded() {
                Result result = new ResultImpl(configuration(), fields.fields.fields);
                result.add(AbstractRecord.this);
                return result.toString();
            }

            @Override
            public String guarded() {
                return valuesRow().toString();
            }
        });
    }

    @Override
    public int compareTo(Record that) {
        // Note: keep this implementation in-sync with AbstractStore.equals()!

        if (that == null) {
            throw new NullPointerException();
        }
        if (size() != that.size()) {
            throw new ClassCastException(String.format("Trying to compare incomparable records (wrong degree):\n%s\n%s", this, that));
        }

        Class[] thisTypes = this.fieldsRow().types();
        Class[] thatTypes = that.fieldsRow().types();

        if (!asList(thisTypes).equals(asList(thatTypes))) {
            throw new ClassCastException(String.format("Trying to compare incomparable records (type mismatch):\n%s\n%s", this, that));
        }

        for (int i = 0; i < size(); i++) {
            final Object thisValue = get(i);
            final Object thatValue = that.get(i);

            // [#1850] Only return -1/+1 early. In all other cases,
            // continue checking the remaining fields
            if (thisValue == null && thatValue == null) {
                continue;
            }

            // Order column values in a SQL NULLS LAST manner
            else if (thisValue == null) {
                return 1;
            }

            else if (thatValue == null) {
                return -1;
            }

            // [#985] Compare arrays too.
            else if (thisValue.getClass().isArray() && thatValue.getClass().isArray()) {

                // Might be byte[]
                if (thisValue.getClass() == byte[].class) {
                    int compare = compare((byte[]) thisValue, (byte[]) thatValue);

                    if (compare != 0) {
                        return compare;
                    }
                }

                // Other primitive types are not expected
                else if (!thisValue.getClass().getComponentType().isPrimitive()) {
                    int compare = compare((Object[]) thisValue, (Object[]) thatValue);

                    if (compare != 0) {
                        return compare;
                    }
                }

                else {
                    throw new ClassCastException(String.format("Unsupported data type in natural ordering: %s", thisValue.getClass()));
                }
            }
            else {
                int compare = ((Comparable) thisValue).compareTo(thatValue);

                if (compare != 0) {
                    return compare;
                }
            }
        }

        // If we got through the above loop, the two records are equal
        return 0;
    }

    /**
     * Compare two byte arrays
     */
    final int compare(byte[] array1, byte[] array2) {
        int length = Math.min(array1.length, array2.length);

        for (int i = 0; i < length; i++) {
            int v1 = (array1[i] & 0xff);
            int v2 = (array2[i] & 0xff);

            if (v1 != v2) {
                return v1 < v2 ? -1 : 1;
            }
        }

        return array1.length - array2.length;
    }

    /**
     * Compare two arrays
     */
    final int compare(Object[] array1, Object[] array2) {
        int length = Math.min(array1.length, array2.length);

        for (int i = 0; i < length; i++) {
            int compare = ((Comparable) array1[i]).compareTo(array2[i]);

            if (compare != 0) {
                return compare;
            }
        }

        return array1.length - array2.length;
    }

    // -------------------------------------------------------------------------
    // XXX: Deprecated and discouraged methods
    // -------------------------------------------------------------------------

    @Override
    public final  T getValue(Field field) {
        return get(field);
    }

    @Override
    @Deprecated
    public final  T getValue(Field field, T defaultValue) {
        T result = getValue(field);
        return result != null ? result : defaultValue;
    }

    @Override
    public final  T getValue(Field field, Class type) {
        return get(field, type);
    }

    @Override
    @Deprecated
    public final  T getValue(Field field, Class type, T defaultValue) {
        final T result = get(field, type);
        return result == null ? defaultValue : result;
    }

    @Override
    public final  U getValue(Field field, Converter converter) {
        return get(field, converter);
    }

    @Override
    @Deprecated
    public final  U getValue(Field field, Converter converter, U defaultValue) {
        final U result = get(field, converter);
        return result == null ? defaultValue : result;
    }

    @Override
    public final Object getValue(int index) {
        return get(index);
    }

    @Override
    @Deprecated
    public final Object getValue(int index, Object defaultValue) {
        final Object result = get(index);
        return result == null ? defaultValue : result;
    }

    @Override
    public final  T getValue(int index, Class type) {
        return get(index, type);
    }

    @Override
    @Deprecated
    public final  T getValue(int index, Class type, T defaultValue) {
        final T result = get(index, type);
        return result == null ? defaultValue : result;
    }

    @Override
    public final  U getValue(int index, Converter converter) {
        return get(index, converter);
    }

    @Override
    @Deprecated
    public final  U getValue(int index, Converter converter, U defaultValue) {
        final U result = get(index, converter);
        return result == null ? defaultValue : result;
    }

    @Override
    public final Object getValue(String fieldName) {
        return get(fieldName);
    }

    @Override
    @Deprecated
    public final Object getValue(String fieldName, Object defaultValue) {
        return getValue(indexOrFail(fieldsRow(), fieldName), defaultValue);
    }

    @Override
    public final  T getValue(String fieldName, Class type) {
        return get(fieldName, type);
    }

    @Override
    @Deprecated
    public final  T getValue(String fieldName, Class type, T defaultValue) {
        final T result = get(fieldName, type);
        return result == null ? defaultValue : result;
    }

    @Override
    public final  U getValue(String fieldName, Converter converter) {
        return get(fieldName, converter);
    }

    @Override
    @Deprecated
    public final  U getValue(String fieldName, Converter converter, U defaultValue) {
        final U result = get(fieldName, converter);
        return result == null ? defaultValue : result;
    }

    @Override
    public final Object getValue(Name fieldName) {
        return get(fieldName);
    }

    @Override
    public final  T getValue(Name fieldName, Class type) {
        return get(fieldName, type);
    }

    @Override
    public final  U getValue(Name fieldName, Converter converter) {
        return get(fieldName, converter);
    }

    @Override
    public final  void setValue(Field field, T value) {
        set(field, value);
    }

    @Override
    public final  void setValue(Field field, U value, Converter converter) {
        set(field, value, converter);
    }

}