/*
* 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.
*/
/*
* PolymorphicVarContext.java
* Created: Mar 19, 2004
* By: Bo Ilic
*/
package org.openquark.cal.compiler;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* A helper class intended to hold the uninstantiated type and record variables occurring in a
* collection of (one or more) type expressions. This is so that they can toString
* properly, with equal type and record variables having the same string representation.
*
* @author Bo Ilic
*/
public class PolymorphicVarContext {
/**
* (PolymorphicVar -> Integer) map from uninstantiated type or record variables to their index of occurrence in
* a type (or list of types), which we thread through toString() invocations to keep track of what text should be
* assigned to the record or type variable in its string representation.
* Keys are ordered by index.
*/
private final Map<PolymorphicVar, Integer> polymorphicVarToIndexMap;
/**
* if true, then the preferred names of type are record variables will be used if possible. The definition of 'if possible'
* is subject to change so this is more of a preference to display prettier type strings in client code.
*/
private final boolean favorPreferredNames;
/**
* (String Set) of preferred names. Distinct type or record variables may have the same preferred name, which makes
* using the preferred name for both impossible.
*/
private final Set<String> preferredNames;
private PolymorphicVarContext(boolean favorPreferredNames) {
this.favorPreferredNames = favorPreferredNames;
this.polymorphicVarToIndexMap = new LinkedHashMap<PolymorphicVar, Integer>();
if (favorPreferredNames) {
this.preferredNames = new HashSet<String>();
} else {
this.preferredNames = null;
}
}
static public PolymorphicVarContext make() {
return new PolymorphicVarContext(false);
}
/**
* WARNING- DO NOT MAKE PUBLIC. The reason for this is that toString-ing type expressions cannot be done consistently
* with 'favorPreferredName = true' in an incremental fashion. That is why there are no public TypeExpr.toString methods
* that take both a PolymorphicVarContext and a favorPreferredNames argument.
*
* @param favorPreferredNames if true, then the preferred names of type are record variables will be used if possible. The definition of 'if possible'
* is subject to change so this is more of a preference to display prettier type strings in client code.
* @return a new PolymorphicVarContext
*/
static PolymorphicVarContext make(boolean favorPreferredNames) {
return new PolymorphicVarContext(favorPreferredNames);
}
/**
* Adds a type or record variable to this PolymorphicVarContext, if it has not already been added.
*
* Note that the type or record variable must be uninstantiated otherwise an IllegalArgumentException is thrown.
*
* @param polymorphicVar
*/
void addPolymorphicVar(PolymorphicVar polymorphicVar) {
if (polymorphicVar == null) {
throw new NullPointerException("argument 'polymorphicVar' cannot be null");
}
//todoBI the type casting can be cleaned up when we move PolymorphicVar to an internal compiler package.
//we cannot expose e.g. getInstance as a public method.
if (polymorphicVar instanceof TypeVar) {
TypeVar typeVar = (TypeVar)polymorphicVar;
//should only accept uninstantiated TypeVars.
if (typeVar.getInstance() != null) {
throw new IllegalArgumentException("cannot add an instantiated type variable");
}
} else if (polymorphicVar instanceof RecordVar) {
RecordVar recordVar = (RecordVar)polymorphicVar;
if (recordVar.getInstance() != null || recordVar.isNoFields()) {
throw new IllegalArgumentException("cannot add an instantiated record variable");
}
} else {
throw new IllegalArgumentException("unexpected PolymorphicVar subtype");
}
Object indexObject = polymorphicVarToIndexMap.get(polymorphicVar);
if (indexObject == null) {
int index = polymorphicVarToIndexMap.size() + 1;
polymorphicVarToIndexMap.put(polymorphicVar, Integer.valueOf(index));
String preferredName = polymorphicVar.getPreferredName();
if (favorPreferredNames && preferredName != null) {
preferredNames.add(preferredName);
}
}
}
/**
* Maps the ints 1, 2, 3,... to a, b, c, ...,
* @param index must be >= 1 to not generate an IllegalArgumentException.
* @return name to use for the variable
*/
static String indexToVarName(int index) {
if (index <= 0) {
throw new IllegalArgumentException("argument 'index' must be positive");
}
if (index > 0 && index <= 26) {
return String.valueOf((char) ('a' + index - 1));
}
return "a" + String.valueOf(index);
}
private int getPolymorphicVarIndex (PolymorphicVar polymorphicVar) {
return polymorphicVarToIndexMap.get(polymorphicVar).intValue();
}
/**
* Gets the name of the polymorphic var. This takes into account naming preferences represent by the
* PolymorphicVarContext object.
* @param polymorphicVar
* @return name to use for this particular polymorphic var in the context of all the other polymorphic vars
*/
String getPolymorphicVarName(PolymorphicVar polymorphicVar) {
if (usePreferredName()) {
if (!polymorphicVarToIndexMap.containsKey(polymorphicVar)) {
throw new IllegalArgumentException("polymorphicVar is not in this PolymorphicVarContext");
}
return polymorphicVar.getPreferredName();
}
return indexToVarName(getPolymorphicVarIndex(polymorphicVar));
}
private boolean usePreferredName() {
//we only use preferred names if
//a) we are in favorPreferredNames mode
//b) every polymorphic var has a preferred name, and distinct polymorphic vars
//have distinct preferred names.
//This is a very conservative policy.
return favorPreferredNames && polymorphicVarToIndexMap.size() == preferredNames.size();
}
/**
* The set of constrained uninstantiated record and type variables determined by this PolymorphicVarContext,
* ordered by their index. This is used as a helper function for toString'ing type expressions.
*
* Note: a constrained record variable is one that has lacks constraints or has type class constraints.
* A constrained type variable is one that has type class constraints.
*
* @return Set (Object Set, where each Object is either a TypeVar or RecordVar) set of the constrained
* polymorphic variables in this context, ordered by their index.
*/
Set<PolymorphicVar> getConstrainedPolymorphicVars () {
Set<PolymorphicVar> constrainedPolymorphicVars = new LinkedHashSet<PolymorphicVar>(); //Set of PolymorphicVars
for (final PolymorphicVar polymorphicVar : polymorphicVarToIndexMap.keySet()) {
if (polymorphicVar instanceof TypeVar) {
TypeVar typeVar = (TypeVar)polymorphicVar;
if (!typeVar.noClassConstraints()) {
constrainedPolymorphicVars.add(typeVar);
}
} else if (polymorphicVar instanceof RecordVar) {
RecordVar recordVar = (RecordVar)polymorphicVar;
if (!recordVar.noClassConstraints() ||
!recordVar.isNoFields() && !recordVar.getLacksFieldsSet().isEmpty()) {
constrainedPolymorphicVars.add(recordVar);
}
} else {
throw new IllegalStateException("Unexpected PolymorphicVar subtype");
}
}
return constrainedPolymorphicVars;
}
/**
* @param polymorphicVar
* @return true if polymorphicVar is contained in this PolymorphicVarContext
*/
boolean containsPolymorphicVar(PolymorphicVar polymorphicVar) {
return polymorphicVarToIndexMap.containsKey(polymorphicVar);
}
/**
* @return if true, then the preferred names of type are record variables will be used if possible.
* The definition of 'if possible' is subject to change so this is more of a preference to display
* prettier type strings in client code.
*/
boolean getFavorPreferredNames() {
return favorPreferredNames;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
for (Iterator<PolymorphicVar> it = polymorphicVarToIndexMap.keySet().iterator(); it.hasNext(); ) {
PolymorphicVar polyVar = it.next();
sb.append(indexToVarName(getPolymorphicVarIndex(polyVar)));
if (usePreferredName()) {
sb.append(" = ");
sb.append(getPolymorphicVarName(polyVar));
}
if (it.hasNext()) {
sb.append(", ");
}
}
sb.append("]");
return sb.toString();
}
}