Package org.locationtech.geogig.storage.datastream

Source Code of org.locationtech.geogig.storage.datastream.FormatCommonV2

/* Copyright (c) 2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Gabriel Roldan (Boundless) - initial implementation
*/
package org.locationtech.geogig.storage.datastream;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.Integer.toBinaryString;
import static org.locationtech.geogig.storage.datastream.Varint.readSignedVarLong;
import static org.locationtech.geogig.storage.datastream.Varint.readUnsignedVarInt;
import static org.locationtech.geogig.storage.datastream.Varint.readUnsignedVarLong;
import static org.locationtech.geogig.storage.datastream.Varint.writeSignedVarLong;
import static org.locationtech.geogig.storage.datastream.Varint.writeUnsignedVarInt;
import static org.locationtech.geogig.storage.datastream.Varint.writeUnsignedVarLong;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.annotation.Nullable;

import org.geotools.feature.NameImpl;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.feature.type.BasicFeatureTypes;
import org.geotools.referencing.CRS;
import org.geotools.referencing.CRS.AxisOrder;
import org.geotools.referencing.wkt.Formattable;
import org.locationtech.geogig.api.Bucket;
import org.locationtech.geogig.api.Node;
import org.locationtech.geogig.api.NodeRef;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.RevCommit;
import org.locationtech.geogig.api.RevCommitImpl;
import org.locationtech.geogig.api.RevFeature;
import org.locationtech.geogig.api.RevFeatureImpl;
import org.locationtech.geogig.api.RevFeatureType;
import org.locationtech.geogig.api.RevFeatureTypeImpl;
import org.locationtech.geogig.api.RevObject;
import org.locationtech.geogig.api.RevObject.TYPE;
import org.locationtech.geogig.api.RevPerson;
import org.locationtech.geogig.api.RevPersonImpl;
import org.locationtech.geogig.api.RevTag;
import org.locationtech.geogig.api.RevTagImpl;
import org.locationtech.geogig.api.RevTree;
import org.locationtech.geogig.api.RevTreeImpl;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry;
import org.locationtech.geogig.storage.FieldType;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.FeatureTypeFactory;
import org.opengis.feature.type.GeometryType;
import org.opengis.feature.type.Name;
import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.feature.type.PropertyType;
import org.opengis.filter.Filter;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.math.DoubleMath;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;

public class FormatCommonV2 {

    public final static byte NUL = 0x00;

    public final static String readToMarker(DataInput in, byte marker) throws IOException {
        StringBuilder buff = new StringBuilder();
        byte b = in.readByte();
        while (b != marker) {
            buff.append((char) b);
            b = in.readByte();
        }
        return buff.toString();
    }

    public final static ObjectId readObjectId(DataInput in) throws IOException {
        byte[] bytes = new byte[ObjectId.NUM_BYTES];
        in.readFully(bytes);
        return ObjectId.createNoClone(bytes);
    }

    /**
     * Constant for reading TREE objects. Indicates that the end of the tree object has been
     * reached.
     */
    public static final byte NO_MORE_NODES = 0x00;

    /**
     * Constant for reading TREE objects. Indicates that the next entry is a subtree node or a
     * features node.
     */
    public static final byte NODE = 0x01;

    /**
     * Constant for reading TREE objects. Indicates that the next entry is a bucket.
     */
    public static final byte BUCKET = 0x02;

    /**
     * The featuretype factory to use when calling code does not provide one.
     */
    private static final FeatureTypeFactory DEFAULT_FEATURETYPE_FACTORY = new SimpleFeatureTypeBuilder()
            .getFeatureTypeFactory();

    public static RevTag readTag(ObjectId id, DataInput in) throws IOException {
        final ObjectId commitId = readObjectId(in);
        final String name = in.readUTF();
        final String message = in.readUTF();
        final RevPerson tagger = readRevPerson(in);

        return new RevTagImpl(id, name, commitId, message, tagger);
    }

    public static void writeTag(RevTag tag, DataOutput out) throws IOException {
        out.write(tag.getCommitId().getRawValue());
        out.writeUTF(tag.getName());
        out.writeUTF(tag.getMessage());
        writePerson(tag.getTagger(), out);
    }

    public static void writeCommit(RevCommit commit, DataOutput data) throws IOException {
        data.write(commit.getTreeId().getRawValue());
        final int nParents = commit.getParentIds().size();
        writeUnsignedVarInt(nParents, data);
        for (ObjectId pId : commit.getParentIds()) {
            data.write(pId.getRawValue());
        }

        writePerson(commit.getAuthor(), data);
        writePerson(commit.getCommitter(), data);
        data.writeUTF(commit.getMessage());
    }

