package com.revolsys.gis.parallel; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; import com.revolsys.datatype.DataType; import com.revolsys.geometry.model.Geometry; import com.revolsys.logging.Logs; import com.revolsys.parallel.channel.Channel; import com.revolsys.parallel.channel.MultiInputSelector; import com.revolsys.parallel.channel.store.Buffer; import com.revolsys.parallel.process.AbstractInProcess; import com.revolsys.record.Record; import com.revolsys.record.RecordLog; import com.revolsys.record.schema.RecordDefinition; import com.revolsys.util.Property; import com.revolsys.util.Strings; public class OrderedEqualCompareProcessor extends AbstractInProcess<Record> { private List<String> equalExclude = new ArrayList<>(); private String fieldName; private final Set<String> fieldNames = new TreeSet<>(); private Channel<Record> otherIn; private int otherInBufferSize = 0; private String otherName = "Other"; private RecordDefinition recordDefinition1; private RecordDefinition recordDefinition2; private boolean running; private String sourceName = "Source"; private boolean equals(final Geometry geometry1, final Geometry geometry2) { if (geometry1 == null) { return geometry2 == null; } else if (geometry2 == null) { return false; } else if (geometry1.getClass() == geometry2.getClass()) { if (geometry1.isGeometryCollection()) { if (geometry1.getGeometryCount() == geometry2.getGeometryCount()) { for (int i = 0; i < geometry1.getGeometryCount(); i++) { final Geometry subGeometry1 = geometry1.getGeometry(i); final Geometry subGeometry2 = geometry2.getGeometry(i); if (!equals(subGeometry1, subGeometry2)) { return false; } } return true; } else { return false; } } else { return geometry1.equals(geometry1.getAxisCount(), geometry2); } } else { return false; } } protected boolean geometryEquals(final Record object1, final Record object2) { final Geometry geometry1 = object1.getGeometry(); final Geometry geometry2 = object2.getGeometry(); return equals(geometry1, geometry2); } public List<String> getEqualExclude() { return this.equalExclude; } public String getFieldName() { return this.fieldName; } protected Set<String> getNotEqualFieldNames(final Record object1, final Record object2) { final Set<String> notEqualFieldNames = new LinkedHashSet<>(); final String geometryFieldName1 = this.recordDefinition1.getGeometryFieldName(); final String geometryFieldName2 = this.recordDefinition2.getGeometryFieldName(); for (final String fieldName : this.fieldNames) { if (!this.equalExclude.contains(fieldName) && !fieldName.equals(geometryFieldName1) && !fieldName.equals(geometryFieldName2)) { final Object value1 = object1.getValue(fieldName); final Object value2 = object2.getValue(fieldName); if (!valueEquals(value1, value2)) { notEqualFieldNames.add(fieldName); } } } return notEqualFieldNames; } /** * @return the in */ public Channel<Record> getOtherIn() { if (this.otherIn == null) { if (this.otherInBufferSize < 1) { setOtherIn(new Channel<Record>()); } else { final Buffer<Record> buffer = new Buffer<>(this.otherInBufferSize); setOtherIn(new Channel<>(buffer)); } } return this.otherIn; } public int getOtherInBufferSize() { return this.otherInBufferSize; } public String getOtherName() { return this.otherName; } public String getSourceName() { return this.sourceName; } private void initAttributes() { final List<String> fieldNames1 = new ArrayList<>(this.recordDefinition1.getFieldNames()); final List<String> fieldNames2 = new ArrayList<>(this.recordDefinition2.getFieldNames()); this.fieldNames.addAll(fieldNames1); this.fieldNames.retainAll(fieldNames2); fieldNames1.removeAll(this.fieldNames); fieldNames1.remove(this.recordDefinition1.getGeometryFieldName()); if (!fieldNames1.isEmpty()) { Logs.error(this, "Extra columns in file 1: " + fieldNames1); } fieldNames2.removeAll(this.fieldNames); fieldNames2.remove(this.recordDefinition2.getGeometryFieldName()); if (!fieldNames2.isEmpty()) { Logs.error(this, "Extra columns in file 2: " + fieldNames2); } } protected void logNoMatch(final Record record, final boolean other) { if (other) { RecordLog.error(getClass(), this.otherName + " has no match in " + this.sourceName, record); } else { RecordLog.error(getClass(), this.sourceName + " has no match in " + this.otherName, record); } } private void logNoMatch(final Record[] objects, final Channel<Record> channel, final boolean other) { if (objects[0] != null) { logNoMatch(objects[0], false); } if (objects[1] != null) { logNoMatch(objects[1], true); } while (this.running) { final Record object = readObject(channel); logNoMatch(object, other); } } protected void logNotEqual(final Record sourceRecord, final Record otherRecord, final Set<String> notEqualFieldNames, final boolean geometryEquals) { final String fieldNames = Strings.toString(",", notEqualFieldNames); RecordLog.error(getClass(), this.sourceName + " " + fieldNames, sourceRecord); RecordLog.error(getClass(), this.otherName + " " + fieldNames, otherRecord); } protected Record readObject(final Channel<Record> channel) { return channel.read(); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override protected void run(final Channel<Record> in) { this.running = true; final Channel<Record>[] channels = new Channel[] { in, this.otherIn }; Record previousEqualObject = null; final Record[] objects = new Record[2]; final boolean[] guard = new boolean[] { true, true }; final MultiInputSelector alt = new MultiInputSelector(); while (this.running) { final int index = alt.select(channels, guard); if (index == -1) { if (in.isClosed()) { logNoMatch(objects, this.otherIn, true); return; } else if (this.otherIn.isClosed()) { logNoMatch(objects, in, false); return; } else { } } else { final Channel<Record> channel = channels[index]; final Record readObject = readObject(channel); if (index == 0 && this.recordDefinition1 == null) { setRecordDefinition1(readObject.getRecordDefinition()); } else if (index == 1 && this.recordDefinition2 == null) { setRecordDefinition2(readObject.getRecordDefinition()); } if (readObject != null) { if (previousEqualObject != null && DataType.equal(previousEqualObject, readObject)) { if (index == 0) { RecordLog.error(getClass(), "Duplicate in " + this.sourceName, readObject); } else { RecordLog.error(getClass(), "Duplicate in " + this.otherName, readObject); } } else { Record sourceObject; Record otherObject; final int oppositeIndex = (index + 1) % 2; if (index == 0) { sourceObject = readObject; otherObject = objects[oppositeIndex]; } else { sourceObject = objects[oppositeIndex]; otherObject = readObject; } final Object value = readObject.getValue(this.fieldName); if (value == null) { RecordLog.error(getClass(), "Missing key value for " + this.fieldName, readObject); } else if (objects[oppositeIndex] == null) { objects[index] = readObject; guard[index] = false; guard[oppositeIndex] = true; } else { final Object sourceValue = sourceObject.getValue(this.fieldName); final Comparable<Object> sourceComparator; if (sourceValue instanceof Number) { final Number number = (Number)sourceValue; final Double doubleValue = number.doubleValue(); sourceComparator = (Comparable)doubleValue; } else { sourceComparator = (Comparable<Object>)sourceValue; } Object otherValue = otherObject.getValue(this.fieldName); if (otherValue instanceof Number) { final Number number = (Number)otherValue; otherValue = number.doubleValue(); } // TODO duplicates final int compare = sourceComparator.compareTo(otherValue); if (compare == 0) { final Set<String> notEqualFieldNames = getNotEqualFieldNames(sourceObject, otherObject); final boolean geometryEquals = geometryEquals(sourceObject, otherObject); if (!geometryEquals) { final String geometryFieldName = sourceObject.getRecordDefinition() .getGeometryFieldName(); notEqualFieldNames.add(geometryFieldName); } if (!notEqualFieldNames.isEmpty()) { logNotEqual(sourceObject, otherObject, notEqualFieldNames, geometryEquals); } objects[0] = null; objects[1] = null; guard[0] = true; guard[1] = true; previousEqualObject = sourceObject; } else if (compare < 0) { // other object is bigger, keep other // object logNoMatch(sourceObject, false); objects[0] = null; objects[1] = otherObject; guard[0] = true; guard[1] = false; } else { // source is bigger, keep source object logNoMatch(otherObject, true); objects[0] = sourceObject; objects[1] = null; guard[0] = false; guard[1] = true; } } } } } } } public void setEqualExclude(final List<String> equalExclude) { this.equalExclude = equalExclude; } public void setFieldName(final String fieldName) { this.fieldName = fieldName; } /** * @param in the in to set */ public void setOtherIn(final Channel<Record> in) { this.otherIn = in; in.readConnect(); } public void setOtherInBufferSize(final int otherInBufferSize) { this.otherInBufferSize = otherInBufferSize; } public void setOtherName(final String otherName) { this.otherName = otherName; } public void setRecordDefinition1(final RecordDefinition recordDefinition1) { this.recordDefinition1 = recordDefinition1; if (this.recordDefinition2 != null) { initAttributes(); } } public void setRecordDefinition2(final RecordDefinition recordDefinition2) { this.recordDefinition2 = recordDefinition2; if (this.recordDefinition1 != null) { initAttributes(); } } public void setSourceName(final String sourceName) { this.sourceName = sourceName; } protected boolean valueEquals(final Object value1, final Object value2) { if (value1 == null) { if (value2 == null) { return true; } else if (value2 instanceof String) { final String string2 = (String)value2; return !Property.hasValue(string2); } } else if (value2 == null) { if (value1 instanceof String) { final String string1 = (String)value1; return !Property.hasValue(string1); } else { return false; } } else if (value1 instanceof String && value2 instanceof String) { if (!Property.hasValue((String)value1) && !Property.hasValue((String)value2)) { return true; } } return DataType.equal(value1, value2); } }