, PP exten * @return this builder instance */ FloatColumnBuilder
add(double... values); + + /** + * Add new values with fine-grained control. + *
+ * For {@link ValueKind#PRESENT}, the corresponding entry from {@code values} is appended. + * For {@link ValueKind#NOT_PRESENT} and {@link ValueKind#UNKNOWN}, this method delegates to + * {@link #markNextNotPresent()} and {@link #markNextUnknown()} respectively. + *
+ * @param values array of double values + * @param mask array of {@link ValueKind}, must be the same length as {@code values} + * @return this builder instance + * @throws IllegalArgumentException if arrays differ in size + * @throws NullPointerException if {@code values}, {@code mask}, or any {@code mask[i]} is null + */ + default FloatColumnBuilderaddMasked(double[] values, ValueKind[] mask) { + Objects.requireNonNull(values, "values"); + Objects.requireNonNull(mask, "mask"); + if (values.length != mask.length) { + throw new IllegalArgumentException("values.length (" + values.length + ") must equal mask.length (" + mask.length + ")"); + } + + for (int i = 0; i < values.length; i++) { + ValueKind k = Objects.requireNonNull(mask[i], "mask[" + i + "]"); + switch (k) { + case PRESENT: + add(values[i]); + break; + case NOT_PRESENT: + markNextNotPresent(); + break; + case UNKNOWN: + markNextUnknown(); + break; + default: + throw new IllegalStateException("Unhandled ValueKind: " + k); + } + } + return this; + } + + /** + * Add values from an Iterable. + * @param values Double values, null is mapped to ValueKind.NOT_PRESENT (".") + * @return this builder instance + */ + default FloatColumnBuilder
addNullable(Iterable , PP extends
* @return this builder instance
*/
IntColumnBuilder add(int... values);
+
+ /**
+ * Add new values with fine-grained control.
+ *
+ * For {@link ValueKind#PRESENT}, the corresponding entry from {@code values} is appended.
+ * For {@link ValueKind#NOT_PRESENT} and {@link ValueKind#UNKNOWN}, this method delegates to
+ * {@link #markNextNotPresent()} and {@link #markNextUnknown()} respectively.
+ * addMasked(int[] values, ValueKind[] mask) {
+ Objects.requireNonNull(values, "values");
+ Objects.requireNonNull(mask, "mask");
+ if (values.length != mask.length) {
+ throw new IllegalArgumentException("values.length (" + values.length + ") must equal mask.length (" + mask.length + ")");
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ ValueKind k = Objects.requireNonNull(mask[i], "mask[" + i + "]");
+ switch (k) {
+ case PRESENT:
+ add(values[i]);
+ break;
+ case NOT_PRESENT:
+ markNextNotPresent();
+ break;
+ case UNKNOWN:
+ markNextUnknown();
+ break;
+ default:
+ throw new IllegalStateException("Unhandled ValueKind: " + k);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Add values from an Iterable.
+ * @param values Integer values, null is mapped to ValueKind.NOT_PRESENT (".")
+ * @return this builder instance
+ */
+ default IntColumnBuilder addNullable(Iterable Does not support {@code null} keys.
*
- * Part of the spring-framework but really we need just this one class.
- *
* @author Juergen Hoeller
* @author Phillip Webb
* @since 3.0
* @param , PP extends
* @return this builder instance
*/
StrColumnBuilder add(String... values);
+
+ /**
+ * Add new values with fine-grained control.
+ *
+ * For {@link ValueKind#PRESENT}, the corresponding entry from {@code values} is appended.
+ * For {@link ValueKind#NOT_PRESENT} and {@link ValueKind#UNKNOWN}, this method delegates to
+ * {@link #markNextNotPresent()} and {@link #markNextUnknown()} respectively.
+ * addMasked(String[] values, ValueKind[] mask) {
+ Objects.requireNonNull(values, "values");
+ Objects.requireNonNull(mask, "mask");
+ if (values.length != mask.length) {
+ throw new IllegalArgumentException("values.length (" + values.length + ") must equal mask.length (" + mask.length + ")");
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ ValueKind k = Objects.requireNonNull(mask[i], "mask[" + i + "]");
+ if (k == ValueKind.PRESENT && (values[i] == null || ValueKind.isValueKindToken(values[i]))) {
+ throw new IllegalArgumentException("PRESENT value must not be null, '.' or '?': values[" + i + "]");
+ }
+ switch (k) {
+ case PRESENT:
+ add(values[i]);
+ break;
+ case NOT_PRESENT:
+ markNextNotPresent();
+ break;
+ case UNKNOWN:
+ markNextUnknown();
+ break;
+ default:
+ throw new IllegalStateException("Unhandled ValueKind: " + k);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Add values from an Iterable.
+ * @param values String values, null is mapped to NOT_PRESENT ("."); "." and "?" are interpreted as CIF tokens.
+ * @return this builder instance
+ */
+ default StrColumnBuilder addNullable(Iterable , PP extends CifFileB
protected final P parent;
private final List , P, PP>) child);
} else if (child instanceof FloatColumnBuilder) {
diff --git a/src/main/java/org/rcsb/cif/model/builder/ColumnBuilderImpl.java b/src/main/java/org/rcsb/cif/model/builder/ColumnBuilderImpl.java
index c34fc03ff..50473e470 100644
--- a/src/main/java/org/rcsb/cif/model/builder/ColumnBuilderImpl.java
+++ b/src/main/java/org/rcsb/cif/model/builder/ColumnBuilderImpl.java
@@ -12,7 +12,7 @@
public abstract class ColumnBuilderImpl , PP extends BlockBuilder {
private final String categoryName;
private final String columnName;
- final List , PP extends BlockBuilder implements FloatColumnBuilder {
- private final List markNextNotPresent() {
- values.add(0.0);
+ values.add(null);
mask.add(ValueKind.NOT_PRESENT);
return this;
}
@Override
public FloatColumnBuilder markNextUnknown() {
- values.add(0.0);
+ values.add(null);
mask.add(ValueKind.UNKNOWN);
return this;
}
@@ -48,9 +46,14 @@ public FloatColumn build() {
}
@Override
- public FloatColumnBuilder add(double... value) {
- DoubleStream.of(value).forEach(values::add);
- IntStream.range(0, value.length).mapToObj(i -> ValueKind.PRESENT).forEach(mask::add);
+ public FloatColumnBuilder add(double... values) {
+ this.values.ensureCapacity(this.values.size() + values.length);
+ this.mask.ensureCapacity(this.mask.size() + values.length);
+
+ for (double v : values) {
+ this.values.add(v);
+ this.mask.add(ValueKind.PRESENT);
+ }
return this;
}
diff --git a/src/main/java/org/rcsb/cif/model/builder/IntColumnBuilderImpl.java b/src/main/java/org/rcsb/cif/model/builder/IntColumnBuilderImpl.java
index 63a3097e8..5c62162e9 100644
--- a/src/main/java/org/rcsb/cif/model/builder/IntColumnBuilderImpl.java
+++ b/src/main/java/org/rcsb/cif/model/builder/IntColumnBuilderImpl.java
@@ -9,13 +9,12 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.stream.IntStream;
import static org.rcsb.cif.model.CategoryBuilder.createColumnText;
public class IntColumnBuilderImpl , PP extends BlockBuilder implements IntColumnBuilder {
- private final List markNextNotPresent() {
- values.add(0);
+ values.add(null);
mask.add(ValueKind.NOT_PRESENT);
return this;
}
@Override
public IntColumnBuilder markNextUnknown() {
- values.add(0);
+ values.add(null);
mask.add(ValueKind.UNKNOWN);
return this;
}
@@ -48,8 +47,13 @@ public IntColumn build() {
@Override
public IntColumnBuilder add(int... values) {
- IntStream.of(values).forEach(this.values::add);
- IntStream.range(0, values.length).mapToObj(i -> ValueKind.PRESENT).forEach(mask::add);
+ this.values.ensureCapacity(this.values.size() + values.length);
+ this.mask.ensureCapacity(this.mask.size() + values.length);
+
+ for (int v : values) {
+ this.values.add(v);
+ this.mask.add(ValueKind.PRESENT);
+ }
return this;
}
diff --git a/src/main/java/org/rcsb/cif/model/builder/StrColumnBuilderImpl.java b/src/main/java/org/rcsb/cif/model/builder/StrColumnBuilderImpl.java
index 3772a19d3..fef75df02 100644
--- a/src/main/java/org/rcsb/cif/model/builder/StrColumnBuilderImpl.java
+++ b/src/main/java/org/rcsb/cif/model/builder/StrColumnBuilderImpl.java
@@ -14,7 +14,7 @@
public class StrColumnBuilderImpl , PP extends BlockBuilder implements StrColumnBuilder {
- private final List markNextNotPresent() {
- values.add(".");
+ values.add(null);
mask.add(ValueKind.NOT_PRESENT);
return this;
}
@Override
public StrColumnBuilder markNextUnknown() {
- values.add("?");
+ values.add(null);
mask.add(ValueKind.UNKNOWN);
return this;
}
@@ -45,16 +45,45 @@ public StrColumn build() {
return createColumnText(getColumnName(), values, mask, StrColumn.class);
}
+ /**
+ * Add one or more string values to this column.
+ *
+ * CIF has two special tokens for missing data: {@code "."} (not present) and {@code "?"}
+ * (unknown). This method treats those tokens (and {@code null}) as missingness indicators
+ * rather than literal payload:
+ *
+ * Note: this means you cannot write a literal value that is exactly {@code "."} or {@code "?"}
+ * via this overload. If you need explicit control over missingness vs. payload, prefer the
+ * forthcoming masked overload that accepts {@link ValueKind} alongside values (e.g. {@code addMasked(...)}).
+ *
+ * @param values string values to append; {@code null}, {@code "."}, and {@code "?"} are treated specially
+ * @return this builder instance
+ */
@Override
public StrColumnBuilder add(String... values) {
+ this.values.ensureCapacity(this.values.size() + values.length);
+ this.mask.ensureCapacity(this.mask.size() + values.length);
+
for (String s : values) {
- if (".".equals(s)) {
- markNextNotPresent();
- } else if ("?".equals(s)) {
- markNextUnknown();
- } else {
- this.values.add(s);
- mask.add(ValueKind.PRESENT);
+ ValueKind kind = ValueKind.fromCifToken(s);
+ switch (kind) {
+ case NOT_PRESENT:
+ markNextNotPresent();
+ break;
+ case UNKNOWN:
+ markNextUnknown();
+ break;
+ case PRESENT:
+ this.values.add(s);
+ this.mask.add(ValueKind.PRESENT);
+ break;
+ default:
+ throw new IllegalStateException("Unhandled ValueKind: " + kind);
}
}
return this;
diff --git a/src/main/java/org/rcsb/cif/model/text/TextColumn.java b/src/main/java/org/rcsb/cif/model/text/TextColumn.java
index ea4f1144a..10f857e08 100644
--- a/src/main/java/org/rcsb/cif/model/text/TextColumn.java
+++ b/src/main/java/org/rcsb/cif/model/text/TextColumn.java
@@ -36,23 +36,17 @@ public String getStringData(int row) {
}
private String honorValueKind(String value) {
- return (".".equals(value) || "?".equals(value)) ? "" : value;
+ return ValueKind.isValueKindToken(value) ? "" : value;
}
@Override
public ValueKind getValueKind(int row) {
String value = textData.substring(startToken[row], endToken[row]);
- if (value.isEmpty() || ".".equals(value)) {
- return ValueKind.NOT_PRESENT;
- } else if ("?".equals(value)) {
- return ValueKind.UNKNOWN;
- } else {
- return ValueKind.PRESENT;
- }
+ return ValueKind.fromCifToken(value);
}
/**
- * Explicitly creates this array by parsing all data in this text column. Don't use this function on for text data -
+ * Explicitly creates this array by parsing all data in this text column. Don't use this function for text data -
* returned array is not cached
* @return the requested array
*/
diff --git a/src/main/java/org/rcsb/cif/schema/DelegatingBlock.java b/src/main/java/org/rcsb/cif/schema/DelegatingBlock.java
index ca8312dde..a59a1dfdd 100644
--- a/src/main/java/org/rcsb/cif/schema/DelegatingBlock.java
+++ b/src/main/java/org/rcsb/cif/schema/DelegatingBlock.java
@@ -2,8 +2,8 @@
import org.rcsb.cif.model.Block;
import org.rcsb.cif.model.Category;
+import org.rcsb.cif.model.LinkedCaseInsensitiveMap;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -26,7 +26,7 @@ public Category getCategory(String name) {
@Override
public Map? in CIF. String values will be empty, number values will be 0.
*/
- UNKNOWN
+ UNKNOWN;
+
+ public static final String CIF_NOT_PRESENT = ".";
+ public static final String CIF_UNKNOWN = "?";
+
+ /**
+ * Checks whether a String matches "?" or ".", sequences with special meaning in CIF.
+ * @param s payload to evaluate
+ * @return true if this String indicates missing or undefined values
+ */
+ public static boolean isValueKindToken(String s) {
+ return CIF_NOT_PRESENT.equals(s) || CIF_UNKNOWN.equals(s);
+ }
+
+ /**
+ * Transforms a String into a ValueKind.
+ * @param s payload to evaluate
+ * @return appropriate ValueKind for "?" and ".", otherwise marked as PRESENT
+ */
+ public static ValueKind fromCifToken(String s) {
+ if (s == null || s.isEmpty() || CIF_NOT_PRESENT.equals(s)) return NOT_PRESENT;
+ if (CIF_UNKNOWN.equals(s)) return UNKNOWN;
+ return PRESENT;
+ }
}
diff --git a/src/main/java/org/rcsb/cif/model/binary/BinaryColumn.java b/src/main/java/org/rcsb/cif/model/binary/BinaryColumn.java
index 0a5483d45..509e17787 100644
--- a/src/main/java/org/rcsb/cif/model/binary/BinaryColumn.java
+++ b/src/main/java/org/rcsb/cif/model/binary/BinaryColumn.java
@@ -9,7 +9,7 @@ public abstract class BinaryColumn
+ *
+ *