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