package com.revolsys.record.code;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.google.common.collect.Lists;
import com.revolsys.identifier.Identifier;
import com.revolsys.identifier.ListIdentifier;
import com.revolsys.identifier.SingleIdentifier;
import com.revolsys.io.PathName;
import com.revolsys.io.Reader;
import com.revolsys.record.Record;
import com.revolsys.record.comparator.RecordFieldComparator;
import com.revolsys.record.property.RecordDefinitionProperty;
import com.revolsys.record.query.And;
import com.revolsys.record.query.Q;
import com.revolsys.record.query.Query;
import com.revolsys.record.schema.FieldDefinition;
import com.revolsys.record.schema.RecordDefinition;
import com.revolsys.record.schema.RecordStore;
import com.revolsys.util.Property;
public class CodeTableProperty extends AbstractCodeTable implements RecordDefinitionProperty {
private static final List<String> DEFAULT_FIELD_NAMES = Lists.newArrayList("VALUE");
public static final String PROPERTY_NAME = CodeTableProperty.class.getName();
public static final CodeTableProperty getProperty(final RecordDefinition recordDefinition) {
final CodeTableProperty property = recordDefinition.getProperty(PROPERTY_NAME);
return property;
}
private boolean createMissingCodes = true;
private String creationTimestampFieldName;
private List<String> fieldAliases = new ArrayList<>();
private String idFieldName;
private boolean loadAll = true;
private boolean loaded = false;
private boolean loading = false;
private boolean loadMissingCodes = true;
private String modificationTimestampFieldName;
private List<String> orderBy = DEFAULT_FIELD_NAMES;
private RecordDefinition recordDefinition;
private RecordStore recordStore;
private final ThreadLocal<Boolean> threadLoading = new ThreadLocal<>();
private PathName typePath;
private List<String> valueFieldNames = DEFAULT_FIELD_NAMES;
private FieldDefinition valueFieldDefinition;
private boolean allowNullValues = false;
public CodeTableProperty() {
}
public CodeTableProperty(final Map<String, ? extends Object> config) {
setProperties(config);
}
public void addFieldAlias(final String columnName) {
this.fieldAliases.add(columnName);
}
public void addValue(final Record code) {
final String idFieldName = getIdFieldName();
final Identifier id = code.getIdentifier(idFieldName);
if (id == null) {
throw new NullPointerException(idFieldName + "=null for " + code);
} else {
final List<Object> values = new ArrayList<>();
for (final String fieldName : this.valueFieldNames) {
Object value = code.getValue(fieldName);
if (value instanceof SingleIdentifier) {
final SingleIdentifier identifier = (SingleIdentifier)value;
value = identifier.getValue(0);
}
if (value == null) {
if (!this.allowNullValues) {
throw new NullPointerException(this.valueFieldNames + "=null for " + code);
}
}
values.add(value);
}
addValue(id, values);
}
}
protected void addValues(final Iterable<Record> allCodes) {
for (final Record code : allCodes) {
addValue(code);
}
}
@Override
public CodeTableProperty clone() {
final CodeTableProperty clone = (CodeTableProperty)super.clone();
clone.recordDefinition = null;
clone.fieldAliases = new ArrayList<>(this.fieldAliases);
clone.valueFieldNames = new ArrayList<>(this.valueFieldNames);
return clone;
}
@Override
public Map<Identifier, List<Object>> getCodes() {
refreshIfNeeded();
final Map<Identifier, List<Object>> codes = super.getCodes();
return codes;
}
public String getCreationTimestampFieldName() {
return this.creationTimestampFieldName;
}
@Override
public List<String> getFieldNameAliases() {
return this.fieldAliases;
}
@Override
public String getIdFieldName() {
if (Property.hasValue(this.idFieldName)) {
return this.idFieldName;
} else if (this.recordDefinition == null) {
return "";
} else {
final String idFieldName = this.recordDefinition.getIdFieldName();
if (Property.hasValue(idFieldName)) {
return idFieldName;
} else {
return this.recordDefinition.getFieldName(0);
}
}
}
@Override
public Map<String, ? extends Object> getMap(final Identifier id) {
final List<Object> values = getValues(id);
if (values == null) {
return Collections.emptyMap();
} else {
final Map<String, Object> map = new HashMap<>();
for (int i = 0; i < values.size(); i++) {
final String name = this.valueFieldNames.get(i);
final Object value = values.get(i);
map.put(name, value);
}
return map;
}
}
public String getModificationTimestampFieldName() {
return this.modificationTimestampFieldName;
}
@Override
public String getPropertyName() {
return PROPERTY_NAME;
}
public Record getRecord(final Identifier id) {
return this.recordStore.getRecord(this.typePath, id);
}
@Override
public RecordDefinition getRecordDefinition() {
return this.recordDefinition;
}
public RecordStore getRecordStore() {
return this.recordStore;
}
public String getTypeName() {
return this.typePath.getPath();
}
@Override
public <V> V getValue(final Object id) {
if (id instanceof Identifier) {
return getValue((Identifier)id);
} else if (id instanceof List) {
final List list = (List)id;
return getValue(new ListIdentifier(list));
} else {
return getValue(Identifier.newIdentifier(id));
}
}
@Override
public FieldDefinition getValueFieldDefinition() {
return this.valueFieldDefinition;
}
@Override
public List<String> getValueFieldNames() {
return this.valueFieldNames;
}
public boolean isAllowNullValues() {
return this.allowNullValues;
}
public boolean isCreateMissingCodes() {
return this.createMissingCodes;
}
public boolean isLoadAll() {
return this.loadAll;
}
@Override
public boolean isLoaded() {
return this.loaded;
}
@Override
public boolean isLoading() {
return this.loading;
}
public synchronized void loadAll() {
if (this.threadLoading.get() != Boolean.TRUE) {
if (this.loading) {
while (this.loading) {
try {
wait(1000);
} catch (final InterruptedException e) {
}
}
return;
} else {
this.threadLoading.set(Boolean.TRUE);
this.loading = true;
try {
final RecordDefinition recordDefinition = this.recordStore
.getRecordDefinition(this.typePath);
final Query query = new Query(this.typePath);
query.setFieldNames(recordDefinition.getFieldNames());
for (final String order : this.orderBy) {
query.addOrderBy(order, true);
}
try (
Reader<Record> reader = this.recordStore.getRecords(query)) {
final List<Record> codes = reader.toList();
this.recordStore.getStatistics().getLabelCountMap("query").addCount(this.typePath,
-codes.size());
Collections.sort(codes, new RecordFieldComparator(this.orderBy));
addValues(codes);
}
Property.firePropertyChange(this, "valuesChanged", false, true);
} finally {
this.loading = false;
this.loaded = true;
this.threadLoading.set(null);
this.notifyAll();
}
}
}
}
@Override
protected synchronized Identifier loadId(final List<Object> values, final boolean createId) {
if (this.loadAll && !this.loadMissingCodes && !isEmpty()) {
return null;
}
Identifier id = null;
if (createId && this.loadAll && !isLoaded()) {
loadAll();
id = getIdentifier(values, false);
} else {
final Query query = new Query(this.typePath);
final And and = new And();
if (!values.isEmpty()) {
int i = 0;
for (final String fieldName : this.valueFieldNames) {
final Object value = values.get(i);
if (value == null) {
and.and(Q.isNull(fieldName));
} else {
final FieldDefinition fieldDefinition = this.recordDefinition.getField(fieldName);
and.and(Q.equal(fieldDefinition, value));
}
i++;
}
}
query.setWhereCondition(and);
final Reader<Record> reader = this.recordStore.getRecords(query);
try {
final List<Record> codes = reader.toList();
if (codes.size() > 0) {
this.recordStore.getStatistics().getLabelCountMap("query").addCount(this.typePath,
-codes.size());
addValues(codes);
}
id = getIdByValue(values);
Property.firePropertyChange(this, "valuesChanged", false, true);
} finally {
reader.close();
}
}
if (createId && id == null) {
return newIdentifier(values);
} else {
return id;
}
}
@Override
protected List<Object> loadValues(final Object id) {
if (this.loadAll && !isLoaded()) {
loadAll();
} else {
try {
final Record code;
if (id instanceof Identifier) {
final Identifier identifier = (Identifier)id;
code = this.recordStore.getRecord(this.typePath, identifier);
} else {
code = this.recordStore.getRecord(this.typePath, id);
}
if (code != null) {
addValue(code);
}
} catch (final Throwable e) {
return null;
}
}
return getValueById(id);
}
protected synchronized Identifier newIdentifier(final List<Object> values) {
if (this.createMissingCodes) {
// TODO prevent duplicates from other threads/processes
final Record code = this.recordStore.newRecord(this.typePath);
final RecordDefinition recordDefinition = code.getRecordDefinition();
Identifier id = this.recordStore.newPrimaryIdentifier(this.typePath);
if (id == null) {
final FieldDefinition idField = recordDefinition.getIdField();
if (idField != null) {
if (Number.class.isAssignableFrom(idField.getDataType().getJavaClass())) {
id = Identifier.newIdentifier(getNextId());
} else {
id = Identifier.newIdentifier(UUID.randomUUID().toString());
}
}
}
code.setIdentifier(id);
for (int i = 0; i < this.valueFieldNames.size(); i++) {
final String name = this.valueFieldNames.get(i);
final Object value = values.get(i);
code.setValue(name, value);
}
final Timestamp now = new Timestamp(System.currentTimeMillis());
if (this.creationTimestampFieldName != null) {
code.setValue(this.creationTimestampFieldName, now);
}
if (this.modificationTimestampFieldName != null) {
code.setValue(this.modificationTimestampFieldName, now);
}
this.recordStore.insertRecord(code);
return code.getIdentifier();
} else {
return null;
}
}
@Override
public synchronized void refresh() {
super.refresh();
if (isLoadAll()) {
this.loaded = false;
loadAll();
}
}
public void setAllowNullValues(final boolean allowNullValues) {
this.allowNullValues = allowNullValues;
}
public void setCreateMissingCodes(final boolean createMissingCodes) {
this.createMissingCodes = createMissingCodes;
}
public void setCreationTimestampFieldName(final String creationTimestampFieldName) {
this.creationTimestampFieldName = creationTimestampFieldName;
}
public void setFieldAliases(final List<String> fieldAliases) {
this.fieldAliases = new ArrayList<>(fieldAliases);
}
public void setFieldAliases(final String... fieldAliases) {
setFieldAliases(Lists.newArrayList(fieldAliases));
}
public void setIdFieldName(final String idFieldName) {
this.idFieldName = idFieldName;
}
public void setLoadAll(final boolean loadAll) {
this.loadAll = loadAll;
}
public void setLoadMissingCodes(final boolean loadMissingCodes) {
this.loadMissingCodes = loadMissingCodes;
}
public void setModificationTimestampFieldName(final String modificationTimestampFieldName) {
this.modificationTimestampFieldName = modificationTimestampFieldName;
}
public void setOrderBy(final List<String> orderBy) {
this.orderBy = new ArrayList<>(orderBy);
}
public void setOrderByFieldName(final String orderBy) {
this.orderBy = Lists.newArrayList(orderBy);
}
@Override
public void setRecordDefinition(final RecordDefinition recordDefinition) {
if (this.recordDefinition != recordDefinition) {
if (this.recordDefinition != null) {
this.recordDefinition.setProperty(getPropertyName(), null);
this.valueFieldDefinition = null;
}
this.recordDefinition = recordDefinition;
if (recordDefinition == null) {
this.recordStore = null;
this.typePath = null;
} else {
this.typePath = recordDefinition.getPathName();
final String name = this.typePath.getName();
setName(name);
this.recordStore = this.recordDefinition.getRecordStore();
recordDefinition.setProperty(getPropertyName(), this);
this.recordStore.addCodeTable(this);
if (!this.valueFieldNames.isEmpty()) {
final String fieldName = this.valueFieldNames.get(0);
this.valueFieldDefinition = recordDefinition.getField(fieldName);
}
}
}
}
public void setValueFieldName(final String valueColumns) {
setValueFieldNames(valueColumns);
}
public void setValueFieldNames(final List<String> valueColumns) {
this.valueFieldNames = new ArrayList<>(valueColumns);
if (this.orderBy == DEFAULT_FIELD_NAMES) {
setOrderBy(valueColumns);
}
}
public void setValueFieldNames(final String... valueColumns) {
setValueFieldNames(Arrays.asList(valueColumns));
}
@Override
public String toString() {
return this.typePath + " " + getIdFieldName() + " " + this.valueFieldNames;
}
public String toString(final List<String> values) {
final StringBuilder string = new StringBuilder(values.get(0));
for (int i = 1; i < values.size(); i++) {
final String value = values.get(i);
string.append(",");
string.append(value);
}
return string.toString();
}
}