/* // $Id: //guest/julian_hyde/saffron/src/main/openjava/ptree/util/SyntheticClass.java#6 $ // Saffron preprocessor and data engine // Copyright (C) 2002 Julian Hyde <julian.hyde@mail.com> // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library 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 // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public // License along with this library; if not, write to the // Free Software Foundation, Inc., 59 Temple Place - Suite 330, // Boston, MA 02111-1307, USA. // // See the COPYING file located in the top-level-directory of // the archive of this library for complete text of license. */ package openjava.ptree.util; import openjava.mop.*; import openjava.ptree.*; import openjava.tools.DebugOut; import saffron.util.Util; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; /** * A <code>SyntheticClass</code> is a {@link OJClass class declaration} for * intermediate results in saffron expressions. It is created implicitly while * expressions are being compiled. * * Two synthetic classes are identical if their attributes are of the same * number, type, and order. * * Synthetic classes are created in two ways: {@link #createProject(OJClass, * OJClass[], String[])} creates the type of a select clause, and {@link * #createJoin(OJClass, OJClass[])} creates the type of a join. The semantics * are slightly different: projection classes have field names, but join * classes do not; two join classes with the same member types are equivalent, * but two distinct projection classes may have the same set of attributes. **/ public class SyntheticClass extends OJClass { /* -- Members -- */ String description; // for debug OJClass[] classes; ClassDeclaration decl; private static int id = 0; /** * Map a {@link HashableArray} (which is just a wrapper around an array of * classes and names to the {@link SyntheticClass} which implements that * array of types. */ static Hashtable mapKey2SyntheticClass = new Hashtable(); public static final String JOIN_CLASS_PREFIX = "Oj_"; public static final String PROJECT_CLASS_PREFIX = "Ojp_"; public static final String FIELD_PREFIX = "$f"; /* -- Constructors -- */ public String toString() { return super.toString() + " " + description; } private SyntheticClass( Environment env, OJClass declarer, OJClass[] classes, String[] fieldNames, ClassDeclaration decl, String description) { super(env, declarer, decl); this.classes = classes; this.decl = decl; this.description = description; // default constructor ConstructorDeclaration constructor = new ConstructorDeclaration( null, decl.getName(), null, null, new StatementList()); decl.getBody().add(constructor); // create value constructor (unless it is the same as the default // constructor) if (classes.length > 0) { ParameterList parameterList = new ParameterList(); StatementList statementList = new StatementList(); for (int i = 0; i < classes.length; i++) { String varName = fieldNames[i]; parameterList.add( new Parameter(TypeName.forOJClass(classes[i]), varName)); statementList.add( new ExpressionStatement( new AssignmentExpression( new FieldAccess( SelfAccess.makeThis(), varName), AssignmentExpression.EQUALS, new Variable(varName)))); } ConstructorDeclaration constructor2 = new ConstructorDeclaration( null, decl.getName(), parameterList, null, statementList); decl.getBody().add(constructor2); } // register ourself try { declarer.addClass(this); } catch (openjava.mop.CannotAlterException e) { throw Toolbox.newInternal( e, "holder class must be OJClassSourceCode"); } env.recordMemberClass(declarer.getName(), decl.getName()); env.getGlobalEnvironment().record(getName(), this); mapKey2SyntheticClass.put(description, this); DebugOut.println( "created SyntheticClass: name=" + getName() + ", description=" + description); } /* -- Methods -- */ /** * Creates a <code>SyntheticClass</code>, or if there is already one with * the same number and type of fields, returns that. */ public static OJClass createJoin(OJClass declarer, OJClass[] classes) { if (classes.length == 1) { // don't make a singleton SyntheticClass, just return the atomic // class return classes[0]; } boolean isJoin = true; return create(declarer, classes, null, isJoin); } /** * Creates a <code>SyntheticClass</code> with named fields. We don't check * whether there is an equivalent class -- all classes with named fields * are different. */ public static OJClass createProject( OJClass declarer, OJClass[] classes, String[] fieldNames) { boolean isJoin = false; return create(declarer, classes, fieldNames, isJoin); } private static OJClass create( OJClass declarer, OJClass[] classes, String[] fieldNames, boolean isJoin) { if (fieldNames == null) { fieldNames = new String[classes.length]; } Toolbox.assert( classes.length == fieldNames.length, "SyntheticClass.create: mismatch between classes and field names"); for (int i = 0; i < fieldNames.length; i++) { if (fieldNames[i] == null) { fieldNames[i] = makeField(i); } } // make description StringBuffer sb = new StringBuffer(); sb.append("{"); for (int i = 0; i < classes.length; i++) { if (i > 0) sb.append(", "); sb.append(fieldNames[i]); sb.append(": "); sb.append(classes[i].toString().replace('$', '.')); if (isJoin) { Toolbox.assert( !isJoinClass(classes[i]), "join classes cannot contain join classes"); } } sb.append("}"); String description = sb.toString(); // is there already an equivalent SyntheticClass? SyntheticClass clazz = (SyntheticClass) mapKey2SyntheticClass.get( description); if (clazz != null) { return clazz; } Environment env = declarer.getEnvironment(); String className = (isJoin ? JOIN_CLASS_PREFIX : PROJECT_CLASS_PREFIX) + Integer.toHexString(id++); ClassDeclaration decl = makeDeclaration( className, classes, fieldNames); return new SyntheticClass( env, declarer, classes, fieldNames, decl, description); } private static ClassDeclaration makeDeclaration( String className, OJClass[] classes, String[] fieldNames) { MemberDeclarationList fieldList = new MemberDeclarationList(); for (int i = 0; i < classes.length; i++) { FieldDeclaration field = new FieldDeclaration( new ModifierList(ModifierList.PUBLIC), TypeName.forOJClass(classes[i]), fieldNames[i], null); fieldList.add(field); } ModifierList modifierList = new ModifierList( ModifierList.PUBLIC | ModifierList.STATIC); ClassDeclaration classDecl = new ClassDeclaration( modifierList, className, new TypeName[] { TypeName.forOJClass(Toolbox.clazzSyntheticObject)}, null, fieldList); return classDecl; } // override Object public boolean equals(Object o) { return o instanceof SyntheticClass && this.description.equals(((SyntheticClass) o).description); } // override Object public int hashCode() { return HashableArray.arrayHashCode(classes); } public int getWidth() { return classes.length; } /** * Returns the number of relations in a class which may or may not be * synthetic. */ public static int getWidth(OJClass clazz) { if (isJoinClass(clazz)) { return ((SyntheticClass) clazz).getWidth(); } else { return 1; } } /** * <p>Make the type of a join. There are two kinds of classes. A <dfn>real * class</dfn> exists in the developer's environment. A <dfn>synthetic * class</dfn> is constructed by the Saffron system to describe the * intermediate and final results of a query. We are at liberty to modify * synthetic classes.</p> * * <p>If we join class C1 to class C2, the result is a synthetic class: * * <pre> * class SC1 { * C1 $f0; * C2 $f1; * } * </pre> * * Suppose that we now join class C3 to this; you would expect the result * type to be a new synthetic class: * * <pre> * class SC2 { * class SC1 { * C1 $f0; * C2 $f1; * } $f0; * class C3 $f1; * } * </pre> * * Now imagine the type resulting from a 6-way join. It will be very * difficult to unpick the nesting in order to reference fields or to * permute the join order. Therefore when one or both of the inputs to a * join are synthetic, we break them apart and re-construct them. Type of * synthetic class SC1 joined to class C3 above is * * <pre> * class SC3 { * C1 $f0; * C2 $f1; * C3 $f2; * } * </pre> * * <p>There are also <dfn>row classes</dfn>, which are synthetic classes * arising from projections. The type of * * <pre>select from (select deptno from dept) * join emp * join (select loc.nation, loc.zipcode from loc)</pre> * * is * * <pre> * class SC { * int $f0; * Emp $f1; * class RC { * String nation; * int zipcode; * } $f2; * } * </pre> * * <p>This deals with nesting; we still need to deal with the field * permutations which occur when we re-order joins. A permutation operator * moves fields back to their original positions, so that join transforms * preserve type.</p> **/ public static OJClass makeJoinType( OJClass declarer, OJClass left, OJClass right) { Vector classesVector = new Vector(); addAtomicClasses(classesVector, left); addAtomicClasses(classesVector, right); OJClass[] classes = new OJClass[classesVector.size()]; classesVector.copyInto(classes); return createJoin(declarer, classes); } private static void addAtomicClasses(Vector classesVector, OJClass clazz) { if (isJoinClass(clazz)) { OJClass[] classes = ((SyntheticClass) clazz).classes; for (int i = 0; i < classes.length; i++) { addAtomicClasses(classesVector, classes[i]); } } else { classesVector.addElement(clazz); } } /** * Add declarations of a set of classes <code>classes</code> as inner * classes of a class declaration <code>outerClassDecl</code>. * Declarations which are already present are not added again. **/ public static void addMembers( ClassDeclaration outerClassDecl, OJClass[] classes) { MemberDeclarationList memberDecls = outerClassDecl.getBody(); outer: for (int i = 0; i < classes.length; i++) { if (classes[i] instanceof SyntheticClass) { ClassDeclaration innerClassDecl = ((SyntheticClass) classes[i]).decl; for (Enumeration existingDecls = memberDecls.elements(); existingDecls.hasMoreElements();) { if (existingDecls.nextElement() == innerClassDecl) { continue outer; } } memberDecls.add(innerClassDecl); } } } /** * Creates a method in a class. **/ public static void addMethod( ClassDeclaration classDecl, Environment env, Expression expression, String name, String[] parameterNames, OJClass[] parameterTypes, OJClass returnType) { OJClass clazz = Toolbox.getType(env, expression); StatementList statementList = new StatementList( returnType == OJSystem.VOID ? (Statement) new ExpressionStatement(expression) : (Statement) new ReturnStatement(expression)); addMethod( classDecl, env, expression, name, parameterNames, parameterTypes, returnType); } /** * Creates a method in a class. **/ public static void addMethod( ClassDeclaration classDecl, Environment env, StatementList statementList, String name, String[] parameterNames, OJClass[] parameterTypes, OJClass returnType) { ParameterList parameterList = new ParameterList(); if (parameterNames.length != parameterTypes.length) { throw Toolbox.newInternal( "must have same number & type of parameters"); } for (int i = 0; i < parameterNames.length; i++) { parameterList.add( new Parameter( TypeName.forOJClass(parameterTypes[i]), parameterNames[i])); } MethodDeclaration methodDecl = new MethodDeclaration( new ModifierList(ModifierList.STATIC | ModifierList.PUBLIC), TypeName.forOJClass(returnType), name, parameterList, null, // no throws statementList); MethodDeclaration oldMethodDecl = null; MemberDeclarationList body = classDecl.getBody(); for (int i = 0, count = body.size(); i < count; i++) { MemberDeclaration memberDecl = body.get(i); if (memberDecl instanceof MethodDeclaration) { MethodDeclaration existingMethodDecl = (MethodDeclaration) memberDecl; if (existingMethodDecl.getName().equals(name) && existingMethodDecl.getParameters().equals(parameterList)) { oldMethodDecl = existingMethodDecl; } } } if (oldMethodDecl == null) { body.add(methodDecl); } else { try { oldMethodDecl.replace(methodDecl); } catch (ParseTreeException e) { throw Util.newInternal(e, "while replacing method " + oldMethodDecl); } } } /** * Converts a field name back to an ordinal. For example, * <code>getOrdinal("$f2")</code> returns 2. If fieldName is not valid, * throws an error if "fail" is true, otherwise returns -1. **/ public static int getOrdinal(String fieldName, boolean fail) { if (fieldName.startsWith(FIELD_PREFIX)) { String s = fieldName.substring(FIELD_PREFIX.length()); try { return Integer.parseInt(s); } catch (NumberFormatException e) { // fall through } } if (fail) { throw Toolbox.newInternal( "bad field in synthetic class [" + fieldName + "]"); } else { return -1; } } public static String makeField(int ordinal) { return FIELD_PREFIX + ordinal; } public static boolean isJoinClass(OJClass clazz) { return clazz.getName().indexOf(JOIN_CLASS_PREFIX) >= 0; } public static boolean isProjectClass(OJClass clazz) { return clazz.getName().indexOf(PROJECT_CLASS_PREFIX) >= 0; } public static int lookupField(OJClass clazz, String fieldName) { boolean fail = true; if (isJoinClass(clazz)) { int i = getOrdinal(fieldName, fail); SyntheticClass syntheticClass = (SyntheticClass) clazz; Util.assert(i < syntheticClass.classes.length); return i; } else { OJField[] fields = clazz.getFields(); for (int i = 0; i < fields.length; i++) { OJField field = fields[i]; if (field.getName().equals(fieldName)) { return i; } } if (fail) { throw Util.newInternal( "field " + fieldName + " not found in class " + clazz); } else { return -1; } } } } // End SyntheticClass.java
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#6 | 1853 | Julian Hyde |
saffron: Further improve binding of rows to variables. |
||
#5 | 1818 | Julian Hyde |
saffron: Oops (forgot ParseTreeAction.java), Correlation now works, Smarter code generation (now burrow into synthetic classes, rather than invoking constructors). |
||
#4 | 1801 | Julian Hyde |
saffron: add ObjectSchema; rules can now be matched more than once; started to implement correlations in queries in from list. |
||
#3 | 1748 | Julian Hyde |
saffron: convert unit tests to JUnit; add CallingConvention.ITERABLE; lots of other stuff; release 0.1. |
||
#2 | 1474 | Julian Hyde |
saffron: Aggregations are working. Renamed 'aggregator' to 'aggregation'. |
||
#1 | 1467 | Julian Hyde |
saffron: First saffron check-in; incorporate my changes to openjava. |