    public static RevCommit readCommit(ObjectId id, DataInput in) throws IOException {
        final ObjectId treeId = readObjectId(in);
        final int nParents = readUnsignedVarInt(in);
        final Builder<ObjectId> parentListBuilder = ImmutableList.builder();

        for (int i = 0; i < nParents; i++) {
            ObjectId parentId = readObjectId(in);
            parentListBuilder.add(parentId);
        }
        final RevPerson author = readRevPerson(in);
        final RevPerson committer = readRevPerson(in);
        final String message = in.readUTF();

        return new RevCommitImpl(id, treeId, parentListBuilder.build(), author, committer, message);
    }

    public static final RevPerson readRevPerson(DataInput in) throws IOException {
        final String name = in.readUTF();
        final String email = in.readUTF();
        final long timestamp = readUnsignedVarLong(in);
        final int tzOffset = readUnsignedVarInt(in);
        return new RevPersonImpl(name.length() == 0 ? null : name, email.length() == 0 ? null
                : email, timestamp, tzOffset);
    }

    public static final void writePerson(RevPerson person, DataOutput data) throws IOException {
        data.writeUTF(person.getName().or(""));
        data.writeUTF(person.getEmail().or(""));
        writeUnsignedVarLong(person.getTimestamp(), data);
        writeUnsignedVarInt(person.getTimeZoneOffset(), data);
    }

    public static void writeTree(RevTree tree, DataOutput data) throws IOException {

        writeUnsignedVarLong(tree.size(), data);
        writeUnsignedVarInt(tree.numTrees(), data);

        Envelope envBuff = new Envelope();

        final int nFeatures = tree.features().isPresent() ? tree.features().get().size() : 0;
        writeUnsignedVarInt(nFeatures, data);
        if (nFeatures > 0) {
            for (Node feature : tree.features().get()) {
                writeNode(feature, data, envBuff);
            }
        }
        final int nTrees = tree.trees().isPresent() ? tree.trees().get().size() : 0;
        writeUnsignedVarInt(nTrees, data);
        if (nTrees > 0) {
            for (Node subTree : tree.trees().get()) {
                writeNode(subTree, data, envBuff);
            }
        }

        final int nBuckets = tree.buckets().isPresent() ? tree.buckets().get().size() : 0;
        writeUnsignedVarInt(nBuckets, data);
        if (tree.buckets().isPresent()) {
            ImmutableSortedMap<Integer, Bucket> buckets = tree.buckets().get();
            for (Map.Entry<Integer, Bucket> bucket : buckets.entrySet()) {
                writeBucket(bucket.getKey(), bucket.getValue(), data, envBuff);
            }
        }
    }

    public static RevTree readTree(ObjectId id, DataInput in) throws IOException {
        final long size = readUnsignedVarLong(in);
        final int treeCount = readUnsignedVarInt(in);

        final ImmutableList.Builder<Node> featuresBuilder = new ImmutableList.Builder<Node>();
        final ImmutableList.Builder<Node> treesBuilder = new ImmutableList.Builder<Node>();
        final SortedMap<Integer, Bucket> buckets = new TreeMap<Integer, Bucket>();

        final int nFeatures = readUnsignedVarInt(in);
        for (int i = 0; i < nFeatures; i++) {
            Node n = readNode(in);
            checkState(RevObject.TYPE.FEATURE.equals(n.getType()),
                    "Non-feature node in tree's feature list.");
            featuresBuilder.add(n);
        }

        final int nTrees = readUnsignedVarInt(in);
        for (int i = 0; i < nTrees; i++) {
            Node n = readNode(in);
            checkState(RevObject.TYPE.TREE.equals(n.getType()),
                    "Non-tree node in tree's subtree list.");

            treesBuilder.add(n);
        }

        final int nBuckets = readUnsignedVarInt(in);
        for (int i = 0; i < nBuckets; i++) {
            int bucketIndex = readUnsignedVarInt(in);
            {
                Integer idx = Integer.valueOf(bucketIndex);
                checkState(!buckets.containsKey(idx), "duplicate bucket index: %s", idx);
                // checkState(bucketIndex < RevTree.MAX_BUCKETS, "Illegal bucket index: %s", idx);
            }
            Bucket bucket = readBucketBody(in);
            buckets.put(Integer.valueOf(bucketIndex), bucket);
        }
        checkState(nBuckets == buckets.size(), "expected %s buckets, got %s", nBuckets,
                buckets.size());
        ImmutableList<Node> trees = treesBuilder.build();
        ImmutableList<Node> features = featuresBuilder.build();
        checkArgument(buckets.isEmpty() || (trees.isEmpty() && features.isEmpty()),
                "Tree has mixed buckets and nodes; this is not supported.");

        if (trees.isEmpty() && features.isEmpty()) {
            return RevTreeImpl.createNodeTree(id, size, treeCount, buckets);
        }
        return RevTreeImpl.createLeafTree(id, size, features, trees);
    }

