001/**
002 *  '$RCSfile$'
003 *  '$Author: crawl $'
004 *  '$Date: 2015-08-24 22:44:14 +0000 (Mon, 24 Aug 2015) $'
005 *  '$Revision: 33630 $'
006 *
007 *  For Details:
008 *  http://www.kepler-project.org
009 *
010 *  Copyright (c) 2012 The Regents of the
011 *  University of California. All rights reserved. Permission is hereby granted,
012 *  without written agreement and without license or royalty fees, to use, copy,
013 *  modify, and distribute this software and its documentation for any purpose,
014 *  provided that the above copyright notice and the following two paragraphs
015 *  appear in all copies of this software. IN NO EVENT SHALL THE UNIVERSITY OF
016 *  CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL,
017 *  OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
018 *  DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE
019 *  POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY
020 *  DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
022 *  SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
023 *  CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
024 *  ENHANCEMENTS, OR MODIFICATIONS.
025 */
026package org.kepler.loader.util;
027
028import java.io.BufferedReader;
029import java.io.BufferedWriter;
030import java.io.File;
031import java.io.FileInputStream;
032import java.io.FileOutputStream;
033import java.io.FileReader;
034import java.io.FileWriter;
035import java.io.IOException;
036import java.io.InputStream;
037import java.io.OutputStream;
038import java.lang.reflect.Field;
039import java.net.URL;
040import java.sql.Connection;
041import java.sql.ResultSet;
042import java.sql.SQLException;
043import java.sql.Statement;
044import java.util.Arrays;
045import java.util.HashMap;
046import java.util.HashSet;
047import java.util.LinkedList;
048import java.util.List;
049import java.util.Map;
050import java.util.Set;
051import java.util.Vector;
052import java.util.regex.Matcher;
053import java.util.regex.Pattern;
054
055import org.kepler.Kepler;
056import org.kepler.build.modules.Module;
057import org.kepler.build.modules.ModuleTree;
058import org.kepler.kar.KARCacheContent;
059import org.kepler.kar.KARCacheManager;
060import org.kepler.kar.KAREntry;
061import org.kepler.kar.KARManifest;
062import org.kepler.kar.KarDoclet;
063import org.kepler.kar.SuperClassPathFinderDoclet;
064import org.kepler.kar.handlers.ActorMetadataKAREntryHandler;
065import org.kepler.moml.NamedObjId;
066import org.kepler.objectmanager.ActorMetadata;
067import org.kepler.objectmanager.library.LibraryManager;
068import org.kepler.objectmanager.lsid.KeplerLSID;
069import org.kepler.sms.SemanticType;
070import org.kepler.util.sql.DatabaseFactory;
071import org.kepler.util.sql.HSQL;
072
073import com.sun.tools.javadoc.Main;
074
075import ptolemy.actor.AtomicActor;
076import ptolemy.actor.CompositeActor;
077import ptolemy.actor.Director;
078import ptolemy.actor.TypedCompositeActor;
079import ptolemy.actor.gui.ConfigurationApplication;
080import ptolemy.actor.lib.RandomSource;
081import ptolemy.actor.lib.TimedSource;
082import ptolemy.kernel.CompositeEntity;
083import ptolemy.kernel.Port;
084import ptolemy.kernel.util.IllegalActionException;
085import ptolemy.kernel.util.NamedObj;
086import ptolemy.kernel.util.Workspace;
087import ptolemy.moml.MoMLParser;
088import ptolemy.util.MessageHandler;
089import ptolemy.vergil.basic.KeplerDocumentationAttribute;
090import ptolemy.vergil.basic.export.web.WebContent;
091import util.StringUtil;
092
093/** A class to update the KAR XMLs for actors, directors, etc.
094 * 
095 *  @author Daniel Crawl
096 *  @version $Id: UpdateActorTreeFiles.java 33630 2015-08-24 22:44:14Z crawl $
097 */
098public class UpdateActorTreeFiles {
099
100        /** Update the KAR XMLs for modules.
101         * 
102         *  @param args a list of module names
103         */
104        public static void main(String[] args) {
105                
106                // call the module initializers
107                try {
108                        Kepler.initialize();
109                } catch (Exception e) {
110                        MessageHandler.error("ERROR initializing modules.", e);
111                        System.exit(1);
112                }
113
114        Kepler.setOntologyIndexFile();
115
116                _initializeCache();
117                
118        final ModuleTree tree = ModuleTree.instance();
119        List<String> moduleNames;
120        if(args.length == 0 || (args.length == 1 && args[0].equals("undefined"))) {
121            List<Module>modules = tree.getModuleList();
122            moduleNames = new LinkedList<String>();
123            for(Module module : modules) {
124                moduleNames.add(module.getName());
125            }
126        } else {
127            moduleNames = Arrays.asList(args);
128        }
129                
130                for(String name : moduleNames) {
131                        try {
132                                updateKarXMLDocsForModule(name);
133                        } catch(Exception e) {
134                                MessageHandler.error("ERROR updating module " + name, e);
135                        }
136                }
137                                
138                _shutdownCache(true);           
139        }
140        
141        /** */
142        public static void updateKarXMLDocsForFile(String[] args) throws Exception {
143                
144            boolean overwrite = false;
145            boolean allowMultiClasses = false;
146            List<String> names = new LinkedList<String>();
147            for(String arg : args) {
148                if(arg.equals("-overwrite")) {
149                    overwrite = true;
150                } else if(arg.equals("-allowMultiClasses")) {
151                    allowMultiClasses = true;
152                } else {
153                    names.add(arg);
154                }
155            }
156            
157                _initializeCache();
158                
159                final ModuleTree moduleTree = ModuleTree.instance();
160                Connection conn = null;
161                Statement st = null;
162                ResultSet result = null;
163                try {
164                        conn = DatabaseFactory.getDBConnection();
165                        st = conn.createStatement();
166                        for(String name : names) {
167                            System.out.println("Updating docs for " + name);
168        
169                            if(!name.endsWith(".xml")) {
170                                System.out.println("  ERROR: do not know how to update " + name + " (does not end in .xml)");
171                            } else {
172                                File xmlFile = new File(name);
173                                String xmlName = xmlFile.getName();
174                                        result = st.executeQuery("select kars_cached.file, kar_contents.name, reponame, classname, kar_contents.lsid " +
175                                                       "from kars_cached, kar_contents, cachecontenttable " +
176                                                       "where kar_contents.lsid = cachecontenttable.lsid and " +
177                                                       "kar_contents.file = kars_cached.file and kar_contents.name = '" + xmlName + "'");                               
178                            }
179                                if(!result.next()) {
180                                        System.out.println("  ERROR: could not find " + name + " in cache.");
181                                        continue;
182                                }
183                                
184                                String karPath = result.getString(1);
185                                final String xmlName = result.getString(2);
186                                final String moduleName = result.getString(3).toLowerCase();
187                                final String className = result.getString(4);
188                                final String lsidStr = result.getString(5);
189                                                                
190                                Module module = moduleTree.getModuleByStemName(moduleName);
191                                if(module == null) {
192                                        throw new IllegalActionException("ERROR: module " + moduleName + " not in modules.txt");
193                                }
194                                
195                                // the path returned is the query is something like:
196                                // .../KeplerData/modules/actors/kar/CoreActors.kar
197                                // the name of the kar is the directory name where to write the new xml file:
198                                // .../kepler.modules/actors/resources/kar/CoreActors
199                                
200                                final File karRepoFile = new File(karPath.replaceAll("\\.kar$", ""));
201                                final String karDirName = karRepoFile.getName();
202                                final String fullXMLPath = module.getKarResourcesDir() + File.separator + karDirName;
203                                
204                updateDocs(className, fullXMLPath + File.separator + xmlName,
205                        true, lsidStr, !overwrite, allowMultiClasses);
206                        }
207                } finally {
208                        try {
209                                if(result != null) {
210                                        result.close();
211                                }
212                                if(st != null) {
213                                        st.close();
214                                }
215                                if(conn != null) {
216                                        conn.close();
217                                }
218                        } catch(SQLException e) {
219                                MessageHandler.error("ERROR closing cache database.", e);
220                        }
221                }               
222
223                _shutdownCache(true);
224        }
225
226        /** Update the KAR XML documentation for a module. */ 
227        public static void updateKarXMLDocsForModule(String moduleName) throws Exception {
228                
229                System.out.println("Updating docs for module " + moduleName);
230                
231                // get the classes for the xmls
232                
233                ModuleTree moduleTree = ModuleTree.instance();
234                Module module = moduleTree.getModuleByStemName(moduleName);
235                if(module == null) {
236                        throw new IllegalActionException("ERROR: module " + moduleName + " not in modules.txt");
237                }
238                
239                Connection conn = null;
240                Statement st = null;
241                ResultSet result = null;
242                try {
243                        conn = DatabaseFactory.getDBConnection();
244                        st = conn.createStatement();
245                        
246                        // first character of repository name is upper case
247                        final char[] stringArray = moduleName.toCharArray();
248                        stringArray[0] = Character.toUpperCase(stringArray[0]);
249                        final String repoName = new String(stringArray);
250                        
251                        result = st.executeQuery("select kars_cached.file, kar_contents.name, classname, kar_contents.lsid " +
252                                       "from kars_cached, kar_contents, cachecontenttable " +
253                                       "where kar_contents.lsid = cachecontenttable.lsid and " +
254                                       "kar_contents.file = kars_cached.file and reponame = '" + repoName + "'");
255                        
256                        while(result.next()) {
257                                String karPath = result.getString(1);
258                                final String xmlName = result.getString(2);
259                                final String className = result.getString(3);
260                final String lsidStr = result.getString(4);
261
262                                // the path returned is the query is something like:
263                                // .../KeplerData/modules/actors/kar/CoreActors.kar
264                                // the name of the kar is the directory name where to write the new xml file:
265                                // .../kepler.modules/actors/resources/kar/CoreActors
266                                
267                                final File karRepoFile = new File(karPath.replaceAll("\\.kar$", ""));
268                                final String karDirName = karRepoFile.getName();
269                                final String fullXMLPath = module.getKarResourcesDir() + File.separator + karDirName;
270                                
271                                final String outputFileName = fullXMLPath + File.separator + xmlName;
272                                System.out.println("Updating docs for " + outputFileName);
273                                updateDocs(className, outputFileName, false, lsidStr, true, false);
274                        }
275                        
276                } finally {
277                        try {
278                                if(result != null) {
279                                        result.close();
280                                }
281                                if(st != null) {
282                                        st.close();
283                                }
284                                if(conn != null) {
285                                        conn.close();
286                                }
287                        } catch(SQLException e) {
288                                MessageHandler.error("ERROR closing cache database.", e);
289                        }
290                }               
291        }
292        
293    /** Create XML files for actor KARs.
294     *  @param names a java source file names or class name.
295     *  @param outputFileName the name of the output file.
296     *  @param printWhenChangePropName if true, print when the class field names
297     *  in the docs are different from the name returned by NamedObj.getName(). 
298     *  @param lsidStr the lsid
299     *  @param onlyCreateForMissing if true, documentation is only created if not present.
300     *  @param allowMultiClasses if true, create documentation even if multiple actors
301     *  use the same class, e.g., customized versions of the R actor.
302     */
303    public static void updateDocs(String name, String outputFileName, 
304            boolean printWhenChangePropName, String lsidStr,
305            boolean onlyCreateForMissing, boolean allowMultiClasses) throws Exception {
306
307        //System.out.println("    output file name = " + outputFileName);
308        
309        // read the existing file
310        _parser.reset();
311        NamedObj oldKarXML = _parser.parseFile(outputFileName);
312        final KeplerDocumentationAttribute oldDoc = (KeplerDocumentationAttribute) oldKarXML.getAttribute("KeplerDocumentation");
313        final KeplerDocumentationAttribute tmpDoc = new KeplerDocumentationAttribute(_workspace);
314        tmpDoc.createInstanceFromExisting(oldDoc);
315        if(tmpDoc != null) {
316            String userLevelDoc = tmpDoc.getUserLevelDocumentation();
317            if(onlyCreateForMissing && userLevelDoc != null && userLevelDoc.trim().length() > 10) {
318                System.out.println("WARNING: not updating docs for " +
319                    outputFileName + " since docs already exist.");
320                return;
321            }
322        }
323        
324        String fileName = _getFileName(name);
325        if(fileName == null) {
326            return;
327        }
328                    
329        final KeplerDocumentationAttribute newDoc = _generateDocsFromDoclet(fileName);
330        if(newDoc == null) {
331            return;
332        }
333
334        final String className = _getClassName(fileName);
335        
336        if(!allowMultiClasses && _classHasMultipleDocs(className)) {
337            System.out.println("WARNING: not updating docs for " + outputFileName + " since multiple doc files found for " + className);
338            return;
339        }
340
341        //System.out.println("    class name = " + className);
342        
343        String shortName = className
344                .substring(className.lastIndexOf(".") + 1);
345
346        Class<?> cls = Class.forName(className);
347        NamedObj no;
348        
349        try {
350            no = ActorMetadata.createInstance(cls,
351                new Object[] { new CompositeEntity(), shortName });
352        } catch(Exception e) {
353            System.err.println("ERROR: could not instantiate " + className);
354            return;
355        }
356        
357        // convert names of parameters and ports from object field name
358        // to the name returned by NamedObj.getName()
359        _fixNames(no, newDoc, printWhenChangePropName);
360        
361        if(oldDoc != null) {     
362            
363            /*
364            // see if we're only creating docs when they are missing
365            if(onlyCreateForMissing) {
366                // update the existing docs from the new class docs
367                oldDoc.updateFromExisting(newDoc, true);
368            } else {
369            */ 
370            
371            // use old entries if the javadoc ones are empty
372            newDoc.updateFromExisting(oldDoc, true);
373            
374            // remove the old doc attribute, and move the new doc attribute 
375            // to the same position as the old one
376            int index = oldDoc.moveToLast();
377            oldDoc.setContainer(null);
378            newDoc.setContainer(oldKarXML);
379            newDoc.moveToIndex(index);
380            
381            //}
382        } else {
383            newDoc.setContainer(oldKarXML);
384        }
385
386        String xmlStr = oldKarXML.exportMoML();
387        String prettyXMLStr = StringUtil.prettifyXML(xmlStr, 2);
388        
389        final File outputFile = new File(outputFileName);
390        final FileWriter writer = new FileWriter(outputFile);
391        writer.write(prettyXMLStr);
392        writer.close();
393    }
394    
395    /** Create XML files for a list of source files. */
396    public static void buildXMLs(String args[]) {
397
398        _initializeCache();
399
400        List<String> names = new LinkedList<String>();
401        
402        boolean dup = true;
403        boolean requireSemanticTypes = false;
404        
405        for (String arg : args) {            
406            if(arg.equals("-nodup")) {
407                System.out.println("Will not create files for actors already in cache.");
408                dup = false;
409            } else if(arg.equals("-requireSem")) {
410                System.out.println("Will not create files for actors without semantic types.");
411                requireSemanticTypes = true;
412            } else {
413                names.add(arg);
414            }
415        }
416        
417        // do not generate XMLs for deprecated classes
418        KarDoclet.setGenerateForDeprecated(false);
419        
420        for(String name : names) {
421            System.out.println("Building actor XML for " + name);
422            buildKARXML(name, null, null, true, true, dup, requireSemanticTypes);
423        }
424        
425        _shutdownCache(true);
426    }
427
428    /** Create XML files for actor KARs.
429         *  @param names a java source file names or class name.
430         *  @param outputDir the output directory to write the file to. if null,
431         *  then try to guess module containing the source or class name and write
432         *  to module/resources/kar. if cannot guess module, write to user.dir.
433         *  @param outputName the name of the output file. if null, then use the short class name. 
434         *  @param printWhenNameIsDifferent if true, print when the class name and name in the cache are different.
435         *  @param printWhenChangePropName if true, print when the class field names in the docs are different
436         *  from the name returned by NamedObj.getName(). 
437         *  @param duplicateExistingActor if true, create XML for actor already in cache. otherwise,
438         *  do nothing.
439         *  @param requireSemanticTypes if true, do not create XML for actors unless semantic types are
440         *  known or can be guessed.
441         */
442        public static void buildKARXML(String name, String outputDir, String outputName, 
443                        boolean printWhenNameIsDifferent, boolean printWhenChangePropName,
444                        boolean duplicateExistingActor, boolean requireSemanticTypes) {
445                try {
446                    
447            final KARCacheManager karCacheManager = KARCacheManager.getInstance();
448            final Vector<KARCacheContent> karCacheContents = karCacheManager.getKARCacheContents();
449                    
450                    String fileName = _getFileName(name);
451                    if(fileName == null) {
452                        return;
453                    }
454                                                    
455            final String className = _getClassName(fileName);
456
457            if(className == null) {
458                System.err.println("ERROR: could not determine class name for " + fileName);
459                return;
460            }
461            
462            //System.out.println("class name is = " + className);
463            
464                        String shortName = className
465                                        .substring(className.lastIndexOf(".") + 1);
466
467                        Class<?> cls = Class.forName(className);
468                        NamedObj no;
469                        
470                        try {
471                            no = ActorMetadata.createInstance(cls,
472                                        new Object[] { new CompositeEntity(), shortName });
473                        } catch(Exception e) {
474                            System.err.println("ERROR: could not instantiate " + className);
475                            return;
476                        }
477                        
478                        no = (NamedObj) no.clone(_workspace);
479                        
480                        String LSIDStr = null;
481                        Vector<KeplerLSID> semanticLSIDs = null;
482                        
483            final String parentClassName = cls.getSuperclass().getName();
484            Vector<KeplerLSID> parentSemanticLSIDs = null;
485
486                        //System.out.println("  Searching for " + className + " in Kepler actor cache.");
487            
488            boolean foundActorInCache = false;
489            
490                for(KARCacheContent content : karCacheContents) {
491                    String cacheClassName = content.getCacheContent().getClassName();
492                    
493                    // make sure class name is not null.
494                    // it's null for, e.g., workflow runs and report layouts
495                    if(cacheClassName != null) {
496
497                        if(cacheClassName.equals(className)) {
498                            //System.out.println("  Found in cache.");
499                                
500                                // get the lsid
501                                KeplerLSID lsid = content.getLsid();
502                                LSIDStr = lsid.toString();
503                                foundActorInCache = true;
504                                if(!duplicateExistingActor) {
505                                    System.out.println("  WARNING: will not duplicate actor in cache: " + name);
506                                    return;
507                                }
508
509                                // get the semantic types
510                                semanticLSIDs = content.getSemanticTypes(); 
511                                
512                                String cachedName = content.getCacheContent().getName();
513                                if(!shortName.equals(cachedName)) {
514                                        if(printWhenNameIsDifferent) {
515                                                System.out.println("  using name found in cache: " + cachedName);
516                                        }
517                                        no.setName(cachedName);
518                                }                               
519                                break;
520                                
521                            } else if(cacheClassName.equals(parentClassName)) {
522                                parentSemanticLSIDs = content.getSemanticTypes();
523                            }
524                    }
525                }
526
527                // generate and set the documentation
528            final KeplerDocumentationAttribute doc = _generateDocsFromDoclet(fileName);
529            if(doc == null) {
530                return;
531            }
532            
533            _fixNames(no, doc, printWhenChangePropName);
534            doc.setContainer(no);
535            
536            //_removeDefaultValues(no);
537
538            Set<String> semanticTypes = new HashSet<String>();
539            
540            // see if we found semantic types in the cache
541            if(semanticLSIDs == null || semanticLSIDs.isEmpty()) {
542                    
543                    // try guessing
544                semanticTypes = _guessSemanticTypes(no);
545                for(String type : semanticTypes) {
546                    System.out.println("  guessed " + type);
547                }
548
549                // see if we found semantic types of the parent class in the cache.
550                    if(parentSemanticLSIDs != null) {
551                        System.out.println("  WARNING: semantic types not found; using types from parent " + parentClassName);
552                        semanticLSIDs = parentSemanticLSIDs;
553                    }
554                        }
555                                                
556                        // if we found semantic types in the cache for the class or a parent class,
557            // convert to strings.
558                        if(semanticLSIDs != null) {
559                // convert from LSID to string
560                for(KeplerLSID semanticTypeLSID : semanticLSIDs) {
561                    semanticTypes.add(semanticTypeLSID.toString());
562                }
563                        }
564
565                // see if we still have to semantic types
566                    if(semanticTypes.isEmpty()) {                    
567                        
568                        if(requireSemanticTypes) {
569                            System.out.println("  WARNING: will not create XML since no semantic types for " + name);
570                            return;
571                        }
572                        
573                        // add a default semantic type
574                        semanticTypes.add("urn:lsid:localhost:onto:2:1#FIXME");    
575                System.out.println("  WARNING: semantic types need to be set for " + name);
576                        }
577                        
578                        // add the semantic types
579            int count = 0;
580            for(String semanticTypeStr : semanticTypes) {
581                SemanticType semanticType = new SemanticType(no, "semanticType0" + count);
582                semanticType.moveToLast();
583                semanticType.setExpression(semanticTypeStr);
584                count++;
585                //System.out.println("  added semantic type " + semanticType.getName() + " = " + semanticType.getExpression());
586            }
587            
588            NamedObjId noId = new NamedObjId(no, NamedObjId.NAME);
589            noId.moveToFirst();
590            
591            if(LSIDStr != null) {
592                noId.setExpression(LSIDStr);
593                //System.out.println("LSID is " + LSIDStr);
594            } else {
595                
596                LSIDStr = _getNextActorLSIDFromReadme();
597                
598                if(LSIDStr != null) {
599                    System.out.println("  Using new LSID: " + LSIDStr);
600                    noId.setExpression(LSIDStr);
601                } else {
602                    System.out.println("  WARNING: the LSID needs to be set.");
603                    noId.setExpression("urn:lsid:kepler-project.org:actor:999:1");
604                }
605            }
606
607                        if(outputDir == null) {
608                            
609                            // try to find the module containing the source.
610                            // NOTE: if the module is found, outputDir is set to
611                            // module/resources/kar. However, the actor kar
612                            // entries are always in a sub-directory of this
613                            // directory. Usually there is only one sub-directory
614                            // in module/resources/kar, and below we try to find it.
615                            
616                            Module module = findModuleSrcDirForNameObj(no);
617                            if(module != null) {
618                                File karResourcesDir = module.getKarResourcesDir();
619                                if(!karResourcesDir.exists() && !karResourcesDir.mkdirs()) {
620                                System.out.println("  WARNING: unable to mkdirs: " + karResourcesDir);
621                            } else {
622                                // see if there is only one directory in the kar resources dir
623                                String potentialDir = null;
624                                int potentialDirs = 0;
625                                final File[] filesInKarResourcesDir = karResourcesDir.listFiles();
626                                for(File fileInKarResourcesDir : filesInKarResourcesDir) {
627                                    if(fileInKarResourcesDir.isDirectory() && !fileInKarResourcesDir.getName().startsWith(".")) {
628                                        potentialDirs++;
629                                        potentialDir = fileInKarResourcesDir.getAbsolutePath();
630                                    }
631                                }
632                                // if there's only one sub-directory, use it
633                                if(potentialDirs == 1) {
634                                    outputDir = potentialDir;
635                                } else {
636                                    // otherwise use module/resources/kar and files must be moved by hand
637                                    outputDir = karResourcesDir.getAbsolutePath();
638                                }
639                            }
640                            }
641                        }
642                        
643                        if(outputDir == null) {
644                            outputDir = System.getProperty("user.dir");
645                        }
646                        
647                        // use ActorMetadata instead of NamedObj.exportMoML() to generate
648                        // the XML since the XML generated by exportMoML is not complete:
649                        // missing <?xml version="1.0"?> (necessary?)
650                        // <entity> class attribute is actor class, not ptolemy.kernel.ComponentEntity
651                        
652                        String xmlOutputFileName = outputDir + File.separator;
653                        if(outputName == null) {
654                                xmlOutputFileName += shortName + ".xml";
655                        } else {
656                                xmlOutputFileName += outputName;
657                        }
658                        
659                        System.out.println("  output file is " + xmlOutputFileName);
660                        
661                        final File xmlOutputFile = new File(xmlOutputFileName);
662                        
663                        // if we're not duplicating existing actors, make sure the output
664                        // file does not exist.
665                        if(!duplicateExistingActor && xmlOutputFile.exists()) {
666                            System.out.println("  not writing output file since it already exists.");
667                            return;
668                        }
669                        
670                        FileWriter writer = new FileWriter(xmlOutputFile);
671                        ActorMetadata actorMetadata = null;                                     
672                        
673                        if(actorMetadata == null) {
674                                actorMetadata = new ActorMetadata(no);                                  
675                        }
676                        
677                        writer.write(actorMetadata.toString(false, false, false));
678                        writer.close();
679                        
680                        if(!foundActorInCache) {
681                        InputStream inputStream = null;
682                        OutputStream outputStream = null;
683                        try {
684                                // create a MANIFEST.MF if it does not exist
685                                KARManifest manifest;
686                    File manifestFile = new File(outputDir, "MANIFEST.MF");
687                                if(!manifestFile.exists()) {
688                                    manifest = new KARManifest();
689                                } else {
690                                    inputStream = new FileInputStream(manifestFile);
691                                    manifest = new KARManifest(inputStream);
692                                }
693                                
694                                // add the new entries
695                                // e.g.:
696                                // Name: DecimalFormatConverter.xml
697                                // type: ptolemy.kernel.ComponentEntity
698                                // lsid: urn:lsid:kepler-project.org:actor:570:1
699                                // handler: org.kepler.kar.handlers.ActorMetadataKAREntryHandler
700    
701                                String typeStr;
702                                if(no instanceof AtomicActor) {
703                                    typeStr = "ptolemy.kernel.ComponentEntity";
704                                } else if(no instanceof CompositeActor) {
705                                    typeStr = "org.kepler.moml.CompositeClassEntity";
706                                } else {
707                                    typeStr = "org.kepler.moml.PropertyEntity";
708                                }
709                                
710                                String xmlOutputFileBaseName = xmlOutputFile.getName();
711                                manifest.addEntryAttribute(xmlOutputFileBaseName, KAREntry.TYPE.toString(), typeStr);
712                    manifest.addEntryAttribute(xmlOutputFileBaseName, KAREntry.LSID.toString(), LSIDStr);
713                    manifest.addEntryAttribute(xmlOutputFileBaseName, KAREntry.HANDLER.toString(),
714                            ActorMetadataKAREntryHandler.class.getName());
715                                
716                                
717                    if(inputStream != null) {
718                        inputStream.close();
719                        inputStream = null;
720                    }
721                                
722                                // write the updated manifest
723                                outputStream = new FileOutputStream(manifestFile);                      
724                                manifest.write(outputStream);
725                                
726                        } finally {
727                            if(inputStream != null) {
728                                inputStream.close();
729                            }
730                            if(outputStream != null) {
731                                outputStream.close();
732                            }
733                        }
734                        }
735                } catch (Exception e) {
736                    MessageHandler.error("Error creating XML.", e);
737                }
738        }
739        
740        /** Find the module whose source directory contains a NamedObj.
741         *  Returns null if not found.
742         */
743        public static Module findModuleSrcDirForNameObj(NamedObj namedObj) throws ClassNotFoundException {
744
745        ModuleTree tree = ModuleTree.instance();
746
747            String className = namedObj.getClass().getName();
748            
749            // see if it's a ptolemy class
750            if(className.startsWith("ptolemy")) {
751                if(namedObj instanceof Director) {
752                    return tree.getModuleByStemName("directors");
753                } else {
754                    return tree.getModuleByStemName("actors");
755                }
756            }
757            
758            String fileName = _getFileName(className);
759            if(fileName != null) {
760            for(Module module : tree.getModuleList()) {
761                if(fileName.startsWith(module.getSrc().getAbsolutePath())) {
762                    return module;
763                }
764            }
765            }
766            return null;
767        }
768                
769        /** Try to guess the semantic types for a NamedObj. */
770        private static Set<String> _guessSemanticTypes(NamedObj namedObj) {
771            
772            Set<String> types = new HashSet<String>();
773            
774            Matcher matcher;
775            
776            final String namedObjName = namedObj.getName();
777            final String namedObjClassName = namedObj.getClass().getName();
778            final KeplerDocumentationAttribute doc = 
779                    (KeplerDocumentationAttribute) namedObj.getAttribute("KeplerDocumentation");
780            final String userLevelDocLowerCase = doc.getUserLevelDocumentation().toLowerCase();
781            
782            if(namedObjName.contains("Array")) {
783                types.add("urn:lsid:localhost:onto:2:1#DataArrayOperation");
784            }
785            
786            matcher = _CONVERT_FROM_TO_PATTERN.matcher(namedObjName);
787            if(namedObjName.contains("Assembler") ||
788                namedObjName.contains("Disassembler") ||
789                namedObjName.contains("Updater") ||
790                (matcher.find() && !(namedObjName.contains("String")))) {
791                types.add("urn:lsid:localhost:onto:2:1#DataStructureOperation");
792            }
793            
794            if(namedObjName.contains("String")) {
795            types.add("urn:lsid:localhost:onto:2:1#DataStringOperation");               
796            }
797            
798            if(namedObjName.endsWith(("Average")) ||
799                namedObjName.endsWith("Maximum") ||
800                namedObjName.endsWith(("Minimum"))) {
801                types.add("urn:lsid:localhost:onto:2:1#StatisticalOperation");
802            }
803            
804            if(namedObjName.contains("Matrix")) {
805                types.add("urn:lsid:localhost:onto:2:1#MatrixOperation");
806            }
807            
808            if(namedObj instanceof RandomSource) {
809                types.add("urn:lsid:localhost:onto:2:1#RandomNumberOperation");
810            }
811            
812            if(namedObjClassName.startsWith("ptolemy.actor.lib.comm")) {
813                types.add("urn:lsid:localhost:onto:2:1#Communications");
814            }
815            
816            if(namedObjClassName.startsWith("ptolemy.actor.lib.logic")) {
817                types.add("urn:lsid:localhost:onto:2:1#BooleanControl");
818            }
819            
820            if(userLevelDocLowerCase.contains("filter") &&
821            (userLevelDocLowerCase.contains("adaptive") ||
822            userLevelDocLowerCase.contains("recursive") ||
823            userLevelDocLowerCase.contains("lattice") ||
824            userLevelDocLowerCase.contains("impulse response"))) {
825            types.add("urn:lsid:localhost:onto:2:1#Filtering");
826        }
827            
828            if(namedObjName.endsWith("Select") ||
829                namedObjName.endsWith("Switch")) {
830                types.add("urn:lsid:localhost:onto:2:1#WorkflowControl");
831                
832                if(namedObjName.contains("Boolean")) {
833                    types.add("urn:lsid:localhost:onto:2:1#BooleanControl");
834                }
835            }
836            
837            if(namedObjName.contains("Time") ||
838                (namedObj instanceof TimedSource)) {
839                types.add("urn:lsid:localhost:onto:2:1#Time");
840            }
841            
842            if(namedObj instanceof TypedCompositeActor) {
843                types.add("urn:lsid:localhost:onto:2:1#Workflow");
844            }
845            
846            if((namedObj instanceof WebContent) ||
847                namedObjClassName.startsWith("ptolemy.vergil.basic.export.web")) {
848                types.add("urn:lsid:localhost:onto:2:1#WorkflowWebExport");
849            }
850                
851            if(namedObjName.contains("XML") ||
852                namedObjName.contains("XSLT")) {
853                types.add("urn:lsid:localhost:onto:2:1#XMLProcessor");
854            }
855                    
856            return types;
857        }
858            
859        /** Returns true if the specified class appears more than once
860         *  in the cache content table.
861         */
862        private static boolean _classHasMultipleDocs(String className) throws Exception {
863            
864            if(_numClassDocFilesMap.isEmpty()) {
865                Connection conn = null;
866                Statement st = null;
867                ResultSet result = null;
868                try {
869                    conn = DatabaseFactory.getDBConnection();
870                    st = conn.createStatement();
871                                    
872                    result = st.executeQuery("select classname, count(classname) from cachecontenttable group by classname");
873                    
874                    while(result.next()) {
875                        _numClassDocFilesMap.put(result.getString(1), result.getInt(2));
876                    }
877                    
878                } finally {
879                    try {
880                        if(result != null) {
881                            result.close();
882                        }
883                        if(st != null) {
884                            st.close();
885                        }
886                        if(conn != null) {
887                            conn.close();
888                        }
889                    } catch(SQLException e) {
890                        MessageHandler.error("ERROR closing cache database.", e);
891                    }
892                }       
893            }
894            
895            Integer count = _numClassDocFilesMap.get(className);
896            if(count == null) {
897                System.out.println("Class " + className + " not found in cache.");
898                return true;
899            }
900            return count > 1;
901        }
902        
903        /**
904         * Change property (parameter) and port names in a KeplerDocumentationAttribute from
905         * the class field name to the name return by getName().
906         */
907        private static void _fixNames(NamedObj namedObj,
908                        KeplerDocumentationAttribute doc, boolean printWhenChangeName) {
909                Class<?> clazz = namedObj.getClass();
910
911                // get the property names
912                Map<String, String> docNameTable = new HashMap<String, String>(
913                                doc.getPropertyHash());
914                docNameTable.putAll(doc.getPortHash());
915                for (String docName : docNameTable.keySet()) {
916
917                        try {
918                                // get the field for this property name
919                                Field field = clazz.getField(docName);
920                                if (field == null) {
921                                        System.out.println("ERROR: unable to find field for "
922                                                        + docName);
923                                } else {
924                                        // get the NamedObj for this field
925                                        NamedObj fieldNamedObj = (NamedObj) field.get(namedObj);
926                                        if (fieldNamedObj != null) {
927                                            
928                                            // if the property is nested within another property, 
929                                            // do not create docs for it
930                                            if(fieldNamedObj.getContainer() != namedObj) {
931                                                doc.removeProperty(docName);
932                                                continue;
933                                            }
934                                            
935                                                // get the real name
936                                                String fullName = fieldNamedObj.getName();
937
938                                                if(!fullName.equals(docName)) {
939                                                
940                                                    // remove the old name from the doc attribute and
941                                                    // add the new one
942                                                    String value;
943                                                    if(fieldNamedObj instanceof Port) {
944                                value = doc.removePort(docName);
945                                doc.addPort(fullName, value);
946                                                    } else {
947                                value = doc.removeProperty(docName);
948                                doc.addProperty(fullName, value);
949                                                    }
950    
951                                                if(printWhenChangeName) {
952                                                        System.out.println("changed name " + docName
953                                                                + " --> " + fullName);
954                                                }
955                                        }
956                                        }
957                                }
958                        } catch (Exception e) {
959                                System.out.println(e.getClass() + " ERROR: " + e.getMessage());
960                        }
961                }
962        }
963                
964        /** Get the next actor LSID from the README file. This method also
965         *  updates the README file.
966         */
967        private static String _getNextActorLSIDFromReadme() {
968            
969            try {
970                
971            ModuleTree modules = ModuleTree.instance();
972            Module module = modules.getModuleByStemName("actors");
973            String readmePath = module.getKarResourcesDir() + File.separator + "README";
974                
975                BufferedReader reader = new BufferedReader(new FileReader(readmePath));
976                StringBuilder buf = new StringBuilder();
977                
978                Matcher matcher = null;
979                String line = reader.readLine();
980                while(line != null) {
981                    
982                    // see if line matches
983                    matcher = _LAST_ACTOR_ID_PATTERN.matcher(line);
984                    if(matcher.matches()) {
985                        break;
986                    } 
987                    
988                    buf.append(line);
989                    line = reader.readLine();
990                    if(line != null) {
991                        buf.append(System.getProperty("line.separator"));
992                    }
993                }
994                                
995                if(matcher == null) {
996                    System.out.println("WARNING: could not read LSID README file.");
997                    reader.close();
998                    return null;
999                }
1000                
1001                // parse last id from line and increment
1002                String idStr = matcher.group(1);
1003            int id = Integer.parseInt(idStr);
1004            id++;
1005            System.out.println("  incrementing README id to " + id);
1006                                
1007                // write id line to file
1008            buf.append(_LAST_ACTOR_ID_PREFIX + id + System.getProperty("line.separator"));
1009                
1010                // write remaining file
1011                line = reader.readLine();
1012                while(line != null) {
1013                    buf.append(line);
1014                    line = reader.readLine();
1015                    if(line != null) {
1016                        buf.append(System.getProperty("line.separator"));
1017                    }
1018                }
1019                reader.close();
1020                
1021                BufferedWriter writer = new BufferedWriter(new FileWriter(readmePath));
1022                writer.write(buf.toString());
1023                writer.close();
1024                
1025                
1026                return "urn:lsid:kepler-project.org:actor:" + id + ":1";
1027                
1028            } catch (IOException e) {
1029                System.out.println("Error updating LSID README file: " + e.getMessage());
1030                return null;
1031            }
1032        }
1033        
1034        /** Get a file name from a class name or file name. */
1035        private static String _getFileName(String name) throws ClassNotFoundException {
1036            // see if it's a file name
1037            if(name.endsWith(".java")) {
1038                return name;
1039            } else {
1040                // looks like a class name. see if we can figure out the
1041                // file name.
1042                String fileName = SuperClassPathFinderDoclet.getFileNameForClassName(name);
1043                if(fileName == null) {
1044                    //System.out.println("ERROR: skipping " + name +
1045                        //" since could not determine source file name.");
1046                    return null;
1047                }
1048                return fileName;
1049            }
1050        }
1051        
1052    /** Get a class name from a file name or class name. */
1053    private static String _getClassName(String name) {
1054
1055        // if it looks like a file name, assume it's a class name
1056        if (!name.endsWith(".java")) {
1057            return name;
1058        }
1059
1060        String className = _filenameToClassMap.get(name);
1061        if (className == null) {
1062            
1063            Main.execute(new String[] { "-quiet", "-doclet",
1064                    "org.kepler.kar.SuperClassPathFinderDoclet", name });
1065            className = SuperClassPathFinderDoclet.getClassName();
1066                        
1067            _filenameToClassMap.put(name, className);
1068            //System.out.println("file name " + name + " class name " + className);
1069            
1070        }
1071        return className;
1072
1073    }
1074
1075        private static void _initializeCache() {
1076                                
1077                // load the configuration since it is required to load the
1078                // KAR entries handlers, which in turn is required to build
1079                // the actor library
1080                
1081            // NOTE: we must use a configuration with the GUI, otherwise
1082            // MoML filters are used to remove GUI actors.
1083                String spec = "ptolemy/configs/kepler/ConfigGUIAndCache.xml";
1084                URL url = null;
1085                try {
1086                        url = ConfigurationApplication.specToURL(spec);
1087                } catch (IOException e) {
1088                        MessageHandler.error("ERROR configuration URL.", e);
1089                        System.exit(1);
1090                }
1091                
1092                try {
1093                    ConfigurationApplication.readConfiguration(url);
1094                } catch (Exception e) {
1095                        MessageHandler.error("ERROR reading configuration.", e);
1096                        System.exit(1);
1097                }               
1098                
1099                // build the actor library in case it does not exist
1100                LibraryManager.getInstance().buildLibrary();
1101        }
1102        
1103        private static KeplerDocumentationAttribute _generateDocsFromDoclet(String fileName) {
1104
1105            // find parent files for this class
1106        Main.execute(new String[] {
1107                "-quiet",
1108                "-doclet",
1109                "org.kepler.kar.SuperClassPathFinderDoclet", fileName });
1110        
1111        final Set<String> classFiles = SuperClassPathFinderDoclet.getClassFiles();
1112        final String className = SuperClassPathFinderDoclet.getClassName();
1113        _filenameToClassMap.put(fileName, className);
1114        
1115        if(classFiles.isEmpty()) {
1116            System.out.println("ERROR: could not find files of super classes of " + fileName);
1117            return null;
1118        }
1119
1120        // construct documentation for the class using its source file, and
1121        // those of its super classes
1122        final String[] args = new String[3 + classFiles.size()];
1123        args[0] = "-quiet";
1124        args[1] = "-doclet";
1125        args[2] = "org.kepler.kar.KarDoclet";
1126        int j = 3;
1127        for(String filename : classFiles) {
1128            args[j] = filename;
1129            j++;
1130        }
1131        
1132        Main.execute(args);
1133
1134        return KarDoclet.getDoc(className);
1135        }
1136        
1137        /** Remove parameter and port entries. TODO: remove ports */
1138        /*
1139        private static void _removeDefaultValues(NamedObj namedObj) {
1140            
1141            List<?> attributes = new LinkedList<Object>(namedObj.attributeList());
1142            for(Object object : attributes) {
1143                String name = ((Attribute)object).getName();
1144                if(!name.equals("KeplerDocumentation") && 
1145                    !name.startsWith("semanticType")) {
1146                    
1147                    System.out.println("Removing attribute " + name);
1148                    
1149                    try {
1150                    ((Attribute)object).setContainer(null);
1151                } catch (Exception e) {
1152                    System.out.println("ERROR removing attribute " + name + ": " + e.getMessage());
1153                }
1154                }
1155            }
1156            
1157            // XXX remove ports
1158            
1159        }
1160        */
1161                
1162        private static void _shutdownCache(boolean exit) {
1163                // call the module deinitializers
1164                Kepler.shutdown();
1165                HSQL.shutdownServers();
1166                
1167                if(exit) {
1168                    // we have to call System.exit() because ???
1169                    System.exit(0);
1170                }
1171        }
1172
1173        /** The prefix string for the last known LSID. */
1174        private final static String _LAST_ACTOR_ID_PREFIX = "The last known id for an actor is actor:";
1175        
1176        /** A pattern to match the last know LSID string. */
1177        private final static Pattern _LAST_ACTOR_ID_PATTERN = Pattern.compile(_LAST_ACTOR_ID_PREFIX + "\\s*(\\d+)");
1178        
1179        private static Map<String,Integer>_numClassDocFilesMap = new HashMap<String,Integer>();
1180        
1181        private static Map<String,String> _filenameToClassMap = new HashMap<String,String>();
1182        
1183        private static Workspace _workspace = new Workspace();
1184        private static MoMLParser _parser = new MoMLParser(_workspace);
1185        
1186        static {
1187            KarDoclet.setWorkspace(_workspace);
1188        }
1189
1190        /** Regex to match actor names that perform conversions. */
1191        private final static Pattern _CONVERT_FROM_TO_PATTERN = Pattern.compile(".+To[A-Z].+");
1192}