/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* RecordVar.java
* Created: Feb 17, 2004
* By: Bo Ilic
*/
package org.openquark.cal.compiler;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import org.openquark.cal.internal.serialization.ModuleSerializationTags;
import org.openquark.cal.internal.serialization.RecordInputStream;
import org.openquark.cal.internal.serialization.RecordOutputStream;
import org.openquark.cal.internal.serialization.RecordInputStream.RecordHeaderInfo;
/**
* Represents a variable value for a record (i.e. a finite set of Fields, along with their associated types).
* This variable can have constraints on it so that the RecordType that this RecordVar can be
* instantiated to are not allowed to include certain field names.
*
* @author Bo Ilic
*/
public class RecordVar implements PolymorphicVar {
private static final int serializationSchema = 0;
private static final int alreadyVisitedRecordVarSerializationSchema = 0;
// Serialization flags
private static final byte HAS_NON_NULL_INSTANCE = 0x01;
private static final byte HAS_LACKS_FIELD_CONSTRAINTS = 0x02;
private static final byte HAS_TYPE_CLASS_CONSTRAINTS = 0x04;
/**
* The array of possible record tags used in calls to {@link RecordInputStream#findRecord(short[])} by
* the {@link #load} method.
*/
private static final short[] RECORD_VAR_RECORD_TAGS = new short[] {
ModuleSerializationTags.RECORD_VAR,
ModuleSerializationTags.ALREADY_VISITED_RECORD_VAR
};
static final Set<FieldName> NO_LACKS_FIELDS = Collections.<FieldName>emptySet();
/**
* An instantiated RecordVar (i.e. instance != null) behaves like its instantiation.
* There are consistency requirements for the instantiation:
* instance.baseRecordVar.lacksFieldsSet must contain this.lacksFieldsSet
* instance.hasFieldsMap must not contain any of the fields in this.lacksFieldsSet
*/
private RecordType instance;
/**
* The preferred textual name of this RecordVar. null if there is no preferredName.
* When a record variable is introduced via a textual declaration, such as with a type declaration, a class instance
* definition, or a data declaration, then we can preserve the user supplied name, and make use of it whenever
* possible in displaying String representations of types involving this RecordVar.
*/
private String preferredName;
/**
* (FieldName Set) fields that this RecordVar is constrained not have. For example, in the type
* (r\field1, r\field2, r\field3) => {r | field1 :: Int, field2 :: Char}, then for the record variable r,
* this set is {field1, field2, field3}.
*/
private Set<FieldName> lacksFieldsSet;
/**
* (TypeClass SortedSet). an uninstantiated record variable can be constrained to be instantiated to a record type
* such that the types of all the fields in the record type satisfy all the type class constraints in
* typeClassConstraintSet.
* Note that if a class B inherits from A and B is in the typeClassConstraintSet, then A will not be-
* the constraint is implicit.
* If typeClassConstraintSet is the empty set, then the record variable can be instantiated to any recordVariable,
* subject to the constraints of the lacksFieldsSet.
*/
private SortedSet<TypeClass> typeClassConstraintSet;
/**
* A special RecordVar corresponding to a record variable that is constrained so that lacksFieldsSet includes
* all fields. In our implementation:
* {NO_FIELDS | foo :: Int, bar :: Char} is what we mean by {foo :: Int, bar :: Char}
*/
static final RecordVar NO_FIELDS = new RecordVar();
/**
* A special constructor used for constructing the special NO_FIELDS value of RecordVar and for
* constructing blank RecordVars during deserialization.
*/
private RecordVar () {
lacksFieldsSet = null;
typeClassConstraintSet = TypeClass.NO_CLASS_CONSTRAINTS;
}
private RecordVar(String preferredName, Set<FieldName> lacksFieldsSet, SortedSet<TypeClass> typeClassConstraintSet) {
if (lacksFieldsSet == null || typeClassConstraintSet == null) {
throw new NullPointerException();
}
if (preferredName != null && !LanguageInfo.isValidTypeVariableName(preferredName)) {
throw new IllegalArgumentException("invalid preferred name " + preferredName);
}
this.preferredName = preferredName;
this.lacksFieldsSet = lacksFieldsSet;
this.typeClassConstraintSet = typeClassConstraintSet;
}
/**
* @param preferredName the preferred textual name of this RecordVar. null if there is no preferredName.
* @return an unconstrained polymorphic RecordVar that can be further instantiated. Its type is {r}.
*/
static RecordVar makePolymorphicRecordVar(String preferredName) {
return new RecordVar(preferredName, NO_LACKS_FIELDS, TypeClass.NO_CLASS_CONSTRAINTS);
}
/**
* General factory method for creating a new RecordVar.
*
* @param preferredName the preferred textual name of this RecordVar. null if there is no preferredName.
* @param lacksFieldsSet (FieldName Set) fields that this RecordVar is constrained not have.
* @param typeClassConstraintSet (TypeClass Set) an uninstantiated record variable can be constrained to be
* instantiated to a record type such that the types of all the fields in the record type satisfy all the
* type class constraints in typeClassConstraintSet. constraints set should initially be
* created with TypeClass.makeNewClassConstraintSet().
* @param validateTypeClassConstraintSet remove redundant superclass constaints, and make the set unmodifiable. This
* is not necessary if reusing another TypeVar's or RecordVar's typeClassConstraintSet.
* @return the new RecordVar. It can be instantiated if needed.
*/
static RecordVar makeRecordVar(String preferredName, Set<FieldName> lacksFieldsSet, SortedSet<TypeClass> typeClassConstraintSet, boolean validateTypeClassConstraintSet) {
if (lacksFieldsSet.isEmpty() && typeClassConstraintSet.isEmpty()) {
return makePolymorphicRecordVar(preferredName);
}
if (validateTypeClassConstraintSet) {
//remove redundant superclass constraints (so that the type class constraint set
//is in a canonical form.
TypeClass.removeSuperclassConstraints(typeClassConstraintSet);
return new RecordVar (preferredName, Collections.unmodifiableSet(lacksFieldsSet),
Collections.unmodifiableSortedSet(typeClassConstraintSet));
} else {
return new RecordVar (preferredName, Collections.unmodifiableSet(lacksFieldsSet), typeClassConstraintSet);
}
}
/**
* @return an efficient copy of this uninstantiated record variable.
*/
RecordVar copyUninstantiatedRecordVar() {
if (isNoFields()) {
return this;
} else {
//since both lacksFieldsSet and typeClassConstraintSet are final and unmodifiable, can use the same instances.
return new RecordVar(preferredName, lacksFieldsSet, typeClassConstraintSet);
}
}
/**
* If this RecordVar is instantiated, then adds all the uninstantiated RecordVars contained in its instance to varSet.
* Otherwise adds itself to varSet. Don't depend on varSet coming out in any particular order.
* @param varSet Set
*/
void getUninstantiatedRecordVars(Set<RecordVar> varSet) {
if(instance != null) {
instance.getUninstantiatedRecordVars(varSet);
return;
}
// NO_FIELDS doesn't really count as an uninstantiated record var, because
// it is instantiated as {} (in a sense)
if(!isNoFields()) {
varSet.add(this);
}
}
/**
* If this RecordVar is instantiated (i.e. it is not NO_FIELDS or an uninstantiated record
* variable), then this function follows the chain of instantiations to get to a RecordVar
* which is NO_FIELDS or uninstantiated.
* @return follows the chain of instantiated RecordVars to reach an uninstantiated RecordVar
* or NO_FIELDS. The returned RecordVar may be this if this RecordVar is already uninstantiated
* or NO_FIELDS.
*/
RecordVar prune() {
if (instance != null) {
return instance.getPrunedRecordVar();
}
return this;
}
boolean isNoFields() {
return this == NO_FIELDS;
}
/**
* The returned lacks field set cannot be modified.
* @return (FieldName Set) this RecordVar cannot be instantiated to anything that contains any of the lacks fields.
*/
Set<FieldName> getLacksFieldsSet () {
return lacksFieldsSet;
}
RecordType getInstance() {
return instance;
}
void setInstance(RecordType instance) {
if (this.instance != null || isNoFields()) {
//can't instantiate an already instantiated RecordVar or modify the special NO_FIELDS recordVar.
throw new IllegalStateException();
}
this.instance = instance;
}
/**{@inheritDoc} */
public String getPreferredName() {
return preferredName;
}
/**
* @return boolean checks whether this record var has an empty type class constraint set.
*/
boolean noClassConstraints(){
return typeClassConstraintSet.isEmpty();
}
/**
* Represents the type class constraints on this record variable. This affects the allowable instantiations
* of the record variable.
* @return SortedSet of TypeClass ordered alphabetically by fully qualified name.
* Will always return a non-null value. Note that this set is unmodifiable.
*/
SortedSet<TypeClass> getTypeClassConstraintSet() {
return typeClassConstraintSet;
}
String toString(PolymorphicVarContext polymorphicVarContext) {
polymorphicVarContext.addPolymorphicVar(this);
return polymorphicVarContext.getPolymorphicVarName(this);
}
@Override
public String toString() {
if (isNoFields()) {
return "NoFields";
}
return "lacks=" + String.valueOf(prune().lacksFieldsSet);
}
/**
* Write an instance of RecordVar to a RecordOutputStream.
* @param s
* @param visitedTypeExpr
* @throws IOException
*/
void write (RecordOutputStream s, Map<TypeExpr, Short> visitedTypeExpr, Map<RecordVar, Short> visitedRecordVar) throws IOException {
Short key = visitedRecordVar.get(this);
if(key != null) {
s.startRecord(ModuleSerializationTags.ALREADY_VISITED_RECORD_VAR, alreadyVisitedRecordVarSerializationSchema);
s.writeShortCompressed(key.shortValue());
s.endRecord();
} else {
key = new Short((short)visitedRecordVar.size());
visitedRecordVar.put(this, key);
writeActual(s, visitedTypeExpr, visitedRecordVar);
}
}
void writeActual(RecordOutputStream s, Map<TypeExpr, Short> visitedTypeExpr, Map<RecordVar, Short> visitedRecordVar) throws IOException {
s.startRecord(ModuleSerializationTags.RECORD_VAR, serializationSchema);
byte flags = 0;
if (instance != null) {
flags |= HAS_NON_NULL_INSTANCE;
}
if (lacksFieldsSet != null) {
flags |= HAS_LACKS_FIELD_CONSTRAINTS;
}
if (!typeClassConstraintSet.isEmpty()) {
flags |= HAS_TYPE_CLASS_CONSTRAINTS;
}
s.writeByte(flags);
if (instance != null) {
instance.write(s, visitedTypeExpr, visitedRecordVar);
}
if (lacksFieldsSet != null) {
s.writeShortCompressed(lacksFieldsSet.size());
for (final FieldName fn : lacksFieldsSet) {
FieldNameIO.writeFieldName(fn, s);
}
}
if (!typeClassConstraintSet.isEmpty()) {
s.writeShortCompressed(typeClassConstraintSet.size());
for (final TypeClass tc : typeClassConstraintSet) {
s.writeQualifiedName(tc.getName());
}
}
s.writeUTF(preferredName);
s.endRecord();
}
/**
* Load an instance of RecordVar from a RecordInputStream.
* @param s
* @param mti
* @param visitedTypeExpr
* @param msgLogger the logger to which to log deserialization messages.
* @return an instance of RecordVar.
*/
static RecordVar load (RecordInputStream s, ModuleTypeInfo mti, Map<Short, TypeExpr> visitedTypeExpr, Map<Short, RecordVar> visitedRecordVar, CompilerMessageLogger msgLogger) throws IOException {
RecordHeaderInfo rhi = s.findRecord(RECORD_VAR_RECORD_TAGS);
if (rhi == null) {
throw new IOException("Unable to find RecordVar record header.");
}
switch(rhi.getRecordTag()) {
case ModuleSerializationTags.ALREADY_VISITED_RECORD_VAR:
{
DeserializationHelper.checkSerializationSchema(rhi.getSchema(), alreadyVisitedRecordVarSerializationSchema, mti.getModuleName(), "RecordVar", msgLogger);
// Read the key and do the lookup.
short id = s.readShortCompressed();
RecordVar recordVar = visitedRecordVar.get(new Short(id));
if (recordVar == null) {
throw new IOException ("Unable to resolve previously encountered RecordVar instance in RecordVar.");
}
s.skipRestOfRecord();
return recordVar;
}
case ModuleSerializationTags.RECORD_VAR:
{
DeserializationHelper.checkSerializationSchema(rhi.getSchema(), serializationSchema, mti.getModuleName(), "RecordVar", msgLogger);
byte flags = s.readByte();
// Short-circuit for the special NO_FIELDS case
if((flags & (HAS_LACKS_FIELD_CONSTRAINTS | HAS_TYPE_CLASS_CONSTRAINTS)) == 0) {
s.skipRestOfRecord();
visitedRecordVar.put(new Short((short)visitedRecordVar.size()), NO_FIELDS);
return NO_FIELDS;
}
RecordVar rv = new RecordVar();
visitedRecordVar.put(new Short((short)visitedRecordVar.size()), rv);
RecordType instance = null;
if ((flags & HAS_NON_NULL_INSTANCE) > 0) {
instance = (RecordType)TypeExpr.load(s, mti, visitedTypeExpr, visitedRecordVar, msgLogger);
}
Set<FieldName> lacksFieldSet = null;
if ((flags & HAS_LACKS_FIELD_CONSTRAINTS) > 0) {
int nLacksFields = s.readShortCompressed();
if (nLacksFields > 0) {
lacksFieldSet = new HashSet<FieldName>();
for (int i = 0; i < nLacksFields; ++i) {
FieldName fn = FieldNameIO.load(s, mti.getModuleName(), msgLogger);
lacksFieldSet.add(fn);
}
} else {
lacksFieldSet = NO_LACKS_FIELDS;
}
}
SortedSet<TypeClass> typeClassConstraintSet = TypeClass.NO_CLASS_CONSTRAINTS;
if((flags & HAS_TYPE_CLASS_CONSTRAINTS) > 0) {
int nTC = s.readShortCompressed();
if (nTC > 0) {
typeClassConstraintSet = TypeClass.makeNewClassConstraintSet();
for (int i = 0; i < nTC; ++i) {
QualifiedName qn = s.readQualifiedName();
TypeClass tc = mti.getReachableTypeClass(qn);
// This could be a type from a non-direct dependee module.
if (tc == null) {
IOException ioe = new IOException("Unable to resolve TypeClass " + qn + " while loading RecordVar.");
throw ioe;
}
typeClassConstraintSet.add(tc);
}
}
}
rv.lacksFieldsSet = lacksFieldSet;
rv.typeClassConstraintSet = typeClassConstraintSet;
if (instance != null) {
rv.setInstance(instance);
}
rv.preferredName = s.readUTF();
s.skipRestOfRecord();
return rv;
}
default:
{
throw new IOException("Unexpected record tag " + rhi.getRecordTag() + " found when looking for RecordVar.");
}
}
}
}