    public static DiffEntry readDiff(DataInput in) throws IOException {
        boolean oldNode = in.readBoolean();
        NodeRef oldNodeRef = null;
        if (oldNode) {
            oldNodeRef = readNodeRef(in);
        }
        boolean newNode = in.readBoolean();
        NodeRef newNodeRef = null;
        if (newNode) {
            newNodeRef = readNodeRef(in);
        }

        return new DiffEntry(oldNodeRef, newNodeRef);
    }

    public static NodeRef readNodeRef(DataInput in) throws IOException {
        Node node = readNode(in);
        final ObjectId metadataId = readObjectId(in);
        String parentPath = in.readUTF();
        return new NodeRef(node, parentPath, metadataId);
    }

    public static void writeFeature(RevFeature feature, DataOutput data) throws IOException {
        ImmutableList<Optional<Object>> values = feature.getValues();

        writeUnsignedVarInt(values.size(), data);

        for (Optional<Object> field : values) {
            FieldType type = FieldType.forValue(field);
            data.writeByte(type.getTag());
            if (type != FieldType.NULL) {
                DataStreamValueSerializerV2.write(field, data);
            }
        }
    }

    public static RevFeature readFeature(ObjectId id, DataInput in) throws IOException {
        final int count = readUnsignedVarInt(in);
        final ImmutableList.Builder<Optional<Object>> builder = ImmutableList.builder();

        for (int i = 0; i < count; i++) {
            final byte fieldTag = in.readByte();
            final FieldType fieldType = FieldType.valueOf(fieldTag);
            Object value = DataStreamValueSerializerV2.read(fieldType, in);
            builder.add(Optional.fromNullable(value));
        }

        return new RevFeatureImpl(id, builder.build());
    }

    public static void writeHeader(DataOutput data, RevObject.TYPE header) throws IOException {
        data.writeByte(header.value());
    }

    public static TYPE readHeader(DataInput in) throws IOException {
        final int header = in.readByte() & 0xFF;
        checkState(header > -1 && header < 6,
                "Illegal RevObject type header: %s, must be between 0 and 4 inclusive",
                Integer.valueOf(header));
        final RevObject.TYPE type = TYPE.valueOf(header);
        return type;
    }

    public final static void requireHeader(DataInput in, RevObject.TYPE header) throws IOException {
        int s = in.readByte() & 0xFF;
        if (header.value() != s) {
            throw new IllegalArgumentException(String.format(
                    "Expected header %s(%d), but actually got %d", header, header.value(), s));
        }
    }

    private static void writeBoundingBox(double minx, double maxx, double miny, double maxy,
            DataOutput data) throws IOException {

        long x1 = toFixedPrecision(minx, RoundingMode.HALF_DOWN);
        long y1 = toFixedPrecision(miny, RoundingMode.HALF_DOWN);
        long x2 = toFixedPrecision(maxx, RoundingMode.HALF_UP);
        long y2 = toFixedPrecision(maxy, RoundingMode.HALF_UP);

        writeSignedVarLong(x1, data);
        writeSignedVarLong(y1, data);
        writeSignedVarLong(x2, data);
        writeSignedVarLong(y2, data);
    }

    private static Envelope readBoundingBox(DataInput in) throws IOException {
        final long x1 = readSignedVarLong(in);
        final long y1 = readSignedVarLong(in);
        final long x2 = readSignedVarLong(in);
        final long y2 = readSignedVarLong(in);

        final double minx = toDoublePrecision(x1);
        final double maxx = toDoublePrecision(x2);
        final double miny = toDoublePrecision(y1);
        final double maxy = toDoublePrecision(y2);

        return new Envelope(minx, maxx, miny, maxy);
    }

