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 gnu.trove.TIntHashSet; 026 027 import java.awt.event.WindowEvent; 028 import java.sql.SQLException; 029 import java.sql.Timestamp; 030 import java.util.ArrayList; 031 import java.util.Collection; 032 import java.util.Collections; 033 import java.util.HashMap; 034 import java.util.HashSet; 035 import java.util.Iterator; 036 import java.util.Map; 037 import java.util.Set; 038 039 import javax.swing.WindowConstants; 040 import javax.swing.tree.TreeNode; 041 042 import org.mesopotamia.sql.LoadError; 043 import org.mesopotamia.sql.MesopotamiaEngine; 044 045 import biz.hammurapi.cache.Cache; 046 import biz.hammurapi.cache.Entry; 047 import biz.hammurapi.cache.MemoryCache; 048 import biz.hammurapi.cache.Producer; 049 import biz.hammurapi.convert.ConverterClosure; 050 import biz.hammurapi.convert.ConvertingService; 051 import biz.hammurapi.metrics.MeasurementCategory; 052 import biz.hammurapi.metrics.MeasurementCategoryFactory; 053 import biz.hammurapi.sql.IdentityGenerator; 054 import biz.hammurapi.sql.IdentityManager; 055 import biz.hammurapi.sql.IdentityRetriever; 056 import biz.hammurapi.sql.SQLProcessor; 057 import biz.hammurapi.sql.Transaction; 058 import biz.hammurapi.swing.Browser; 059 import biz.hammurapi.swing.Visualizable; 060 import biz.hammurapi.util.Attributable; 061 import biz.hammurapi.util.CollectionVisitable; 062 import biz.hammurapi.util.ConvertingCollection; 063 import biz.hammurapi.util.VisitableBase; 064 import biz.hammurapi.util.Visitor; 065 066 067 public class Scan extends VisitableBase implements Attributable { 068 private static final MeasurementCategory mc=MeasurementCategoryFactory.getCategory(Repository.class); 069 070 private Repository repository; 071 private org.mesopotamia.sql.Scan dbData; 072 073 Object environment; 074 075 private class SourceUnitProducer implements Producer { 076 077 public Entry get(Object key) { 078 Integer sid =(Integer) key; 079 MesopotamiaEngine engine=repository.getFactory().getEngine(); 080 try { 081 org.mesopotamia.sql.SourceUnit dbData=engine.getSourceUnit(sid.intValue()); 082 if (dbData==null) { 083 throw new MesopotamiaRuntimeException("Invalid source unit ID: "+sid); 084 } 085 086 RepositoryLanguage repoLanguage=repository.getFactory().getRepositoryLanguage(new Language(dbData.getLanguage(), dbData.getLanguageVersion(), null)); 087 if (repoLanguage==null) { 088 throw new MesopotamiaRuntimeException("Language not found: "+dbData.getLanguage()+" "+dbData.getLanguageVersion()); 089 } 090 091 final SourceUnit su = repoLanguage.instantiateSourceUnit(dbData, Scan.this); 092 093 return new Entry() { 094 095 public long getExpirationTime() { 096 return 0; 097 } 098 099 public long getTime() { 100 return 0; 101 } 102 103 public Object get() { 104 return su; 105 } 106 107 }; 108 } catch (SQLException e) { 109 throw new MesopotamiaRuntimeException("Cannot instantiate source unit", e); 110 } 111 } 112 113 public void addCache(Cache cache) { 114 // Empty method 115 } 116 117 public Set keySet() { 118 // Empty method 119 return null; 120 } 121 122 } 123 124 private class LanguageElementProducer implements Producer { 125 126 public Entry get(Object key) { 127 LanguageElementHandle lek=(LanguageElementHandle) key; 128 SourceUnit su = getSourceUnit(lek.getSourceUnitId()); 129 if (su==null) { 130 throw new MesopotamiaRuntimeException("Invalid source unit ID: "+lek.getSourceUnitId()); 131 } 132 133 SyntaxTree st = (SyntaxTree) su.getLevelData("ast"); 134 if (st==null) { 135 throw new MesopotamiaRuntimeException("Source unit "+lek.getSourceUnitId()+" is not loaded at level ast"); 136 } 137 138 org.mesopotamia.MesopotamiaNode xData=st.findNode(lek.getId()); 139 if (xData==null) { 140 throw new MesopotamiaRuntimeException("Invalid language element ID: "+lek.getId()); 141 } 142 143 RepositoryLanguage repoLanguage=repository.getFactory().getRepositoryLanguageByTokenTypeId(xData.getType()); 144 if (repoLanguage==null) { 145 throw new MesopotamiaRuntimeException("Language not found for token type id: "+xData.getType()); 146 } 147 148 final LanguageElement le = repoLanguage.instantiateLanguageElement(xData, lek.getContextClass(), lek.getTargetClass(), Scan.this); 149 150 return new Entry() { 151 152 public long getExpirationTime() { 153 return 0; 154 } 155 156 public long getTime() { 157 return 0; 158 } 159 160 public Object get() { 161 return le; 162 } 163 164 }; 165 } 166 167 public void addCache(Cache cache) { 168 // Empty method 169 170 } 171 172 public Set keySet() { 173 // Empty method 174 return null; 175 } 176 177 } 178 179 /** 180 * Repository is instantiated only by RepositoryFactory 181 * @param environment 182 * @param checkSumName 183 * @param selector 184 * @param sourceIterator 185 * @param listener 186 * @param factory 187 * @param language 188 * @param languageVersion 189 * @param name 190 */ 191 Scan( 192 Repository repository, 193 org.mesopotamia.sql.Scan dbData, 194 SourceIterator sourceIterator, 195 LanguageSelector selector, 196 String checkSumAlgorithm, 197 final Object environment, 198 final ScanLoadListener listener) 199 throws MesopotamiaException { 200 this(repository, dbData); 201 this.environment=environment; 202 203 if (listener!=null) { 204 listener.onScanStarted(dbData.getId()); 205 } 206 207 int sourceCounter = 0; 208 209 if (sourceIterator!=null) { 210 Source s; 211 while ((s=sourceIterator.nextSource())!=null) { 212 Language language = selector.select(s); 213 if (language==null) { 214 continue; // No language associated with extension 215 } 216 217 if (loadSource(s, language, checkSumAlgorithm, listener, true)) { 218 ++sourceCounter; 219 } 220 } 221 } 222 223 if (listener!=null) { 224 listener.onScanFinished(dbData.getId(), sourceCounter); 225 } 226 } 227 228 /** 229 * Loads sourceUnit 230 * @param source Source 231 * @param language 232 * @param checkSumAlgorithm 233 * @param expectedLevel 234 * @throws MesopotamiaException 235 * @return true if source is being loaded. 236 */ 237 public boolean loadSource(Source source, Language language, String checkSumAlgorithm, SourceUnitLoadListener listener) throws MesopotamiaException { 238 return loadSource(source, language, checkSumAlgorithm, listener, false); 239 } 240 241 private boolean loadSource(Source source, Language language, String checkSumAlgorithm, SourceUnitLoadListener listener, boolean fromConstructor) throws MesopotamiaException { 242 if (language==null) { 243 return false; 244 } 245 246 final RepositoryLanguage repoLanguage = getRepository().getFactory().getRepositoryLanguage(language); 247 248 if (repoLanguage==null) { 249 return false; 250 } 251 252 if (!fromConstructor) { 253 if (sourceUnitCache!=null) { 254 sourceUnitCache.clear(); 255 } 256 sourceUnits = null; 257 } 258 259 final SourceLink sl=linkSource(repoLanguage, source, checkSumAlgorithm); 260 261 Iterator<LoaderEntry> lit=repoLanguage.getLoaders().iterator(); 262 while (lit.hasNext()) { 263 final LoaderEntry le = lit.next(); 264 final String levelName = le.getLevel(); 265 266 if (sl.loadLevels.contains(le.getId())) { 267 listener.onLink(getId(), sl.sourceId, levelName); 268 } 269 } 270 271 // TODO - in the future analyze which levels are not yet loaded 272 // and invoke load only if there are unloaded levels. 273 // it is done in load() anyway 274 repoLanguage.load(getId(), sl.sourceId, source, environment, listener); 275 276 return true; 277 } 278 279 /** 280 * Unlinks source unit from scan. 281 * @param id 282 * @throws MesopotamiaException 283 */ 284 public void unlinkSourceUnit(int id) throws MesopotamiaException { 285 try { 286 getRepository().getFactory().getEngine().deleteSourceUnitScan(getId(), id); 287 if (sourceUnitCache!=null) { 288 sourceUnitCache.clear(); 289 } 290 sourceUnits = null; 291 } catch (SQLException e) { 292 throw new MesopotamiaException(e); 293 } 294 } 295 296 /** 297 * Repository is instantiated only by RepositoryFactory 298 * @param environment 299 * @param checkSumName 300 * @param selector 301 * @param sourceIterator 302 * @param listener 303 * @param factory 304 * @param language 305 * @param languageVersion 306 * @param name 307 */ 308 Scan(final Repository repository, org.mesopotamia.sql.Scan dbData) { 309 this.repository=repository; 310 this.dbData=dbData; 311 312 languageElementsCache=new MemoryCache( 313 new LanguageElementProducer(), 314 null, 315 mc, 316 repository.getFactory().getTimer(), 317 MemoryCache.CLEANUP_INTERVAL); 318 319 sourceUnitCache=new MemoryCache( 320 new SourceUnitProducer(), 321 null, 322 mc, 323 repository.getFactory().getTimer(), 324 MemoryCache.CLEANUP_INTERVAL); 325 326 sourceUnitLoadLevelsCache=new MemoryCache( 327 new Producer() { 328 329 public Entry get(Object key) { 330 try { 331 final Collection value = repository.getFactory().getEngine().getSourceUnitSuccessfulLoadLevels( 332 ((Number) key).intValue(), 333 getId(), 334 new HashSet(), 335 repository.getFactory().levelIdToIntegerConverter); 336 337 return new Entry() { 338 339 public long getExpirationTime() { 340 return 0; 341 } 342 343 public long getTime() { 344 return 0; 345 } 346 347 public Object get() { 348 return value; 349 } 350 351 }; 352 } catch (SQLException e) { 353 throw new MesopotamiaRuntimeException("Cannol retrieve source unit load levels from the database", e); 354 } 355 } 356 357 public void addCache(Cache cache) { 358 // Empty method 359 } 360 361 public Set keySet() { 362 // Empty method 363 return null; 364 } 365 366 }, 367 null, 368 mc, 369 repository.getFactory().getTimer(), 370 MemoryCache.CLEANUP_INTERVAL); 371 372 } 373 374 private class SourceLink { 375 Source source; 376 int sourceId; 377 public TIntHashSet loadLevels; 378 } 379 380 /** 381 * @param sources 382 * @param digestAlgorithm 383 * @param factory 384 * @throws MesopotamiaException 385 */ 386 private SourceLink linkSource( 387 RepositoryLanguage repoLanguage, 388 Source source, 389 String digestAlgorithm) throws MesopotamiaException { 390 try { 391 final RepositoryFactory factory = repoLanguage.getFactory(); 392 String digest = source.getDigest(factory.getMessageDigest(digestAlgorithm)); 393 MesopotamiaEngine engine = factory.getEngine(); 394 final org.mesopotamia.sql.SourceUnit[] su = {engine.getExistingSourceUnit( 395 repository.getId(), 396 source.getSize(), 397 digest, 398 source.getPath())}; 399 400 TIntHashSet loadLevels=new TIntHashSet(); 401 if (su[0]==null) { 402 // TODO - For changed source set reference to previous revision 403 su[0]=new org.mesopotamia.sql.SourceUnitImpl(true); 404 su[0].setDigestAlgorithm(digestAlgorithm); 405 su[0].setName(source.getName()); 406 su[0].setPath(source.getPath()); 407 su[0].setUnitDigest(digest); 408 su[0].setUnitSize(new Long(source.getSize())); 409 su[0].setLastModified(new Timestamp(source.getLastModified())); 410 su[0].setLanguage(repoLanguage.getName()); 411 su[0].setLanguageVersion(repoLanguage.getVersion()); 412 su[0].setRepositoryId(repository.getId()); 413 414 factory.getProcessor().executeTransaction(new Transaction() { 415 416 public boolean execute(SQLProcessor processor) throws SQLException { 417 IdentityManager identityManager = factory.getIdentityManager(); 418 if (identityManager instanceof IdentityGenerator) { 419 su[0].setId(((IdentityGenerator) identityManager).generate(processor.getConnection(), "REPOSITORY")); 420 } 421 new MesopotamiaEngine(processor).insertSourceUnit(su[0]); 422 if (identityManager instanceof IdentityRetriever) { 423 su[0].setId(((IdentityRetriever) identityManager).retrieve(processor.getConnection())); 424 } 425 426 return true; 427 } 428 429 }); 430 } 431 432 if (engine.getSourceUnitScan(getId(), su[0].getId())==null) { 433 engine.insertSourceUnitScan(getId(), su[0].getId()); 434 } 435 SourceLink sl=new SourceLink(); 436 sl.source=source; 437 sl.sourceId=su[0].getId(); 438 sl.loadLevels=loadLevels; 439 return sl; 440 } catch (SQLException e) { 441 throw new MesopotamiaException(e); 442 } 443 } 444 445 // Should be instantiated by repository 446 // dbData - Scan, expose its accessors through 447 // delegation 448 449 protected void acceptChildren(Visitor visitor) { 450 try { 451 new CollectionVisitable(getSourceUnits(), false).accept(visitor); 452 } catch (MesopotamiaException e) { 453 throw new MesopotamiaRuntimeException(e); 454 } 455 // For languages which do not support namespaces 456 // or where one source unit can contain multiple 457 // namespaces (C++) 458 // iterate through source units 459 // otherwise iterate through namespaces 460 // which will iterate through source units 461 462 // TODO Auto-generated method stub 463 } 464 465 466 467 public String getDescription() { 468 return dbData.getDescription(); 469 } 470 471 472 473 public int getId() { 474 return dbData.getId(); 475 } 476 477 478 479 public Repository getRepository() { 480 return repository; 481 } 482 483 484 485 public Timestamp getScanDate() { 486 return dbData.getScanDate(); 487 } 488 489 490 491 public void setDescription(String description) { 492 dbData.setDescription(description); 493 } 494 495 /** 496 * Deletes scan records from database. 497 * @throws MesopotamiaException 498 */ 499 public void delete() throws MesopotamiaException { 500 MesopotamiaEngine engine = repository.getFactory().getEngine(); 501 try { 502 engine.deleteSourceUnitScanByScan(getId()); 503 engine.deleteScan(getId()); 504 engine.deleteOrphanSourceUnits(repository.getId()); 505 } catch (SQLException e) { 506 throw new MesopotamiaException("Scan detetion failed: "+e, e); 507 } 508 } 509 510 /** 511 * Deletes scan records from database. 512 * @throws SQLException 513 * @throws MesopotamiaException 514 */ 515 public static void delete(int scanId, SQLProcessor processor) throws SQLException { 516 MesopotamiaEngine engine = new MesopotamiaEngine(processor); 517 engine.deleteSourceUnitScanByScan(scanId); 518 org.mesopotamia.sql.Scan scan = engine.getScan(scanId); 519 engine.deleteScan(scanId); 520 engine.deleteOrphanSourceUnits(scan.getRepository()); 521 } 522 523 /** 524 * Memory cache is a front-end for language elment producer. 525 * This approach guarantees that at any point of time there is only one 526 * instance of a particular language element in memory 527 */ 528 private MemoryCache languageElementsCache; 529 530 /** 531 * Memory cache is a front-end for source unit producer. 532 * This approach guarantees that at any point of time there is only one 533 * instance of a particular source unit in memory 534 */ 535 private MemoryCache sourceUnitCache; 536 537 538 public LanguageElement getLanguageElement(LanguageElementHandle handle) { 539 if (handle==null) { 540 return null; 541 } 542 543 Entry e=languageElementsCache.get(handle); 544 LanguageElement ret = (LanguageElement) (e==null ? null : e.get()); 545 // if (ret!=null && ret.getSourceUnit().getId()!=handle.getSourceUnitId()) { 546 // throw new IllegalArgumentException("Handle and return are from different source units, "+handle+", "+ret); 547 // } 548 549 return ret; 550 } 551 552 public SourceUnit getSourceUnit(int sourceUnitId) { 553 Entry e=sourceUnitCache.get(new Integer(sourceUnitId)); 554 return (SourceUnit) (e==null ? null : e.get()); 555 } 556 557 private Collection<SourceUnit> sourceUnits; 558 559 public Collection<SourceUnit> getSourceUnits() throws MesopotamiaException { 560 if (sourceUnits==null) { 561 try { 562 Collection<Object> suIds = repository.getFactory().getEngine().getSourceUnitsInScan( 563 dbData.getId(), 564 new ArrayList(), 565 repository.getFactory().toIntegerConverter); 566 567 sourceUnits=Collections.unmodifiableCollection( 568 new ConvertingCollection( 569 suIds, 570 new ConverterClosure() { 571 572 public Object convert(Object source) { 573 return getSourceUnit(((Number) source).intValue()); 574 } 575 576 }, 577 new ConverterClosure() { 578 579 public Object convert(Object source) { 580 return new Integer(((SourceUnit) source).getId()); 581 } 582 583 })); 584 } catch (SQLException e) { 585 throw new MesopotamiaException("Cannot load source unit id's", e); 586 } 587 588 } 589 return sourceUnits; 590 } 591 592 /** 593 * Shuts down language element and source unit caches 594 */ 595 public void stop() { 596 languageElementsCache.stop(); 597 sourceUnitCache.stop(); 598 sourceUnitLoadLevelsCache.stop(); 599 } 600 601 public Namespace getNamespace(Integer namespaceId) { 602 // TODO Auto-generated method stub 603 return null; 604 } 605 606 private MemoryCache sourceUnitLoadLevelsCache; 607 608 public Collection<Number> getSourceUnitLoadLevels(int id) { 609 return (Collection<Number>) sourceUnitLoadLevelsCache.get(new Integer(id)).get(); 610 } 611 612 613 // load(Object environment, String[] levels) - loads specified levels 614 // delete() 615 // getErrorMessages() 616 // findBySignature(String) 617 618 public String toString() { 619 return getClass().getName()+" "+getId(); 620 } 621 622 private Collection<LoadError> errors; 623 624 public synchronized Collection<LoadError> getErrors() { 625 if (errors==null) { 626 try { 627 errors=Collections.unmodifiableCollection(repository.getFactory().getEngine().getScanLoadError(dbData.getId(), new ArrayList<LoadError>())); 628 } catch (SQLException e) { 629 throw new MesopotamiaRuntimeException("Cannot retrieve load errors: "+e,e); 630 } 631 } 632 633 return errors; 634 } 635 636 /** 637 * Shows scan in browser. Use it for debugging. 638 */ 639 public void show() { 640 Visualizable vs = (Visualizable) ConvertingService.convert(this, Visualizable.class); 641 TreeNode root = vs.toTreeNode(null, "Scan"); 642 643 final Object monitor = new Object(); 644 645 Browser browser = new Browser("Scan "+getId(), root) { 646 647 protected void processWindowEvent(WindowEvent e) { 648 super.processWindowEvent(e); 649 if (e.getID()==WindowEvent.WINDOW_CLOSED) { 650 synchronized (monitor) { 651 monitor.notifyAll(); 652 } 653 } 654 } 655 }; 656 657 browser.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 658 browser.setVisible(true); 659 660 synchronized (monitor) { 661 try { 662 monitor.wait(); 663 } catch (InterruptedException ex) { 664 // Ignore 665 } 666 } 667 668 } 669 670 private Map<Object, Object> attributes = new HashMap<Object, Object>(); 671 672 public Object getAttribute(Object key) { 673 return attributes.get(key); 674 } 675 676 public Object removeAttribute(Object key) { 677 return attributes.remove(key); 678 } 679 680 public void setAttribute(Object key, Object value) { 681 attributes.put(key, value); 682 } 683 684 }