/*
* 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.
*/
/*
* ClassInstanceName.java
* Creation date: (Nov 6, 2002)
* By: Bo Ilic
*/
package org.openquark.cal.compiler;
import java.util.Arrays;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* A helper class for holding onto enough information to easily look up a class instance
* given a module context defining the visibility.
* <p>
* For example, for instance Prelude.Ord Prelude.Date, this is Prelude.Ord Prelude.Date.
* <p>
* For instance (Prelude.Eq a, Prelude.Eq b) => Prelude.Eq (Prelude.Tuple2 a b)
* this is Prelude.Eq Prelude.Tuple2. The point is that we don't need the constraining context because
* overlapping instances are not allowed. We also don't need the module in which the instance is defined
* because instance definitions have different scoping rules from most CAL entities and can't be hidden if
* the module in which the instance is defined is imported either directly or indirectly.
*
* Creation date (Nov 6, 2002).
* @author Bo Ilic
*/
public abstract class ClassInstanceIdentifier {
/** a String representation of this ClassInstanceIdentier. */
private /* final */ transient String instanceIdentifier;
private final QualifiedName typeClassName;
/**
* Represents instances whose instance type is rooted in a type constructor.
* For example,
* instance Num Int
* instance Eq a => Eq [a]
* instance Enum Ordering
*
* @author Bo Ilic
*/
public static class TypeConstructorInstance extends ClassInstanceIdentifier {
private final QualifiedName typeConsName;
public TypeConstructorInstance (QualifiedName typeClassName, QualifiedName typeConsName) {
super (typeClassName);
if (typeConsName == null) {
throw new NullPointerException();
}
if (!LanguageInfo.isValidTypeConstructorName(typeConsName.getUnqualifiedName())) {
throw new IllegalArgumentException();
}
this.typeConsName = typeConsName;
}
public QualifiedName getTypeConsName() {
return typeConsName;
}
@Override
public boolean equals(Object other) {
if (other instanceof TypeConstructorInstance) {
TypeConstructorInstance otherName = (TypeConstructorInstance) other;
return super.typeClassName.equals(otherName.getTypeClassName()) &&
typeConsName.equals(otherName.typeConsName);
}
return false;
}
@Override
public String getTypeIdentifier() {
return typeConsName.getQualifiedName();
}
}
/**
* Represents instances whose instance type is a record-polymorphic record.
* It is "universal" because the instance definition applies to *all* records whose fields satisfy the specified
* constraints i.e. it does not make any restrictions on the allowable field names.
* instance Outputable a => Outputable {a}
*
* @author Bo Ilic
*/
public static class UniversalRecordInstance extends ClassInstanceIdentifier {
private static final String UNIVERSAL = "$UniveralRecord";
public UniversalRecordInstance (QualifiedName typeClassName) {
super (typeClassName);
}
@Override
public boolean equals(Object other) {
if (other instanceof UniversalRecordInstance) {
UniversalRecordInstance otherName = (UniversalRecordInstance) other;
return super.typeClassName.equals(otherName.getTypeClassName());
}
return false;
}
@Override
public String getTypeIdentifier() {
return UNIVERSAL;
}
}
/**
* Represents instances whose instance type is a non record-polymorphic record.
* It is "ad hoc" in the sense that an explicit set of field names are given for the instance definition
* to apply to- and the instance definition does not apply to any other field name set.
* instance (Show a, Show b) => Show {#1 :: a, #2 :: b}
*
* @author Bo Ilic
*/
public static class AdHocRecordInstance extends ClassInstanceIdentifier {
private final SortedSet<FieldName> fieldNames;
private static final String ADHOC = "$AdHocRecord";
public AdHocRecordInstance (QualifiedName typeClassName, SortedSet<FieldName> fieldNames) {
super (typeClassName);
if (fieldNames == null) {
throw new NullPointerException();
}
this.fieldNames = fieldNames;
}
@Override
public boolean equals(Object other) {
if (other instanceof AdHocRecordInstance) {
AdHocRecordInstance otherName = (AdHocRecordInstance) other;
return super.typeClassName.equals(otherName.getTypeClassName()) &&
fieldNames.equals(otherName.fieldNames);
}
return false;
}
public SortedSet<FieldName> getFieldNames () {
return Collections.unmodifiableSortedSet(fieldNames);
}
@Override
public String getTypeIdentifier() {
StringBuilder result = new StringBuilder(ADHOC);
for (final FieldName fieldName : fieldNames) {
result.append("$");
if (fieldName instanceof FieldName.Ordinal) {
result.append(((FieldName.Ordinal)fieldName).getOrdinal());
} else {
result.append(fieldName.getCalSourceForm());
}
}
return result.toString();
}
}
private ClassInstanceIdentifier(QualifiedName typeClassName) {
if (typeClassName == null) {
throw new NullPointerException();
}
if (!LanguageInfo.isValidTypeClassName(typeClassName.getUnqualifiedName())) {
throw new IllegalArgumentException();
}
this.typeClassName = typeClassName;
}
static ClassInstanceIdentifier make(ClassInstance classInstance) {
if (classInstance.isTypeConstructorInstance()) {
return new TypeConstructorInstance(classInstance.getTypeClass().getName(),
((TypeConsApp)classInstance.getType()).getName());
}
if (classInstance.isUniversalRecordInstance()) {
return new UniversalRecordInstance(classInstance.getTypeClass().getName());
}
throw new IllegalArgumentException();
}
static ClassInstanceIdentifier make(QualifiedName typeClassName, TypeExpr instanceType) {
if (instanceType instanceof RecordType) {
RecordType recordType = (RecordType)instanceType;
if (recordType.isRecordPolymorphic()) {
return new UniversalRecordInstance(typeClassName);
}
return new AdHocRecordInstance(typeClassName, recordType.getHasFields());
}
if (instanceType instanceof TypeConsApp) {
return new TypeConstructorInstance(typeClassName, ((TypeConsApp)instanceType).getName());
}
throw new IllegalArgumentException();
}
public static ClassInstanceIdentifier make(QualifiedName typeClassName, String typeIdentifier) {
if (typeIdentifier.equals(UniversalRecordInstance.UNIVERSAL)) {
return new UniversalRecordInstance(typeClassName);
}
if (typeIdentifier.startsWith(AdHocRecordInstance.ADHOC)) {
String[] fieldNameStrings = typeIdentifier.substring(AdHocRecordInstance.ADHOC.length()).split("$");
int nFields = fieldNameStrings.length;
FieldName[] fieldNames = new FieldName[nFields];
for (int i = 0; i < nFields; ++i) {
String fieldNameString = fieldNameStrings[i];
char firstChar = fieldNameString.charAt(0);
if (firstChar >= '1' && firstChar <= '9') {
fieldNameString = "#" + fieldNameString;
}
FieldName fieldName = FieldName.make(fieldNameString);
if (fieldName == null) {
//invalid field name
throw new IllegalArgumentException("invalid field name.");
}
fieldNames[i] = fieldName;
if (i > 0) {
if (fieldNames[i-1].compareTo(fieldName) >= 0) {
//fieldNames must be ordered in FieldName order with no duplicates
throw new IllegalArgumentException("fieldNames must be ordered in FieldName order with no duplicates");
}
}
}
SortedSet<FieldName> fieldNamesSet = new TreeSet<FieldName>(Arrays.asList(fieldNames));
return new AdHocRecordInstance(typeClassName, fieldNamesSet);
}
QualifiedName typeConsName = QualifiedName.makeFromCompoundName(typeIdentifier);
return new TypeConstructorInstance(typeClassName, typeConsName);
}
/**
* @return a canonical form of the class instance identifier, defined to be:
* for type constructor instances:
* <qualified class name><#><qualified type cons name>
* e.g. Prelude.Eq#Prelude.Maybe
*
* for universal record instances:
* <qualified class name><#><$UniversalRecord>
* e.g. Prelude.Ord#$UniversalRecord
*
* for ad hoc record instances
* <qualified class name><#><$AdHocRecord><$fieldName1><$fieldName2>...<$fieldNameN>
* where the field names are in FieldName order, and the '#' is dropped form ordinal
* fieldNames.
* e.g. Prelude.Foo#$AdHocRecord$1$2$date$quantity
*/
public String getInstanceIdentifier() {
if (instanceIdentifier == null) {
instanceIdentifier = new StringBuilder(typeClassName.getQualifiedName()).append('#').append(getTypeIdentifier()).toString();
}
return instanceIdentifier;
}
/**
* @return the name of the type class this instance is for
*/
public QualifiedName getTypeClassName() {
return typeClassName;
}
/**
* @return a canonical form of the type of this identifier. Note this String does not
* include information about the type class only the type.
* for type constructor instances:
* <qualified type cons name>
* e.g. Prelude.Maybe
*
* for universal record instances:
* <$UniversalRecord>
* e.g. $UniversalRecord
*
* for ad hoc record instances
* <$AdHocRecord><$fieldName1><$fieldName2>...<$fieldNameN>
* where the field names are in FieldName order, and the '#' is dropped form ordinal
* fieldNames.
* e.g. $AdHocRecord$1$2$date$quantity
*/
public abstract String getTypeIdentifier();
@Override
public String toString() {
return getInstanceIdentifier();
}
@Override
public abstract boolean equals(Object other);
@Override
public int hashCode() {
return getInstanceIdentifier().hashCode();
}
}