    public static void writePointBoundingBox(double x, double y, DataOutput data)
            throws IOException {
        long x1 = toFixedPrecision(x);
        long y1 = toFixedPrecision(y);
        writeSignedVarLong(x1, data);
        writeSignedVarLong(y1, data);
    }

    public static Envelope readPointBoundingBox(DataInput in) throws IOException {
        final long x1 = readSignedVarLong(in);
        final long y1 = readSignedVarLong(in);

        final double x = toDoublePrecision(x1);
        final double y = toDoublePrecision(y1);
        return new Envelope(x, x, y, y);
    }

    private static final double FIXED_PRECISION_FACTOR = 10_000_000D;

    /**
     * Converts the requested coordinate from double to fixed precision.
     */
    private static long toFixedPrecision(double ordinate) {
        long fixedPrecisionOrdinate = Math.round(ordinate * FIXED_PRECISION_FACTOR);
        return fixedPrecisionOrdinate;
    }

    private static long toFixedPrecision(double ordinate, RoundingMode mode) {
        long fixedPrecisionOrdinate = DoubleMath.roundToLong(ordinate * FIXED_PRECISION_FACTOR,
                mode);
        return fixedPrecisionOrdinate;
    }

    /**
     * Converts the requested coordinate from fixed to double precision.
     */
    private static double toDoublePrecision(long fixedPrecisionOrdinate) {
        double ordinate = (double) fixedPrecisionOrdinate / FIXED_PRECISION_FACTOR;
        return ordinate;
    }

    public static void writeBucket(final int index, final Bucket bucket, DataOutput data,
            Envelope envBuff) throws IOException {

        writeUnsignedVarInt(index, data);

        data.write(bucket.id().getRawValue());
        envBuff.setToNull();
        bucket.expand(envBuff);
        if (envBuff.isNull()) {
            data.writeByte(BOUNDS_NULL_MASK);
        } else if (envBuff.getWidth() == 0D && envBuff.getHeight() == 0D) {
            data.writeByte(BOUNDS_POINT_MASK);
            writePointBoundingBox(envBuff.getMinX(), envBuff.getMinY(), data);
        } else {
            data.writeByte(BOUNDS_BOX2D_MASK);
            writeBoundingBox(envBuff.getMinX(), envBuff.getMaxX(), envBuff.getMinY(),
                    envBuff.getMaxY(), data);
        }
    }

    /**
     * Reads a bucket body (i.e assumes the head unsigned int "index" has been read already)
     */
    private static final Bucket readBucketBody(DataInput in) throws IOException {
        ObjectId objectId = readObjectId(in);
        final int boundsMask = in.readByte() & 0xFF;
        @Nullable
        final Envelope bounds;
        if (BOUNDS_POINT_MASK == boundsMask) {
            bounds = readPointBoundingBox(in);
        } else if (BOUNDS_BOX2D_MASK == boundsMask) {
            bounds = readBoundingBox(in);
        } else {
            bounds = null;
        }
        return Bucket.create(objectId, bounds);
    }

    public static void writeNode(Node node, DataOutput data) throws IOException {
        writeNode(node, data, new Envelope());
    }

    static final int BOUNDS_NULL_MASK = 0b00000;

    static final int BOUNDS_POINT_MASK = 0b01000;

    static final int BOUNDS_BOX2D_MASK = 0b10000;

    static final int METADATA_PRESENT_MASK = 0b100000;

    static final int METADATA_ABSENT_MASK = 0b000000;

    static final int METADATA_READ_MASK = 0b100000;

    static final int BOUNDS_READ_MASK = 0b011000;

    static final int TYPE_READ_MASK = 0b000111;

