Package org.locationtech.geogig.storage.text

Source Code of org.locationtech.geogig.storage.text.TextSerializationFactory$TextReader

/* Copyright (c) 2013-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:
* Victor Olaya (Boundless) - initial implementation
*/
package org.locationtech.geogig.storage.text;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import org.geotools.feature.NameImpl;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.locationtech.geogig.api.Bucket;
import org.locationtech.geogig.api.CommitBuilder;
import org.locationtech.geogig.api.Node;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.RevCommit;
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.storage.FieldType;
import org.locationtech.geogig.storage.ObjectReader;
import org.locationtech.geogig.storage.ObjectSerializingFactory;
import org.locationtech.geogig.storage.ObjectWriter;
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.PropertyDescriptor;
import org.opengis.feature.type.PropertyType;
import org.opengis.filter.Filter;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.util.InternationalString;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;

/**
* An {@link ObjectSerialisingFactory} for the {@link RevObject}s text format.
* <p>
* The following formats are used to interchange {@link RevObject} instances in a text format:
* <H3>Commit:</H3>
*
* <pre>
* {@code COMMIT\n}
* {@code "tree" + "\t" +  <tree id> + "\n"}
* {@code "parents" + "\t" +  <parent id> [+ " " + <parent id>...]  + "\n"}
* {@code "author" + "\t" +  <author name>  + " " + <author email>  + "\t" + <author_timestamp> + "\t" + <author_timezone_offset> + "\n"}
* {@code "committer" + "\t" +  <committer name>  + " " + <committer email>  + "\t" + <committer_timestamp> + "\t" + <committer_timezone_offset> + "\n"}     *
* {@code "message" + "\t" +  <message> + "\n"}
* </pre>
*
* <H3>Tree:</H3>
*
* <pre>
* {@code TREE\n}
* {@code "size" + "\t" +  <size> + "\n"}
* {@code "numtrees" + "\t" +  <numtrees> + "\n"}
*
* {@code "BUCKET" + "\t" +  <bucket_idx> + "\t" + <ObjectId> + "\t" + <bounds> + "\n"}
* or
* {@code "REF" + "\t" +  <ref_type> + "\t" + <ref_name> + "\t" + <ObjectId> + "\t" + <MetadataId> + "\t" + <bounds>"\n"}
* .
* .
* .
* </pre>
*
* <H3>Feature:</H3>
*
* <pre>
* {@code FEATURE\n}
* {@code "<attribute_class_1>" + "\t" +  <attribute_value_1> + "\n"}
* .
* .
* .    
* {@code "<attribute_class_n>" + "\t" +  <attribute_value_n> + "\n"}
* For array types, values are written as a space-separated list of single values, enclosed between square brackets
* </pre>
*
* <H3>FeatureType:</H3>
*
* <pre>
* {@code FEATURE_TYPE\n}
* {@code "name" + "\t" +  <name> + "\n"}
* {@code "<attribute_name>" + "\t" +  <attribute_type> + "\t" + <min_occur> + "\t" + <max_occur> +  "\t" + <nillable <True|False>> + "\n"}
* {@code "<attribute_name>" + "\t" +  <attribute_type> + "\t" + <min_occur> + "\t" + <max_occur> +  "\t" + <nillable <True|False>> + "\n"}
* .
* .
* .
*
* </pre>
*
* <H3>Tag:</H3>
*
* <pre>
* {@code "id" + "\t" +  <id> + "\n"}
* ...
* </pre>
*/
public class TextSerializationFactory implements ObjectSerializingFactory {

    // protected static final String NULL = "null";

    @Override
    public ObjectReader<RevCommit> createCommitReader() {
        return COMMIT_READER;
    }

    @Override
    public ObjectReader<RevTree> createRevTreeReader() {
        return TREE_READER;
    }

    @Override
    public ObjectReader<RevFeature> createFeatureReader() {
        return FEATURE_READER;
    }

    @Override
    public ObjectReader<RevFeature> createFeatureReader(Map<String, Serializable> hints) {
        // TODO
        return FEATURE_READER;
    }

