001 /* 002 * mesopotamia @mesopotamia.version@ 003 * Multilingual parser and repository. 004 * Copyright (C) 2005 Hammurapi Group 005 * 006 * This program is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 2 of the License, or (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with this library; if not, write to the Free Software 018 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 019 * 020 * URL: http://http://www.hammurapi.biz 021 * e-Mail: support@hammurapi.biz 022 */ 023 package org.mesopotamia; 024 025 import java.awt.event.WindowEvent; 026 import java.io.StringReader; 027 import java.lang.reflect.Method; 028 import java.util.ArrayList; 029 import java.util.Collections; 030 import java.util.Iterator; 031 import java.util.List; 032 033 import javax.swing.WindowConstants; 034 import javax.swing.tree.TreeNode; 035 036 import org.mesopotamia.util.BnfLexer; 037 import org.mesopotamia.util.BnfRecognizer; 038 import org.mesopotamia.util.BnfTokenTypes; 039 import org.mesopotamia.util.UnmodifiableConvertingList; 040 import org.w3c.dom.Element; 041 042 import antlr.RecognitionException; 043 import antlr.TokenStreamException; 044 import antlr.collections.AST; 045 import biz.hammurapi.convert.ConverterClosure; 046 import biz.hammurapi.convert.ConvertingService; 047 import biz.hammurapi.legacy.review.Signed; 048 import biz.hammurapi.swing.Browser; 049 import biz.hammurapi.swing.Visualizable; 050 import biz.hammurapi.util.VisitableBase; 051 import biz.hammurapi.xml.dom.DomSerializable; 052 053 /** 054 * Generic wrapper for AST. 055 * More sophisticated classes (e.g. try block representation) 056 * shall extend this class. 057 * @author Pavel Vlasov 058 * @revision $Revision: 1.2 $ 059 */ 060 public class LanguageElement extends VisitableBase implements DomSerializable, Signed, Comparable<LanguageElement> { 061 062 private Scan scan; 063 private NodeData xData; 064 private RepositoryLanguage language; 065 protected Object environment; 066 private LanguageElementHandle handle; 067 068 /** 069 * Instances of LanguageElement shall be created only by 070 * repository 071 */ 072 protected LanguageElement(NodeData xData, Class<?> context, Scan scan, RepositoryLanguage language, Object environment) { 073 this.scan=scan; 074 try { 075 this.xData=(NodeData) xData.clone(); 076 } catch (CloneNotSupportedException e) { 077 throw new MesopotamiaRuntimeException(e); 078 } 079 if (xData instanceof MesopotamiaNode && ((MesopotamiaNode) xData).getParent()!=null) { 080 this.setParentHandle(new LanguageElementHandle(xData.getSourceUnitId(), ((MesopotamiaNode) xData).getParent().getId(), null, null)); 081 } 082 this.language=language; 083 this.environment=environment; 084 this.handle=new LanguageElementHandle(xData.getSourceUnitId(), xData.getId(), context, getClass()); 085 } 086 087 private LanguageElementHandle parentHandle; 088 089 public void setParentHandle(LanguageElementHandle parentHandle) { 090 if (parentHandle!=null) { 091 if (parentHandle.getId()==xData.getId()) { 092 throw new IllegalArgumentException("Self parent"); 093 } 094 if (parentHandle.getSourceUnitId()!=xData.getSourceUnitId()) { 095 throw new IllegalArgumentException("Parent from different source unit, this "+xData+", parent "+parentHandle); 096 } 097 } 098 099 this.parentHandle = parentHandle; 100 } 101 102 public LanguageElement getParent() { 103 return parentHandle==null ? null : scan.getLanguageElement(parentHandle); 104 } 105 106 public RepositoryLanguage getLanguage() { 107 return language; 108 } 109 110 public int getType() { 111 return language.tokenTypeId2type(xData.getType()); 112 } 113 114 private List<Object> childrenData; // Collection of NodeData 115 private List<Object> children; 116 117 /** 118 * Language element should not keep references to children, but a 119 * collection of children's primary keys (java.lang.Integer) and 120 * use converting collection (biz.hammurapi.util.ConvertingCollection), 121 * which would use Repository's languageElementCache to look up child instance. 122 * @return 123 * @throws MesopotamiaException 124 */ 125 public synchronized List<Object> getChildren() throws MesopotamiaException { 126 if (childrenData==null) { 127 childrenData = new ArrayList<Object>(); 128 Iterator<MesopotamiaNode> it = getNode().getChildren().iterator(); 129 while (it.hasNext()) { 130 try { 131 childrenData.add(it.next().clone()); 132 } catch (CloneNotSupportedException e) { 133 throw new MesopotamiaRuntimeException(e); 134 } 135 } 136 137 children=new UnmodifiableConvertingList<Object>(childrenData,data2leConverter); 138 } 139 return children; 140 } 141 142 public MesopotamiaNode getNode() { 143 return getSourceUnit().getSyntaxTree().findNode(xData.getId()); 144 } 145 146 /** 147 * Convenience method 148 * @param idx 149 * @return 150 * @throws MesopotamiaException 151 */ 152 public LanguageElement getChild(int idx) throws MesopotamiaException { 153 List<Object> ch = getChildren(); 154 return ch.size()>idx ? (LanguageElement) ch.get(idx) : null; 155 } 156 157 /** 158 * Convenience method 159 * @param idx 160 * @return 161 * @throws MesopotamiaException 162 */ 163 public int getChildId(int idx) throws MesopotamiaException { 164 return getChildren().size()>idx ? ((NodeData) childrenData.get(idx)).getId() : 0; 165 } 166 167 protected ConverterClosure data2leConverter=new ConverterClosure() { 168 169 public Object convert(Object source) { 170 NodeData nd = (NodeData) source; 171 LanguageElementHandle childHandle = new LanguageElementHandle(nd.getSourceUnitId(), nd.getId(), LanguageElement.this.getClass(), null); 172 LanguageElement child = scan.getLanguageElement(childHandle); 173 child.setParentHandle(LanguageElement.this.handle); 174 return child; 175 } 176 177 }; 178 179 protected ConverterClosure le2idConverter=new ConverterClosure() { 180 181 public Object convert(Object source) { 182 return new Integer(((LanguageElement) source).getId()); 183 } 184 185 }; 186 187 public LanguageElement getPrevSibling() { 188 return getSibling(-1); 189 } 190 191 public LanguageElement getNextSibling() { 192 return getSibling(+1); 193 } 194 195 public LanguageElement getSibling(int offset) { 196 SyntaxTree st = getSourceUnit().getSyntaxTree(); 197 List<MesopotamiaNode> siblings = parentHandle==null ? st.getRoots() : st.findNode(parentHandle.getId()).getChildren(); 198 int idx = xData.getPosition()+offset; 199 if (idx<0 || idx>=siblings.size()) { 200 return null; 201 } 202 NodeData sibling = siblings.get(idx); 203 if (sibling==null) { 204 return null; 205 } 206 LanguageElement parent=getParent(); 207 LanguageElementHandle siblingHandle=new LanguageElementHandle( 208 xData.getSourceUnitId(), 209 sibling.getId(), 210 parent==null ? null : getParent().getClass(), 211 null); 212 return scan.getLanguageElement(siblingHandle); 213 } 214 215 // public Long getChecksum() { 216 // return dbData.getChecksum(); 217 // } 218 219 public int getCol() { 220 return xData.getColumn(); 221 } 222 223 // public Token getFirstToken() { 224 // return xData.getFirstTokenId()==null ? null : getSourceUnit().getToken(dbData.getFirstTokenId().intValue()); 225 // } 226 227 public int getId() { 228 return xData.getId(); 229 } 230 231 // public Token getLastToken() { 232 // return dbData.getLastTokenId()==null ? null : getSourceUnit().getToken(dbData.getLastTokenId().intValue()); 233 // } 234 // 235 // public Token getToken() { 236 // return dbData.getTokenId()==null ? null : getSourceUnit().getToken(dbData.getTokenId().intValue()); 237 // } 238 239 public int getLine() { 240 return xData.getLine(); 241 } 242 243 /** 244 * Change to lazy calculation once in the future. 245 * @return 246 */ 247 public String getSignature() { 248 StringBuffer ret=new StringBuffer(); 249 LanguageElement parent = getParent(); 250 if (parent==this) { 251 /* 252 * There might be cases where signature is not applicable. 253 * We use element ID and location in this case. This signature is not as good as 254 * regular signature, but there is no better way. 255 */ 256 return getSourceUnit().getPath()+":ID:"+getId(); 257 } 258 if (parent==null) { 259 ret.append(getSourceUnit().getPath()); 260 } else { 261 ret.append(parent.getSignature()); 262 } 263 ret.append(":"); 264 ret.append(language.tokenTypeId2name(xData.getType())); 265 // if (xData.getSignatureId()!=null) { 266 // ret.append("["); 267 // ret.append(getText(dbData.getSignatureId())); 268 // ret.append("]"); 269 // } else { 270 if (xData.getSameTypeIndex()!=0) { 271 ret.append("["); 272 ret.append(xData.getSameTypeIndex()); 273 ret.append("]"); 274 } 275 // } 276 return ret.toString(); 277 } 278 279 // /** 280 // * Retrieves text from string storage 281 // * @return 282 // */ 283 // protected String getText(Integer id) { 284 // return scan.getRepository().getFactory().getStringStorage().getText(id); 285 // } 286 287 public AstSourceUnit getSourceUnit() { 288 return (AstSourceUnit) scan.getSourceUnit(xData.getSourceUnitId()); 289 } 290 291 /** 292 * @return Language element text 293 */ 294 public String getText() { 295 return xData.getText(); //getText(dbData.getTextId()); 296 } 297 298 // public Token getToken() throws MesopotamiaException { 299 // // It would be too cumbersome to keep tokens in memory cache - instantiate them 300 // // directly. It can be changed in the future anyway. 301 // if (dbData.getTokenId()==null) { 302 // return null; 303 // } 304 // 305 // try { 306 // return new Token(getSourceUnit(), repository.getFactory().getEngine().getToken(dbData.getSourceUnitId(), dbData.getTokenId().intValue())); 307 // } catch (SQLException e) { 308 // throw new MesopotamiaException(e); 309 // } 310 // } 311 312 // public int getSize() { 313 // return dbData.getTotalSize(); 314 // } 315 316 public void toDom(Element holder) { 317 holder.setAttribute("id",String.valueOf(xData.getId())); 318 holder.setAttribute("column",String.valueOf(xData.getColumn())); 319 holder.setAttribute("line",String.valueOf(xData.getLine())); 320 holder.setAttribute("token-type",language.tokenTypeId2name(xData.getType())); 321 holder.setAttribute("class",getClass().getName()); 322 setAttribute(holder, "text",getText()); 323 // Token token = getToken(); 324 // if (token!=null) { 325 // holder.setAttribute("token-position", String.valueOf(token.getPosition())); 326 // } 327 } 328 329 /** 330 * Token name, e.g. METHOD_CALL 331 * @return 332 */ 333 public String getTokenName() { 334 return language.tokenTypeId2name(xData.getType()); 335 } 336 337 // protected org.mesopotamia.sql.LanguageElement[] getDbChildrenByType(int type) throws MesopotamiaException { 338 // getChildren(); 339 // Collection ret=new ArrayList(); 340 // Iterator it=childrenIds.iterator(); 341 // int typeId=language.tokenType2id(type); 342 // while (it.hasNext()) { 343 // org.mesopotamia.MesopotamiaNodedbLe=(org.mesopotamia.sql.LanguageElement) it.next(); 344 // if (dbLe.getTokenTypeId()==typeId) { 345 // ret.add(dbLe); 346 // } 347 // } 348 // return (org.mesopotamia.sql.LanguageElement[]) ret.toArray(new org.mesopotamia.sql.LanguageElement[ret.size()]); 349 // } 350 351 protected Scan getScan() { 352 return scan; 353 } 354 355 /** 356 * Selects collection of language elements based on path expression. 357 * @param context 358 * @param path 359 * @return List of language elements 360 */ 361 public <T> UnmodifiableConvertingList<T> select(final Class<T> targetClass, String path) throws MesopotamiaException { 362 try { 363 StringReader sr=new StringReader(path); 364 BnfLexer lexer=new BnfLexer(sr); 365 BnfRecognizer parser=new BnfRecognizer(lexer); 366 parser.standaloneIndexedPath(); 367 List<NodeData> results=new ArrayList<NodeData>(); 368 //System.out.println("=== Selecting: "+path); 369 //AstUtil.dump(parser.getAST(), parser.getTokenNames()); 370 for (AST pe=parser.getAST(); pe!=null; pe=pe.getNextSibling()) { 371 List<NodeData> peResults=new ArrayList<NodeData>(); 372 peResults.add(xData); 373 for (AST node=pe.getFirstChild(); node!=null; node=node.getNextSibling()) { 374 peResults=processNode(peResults, node.getFirstChild()); 375 } 376 results.addAll(peResults); 377 } 378 379 return new UnmodifiableConvertingList<T>( 380 results, 381 new ConverterClosure() { 382 383 public Object convert(Object source) { 384 NodeData nd = (NodeData) source; 385 LanguageElementHandle handle = new LanguageElementHandle(nd.getSourceUnitId(), nd.getId(), LanguageElement.this.getClass(), targetClass); 386 LanguageElement selectedChild = scan.getLanguageElement(handle); 387 selectedChild.setParentHandle(LanguageElement.this.handle); 388 return selectedChild; 389 } 390 391 }); 392 393 } catch (RecognitionException e) { 394 throw new MesopotamiaException("Cannot parse path: "+path, e); 395 } catch (TokenStreamException e) { 396 throw new MesopotamiaException("Cannot parse path: "+path, e); 397 } catch (ClassNotFoundException e) { 398 throw new MesopotamiaException("Cannot process selection from path: "+path, e); 399 } 400 } 401 402 private List<NodeData> processNode(List<NodeData> input, AST node) throws MesopotamiaException, ClassNotFoundException { 403 //System.out.println("--- Processing: "+BnfRecognizer._tokenNames[node.getType()]); 404 SyntaxTree st = getSourceUnit().getSyntaxTree(); 405 List<NodeData> ret=new ArrayList<NodeData>(); 406 Iterator<NodeData> it=input.iterator(); 407 while (it.hasNext()) { 408 List<NodeData> receiver=new ArrayList<NodeData>(); 409 MesopotamiaNode contextNode = st.findNode(it.next().getId()); 410 switch (node.getType()) { 411 case BnfTokenTypes.IDENT: { 412 Iterator<MesopotamiaNode> cit = contextNode.getChildren().iterator(); 413 while (cit.hasNext()) { 414 NodeData cnd = cit.next(); 415 checkSameSourceUnit(cnd); 416 if (cnd.getType()==language.tokenName2id(node.getText())) { 417 receiver.add(cnd); 418 } 419 } 420 break; 421 } 422 case BnfTokenTypes.EXCL: { 423 Iterator<MesopotamiaNode> cit = contextNode.getChildren().iterator(); 424 while (cit.hasNext()) { 425 NodeData cnd = cit.next(); 426 checkSameSourceUnit(cnd); 427 if (cnd.getType()!=language.tokenName2id(node.getFirstChild().getText())) { 428 receiver.add(cnd); 429 } 430 } 431 break; 432 } 433 case BnfTokenTypes.STAR: 434 receiver.addAll(contextNode.getChildren()); 435 break; 436 case BnfTokenTypes.DOUBLEDOT: 437 throw new UnsupportedOperationException("Not yet implemented"); 438 case BnfTokenTypes.TYPE_REF: 439 StringBuffer typeName=new StringBuffer(); 440 typeName(node.getFirstChild(), typeName); 441 Class<?> type=language.getClassLoader().loadClass(typeName.toString()); 442 Iterator<MesopotamiaNode> lit=contextNode.getChildren().iterator(); 443 while (lit.hasNext()) { 444 NodeData cnd = lit.next(); 445 checkSameSourceUnit(cnd); 446 LanguageElement cle=scan.getLanguageElement(new LanguageElementHandle(cnd.getSourceUnitId(), cnd.getId(), this.getClass(), null)); 447 if (type.isInstance(cle)) { 448 receiver.add(cnd); 449 } 450 } 451 452 break; 453 default: 454 throw new MesopotamiaException("Unexpected node type: "+BnfRecognizer._tokenNames[node.getType()]); 455 } 456 457 if (node.getNextSibling()!=null) { 458 int idx=Integer.parseInt(node.getNextSibling().getFirstChild().getText()); 459 if (idx>=0 && idx<receiver.size()) { 460 ret.add(receiver.get(idx)); 461 } 462 } else { 463 ret.addAll(receiver); 464 } 465 } 466 return ret; 467 } 468 469 private void checkSameSourceUnit(NodeData cnd) { 470 if (cnd.getSourceUnitId()!=xData.getSourceUnitId()) { 471 throw new IllegalStateException("Nodes from different source units: "+xData+", "+cnd); 472 } 473 474 } 475 476 static void typeName(AST node, StringBuffer sb) { 477 if (node.getType()==BnfTokenTypes.DOT) { 478 typeName(node.getFirstChild(), sb); 479 sb.append("."); 480 typeName(node.getFirstChild().getNextSibling(), sb); 481 } else { 482 sb.append(node.getText()); 483 } 484 } 485 486 /** 487 * Selects single language element handle based on path expression 488 * @param context 489 * @param path 490 * @return 491 * @throws MesopotamiaException 492 */ 493 public LanguageElementHandle selectSingleElementHandle(Class targetClass, String path) throws MesopotamiaException { 494 UnmodifiableConvertingList<LanguageElement> c=select(targetClass, path); 495 if (c.isEmpty()) { 496 return null; 497 } 498 NodeData nd = (NodeData) c.getMaster().get(0); 499 return new LanguageElementHandle(nd.getSourceUnitId(), nd.getId(), getClass(), targetClass); 500 } 501 502 /** 503 * Selects single language element based on path expression 504 * @param context 505 * @param path 506 * @return 507 * @throws MesopotamiaException 508 */ 509 public <T> T selectSingleElement(Class<T> targetClass, String path) throws MesopotamiaException { 510 List<T> c=select(targetClass, path); 511 return (T) (c.isEmpty() ? null : c.get(0)); 512 } 513 514 public <T> String selectSingleElementText(Class<T> targetClass, String path) throws MesopotamiaException { 515 T le=selectSingleElement(targetClass, path); 516 return le instanceof LanguageElement ? ((LanguageElement) le).getText() : null; 517 } 518 519 public List<String> selectText(Class targetClass, String path) throws MesopotamiaException { 520 List<String> ret=new ArrayList<String>(); 521 Iterator<LanguageElement> it=select(targetClass, path).iterator(); 522 while (it.hasNext()) { 523 ret.add(it.next().getText()); 524 } 525 return ret; 526 } 527 528 /** 529 * Sets attribute if value is not null. 530 * @param holder 531 * @param name 532 * @param value 533 */ 534 protected void setAttribute(Element holder, String name, String value) { 535 if (value!=null) { 536 holder.setAttribute(name, value); 537 } 538 } 539 540 /** 541 * Adds nested element if value is not null 542 * @param holder 543 * @param name 544 * @param value 545 */ 546 protected void setElement(Element holder, String name, Object value) { 547 if (value!=null) { 548 Element el=holder.getOwnerDocument().createElement(name); 549 holder.appendChild(el); 550 DomSerializable ds = (DomSerializable) ConvertingService.convert(value, DomSerializable.class); 551 ds.toDom(el); 552 } 553 } 554 555 /** 556 * Creates a converting list of language elements. Use this method to construct 557 * custom lists of language element children. 558 * @param idList 559 * @param targetType 560 * @return 561 */ 562 protected List<LanguageElement> createLanguageElementsList(List<Object> nodeDataList, final Class targetType) { 563 return new UnmodifiableConvertingList<LanguageElement>( 564 nodeDataList, 565 new ConverterClosure() { 566 567 public Object convert(Object source) { 568 NodeData nd = (NodeData) source; 569 LanguageElementHandle leHandle = new LanguageElementHandle( 570 nd.getSourceUnitId(), 571 nd.getId(), 572 LanguageElement.this.getClass(), 573 targetType); 574 575 LanguageElement ret = getScan().getLanguageElement(leHandle); 576 ret.setParentHandle(handle); 577 return ret; 578 } 579 580 }); 581 } 582 583 /** 584 * @return Language element handle. 585 */ 586 public LanguageElementHandle getHandle() { 587 return handle; 588 } 589 590 /** 591 * For sorting. 592 */ 593 public int compareTo(LanguageElement otherElement) { 594 int pathComparison=getSourceUnit().getPath().compareTo(otherElement.getSourceUnit().getPath()); 595 if (pathComparison==0) { 596 int lineComparison=getLine()-otherElement.getLine(); 597 if (lineComparison==0) { 598 return getCol()-otherElement.getCol(); 599 } 600 return lineComparison; 601 } 602 return pathComparison; 603 } 604 605 public String getLocation() { 606 return getSourceUnit().getPath()+" "+getLine()+":"+getCol(); 607 } 608 609 public String toString() { 610 return "["+getClass().getName()+"] "+getTokenName()+" '"+getText()+"' "+getLocation(); 611 } 612 613 // Empty list to initialize list attributes 614 @SuppressWarnings("unchecked") 615 protected static final UnmodifiableConvertingList emptyList= 616 new UnmodifiableConvertingList(Collections.unmodifiableList(new ArrayList()), null); 617 618 /** 619 * Shows element in browser. Use it for debugging. 620 */ 621 public void show() { 622 Visualizable vs = (Visualizable) ConvertingService.convert(this, Visualizable.class); 623 String nName = getClass().getName(); 624 int idx = nName.lastIndexOf("."); 625 String cName = idx==-1 ? nName : nName.substring(idx+1); 626 TreeNode root = vs.toTreeNode(null, cName); 627 628 final Object monitor = new Object(); 629 630 Browser browser = new Browser(cName+" "+getId()+" at "+getLocation(), root) { 631 632 protected void processWindowEvent(WindowEvent e) { 633 super.processWindowEvent(e); 634 if (e.getID()==WindowEvent.WINDOW_CLOSED) { 635 synchronized (monitor) { 636 monitor.notifyAll(); 637 } 638 } 639 } 640 }; 641 642 browser.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 643 browser.setVisible(true); 644 645 synchronized (monitor) { 646 try { 647 monitor.wait(); 648 } catch (InterruptedException ex) { 649 // Ignore 650 } 651 } 652 } 653 654 /** 655 * Navigates up the tree looking for parent of particular type. 656 * @param clazz Type of parent to be found. 657 * @return Parent of type specified in <code>clazz</code> parameter. 658 */ 659 @SuppressWarnings("unchecked") 660 public <T> T findParent(Class<T> clazz) { 661 LanguageElement parent = getParent(); 662 663 if (clazz.isInstance(parent) && parent != this && parent.getId()!=this.getId()) { 664 return (T) parent; 665 } 666 667 if (parent!=null && parent != this && parent.getId()!=this.getId()) { 668 LanguageElement ret = (LanguageElement) parent.findParent(clazz); 669 if (ret!=null) { 670 return (T) ret; 671 } 672 } 673 674 // Finding using syntax tree 675 for (MesopotamiaNode parentNode = getNode().getParent(); parentNode!=null && parentNode.getParent()!=parentNode; parentNode = parentNode.getParent()) { 676 LanguageElementHandle lh=new LanguageElementHandle(parentNode.getSourceUnitId(), parentNode.getId(), null, null); 677 LanguageElement pe = scan.getLanguageElement(lh); 678 if (clazz.isInstance(pe) && pe != this && pe.getId()!=this.getId()) { 679 return (T) pe; 680 } 681 } 682 return null; 683 } 684 685 /** 686 * Override it to return false for methods which shall not be 687 * mounted to visualizer tree, i.e. methods which return references 688 * to other language elements. 689 * @return true if method return value shall be shown in the visualizer tree. 690 */ 691 public boolean showInTree(Method method) { 692 return method!=null && !method.getDeclaringClass().equals(LanguageElement.class) && LanguageElement.class.isAssignableFrom(method.getDeclaringClass()); 693 } 694 695 }