    public static void writeNode(Node node, DataOutput data, Envelope env) throws IOException {
        // Encode the node type and the bounds and metadata presence masks in one single byte:
        // - bits 1-3 for the object type (up to 8 types, there are only 5 and no plans to add more)
        // - bits 4-5 bits for the bounds mask
        // - bit 6 metadata id present(1) or absent(0)
        // - bits 7-8 unused

        final int nodeType = node.getType().value();
        final int boundsMask;
        final int metadataMask;

        env.setToNull();
        node.expand(env);
        if (env.isNull()) {
            boundsMask = BOUNDS_NULL_MASK;
        } else if (env.getWidth() == 0D && env.getHeight() == 0D) {
            boundsMask = BOUNDS_POINT_MASK;
        } else {
            boundsMask = BOUNDS_BOX2D_MASK;
        }

        metadataMask = node.getMetadataId().isPresent() ? METADATA_PRESENT_MASK
                : METADATA_ABSENT_MASK;

        // encode type and bounds mask together
        final int typeAndMasks = nodeType | boundsMask | metadataMask;

        data.writeByte(typeAndMasks);
        data.writeUTF(node.getName());
        data.write(node.getObjectId().getRawValue());
        if (metadataMask == METADATA_PRESENT_MASK) {
            data.write(node.getMetadataId().or(ObjectId.NULL).getRawValue());
        }
        if (BOUNDS_BOX2D_MASK == boundsMask) {
            writeBoundingBox(env.getMinX(), env.getMaxX(), env.getMinY(), env.getMaxY(), data);
        } else if (BOUNDS_POINT_MASK == boundsMask) {
            writePointBoundingBox(env.getMinX(), env.getMinY(), data);
        }
    }

    public static Node readNode(DataInput in) throws IOException {
        final int typeAndMasks = in.readByte() & 0xFF;
        final int nodeType = typeAndMasks & TYPE_READ_MASK;
        final int boundsMask = typeAndMasks & BOUNDS_READ_MASK;
        final int metadataMask = typeAndMasks & METADATA_READ_MASK;

        final RevObject.TYPE contentType = RevObject.TYPE.valueOf(nodeType);
        final String name = in.readUTF();
        final ObjectId objectId = readObjectId(in);
        ObjectId metadataId = ObjectId.NULL;
        if (metadataMask == METADATA_PRESENT_MASK) {
            metadataId = readObjectId(in);
        }
        @Nullable
        final Envelope bbox;
        if (boundsMask == BOUNDS_NULL_MASK) {
            bbox = null;
        } else if (boundsMask == BOUNDS_POINT_MASK) {
            bbox = readPointBoundingBox(in);
        } else if (boundsMask == BOUNDS_BOX2D_MASK) {
            bbox = readBoundingBox(in);
        } else {
            throw new IllegalStateException(String.format(
                    "Illegal bounds mask: %s, expected one of %s, %s, %s",
                    toBinaryString(boundsMask), toBinaryString(BOUNDS_NULL_MASK),
                    toBinaryString(BOUNDS_POINT_MASK), toBinaryString(BOUNDS_BOX2D_MASK)));
        }
        final Node node;
        node = Node.create(name, objectId, metadataId, contentType, bbox);
        return node;
    }

    public static void writeDiff(DiffEntry diff, DataOutput data) throws IOException {
        if (diff.getOldObject() == null) {
            data.writeBoolean(false);
        } else {
            data.writeBoolean(true);
            writeNodeRef(diff.getOldObject(), data);
        }
        if (diff.getNewObject() == null) {
            data.writeBoolean(false);
        } else {
            data.writeBoolean(true);
            writeNodeRef(diff.getNewObject(), data);
        }
    }

    public static void writeNodeRef(NodeRef nodeRef, DataOutput data) throws IOException {
        writeNode(nodeRef.getNode(), data);
        data.write(nodeRef.getMetadataId().getRawValue());
        data.writeUTF(nodeRef.getParentPath());
    }

    public static void writeFeatureType(RevFeatureType object, DataOutput data) throws IOException {
        writeName(object.getName(), data);

        ImmutableList<PropertyDescriptor> descriptors = object.sortedDescriptors();
        writeUnsignedVarInt(descriptors.size(), data);

        for (PropertyDescriptor desc : object.type().getDescriptors()) {
            writeProperty(desc, data);
        }
    }

    public static RevFeatureType readFeatureType(ObjectId id, DataInput in) throws IOException {
        return readFeatureType(id, in, DEFAULT_FEATURETYPE_FACTORY);
    }

    public static RevFeatureType readFeatureType(ObjectId id, DataInput in,
            FeatureTypeFactory typeFactory) throws IOException {

        Name name = readName(in);
        int propertyCount = readUnsignedVarInt(in);
        List<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>();
        for (int i = 0; i < propertyCount; i++) {
            attributes.add(readAttributeDescriptor(in, typeFactory));
        }
        SimpleFeatureType ftype = typeFactory.createSimpleFeatureType(name, attributes, null,
                false, Collections.<Filter> emptyList(), BasicFeatureTypes.FEATURE, null);
        return new RevFeatureTypeImpl(id, ftype);
    }

