001    /*
002     * Copyright (c) 1998-2014 ChemAxon Ltd. All Rights Reserved.
003     *
004     * This software is the confidential and proprietary information of
005     * ChemAxon. You shall not disclose such Confidential Information
006     * and shall use it only in accordance with the terms of the agreements
007     * you entered into with ChemAxon.
008     *
009     */
010    package com.chemaxon.overlap.io;
011    
012    import chemaxon.struc.Molecule;
013    import com.google.common.base.Optional;
014    import java.io.Serializable;
015    import java.util.ArrayList;
016    import java.util.Arrays;
017    import java.util.List;
018    
019    /**
020     * Utilities for ID projection.
021     *
022     * @author Gabor Imre
023     */
024    public final class IdProjectors {
025    
026        /**
027         * No constructor exposed.
028         */
029        private IdProjectors() {
030        }
031    
032        /**
033         * Plain ID projector builder.
034         *
035         * <p>
036         * Note that sequential reporting of IDs are expected.</p>
037         */
038        public static class IdProjectorBuilder {
039            /**
040             * List of missing master IDs missing from client side.
041             */
042            private final List<Integer> skippedMasterIds;
043    
044            /**
045             * Next expected readno.
046             */
047            private int nextReadno;
048    
049            /**
050             * Next expected index.
051             */
052            private int nextIndex;
053    
054            /**
055             * Initially skipped master count.
056             */
057            private final int initialMasterSkips;
058    
059            /**
060             * Construct new builder.
061             */
062            public IdProjectorBuilder() {
063                this(0);
064            }
065    
066            /**
067             * Construct new builder.
068             *
069             * @param skip Skipped in master IDs
070             */
071            public IdProjectorBuilder(int skip) {
072                if (skip < 0) {
073                    throw new IllegalArgumentException("Invalid skip " + skip);
074                }
075                this.skippedMasterIds = new ArrayList<Integer>();
076                this.nextIndex = 0;
077                this.nextReadno = skip;
078                this.initialMasterSkips = skip;
079            }
080    
081            /**
082             * Ensure that the expected master id reported.
083             *
084             * @param readno Reported master id.
085             */
086            private void checkReadno(int readno) {
087                if (this.nextReadno != readno) {
088                    throw new IllegalArgumentException("Expected readno: " + this.nextReadno + ", got: " + readno);
089                }
090                this.nextReadno++;
091            }
092    
093            /**
094             * Ensure that the expected client id reported.
095             *
096             * @param index Reported master id.
097             */
098            private void checkIndex(int index) {
099                if (this.nextIndex != index) {
100                    throw new IllegalArgumentException("Expected index: " + this.nextIndex + ", got: " + index);
101                }
102                this.nextIndex++;
103            }
104    
105            /**
106             * Report new master-client mapping.
107             *
108             * @param masterId Master ID
109             * @param clientId Client ID
110             */
111            public void addOk(int masterId, int clientId) {
112                checkReadno(masterId);
113                checkIndex(clientId);
114            }
115    
116            /**
117             * Report master ID for which no client ID.
118             *
119             * @param masterId Master ID skipped
120             */
121            public void addError(int masterId) {
122                checkReadno(masterId);
123                this.skippedMasterIds.add(masterId);
124            }
125    
126            /**
127             * Build serializable, immutable projector.
128             *
129             * @return ID projector to build.
130             */
131            public IdProjector build() {
132                return new SkipListIdProjector(
133                        this.initialMasterSkips, this.nextReadno - 1, this.nextIndex - 1, this.skippedMasterIds);
134            }
135    
136        }
137    
138        /**
139         * Projector builder over molecule callbacks.
140         */
141        public static class IdProjectorMCB implements MoleculeCallback {
142    
143            /**
144             * Underlying builder.
145             */
146            private final IdProjectorBuilder builder;
147    
148            /**
149             * Callback to forward or null;
150             */
151            private final MoleculeCallback fwd;
152    
153    
154            /**
155             * Instantiate without forwarding.
156             */
157            public IdProjectorMCB() {
158                this(0, null);
159            }
160    
161            /**
162             * Instantiate with a forwarding callback.
163             *
164             * @param fwd Callback to call; use null when no forwarding required
165             */
166            public IdProjectorMCB(MoleculeCallback fwd) {
167                this(0, fwd);
168            }
169    
170            /**
171             * Instantiate with a forwarding callback.
172             *
173             * @param skip Skipped in master IDs
174             * @param fwd Callback to call; use null when no forwarding required
175             */
176            public IdProjectorMCB(int skip, MoleculeCallback fwd) {
177                this.fwd = fwd;
178                this.builder = new IdProjectorBuilder(skip);
179            }
180    
181    
182            @Override
183            public void notifyMolecule(int readno, Optional<String> molString, Molecule m, int index) {
184                this.builder.addOk(readno, index);
185                if (this.fwd != null) {
186                    this.fwd.notifyMolecule(readno, molString, m, index);
187                }
188            }
189    
190            @Override
191            public void notifyProcessingError(int readno, Optional<String> molString, Molecule m, Throwable t) {
192                this.builder.addError(readno);
193                if (this.fwd != null) {
194                    this.fwd.notifyProcessingError(readno, molString, m, t);
195                }
196            }
197    
198            @Override
199            public void notifyParseError(int readno, Optional<String> molString, Throwable t) {
200                this.builder.addError(readno);
201                if (this.fwd != null) {
202                    this.fwd.notifyParseError(readno, molString, t);
203                }
204            }
205    
206            /**
207             * Build serializable, immutable projector.
208             *
209             * @return ID projector to build.
210             */
211            public IdProjector build() {
212                return this.builder.build();
213            }
214        }
215    
216        /**
217         * Projector based on skipped master IDs.
218         */
219        private static class SkipListIdProjector implements IdProjector, Serializable {
220            /**
221             * Serial version.
222             */
223            private static final long serialVersionUID = 0;
224    
225            /**
226             * Max master ID.
227             */
228            private final int maxMasterId;
229    
230            /**
231             * Max client ID.
232             */
233            private final int maxClientId;
234    
235            /**
236             * Number of master IDs skipped. Equivalent to having IDs 0 .. (initialMasterSkip-1) in the skiplist.
237             */
238            private final int intialMasterSkips;
239    
240            /**
241             * Master ID skiplist.
242             */
243            private final int[] masterIdsSkipped;
244    
245            /**
246             * Construct immutable representation.
247             *
248             * @param initialMasterSkips Number of masters skipped at the begining
249             * @param maxMasterID Max master ID present
250             * @param maxClientID Max client ID present
251             * @param skippedMasterIds Skiplist
252             */
253            SkipListIdProjector(int initialMasterSkips, int maxMasterID, int maxClientID, List<Integer> skippedMasterIds) {
254                if (initialMasterSkips < 0) {
255                    throw new IllegalArgumentException("Invalid initial master skips " + initialMasterSkips);
256    
257                }
258                if (initialMasterSkips > maxMasterID) {
259                    throw new IllegalArgumentException("Skips " + initialMasterSkips + " and max id " + maxMasterID
260                            + " conflict");
261                }
262                this.intialMasterSkips = initialMasterSkips;
263                this.maxClientId = maxClientID;
264                this.maxMasterId = maxMasterID;
265                if (!skippedMasterIds.isEmpty()) {
266                    if (skippedMasterIds.get(0) < initialMasterSkips) {
267                        // Skip count defined interval intersects with skiplist
268                        throw new IllegalArgumentException("Master skips " + initialMasterSkips
269                                + " and first skiplist item " + skippedMasterIds.get(0) + " conflicts");
270                    }
271                }
272                this.masterIdsSkipped = new int[skippedMasterIds.size()];
273                for (int i = 0; i < this.masterIdsSkipped.length; i++) {
274                    this.masterIdsSkipped[i] = skippedMasterIds.get(i);
275                    if (this.masterIdsSkipped[i] < 0 || this.masterIdsSkipped[i] > this.maxMasterId) {
276                        throw new IllegalArgumentException("Invalid ID " + this.masterIdsSkipped[i]
277                                + " in skiplist: " + skippedMasterIds.toString());
278                    }
279                    if (i > 0 && this.masterIdsSkipped[i - 1] >= this.masterIdsSkipped[i]) {
280                        throw new IllegalArgumentException("Invalid ordering in skiplist: " + skippedMasterIds.toString());
281                    }
282                }
283            }
284    
285            @Override
286            public int getMasterId(int clientId) {
287                if (clientId < 0 || clientId > this.maxClientId) {
288                    throw new IllegalArgumentException("Invalid client ID: " + clientId);
289                }
290                if (this.masterIdsSkipped.length == 0) {
291                    return clientId + this.intialMasterSkips;
292                }
293    
294                // Number of skipped master IDs up to the point reachnig given this client id
295                int skippeds = 0;
296                for (int sid : this.masterIdsSkipped) {
297                    if (sid - skippeds - this.intialMasterSkips <= clientId) {
298                        // The skipped master ID was skipped before reaching the given client ID
299                        skippeds++;
300                    } else {
301                        // skipped ID is greater
302                        break;
303                    }
304                }
305    
306                return clientId + skippeds + this.intialMasterSkips;
307            }
308    
309            @Override
310            public Optional<Integer> getClientId(int masterId) {
311                if (masterId < 0 || masterId > this.maxMasterId) {
312                    throw new IllegalArgumentException("Invalid master ID: " + masterId);
313                }
314                if (masterId < this.intialMasterSkips) {
315                    // in the initial skipped interval
316                    return Optional.<Integer>absent();
317                }
318                if (this.masterIdsSkipped.length == 0) {
319                    return Optional.of(masterId - this.intialMasterSkips);
320                }
321    
322                int skippeds = 0;
323                for (int sid : this.masterIdsSkipped) {
324                    if (sid == masterId) {
325                        // missing
326                        return Optional.<Integer>absent();
327                    } else if (sid < masterId) {
328                        // this id was skipped before reaching master ID
329                        skippeds++;
330                    } else {
331                        // skipped ID is greater
332                        break;
333                    }
334                }
335                return Optional.of(masterId - skippeds - this.intialMasterSkips);
336    
337            }
338    
339            @Override
340            public String toString() {
341                return "Skiplist ID projector initialMasterSkips: " + this.intialMasterSkips + " maxClientID: "
342                        + this.maxClientId + " maxMasterID: " + this.maxMasterId
343                        + " master ID skiplist: " + Arrays.toString(this.masterIdsSkipped);
344    
345            }
346        }
347    }