diff --git a/src/main/java/net/openhft/chronicle/wire/WireMarshaller.java b/src/main/java/net/openhft/chronicle/wire/WireMarshaller.java index 0b244490af..3741e4cd53 100644 --- a/src/main/java/net/openhft/chronicle/wire/WireMarshaller.java +++ b/src/main/java/net/openhft/chronicle/wire/WireMarshaller.java @@ -450,6 +450,7 @@ abstract static class FieldAccess { Comment commentAnnotation; Boolean isLeaf; + boolean isFinalNoWarning; FieldAccess(@NotNull Field field) { this(field, null); @@ -461,6 +462,10 @@ abstract static class FieldAccess { offset = unsafeObjectFieldOffset(field); key = field::getName; this.isLeaf = isLeaf; + + if ((field.getModifiers() & Modifier.FINAL) != 0) + isFinalNoWarning = true; + try { commentAnnotation = field.getAnnotation(Comment.class); } catch (NullPointerException ignore) { @@ -613,6 +618,8 @@ protected boolean sameValue(Object o, Object o2) throws IllegalAccessException { } protected void copy(Object from, Object to) throws IllegalAccessException { + triggerFinalWarning(); + ObjectUtils.requireNonNull(from); ObjectUtils.requireNonNull(to); @@ -622,6 +629,8 @@ protected void copy(Object from, Object to) throws IllegalAccessException { protected abstract void getValue(Object o, ValueOut write, Object previous) throws IllegalAccessException; protected void readValue(Object o, Object defaults, ValueIn read, boolean overwrite) throws IllegalAccessException { + triggerFinalWarning(); + if (!read.isPresent()) { if (overwrite && defaults != null) copy(Objects.requireNonNull(defaults), o); @@ -641,6 +650,14 @@ protected void readValue(Object o, Object defaults, ValueIn read, boolean overwr } } + protected void triggerFinalWarning() { + if (isFinalNoWarning) { + Jvm.warn().on(WireMarshaller.class, "Found final field " + field.getName() + " in " + + field.getDeclaringClass() + ", final fields cannot be updated safely and will be ignored in future releases"); + isFinalNoWarning = false; + } + } + protected abstract void setValue(Object o, ValueIn read, boolean overwrite) throws IllegalAccessException; public abstract void getAsBytes(Object o, Bytes bytes) throws IllegalAccessException; @@ -1093,6 +1110,8 @@ protected void getValue(final Object o, final ValueOut write, final Object previ @Override protected void readValue(Object o, Object defaults, ValueIn read, boolean overwrite) throws IllegalAccessException { + triggerFinalWarning(); + EnumSet coll = (EnumSet) field.get(o); if (coll == null) { coll = enumSetSupplier.get(); @@ -1248,6 +1267,8 @@ protected void copy(Object from, Object to) throws IllegalAccessException { @Override protected void readValue(Object o, Object defaults, ValueIn read, boolean overwrite) throws IllegalAccessException { + triggerFinalWarning(); + Collection coll = (Collection) field.get(o); if (coll == null) { coll = collectionSupplier.get(); @@ -1337,6 +1358,8 @@ protected void getValue(Object o, @NotNull ValueOut write, Object previous) thro @Override protected void readValue(Object o, Object defaults, ValueIn read, boolean overwrite) throws IllegalAccessException { + triggerFinalWarning(); + Collection coll = (Collection) field.get(o); if (coll == null) { coll = collectionSupplier.get(); @@ -1417,6 +1440,8 @@ protected void copy(Object from, Object to) throws IllegalAccessException { @Override protected void readValue(Object o, Object defaults, ValueIn read, boolean overwrite) throws IllegalAccessException { + triggerFinalWarning(); + Map map = (Map) field.get(o); if (map == null) { map = collectionSupplier.get(); diff --git a/src/test/java/net/openhft/chronicle/wire/FinalFieldsTest.java b/src/test/java/net/openhft/chronicle/wire/FinalFieldsTest.java new file mode 100644 index 0000000000..7f9d61b66a --- /dev/null +++ b/src/test/java/net/openhft/chronicle/wire/FinalFieldsTest.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016-2020 chronicle.software + * + * 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 net.openhft.chronicle.wire; + +import net.openhft.chronicle.bytes.Bytes; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class FinalFieldsTest extends WireTestCommon { + @SuppressWarnings("rawtypes") + @Test + public void testCopy() { + expectException("Found final field map"); + expectException("Found final field array"); + expectException("Found final field intValue"); + expectException("Found final field value"); + + Bytes bytesFrom = Bytes.allocateElasticOnHeap(64); + Wire wireFrom = WireType.BINARY.apply(bytesFrom); + Bytes bytesTo = Bytes.allocateElasticOnHeap(64); + Wire wireTo = WireType.JSON.apply(bytesTo); + + FinalFieldsClass a = create(); + + wireFrom.getValueOut().marshallable(a); + + wireFrom.copyTo(wireTo); + FinalFieldsClass b = wireTo.getValueIn().object(FinalFieldsClass.class); + + assertEquals(a, b); + } + + private FinalFieldsClass create() { + Map map = new HashMap<>(); + map.put(CcyPair.EURUSD, "eurusd"); + return new FinalFieldsClass(map, new String[]{"hello", "there"}, 11, 123.4); + } + + @SuppressWarnings("unused") + private static class FinalFieldsClass extends SelfDescribingMarshallable { + final Map map; + final String[] array; + final int intValue; + final double value; + + public FinalFieldsClass(Map map, String[] array, int intValue, double value) { + this.map = map; + this.array = array; + this.intValue = intValue; + this.value = value; + } + } +}