    @Override
    public ObjectReader<RevFeatureType> createFeatureTypeReader() {
        return FEATURETYPE_READER;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends RevObject> ObjectWriter<T> createObjectWriter(TYPE type) {
        switch (type) {
        case COMMIT:
            return (ObjectWriter<T>) COMMIT_WRITER;
        case FEATURE:
            return (ObjectWriter<T>) FEATURE_WRITER;
        case FEATURETYPE:
            return (ObjectWriter<T>) FEATURETYPE_WRITER;
        case TREE:
            return (ObjectWriter<T>) TREE_WRITER;
        case TAG:
            return (ObjectWriter<T>) TAG_WRITER;
        default:
            throw new IllegalArgumentException("Unknown or unsupported object type: " + type);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends RevObject> ObjectReader<T> createObjectReader(TYPE type) {
        switch (type) {
        case COMMIT:
            return (ObjectReader<T>) COMMIT_READER;
        case FEATURE:
            return (ObjectReader<T>) FEATURE_READER;
        case FEATURETYPE:
            return (ObjectReader<T>) FEATURETYPE_READER;
        case TREE:
            return (ObjectReader<T>) TREE_READER;
        case TAG:
            return (ObjectReader<T>) TAG_READER;
        default:
            throw new IllegalArgumentException("Unknown or unsupported object type: " + type);
        }
    }

    @Override
    public ObjectReader<RevObject> createObjectReader() {
        return OBJECT_READER;
    }

    /**
     * Abstract text writer that provides print methods on a {@link Writer} to consistently write
     * newlines as {@code \n} instead of using the platform's line separator as in
     * {@link PrintWriter}. It also provides some common method used by different writers.
     */
    private static abstract class TextWriter<T extends RevObject> implements ObjectWriter<T> {

        public static final String NULL_BOUNDING_BOX = "null";

        /**
         * Different types of tree nodes.
         */
        public enum TreeNode {
            REF, BUCKET;
        }

        @Override
        public void write(T object, OutputStream out) throws IOException {
            OutputStreamWriter writer = new OutputStreamWriter(out, "UTF-8");
            println(writer, object.getType().name());
            print(object, writer);
            writer.flush();
        }

        protected abstract void print(T object, Writer w) throws IOException;

        protected void print(Writer w, CharSequence... s) throws IOException {
            if (s == null) {
                return;
            }
            for (CharSequence c : s) {
                w.write(String.valueOf(c));
            }
        }

        protected void println(Writer w, CharSequence... s) throws IOException {
            if (s != null) {
                print(w, s);
            }
            w.write('\n');
        }

        protected void writeNode(Writer w, Node node) throws IOException {
            print(w, TreeNode.REF.name());
            print(w, "\t");
            print(w, node.getType().name());
            print(w, "\t");
            print(w, node.getName());
            print(w, "\t");
            print(w, node.getObjectId().toString());
            print(w, "\t");
            print(w, node.getMetadataId().or(ObjectId.NULL).toString());
            print(w, "\t");
            Envelope envHelper = new Envelope();
            writeBBox(w, node, envHelper);
            println(w);
        }

        protected void writeBBox(Writer w, Node node, Envelope envHelper) throws IOException {
            envHelper.setToNull();
            node.expand(envHelper);
            writeEnvelope(w, envHelper);

        }

        protected void writeEnvelope(Writer w, Envelope envHelper) throws IOException {
            if (envHelper.isNull()) {
                print(w, TextWriter.NULL_BOUNDING_BOX);
                return;
            }

            print(w, Double.toString(envHelper.getMinX()));
            print(w, ";");
            print(w, Double.toString(envHelper.getMaxX()));
            print(w, ";");
            print(w, Double.toString(envHelper.getMinY()));
            print(w, ";");
            print(w, Double.toString(envHelper.getMaxY()));
        }

    }

    /**
     * Commit writer.
     * <p>
     * Output format:
     *
     * <pre>
     * {@code COMMIT\n}
     * {@code "tree" + "\t" +  <tree id> + "\n"}
     * {@code "parents" + "\t" +  <parent id> [+ " " + <parent id>...]  + "\n"}
     * {@code "author" + "\t" +  <author name>  + " " + <author email>  + "\t" + <author_timestamp> + "\t" + <author_timezone_offset> + "\n"}
     * {@code "committer" + "\t" +  <committer name>  + " " + <committer email>  + "\t" + <committer_timestamp> + "\t" + <committer_timezone_offset> + "\n"}     *
     * {@code "message" + "\t" +  <message> + "\n"}
     * </pre>
     *
     */
    private static final TextWriter<RevCommit> COMMIT_WRITER = new TextWriter<RevCommit>() {

        @Override
        protected void print(RevCommit commit, Writer w) throws IOException {
            println(w, "tree\t", commit.getTreeId().toString());
            print(w, "parents\t");
            for (Iterator<ObjectId> it = commit.getParentIds().iterator(); it.hasNext();) {
                print(w, it.next().toString());
                if (it.hasNext()) {
                    print(w, " ");
                }
            }
            println(w);
            printPerson(w, "author", commit.getAuthor());
            printPerson(w, "committer", commit.getCommitter());
            println(w, "message\t", Optional.fromNullable(commit.getMessage()).or(""));
            w.flush();
        }

        private void printPerson(Writer w, String name, RevPerson person) throws IOException {
            print(w, name);
            print(w, "\t");
            print(w, person.getName().or(" "));
            print(w, "\t");
            print(w, person.getEmail().or(" "));
            print(w, "\t");
            print(w, Long.toString(person.getTimestamp()));
            print(w, "\t");
            print(w, Long.toString(person.getTimeZoneOffset()));
            println(w);
        }
    };

    /**
     * Feature writer.
     * <p>
     * Output format:
     *
     * <pre>
     * {@code FEATURE\n}
     * {@code "<attribute_class_1>" + "\t" +  <attribute_value_1> + "\n"}
     * .
     * .
     * .    
     * {@code "<attribute_class_n>" + "\t" +  <attribute_value_n> + "\n"}
     * For array types, values are written as a space-separated list of single values, enclosed between square brackets
     * </pre>
     *
     */
    private static final TextWriter<RevFeature> FEATURE_WRITER = new TextWriter<RevFeature>() {

        @Override
        protected void print(RevFeature feature, Writer w) throws IOException {
            ImmutableList<Optional<Object>> values = feature.getValues();
            for (Optional<Object> opt : values) {
                final FieldType type = FieldType.forValue(opt);
                String valueString = TextValueSerializer.asString(opt);
                println(w, type.toString() + "\t" + valueString);

            }
            w.flush();
        }

    };

    /**
     * Feature type writer.
     * <p>
     * Output format:
     *
     * <pre>
     * {@code FEATURE_TYPE\n}
     * {@code "name" + "\t" +  <name> + "\n"}
     * {@code "<attribute_name>" + "\t" +  <attribute_type> + "\t" + <min_occur> + "\t" + <max_occur> +  "\t" + <nillable <True|False>> + "\n"}
     * {@code "<attribute_name>" + "\t" +  <attribute_type> + "\t" + <min_occur> + "\t" + <max_occur> +  "\t" + <nillable <True|False>> + "\n"}
     * .
     * .
     * .
     *
     * </pre>
     *
     * Geometry attributes have an extra token per line representing the crs
     *
     */
    private static final TextWriter<RevFeatureType> FEATURETYPE_WRITER = new TextWriter<RevFeatureType>() {

        @Override
        protected void print(RevFeatureType featureType, Writer w) throws IOException {
            println(w, "name\t", featureType.getName().toString());
            Collection<PropertyDescriptor> attribs = featureType.type().getDescriptors();
            for (PropertyDescriptor attrib : attribs) {
                printAttributeDescriptor(w, attrib);
            }
            w.flush();
        }

        private void printAttributeDescriptor(Writer w, PropertyDescriptor attrib)
                throws IOException {
            print(w, attrib.getName().toString());
            print(w, "\t");
            print(w, FieldType.forBinding(attrib.getType().getBinding()).name());
            print(w, "\t");
            print(w, Integer.toString(attrib.getMinOccurs()));
            print(w, "\t");
            print(w, Integer.toString(attrib.getMaxOccurs()));
            print(w, "\t");
            print(w, Boolean.toString(attrib.isNillable()));
            PropertyType attrType = attrib.getType();
            if (attrType instanceof GeometryType) {
                GeometryType gt = (GeometryType) attrType;
                CoordinateReferenceSystem crs = gt.getCoordinateReferenceSystem();
                String crsText = CrsTextSerializer.serialize(crs);
                print(w, "\t");
                println(w, crsText);
            } else {
                println(w, "");
            }

        }

    };

    /**
     * Tree writer.
     * <p>
     * Output format:
     *
     * <pre>
     * {@code TREE\n}
     * {@code "size" + "\t" +  <size> + "\n"}
     * {@code "numtrees" + "\t" +  <numtrees> + "\n"}
     *
     * {@code "BUCKET" + "\t" +  <bucket_idx> + "\t" + <ObjectId> + "\t" + <bounds> + "\n"}
     * or
     * {@code "REF" + "\t" +  <ref_type> + "\t" + <ref_name> + "\t" + <ObjectId> + "\t" + <MetadataId> + "\t" + <bounds>"\n"}
     * .
     * .
     * .
     * </pre>
     */
    private static final TextWriter<RevTree> TREE_WRITER = new TextWriter<RevTree>() {

        @Override
        protected void print(RevTree revTree, Writer w) throws IOException {
            println(w, "size\t", Long.toString(revTree.size()));
            println(w, "numtrees\t", Integer.toString(revTree.numTrees()));
            if (revTree.trees().isPresent()) {
                writeChildren(w, revTree.trees().get());
            }
            if (revTree.features().isPresent()) {
                writeChildren(w, revTree.features().get());
            } else if (revTree.buckets().isPresent()) {
                writeBuckets(w, revTree.buckets().get());
            }

        }

        private void writeChildren(Writer w, ImmutableCollection<Node> children) throws IOException {
            for (Node ref : children) {
                writeNode(w, ref);
            }
        }

        private void writeBuckets(Writer w, ImmutableSortedMap<Integer, Bucket> buckets)
                throws IOException {

            for (Entry<Integer, Bucket> entry : buckets.entrySet()) {
                Integer bucketIndex = entry.getKey();
                Bucket bucket = entry.getValue();
                print(w, TreeNode.BUCKET.name());
                print(w, "\t");
                print(w, bucketIndex.toString());
                print(w, "\t");
                print(w, bucket.id().toString());
                print(w, "\t");
                Envelope env = new Envelope();
                env.setToNull();
                bucket.expand(env);
                writeEnvelope(w, env);
                println(w);
            }
        }

    };

    /**
     * Tag writer.
     * <p>
     * Output format:
     *
     * <pre>
     * {@code TAG\n}
     * {@code "name" + "\t" +  <tagname> + "\n"}
     * {@code "commitid" + "\t" +  <comitid> + "\n"} 
     * {@code "message" + "\t" +  <message> + "\n"}
     *  {@code "tagger" + "\t" +  <tagger name>  + " " + <tagger email>  + "\t" + <tagger> + "\t" + <tagger_timezone_offset> + "\n"}
     * </pre>
     *
     */
    private static final TextWriter<RevTag> TAG_WRITER = new TextWriter<RevTag>() {

        @Override
        protected void print(RevTag tag, Writer w) throws IOException {
            println(w, "name\t", tag.getName());
            println(w, "commitid\t", tag.getCommitId().toString());
            println(w, "message\t", tag.getMessage());
            print(w, "tagger");
            print(w, "\t");
            print(w, tag.getTagger().getName().or(" "));
            print(w, "\t");
            print(w, tag.getTagger().getEmail().or(" "));
            print(w, "\t");
            print(w, Long.toString(tag.getTagger().getTimestamp()));
            print(w, "\t");
            print(w, Long.toString(tag.getTagger().getTimeZoneOffset()));
            println(w);
            w.flush();
        }

    };

    private abstract static class TextReader<T extends RevObject> implements ObjectReader<T> {

        @Override
        public T read(ObjectId id, InputStream rawData) throws IllegalArgumentException {
            try {
                BufferedReader reader;
                reader = new BufferedReader(new InputStreamReader(rawData, "UTF-8"));
                TYPE type = RevObject.TYPE.valueOf(requireLine(reader).trim());
                T parsed = read(id, reader, type);
                Preconditions.checkState(parsed != null, "parsed to null");
                if (id != null) {
                    Preconditions
                            .checkState(id.equals(parsed.getId()),
                                    "Expected and parsed object ids don't match: %s %s", id,
                                    parsed.getId());
                }
                return parsed;
            } catch (Exception e) {
                throw Throwables.propagate(e);
            }
        }

        protected String parseLine(String line, String expectedHeader) throws IOException {
            List<String> fields = Lists.newArrayList(Splitter.on('\t').split(line));
            Preconditions.checkArgument(fields.size() == 2, "Expected %s\\t<...>, got '%s'",
                    expectedHeader, line);
            Preconditions.checkArgument(expectedHeader.equals(fields.get(0)),
                    "Expected field %s, got '%s'", expectedHeader, fields.get(0));
            String value = fields.get(1);
            return value;
        }

        protected abstract T read(ObjectId id, BufferedReader reader, TYPE type) throws IOException;

        protected Node parseNodeLine(String line) {
            List<String> tokens = Lists.newArrayList(Splitter.on('\t').split(line));
            Preconditions.checkArgument(tokens.size() == 6, "Wrong tree element definition: %s",
                    line);
            TYPE type = TYPE.valueOf(tokens.get(1));
            String name = tokens.get(2);
            ObjectId id = ObjectId.valueOf(tokens.get(3));
            ObjectId metadataId = ObjectId.valueOf(tokens.get(4));
            Envelope bbox = parseBBox(tokens.get(5));

            org.locationtech.geogig.api.Node ref = org.locationtech.geogig.api.Node.create(name, id, metadataId, type, bbox);

            return ref;

        }

        protected Envelope parseBBox(String s) {
            if (s.equals(TextWriter.NULL_BOUNDING_BOX)) {
                return new Envelope();
            }
            List<String> tokens = Lists.newArrayList(Splitter.on(';').split(s));
            Preconditions.checkArgument(tokens.size() == 4, "Wrong bounding box definition: %s", s);

            double minx = Double.parseDouble(tokens.get(0));
            double maxx = Double.parseDouble(tokens.get(1));
            double miny = Double.parseDouble(tokens.get(2));
            double maxy = Double.parseDouble(tokens.get(3));

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

    }

    private static final TextReader<RevObject> OBJECT_READER = new TextReader<RevObject>() {

        @Override
        protected RevObject read(ObjectId id, BufferedReader read, TYPE type) throws IOException {
            switch (type) {
            case COMMIT:
                return COMMIT_READER.read(id, read, type);
            case FEATURE:
                return FEATURE_READER.read(id, read, type);
            case TREE:
                return TREE_READER.read(id, read, type);
            case FEATURETYPE:
                return FEATURETYPE_READER.read(id, read, type);
            case TAG:
                return TAG_READER.read(id, read, type);
            default:
                throw new IllegalArgumentException("Unknown object type " + type);
            }
        }

    };

    /**
     * Commit reader.
     * <p>
     * Parses a commit of the format:
     *
     * <pre>
     * {@code COMMIT\n}
     * {@code "tree" + "\t" +  <tree id> + "\n"}
     * {@code "parents" + "\t" +  <parent id> [+ " " + <parent id>...]  + "\n"}
     * {@code "author" + "\t" +  <author name>  + " " + <author email>  + "\t" + <author_timestamp> + "\t" + <author_timezone_offset> + "\n"}
     * {@code "committer" + "\t" +  <committer name>  + " " + <committer email>  + "\t" + <committer_timestamp> + "\t" + <committer_timezone_offset> + "\n"}     *
     * {@code "message" + "\t" +  <message> + "\n"}
     * </pre>
     *
     */
    private static final TextReader<RevCommit> COMMIT_READER = new TextReader<RevCommit>() {

        @Override
        protected RevCommit read(ObjectId id, BufferedReader reader, TYPE type) throws IOException {
            Preconditions.checkArgument(TYPE.COMMIT.equals(type), "Wrong type: %s", type.name());
            String tree = parseLine(requireLine(reader), "tree");
            List<String> parents = Lists.newArrayList(Splitter.on(' ').omitEmptyStrings()
                    .split(parseLine(requireLine(reader), "parents")));
            RevPerson author = parsePerson(requireLine(reader), "author");
            RevPerson committer = parsePerson(requireLine(reader), "committer");
            String message = parseMessage(reader);

            CommitBuilder builder = new CommitBuilder();
            builder.setAuthor(author.getName().orNull());
            builder.setAuthorEmail(author.getEmail().orNull());
            builder.setAuthorTimestamp(author.getTimestamp());
            builder.setAuthorTimeZoneOffset(author.getTimeZoneOffset());
            builder.setCommitter(committer.getName().orNull());
            builder.setCommitterEmail(committer.getEmail().orNull());
            builder.setCommitterTimestamp(committer.getTimestamp());
            builder.setCommitterTimeZoneOffset(committer.getTimeZoneOffset());
            builder.setMessage(message);
            List<ObjectId> parentIds = Lists.newArrayList(Iterators.transform(parents.iterator(),
                    new Function<String, ObjectId>() {

                        @Override
                        public ObjectId apply(String input) {
                            ObjectId objectId = ObjectId.valueOf(input);
                            return objectId;
                        }
                    }));
            builder.setParentIds(parentIds);
            builder.setTreeId(ObjectId.valueOf(tree));
            RevCommit commit = builder.build();
            return commit;
        }

        private RevPerson parsePerson(String line, String expectedHeader) throws IOException {
            String[] tokens = line.split("\t");
            Preconditions.checkArgument(expectedHeader.equals(tokens[0]),
                    "Expected field %s, got '%s'", expectedHeader, tokens[0]);
            String name = tokens[1].trim().isEmpty() ? null : tokens[1];
            String email = tokens[2].trim().isEmpty() ? null : tokens[2];
            long timestamp = Long.parseLong(tokens[3]);
            int offset = Integer.parseInt(tokens[4]);
            return new RevPersonImpl(name, email, timestamp, offset);
        }

        private String parseMessage(BufferedReader reader) throws IOException {
            StringBuilder msg = new StringBuilder(parseLine(requireLine(reader), "message"));
            String extraLine;
            while ((extraLine = reader.readLine()) != null) {
                msg.append('\n').append(extraLine);
            }
            return msg.toString();
        }

    };

    /**
     * Feature reader.
     * <p>
     * Parses a feature in the format:
     *
     * <pre>
     * {@code "<attribute class_1>" + "\t" +  <attribute_value_1> + "\n"}
     * .
     * .
     * .    
     * {@code "<attribute class_n>" + "\t" +  <attribute_value_n> + "\n"}
     *
     * Array values are written as a space-separated list of single values, enclosed between square brackets
     * </pre>
     *
     */
    private static final TextReader<RevFeature> FEATURE_READER = new TextReader<RevFeature>() {

        @Override
        protected RevFeature read(ObjectId id, BufferedReader reader, TYPE type) throws IOException {
            Preconditions.checkArgument(TYPE.FEATURE.equals(type), "Wrong type: %s", type.name());
            List<Object> values = Lists.newArrayList();
            String line;
            while ((line = reader.readLine()) != null) {
                values.add(parseAttribute(line));
            }

            ImmutableList.Builder<Optional<Object>> valuesBuilder = new ImmutableList.Builder<Optional<Object>>();
            for (Object value : values) {
                valuesBuilder.add(Optional.fromNullable(value));
            }
            return RevFeatureImpl.build(valuesBuilder.build());
        }

        private Object parseAttribute(String line) {
            List<String> tokens = Lists.newArrayList(Splitter.on('\t').split(line));
            Preconditions.checkArgument(tokens.size() == 2, "Wrong attribute definition: %s", line);
            String typeName = tokens.get(0);
            String value = tokens.get(1);
            FieldType type;
            try {
                type = FieldType.valueOf(typeName);
            } catch (IllegalArgumentException e) {
                throw new IllegalArgumentException("Wrong type name: " + typeName);
            }
            return TextValueSerializer.fromString(type, value);
        }

    };

    /**
     * Feature type reader.
     * <p>
     * Parses a feature type in the format:
     *
     * <pre>
     * {@code "id" + "\t" +  <id> + "\n"}
     * {@code "name" + "\t" +  <name> + "\n"}
     * {@code "<attribute_name1>" + "\t" +  <attribute_class1> + "\t" + <min_occur> + "\t" + <max_occur> +  "\n" + <nillable <True|False>>}
     * .
     * .
     * .
     *
     * </pre>
     *
     * Geometry attributes have an extra token per line representing the crs
     *
     */
    private static final TextReader<RevFeatureType> FEATURETYPE_READER = new TextReader<RevFeatureType>() {

        private SimpleFeatureTypeBuilder builder;

        private FeatureTypeFactory typeFactory;

        @Override
        protected RevFeatureType read(ObjectId id, BufferedReader reader, TYPE type)
                throws IOException {
            Preconditions.checkArgument(TYPE.FEATURETYPE.equals(type), "Wrong type: %s",
                    type.name());
            builder = new SimpleFeatureTypeBuilder();
            typeFactory = builder.getFeatureTypeFactory();
            String name = parseLine(requireLine(reader), "name");
            SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
            if (name.contains(":")) {
                int idx = name.lastIndexOf(':');
                String namespace = name.substring(0, idx);
                String local = name.substring(idx + 1);
                builder.setName(new NameImpl(namespace, local));
            } else {
                builder.setName(new NameImpl(name));
            }

            String line;
            while ((line = reader.readLine()) != null) {
                builder.add(parseAttributeDescriptor(line));
            }
            SimpleFeatureType sft = builder.buildFeatureType();
            return RevFeatureTypeImpl.build(sft);

        }

        private AttributeDescriptor parseAttributeDescriptor(String line) {
            ArrayList<String> tokens = Lists.newArrayList(Splitter.on('\t').split(line));
            Preconditions.checkArgument(tokens.size() == 5 || tokens.size() == 6,
                    "Wrong attribute definition: %s", line);
            NameImpl name = new NameImpl(tokens.get(0));
            Class<?> type;
            try {
                type = FieldType.valueOf(tokens.get(1)).getBinding();
            } catch (IllegalArgumentException e) {
                throw new IllegalArgumentException("Wrong type: " + tokens.get(1));
            }
            int min = Integer.parseInt(tokens.get(2));
            int max = Integer.parseInt(tokens.get(3));
            boolean nillable = Boolean.parseBoolean(tokens.get(4));

            /*
             * Default values that are currently not encoded.
             */
            boolean isIdentifiable = false;
            boolean isAbstract = false;
            List<Filter> restrictions = null;
            AttributeType superType = null;
            InternationalString description = null;
            Object defaultValue = null;

            AttributeType attributeType;
            AttributeDescriptor attributeDescriptor;
            if (Geometry.class.isAssignableFrom(type)) {
                String crsText = tokens.get(5);
                CoordinateReferenceSystem crs = CrsTextSerializer.deserialize(crsText);

                attributeType = typeFactory.createGeometryType(name, type, crs, isIdentifiable,
                        isAbstract, restrictions, superType, description);
                attributeDescriptor = typeFactory.createGeometryDescriptor(
                        (GeometryType) attributeType, name, min, max, nillable, defaultValue);
            } else {
                attributeType = typeFactory.createAttributeType(name, type, isIdentifiable,
                        isAbstract, restrictions, superType, description);
                attributeDescriptor = typeFactory.createAttributeDescriptor(attributeType, name,
                        min, max, nillable, defaultValue);
            }
            return attributeDescriptor;
        }
    };

    /**
     * Tree reader.
     * <p>
     * Parses a tree in the format:
     *
     * <pre>
     * {@code "id" + "\t" +  <id> + "\n"}
     *
     * {@code "BUCKET" + "\t" +  <bucket_idx> + "\t" + <ObjectId> +"\t" + <bounds> "\n"}
     * or
     * {@code "REF" + "\t" +  <ref_type> + "\t" + <ref_name> + "\t" + <ObjectId> + "\t" + <MetadataId> + "\t" + <bounds> + "\n"}
     * .
     * .
     * .
     * </pre>
     *
     */
    private static final TextReader<RevTree> TREE_READER = new TextReader<RevTree>() {

        @Override
        protected RevTree read(ObjectId id, BufferedReader reader, TYPE type) throws IOException {
            Preconditions.checkArgument(TYPE.TREE.equals(type), "Wrong type: %s", type.name());
            Builder<Node> features = ImmutableList.builder();
            Builder<Node> trees = ImmutableList.builder();
            TreeMap<Integer, Bucket> subtrees = Maps.newTreeMap();
            long size = Long.parseLong(parseLine(requireLine(reader), "size"));
            int numTrees = Integer.parseInt(parseLine(requireLine(reader), "numtrees"));
            String line;
            while ((line = reader.readLine()) != null) {
                Preconditions.checkArgument(!line.isEmpty(), "Empty tree element definition");
                ArrayList<String> tokens = Lists.newArrayList(Splitter.on('\t').split(line));
                String nodeType = tokens.get(0);
                if (nodeType.equals(TextWriter.TreeNode.REF.name())) {
                    Node entryRef = parseNodeLine(line);
                    if (entryRef.getType().equals(TYPE.TREE)) {
                        trees.add(entryRef);
                    } else {
                        features.add(entryRef);
                    }
                } else if (nodeType.equals(TextWriter.TreeNode.BUCKET.name())) {
                    Preconditions.checkArgument(tokens.size() == 4, "Wrong bucket definition: %s",
                            line);
                    Integer idx = Integer.parseInt(tokens.get(1));
                    ObjectId bucketId = ObjectId.valueOf(tokens.get(2));
                    Envelope bounds = parseBBox(tokens.get(3));
                    Bucket bucket = Bucket.create(bucketId, bounds);
                    subtrees.put(idx, bucket);
                } else {
                    throw new IllegalArgumentException("Wrong tree element definition: " + line);
                }

            }

            RevTree tree;
            if (subtrees.isEmpty()) {
                tree = RevTreeImpl.createLeafTree(id, size, features.build(), trees.build());
            } else {
                tree = RevTreeImpl.createNodeTree(id, size, numTrees, subtrees);
            }
            return tree;
        }
    };

    /**
     * Tag reader.
     * <p>
     * Parses a tag of the format:
     *
     * <pre>
     * {@code TAG\n}
     * {@code "name" + "\t" +  <tagname> + "\n"}
     * {@code "commitid" + "\t" +  <comitid> + "\n"} 
     * {@code "message" + "\t" +  <message> + "\n"}
     *  {@code "tagger" + "\t" +  <tagger name>  + " " + <tagger email>  + "\t" + <tagger> + "\t" + <tagger_timezone_offset> + "\n"}
     * </pre>
     *
     */
    private static final TextReader<RevTag> TAG_READER = new TextReader<RevTag>() {

        @Override
        protected RevTag read(ObjectId id, BufferedReader reader, TYPE type) throws IOException {
            Preconditions.checkArgument(TYPE.TAG.equals(type), "Wrong type: %s", type.name());
            String name = parseLine(requireLine(reader), "name");
            String message = parseLine(requireLine(reader), "message");
            String commitId = parseLine(requireLine(reader), "commitid");
            RevPerson tagger = parsePerson(requireLine(reader));
            RevTag tag = new RevTagImpl(id, name, ObjectId.valueOf(commitId), message, tagger);
            return tag;
        }

        private RevPerson parsePerson(String line) throws IOException {
            String[] tokens = line.split("\t");
            String header = "tagger";
            Preconditions.checkArgument(header.equals(tokens[0]), "Expected field %s, got '%s'",
                    header, tokens[0]);
            String name = tokens[1].trim().isEmpty() ? null : tokens[1];
            String email = tokens[2].trim().isEmpty() ? null : tokens[2];
            long timestamp = Long.parseLong(tokens[3]);
            int offset = Integer.parseInt(tokens[4]);
            return new RevPersonImpl(name, email, timestamp, offset);
        }

    };

    private static String requireLine(BufferedReader reader) throws IOException {
        String line = reader.readLine();
        if (line == null) {
            throw new IllegalStateException("Expected line bug got EOF");
        }
        return line;
    }

}
TOP

Related Classes of org.locationtech.geogig.storage.text.TextSerializationFactory$TextReader

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.