From 6b8f98db93689370a6df47a8645c80b1b0b39480 Mon Sep 17 00:00:00 2001 From: Karl Wright Date: Wed, 23 Aug 2017 10:00:31 -0400 Subject: [PATCH] LUCENE-7936: Add Geo3d framework for serialization and deserialization. --- .../spatial3d/geom/BasePlanetObject.java | 12 +- .../spatial3d/geom/GeoDegeneratePoint.java | 15 +- .../lucene/spatial3d/geom/GeoPoint.java | 24 ++- .../lucene/spatial3d/geom/PlanetObject.java | 1 + .../spatial3d/geom/SerializableObject.java | 200 ++++++++++++++++++ 5 files changed, 248 insertions(+), 4 deletions(-) create mode 100644 lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/SerializableObject.java diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/BasePlanetObject.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/BasePlanetObject.java index 13072fe77d0..86ec61ff469 100644 --- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/BasePlanetObject.java +++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/BasePlanetObject.java @@ -16,13 +16,16 @@ */ package org.apache.lucene.spatial3d.geom; +import java.io.OutputStream; +import java.io.IOException; + /** * All Geo3D shapes can derive from this base class, which furnishes * some common code * * @lucene.internal */ -public abstract class BasePlanetObject implements PlanetObject { +public abstract class BasePlanetObject implements PlanetObject, SerializableObject { /** This is the planet model embedded in all objects derived from this * class. */ @@ -40,6 +43,11 @@ public abstract class BasePlanetObject implements PlanetObject { return planetModel; } + @Override + public void write(final OutputStream outputStream) throws IOException { + throw new UnsupportedOperationException(); + } + @Override public int hashCode() { return planetModel.hashCode(); @@ -51,6 +59,8 @@ public abstract class BasePlanetObject implements PlanetObject { return false; return planetModel.equals(((BasePlanetObject)o).planetModel); } + + } diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegeneratePoint.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegeneratePoint.java index 93d47088ff9..8a96fdf10f3 100644 --- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegeneratePoint.java +++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegeneratePoint.java @@ -15,7 +15,10 @@ * limitations under the License. */ package org.apache.lucene.spatial3d.geom; - + +import java.io.InputStream; +import java.io.IOException; + /** * This class represents a degenerate point bounding box. * It is not a simple GeoPoint because we must have the latitude and longitude. @@ -39,6 +42,16 @@ class GeoDegeneratePoint extends GeoPoint implements GeoBBox, GeoCircle { this.edgePoints = new GeoPoint[]{this}; } + /** Constructor for deserialization. + *@param planetModel is the planet model to use. + *@param inputStream is the input stream. + */ + public GeoDegeneratePoint(final PlanetModel planetModel, final InputStream inputStream) throws IOException { + super(planetModel, inputStream); + this.planetModel = planetModel; + this.edgePoints = new GeoPoint[]{this}; + } + @Override public PlanetModel getPlanetModel() { return planetModel; diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPoint.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPoint.java index a63dd8f9fd4..de51a2b118b 100755 --- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPoint.java +++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPoint.java @@ -15,13 +15,17 @@ * limitations under the License. */ package org.apache.lucene.spatial3d.geom; - + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + /** * This class represents a point on the surface of a sphere or ellipsoid. * * @lucene.experimental */ -public class GeoPoint extends Vector { +public class GeoPoint extends Vector implements SerializableObject { // By making lazily-evaluated variables be "volatile", we guarantee atomicity when they // are updated. This is necessary if we are using these classes in a multi-thread fashion, @@ -73,6 +77,14 @@ public class GeoPoint extends Vector { this(planetModel, Math.sin(lat), Math.sin(lon), Math.cos(lat), Math.cos(lon), lat, lon); } + /** Construct a GeoPoint from a planet model and an input stream. + */ + public GeoPoint(final PlanetModel planetModel, final InputStream inputStream) throws IOException { + // Note: this relies on left-right parameter execution order!! Much code depends on that though and + // it is apparently in a java spec: https://stackoverflow.com/questions/2201688/order-of-execution-of-parameters-guarantees-in-java + this(planetModel, SerializableObject.readDouble(inputStream), SerializableObject.readDouble(inputStream)); + } + /** Construct a GeoPoint from a unit (x,y,z) vector and a magnitude. * @param magnitude is the desired magnitude, provided to put the point on the ellipsoid. * @param x is the unit x value. @@ -115,6 +127,12 @@ public class GeoPoint extends Vector { super(x, y, z); } + @Override + public void write(final OutputStream outputStream) throws IOException { + SerializableObject.writeDouble(outputStream, getLatitude()); + SerializableObject.writeDouble(outputStream, getLongitude()); + } + /** Compute an arc distance between two points. * Note: this is an angular distance, and not a surface distance, and is therefore independent of planet model. * For surface distance, see {@link PlanetModel#surfaceDistance(GeoPoint, GeoPoint)} @@ -190,4 +208,6 @@ public class GeoPoint extends Vector { } return "[lat="+getLatitude()+", lon="+getLongitude()+"("+super.toString()+")]"; } + + } diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/PlanetObject.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/PlanetObject.java index 05ed7b15be9..836bf4ceba2 100644 --- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/PlanetObject.java +++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/PlanetObject.java @@ -26,4 +26,5 @@ public interface PlanetObject { /** Returns the {@link PlanetModel} provided when this shape was created. */ PlanetModel getPlanetModel(); + } diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/SerializableObject.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/SerializableObject.java new file mode 100644 index 00000000000..618ebafcbd8 --- /dev/null +++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/SerializableObject.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.lucene.spatial3d.geom; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * Indicates that a geo3d object can be serialized and deserialized. + * + * @lucene.experimental + */ +public interface SerializableObject { + + /** Serialize to output stream. + * @param outputStream is the output stream to write to. + */ + void write(OutputStream outputStream) throws IOException; + + /** Write an object to a stream. + * @param outputStream is the output stream. + * @param object is the object to write. + */ + static void writeObject(final OutputStream outputStream, final SerializableObject object) throws IOException { + writeString(outputStream, object.getClass().getName()); + object.write(outputStream); + } + + /** Read an object from a stream (for objects that need a PlanetModel). + * @param planetModel is the planet model to use to deserialize the object. + * @param inputStream is the input stream. + * @return the deserialized object. + */ + static SerializableObject readObject(final PlanetModel planetModel, final InputStream inputStream) throws IOException { + // Read the class name + final String className = readString(inputStream); + try { + // Look for the class + final Class clazz = Class.forName(className); + // Look for the right constructor + final Constructor c = clazz.getDeclaredConstructor(PlanetModel.class, InputStream.class); + // Invoke it + final Object object = c.newInstance(planetModel, inputStream); + // check whether caste will work + if (!(object instanceof SerializableObject)) { + throw new IOException("Object "+className+" does not implement SerializableObject"); + } + return (SerializableObject)object; + } catch (ClassNotFoundException e) { + throw new IOException("Can't find class "+className+" for deserialization: "+e.getMessage(), e); + } catch (InstantiationException e) { + throw new IOException("Instantiation exception for class "+className+": "+e.getMessage(), e); + } catch (IllegalAccessException e) { + throw new IOException("Illegal access creating class "+className+": "+e.getMessage(), e); + } catch (NoSuchMethodException e) { + throw new IOException("No such method exception for class "+className+": "+e.getMessage(), e); + } catch (InvocationTargetException e) { + throw new IOException("Exception instantiating class "+className+": "+e.getMessage(), e); + } + } + + /** Read an object from a stream (for objects that do not need a PlanetModel). + * @param inputStream is the input stream. + * @return the deserialized object. + */ + static SerializableObject readObject(final InputStream inputStream) throws IOException { + // Read the class name + final String className = readString(inputStream); + try { + // Look for the class + final Class clazz = Class.forName(className); + // Look for the right constructor + final Constructor c = clazz.getDeclaredConstructor(InputStream.class); + // Invoke it + final Object object = c.newInstance(inputStream); + // check whether caste will work + if (!(object instanceof SerializableObject)) { + throw new IOException("Object "+className+" does not implement SerializableObject"); + } + return (SerializableObject)object; + } catch (ClassNotFoundException e) { + throw new IOException("Can't find class "+className+" for deserialization: "+e.getMessage(), e); + } catch (InstantiationException e) { + throw new IOException("Instantiation exception for class "+className+": "+e.getMessage(), e); + } catch (IllegalAccessException e) { + throw new IOException("Illegal access creating class "+className+": "+e.getMessage(), e); + } catch (NoSuchMethodException e) { + throw new IOException("No such method exception for class "+className+": "+e.getMessage(), e); + } catch (InvocationTargetException e) { + throw new IOException("Exception instantiating class "+className+": "+e.getMessage(), e); + } + } + + /** Write a string to a stream. + * @param outputStream is the output stream. + * @param value is the string to write. + */ + static void writeString(final OutputStream outputStream, final String value) throws IOException { + final byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + writeInt(outputStream, bytes.length); + outputStream.write(bytes); + } + + /** Read a string from a stream. + * @param inputStream is the stream to read from. + * @return the string that was read. + */ + static String readString(final InputStream inputStream) throws IOException { + int stringLength = readInt(inputStream); + int stringOffset = 0; + final byte[] bytes = new byte[stringLength]; + while (stringLength > 0) { + final int amt = inputStream.read(bytes, stringOffset, stringLength); + if (amt == -1) { + throw new IOException("Unexpected end of input stream"); + } + stringOffset += amt; + stringLength -= amt; + } + return new String(bytes, StandardCharsets.UTF_8); + } + + /** Write a double to a stream. + * @param outputStream is the output stream. + * @param value is the value to write. + */ + static void writeDouble(final OutputStream outputStream, final double value) throws IOException { + writeLong(outputStream, Double.doubleToLongBits(value)); + } + + /** Read a double from a stream. + * @param inputStream is the input stream. + * @return the double value read from the stream. + */ + static double readDouble(final InputStream inputStream) throws IOException { + return Double.longBitsToDouble(readLong(inputStream)); + } + + /** Write a long to a stream. + * @param outputStream is the output stream. + * @param value is the value to write. + */ + static void writeLong(final OutputStream outputStream, final long value) throws IOException { + writeInt(outputStream, (int)value); + writeInt(outputStream, (int)(value >> 32)); + } + + /** Read a long from a stream. + * @param inputStream is the input stream. + * @return the long value read from the stream. + */ + static long readLong(final InputStream inputStream) throws IOException { + final long lower = ((long)(readInt(inputStream))) & 0x00000000ffffffffL; + final long upper = (((long)(readInt(inputStream))) << 32) & 0xffffffff00000000L; + return lower + upper; + } + + /** Write an int to a stream. + * @param outputStream is the output stream. + * @param value is the value to write. + */ + static void writeInt(final OutputStream outputStream, final int value) throws IOException { + outputStream.write(value); + outputStream.write(value >> 8); + outputStream.write(value >> 16); + outputStream.write(value >> 24); + } + + /** Read an int from a stream. + * @param inputStream is the input stream. + * @return the value read from the stream. + */ + static int readInt(final InputStream inputStream) throws IOException { + final int l1 = (inputStream.read()) & 0x000000ff; + final int l2 = (inputStream.read() << 8) & 0x0000ff00; + final int l3 = (inputStream.read() << 16) & 0x00ff0000; + final int l4 = (inputStream.read() << 24) & 0xff000000; + return l1 + l2 + l3 + l4; + } + +}