source: trunk/org.modelica.mdt.omc/src/org/modelica/mdt/omc/OMCProxy.java @ 307

Last change on this file since 307 was 307, checked in by remar, 19 years ago
  • error messages now contain both starting and ending position of errors, this is used when setting problem markers
  • getCrefInfo return value changed from file:line:column to file:writable:line:column
  • changed tests to reflect changes to error message handling
  • updated documentation
File size: 20.0 KB
Line 
1/*
2 * This file is part of Modelica Development Tooling.
3 *
4 * Copyright (c) 2005, Link�pings universitet, Department of
5 * Computer and Information Science, PELAB
6 *
7 * All rights reserved.
8 *
9 * (The new BSD license, see also
10 * http://www.opensource.org/licenses/bsd-license.php)
11 *
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions are
15 * met:
16 *
17 * * Redistributions of source code must retain the above copyright
18 *   notice, this list of conditions and the following disclaimer.
19 *
20 * * Redistributions in binary form must reproduce the above copyright
21 *   notice, this list of conditions and the following disclaimer in
22 *   the documentation and/or other materials provided with the
23 *   distribution.
24 *
25 * * Neither the name of Link�pings universitet nor the names of its
26 *   contributors may be used to endorse or promote products derived from
27 *   this software without specific prior written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 */
41
42package org.modelica.mdt.omc;
43
44import java.io.BufferedReader;
45import java.io.File;
46import java.io.FileReader;
47import java.io.IOException;
48import java.util.StringTokenizer;
49
50import org.eclipse.core.resources.IFile;
51import org.eclipse.core.runtime.Platform;
52import org.modelica.mdt.core.IModelicaClass;
53import org.modelica.mdt.core.List;
54import org.modelica.mdt.core.compiler.ConnectException;
55import org.modelica.mdt.core.compiler.IModelicaCompiler;
56import org.modelica.mdt.core.compiler.InvocationError;
57import org.modelica.mdt.core.compiler.ModelicaParser;
58import org.modelica.mdt.core.compiler.UnexpectedReplyException;
59import org.modelica.mdt.internal.core.ErrorManager;
60import org.modelica.mdt.omc.internal.ElementLocation;
61import org.modelica.mdt.omc.internal.ParseResults;
62import org.modelica.mdt.omc.internal.OMCParser;
63import org.modelica.mdt.omc.internal.corba.OmcCommunication;
64import org.modelica.mdt.omc.internal.corba.OmcCommunicationHelper;
65import org.omg.CORBA.ORB;
66
67/**
68 * The OMCProxy is the glue between the OpenModelica Compiler and MDT.
69 * It uses the interactive API of OMC to get information about classes
70 * and to load classes into OMC.
71 *
72 * @author Andreas Remar
73 */
74public class OMCProxy implements IModelicaCompiler
75{
76    /* the CORBA object */
77    private static OmcCommunication omcc;
78   
79    /* what Operating System we're running on */
80    private static String os;
81   
82    /* indicates if we've setup the communication with OMC */
83    private boolean hasInitialized = false;
84   
85    /* indicates if the Modelica System Library has been loaded */
86    private boolean systemLibraryLoaded = false;
87
88    private String[] standardLibraryPackages = { "Modelica" };
89   
90    /* should we trace the calls to sendExpression? */
91    private static boolean traceOMCCalls = false;
92    private static boolean traceOMCStatus = false;
93    static
94    {
95        /* load debug options and set debug flag variables accordingly */
96       
97       
98        String value = Platform.getDebugOption  /*load trace/omcCalls flag */
99            ("org.modelica.mdt.omc/trace/omcCalls");
100        if (value != null && value.equalsIgnoreCase("true"))
101        {
102            traceOMCCalls = true;
103        }
104       
105        value = Platform.getDebugOption
106        ("org.modelica.mdt.omc/trace/omcStatus");
107        if (value != null && value.equalsIgnoreCase("true"))
108        {
109            traceOMCStatus = true;
110        }
111    }
112   
113    public OMCProxy()
114    {
115       
116    }
117
118    /**
119     * Reads in the OMC CORBA object reference from a file on disk.
120     * @return the object reference as a <code>String</code>
121     */
122    private static String readObjectFromFile() throws ConnectException
123    {
124        File f = new File(getPathToObject());
125        String stringifiedObjectReference = null;
126
127        BufferedReader br = null;
128        FileReader fr = null;
129        try
130        {
131            fr = new FileReader(f);
132        }
133        catch(IOException e)
134        {
135            throw new ConnectException
136                ("Unable to read OpenModelica Compiler CORBA object from "
137                        + f.toString());
138        }
139
140        br = new BufferedReader(fr);
141           
142        try
143        {
144            stringifiedObjectReference = br.readLine();
145        }
146        catch(IOException e)
147        {
148            throw new ConnectException("Unable to read OpenModelica Compiler"
149                    + " CORBA object from " + getPathToObject());
150        }
151        return stringifiedObjectReference;
152    }
153   
154    /**
155     * @return Returns the path to the OMC CORBA object that is stored on disk.
156     */
157    private static String getPathToObject()
158    {
159        String fileName = null;
160        if(os.equals("Unix"))
161        {
162            /* This mirrors the way OMC creates the object file. */
163            String username = System.getenv("USER");
164            if(username == null)
165            {
166                username = "nobody";
167            }
168            fileName = "/tmp/openmodelica." + username + ".objid";
169        }
170        else if(os.equals("Windows"))
171        {
172            String temp = System.getenv("TMP");         
173            fileName = temp + "\\openmodelica.objid";
174        }
175       
176        logOMCStatus("will look for OMC object reference in '" 
177                + fileName + "'");
178       
179        return fileName;
180    }
181   
182    /**
183     * Start a new OMC server.
184     */
185    private static void startServer() throws ConnectException
186    {
187        String pathToOmc = null;
188
189        /*
190         * Path to omc (or omc.exe) can be found in the OPENMODELICAHOME
191         * variable.
192         */
193        String omHome = System.getenv("OPENMODELICAHOME");
194        if(omHome == null)
195        {
196            final String m = "Environment variable OPENMODELICAPATH not set";
197            logOMCStatus("Environment variable OPENMODELICAPATH not set,"+
198                    " don't know how to start OMC");
199            throw new ConnectException(m);
200        }
201       
202        if(os.equals("Unix"))
203        {
204            pathToOmc = omHome + "/omc";
205        }
206        else if(os.equals("Windows"))
207        {
208            pathToOmc = omHome + "\\omc.exe";
209        }
210
211        /*
212         * Delete old object reference file. We need to do this because we're
213         * checking if the file exists to determine if the server has started
214         * or not (further down).
215         */
216        File f = new File(getPathToObject());
217        if(f.exists())
218        {
219            logOMCStatus("removing old OMC object reference file");
220            f.delete();
221        }
222       
223        String command[] = { pathToOmc, "+d=interactiveCorba" };
224        try
225        {
226            logOMCStatus("running command " + command[0] + " " + command[1]);
227            Runtime.getRuntime().exec(command);
228            logOMCStatus("command run successfully");
229        }
230        catch(IOException e)
231        {
232            /*
233             * If we fail to start the compiler, maybe the executable is in
234             * the Compiler directory (if we've compiled the compiler from
235             * source). Try starting OMC from this secondary location.
236             */
237            logOMCStatus("error running command " + e.getMessage()
238                    + "\ntrying allternative path to the binary");
239            String secondaryPathToOmc = null;
240            try
241            {
242                if(os.equals("Unix"))
243                {
244                    secondaryPathToOmc = omHome + "/Compiler/omc";
245                }
246                else if(os.equals("Windows"))
247                {
248                    secondaryPathToOmc = omHome + "\\Compiler\\omc.exe";
249                }
250
251                command = 
252                    new String[]{secondaryPathToOmc, "+d=interactiveCorba"};
253                logOMCStatus("running command " 
254                        + command[0] + " " + command[1]);
255                Runtime.getRuntime().exec(command);
256                logOMCStatus("command run successfully");
257            }
258            catch(IOException ex)
259            {
260                logOMCStatus("error running command, giving up"); 
261                throw new ConnectException
262                    ("Unable to start Open Modelica Compiler. "
263                     + "Tried starting " + pathToOmc
264                     + " and " + secondaryPathToOmc);
265            }
266        }
267
268        /*
269         * Wait until the object exists on disk, but if it takes longer than
270         * 5 seconds, abort. (Very arbitrary 5 seconds..)
271         */
272        int ticks = 0;
273        while(!f.exists())
274        {
275            try
276            {
277                Thread.sleep(100);
278            }
279            catch(InterruptedException e)
280            {
281                // Ignore
282            }
283            ticks++;
284           
285            /* If we've waited for 5 seconds, abort the wait for OMC */
286            if(ticks > 50)
287            {
288                logOMCStatus("no OMC object reference file created after " + 
289                        "approximatly 5 seconds\n" +
290                        "it seems OMC does not want to come up, giving up");
291                throw new ConnectException
292                    ("Unable to start the Open Modelica Compiler. Waited for 5"
293                            +" seconds, but it didn't respond.");
294            }
295        }
296    }
297   
298    /**
299     * Initializes an ORB, converts the stringified OMC object to a real
300     * CORBA object, and then narrows that object to an OmcCommunication
301     * object.
302     */
303    private static void setupOmcc(String stringifiedObjectReference)
304    {
305        /* Can't remember why this is needed. But it is. */
306        String args[] = {null};
307       
308        ORB orb;
309        orb = ORB.init(args, null);
310       
311        /* Convert string to object. */
312        org.omg.CORBA.Object obj
313            = orb.string_to_object(stringifiedObjectReference);
314       
315        /* Convert object to OmcCommunication object. */
316        omcc = OmcCommunicationHelper.narrow(obj);
317    }
318   
319    /**
320     * @return the name of the operating system. If an unknown os is found,
321     * the default is Unix.
322     */
323    private static String getOs()
324    {
325        String osName = System.getProperty("os.name");
326        if(osName.contains("Linux"))
327        {
328            return "Unix";
329        }
330        else if(osName.contains("Windows"))
331        {
332            return "Windows";
333        }
334        else
335        {
336            ErrorManager.logWarning("'" + osName + "' is unsupported OS");
337            /* If the OS is not GNU/Linux or Windows, default to Unix */
338            return "Unix";
339        }
340    }
341
342    /**
343     * Initialize the communication with OMC
344     * @throws ConnectException if we're unable to start communicating with
345     * the server
346     */
347    private void init() throws ConnectException
348    {
349        /*
350         * Get type of operating system, used for finding object
351         * reference and starting OMC if the reference is faulty
352         */
353        os = getOs();
354       
355        /* See if an OMC server is already running */
356        File f = new File(getPathToObject());
357        String stringifiedObjectReference = null;
358        if(!f.exists())
359        {
360            /* If a server isn't running, start it */
361            logOMCStatus("no OMC object reference found");
362            startServer();
363        }
364        else
365        {
366            logOMCStatus("OMC object reference present," +
367                    " assuming OMC is running");
368        }
369       
370        /* Read in the CORBA OMC object from a file on disk */
371        stringifiedObjectReference = readObjectFromFile();
372
373        /*
374         * Setup up OMC object reference by initializing ORB and then
375         * converting the string object to a real CORBA object.
376         */
377        setupOmcc(stringifiedObjectReference);
378
379        try
380        {
381            /*
382             * Test the server by trying to send an expression to it.
383             * This might fail if the object reference found on disk didn't
384             * have a corresponding server running. If a server is missing,
385             * catch an exception and try starting a server.
386             */
387            logOMCStatus("trying to send expression to OMC");
388            omcc.sendExpression("1+1");
389            logOMCStatus("expression send successfully");
390        }
391        catch(org.omg.CORBA.COMM_FAILURE e)
392        {
393            /* Start server and set up omcc */
394            logOMCStatus("failed sending expression");
395            startServer();
396            stringifiedObjectReference = readObjectFromFile();
397            setupOmcc(stringifiedObjectReference);
398
399            try
400            {
401                /* Once again try to send an expression to OMC. If it fails this
402                 * time it's time to send back an exception to the caller of
403                 * this function. */
404                logOMCStatus("trying to send expression to OMC");
405                omcc.sendExpression("1+1");
406                logOMCStatus("expression send successfully");
407            }
408            catch(org.omg.CORBA.COMM_FAILURE x)
409            {
410                logOMCStatus("failed sending expression, giving up");
411                throw new ConnectException("Unable to start the OpenModelica"
412                        +" Compiler.");
413            }
414        }
415
416        hasInitialized = true;
417    }
418   
419    /**
420     * Send expression to OMC. If communication is not initialized, it
421     * is initialized here.
422     * @param exp the expression to send to OMC
423     * @throws ConnectException if we're unable to start communicating with
424     * the server
425     */
426    // TODO add synchronization so that two threads don't fudge up each others
427    // communication with OMC
428    // old synchronization aka 'private synchronized String sendExpression(String exp)'
429    // doesnt work when there is possibility of multiple instances of OMCProxy objects
430    private String sendExpression(String exp)
431        throws ConnectException
432    {
433        String retval = null;
434       
435        if(hasInitialized == false)
436        {
437            init();
438        }
439       
440        try
441        {
442            logOMCCall(exp);
443            retval = omcc.sendExpression(exp);
444            logOMCReply(retval);
445        }
446        catch(org.omg.CORBA.COMM_FAILURE x)
447        {
448            /* lost connection to OMC or something */
449            throw new ConnectException("Couldn't send expression to the "+
450                    "OpenModelica Compiler. Tried sending: " + exp);
451        }
452       
453        return retval;
454    }
455   
456    /**
457     * Logs the expression sent to OMC if the
458     * tracing flag (traceOMCCalls) is set
459     *
460     * @param expression the expression that is about to be sent to OMC
461     */
462    private static void logOMCCall(String expression)
463    {
464        if (!traceOMCCalls)
465        {
466            return;
467        }
468        System.out.println(">> " + expression);
469    }
470   
471    /**
472     * loggs the message conserning OMC status if the
473     * tracing flag traceOMCStatus is set
474     * @param message the message to log
475     */
476    private static void logOMCStatus(String message)
477    {
478        if (!traceOMCStatus)
479        {
480            return;
481        }
482        System.out.println(message);
483    }
484
485    /**
486     * Logs the reply received from OMC if
487     * the tracing flag (traceOMCCalls) is set
488     *
489     * @param reply the reply recieved from the OMC
490     */
491    private static void logOMCReply(String reply)
492    {
493        if (!traceOMCCalls)
494        {
495            return;
496        }
497
498        StringTokenizer tokenizer = new StringTokenizer(reply, "\n");
499       
500        while (tokenizer.hasMoreTokens())
501        {
502            System.out.println("<< " + tokenizer.nextToken());
503        }
504    }
505   
506    /**
507     * Get the classes contained in a class (a package is a class..)
508     *
509     *
510     * @param className full class name where to look for packages
511     * @return an array of subclasses defined (and loaded into OMC)
512     *  inside the class named className, but don't return packages in this
513     *  class. The results is returned as Vector of objects but objects
514     *  are actually String's.
515     * 
516     * @throws ConnectException
517     * @throws UnexpectedReplyException
518     * @throws InitializationException
519     */ 
520    public List getClassNames(String className)
521        throws ConnectException, UnexpectedReplyException
522    {
523        String retval = sendExpression("getClassNames("+className+")");
524       
525        return ModelicaParser.parseList(retval);
526    }
527
528    /**
529     * Gets the restriction type of a class.
530     *
531     * @param className fully qualified class name
532     * @return the restriction type of the class or Type.CLASS if
533     *         type can't be determined
534     * @throws ConnectException
535     */
536    public IModelicaClass.Type getRestrictionType(String className)
537        throws ConnectException
538    {
539        String reply = 
540            sendExpression("getClassRestriction(" + className + ")");
541       
542        /* remove " around the reply */
543        reply = reply.trim();
544        reply = reply.substring(1, reply.length()-1);
545       
546        return IModelicaClass.Type.parse(reply);
547    }
548   
549    /**
550     * Fetches the error string from OMC. This should be called after an "Error"
551     * is received.
552     * @return
553     * @throws ConnectException
554     */
555    private String getErrorString()
556        throws ConnectException
557    {
558        String res = sendExpression("getErrorString()");
559        if(res != null)
560        {
561            res = res.trim();
562            return res.substring(1, res.length() - 1);
563        }
564        else
565            return "";
566    }
567   
568
569    /**
570     * Tries to load file into OMC which causes it to be parsed and the syntax
571     * checked.
572     * @param file the file we want to load
573     * @return either returns the classes (and packages) found in the file or
574     * the error messages from OMC
575     * @throws ConnectException
576     * @throws UnexpectedReplyException
577     * @throws InitializationException
578     */
579    public ParseResults loadSourceFile(IFile file)
580        throws ConnectException, UnexpectedReplyException
581    {
582        ParseResults res = new ParseResults();
583
584        // We need to call this to empty the error queue in OMC
585        sendExpression("getMessagesString()");
586       
587        String fullName = file.getLocation().toString();
588        String retval = 
589            sendExpression("loadFileInteractiveQualified(\"" + fullName + "\")");
590       
591        String errorString = sendExpression("getMessagesString()");
592       
593        /*
594         * See if there were parse errors
595         */
596        if(retval.toLowerCase().contains("error"))
597        {           
598            res.setCompileErrors
599                (OMCParser.parseErrorString(getErrorString()));
600            res.setClassNames(new List());
601        }
602        /*
603         * file loaded and parse successsfully
604         */
605        else
606        {
607            res.setClassNames(ModelicaParser.parseList(retval));
608        }
609
610        /*
611         * If there were errors, but the compilation went through,
612         * collect the error messages. (Test if errorString == "")
613         */
614        errorString = errorString.trim();
615        if(errorString.equals("\"\"") == false)
616        {
617            errorString = errorString.substring(1, errorString.length() - 1);
618            res.setCompileErrors(OMCParser.parseErrorString(errorString));
619        }
620       
621        return res;
622    }
623
624    /**
625     * Gets the location (file, line number and column number) of a Modelica
626     * element.
627     * @param className the element we want to get location of
628     * @return an IElementLocation containing the file, line number and column
629     * number of the given class
630     * @throws ConnectException
631     * @throws UnexpectedReplyException
632     * @throws InvocationError
633     */
634    public ElementLocation getClassLocation(String className)
635        throws ConnectException, UnexpectedReplyException, InvocationError
636    {
637        String retval = sendExpression("getCrefInfo(" + className + ")");
638       
639        if(retval.contains("Error") || retval.contains("error"))
640        {
641            throw new 
642                InvocationError("fetching file position of " + className,
643                        "getCrefInfo(" + className + ")");
644        }
645       
646       
647        /*
648         * The getCrefInfo reply have the following format:
649         *
650         * <file path>,<line number>,<column number>
651         *
652         */
653
654        /* For some reason, the list returned doesn't contain curly braces. */
655        retval = retval.trim();
656        retval = "{" + retval + "}"; 
657
658        List tokens = ModelicaParser.parseList(retval);
659        int line;
660
661        try
662        {
663            line = Integer.parseInt(tokens.elementAt(2).toString());
664        }
665        catch (NumberFormatException e)
666        {
667            throw new 
668            UnexpectedReplyException("can't parse getCrefInfo() reply, "+
669                    "unexpected format");
670        }
671       
672        return new ElementLocation(tokens.elementAt(0).toString(), line);
673    }
674   
675    /**
676     * Queries the compiler if a particular modelica class/package is a package.
677     *
678     * @param className fully qualified name of the class/package
679     * @return true if className is a package false otherwise
680     * @throws ConnectException
681     */
682    public boolean isPackage(String className)
683        throws ConnectException
684    {
685        String retval = sendExpression("isPackage(" + className + ")");
686        return retval.contains("true");
687    }
688   
689    /**
690     * @param className
691     * @return
692     * @throws ConnectException
693     * @throws InvocationError
694     * @throws UnexpectedReplyException
695     */
696    public List getElementsInfo(String className)
697        throws ConnectException, InvocationError, UnexpectedReplyException
698    {
699        String retval = sendExpression("getElementsInfo("+ className +")");
700       
701        /*
702         * we need a efficient way to check if the result is
703         * humongosly huge list or 'Error' or maybe 'error'
704         */
705        for (int i = 0; i < retval.length(); i++)
706        {
707            if (retval.charAt(i) == '{')
708            {
709                /* we found the begining of the list, let's hope for the best */
710                return ModelicaParser.parseList(retval);       
711            }
712            else if (retval.charAt(i) == 'E' || retval.charAt(i) == 'e')
713            {
714                /*
715                 * this is the unreadable way to check if the retval
716                 * equals 'Error' or 'error'
717                 */
718                if (retval.substring(i+1,i+5).equals("rror"))
719                {
720                    throw new 
721                        InvocationError("fetching contents of " + className,
722                                "getElementsInfo("+ className +")");
723                }
724                else
725                {
726                    /* OMC returned someting wierd, panic mode ! */
727                    break;
728                }
729            }
730        }
731        /* we have no idea what OMC returned */
732        throw new UnexpectedReplyException("getElementsInfo("+ className +")" + 
733                        "replies:'" + retval + "'");
734    }
735
736   
737    public String getCompilerName()
738    {
739        return "OpenModelica Compiler";
740    }
741
742    /**
743     * Loads in the Modelica System Library and returns names of the top-level
744     * packages.
745     * 
746     * @throws ConnectException if we're unable to start communicating with
747     * the server
748     */ 
749    public String[] getStandardLibrary() throws ConnectException
750    {
751        if (!systemLibraryLoaded)
752        {
753            sendExpression("loadModel(Modelica)");
754            systemLibraryLoaded = true;
755        }
756
757        return standardLibraryPackages;
758    }
759}
Note: See TracBrowser for help on using the repository browser.