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    }