/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.scripting;
import java.util.HashSet;
import java.util.Set;
import org.mozilla.javascript.Scriptable;
import com.servoy.j2db.FlattenedSolution;
import com.servoy.j2db.dataprocessing.IFoundSetInternal;
import com.servoy.j2db.dataprocessing.IRecordInternal;
import com.servoy.j2db.dataprocessing.PrototypeState;
import com.servoy.j2db.dataprocessing.RelatedFoundSet;
import com.servoy.j2db.persistence.IDataProvider;
import com.servoy.j2db.persistence.LiteralDataprovider;
import com.servoy.j2db.persistence.Relation;
import com.servoy.j2db.persistence.RepositoryException;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.ScopesUtils;
/**
* Keep track of used data when calculating a calculation value.
*
* @author rgansevles
*
*/
public class UsedDataProviderTracker
{
private final FlattenedSolution flattenedSolution;
Set<String> usedGlobals = null;
Set<UsedDataProvider> usedColumns = null;
Set<UsedRelation> usedRelations = null;
Set<UsedAggregate> usedAggregates = null;
public UsedDataProviderTracker(FlattenedSolution flattenedSolution)
{
this.flattenedSolution = flattenedSolution;
}
public void usedRelatedFoundSet(String name, RelatedFoundSet foundSet)
{
// add a dependency for relation size changes, rowManager of current record will listen to changes to the related foundset identified by whereArgsHash.
String whereArgsHash = foundSet.getWhereArgsHash();
if (usedRelations == null)
{
usedRelations = new HashSet<UsedRelation>();
}
usedRelations.add(new UsedRelation(name, whereArgsHash));
}
public void usedName(Scriptable scriptable, String name)
{
if (ScopesUtils.isVariableScope(name))
{
// global
usedGlobal(name);
}
else if (scriptable instanceof IFoundSetInternal)
{
IFoundSetInternal foundSet = (IFoundSetInternal)scriptable;
if (foundSet.getSQLSheet().containsAggregate(name))
{
// aggregate
usedAggregate(foundSet, name);
}
else
{
usedFromRecord(foundSet.getRecord(foundSet.getSelectedIndex()) /* may be null for global relations */, name);
}
}
else if (scriptable instanceof IRecordInternal)
{
usedFromRecord((IRecordInternal)scriptable, name);
}
else if (scriptable instanceof TableScope)
{
usedFromRecord((IRecordInternal)scriptable.getPrototype(), name);
}
}
/**
* Used name from Record,
* @param record may be null (for global relations)
* @param name
*/
protected void usedFromRecord(IRecordInternal record, String name)
{
IRecordInternal currentRecord = record;
try
{
String[] parts = name.split("\\."); //$NON-NLS-1$
for (String part : parts)
{
Relation relation = flattenedSolution.getRelation(part);
if (relation != null)
{
// calc depends on the relation, add a dependency for the primary data providers for the relation
IDataProvider[] primaryDataProviders = relation.getPrimaryDataProviders(flattenedSolution);
for (IDataProvider prim : primaryDataProviders)
{
if (prim instanceof LiteralDataprovider) continue;
String primdp = prim.getDataProviderID();
if (ScopesUtils.isVariableScope(primdp))
{
// global
usedGlobal(primdp);
}
else
{ // column
if (currentRecord != null)
{
if (currentRecord.getRawData() == null)
{
if (currentRecord instanceof PrototypeState)
{
Debug.trace("Calculation '" + name + "' depends on field '" + part + "' from PrototypeState " + currentRecord);
}
else
{
// should not happen
Debug.error("Unexpected state: calculation '" + name + "' depends on column '" + part + "' from record without pk: " +
currentRecord, new IllegalStateException());
}
}
else
{
usedColumn(currentRecord.getParentFoundSet().getDataSource(), currentRecord.getRawData().getPKHashKey(), primdp);
}
}
}
}
IFoundSetInternal foundSet = null;
if (currentRecord != null) foundSet = currentRecord.getRelatedFoundSet(relation.getName());
currentRecord = null;
if (foundSet instanceof RelatedFoundSet)
{
usedRelatedFoundSet(relation.getName(), (RelatedFoundSet)foundSet);
currentRecord = foundSet.getRecord(foundSet.getSelectedIndex());
}
}
else
{
if (currentRecord != null)
{
IFoundSetInternal foundSet = currentRecord.getParentFoundSet();
if (foundSet.getSQLSheet().containsAggregate(part))
{
// aggregate
usedAggregate(foundSet, part);
}
else
{
// field or calc
if (currentRecord.has(part))
{
if (currentRecord.getRawData() == null)
{
if (currentRecord instanceof PrototypeState)
{
Debug.trace("Calculation '" + name + "' depends on field '" + part + "' from PrototypeState " + currentRecord);
}
else
{
// should not happen
Debug.error("Unexpected state: calculation '" + name + "' depends on field '" + part + "' from record without pk: " +
currentRecord, new IllegalStateException());
}
}
else
{
usedColumn(foundSet.getDataSource(), currentRecord.getRawData().getPKHashKey(), part);
}
}
}
}
return;
}
if (currentRecord == null)
{
return;
}
}
}
catch (RepositoryException e)
{
Debug.error(e);
}
}
public Set<String> getGlobals()
{
return usedGlobals;
}
public Set<UsedAggregate> getAggregates()
{
return usedAggregates;
}
public Set<UsedDataProvider> getColumns()
{
return usedColumns;
}
public Set<UsedRelation> getRelations()
{
return usedRelations;
}
public void usedAggregate(IFoundSetInternal foundSet, String name)
{
if (usedAggregates == null)
{
usedAggregates = new HashSet<UsedAggregate>();
}
usedAggregates.add(new UsedAggregate(foundSet, name));
}
public void usedColumn(String dataSource, String pkHashKey, String dataProviderId)
{
if (usedColumns == null)
{
usedColumns = new HashSet<UsedDataProvider>();
}
usedColumns.add(new UsedDataProvider(dataSource, pkHashKey, dataProviderId));
}
public void usedGlobal(String name)
{
if (usedGlobals == null)
{
usedGlobals = new HashSet<String>();
}
usedGlobals.add(name);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder("UsedDataProviderTracker ["); //$NON-NLS-1$
if (usedAggregates != null)
{
sb.append(" aggregates="); //$NON-NLS-1$
sb.append(usedAggregates);
}
if (usedColumns != null)
{
sb.append(" columns="); //$NON-NLS-1$
sb.append(usedColumns);
}
if (usedGlobals != null)
{
sb.append(" globals="); //$NON-NLS-1$
sb.append(usedGlobals);
}
if (usedRelations != null)
{
sb.append(" relations="); //$NON-NLS-1$
sb.append(usedRelations);
}
return sb.append(" ]").toString(); //$NON-NLS-1$
}
public static class UsedDataProvider
{
public final String dataSource;
public final String pkHashKey;
public final String dataProviderId;
public UsedDataProvider(String dataSource, String pkHashKey, String dataProviderId)
{
this.dataSource = dataSource;
this.pkHashKey = pkHashKey;
this.dataProviderId = dataProviderId;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((dataProviderId == null) ? 0 : dataProviderId.hashCode());
result = prime * result + ((dataSource == null) ? 0 : dataSource.hashCode());
result = prime * result + ((pkHashKey == null) ? 0 : pkHashKey.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
UsedDataProvider other = (UsedDataProvider)obj;
if (dataProviderId == null)
{
if (other.dataProviderId != null) return false;
}
else if (!dataProviderId.equals(other.dataProviderId)) return false;
if (dataSource == null)
{
if (other.dataSource != null) return false;
}
else if (!dataSource.equals(other.dataSource)) return false;
if (pkHashKey == null)
{
if (other.pkHashKey != null) return false;
}
else if (!pkHashKey.equals(other.pkHashKey)) return false;
return true;
}
@Override
public String toString()
{
return "UsedDataProvider [dataProviderId=" + dataProviderId + ", dataSource=" + dataSource + ", pkHashKey=" + pkHashKey + ']'; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
public static class UsedRelation
{
public final String name;
public final String whereArgsHash;
public UsedRelation(String name, String whereArgsHash)
{
this.name = name;
this.whereArgsHash = whereArgsHash;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((whereArgsHash == null) ? 0 : whereArgsHash.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
UsedRelation other = (UsedRelation)obj;
if (name == null)
{
if (other.name != null) return false;
}
else if (!name.equals(other.name)) return false;
if (whereArgsHash == null)
{
if (other.whereArgsHash != null) return false;
}
else if (!whereArgsHash.equals(other.whereArgsHash)) return false;
return true;
}
@Override
public String toString()
{
return "UsedRelation [name=" + name + ", whereArgsHash=" + whereArgsHash + ']'; //$NON-NLS-1$ //$NON-NLS-2$
}
}
public static class UsedAggregate
{
public final IFoundSetInternal foundSet;
public final String name;
public UsedAggregate(IFoundSetInternal foundSet, String name)
{
this.foundSet = foundSet;
this.name = name;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((foundSet == null) ? 0 : foundSet.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
UsedAggregate other = (UsedAggregate)obj;
if (foundSet == null)
{
if (other.foundSet != null) return false;
}
else if (!foundSet.equals(other.foundSet)) return false;
if (name == null)
{
if (other.name != null) return false;
}
else if (!name.equals(other.name)) return false;
return true;
}
@Override
public String toString()
{
return "UsedAggregate [foundSet=" + foundSet + ", name=" + name + ']'; //$NON-NLS-1$ //$NON-NLS-2$
}
}
}