    private static Name readName(DataInput in) throws IOException {
        String namespace = in.readUTF();
        String localPart = in.readUTF();
        return new NameImpl(namespace.length() == 0 ? null : namespace,
                localPart.length() == 0 ? null : localPart);
    }

    private static AttributeType readAttributeType(DataInput in, FeatureTypeFactory typeFactory)
            throws IOException {
        final Name name = readName(in);
        final byte typeTag = in.readByte();
        final FieldType type = FieldType.valueOf(typeTag);
        if (Geometry.class.isAssignableFrom(type.getBinding())) {
            final boolean isCRSCode = in.readBoolean(); // as opposed to a raw WKT string
            final String crsText = in.readUTF();
            final CoordinateReferenceSystem crs;
            try {
                if (isCRSCode) {
                    if ("urn:ogc:def:crs:EPSG::0".equals(crsText)) {
                        crs = null;
                    } else {
                        boolean forceLongitudeFirst = crsText.startsWith("EPSG:");
                        crs = CRS.decode(crsText, forceLongitudeFirst);
                    }
                } else {
                    crs = CRS.parseWKT(crsText);
                }
            } catch (FactoryException e) {
                throw new RuntimeException(e);
            }
            return typeFactory.createGeometryType(name, type.getBinding(), crs, false, false,
                    Collections.<Filter> emptyList(), null, null);
        } else {
            return typeFactory.createAttributeType(name, type.getBinding(), false, false,
                    Collections.<Filter> emptyList(), null, null);
        }
    }

    private static AttributeDescriptor readAttributeDescriptor(DataInput in,
            FeatureTypeFactory typeFactory) throws IOException {
        final Name name = readName(in);
        final boolean nillable = in.readBoolean();
        final int minOccurs = in.readInt();
        final int maxOccurs = in.readInt();
        final AttributeType type = readAttributeType(in, typeFactory);
        if (type instanceof GeometryType)
            return typeFactory.createGeometryDescriptor((GeometryType) type, name, minOccurs,
                    maxOccurs, nillable, null);
        else
            return typeFactory.createAttributeDescriptor(type, name, minOccurs, maxOccurs,
                    nillable, null);
    }

    private static void writeName(Name name, DataOutput data) throws IOException {
        final String ns = name.getNamespaceURI();
        final String lp = name.getLocalPart();
        data.writeUTF(ns == null ? "" : ns);
        data.writeUTF(lp);
    }

    private static void writePropertyType(PropertyType type, DataOutput data) throws IOException {
        writeName(type.getName(), data);
        data.writeByte(FieldType.forBinding(type.getBinding()).getTag());
        if (type instanceof GeometryType) {
            GeometryType gType = (GeometryType) type;
            CoordinateReferenceSystem crs = gType.getCoordinateReferenceSystem();
            String srsName;
            if (crs == null) {
                srsName = "urn:ogc:def:crs:EPSG::0";
            } else {
                final boolean longitudeFirst = CRS.getAxisOrder(crs, false) == AxisOrder.EAST_NORTH;
                final boolean codeOnly = true;
                String crsCode = CRS.toSRS(crs, codeOnly);
                if (crsCode != null) {
                    srsName = (longitudeFirst ? "EPSG:" : "urn:ogc:def:crs:EPSG::") + crsCode;
                    // check that what we are writing is actually a valid EPSG code and we will be
                    // able to decode it later. If not, we will use WKT instead
                    try {
                        CRS.decode(srsName, longitudeFirst);
                    } catch (NoSuchAuthorityCodeException e) {
                        srsName = null;
                    } catch (FactoryException e) {
                        srsName = null;
                    }
                } else {
                    srsName = null;
                }
            }
            if (srsName != null) {
                data.writeBoolean(true);
                data.writeUTF(srsName);
            } else {
                final String wkt;
                if (crs instanceof Formattable) {
                    wkt = ((Formattable) crs).toWKT(Formattable.SINGLE_LINE);
                } else {
                    wkt = crs.toWKT();
                }
                data.writeBoolean(false);
                data.writeUTF(wkt);
            }
        }
    }

    private static void writeProperty(PropertyDescriptor attr, DataOutput data) throws IOException {
        writeName(attr.getName(), data);
        data.writeBoolean(attr.isNillable());
        data.writeInt(attr.getMinOccurs());
        data.writeInt(attr.getMaxOccurs());
        writePropertyType(attr.getType(), data);
    }

}
TOP

Related Classes of org.locationtech.geogig.storage.datastream.FormatCommonV2

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.