/* Protocol Definition Language Copyright (C) 2003-2006 Marcus Andersson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package net.sf.nmedit.jpdl2.format; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import net.sf.nmedit.jpdl2.PDLException; import net.sf.nmedit.jpdl2.dom.PDLBlock; import net.sf.nmedit.jpdl2.dom.PDLCaseStatement; import net.sf.nmedit.jpdl2.dom.PDLChoice; import net.sf.nmedit.jpdl2.dom.PDLCondition; import net.sf.nmedit.jpdl2.dom.PDLDocument; import net.sf.nmedit.jpdl2.dom.PDLFunction; import net.sf.nmedit.jpdl2.dom.PDLItem; import net.sf.nmedit.jpdl2.dom.PDLItemType; import net.sf.nmedit.jpdl2.dom.PDLMultiplicity; import net.sf.nmedit.jpdl2.dom.PDLPacketDecl; import net.sf.nmedit.jpdl2.dom.PDLPacketRef; import net.sf.nmedit.jpdl2.impl.PDLCompiledCondition; import net.sf.nmedit.jpdl2.impl.PDLPacketDeclImpl; import net.sf.nmedit.jpdl2.utils.PDLUtils; /** * ensures * - start packet exists (if the start statement is present) * detects: * - mutual packet references * - unreachable code (after fail statements) * - variable(-list) name, label name, packet(-list)-binding collisions * - mutual exclusive statement: unreachable optional items (only a few cases are tested yet) * - invalid variable / label references * TODO: * - test that each path is tagged by a messageId (optional) */ public class PDLDocumentVerifier { private PDLDocument doc; public PDLDocumentVerifier(PDLDocument doc) { this.doc = doc; } public void verify() throws PDLException { verifyStartDecl(); verifyPacketDecl(); // verifyPaths(); // test if each possible path contains a message() statement } private void verifyStartDecl() throws PDLException { // verify start packet if (doc.getStartPacketName()!=null && doc.getPacketDecl(doc.getStartPacketName())==null) { throw new PDLException("start packet not declared: "+doc.getStartPacketName()); } } private void verifyPacketDecl() throws PDLException { Set<String> visitedPackets = new HashSet<String>(); for (PDLPacketDecl decl: doc) { // verify local declaration of packet verifyPacketDecl(decl); // verify packet references and detect unconditional mutual recursion visitedPackets.clear(); verifyMutualRecursion(visitedPackets, decl, false); } } private void verifyPacketDecl(PDLPacketDecl decl) throws PDLException { try { verifyBlock(decl, Collections.<String>emptySet(), decl, false); } catch (PDLException e) { throw new PDLException(e, decl); } } private boolean verifyBlock(PDLPacketDecl root, Set<String> declared, PDLBlock block, boolean conditionalPath) throws PDLException { // after 'break' or 'fail' statement, no further statements are allowed (in unconditional paths) boolean allowMoreStatements = true; // declarations in current block declared = new HashSet<String>(declared); for (int index = 0;index<block.getItemCount();index++) { PDLItem item = block.getItem(index); if ((!allowMoreStatements) ) { error(item, "unreachable code"); } try { switch (item.getType()) { // store reference names case Label: declare(declared, item, "@", item.asInstruction().getString()); break; case Variable: declare(declared, item, null, item.asVariable().getName()); break; // items referencing other items case Constant: verifyMultiplicativeReference(root, declared, item, item.asConstant().getMultiplicity()); break; case ImplicitVariable: case AnonymousVariable: declare(declared, item, null, item.asVariable().getName()); verifyFunction(root, declared, item, item.asVariable().getFunction()); break; case VariableList: // use unique prefix "%" for variables which can not be referenced declare(declared, item, "%", item.asVariable().getName()); verifyMultiplicativeReference(root, declared, item, item.asVariable().getMultiplicity()); break; case PacketRef: { PDLPacketRef ref = item.asPacketRef(); declare(declared, item, "$", ref.getBinding()); verifyPacketReference(item, ref); break; } case InlinePacketRef: { PDLPacketRef ref = item.asPacketRef(); declare(declared, item, "$", "$"); verifyPacketReference(item, ref); allowMoreStatements = verifyBlock(root, declared, ref.getReferencedPacket(), conditionalPath); break; } case PacketRefList: { PDLPacketRef ref = item.asPacketRef(); declare(declared, item, "$", ref.getBinding()); verifyPacketReference(item, ref); verifyMultiplicativeReference(root, declared, item, ref.getMultiplicity()); break; } case Conditional: { PDLCondition condition = item.asConditional().getCondition(); verifyCondition(root, declared, item, condition); verifyBlock(root, declared, item.asConditional(), true); break; } case Optional: verifyBlock(root, declared, item.asOptional(), true); break; case Choice: for (PDLBlock nested: item.asChoice().getItems()) verifyBlock(root, declared, nested, true); break; case Block: allowMoreStatements = verifyBlock(root, declared, item.asBlock(), conditionalPath); break; case SwitchStatement: for (PDLCaseStatement nested: item.asSwitchStatement().getItems()) verifyBlock(root, declared, nested.getBlock(), true); break; // no references case MessageId: break; case StringDef: break; case Fail: allowMoreStatements = false; break; default: PDLUtils.unknownItemTypeError(item); break; } } catch (PDLException e) { throw new PDLException(e, item); } } return allowMoreStatements; } private void declare(Set<String> declared, PDLItem item, Object prefix, String name) throws PDLException { String fullName = prefix == null ? name : (prefix+name); if (declared.contains(name) ||declared.contains("@"+name) ||declared.contains("$"+name) ||declared.contains("%"+name)) error(item, "name already in use: "+fullName); declared.add(fullName); } private void verifyMutualRecursion(Set<String> visitedPackets, PDLPacketDecl packet, boolean conditionalPath) throws PDLException { if (conditionalPath) return; if (visitedPackets.contains(packet.getName())) error(packet, "mutual recursion detected in packet "+packet.getName()); visitedPackets.add(packet.getName()); verifyMutualRecursionInBlock(visitedPackets, packet, conditionalPath); visitedPackets.remove(packet.getName()); } private void verifyMutualRecursionInBlock(Set<String> visitedPackets, PDLBlock block, boolean conditionalPath) throws PDLException { for (int index = 0;index<block.getItemCount();index++) { PDLItem item = block.getItem(index); switch (item.getType()) { // no children case Label: break; case Variable: break; case Constant: break; case ImplicitVariable: break; case AnonymousVariable: break; case VariableList: break; case MessageId: break; case StringDef: break; case Fail: break; // packets case InlinePacketRef: verifyMutualRecursion(visitedPackets, item.asPacketRef().getReferencedPacket(), conditionalPath); break; case PacketRef: verifyMutualRecursion(visitedPackets, item.asPacketRef().getReferencedPacket(), conditionalPath); break; case PacketRefList: verifyMutualRecursion(visitedPackets, item.asPacketRef().getReferencedPacket(), true); break; // blocks case Conditional: verifyMutualRecursionInBlock(visitedPackets, item.asConditional(), true); break; case Optional: verifyMutualRecursionInBlock(visitedPackets, item.asOptional(), true); break; case Choice: { PDLChoice me = item.asChoice(); for (PDLBlock nested: me.getItems()) verifyMutualRecursionInBlock(visitedPackets, nested, true); verifyNoUnrechableCode(me); break; } case Block: verifyMutualRecursionInBlock(visitedPackets, item.asBlock(), conditionalPath); break; case SwitchStatement: for (PDLCaseStatement nested: item.asSwitchStatement().getItems()) verifyMutualRecursionInBlock(visitedPackets, nested.getBlock(), true); break; // no references default: PDLUtils.unknownItemTypeError(item); break; } } } private void verifyNoUnrechableCode(PDLChoice m) throws PDLException { List<PDLBlock> list = m.getItems(); if (list.size()<2) error(m, "choice statement has less than two elements"); for (int i=0;i<list.size()-1;i++) { PDLItem a = meGetItem(list.get(i)); PDLItem sa = getSimpleDataTypeFor(a); int sza = getSimpleDataItemSize(sa); for (int j=i+1;j<list.size();j++) { PDLItem b = meGetItem(list.get(j)); PDLItem sb = getSimpleDataTypeFor(b); // now compare pair (a,b) // a parsed/tested before b boolean moreTests = true; int szb = getSimpleDataItemSize(sb); if (sza<=szb && sza>0 && szb>0) { // but if a is constant and b is not a constant, then we accept if (sa.getType() == PDLItemType.Constant && sb.getType() != PDLItemType.Constant) { // ok moreTests = false; } else { throw new PDLException(b, "item never reached"); } } else { moreTests = sa==null || sb==null; } if (moreTests) { // now test for variable list/constant list // TODO // now test minimum size property if (a.getMinimumSize()<b.getMinimumSize()) error(b, "elements must be ordered by getMinimumSize()"); if (a.getMinimumCount()<b.getMinimumCount()) error(b, "elements must be ordered by getMinimumCount()"); } } } } private PDLItem meGetItem(PDLBlock item) { if (item.getType() == PDLItemType.Block && item.getItemCount()==1) { return item.getItem(0); } return item; } private PDLItem getSimpleDataTypeFor(PDLItem item) { switch (item.getType()) { case Constant: return item; case Variable: return item; case ImplicitVariable: return item; } if (item instanceof PDLBlock) { PDLBlock block = (PDLBlock) item; if (block.getItemCount() == 1) return getSimpleDataTypeFor(block.getItem(0)); } return null; } private int getSimpleDataItemSize(PDLItem item) { if (item == null) return -1; switch (item.getType()) { case Constant: if(item.asConstant().getMultiplicity() != null) return -1; // constant list return item.asConstant().getSize(); case Variable: return item.asVariable().getSize(); case ImplicitVariable: return item.asVariable().getSize(); default: return -1; } } private void verifyCondition(PDLPacketDecl root, Set<String> declared, PDLItem item, PDLCondition condition) throws PDLException { if (condition instanceof PDLCompiledCondition) { ensureReferencedExist(root, declared, item, ((PDLCompiledCondition) condition).getDependencies()); } } private void verifyFunction(PDLPacketDecl root, Set<String> declared, PDLItem item, PDLFunction function) throws PDLException { ensureReferencedExist(root, declared, item, function.getDependencies()); } private void ensureReferencedExist(PDLPacketDecl root, Set<String> declared, PDLItem item, Collection<String> dependencies) throws PDLException { for (String name: dependencies) ensureReferencedExists(root, declared, item, name); } private void verifyPacketReference(PDLItem item, PDLPacketRef packetRef) throws PDLException { if (doc.getPacketDecl(packetRef.getPacketName())==null) error(item, "packet referenced but not declared:"+packetRef.getPacketName()); // TODO ensure packetRef is not inlined } private void verifyMultiplicativeReference(PDLPacketDecl root, Set<String> declared, PDLItem item, PDLMultiplicity multiplicity) throws PDLException { if (multiplicity == null) return; if (multiplicity.getType() == PDLMultiplicity.Type.Variable) ensureReferencedExists(root, declared, item, multiplicity.getVariable()); } private void ensureReferencedExists(PDLPacketDecl root, Set<String> declared, PDLItem item, String name) throws PDLException { if (name == null) throw new NullPointerException("reference name must not be null"); if (!declared.contains(name)) { boolean allowInlining = true; if (root.getName().equals(doc.getStartPacketName())) allowInlining = false; if (allowInlining) { ((PDLPacketDeclImpl) root).setInlined(true); // TODO warn } else { error(item, "item references '"+name+"' before assignment"); } } } private void error(PDLPacketDecl item, String string) throws PDLException { throw new PDLException(string, item); } private void error(PDLItem item, String string) throws PDLException { throw new PDLException(item, string); } }