/******************************************************************************* * ALMA - Atacama Large Millimeter Array * Copyright (c) ESO - European Southern Observatory, 2011 * (in the framework of the ALMA collaboration). * All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 *******************************************************************************/ package alma.tools.idlgen; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.Set; import org.jacorb.idl.AcsAdapterForOldJacorb; import org.jacorb.idl.AliasTypeSpec; import org.jacorb.idl.ConstrTypeSpec; import org.jacorb.idl.Declaration; import org.jacorb.idl.Definition; import org.jacorb.idl.EnumType; import org.jacorb.idl.IdlSymbol; import org.jacorb.idl.Interface; import org.jacorb.idl.InterfaceBody; import org.jacorb.idl.Member; import org.jacorb.idl.MemberList; import org.jacorb.idl.Module; import org.jacorb.idl.OpDecl; import org.jacorb.idl.ParamDecl; import org.jacorb.idl.Spec; import org.jacorb.idl.StructType; import org.jacorb.idl.TypeDeclaration; import org.jacorb.idl.TypeSpec; import org.jacorb.idl.VectorType; import org.jacorb.idl.parser; import org.jacorb.idl.javamapping.JavaMappingGeneratingVisitor; /** * This class gets instantiated by {@link XmlIdlCompiler} and gets called by * {@link org.jacorb.idl.parser#compile(String[], StringWriter)}. * It walks the tree of IDL parser nodes and collects information about ACS interfaces and * typedefs, structs and those interfaces that are affected by XML. * <p> * It does not rename any tree nodes nor does it generate code. * * @author jschwarz * @author hsommer */ public class JacorbVisitor extends JavaMappingGeneratingVisitor { public static final String XML_ENTITY_STRUCT_NAME = "XmlEntityStruct"; public static final String ACSCOMPONENT_IDLTYPE = "IDL:alma/ACS/ACSComponent:1.0"; public static final String ACSOFFSHOOT_IDLTYPE = "IDL:alma/ACS/OffShoot:1.0"; /** * Contains the typedef nodes from the currently parsed IDL file * that unwind to an XmlEntityStruct or a sequence of that. * We use this set only to generate code for the typedefs of this IDL file. * <p> * This set should not be used elsewhere to determine whether a parameter or struct member is of an xml type. * If those typedefs come from included IDL files, they will not be contained in xmlTypedefs, * so that we must unwind and check them separately. * @see #visitAlias(AliasTypeSpec) */ final Set<AliasTypeSpec> xmlTypedefs = new LinkedHashSet<AliasTypeSpec>(); final Set<StructType> xmlAwareStructs = new LinkedHashSet<StructType>(); final Set<Interface> xmlAwareIFs = new LinkedHashSet<Interface>(); final Set<Interface> acsCompIFs = new LinkedHashSet<Interface>(); /** * This stack contains the interfaces we are currently visiting, * which can be more than one interface if an interfaces references another one * and then back to itself, either directly or via a struct that has an interface member. * This stack is used to break cyclic references. * Note that only interfaces can be forward declared in IDL, so that no other types * can have circular dependencies. */ private final Set<Interface> interfaceStack = new LinkedHashSet<Interface>(); public JacorbVisitor() { super(); // System.out.println("DEBUG: JacorbVisitor created."); } /** * The top-level call, from which we start walking the IDL parse tree * using mostly the base class implementation of the tree descend. */ @Override public void visitSpec(Spec spec) { // System.out.println("DEBUG: visitSpec called."); super.visitSpec(spec); } @Override public void visitModule(Module module) { // Skip modules for which we anyway don't generate code. // There is no noticeable performance gain, but it helps with debugging. // Note that the jacorb idl print methods only skip included interfaces, not modules, unsure why. // // TODO: Try out included xml entity typedefs and structs etc in our IDL, // whether visiting them only when processing their own IDL is good enough. It should be. if (module.is_included() && !generateIncluded()) { // System.out.println("Skipping included module " + module.pack_name); return; } super.visitModule(module); } /** * We override this to detect all XmlEntityStruct typedefs, even if they do not get used * as parameters or struct members inside our own IDL file, be it directly or through other typedefs. * The reason is that these typedefs may get included in other IDL files, where the * respective Holder class is then expected to be found in the jar generated from this IDL. * (Note that in contrast to the normal Idl2Java mapping, we always must generate a Holder class for an Xml typedef, * e.g. SchedBlockHolder with the castor class field "mycastorpackage.SchedBlock value".) * <p> * This method corresponds to {@link alma.tools.idlgen.IdlTreeManipulator#findXmlTypedefNodes(IdlObject, Set)} * in the OpenORB based implementation. */ @Override public void visitAlias(AliasTypeSpec alias) { if (isOrHasXmlEntityStruct(alias, 0)) { xmlTypedefs.add(alias); } } @Override public void visitStruct(StructType struct) { // System.out.println("visitStruct called with struct name=" + struct.name()); if (hasXmlEntityStruct(struct, 0)) { xmlAwareStructs.add(struct); } } @Override public void visitInterface(Interface interfce) { // System.out.println("visitInterface called with interfce.name=" + interfce.name() + ", body=" + interfce.body); // Don't need to check if it's an included interface, // because this we do already on the module level. // Skip interfce if it's not an ACS interface or offshoot. // This automatically skips forward declarations of interfaces. // (This code corresponds to XmlIdl's class IDLComponentTester. // With JacORB it's much less code because we get all inherited interfaces in one call // instead of only getting the direct parent generation // and also having to collect all interfaces first in an extra step, because of an OpenORB bug.) String[] superInts = interfce.get_ids(); boolean isAcsInterface = false; for (int i = 1; i < superInts.length; i++) { if (superInts[i].equals(ACSCOMPONENT_IDLTYPE)) { isAcsInterface = true; acsCompIFs.add(interfce); // System.out.println("I/F "+interfce+" is an ACS component"); break; } else if (superInts[i].equals(ACSOFFSHOOT_IDLTYPE)) { isAcsInterface = true; // System.out.println("I/F "+interfce+" is an ACS offshoot"); break; } } if (isAcsInterface) { // avoid cyclic recursion if (interfaceStack.contains(interfce)) { // System.out.println("Breaking interface recursion for " + interfce.name()); } else { interfaceStack.add(interfce); // Let the base class do the tree descending via InterfaceBody to OpDecl, // where it will be decided if our ACS interface is affected by XML. super.visitInterface(interfce); interfaceStack.remove(interfce); } } } /** * We override this method in order to descend not only to operations * but also to inner structs and typedefs. * @see org.jacorb.idl.InterfaceBody#print(PrintWriter) */ @Override public void visitInterfaceBody(InterfaceBody body) { // The base impl (only) takes care of descending to the interface's operations super.visitInterfaceBody(body); // Visit structs defined inside the interface. // TODO: Can there be the case that none of the operations contain XML and yet we find XML in the inner structs etc, // and if so, must we add the interface to xmlAwareIFs? for (Definition def : body.v) { Declaration dec = def.get_declaration(); if (!(dec instanceof OpDecl)) { def.accept(this); } } } @Override public void visitOpDecl(OpDecl op) { // The interface this operation belongs to Interface interfce = (Interface)op.myInterface; // Optimization: Skip the operation if the interface has already been marked. if (xmlAwareIFs.contains(interfce)) { return; } // The operation's return type TypeSpec retType = op.opTypeSpec; // Check if the return type brings in XML boolean hasXML = isOrHasXmlEntityStruct(retType, 0); if (hasXML) { xmlAwareIFs.add(interfce); // The operation's return data is a typedef'd XmlEntityStruct (or sequence, nested structs etc, of it). hasXML = true; // System.out.println("Operation " + interfce.name() + "#" + op.name() + " returns XML-affected data " + retType.name()+ "."); } else { // Descend to the op parameters to check if they bring in XML. // As a little optimization that could make debugging easier, we skip checking of parameters if we already found XML in the return type. // The next few lines are largely copied from the base class. However, we don't simply call super.visitOpDecl(op) // because in addition we want to link the ParamDecls back to our OpDecl using "setEnclosingSymbol" // (which jacorb for whatever reason has not called during parsing). for (Enumeration<?> e = op.paramDecls.elements(); e.hasMoreElements();) { ParamDecl param = (ParamDecl) e.nextElement(); param.setEnclosingSymbol(op); param.accept(this); } } } /** * We override this method to check if a parameter is of an XML-related type * and to mark the respective interface. * <p> * The check is simple enough so that we could inline it in {@link #visitOpDecl(OpDecl)}, * but by using visitParamDecl we cleanly follow jacorb's visitor pattern. */ @Override public void visitParamDecl(ParamDecl param) { OpDecl op = (OpDecl) param.getEnclosingSymbol(); Interface interfce = (Interface) op.myInterface; TypeSpec ts = param.paramTypeSpec; if (isOrHasXmlEntityStruct(ts, 0)) { xmlAwareIFs.add(interfce); // System.out.println("Operation " + interfce.name() + "#" + op.name() + " has XML-affected parameter " + param.simple_declarator + "."); // TODO: Remove this setpackage call because it taints the tree walking with code generation. // hard-wired for now to see whether this will work, but it applies only to the typedef, apparently // ts.setPackage("dummyentities"); // // and it shows up as "dummyentities.xmltest" in the debugger. } } //////////////////////// /** * Recursively checks if the given type is a typedef'd XmlEntityStruct or sequence of that, * or if it is a struct that contains such a typedef or a nested struct that contains the XML struct, * or if it is an interface that contains xml types. * <p> * This method descends into interfaces and struct children while resolving sequences and typedefs. * @param depth 0-based counter for recursion depth, to control log output. * @return true if <code>type</code> is an XmlEntityStruct or uses one. */ private boolean isOrHasXmlEntityStruct(TypeSpec type, int depth) { boolean ret = false; if (type instanceof AliasTypeSpec) { // check underlying (aliased) type AliasTypeSpec alias = (AliasTypeSpec) type; TypeSpec otype = alias.originalType(); ret = isOrHasXmlEntityStruct(otype, depth + 1); } else if (type instanceof VectorType) { // check the type that we have a sequence of VectorType vector = (VectorType) type; ret = isOrHasXmlEntityStruct(vector.elementTypeSpec(), depth + 1); } else if (type instanceof ConstrTypeSpec) { TypeDeclaration decl = ((ConstrTypeSpec) type.typeSpec()).c_type_spec.declaration(); if (decl instanceof StructType) { StructType struct = (StructType) decl; if (struct.name().equals(XML_ENTITY_STRUCT_NAME)) { // TODO: Consider IDL package as well, not just the struct name "XmlEntityStruct". // Allow a cheating property for packages such as "xmltest", but otherwise insist on "xmlentity". // String packageName = struct.pack_name; // End of recursion... Our struct is an XmlEntityStruct ret = true; } else { // Iterate over struct children and recurse as needed. ret = hasXmlEntityStruct(struct, depth); } } else if (decl instanceof Interface) { // Examples: Offshoot used as a struct member, in a typedef, or as an operation parameter Interface interfce = (Interface) decl; if (interfce.body == null) { // A forward declared interface stays in the parse tree as a separate Interface object. // We must resolve the corresponding Interface definition. interfce = resolveForwardDecl(interfce); // Our forward declared interface may not have been processed yet. // We do it now so that we can rely on the information from "xmlAwareIFs". visitInterface(interfce); } // Now after the call to visitInterface we can trust xmlAwareIFs with regard to our interfce if (xmlAwareIFs.contains(interfce)) { ret = true; } } else if (decl instanceof EnumType) { // enums are not associated with XML. Just leave ret=false } else { System.out.println("*** isOrHasXmlEntityStruct: Unexpected ConstrTypeSpec with TypeDeclaration subtype " + decl.getClass().getSimpleName()); } } // if (depth == 0) { // System.out.println("isOrHasXmlEntityStruct(" + type.toString() + ") returns " + ret); // } return ret; } /** * Iterates over and recurses a struct's members to check if they are or have XmlEntityStruct types. * <p> * We need this as an auxiliary method to be called from {@link #visitStruct(StructType)} * because there we do not seem to have a TypeSpec available for our struct that we could * directly pass to {@link #isOrHasXmlEntityStruct(TypeSpec, int)}. * Thus we call this loop of struct members from both methods to avoid code duplication. */ private boolean hasXmlEntityStruct(StructType struct, int depth) { MemberList members = struct.memberlist; if (members != null) { for (Enumeration<?> e = members.elements(); e.hasMoreElements();) { TypeSpec memberType = ((Member) e.nextElement()).type_spec; if (isOrHasXmlEntityStruct(memberType, depth + 1)) { return true; } } } return false; } /** * If the given interface is instantiated from a forward declaration then * this method returns the corresponding Interface object that contains the actual definition. * Otherwise the same object is returned. */ public static Interface resolveForwardDecl(Interface interfce) { Interface ret = interfce; if (interfce.body == null) { String fullTypeName = interfce.pack_name + "." + interfce.name(); // method Interface#full_name is not visible ret = (Interface) ((ConstrTypeSpec) AcsAdapterForOldJacorb.getFromTypeMap(fullTypeName)).c_type_spec; } return ret; } //////////////////////// /** * We must "repeat" this method here because {@link IdlSymbol#generateIncluded()} * is not publicly visible. */ private boolean generateIncluded() { return ( parser.generateIncluded() && !parser.getInhibitionState() ); } /** * Overridden to avoid the annoying log that the base impl produces. */ @Override public void visitDeclaration(Declaration declaration) { // System.out.println("visitDeclaration: skipping " + declaration.getClass().getName()); } /** * We override this method only to fix a bug with an endless loop in the base class. * <p> * Note that the normal jacorb run does not call EnumType#accept which calls the broken "visitEnum", * but instead it calls EnumType#print, so that the bug in JavaMappingGeneratingVisitor goes unnoticed. */ @Override public void visitEnum(EnumType enumType) { // ps.println("DEBUG: skipping visitEnum(" + enumType.typeName() + ")."); } }