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 import gnu.trove.TIntIntHashMap; 027 import gnu.trove.TIntObjectHashMap; 028 import gnu.trove.TObjectIntHashMap; 029 030 import java.sql.ResultSet; 031 import java.sql.SQLException; 032 import java.util.ArrayList; 033 import java.util.Collection; 034 import java.util.Collections; 035 import java.util.Comparator; 036 import java.util.HashMap; 037 import java.util.HashSet; 038 import java.util.Iterator; 039 import java.util.List; 040 import java.util.Map; 041 import java.util.Set; 042 import java.util.logging.Logger; 043 044 import org.mesopotamia.sql.LanguageElementClassImpl; 045 import org.mesopotamia.sql.SourceUnitClassImpl; 046 import org.mesopotamia.sql.TokenType; 047 048 import biz.hammurapi.sql.RowProcessor; 049 import biz.hammurapi.sql.SQLExceptionEx; 050 051 /** 052 * Language defined in repository. 053 * @author Pavel Vlasov 054 * @revision $Revision$ 055 */ 056 public class RepositoryLanguage extends Language { 057 private static final Logger logger = Logger.getLogger(RepositoryLanguage.class.getName()); 058 059 private RepositoryFactory factory; 060 private ClassLoader classLoader; 061 062 public ClassLoader getClassLoader() { 063 return classLoader; 064 } 065 066 // public RepositoryLanguage(String name, String version, String description) { 067 // super(name, version, description); 068 // // TODO Auto-generated constructor stub 069 // } 070 071 public RepositoryLanguage(final RepositoryFactory factory, org.mesopotamia.sql.Language data, ClassLoader classLoader) throws SQLException { 072 super(data); 073 this.factory=factory; 074 this.classLoader=classLoader; 075 // TODO Read path elements from database table and construct 076 // TODO classloader if there are any. It will allow to keep language module 077 // TODO jars centrally. 078 079 Iterator it=factory 080 .getEngine() 081 .getTokenTypeByLanguage( 082 dbData.getName(), 083 dbData.getVersion()) 084 .iterator(); 085 086 while (it.hasNext()) { 087 TokenType tt = (org.mesopotamia.sql.TokenType) it.next(); 088 type2id.put(tt.getTokenType(), tt.getId()); 089 id2type.put(tt.getId(), tt.getTokenType()); 090 name2id.put(tt.getTokenName(), tt.getId()); 091 id2name.put(tt.getId(), tt.getTokenName()); 092 if (tt.getIsWhitespace()) { 093 whitespaces.add(tt.getId()); 094 } 095 096 final Collection<LanguageElementClassEntry> lecs=new ArrayList<LanguageElementClassEntry>(); 097 factory.getEngine().processLanguageElementClassByTokenType( 098 tt.getId(), 099 new RowProcessor() { 100 public boolean process(ResultSet rs) throws SQLException { 101 lecs.add(new LanguageElementClassEntry(rs)); 102 return true; 103 } 104 105 }); 106 languageElementClasses.put(tt.getId(), lecs); 107 } 108 109 loadLoaders(); 110 111 factory.getEngine().processSourceUnitClassByLanguage( 112 getName(), 113 getVersion(), 114 new RowProcessor() { 115 116 public boolean process(ResultSet rs) throws SQLException { 117 sourceUnitClasses.add(new SourceUnitClassEntry(rs)); 118 return true; 119 } 120 121 }); 122 } 123 124 private List<LoaderEntry> loaders; 125 126 /** 127 * @return loaders ordered by dependency 128 */ 129 List<LoaderEntry> getLoaders() { 130 return loaders; 131 } 132 133 private Map<String, Loader> levelMap = new HashMap<String, Loader>(); 134 135 public Loader getLoader(String level) { 136 return levelMap.get(level); 137 } 138 139 TIntObjectHashMap loadersMap=new TIntObjectHashMap(); 140 141 /** 142 * @param language 143 * @return List of loaders ordered by dependency 144 * @throws SQLException 145 * @throws MesopotamiaException If loaders cannot be retrieved from the database or there is a circular dependency between 146 * loaders 147 */ 148 private void loadLoaders() throws SQLException { 149 final List<LoaderEntry> ldrs=new ArrayList<LoaderEntry>(); 150 factory.getEngine().processLoaderByLanguage( 151 getName(), 152 getVersion(), 153 new RowProcessor() { 154 155 public boolean process(ResultSet rs) throws SQLException { 156 LoaderEntry loaderEntry = new LoaderEntry(RepositoryLanguage.this, rs); 157 ldrs.add(loaderEntry); 158 loadersMap.put(loaderEntry.getId(), loaderEntry); 159 levelMap.put(loaderEntry.getLevel(), loaderEntry.getLoader()); 160 return true; 161 } 162 163 }); 164 165 // Sorting loaders by dependency 166 Collections.sort( 167 ldrs, 168 new Comparator<LoaderEntry>() { 169 170 public int compare(LoaderEntry o1, LoaderEntry o2) { 171 if (o1==o2) { 172 return 0; 173 } 174 175 LoaderEntry le1=(LoaderEntry) o1; 176 LoaderEntry le2=(LoaderEntry) o2; 177 if (le1.dependsOn(le2.getId(), le2.getId())) { 178 return 1; 179 } 180 if (le2.dependsOn(le1.getId(), le1.getId())) { 181 return -1; 182 } 183 return le1.getId()-le2.getId(); 184 } 185 186 }); 187 188 loaders=Collections.unmodifiableList(ldrs); 189 } 190 191 public RepositoryFactory getFactory() { 192 return factory; 193 } 194 195 private TIntIntHashMap type2id=new TIntIntHashMap(); 196 private TIntIntHashMap id2type=new TIntIntHashMap(); 197 private TObjectIntHashMap name2id=new TObjectIntHashMap(); 198 private TIntObjectHashMap id2name=new TIntObjectHashMap(); 199 private TIntHashSet whitespaces = new TIntHashSet(); 200 201 /** 202 * Converts token type as defined in grammar to token type id as stored in repository 203 * @param tokenType 204 * @return 205 */ 206 public int tokenType2id(int tokenType) { 207 return type2id.get(tokenType); 208 } 209 210 public boolean isWhitespace(int typeId) { 211 return whitespaces.contains(typeId); 212 } 213 214 /** 215 * Converts token type id as defined in repository to token type as defined in grammar. 216 * @param tokenTypeId 217 * @return 218 */ 219 public int tokenTypeId2type(int tokenTypeId) { 220 return id2type.get(tokenTypeId); 221 } 222 223 public int tokenName2id(String tokenName) throws MesopotamiaException { 224 if (supportsTokenName(tokenName)) { 225 return name2id.get(tokenName); 226 } 227 throw new MesopotamiaException("Invalid token name: "+tokenName); 228 } 229 230 /** 231 * @param tokenName 232 * @return 233 */ 234 public boolean supportsTokenName(String tokenName) { 235 return name2id.containsKey(tokenName); 236 } 237 238 /** 239 * Converts token type id as defined in repository to token type as defined in grammar. 240 * @param tokenTypeId 241 * @return 242 */ 243 public String tokenTypeId2name(int tokenTypeId) { 244 return (String) id2name.get(tokenTypeId); 245 } 246 247 private class SourceUnitClassEntry extends SourceUnitClassImpl implements Comparable { 248 private Set requiredLevels=new HashSet(); 249 private Class sourceUnitClass; 250 251 public SourceUnitClassEntry(ResultSet rs) throws SQLException { 252 super(rs); 253 try { 254 sourceUnitClass=classLoader.loadClass(getClassName()); 255 } catch (ClassNotFoundException e) { 256 throw new SQLExceptionEx("Cannot load source unit class "+getClassName(), e); 257 } 258 if (!SourceUnit.class.isAssignableFrom(sourceUnitClass)) { 259 throw new SQLException(sourceUnitClass.getName()+" must be a subclass of "+SourceUnit.class.getName()); 260 } 261 factory.getEngine().getSourceUnitClassRequiredLevels( 262 getId(), 263 requiredLevels, 264 factory.toIntegerConverter); 265 } 266 267 boolean isCompatible(Collection loadedLevels) { 268 return loadedLevels.containsAll(requiredLevels); 269 } 270 271 public int compareTo(Object o) { 272 if (o==this) { 273 return 0; 274 } 275 276 if (o instanceof SourceUnitClassEntry) { 277 SourceUnitClassEntry s = (SourceUnitClassEntry) o; 278 if (sourceUnitClass.equals(s.sourceUnitClass)) { 279 throw new IllegalStateException("Two entries with the same class "+sourceUnitClass.getName()); 280 } 281 282 if (sourceUnitClass.isAssignableFrom(s.sourceUnitClass)) { 283 return 1; 284 } 285 286 if (s.sourceUnitClass.isAssignableFrom(sourceUnitClass)) { 287 return -1; 288 } 289 290 throw new IllegalStateException("Classes "+sourceUnitClass.getName()+" and "+s.sourceUnitClass.getName()+" do not belong to the same inheritance hierarchy"); 291 } 292 293 throw new IllegalArgumentException("Cannot compare "+this.getClass().getName()+" with "+o.getClass().getName()); 294 } 295 } 296 297 private Collection<SourceUnitClassEntry> sourceUnitClasses=new ArrayList<SourceUnitClassEntry>(); 298 299 private class LanguageElementClassEntry extends LanguageElementClassImpl implements Comparable { 300 private Set<Number> requiredLevels=new HashSet<Number>(); 301 private Class languageElementClass; 302 private Class contextClass; 303 304 public LanguageElementClassEntry(ResultSet rs) throws SQLException { 305 super(rs); 306 try { 307 languageElementClass=classLoader.loadClass(getClassName()); 308 } catch (ClassNotFoundException e) { 309 throw new SQLExceptionEx("Cannot load language element class "+getClassName(), e); 310 } 311 312 if (!(LanguageElement.class.isAssignableFrom(languageElementClass) || LanguageElementFactory.class.isAssignableFrom(languageElementClass))) { 313 throw new SQLException(languageElementClass.getName()+" must be a subclass of "+LanguageElement.class.getName()+" or implement "+LanguageElementFactory.class); 314 } 315 316 if (getContextClass()!=null) { 317 try { 318 contextClass=classLoader.loadClass(getContextClass()); 319 } catch (ClassNotFoundException e) { 320 throw new SQLExceptionEx("Cannot load context class "+getContextClass(), e); 321 } 322 } 323 324 factory.getEngine().getLanguageElementClassRequiredLevels( 325 getId(), 326 requiredLevels, 327 factory.toIntegerConverter); 328 } 329 330 boolean isCompatible(Class actualContextClass, Class targetClass, Collection loadedLevels, boolean noEnvironment) { 331 return !(getRequiresEnvironment() && noEnvironment) 332 && (this.contextClass==null || (actualContextClass!=null && this.contextClass.isAssignableFrom(actualContextClass))) 333 && loadedLevels.containsAll(requiredLevels) 334 && (targetClass==null || targetClass.isAssignableFrom(languageElementClass) || LanguageElementFactory.class.isAssignableFrom(languageElementClass)); 335 } 336 337 public int compareTo(Object o) { 338 if (o==this) { 339 return 0; 340 } 341 342 if (o instanceof LanguageElementClassEntry) { 343 LanguageElementClassEntry l = (LanguageElementClassEntry) o; 344 if (languageElementClass.equals(l.languageElementClass)) { 345 logger.warning("Two entries with the same class "+languageElementClass.getName()); 346 return 0; 347 } 348 349 if (languageElementClass.isAssignableFrom(l.languageElementClass)) { 350 return 1; 351 } 352 353 if (l.languageElementClass.isAssignableFrom(languageElementClass)) { 354 return -1; 355 } 356 357 String myContextClass = getContextClass(); 358 String hisContextClass = l.getContextClass(); 359 360 if (isBlank(myContextClass)) { 361 if (!isBlank(hisContextClass)) { 362 return 1; 363 } 364 } else { 365 if (isBlank(hisContextClass)) { 366 return -1; 367 } 368 } 369 370 // TODO - Compare contexts hierarchy. 371 372 logger.warning("Ambiguous language element class: "+languageElementClass.getName()+" and "+l.languageElementClass.getName()); 373 return languageElementClass.getName().compareTo(l.languageElementClass.getName()); // Alphabetially, though it is bad. 374 } 375 376 throw new IllegalArgumentException("Cannot compare "+this.getClass().getName()+" with "+o.getClass().getName()); 377 } 378 } 379 380 private static boolean isBlank(String str) { 381 return str==null || str.trim().length()==0; 382 } 383 384 private TIntObjectHashMap languageElementClasses=new TIntObjectHashMap(); 385 386 SourceUnit instantiateSourceUnit(org.mesopotamia.sql.SourceUnit dbData, Scan scan) { 387 try { 388 Collection<Number> loadLevels=factory.getEngine().getSourceUnitSuccessfulLoadLevels( 389 dbData.getId(), 390 scan.getId(), 391 new HashSet<Number>(), 392 factory.levelIdToIntegerConverter); 393 394 SourceUnitClassEntry suce=null; 395 Iterator<SourceUnitClassEntry> it=sourceUnitClasses.iterator(); 396 while (it.hasNext()) { 397 SourceUnitClassEntry candidate=it.next(); 398 if (candidate.isCompatible(loadLevels) && (suce==null || candidate.compareTo(suce)<0)) { 399 suce=candidate; 400 } 401 } 402 403 if (suce==null) { 404 return new SourceUnit(dbData, scan, this, Collections.unmodifiableCollection(loadLevels)); 405 } 406 407 return (SourceUnit) suce.sourceUnitClass 408 .getConstructor( 409 new Class[] { 410 org.mesopotamia.sql.SourceUnit.class, 411 Scan.class, 412 RepositoryLanguage.class, 413 Collection.class}) 414 .newInstance( 415 new Object[] { 416 dbData, 417 scan, 418 this, 419 Collections.unmodifiableCollection(loadLevels)}); 420 } catch (Exception e) { 421 throw new MesopotamiaRuntimeException("Cannot instantiate source unit "+dbData, e); 422 } 423 } 424 425 /** 426 * @param tokenTypeId token type id. 427 * @return true if given token type id belongs to this language. 428 */ 429 boolean belongsTo(int tokenTypeId) { 430 return id2type.containsKey(tokenTypeId); 431 } 432 433 public LanguageElement instantiateLanguageElement( 434 NodeData xData, 435 Class contextClass, 436 Class targetClass, 437 Scan scan) { 438 try { 439 Collection<Number> loadLevels=scan.getSourceUnitLoadLevels(xData.getSourceUnitId()); 440 441 LanguageElementClassEntry lece=null; 442 443 Collection lecc=(Collection) languageElementClasses.get(xData.getType()); 444 StringBuffer candidates=new StringBuffer("["); 445 if (lecc!=null) { 446 Iterator it=lecc.iterator(); 447 while (it.hasNext()) { 448 LanguageElementClassEntry candidate=(LanguageElementClassEntry) it.next(); 449 candidates.append(candidate.getClassName()); 450 if (it.hasNext()) { 451 candidates.append(", "); 452 } 453 if (candidate.isCompatible(contextClass, targetClass, loadLevels, scan.environment==null) && (lece==null || candidate.compareTo(lece)<0)) { 454 lece=candidate; 455 } 456 } 457 } 458 candidates.append("]"); 459 460 if (lece==null) { 461 if (targetClass==null || targetClass.isAssignableFrom(SimpleLanguageElement.class)) { 462 return new SimpleLanguageElement(xData, contextClass, scan, this, scan.environment); 463 } 464 465 466 String sourceUnitPath = ""; 467 try { 468 sourceUnitPath=factory.getEngine().getSourceUnit(xData.getSourceUnitId()).getPath(); 469 } catch (SQLException e) { 470 factory.consume(xData, e); 471 } 472 throw new MesopotamiaRuntimeException( 473 "Cannot instantiate language element of type " 474 +targetClass.getName() 475 +" ("+xData.getLine()+":"+xData.getColumn()+")" 476 +" from token type " 477 +tokenTypeId2name(xData.getType()) 478 +" in " 479 +contextClass 480 +" context of source unit " 481 + xData.getSourceUnitId() 482 + " " 483 + sourceUnitPath 484 +". None of candidates " 485 +candidates.toString() 486 +" is compatible with "+targetClass.getName()); 487 } 488 489 Object ret = lece.languageElementClass 490 .getConstructor( 491 new Class[] { 492 NodeData.class, 493 Class.class, 494 Scan.class, 495 RepositoryLanguage.class, 496 Object.class}) 497 .newInstance( 498 new Object[] { 499 xData, 500 contextClass, 501 scan, 502 this, 503 scan.environment}); 504 505 if (ret instanceof LanguageElementFactory) { 506 return ((LanguageElementFactory) ret).createLanguageElement(targetClass); 507 } else if (ret instanceof LanguageElement) { 508 return (LanguageElement) ret; 509 } 510 511 throw new MesopotamiaRuntimeException(ret.getClass()+" is neither LangaugeElement nor LanguageElementFactory"); 512 } catch (Exception e) { 513 throw new MesopotamiaRuntimeException("Cannot instantiate language element "+dbData, e); 514 } 515 } 516 517 boolean isScanDependent(int loadLevelId) { 518 return ((LoaderEntry) loadersMap.get(loadLevelId)).isScanDependent(); 519 } 520 521 /** 522 * Posts jobs to load source unit to subsequent levels. 523 */ 524 void load( 525 final int scanId, 526 final int sourceUnitId, 527 final Source source, 528 final Object environment, 529 final SourceUnitLoadListener listener) { 530 531 final TIntHashSet successfullyLoadedLevels=new TIntHashSet(); 532 final TIntHashSet loadedLevels=new TIntHashSet(); 533 try { 534 factory.getEngine().processSourceUnitSuccessfulLoadLevels( 535 sourceUnitId, 536 scanId, 537 new RowProcessor() { 538 539 public boolean process(ResultSet rs) throws SQLException { 540 int levelId = rs.getInt("LEVEL_ID"); 541 loadedLevels.add(levelId); 542 if (!rs.getBoolean("LOAD_FAILED")) { 543 successfullyLoadedLevels.add(levelId); 544 } 545 return true; 546 } 547 548 }); 549 550 Iterator<LoaderEntry> it=loaders.iterator(); 551 while (it.hasNext()) { 552 final LoaderEntry le=it.next(); 553 if (le.isCompatible(successfullyLoadedLevels) && !loadedLevels.contains(le.getId())) { 554 555 if (le.getLoader() instanceof SourceLoader) { 556 if (source!=null) { 557 // TODO - make job public class, DOM Serializable in order to distribute. 558 Runnable job = new Runnable() { 559 public void run() { 560 logger.fine("Loading to level "+le.getLevel()); 561 if (((SourceLoader) le.getLoader()).load( 562 scanId, 563 sourceUnitId, 564 source, 565 environment)) { 566 if (listener!=null) { 567 listener.onLoad(scanId, sourceUnitId, le.getLevel()); 568 } 569 load(scanId, sourceUnitId, source, environment, listener); 570 } 571 } 572 573 public String toString() { 574 return "Load job: "+le.getLoader().getClass()+" loading source "+source.getName()+" to source unit "+sourceUnitId; 575 } 576 }; 577 factory.process(job); 578 } else { 579 logger.severe("Source is null for loader "+le.getLevel()); 580 } 581 } else if (le.getLoader() instanceof SourceUnitLoader) { 582 // TODO - make job public class, DOM Serializable in order to distribute. 583 Runnable job = new Runnable() { 584 public void run() { 585 logger.fine("Loading to level "+le.getLevel()); 586 587 if (((SourceUnitLoader) le.getLoader()).load( 588 scanId, 589 sourceUnitId, 590 environment)) { 591 if (listener!=null) { 592 listener.onLoad(scanId, sourceUnitId, le.getLevel()); 593 } 594 load(scanId, sourceUnitId, source, environment, listener); 595 } 596 } 597 598 public String toString() { 599 return "Load job: "+le.getLoader().getClass()+" loading source unit "+sourceUnitId; 600 } 601 }; 602 factory.process(job); 603 } else { 604 logger.severe("Loader is neither SourceLoader nor SourceUnitLoader"); 605 } 606 } 607 } 608 } catch (SQLException e) { 609 factory.consume(this, e); 610 } 611 } 612 613 public String toString() { 614 return "["+getClass().getName()+"] "+getName()+" "+getVersion(); 615 } 616 617 }