From bd41e72f09c09f3edd021976198e635ad9433699 Mon Sep 17 00:00:00 2001 From: Mark Raynsford Date: Sat, 21 Nov 2015 15:07:32 +0000 Subject: [PATCH] Implement strings Implement code generation for string types. Fix record type offsets. Fix string sizes. Fix #2 Fix #7 --- .../core/checker/JPRACheckerContract.java | 15 +- .../compiler/tests/java/generation/code.jpr | 22 ++ .../tests/java/output/MatricesTest.java | 10 +- .../tests/java/output/NestedString0Test.java | 73 +++++ .../compiler/java/JPRAGeneratedNames.java | 48 ++-- ...eldImplementationConstructorProcessor.java | 25 +- .../RecordFieldImplementationProcessor.java | 26 +- .../java/RecordFieldInterfaceProcessor.java | 31 +- .../com/io7m/jpra/model/types/TString.java | 13 +- .../runtime/java/JPRAStringByteBuffered.java | 187 ++++++++++++ .../runtime/java/JPRAStringReadableType.java | 66 +++++ .../runtime/java/JPRAStringTruncation.java | 36 +++ .../jpra/runtime/java/JPRAStringType.java | 27 ++ .../runtime/java/JPRAStringWritableType.java | 35 +++ .../java/JPRAStringByteBufferedTest.java | 271 ++++++++++++++++++ 15 files changed, 847 insertions(+), 38 deletions(-) create mode 100644 io7m-jpra-compiler-java-tests/src/test/java/com/io7m/jpra/compiler/tests/java/output/NestedString0Test.java create mode 100644 io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringByteBuffered.java create mode 100644 io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringReadableType.java create mode 100644 io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringTruncation.java create mode 100644 io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringType.java create mode 100644 io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringWritableType.java create mode 100644 io7m-jpra-runtime-java/src/test/java/com/io7m/jpra/tests/runtime/java/JPRAStringByteBufferedTest.java diff --git a/io7m-jpra-compiler-core/src/test/java/com/io7m/jpra/tests/compiler/core/checker/JPRACheckerContract.java b/io7m-jpra-compiler-core/src/test/java/com/io7m/jpra/tests/compiler/core/checker/JPRACheckerContract.java index 33bc856..451ddd5 100644 --- a/io7m-jpra-compiler-core/src/test/java/com/io7m/jpra/tests/compiler/core/checker/JPRACheckerContract.java +++ b/io7m-jpra-compiler-core/src/test/java/com/io7m/jpra/tests/compiler/core/checker/JPRACheckerContract.java @@ -227,7 +227,12 @@ protected abstract SExpressionType newStringSExpr( final TypeExprString e = TypeExprString.class.cast(ch.checkTypeExpression(te)); final TString ti = TString.class.cast(e.getType()); - Assert.assertEquals(BigInteger.valueOf(256L), ti.getSizeInBits().getValue()); + Assert.assertEquals( + BigInteger.valueOf(288L), + ti.getSizeInBits().getValue()); + Assert.assertEquals( + BigInteger.valueOf(32L), + ti.getMaximumStringLength().getValue()); Assert.assertEquals("UTF-8", ti.getEncoding()); } @@ -268,7 +273,9 @@ protected abstract SExpressionType newStringSExpr( final TypeExprArray e = TypeExprArray.class.cast(ch.checkTypeExpression(te)); final TArray ta = TArray.class.cast(e.getType()); - Assert.assertEquals(BigInteger.valueOf(32L * 32L), ta.getSizeInBits().getValue()); + Assert.assertEquals( + BigInteger.valueOf(32L * 32L), + ta.getSizeInBits().getValue()); Assert.assertEquals( BigInteger.valueOf(32L), ta.getElementCount().getValue()); Assert.assertEquals(TIntegerSigned.class, ta.getElementType().getClass()); @@ -636,7 +643,9 @@ c, new CapsSupportingIntegerMatrices( final TRecord.FieldPaddingOctets f = TRecord.FieldPaddingOctets.class.cast( tt.getFieldsInDeclarationOrder().get(0)); - Assert.assertEquals(BigInteger.valueOf(8L * 8L), f.getSizeInBits().getValue()); + Assert.assertEquals( + BigInteger.valueOf(8L * 8L), + f.getSizeInBits().getValue()); } @Test public final void testTypeDeclRecordPaddingOctets_Error0() diff --git a/io7m-jpra-compiler-java-tests/src/main/resources/com/io7m/jpra/compiler/tests/java/generation/code.jpr b/io7m-jpra-compiler-java-tests/src/main/resources/com/io7m/jpra/compiler/tests/java/generation/code.jpr index cea2d01..2195776 100644 --- a/io7m-jpra-compiler-java-tests/src/main/resources/com/io7m/jpra/compiler/tests/java/generation/code.jpr +++ b/io7m-jpra-compiler-java-tests/src/main/resources/com/io7m/jpra/compiler/tests/java/generation/code.jpr @@ -78,6 +78,28 @@ (field m4d [matrix [float 64] 4 4]) ]) +(record Strings [ + (field s [string 64 "UTF-8"]) + (field x [integer signed 32]) +]) + +(record NestedString2 [ + (padding-octets 4) + (field s [string 4 "UTF-8"]) +]) + +(record NestedString1 [ + (padding-octets 4) + (field s [string 4 "UTF-8"]) + (field n NestedString2) +]) + +(record NestedString0 [ + (padding-octets 4) + (field s [string 4 "UTF-8"]) + (field n NestedString1) +]) + (packed OpenGL565 [ (field r [integer unsigned-normalized 5]) (field g [integer unsigned-normalized 6]) diff --git a/io7m-jpra-compiler-java-tests/src/test/java/com/io7m/jpra/compiler/tests/java/output/MatricesTest.java b/io7m-jpra-compiler-java-tests/src/test/java/com/io7m/jpra/compiler/tests/java/output/MatricesTest.java index 2ca7fe6..6d1185c 100644 --- a/io7m-jpra-compiler-java-tests/src/test/java/com/io7m/jpra/compiler/tests/java/output/MatricesTest.java +++ b/io7m-jpra-compiler-java-tests/src/test/java/com/io7m/jpra/compiler/tests/java/output/MatricesTest.java @@ -218,7 +218,7 @@ private static void check4x4FZero(final MatrixReadable4x4FType m) MatricesTest.check3x3DZero(v.getM3dReadable()); MatricesTest.check3x3FZero(v.getM3fReadable()); // MatricesTest.check4x4FZero(v.getM4fReadable()); - check4x4DZero(v.getM4dReadable()); + MatricesTest.check4x4DZero(v.getM4dReadable()); final MatrixReadable4x4FType k = v.getM4fReadable(); @@ -279,7 +279,7 @@ private static void check4x4FZero(final MatrixReadable4x4FType m) // MatricesTest.check3x3DZero(v.getM3dReadable()); MatricesTest.check3x3FZero(v.getM3fReadable()); MatricesTest.check4x4FZero(v.getM4fReadable()); - check4x4DZero(v.getM4dReadable()); + MatricesTest.check4x4DZero(v.getM4dReadable()); final MatrixReadable3x3DType k = v.getM3dReadable(); @@ -332,7 +332,7 @@ private static void check4x4FZero(final MatrixReadable4x4FType m) MatricesTest.check3x3DZero(v.getM3dReadable()); // MatricesTest.check3x3FZero(v.getM3fReadable()); MatricesTest.check4x4FZero(v.getM4fReadable()); - check4x4DZero(v.getM4dReadable()); + MatricesTest.check4x4DZero(v.getM4dReadable()); final MatrixReadable3x3FType k = v.getM3fReadable(); @@ -379,7 +379,7 @@ private static void check4x4FZero(final MatrixReadable4x4FType m) MatricesTest.check3x3DZero(v.getM3dReadable()); MatricesTest.check2x2FZero(v.getM3fReadable()); MatricesTest.check4x4FZero(v.getM4fReadable()); - check4x4DZero(v.getM4dReadable()); + MatricesTest.check4x4DZero(v.getM4dReadable()); final MatrixReadable2x2DType k = v.getM2dReadable(); @@ -420,7 +420,7 @@ private static void check4x4FZero(final MatrixReadable4x4FType m) MatricesTest.check3x3DZero(v.getM3dReadable()); MatricesTest.check3x3FZero(v.getM3fReadable()); MatricesTest.check4x4FZero(v.getM4fReadable()); - check4x4DZero(v.getM4dReadable()); + MatricesTest.check4x4DZero(v.getM4dReadable()); final MatrixReadable2x2FType k = v.getM2fReadable(); diff --git a/io7m-jpra-compiler-java-tests/src/test/java/com/io7m/jpra/compiler/tests/java/output/NestedString0Test.java b/io7m-jpra-compiler-java-tests/src/test/java/com/io7m/jpra/compiler/tests/java/output/NestedString0Test.java new file mode 100644 index 0000000..0fa45e5 --- /dev/null +++ b/io7m-jpra-compiler-java-tests/src/test/java/com/io7m/jpra/compiler/tests/java/output/NestedString0Test.java @@ -0,0 +1,73 @@ +/* + * Copyright © 2015 http://io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package com.io7m.jpra.compiler.tests.java.output; + +import com.io7m.jpra.compiler.tests.java.generation.code + .NestedString0ByteBuffered; +import com.io7m.jpra.compiler.tests.java.generation.code.NestedString0Type; +import com.io7m.jpra.compiler.tests.java.generation.code + .NestedString1WritableType; +import com.io7m.jpra.compiler.tests.java.generation.code + .NestedString2WritableType; +import com.io7m.jpra.runtime.java.JPRACursor1DByteBufferedChecked; +import com.io7m.jpra.runtime.java.JPRACursor1DType; +import com.io7m.jpra.runtime.java.JPRAStringTruncation; +import com.io7m.jpra.runtime.java.JPRAStringType; +import org.junit.Assert; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public final class NestedString0Test +{ + @Test public void testNesting() + { + final ByteBuffer buf = ByteBuffer.allocate(2 * 36); + final JPRACursor1DType c = + JPRACursor1DByteBufferedChecked.newCursor( + buf, + NestedString0ByteBuffered::newValueWithOffset); + + for (int index = 0; index < 2; ++index) { + c.setElementIndex(index); + + final NestedString0Type n0 = c.getElementView(); + final JPRAStringType n0s = n0.getSWritable(); + final NestedString1WritableType n1w = n0.getNWritable(); + final JPRAStringType n1s = n1w.getSWritable(); + final NestedString2WritableType n2w = n1w.getNWritable(); + final JPRAStringType n2s = n2w.getSWritable(); + + Assert.assertEquals("", n0s.getNewValue()); + Assert.assertEquals("", n1s.getNewValue()); + Assert.assertEquals("", n2s.getNewValue()); + + n0s.setValue("ABCD", JPRAStringTruncation.TRUNCATE); + n1s.setValue("EFGH", JPRAStringTruncation.TRUNCATE); + n2s.setValue("IJKL", JPRAStringTruncation.TRUNCATE); + + final int offset = index * 36; + BufferChecks.checkRangeInclusiveIsZero(buf, offset + 0, offset + 3); + BufferChecks.checkRangeInclusiveIsZero(buf, offset + 12, offset + 15); + BufferChecks.checkRangeInclusiveIsZero(buf, offset + 24, offset + 27); + + Assert.assertEquals("ABCD", n0s.getNewValue()); + Assert.assertEquals("EFGH", n1s.getNewValue()); + Assert.assertEquals("IJKL", n2s.getNewValue()); + } + } +} diff --git a/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/JPRAGeneratedNames.java b/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/JPRAGeneratedNames.java index 0f276f9..ade7de4 100644 --- a/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/JPRAGeneratedNames.java +++ b/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/JPRAGeneratedNames.java @@ -115,22 +115,12 @@ static String getSetterBooleanSetName( static String getGetterRecordReadableName(final FieldName name) { - final String text = JPRAGeneratedNames.getRecased(name.toString()); - final StringBuilder sb = new StringBuilder(128); - sb.append("get"); - sb.append(text); - sb.append("Readable"); - return sb.toString(); + return JPRAGeneratedNames.getObjectReadableName(name); } static String getGetterRecordWritableName(final FieldName name) { - final String text = JPRAGeneratedNames.getRecased(name.toString()); - final StringBuilder sb = new StringBuilder(128); - sb.append("get"); - sb.append(text); - sb.append("Writable"); - return sb.toString(); + return JPRAGeneratedNames.getObjectWritableName(name); } static String getRecordImplementationByteBufferedName( @@ -200,6 +190,11 @@ static String getNormalizedRawSetterName(final FieldName name) } public static String getGetterVectorReadableName(final FieldName name) + { + return JPRAGeneratedNames.getObjectReadableName(name); + } + + private static String getObjectReadableName(final FieldName name) { final String text = JPRAGeneratedNames.getRecased(name.toString()); final StringBuilder sb = new StringBuilder(128); @@ -211,25 +206,20 @@ public static String getGetterVectorReadableName(final FieldName name) public static String getGetterVectorWritableName(final FieldName name) { - final String text = JPRAGeneratedNames.getRecased(name.toString()); - final StringBuilder sb = new StringBuilder(128); - sb.append("get"); - sb.append(text); - sb.append("Writable"); - return sb.toString(); + return JPRAGeneratedNames.getObjectWritableName(name); } public static String getGetterMatrixReadableName(final FieldName name) { - final String text = JPRAGeneratedNames.getRecased(name.toString()); - final StringBuilder sb = new StringBuilder(128); - sb.append("get"); - sb.append(text); - sb.append("Readable"); - return sb.toString(); + return JPRAGeneratedNames.getObjectReadableName(name); } public static String getGetterMatrixWritableName(final FieldName name) + { + return JPRAGeneratedNames.getObjectWritableName(name); + } + + private static String getObjectWritableName(final FieldName name) { final String text = JPRAGeneratedNames.getRecased(name.toString()); final StringBuilder sb = new StringBuilder(128); @@ -238,4 +228,14 @@ public static String getGetterMatrixWritableName(final FieldName name) sb.append("Writable"); return sb.toString(); } + + public static String getGetterStringReadableName(final FieldName name) + { + return JPRAGeneratedNames.getObjectReadableName(name); + } + + public static String getGetterStringWritableName(final FieldName name) + { + return JPRAGeneratedNames.getObjectWritableName(name); + } } diff --git a/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/RecordFieldImplementationConstructorProcessor.java b/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/RecordFieldImplementationConstructorProcessor.java index 395b4b1..235545f 100644 --- a/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/RecordFieldImplementationConstructorProcessor.java +++ b/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/RecordFieldImplementationConstructorProcessor.java @@ -32,12 +32,15 @@ import com.io7m.jpra.model.types.TString; import com.io7m.jpra.model.types.TVector; import com.io7m.jpra.model.types.TypeMatcherType; +import com.io7m.jpra.runtime.java.JPRAStringByteBuffered; +import com.io7m.jpra.runtime.java.JPRAStringType; import com.io7m.junreachable.UnreachableCodeException; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; import javax.lang.model.element.Modifier; +import java.nio.charset.Charset; /** * A type matcher that produces constructor field assignment statements for a @@ -68,6 +71,25 @@ final class RecordFieldImplementationConstructorProcessor @Override public Unit matchString(final TString t) { + final FieldName f_name = this.field.getName(); + final String field_name = JPRAGeneratedNames.getFieldName(f_name); + final String offset_name = JPRAGeneratedNames.getOffsetConstantName(f_name); + + this.constructor_builder.addStatement( + "this.$N = $T.newString($N, $N + $N, $N, $T.forName($S), $L)", + field_name, + JPRAStringByteBuffered.class, + "in_buffer", + offset_name, + "in_base_offset", + "in_pointer", + Charset.class, + t.getEncoding(), + t.getMaximumStringLength().getValue().intValue()); + + this.class_builder.addField( + JPRAStringType.class, field_name, Modifier.FINAL, Modifier.PRIVATE); + return Unit.unit(); } @@ -148,11 +170,12 @@ private void recordOrPackedField( final String field_name = JPRAGeneratedNames.getFieldName(f_name); final String offset_name = JPRAGeneratedNames.getOffsetConstantName(f_name); this.constructor_builder.addStatement( - "this.$N = $N.newValueWithOffset($N, $N, $N)", + "this.$N = $N.newValueWithOffset($N, $N, $N + $N)", field_name, t_imp_name, "in_buffer", "in_pointer", + "in_base_offset", offset_name); final PackageNameQualified p = pkg_ctxt.getName(); diff --git a/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/RecordFieldImplementationProcessor.java b/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/RecordFieldImplementationProcessor.java index f35c923..22a9363 100644 --- a/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/RecordFieldImplementationProcessor.java +++ b/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/RecordFieldImplementationProcessor.java @@ -33,6 +33,8 @@ import com.io7m.jpra.model.types.TString; import com.io7m.jpra.model.types.TVector; import com.io7m.jpra.model.types.TypeMatcherType; +import com.io7m.jpra.runtime.java.JPRAStringReadableType; +import com.io7m.jpra.runtime.java.JPRAStringType; import com.io7m.junreachable.UnimplementedCodeException; import com.io7m.junreachable.UnreachableCodeException; import com.squareup.javapoet.ClassName; @@ -78,8 +80,28 @@ final class RecordFieldImplementationProcessor { this.generateFieldOffsetConstant(); - // TODO: Generated method stub! - throw new UnimplementedCodeException(); + final String reader_name = + JPRAGeneratedNames.getGetterStringReadableName(this.field.getName()); + final String writer_name = + JPRAGeneratedNames.getGetterStringWritableName(this.field.getName()); + + final String f_name = JPRAGeneratedNames.getFieldName(this.field.getName()); + + final MethodSpec.Builder read_b = MethodSpec.methodBuilder(reader_name); + read_b.addModifiers(Modifier.PUBLIC); + read_b.addAnnotation(Override.class); + read_b.returns(JPRAStringReadableType.class); + read_b.addStatement("return this.$N", f_name); + this.class_builder.addMethod(read_b.build()); + + final MethodSpec.Builder write_b = MethodSpec.methodBuilder(writer_name); + write_b.addModifiers(Modifier.PUBLIC); + write_b.addAnnotation(Override.class); + write_b.returns(JPRAStringType.class); + write_b.addStatement("return this.$N", f_name); + this.class_builder.addMethod(write_b.build()); + + return Unit.unit(); } @Override public Unit matchBooleanSet( diff --git a/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/RecordFieldInterfaceProcessor.java b/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/RecordFieldInterfaceProcessor.java index ce9dc7b..c3594ff 100644 --- a/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/RecordFieldInterfaceProcessor.java +++ b/io7m-jpra-compiler-java/src/main/java/com/io7m/jpra/compiler/java/RecordFieldInterfaceProcessor.java @@ -32,6 +32,8 @@ import com.io7m.jpra.model.types.TString; import com.io7m.jpra.model.types.TVector; import com.io7m.jpra.model.types.TypeMatcherType; +import com.io7m.jpra.runtime.java.JPRAStringReadableType; +import com.io7m.jpra.runtime.java.JPRAStringType; import com.io7m.junreachable.UnimplementedCodeException; import com.io7m.junreachable.UnreachableCodeException; import com.squareup.javapoet.ClassName; @@ -70,8 +72,33 @@ final class RecordFieldInterfaceProcessor @Override public Unit matchString(final TString t) { - // TODO: Generated method stub! - throw new UnimplementedCodeException(); + if (this.methods.wantGetters()) { + final String getter_name = + JPRAGeneratedNames.getGetterStringReadableName(this.field.getName()); + + final MethodSpec.Builder getb = MethodSpec.methodBuilder(getter_name); + getb.addJavadoc( + "@return Read-only access to the {@code $L} field", + this.field.getName()); + getb.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT); + getb.returns(JPRAStringReadableType.class); + this.class_builder.addMethod(getb.build()); + } + + if (this.methods.wantSetters()) { + final String setter_name = + JPRAGeneratedNames.getGetterStringWritableName(this.field.getName()); + + final MethodSpec.Builder setb = MethodSpec.methodBuilder(setter_name); + setb.addJavadoc( + "@return Writable access to the {@code $L} field", + this.field.getName()); + setb.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT); + setb.returns(JPRAStringType.class); + this.class_builder.addMethod(setb.build()); + } + + return Unit.unit(); } @Override public Unit matchBooleanSet(final TBooleanSet t) diff --git a/io7m-jpra-model/src/main/java/com/io7m/jpra/model/types/TString.java b/io7m-jpra-model/src/main/java/com/io7m/jpra/model/types/TString.java index 214ab2c..e8fd6f7 100644 --- a/io7m-jpra-model/src/main/java/com/io7m/jpra/model/types/TString.java +++ b/io7m-jpra-model/src/main/java/com/io7m/jpra/model/types/TString.java @@ -33,6 +33,7 @@ private final Size size; private final Optional> lex; private final Size size_bits; + private final Size length; /** * Construct an expression. @@ -50,10 +51,20 @@ public TString( { this.lex = NullCheck.notNull(in_lex); this.encoding = NullCheck.notNull(in_encoding); - this.size = NullCheck.notNull(in_size); + this.length = NullCheck.notNull(in_size); + this.size = in_size.add(Size.valueOf(4L)); this.size_bits = Size.toBits(this.size); } + /** + * @return The maximum length of the string in octets + */ + + public Size getMaximumStringLength() + { + return this.length; + } + /** * @return The string encoding */ diff --git a/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringByteBuffered.java b/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringByteBuffered.java new file mode 100644 index 0000000..475cdab --- /dev/null +++ b/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringByteBuffered.java @@ -0,0 +1,187 @@ +/* + * Copyright © 2015 http://io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package com.io7m.jpra.runtime.java; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Objects; + +/** + * An implementation of the {@link JPRAStringType} that accesses an underlying + * {@link ByteBuffer}. + */ + +public final class JPRAStringByteBuffered implements JPRAStringType +{ + private final ByteBuffer buffer; + private final int max_length; + private final JPRACursorByteReadableType cursor; + private final int offset; + private final Charset encoding; + + private JPRAStringByteBuffered( + final ByteBuffer in_buffer, + final int in_offset, + final JPRACursorByteReadableType in_cursor, + final Charset in_encoding, + final int in_max_length) + { + this.buffer = Objects.requireNonNull(in_buffer, "Buffer"); + this.cursor = Objects.requireNonNull(in_cursor, "Cursor"); + this.encoding = Objects.requireNonNull(in_encoding, "Encoding"); + this.max_length = in_max_length; + this.offset = in_offset; + + if (this.max_length <= 0) { + final String message = + String.format( + "Maximum length %d must be positive", + Integer.valueOf(this.max_length)); + throw new IllegalArgumentException(message); + } + if (this.offset < 0) { + final String message = + String.format( + "Offset %d must be non-negative", + Integer.valueOf(this.offset)); + throw new IllegalArgumentException(message); + } + } + + /** + * Construct a new string pointer. + * + * @param in_buffer The byte buffer + * @param in_offset The offset of this string from the base cursor + * @param in_cursor The base cursor + * @param in_encoding The string encoding + * @param in_max_length The maximum string length + * + * @return A new string pointer + */ + + public static JPRAStringType newString( + final ByteBuffer in_buffer, + final int in_offset, + final JPRACursorByteReadableType in_cursor, + final Charset in_encoding, + final int in_max_length) + { + return new JPRAStringByteBuffered( + in_buffer, + in_offset, + in_cursor, + in_encoding, + in_max_length); + } + + private int getOffsetForStringByte(final int i) + { + final int data_start = this.getOffsetForDataStart(); + return data_start + 4 + i; + } + + private int getOffsetForDataStart() + { + final int base = this.cursor.getByteOffsetObservable().intValue(); + return base + this.offset; + } + + @Override public int getMaximumLength() + { + return this.max_length; + } + + @Override public int getUsedLength() + { + final int off = this.getOffsetForDataStart(); + final int val = this.buffer.getInt(off); + return Math.min(this.max_length, Math.max(0, val)); + } + + @Override public void getBytes( + final byte[] buf, + final int buf_offset, + final int length) + throws IndexOutOfBoundsException + { + final int max = this.getMaximumLength(); + if (length > max) { + final String message = String.format( + "Length %d must be < %d", + Integer.valueOf(length), + Integer.valueOf(max)); + throw new IndexOutOfBoundsException(message); + } + + final int data_offset = this.getOffsetForStringByte(0); + final int old_pos = this.buffer.position(); + this.buffer.position(data_offset); + this.buffer.get(buf, buf_offset, length); + this.buffer.position(old_pos); + } + + @Override public String getNewValue() + { + final int used = this.getUsedLength(); + final byte[] buf = new byte[used]; + final int b_pos = this.getOffsetForStringByte(0); + final int old_pos = this.buffer.position(); + this.buffer.position(b_pos); + this.buffer.get(buf, 0, used); + this.buffer.position(old_pos); + return new String(buf, this.encoding); + } + + @Override public Charset getEncoding() + { + return this.encoding; + } + + @Override public void setValue( + final String text, + final JPRAStringTruncation trunc) + { + final byte[] bytes = text.getBytes(this.encoding); + final int max = this.getMaximumLength(); + final int length = Math.min(bytes.length, max); + + switch (trunc) { + case TRUNCATE: { + break; + } + case REJECT: { + if (bytes.length > max) { + final String message = String.format( + "Bytes length is %d, which must be less than %d", + Integer.valueOf(bytes.length), + Integer.valueOf(max)); + throw new IndexOutOfBoundsException(message); + } + break; + } + } + + final int len_offset = this.getOffsetForDataStart(); + this.buffer.putInt(len_offset, length); + final int data_offset = len_offset + 4; + final int old_pos = this.buffer.position(); + this.buffer.position(data_offset); + this.buffer.put(bytes, 0, length); + this.buffer.position(old_pos); + } +} diff --git a/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringReadableType.java b/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringReadableType.java new file mode 100644 index 0000000..701ee90 --- /dev/null +++ b/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringReadableType.java @@ -0,0 +1,66 @@ +/* + * Copyright © 2015 http://io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package com.io7m.jpra.runtime.java; + +import java.nio.charset.Charset; + +/** + * The type of read-only pointers to strings. + */ + +public interface JPRAStringReadableType +{ + /** + * @return The maximum length of the string in octets + */ + + int getMaximumLength(); + + /** + * @return The number of octets of the string that are actually being used + */ + + int getUsedLength(); + + /** + * @param buf The buffer to which string data will be written + * @param offset The starting index of {@code buf} to which data will be + * written + * @param length The number of octets to copy into {@code buf} + * + * @throws IndexOutOfBoundsException Iff {@code length > getMaximumLength()} + */ + + void getBytes( + byte[] buf, + int offset, + int length) + throws IndexOutOfBoundsException; + + /** + * @return A freshly allocated string based on the current contents of the + * string data + */ + + String getNewValue(); + + /** + * @return The string encoding + */ + + Charset getEncoding(); +} diff --git a/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringTruncation.java b/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringTruncation.java new file mode 100644 index 0000000..e7a6855 --- /dev/null +++ b/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringTruncation.java @@ -0,0 +1,36 @@ +/* + * Copyright © 2015 http://io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package com.io7m.jpra.runtime.java; + +/** + * Behaviour for string truncation. + */ + +public enum JPRAStringTruncation +{ + /** + * Truncate the given string. + */ + + TRUNCATE, + + /** + * Reject the string rather than truncating. + */ + + REJECT +} diff --git a/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringType.java b/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringType.java new file mode 100644 index 0000000..615dfb7 --- /dev/null +++ b/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringType.java @@ -0,0 +1,27 @@ +/* + * Copyright © 2015 http://io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package com.io7m.jpra.runtime.java; + +/** + * The type of readable and writable strings. + */ + +public interface JPRAStringType + extends JPRAStringReadableType, JPRAStringWritableType +{ + // No extra methods +} diff --git a/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringWritableType.java b/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringWritableType.java new file mode 100644 index 0000000..7b33ff5 --- /dev/null +++ b/io7m-jpra-runtime-java/src/main/java/com/io7m/jpra/runtime/java/JPRAStringWritableType.java @@ -0,0 +1,35 @@ +/* + * Copyright © 2015 http://io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package com.io7m.jpra.runtime.java; + +/** + * The type of pointers to writable strings + */ + +public interface JPRAStringWritableType +{ + /** + * Set the string value, optionally truncating data based on {@code trunc}. + * + * @param text The new string value + * @param trunc The truncation behaviour + */ + + void setValue( + String text, + JPRAStringTruncation trunc); +} diff --git a/io7m-jpra-runtime-java/src/test/java/com/io7m/jpra/tests/runtime/java/JPRAStringByteBufferedTest.java b/io7m-jpra-runtime-java/src/test/java/com/io7m/jpra/tests/runtime/java/JPRAStringByteBufferedTest.java new file mode 100644 index 0000000..66379ef --- /dev/null +++ b/io7m-jpra-runtime-java/src/test/java/com/io7m/jpra/tests/runtime/java/JPRAStringByteBufferedTest.java @@ -0,0 +1,271 @@ +/* + * Copyright © 2015 http://io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package com.io7m.jpra.tests.runtime.java; + +import com.io7m.jpra.runtime.java.JPRACursorByteReadableType; +import com.io7m.jpra.runtime.java.JPRAStringByteBuffered; +import com.io7m.jpra.runtime.java.JPRAStringTruncation; +import com.io7m.jpra.runtime.java.JPRAStringType; +import org.hamcrest.core.StringContains; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicLong; + +public final class JPRAStringByteBufferedTest +{ + @Rule public ExpectedException expected = ExpectedException.none(); + + @Test public void testIdentities() + { + final AtomicLong base = new AtomicLong(0L); + final ByteBuffer buf = ByteBuffer.allocate(1000); + final JPRACursorByteReadableType cursor = () -> base; + final JPRAStringType s = + JPRAStringByteBuffered.newString( + buf, + 0, + cursor, + StandardCharsets.UTF_8, + 96); + + Assert.assertEquals(96L, (long) s.getMaximumLength()); + Assert.assertEquals(0L, (long) s.getUsedLength()); + Assert.assertEquals(StandardCharsets.UTF_8, s.getEncoding()); + } + + @Test public void testNegativeMaximum() + { + final AtomicLong base = new AtomicLong(0L); + final ByteBuffer buf = ByteBuffer.allocate(1000); + final JPRACursorByteReadableType cursor = () -> base; + + this.expected.expect(IllegalArgumentException.class); + this.expected.expectMessage(new StringContains("Maximum length")); + + JPRAStringByteBuffered.newString( + buf, + 0, + cursor, + StandardCharsets.UTF_8, + -1); + } + + @Test public void testNegativeOffset() + { + final AtomicLong base = new AtomicLong(0L); + final ByteBuffer buf = ByteBuffer.allocate(1000); + final JPRACursorByteReadableType cursor = () -> base; + + this.expected.expect(IllegalArgumentException.class); + this.expected.expectMessage(new StringContains("Offset")); + + JPRAStringByteBuffered.newString( + buf, + -1, + cursor, + StandardCharsets.UTF_8, + 96); + } + + @Test public void testUsedLength() + { + final AtomicLong base = new AtomicLong(0L); + final ByteBuffer buf = ByteBuffer.allocate(1000); + final JPRACursorByteReadableType cursor = () -> base; + final JPRAStringType s = + JPRAStringByteBuffered.newString( + buf, + 0, + cursor, + StandardCharsets.UTF_8, + 96); + + for (int index = 0; index < 10; ++index) { + final int offset = index * 100; + base.set((long) offset); + + Assert.assertEquals(0L, (long) s.getUsedLength()); + buf.putInt(offset, -1); + Assert.assertEquals(0L, (long) s.getUsedLength()); + buf.putInt(offset, 97); + Assert.assertEquals(96L, (long) s.getUsedLength()); + } + } + + @Test public void testNewValue() + { + final AtomicLong base = new AtomicLong(0L); + final ByteBuffer buf = ByteBuffer.allocate(10 * (8 + 4)); + final JPRACursorByteReadableType cursor = () -> base; + final JPRAStringType s = + JPRAStringByteBuffered.newString( + buf, + 0, + cursor, + StandardCharsets.UTF_8, + 8); + + for (int index = 0; index < 10; ++index) { + final int offset = index * (8 + 4); + base.set((long) offset); + + buf.putInt(offset + 0, 4); + + buf.put(offset + 4, (byte) 'A'); + buf.put(offset + 5, (byte) 'B'); + buf.put(offset + 6, (byte) 'C'); + buf.put(offset + 7, (byte) 'D'); + + buf.put(offset + 8, (byte) 'A'); + buf.put(offset + 9, (byte) 'B'); + buf.put(offset + 10, (byte) 'C'); + buf.put(offset + 11, (byte) 'D'); + + Assert.assertEquals(0L, (long) buf.position()); + Assert.assertEquals("ABCD", s.getNewValue()); + Assert.assertEquals(0L, (long) buf.position()); + + buf.putInt(offset + 0, 8); + Assert.assertEquals(0L, (long) buf.position()); + Assert.assertEquals("ABCDABCD", s.getNewValue()); + Assert.assertEquals(0L, (long) buf.position()); + } + } + + @Test public void testGetBytes() + { + final AtomicLong base = new AtomicLong(0L); + final ByteBuffer buf = ByteBuffer.allocate(10 * (8 + 4)); + final JPRACursorByteReadableType cursor = () -> base; + final JPRAStringType s = + JPRAStringByteBuffered.newString( + buf, + 0, + cursor, + StandardCharsets.UTF_8, + 8); + + for (int index = 0; index < 10; ++index) { + final int offset = index * (8 + 4); + base.set((long) offset); + + buf.putInt(offset + 0, 4); + buf.put(offset + 4, (byte) 'A'); + buf.put(offset + 5, (byte) 'B'); + buf.put(offset + 6, (byte) 'C'); + buf.put(offset + 7, (byte) 'D'); + buf.put(offset + 8, (byte) 'A'); + buf.put(offset + 9, (byte) 'B'); + buf.put(offset + 10, (byte) 'C'); + buf.put(offset + 11, (byte) 'D'); + + Assert.assertEquals(0L, (long) buf.position()); + final byte[] b = new byte[8]; + s.getBytes(b, 0, 8); + + Assert.assertEquals((long) 'A', b[0]); + Assert.assertEquals((long) 'B', b[1]); + Assert.assertEquals((long) 'C', b[2]); + Assert.assertEquals((long) 'D', b[3]); + Assert.assertEquals((long) 'A', b[4]); + Assert.assertEquals((long) 'B', b[5]); + Assert.assertEquals((long) 'C', b[6]); + Assert.assertEquals((long) 'D', b[7]); + Assert.assertEquals(0L, (long) buf.position()); + } + } + + @Test public void testGetBytesOutOfBounds() + { + final AtomicLong base = new AtomicLong(0L); + final ByteBuffer buf = ByteBuffer.allocate(8 + 4); + final JPRACursorByteReadableType cursor = () -> base; + final JPRAStringType s = + JPRAStringByteBuffered.newString( + buf, + 0, + cursor, + StandardCharsets.UTF_8, + 8); + + final byte[] b = new byte[16]; + + this.expected.expect(IndexOutOfBoundsException.class); + this.expected.expectMessage(new StringContains("Length")); + s.getBytes(b, 0, 16); + } + + @Test public void testSetValueTruncated() + { + final AtomicLong base = new AtomicLong(0L); + final ByteBuffer buf = ByteBuffer.allocate(10 * (8 + 4)); + final JPRACursorByteReadableType cursor = () -> base; + final JPRAStringType s = + JPRAStringByteBuffered.newString( + buf, + 0, + cursor, + StandardCharsets.UTF_8, + 8); + + for (int index = 0; index < 10; ++index) { + final int offset = index * (8 + 4); + base.set((long) offset); + + Assert.assertEquals(0L, (long) s.getUsedLength()); + Assert.assertEquals("", s.getNewValue()); + + s.setValue("ABCD", JPRAStringTruncation.TRUNCATE); + Assert.assertEquals(4L, (long) s.getUsedLength()); + Assert.assertEquals("ABCD", s.getNewValue()); + + s.setValue("EFGHIJKLMNOPQRSTUVWXYZ", JPRAStringTruncation.TRUNCATE); + Assert.assertEquals(8L, (long) s.getUsedLength()); + Assert.assertEquals("EFGHIJKL", s.getNewValue()); + } + } + + @Test public void testSetValueRejected() + { + final AtomicLong base = new AtomicLong(0L); + final ByteBuffer buf = ByteBuffer.allocate(8 + 4); + final JPRACursorByteReadableType cursor = () -> base; + final JPRAStringType s = + JPRAStringByteBuffered.newString( + buf, + 0, + cursor, + StandardCharsets.UTF_8, + 8); + + Assert.assertEquals(0L, (long) s.getUsedLength()); + Assert.assertEquals("", s.getNewValue()); + + s.setValue("EFGH", JPRAStringTruncation.REJECT); + Assert.assertEquals(4L, (long) s.getUsedLength()); + Assert.assertEquals("EFGH", s.getNewValue()); + + this.expected.expect(IndexOutOfBoundsException.class); + this.expected.expectMessage(new StringContains("Bytes length")); + s.setValue("EFGHIJKLMNOPQRSTUVWXYZ", JPRAStringTruncation.REJECT); + } +}