package com.revolsys.gis.parallel; import com.revolsys.collection.ArrayUtil; import com.revolsys.parallel.channel.Channel; import com.revolsys.parallel.channel.ClosedException; import com.revolsys.parallel.channel.MultiInputSelector; import com.revolsys.parallel.channel.store.Buffer; import com.revolsys.parallel.process.AbstractInOutProcess; import com.revolsys.record.Record; import com.revolsys.record.schema.RecordDefinition; public abstract class AbstractMergeProcess extends AbstractInOutProcess<Record, Record> { private static final int OTHER_INDEX = 1; private static final int SOURCE_INDEX = 0; private Channel<Record> otherIn; private int otherInBufferSize = 0; private void addObjectFromOtherChannel(final Channel<Record>[] channels, final boolean[] guard, final Record[] objects, final int channelIndex) { int otherIndex; if (channelIndex == SOURCE_INDEX) { otherIndex = OTHER_INDEX; } else { otherIndex = SOURCE_INDEX; } final Channel<Record> otherChannel = channels[otherIndex]; if (otherChannel == null) { guard[otherIndex] = false; guard[channelIndex] = true; } else if (guard[otherIndex]) { while (objects[otherIndex] == null) { try { final Record object = otherChannel.read(); if (testObject(object)) { objects[otherIndex] = object; return; } } catch (final ClosedException e) { guard[otherIndex] = false; guard[channelIndex] = true; return; } } } } /** * Add an object from the other (otherId) channel. * * @param object The object to add. */ protected abstract void addOtherObject(Record object); private RecordDefinition addSavedObjects(final RecordDefinition currentType, final String currentTypeName, final Channel<Record> out, final boolean[] guard, final Record[] objects) { final Record sourceObject = objects[SOURCE_INDEX]; final Record otherObject = objects[OTHER_INDEX]; if (sourceObject == null) { if (otherObject == null) { return null; } else { addOtherObject(otherObject); objects[OTHER_INDEX] = null; guard[OTHER_INDEX] = true; return otherObject.getRecordDefinition(); } } else if (otherObject == null) { if (sourceObject == null) { return null; } else { addSourceObject(sourceObject); objects[SOURCE_INDEX] = null; guard[SOURCE_INDEX] = true; return sourceObject.getRecordDefinition(); } } else { final RecordDefinition sourceType = sourceObject.getRecordDefinition(); final String sourceTypeName = sourceType.getPath(); final RecordDefinition otherType = otherObject.getRecordDefinition(); final String otherTypeName = otherType.getPath(); if (sourceTypeName.equals(currentTypeName)) { addSourceObject(sourceObject); objects[SOURCE_INDEX] = null; guard[SOURCE_INDEX] = true; objects[OTHER_INDEX] = otherObject; guard[OTHER_INDEX] = false; return currentType; } else if (otherTypeName.equals(currentTypeName)) { addOtherObject(otherObject); objects[SOURCE_INDEX] = sourceObject; guard[SOURCE_INDEX] = false; objects[OTHER_INDEX] = null; guard[OTHER_INDEX] = true; return currentType; } else { processObjects(currentType, out); final int nameCompare = sourceTypeName.toString().compareTo(otherTypeName.toString()); if (nameCompare < 0) { // If the first feature type name is < second feature type // name // then add the first feature and save the second feature // for later addSourceObject(sourceObject); objects[SOURCE_INDEX] = null; guard[SOURCE_INDEX] = true; objects[OTHER_INDEX] = otherObject; guard[OTHER_INDEX] = false; return sourceType; } else if (nameCompare == 0) { // If both features have the same type them add them addSourceObject(sourceObject); addOtherObject(otherObject); objects[SOURCE_INDEX] = null; guard[SOURCE_INDEX] = true; objects[OTHER_INDEX] = null; guard[OTHER_INDEX] = true; return sourceType; } else { // If the first feature type name is > second feature type // name // then add the second feature and save the first feature // for later addOtherObject(otherObject); objects[SOURCE_INDEX] = sourceObject; guard[SOURCE_INDEX] = false; objects[OTHER_INDEX] = null; guard[OTHER_INDEX] = true; return otherType; } } } } /** * Add an object from the source (in) channel. * * @param object The object to add. */ protected abstract void addSourceObject(Record object); /** * @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; } protected void init(final RecordDefinition recordDefinition) { } protected abstract void processObjects(RecordDefinition currentType, Channel<Record> out); @Override @SuppressWarnings("unchecked") protected void run(final Channel<Record> in, final Channel<Record> out) { setUp(); try { RecordDefinition currentType = null; String currentTypeName = null; final Channel<Record>[] channels = ArrayUtil.newArray(in, this.otherIn); final boolean[] guard = new boolean[] { true, true }; final Record[] objects = new Record[2]; final String[] typePaths = new String[2]; for (int i = 0; i < 2; i++) { try { final Channel<Record> channel = channels[i]; if (channel == null) { guard[i] = false; } else { Record object = null; boolean test = false; do { object = channel.read(); test = testObject(object); } while (!test); if (test) { objects[i] = object; typePaths[i] = objects[i].getRecordDefinition().getPath(); } } } catch (final ClosedException e) { guard[i] = false; } } final Record otherObject = objects[OTHER_INDEX]; if (typePaths[SOURCE_INDEX] != null) { final Record sourceObject = objects[SOURCE_INDEX]; if (typePaths[OTHER_INDEX] != null) { final int nameCompare = typePaths[SOURCE_INDEX].toString() .compareTo(typePaths[OTHER_INDEX].toString()); if (nameCompare <= 0) { currentType = sourceObject.getRecordDefinition(); currentTypeName = typePaths[SOURCE_INDEX]; addSourceObject(sourceObject); objects[SOURCE_INDEX] = null; if (nameCompare != 0) { guard[OTHER_INDEX] = false; } } if (nameCompare >= 0) { currentType = otherObject.getRecordDefinition(); currentTypeName = typePaths[OTHER_INDEX]; addOtherObject(otherObject); objects[OTHER_INDEX] = null; if (nameCompare != 0) { guard[SOURCE_INDEX] = false; } } } else { currentType = sourceObject.getRecordDefinition(); currentTypeName = typePaths[SOURCE_INDEX]; if (otherObject != null) { addSourceObject(otherObject); } } } else { currentType = otherObject.getRecordDefinition(); currentTypeName = typePaths[OTHER_INDEX]; if (otherObject != null) { addOtherObject(otherObject); } objects[OTHER_INDEX] = null; } try { final MultiInputSelector alt = new MultiInputSelector(); final boolean running = true; while (running) { final int channelIndex = alt.select(channels, guard, 1000); if (channelIndex >= 0) { final Record object = channels[channelIndex].read(); if (testObject(object)) { final RecordDefinition recordDefinition = object.getRecordDefinition(); final String typePath = recordDefinition.getPath(); if (currentTypeName == null) { currentTypeName = typePath; currentType = recordDefinition; init(recordDefinition); } if (typePath.equals(currentTypeName)) { currentTypeName = typePath; currentType = recordDefinition; if (channelIndex == SOURCE_INDEX) { addSourceObject(object); } else { addOtherObject(object); } } else { objects[channelIndex] = object; addObjectFromOtherChannel(channels, guard, objects, channelIndex); currentType = addSavedObjects(currentType, currentTypeName, out, guard, objects); if (currentType != null) { currentTypeName = currentType.getPath(); } } } } else { if (channels[0].isClosed()) { guard[1] = true; } else if (channels[1].isClosed()) { guard[0] = true; } } } } finally { try { while (addSavedObjects(currentType, currentTypeName, out, guard, objects) != null) { } processObjects(currentType, out); } finally { } } } finally { this.otherIn.readDisconnect(); tearDown(); } } /** * @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; } protected void setUp() { } protected void tearDown() { } protected boolean testObject(final Record object) { return true; } }