All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aoindustries.aoserv.client.CachedTable Maven / Gradle / Ivy

There is a newer version: 1.92.0
Show newest version
/*
 * aoserv-client - Java client for the AOServ platform.
 * Copyright (C) 2001-2009, 2016  AO Industries, Inc.
 *     [email protected]
 *     7262 Bull Pen Cir
 *     Mobile, AL 36695
 *
 * This file is part of aoserv-client.
 *
 * aoserv-client 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.
 *
 * aoserv-client 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 aoserv-client.  If not, see .
 */
package com.aoindustries.aoserv.client;

import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * A CachedTable stores all of the
 * available CachedObjects and performs
 * all subsequent data access locally.  The server
 * notifies the client when a table is updated, and
 * the caches are then invalidated.  Once invalidated,
 * the data is reloaded upon next use.
 *
 * @author  AO Industries, Inc.
 */
public abstract class CachedTable> extends AOServTable {

	/**
	 * The last time that the data was loaded, or
	 * -1 if not yet loaded.
	 */
	private long lastLoaded=-1;

	/**
	 * The internal objects are stored in HashMaps
	 * based on unique columns.
	 */
	private List> columnHashes;
	private BitSet columnsHashed;

	/**
	 * The internal objects are stored in HashMaps of CachedObject[]
	 * based on indexed columns.  Each of the contained List are unmodifiable.
	 */
	private List>> indexHashes;
	private BitSet indexesHashed;

	/**
	 * The internal objects are stored in an unmodifiable list
	 * for access to the entire table.
	 */
	private List tableData;

	protected CachedTable(AOServConnector connector, Class clazz) {
		super(connector, clazz);
	}

	@Override
	public List getIndexedRows(int col, Object value) throws IOException, SQLException {
		synchronized(this) {
			validateCache();
			int minLength=col+1;
			if(indexHashes==null) {
				indexHashes=new ArrayList<>(minLength);
				indexesHashed=new BitSet(minLength);
			}
			while(indexHashes.size()> map=indexHashes.get(col);
			if(map==null) indexHashes.set(col, map=new HashMap<>());
			if(!indexesHashed.get(col)) {
				// Build the modifiable lists in a temporary Map
				Map> modifiableIndexes=new HashMap<>();
				for(V obj : tableData) {
					Object cvalue=obj.getColumn(col);
					List list=modifiableIndexes.get(cvalue);
					if(list==null) modifiableIndexes.put(cvalue, list=new ArrayList<>());
					list.add(obj);
				}
				// Wrap each of the newly-created indexes to be unmodifiable
				map.clear();
				Iterator keys=modifiableIndexes.keySet().iterator();
				while(keys.hasNext()) {
					Object key=keys.next();
					List list=modifiableIndexes.get(key);
					map.put(key, Collections.unmodifiableList(list));
				}
				indexesHashed.set(col);
			}
			// Conversion to array is delayed so that indexed but unused parts save the step.
			List list = map.get(value);
			if(list==null) return Collections.emptyList();
			return list;
		}
	}

	@Override
	final protected V getUniqueRowImpl(int col, Object value) throws IOException, SQLException {
		synchronized(this) {
			validateCache();
			int minLength=col+1;
			if(columnHashes==null) {
				columnHashes=new ArrayList<>(minLength);
				columnsHashed=new BitSet(minLength);
			}
			while(columnHashes.size() map=columnHashes.get(col);
			if(!columnsHashed.get(col)) {
				List table=tableData;
				int size=table.size();
				// HashMap default load factor is .75, so we go slightly more than 1/.75, or slightly more than 4/3.  13/9 is a little more than 4/3.
				// This ensures the HashMap will not be restructured while loading the data.
				if(map==null) columnHashes.set(col, map=new HashMap<>(size*13/9));
				else map.clear();
				for(int c=0;c getRows() throws IOException, SQLException {
		synchronized(this) {
			validateCache();
			return tableData;
		}
	}

	/**
	 * Determines if the contents are currently hashed in a hashmap.
	 */
	boolean isHashed(int uniqueColumn) {
		return
			columnsHashed!=null
			&& columnsHashed.get(uniqueColumn)
		;
	}

	/**
	 * Determines if the contents of this column are indexed.
	 */
	boolean isIndexed(int uniqueColumn) {
		return
			indexesHashed!=null
			&& indexesHashed.get(uniqueColumn)
		;
	}

	@Override
	final public boolean isLoaded() {
		return lastLoaded!=-1;
	}

	/**
	 * Clears the cache, freeing up memory.  The data will be reloaded upon
	 * next use.
	 */
	@Override
	public void clearCache() {
		super.clearCache();
		synchronized(this) {
			lastLoaded=-1;
			if(columnHashes!=null) {
				int len=columnHashes.size();
				for(int c=0;c> map=indexHashes.get(c);
					if(map!=null) map.clear();
				}
			}
			if(indexesHashed!=null) indexesHashed.clear();
		}
	}

	/**
	 * Reloads the cache if the cache time has expired.  All accesses are already synchronized.
	 */
	private void validateCache() throws IOException, SQLException {
		long currentTime=System.currentTimeMillis();
		if(
		   // If cache never loaded
		   lastLoaded==-1
		   // If the system time was reset to previous time
		   || currentTime> map=indexHashes.get(c);
					if(map!=null) map.clear();
				}
			}
			if(indexesHashed!=null) indexesHashed.clear();
		}
	}
}