/** * Copyright (C) 2012-2013 Selventa, Inc. * * This file is part of the OpenBEL Framework. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The OpenBEL Framework is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the OpenBEL Framework. If not, see <http://www.gnu.org/licenses/>. * * Additional Terms under LGPL v3: * * This license does not authorize you and you are prohibited from using the * name, trademarks, service marks, logos or similar indicia of Selventa, Inc., * or, in the discretion of other licensors or authors of the program, the * name, trademarks, service marks, logos or similar indicia of such authors or * licensors, in any marketing or advertising materials relating to your * distribution of the program or any covered product. This restriction does * not waive or limit your obligation to keep intact all copyright notices set * forth in the program as delivered to you. * * If you distribute the program in whole or in part, or any modified version * of the program, and you assume contractual liability to the recipient with * respect to the program or modified version, then you will indemnify the * authors and licensors of the program for any liabilities that these * contractual assumptions directly impose on those licensors and authors. */ package org.openbel.framework.core.indexer; import static jdbm.RecordManagerFactory.createRecordManager; import static org.openbel.framework.common.BELUtilities.noLength; import java.io.IOException; import java.util.Collections; import java.util.Set; import org.openbel.framework.common.InvalidArgument; import jdbm.InverseHashView; import jdbm.PrimaryTreeMap; import jdbm.RecordManager; import jdbm.Serializer; /** * This class is a low-level means of performing direct lookups against JDBM * indices. * <p> * Bad use of this class can cause file descriptor leaks (see {@link #open()}, * {@link #close()}, and {@link #finalize()}. A better option may be to use * service-level constructs. * </p> */ abstract class JDBMLookup<K extends Comparable<K>, V> { private final String indexPath; private final Serializer<V> valueSerializer; PrimaryTreeMap<K, V> tmap; InverseHashView<K, V> invTmap; private final int hash; /** * Constructs a JDBM lookup for the associated index path. * * @param indexPath Index path * @throws InvalidArgument Thrown if {@code indexPath} is null or empty */ JDBMLookup(String indexPath) { this(indexPath, null); } /** * Constructs a JDBM lookup for the associated index path with the provided * value {@link Serializer}. * @param indexPath Index path * @param valueSerializer {@link Serializer} for values. May be <code>null</code>. * @throws InvalidArgument Thrown if {@code indexPath} is null or empty */ JDBMLookup(String indexPath, Serializer<V> valueSerializer) { if (noLength(indexPath)) { throw new InvalidArgument("indexPath", indexPath); } this.indexPath = indexPath; this.valueSerializer = valueSerializer; hash = indexPath.hashCode(); } /** * Returns the index path associated with this JDBM lookup. * * @return String */ public String getIndexPath() { return indexPath; } /** * {@inheritDoc} */ @Override public int hashCode() { return hash; } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof JDBMLookup)) return false; @SuppressWarnings("unchecked") JDBMLookup<K, V> other = (JDBMLookup<K, V>) obj; // indexPath is non-null by contract if (!indexPath.equals(other.indexPath)) return false; return true; } /** * Closes the record manager, if it has been left open. * <p> * <blockquote> ... it is a grave error to depend on a finalizer to close * files, because open file descriptors are a limited resource. If many * files are left open because the JVM is tardy in executing finalizers, a * program may fail because it can no longer open files. </blockquote> * </p> * * @see {@link #close()} */ @Override protected void finalize() throws Throwable { if (tmap == null) { return; } RecordManager rm = tmap.getRecordManager(); if (rm == null) { return; } // We don't want any exceptions propagating back up the stack // and causing problems finalizing this object. try { rm.close(); } catch (IOException ioex) { // Assume it has been closed. } } /** * Opens the index, for subsequent lookups. * * @throws IOException Thrown if an I/O related exception occurs while * creating or opening the record manager */ public synchronized void open() throws IOException { if (tmap != null) { return; } final RecordManager rm = createRecordManager(indexPath); if (valueSerializer == null) { tmap = rm.treeMap(IndexerConstants.INDEX_TREE_KEY); } else { tmap = rm.treeMap(IndexerConstants.INDEX_TREE_KEY, valueSerializer); } if (tmap.isEmpty()) { rm.close(); throw new IOException("tree map is empty"); } invTmap = tmap.inverseHashView(IndexerConstants.INVERSE_KEY); } /** * Returns the number of records. * * @return int */ public int getRecordCount() { return tmap.size(); } /** * Closes the index. * * @throws IOException Thrown when one of the underlying I/O operations fail * @throws IllegalStateException Thrown if {@link #open()} has not been * invoked */ public synchronized void close() throws IOException { if (tmap == null) { throw new IllegalStateException("not open"); } final RecordManager rm = tmap.getRecordManager(); rm.close(); tmap = null; } /** * Performs a lookup for the given {@code key}. * * @param key Key * @return String; may be null for the given key */ public synchronized V lookup(final String key) { if (tmap == null) { throw new IllegalStateException("not open"); } return tmap.get(key); } /** * Performs a reverse lookup for the given {@code value}. * If multiple keys resolve to the same value, this method * will return the first key match as per the JDBM impl. * * @param value * @return * @see InverseHashView#findKeyForValue(Object) */ public synchronized K reverseLookup(final V value) { if (tmap == null) { throw new IllegalStateException("not open"); } return invTmap.findKeyForValue(value); } /** * @return an unmodifiable {@link Set} containing {@link String}s * representing namespace values. */ public synchronized Set<K> getKeySet() { if (tmap == null) { throw new IllegalStateException("not open"); } return Collections.unmodifiableSet(tmap.keySet()); } }