package com.eas.client.metadata;
import com.eas.core.Utils;
import com.google.gwt.core.client.JavaScriptObject;
import java.util.*;
/**
* This class is intended to hold rowset's fields information. Supports clone,
* copy operations. Supports factory method for new fields creation. Supports
* field search, operations with primary and foreign keys.
*/
public class Fields {
public static class OrmDef {
private final String baseName;// if not null, -> property is a scalar orm expanding
private final String name;
private final String oppositeName;
private final JavaScriptObject jsDef;
public OrmDef(String aName, String aOppositeName, JavaScriptObject aJsDef) {
this(null, aName, aOppositeName, aJsDef);
}
public OrmDef(String aBaseName, String aName, String aOppositeName, JavaScriptObject aDef) {
baseName = aBaseName;
name = aName;
oppositeName = aOppositeName;
jsDef = aDef;
}
public String getBaseName() {
return baseName;
}
public String getOppositeName() {
return oppositeName;
}
public String getName() {
return name;
}
public JavaScriptObject getJsDef() {
return jsDef;
}
}
private static final String DEFAULT_PARAM_NAME_PREFIX = "Field";
protected String tableDescription;
protected List<Field> fields = new ArrayList<>();
// Map of field name to it's index (0-based)
protected Map<String, Integer> fieldsHash;
//
protected JavaScriptObject instanceConstructor;
protected Map<String, OrmDef> ormScalarDefinitions = new HashMap<>();
protected Map<String, OrmDef> ormCollectionsDefinitions = new HashMap<>();
protected Map<String, Set<OrmDef>> ormScalarExpandings = new HashMap<>();
/**
* The default constructor.
*/
public Fields() {
super();
}
/**
* The copy constructor.
*
* @param aSource
* Instance of <code>Fields</code> class to be used as the
* information source.
*/
public Fields(Fields aSource) {
this();
if (aSource != null) {
for (int i = 0; i < aSource.getFieldsCount(); i++) {
if (aSource.get(i + 1) != null) {
Field copied = aSource.get(i + 1).copy();
fields.add(i, copied);
} else {
fields.add(i, null);
}
}
assert fields.size() == aSource.getFieldsCount();
String sDesc = aSource.getTableDescription();
if (sDesc != null) {
setTableDescription(new String(sDesc.toCharArray()));
}
}
}
public JavaScriptObject getInstanceConstructor() {
return instanceConstructor;
}
public void setInstanceConstructor(JavaScriptObject aValue) {
instanceConstructor = aValue;
}
public void putOrmScalarDefinition(String aName, OrmDef aDefinition) {
if (aName != null && !aName.isEmpty() && aDefinition != null) {
if (!ormScalarDefinitions.containsKey(aName)) {
ormScalarDefinitions.put(aName, aDefinition);
Set<OrmDef> expandings = ormScalarExpandings.get(aDefinition.getBaseName());
if(expandings == null){
expandings = new HashSet<>();
ormScalarExpandings.put(aDefinition.getBaseName(), expandings);
}
expandings.add(aDefinition);
}
}
}
public Map<String, OrmDef> getOrmScalarDefinitions() {
return Collections.unmodifiableMap(ormScalarDefinitions);
}
public Map<String, Set<OrmDef>> getOrmScalarExpandings() {
return Collections.unmodifiableMap(ormScalarExpandings);
}
public void forEachOrmScalarExpandings(String anExpandingsName, Utils.JsObject aProcesssor){
Set<OrmDef> expandings = ormScalarExpandings.get(anExpandingsName);
if(expandings != null){
for(OrmDef def : expandings){
aProcesssor.call(null, def);
}
}
}
public void putOrmCollectionDefinition(String aName, OrmDef aDefinition) {
if (aName != null && !aName.isEmpty() && aDefinition != null) {
if (!ormCollectionsDefinitions.containsKey(aName)) {
ormCollectionsDefinitions.put(aName, aDefinition);
}
}
}
public Map<String, OrmDef> getOrmCollectionsDefinitions() {
return Collections.unmodifiableMap(ormCollectionsDefinitions);
}
/**
* Rebuild hashmap with fields by name mapping.
*/
protected void validateFieldsHash() {
if (fieldsHash == null) {
fieldsHash = new HashMap<>();
for (int i = 0; i < fields.size(); i++) {
String fieldName = fields.get(i).getName();
if (fieldName != null) {
fieldsHash.put(fieldName.toLowerCase(), i);
}
}
}
}
/**
* Invlidates inner fields hash. Fields are hashed by field name. It's not
* recomended to call it directly. It's intended for internal use.
*/
public void invalidateFieldsHash() {
fieldsHash = null;
}
/**
* Returns if the <code>Fields</code> contains no fields.
*
* @return <code>True</code> if this instance contains no fields.
*/
public boolean isEmpty() {
return fields.isEmpty();
}
/**
* Returns default name prefix for generating new field names.
*
* @return New (unique) name for this instance of <code>Fields</code>.
* @see #generateNewName()
* @see #generateNewName(String)
*/
public String getDefaultNamePrefix() {
return DEFAULT_PARAM_NAME_PREFIX;
}
/**
* Returns table description if this <code>Fields</code> instance represents
* database table fields set.
*
* @return Table description.
*/
public String getTableDescription() {
return tableDescription;
}
/**
* Sets the table description if this <code>Fields</code> instance
* represents database table fields set.
*
* @param aValue
* Table description value.
*/
public void setTableDescription(String aValue) {
tableDescription = aValue;
}
/**
* Generates and returns new(unique) field name for this fields set.
*
* @return New(unique) field name for this fields set.
* @see #getDefaultNamePrefix()
* @see #generateNewName(String)
* @see #isNameAlreadyPresent(String aName, Field aField2Skip)
*/
public String generateNewName() {
return generateNewName(null);
}
/**
* Generates and returns new(unique) field name for this fields set. New
* name will start with <code>aPrefix</code> value.
*
* @param aPrefix
* A string value new name will start with.
* @return New(unique) field name for this fields set.
* @see #getDefaultNamePrefix()
* @see #generateNewName()
* @see #isNameAlreadyPresent(String aName, Field aField2Skip)
*/
public String generateNewName(String aPrefix) {
if (aPrefix == null || aPrefix.isEmpty()) {
aPrefix = getDefaultNamePrefix();
}
int paramNumber = 0;
for (int i = 0; i < getFieldsCount(); i++) {
Field param = fields.get(i);
String paramName = param.getName();
if (paramName != null && paramName.toLowerCase().startsWith(aPrefix.toLowerCase())) {
int existingNumber = 1;
String maybeNumber = paramName.substring(aPrefix.length());
if (maybeNumber != null && !maybeNumber.isEmpty()) {
try {
existingNumber = Integer.valueOf(maybeNumber);
if (existingNumber > paramNumber) {
paramNumber = existingNumber;
}
} catch (NumberFormatException fe) {
// no op
}
}
}
}
return aPrefix + String.valueOf(paramNumber + 1);
}
/**
* Tests whether the name already present in this fields set skipping
* particular field instance.
*
* @param aName
* Name to test.
* @param aField2Skip
* <code>Field</code> to skip.
* @return <code>True</code> if the aspecified name already present.
* @see #getDefaultNamePrefix()
* @see #generateNewName()
* @see #generateNewName(String)
*/
public boolean isNameAlreadyPresent(String aName, Field aField2Skip) {
if (aName == null) {
aName = "";
}
for (int i = 0; i < getFieldsCount(); i++) {
Field field = fields.get(i);
if (field == aField2Skip) {
continue;
}
String paramName = field.getName();
if (paramName == null) {
paramName = "";
}
if (aName.toLowerCase().equals(paramName.toLowerCase())) {
return true;
}
}
return false;
}
/**
* Creates new field
*
* @return New <code>Field</code> instance.
*/
public Field createNewField() {
return createNewField(null);
}
/**
* Creates new field with the specified name.
*
* @return New <code>Field</code> instance.
* @see #createNewField()
* @see #getDefaultNamePrefix()
* @see #generateNewName()
* @see #generateNewName(String)
* @see #isNameAlreadyPresent(String aName, Field aField2Skip)
*/
public Field createNewField(String aName) {
if (aName == null || aName.isEmpty()) {
aName = generateNewName();
}
Field lfield = new Field(aName, null);
lfield.setType("String");
return lfield;
}
/**
* Returns fields count, contained in this fields set.
*
* @return Fields count, contained in this fields set.
*/
public int getFieldsCount() {
if (fields != null) {
return fields.size();
}
return 0;
}
/**
* Clears all fields from this set.
*/
public void clear() {
if (fields != null) {
fields.clear();
invalidateFieldsHash();
}
}
/**
* Returns fields, representing primary keys.
*
* @return A vector comprised of <code>Field</code> instances.
*/
public List<Field> getPrimaryKeys() {
List<Field> pks = new ArrayList<>();
for (int i = 0; i < getFieldsCount(); i++) {
Field lfield = fields.get(i);
if (lfield != null && lfield.isPk()) {
pks.add(lfield);
}
}
return pks;
}
/**
* Returns fields, representing foreign keys.
*
* @return A vector comprised of <code>Field</code> instances.
*/
public List<Field> getForeinKeys() {
List<Field> fks = new ArrayList<>();
for (int i = 0; i < getFieldsCount(); i++) {
Field lfield = fields.get(i);
if (lfield != null && lfield.isFk()) {
fks.add(lfield);
}
}
return fks;
}
/**
* Returns indicies of primary-key fields. Values of indicies are 1-based;
*
* @return A vector comprised of <code>Field</code> indicies.
*/
public List<Integer> getPrimaryKeysIndicies() {
List<Integer> pksIndicies = new ArrayList<>();
for (int i = 0; i < getFieldsCount(); i++) {
Field lfield = fields.get(i);
if (lfield != null && lfield.isPk()) {
pksIndicies.add(i + 1);
}
}
return pksIndicies;
}
/**
* Adds particular <code>Field</code> instance to this fields set.
*
* @param aField
* A <code>Field</code> to add.
* @return <code>True</code> if adding have succeded.
*/
public boolean add(Field aField) {
if (aField != null && fields != null) {
boolean res = fields.add(aField);
invalidateFieldsHash();
return res;
}
return false;
}
/**
* Adds particular <code>Field</code> instance to this fields set at the
* specified position.
*
* @param index
* An index at which aField is to be added.
* @param aField
* A <code>Field</code> to add.
*/
public void add(int index, Field aField) {
if (aField != null && fields != null) {
fields.add(index - 1, aField);
invalidateFieldsHash();
}
}
/**
* Removes a particular <code>Field</code> instance from this fields set.
*
* @param aField
* <code>Field</code> instance to remove.
* @return <code>True</code> if removing succeeded.
*/
public boolean remove(Field aField) {
int oldSize = fields.size();
fields.remove(aField);
invalidateFieldsHash();
return oldSize > fields.size();
}
/**
* Returns <code>Field</code> instance at the specified index. Index is 1
* based.
*
* @param index
* Index of <code>Field</code> instance you are interested in.
* @return <code>Field</code> instance at the specified index. If wrong
* index specified, than null is returned.
*/
public Field get(int index) {
if (fields != null && index > 0 && index <= fields.size()) {
return fields.get(index - 1);
}
return null;
}
/**
* Returns <code>Field</code> instance with the specified name.
*
* @param aFieldName
* Field name of <code>Field</code> instance you are interested
* in.
* @return <code>Field</code> instance with the specified name. If absent
* name is specified, than null is returned.
*/
public Field get(String aFieldName) {
if (fields != null && aFieldName != null) {
return get(find(aFieldName));
}
return null;
}
/**
* Returns ordinal position of <code>Field</code> instance with the
* specified name.
*
* @param aFieldName
* Field name of <code>Field</code> instance you are interested
* in.
* @return 1 based ordinal position of <code>Field</code> instance with the
* specified name. If absent name is specified, than 0 is returned.
*/
public int find(String aFieldName) {
if (fields != null && aFieldName != null) {
validateFieldsHash();
assert fieldsHash != null;
Integer fIndex = fieldsHash.get(aFieldName.toLowerCase());
if (fIndex != null && fIndex >= 0 && fIndex < fields.size()) {
return fIndex + 1;
}
}
return 0;
}
/**
* Test if this <code>Fields</code> object contains a field with the
* <code>aFieldName</code> name.
*
* @param aFieldName
* Name to test.
* @return True if fields contains a field with such name.
*/
public boolean contains(String aFieldName) {
return find(aFieldName) > 0;
}
/**
* Copies this <code>Fields</code> instance, creating a new one.
*
* @return New instance of <code>Fields</code>.
* @see #clone()
*/
public Fields copy() {
return new Fields(this);
}
/**
* Returns the fields vector as an abstract collection.
*
* @return Fields vector as an abstract collection.
*/
public List<Field> toCollection() {
return fields;
}
protected JavaScriptObject jsPublished;
public void setPublished(JavaScriptObject aPublished) {
jsPublished = aPublished;
}
public JavaScriptObject getPublished() {
return jsPublished;
}
public static native JavaScriptObject publishFacade(Fields aFields) throws Exception/*-{
if(aFields != null){
var published = aFields.@com.eas.client.metadata.Fields::getPublished()();
if(published == null){
published = {
unwrap : function(){
return aFields;
}
};
Object.defineProperty(published, "empty", { get : function(){ return aFields.@com.eas.client.metadata.Fields::isEmpty()(); }});
Object.defineProperty(published, "tableDescription", { get : function(){ return aFields.@com.eas.client.metadata.Fields::getTableDescription()()}});
var fieldsCount = aFields.@com.eas.client.metadata.Fields::getFieldsCount()();
var lengthMet = false;
for(var i = 0; i < fieldsCount; i++){
(function(){
var nField = aFields.@com.eas.client.metadata.Fields::get(I)(i + 1);
var nFieldName = nField.@com.eas.client.metadata.Field::getName()();
if('length' == nFieldName){
lengthMet = true;
}
var pField = @com.eas.client.metadata.Field::publishFacade(Lcom/eas/client/metadata/Field;)(nField);
if(!published[nFieldName])
Object.defineProperty(published, nFieldName, { get : function(){ return pField; }});
Object.defineProperty(published, i+"", { get : function(){ return pField; }});
})();
}
if(!lengthMet){
Object.defineProperty(published, 'length', {value : fieldsCount});
}
aFields.@com.eas.client.metadata.Fields::setPublished(Lcom/google/gwt/core/client/JavaScriptObject;)(published);
}
return published;
}else
return null;
}-*/;
}