View Javadoc

1   /*
2    * $Id: AbstractCodeGenerator.java,v 1.5 2005/06/01 17:38:41 jlerner Exp $
3    *
4    * Copyright (c) 1999-2004, BBN Technologies, LLC.
5    * All rights reserved.
6    * http://www.daml.org/legal/opensource/bbn_license.html
7    */
8   
9   package com.bbn.swede.tools.codegenerator;
10  
11  import org.eclipse.core.resources.IProject;
12  import org.eclipse.core.resources.IResource;
13  import org.eclipse.core.runtime.CoreException;
14  import org.eclipse.core.runtime.IPath;
15  import org.eclipse.core.runtime.IProgressMonitor;
16  import org.eclipse.core.runtime.IStatus;
17  import org.eclipse.core.runtime.Status;
18  import org.eclipse.core.runtime.SubProgressMonitor;
19  import org.eclipse.core.runtime.jobs.Job;
20  import org.eclipse.jface.dialogs.MessageDialog;
21  import org.eclipse.swt.widgets.Shell;
22  import org.eclipse.ui.PlatformUI;
23  import org.eclipse.ui.actions.WorkspaceModifyOperation;
24  
25  import com.bbn.swede.core.IOWLDocument;
26  import com.bbn.swede.core.config.SWProjectInfo;
27  import com.bbn.swede.core.dom.OASTNode;
28  import com.bbn.swede.tools.ToolsPlugin;
29  
30  /***
31   * <p>An abstract base class that provides the essential
32   * functionality for code generators.  It provides persistence of generated code
33   * locations through project metadata, overwrite warnings, and a progress 
34   * monitor for the generation process.  Subclasses need only implement 
35   * #configureProject(IProject) and #performGeneration(IOWLDocument) to handle 
36   * project setup and do the actual code generation.</p>
37   * 
38   * <p>Extenders of the com.bbn.swede.tools.codeGenerator extension point must extend 
39   * this class.</p>
40   * @author jlerner
41   * @author tself
42   */
43  public abstract class AbstractCodeGenerator
44  {
45     /***
46      * The name to display in the user interface.
47      */
48     protected String _sDisplayName;
49  
50     /***
51      * The unique identifier of the generator.
52      */
53     protected String _sID;
54  
55     /***
56      * Implement configureProject to perform any setup operations that must be
57      * completed before generated code will be usable (i.e. adding natures,
58      * classpath setup, etc.)
59      * 
60      * @param project The project that will contain the generated code
61      */
62     protected abstract void configureProject(IProject project);
63  
64     private static final String S_MESSAGE_BEGIN = "The code generator \"";
65  
66     private static final String S_MESSAGE_MIDDLE = "\" has already been run for"
67        + " this ontology.  The existing generated code at ";
68  
69     private static final String S_MESSAGE_END = " will be overwritten if it is "
70        + "run again.  Would you like to proceed?";
71  
72     private static final String S_TITLE = "Generated Code Overwrite";
73  
74     private static final String[] AS_BUTTONS = {"OK", "Cancel", };
75  
76     private static final String S_ERROR_TITLE = "Cannot Generate Code";
77     private static final String S_ERROR_MESSAGE_BEGIN = 
78        " cannot be run because ";
79     private static final String S_ERROR_MESSAGE_END = " contains errors.  " 
80        + "Fix the errors, then try generating code again.";
81     private static final String[] AS_ERROR_BUTTONS = {"OK", };
82     private boolean isErrorFree(IOWLDocument document)
83     {
84        OASTNode[] unparseables = document.getDocumentInfo().getOAST()
85           .getRoot().getNodesOfType(OASTNode.UNPARSEABLE);
86        if (unparseables.length == 0)
87        {
88           return true;
89        }
90        
91        Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow()
92           .getShell();
93        String sMessage = _sDisplayName + S_ERROR_MESSAGE_BEGIN 
94           + document.getElementName() + S_ERROR_MESSAGE_END;
95        MessageDialog md = new MessageDialog(shell, S_ERROR_TITLE, null, sMessage,
96           MessageDialog.ERROR, AS_ERROR_BUTTONS, 0);
97        md.open();
98        
99        return false;
100    }
101    /***
102     * This method is not intended to be overriden in subclasses. Generate code
103     * for a given OWL document in a given project. This method takes care of the
104     * entire code generation process, including automatically checking for
105     * existing generated code, configuring the project, doing the actual
106     * generation, and setting the document's metadata to reflect the location of
107     * the generated file(s).
108     * 
109     * @param document The OWL document for which to generate code
110     */
111    public void generate(final IOWLDocument document)
112    { // made document 'final' so it can be passed to the Runnable
113       if (!isErrorFree(document))
114       {
115          return;
116       }
117       if (!checkExisting(document))
118       {
119          return;
120       }
121       
122       Job generateJob = new Job("Generating code...")
123       {
124          protected IStatus run(IProgressMonitor monitor)
125          {
126             IStatus toReturn = Status.OK_STATUS;
127             WorkspaceModifyOperation op = new WorkspaceModifyOperation()
128             {
129                public void execute(IProgressMonitor monitor) throws CoreException,
130                   InterruptedException
131                {
132                   runGeneration(document, monitor);
133                }
134             };
135             try
136             {
137                op.run(monitor);
138             }
139             catch(Exception e)
140             {
141                toReturn = new Status(IStatus.ERROR, ToolsPlugin.getID(),
142                   IStatus.OK, "Error encountered during code generation...", e);
143             }
144             return toReturn;
145          }   
146       };
147       
148       generateJob.setUser(true);
149       generateJob.schedule();
150    }
151 
152    /***
153     * Checks if a document has already had the generator run against it.  If
154     * it has, the user is prompted to authorize the overwrite.
155     * @param document The document to check for already-generated code
156     * @return <code>true</code> if it's OK to proceed with the code generation, 
157     *         <code>false</code> if it should be cancelled.
158     */
159    public boolean checkExisting(IOWLDocument document)
160    {
161       IResource res = document.getCorrespondingResource();
162       IPath path = document.getGeneratedCodeLocation(_sID);
163       if(path != null)
164       {
165          String sMessage = S_MESSAGE_BEGIN + _sDisplayName + S_MESSAGE_MIDDLE
166             + path.toString() + S_MESSAGE_END;
167          Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow()
168             .getShell();
169          MessageDialog md = new MessageDialog(shell, S_TITLE, null, sMessage,
170             MessageDialog.WARNING, AS_BUTTONS, 0);
171          if(md.open() == 1)
172          {
173             return false;
174          }
175          //TODO: forcibly delete the existing generated interfaces?
176       }
177       return true;
178    }
179    
180    /***
181     * Runs the heavy lifting of generating the interfaces. This method is called
182     * by <code>generate</code> inside a <code>WorkspaceModifyOperation</code>
183     * @param document The OWL document for which to generate code
184     * @param monitor A progress monitor to update as work is performed, or
185     *                <code>null</code> to generate without a progress bar
186     * @throws CoreException if generation fails
187     * @throws InterruptedException if the user cancels the operation
188     */
189    public void runGeneration(IOWLDocument document, IProgressMonitor monitor)
190       throws CoreException, InterruptedException
191    {
192       
193       if(monitor != null)
194       {
195          monitor.beginTask("Generating Interface", 1000);
196       }
197 
198       //For the existance check that must have already happened by now
199       if(monitor != null)
200       {
201          monitor.worked(100); // 100/1000
202          if(monitor.isCanceled())
203          {
204             throw new InterruptedException();
205          }
206       }
207       
208       // project must be 'final' to be used by the
209       // WorkspaceModifyOperation
210       final IProject project = document.getResource().getProject();
211       configureProject(project);
212       if(monitor != null)
213       {
214          monitor.worked(200); // 300/1000
215          if(monitor.isCanceled())
216          {
217             throw new InterruptedException();
218          }
219       }
220 
221       IPath path = performGeneration(document, project, (monitor == null) ? null
222          : new SubProgressMonitor(monitor, 600)); // 900/1000
223       if(monitor != null && monitor.isCanceled())
224       {
225          throw new InterruptedException();
226       }
227       document.setGeneratedCodeLocation(_sID, path);
228 
229       //The project file has to be forcibly rebuilt to ensure that it
230       //reflects the current location of generated files. Without this,
231       //if you close the workbench immediately after running a code
232       //generator, it never goes into the persisted metadata and you
233       //won't get the overwrite warning if you attempt to re-run it.
234       SWProjectInfo.refreshInfoFile(project);
235       if(monitor != null)
236       {
237          monitor.worked(100); // 1000/1000
238          monitor.done();
239       }
240    }
241 
242    /***
243     * Runs the code generator for the given OWL document and project.
244     * 
245     * @param document The OWL document for which to generate code
246     * @param project The project that generated files should be added to
247     * @param monitor A progress monitor to update as work is done, or 
248     *               <code>null</code>.
249     * @return a project-relative path for the generated file. If multiple files
250     *         are generated, the path may refer to a directory which contains
251     *         the generated files.
252     * @throws CoreException if generation fails
253     * @throws InterruptedException if generation is cancelled by the user
254     */
255    protected abstract IPath performGeneration(IOWLDocument document,
256       IProject project, IProgressMonitor monitor) 
257       throws CoreException, InterruptedException;
258 
259    /***
260     * Retrieves the display name for the code generator.  Display names are
261     * short strings suitable for use in GUIs.
262     * @return The code generator's display name
263     */
264    public String getDisplayName()
265    {
266       return _sDisplayName;
267    }
268 
269    /***
270     * Sets the display name for the code generator.  Display names should be
271     * relatively short, as they are intended for use in GUIs.
272     * @param sDisplayName The new display name for the code generator
273     */
274    public void setDisplayName(String sDisplayName)
275    {
276       _sDisplayName = sDisplayName;
277    }
278 
279    /***
280     * Retrieves the unique ID of the code generator.  Typically, this will
281     * be the ID of the com.bbn.swede.ui.codeGenerator extension. 
282     * @return The code generator ID
283     */
284    public String getID()
285    {
286       return _sID;
287    }
288 
289    /***
290     * Sets the ID of the code generator.  IDs must be unique.
291     * @param sID The new ID for the code generator.
292     */
293    public void setID(String sID)
294    {
295       _sID = sID;
296    }
297 }