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    }