/* * #! * Ontopia DB2TM * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * Licensed 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 net.ontopia.topicmaps.db2tm; import net.ontopia.utils.StringUtils; import net.ontopia.utils.ObjectUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * INTERNAL: This tuple reader wraps an underlying tuple reader, and * collapses a sequence of actions for the same key into a single * final action. Tuples are coming through ordered by key first, then * by sequence. */ public class ChangelogReaderWrapper implements ChangelogReaderIF { private final ChangelogReaderIF source; private int[] keycols; // contains index in relation of each key column // used for tracking next tuple private String[] tuple; // used for tracking previous tuple private String prevorder; // ? private ChangeType prevchange; private String[] prevtuple; static Logger log = LoggerFactory.getLogger(ChangelogReaderWrapper.class); public ChangelogReaderWrapper(ChangelogReaderIF source, Relation relation) { this.source = source; String[] pkey = relation.getPrimaryKey(); this.keycols = new int[pkey.length]; for (int ix = 0; ix < pkey.length; ix++) keycols[ix] = relation.getColumnIndex(pkey[ix]); if (pkey.length == 0) // this will cause the reader to ignore rows, because they will // look the same (equalsKey will proclaim all rows equal since // none of the values in their zero-length keys differ) throw new DB2TMException("Must specify primary key on '" + relation.getName() + "'"); } @Override public ChangeType getChangeType() { return prevchange; } @Override public String getOrderValue() { return prevorder; } @Override public String[] readNext() { // INVARIANT: // (a) prevtuple is null, tuple is null, ready to start on first row // (b) prevtuple holds previous passed-on row, tuple holds next // (c) prevtuple holds previous passed-on row, tuple is null; that is, // we've reached the end of the stream // it could be that we are finished (c), in which case, return null if (prevtuple != null && tuple == null) return null; // it could be that we haven't started yet (a), in which case, // kickstart things if (prevtuple == null && tuple == null) tuple = source.readNext(); // now read new tuples until we find one belonging to a new key while (true) { if (log.isTraceEnabled()) log.trace("State: {} Tuple: ({})", prevchange, (tuple == null ? "null" : StringUtils.join(tuple, "|"))); // move one row forwards prevtuple = tuple; if (tuple != null) { prevchange = source.getChangeType(); prevorder = source.getOrderValue(); } tuple = source.readNext(); // did we just move onto a new key? if (!equalsKey(prevtuple, tuple) || (tuple == null && prevtuple == null)) break; } // notice how we are now back to INVARIANT as stated above return prevtuple; } @Override public void close() { source.close(); } // INTERNAL HELPERS // this code is based on the assumption that the primary key is // always the first n values in the tuple, where n is the length // of the primary key. for now, the code does produce such tuples. private boolean equalsKey(String[] tuple1, String[] tuple2) { if ((tuple1 == null && tuple2 != null) || (tuple1 != null && tuple2 == null)) return false; if (tuple1 == null && tuple2 == null) return true; for (int ix = 0; ix < keycols.length; ix++) if (ObjectUtils.different(tuple1[keycols[ix]], tuple2[keycols[ix]])) return false; return true; } }