package com.revolsys.record.schema;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import javax.annotation.PreDestroy;
import com.revolsys.collection.Parent;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.io.PathName;
import com.revolsys.logging.Logs;
import com.revolsys.record.io.RecordStoreExtension;
import com.revolsys.util.Property;
public class RecordStoreSchema extends AbstractRecordStoreSchemaElement
implements Parent<RecordStoreSchemaElement> {
private final Map<PathName, RecordStoreSchemaElement> elementsByPath = new TreeMap<>();
private boolean initialized = false;
private final Map<PathName, RecordDefinition> recordDefinitionsByPath = new TreeMap<>();
private AbstractRecordStore recordStore;
private final Map<PathName, RecordStoreSchema> schemasByPath = new TreeMap<>();
private GeometryFactory geometryFactory;
public RecordStoreSchema(final AbstractRecordStore recordStore) {
this.recordStore = recordStore;
}
public RecordStoreSchema(final RecordStoreSchema schema, final PathName pathName) {
super(schema, pathName);
}
public void addElement(final RecordStoreSchemaElement element) {
refreshIfNeeded();
final PathName path = getPathName();
final PathName childPath = element.getPathName();
if (path.isParentOf(childPath)) {
this.elementsByPath.put(childPath, element);
if (element instanceof RecordDefinition) {
final RecordDefinition recordDefinition = (RecordDefinition)element;
this.recordDefinitionsByPath.put(childPath, recordDefinition);
final AbstractRecordStore recordStore = getRecordStore();
recordStore.addRecordDefinitionProperties((RecordDefinitionImpl)recordDefinition);
}
if (element instanceof RecordStoreSchema) {
final RecordStoreSchema schema = (RecordStoreSchema)element;
this.schemasByPath.put(childPath, schema);
}
} else {
throw new IllegalArgumentException(path + " is not a parent of " + childPath);
}
}
@Override
@PreDestroy
public synchronized void close() {
if (this.recordDefinitionsByPath != null) {
for (final RecordDefinition recordDefinition : this.recordDefinitionsByPath.values()) {
recordDefinition.destroy();
}
}
this.recordStore = null;
this.recordDefinitionsByPath.clear();
this.elementsByPath.clear();
this.schemasByPath.clear();
super.close();
}
public synchronized RecordDefinition findRecordDefinition(final PathName path) {
refreshIfNeeded();
final RecordDefinition recordDefinition = this.recordDefinitionsByPath.get(path);
return recordDefinition;
}
@Override
public List<RecordStoreSchemaElement> getChildren() {
return getElements();
}
@SuppressWarnings("unchecked")
public <V extends RecordStoreSchemaElement> V getElement(final PathName path) {
if (isClosed()) {
throw new IllegalStateException("Record store is closed");
} else if (path == null) {
return null;
} else {
RecordStoreSchemaElement childElement = this.elementsByPath.get(path);
if (childElement == null) {
if (path != null) {
final PathName schemaPath = getPathName();
if (schemaPath.equals(path)) {
return (V)this;
} else {
if (schemaPath.isParentOf(path)) {
childElement = this.elementsByPath.get(path);
if (childElement == null) {
synchronized (this) {
refreshIfNeeded();
childElement = this.elementsByPath.get(path);
if (childElement == null || childElement instanceof NonExistingSchemaElement) {
return null;
} else if (childElement.equalPath(path)) {
return (V)childElement;
} else if (childElement instanceof RecordStoreSchema) {
final RecordStoreSchema childSchema = (RecordStoreSchema)childElement;
return childSchema.getElement(path);
} else {
return null;
}
}
} else if (childElement instanceof NonExistingSchemaElement) {
return null;
} else {
return (V)childElement;
}
} else if (schemaPath.isAncestorOf(path)) {
final PathName childPath = schemaPath.getChild(path);
final RecordStoreSchema schema = getSchema(childPath);
if (schema != null) {
return schema.getElement(path);
}
} else {
final RecordStoreSchema parent = getSchema();
if (parent != null) {
return parent.getElement(path);
}
}
}
}
if (this.recordStore == null) {
return null;
} else {
return (V)this.recordStore.getRootSchema();
}
}
return (V)childElement;
}
}
public List<RecordStoreSchemaElement> getElements() {
refreshIfNeeded();
final List<RecordStoreSchemaElement> elements = new ArrayList<>();
elements.addAll(getSchemas());
elements.addAll(getRecordDefinitions());
return elements;
}
@Override
public GeometryFactory getGeometryFactory() {
final GeometryFactory geometryFactory = this.geometryFactory;
if (geometryFactory == null) {
final RecordStore recordStore = getRecordStore();
if (recordStore == null) {
return GeometryFactory.DEFAULT_3D;
} else {
return recordStore.getGeometryFactory();
}
} else {
return geometryFactory;
}
}
@Override
public String getIconName() {
return "folder:table";
}
public RecordDefinition getRecordDefinition(final PathName path) {
final RecordStoreSchemaElement element = getElement(path);
if (element instanceof RecordDefinition) {
return (RecordDefinition)element;
} else {
return null;
}
}
public List<RecordDefinition> getRecordDefinitions() {
refreshIfNeeded();
return new ArrayList<>(this.recordDefinitionsByPath.values());
}
@SuppressWarnings("unchecked")
@Override
public <V extends RecordStore> V getRecordStore() {
final RecordStoreSchema schema = getSchema();
if (schema == null) {
return (V)this.recordStore;
} else {
return schema.getRecordStore();
}
}
public RecordStoreSchema getSchema(final PathName path) {
final RecordStoreSchemaElement element = getElement(path);
if (element instanceof RecordStoreSchema) {
return (RecordStoreSchema)element;
} else {
return null;
}
}
public List<PathName> getSchemaPaths() {
refreshIfNeeded();
return new ArrayList<>(this.schemasByPath.keySet());
}
public List<RecordStoreSchema> getSchemas() {
refreshIfNeeded();
return new ArrayList<>(this.schemasByPath.values());
}
public List<String> getTypeNames() {
refreshIfNeeded();
final List<String> names = new ArrayList<>();
for (final PathName typeName : getTypePaths()) {
names.add(typeName.getParentPath());
}
return names;
}
public List<PathName> getTypePaths() {
refreshIfNeeded();
return new ArrayList<>(this.recordDefinitionsByPath.keySet());
}
@Override
public boolean isClosed() {
final AbstractRecordStore recordStore = getRecordStore();
if (recordStore == null) {
return super.isClosed();
} else {
return recordStore.isClosed();
}
}
private boolean isEqual(final RecordStoreSchemaElement oldElement,
final RecordStoreSchemaElement newElement) {
if (oldElement == newElement) {
return true;
} else if (oldElement instanceof RecordStoreSchema) {
if (newElement instanceof RecordStoreSchema) {
return true;
}
} else if (oldElement instanceof RecordDefinition) {
final RecordDefinition oldRecordDefinition = (RecordDefinition)oldElement;
if (newElement instanceof RecordDefinition) {
final RecordDefinition newRecordDefinition = (RecordDefinition)newElement;
if (Property.equals(newRecordDefinition, oldRecordDefinition, "idFieldNames")) {
if (Property.equals(newRecordDefinition, oldRecordDefinition, "idFieldIndexes")) {
if (Property.equals(newRecordDefinition, oldRecordDefinition, "geometryFieldNames")) {
if (Property.equals(newRecordDefinition, oldRecordDefinition,
"geometryFieldIndexes")) {
if (Property.equals(newRecordDefinition, oldRecordDefinition, "fields")) {
return true;
}
}
}
}
}
}
}
return false;
}
public boolean isInitialized() {
return this.initialized;
}
public RecordStoreSchema newSchema(final PathName path) {
final RecordStoreSchemaElement element = getElement(path);
if (element == null) {
final RecordStoreSchema schema = new RecordStoreSchema(this, path);
addElement(schema);
return schema;
} else if (element instanceof RecordStoreSchema) {
final RecordStoreSchema schema = (RecordStoreSchema)element;
return schema;
} else {
throw new IllegalArgumentException(
"Non schema element with path " + path + " already exists");
}
}
@Override
public synchronized void refresh() {
this.initialized = true;
final AbstractRecordStore recordStore = getRecordStore();
if (recordStore != null) {
final Collection<RecordStoreExtension> extensions = recordStore.getRecordStoreExtensions();
for (final RecordStoreExtension extension : extensions) {
try {
if (extension.isEnabled(recordStore)) {
extension.preProcess(this);
}
} catch (final Throwable e) {
Logs.error(extension.getClass(), "Unable to pre-process schema " + this, e);
}
}
final Map<PathName, ? extends RecordStoreSchemaElement> elementsByPath = recordStore
.refreshSchemaElements(this);
final Set<PathName> removedPaths = new HashSet<>(this.elementsByPath.keySet());
for (final Entry<PathName, ? extends RecordStoreSchemaElement> entry : elementsByPath
.entrySet()) {
final PathName path = entry.getKey();
removedPaths.remove(path);
final RecordStoreSchemaElement newElement = entry.getValue();
final RecordStoreSchemaElement oldElement = this.elementsByPath.get(path);
if (oldElement == null) {
addElement(newElement);
} else {
replaceElement(path, oldElement, newElement);
}
}
for (final PathName removedPath : removedPaths) {
removeElement(removedPath);
}
for (final RecordDefinition recordDefinition : getRecordDefinitions()) {
recordStore.initRecordDefinition(recordDefinition);
}
for (final RecordStoreExtension extension : extensions) {
try {
if (extension.isEnabled(recordStore)) {
extension.postProcess(this);
}
} catch (final Throwable e) {
Logs.error(extension.getClass(), "Unable to post-process schema " + this, e);
}
}
}
}
protected void refreshIfNeeded() {
final RecordStore recordStore = getRecordStore();
if (!isInitialized() && recordStore.isLoadFullSchema()) {
refresh();
}
}
private void removeElement(final PathName pathName) {
this.elementsByPath.remove(pathName);
this.recordDefinitionsByPath.remove(pathName);
this.schemasByPath.remove(pathName);
}
private void replaceElement(final PathName pathName, final RecordStoreSchemaElement oldElement,
final RecordStoreSchemaElement newElement) {
if (!isEqual(oldElement, newElement)) {
removeElement(pathName);
addElement(newElement);
}
}
public void setGeometryFactory(final GeometryFactory geometryFactory) {
this.geometryFactory = geometryFactory;
}
}