View Javadoc

1   /* ====================================================================
2    *   Copyright 2003-2005 Fabrizio Giustina.
3    *
4    *   Licensed under the Apache License, Version 2.0 (the "License");
5    *   you may not use this file except in compliance with the License.
6    *   You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *   Unless required by applicable law or agreed to in writing, software
11   *   distributed under the License is distributed on an "AS IS" BASIS,
12   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *   See the License for the specific language governing permissions and
14   *   limitations under the License.
15   * ====================================================================
16   */
17  package net.sf.commonclipse;
18  
19  import java.text.MessageFormat;
20  import java.util.HashMap;
21  import java.util.Iterator;
22  import java.util.Map;
23  import java.util.regex.Pattern;
24  
25  import org.eclipse.core.resources.IFile;
26  import org.eclipse.core.resources.IResource;
27  import org.eclipse.core.resources.ResourcesPlugin;
28  import org.eclipse.core.runtime.IProgressMonitor;
29  import org.eclipse.core.runtime.IStatus;
30  import org.eclipse.core.runtime.Preferences;
31  import org.eclipse.jdt.core.Flags;
32  import org.eclipse.jdt.core.IBuffer;
33  import org.eclipse.jdt.core.ICompilationUnit;
34  import org.eclipse.jdt.core.IField;
35  import org.eclipse.jdt.core.IJavaElement;
36  import org.eclipse.jdt.core.IMethod;
37  import org.eclipse.jdt.core.ISourceReference;
38  import org.eclipse.jdt.core.IType;
39  import org.eclipse.jdt.core.ITypeHierarchy;
40  import org.eclipse.jdt.core.JavaCore;
41  import org.eclipse.jdt.core.JavaModelException;
42  import org.eclipse.jdt.core.ToolFactory;
43  import org.eclipse.jdt.core.formatter.CodeFormatter;
44  import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
45  import org.eclipse.jface.dialogs.MessageDialog;
46  import org.eclipse.jface.dialogs.ProgressMonitorDialog;
47  import org.eclipse.jface.text.BadLocationException;
48  import org.eclipse.jface.text.Document;
49  import org.eclipse.swt.widgets.Shell;
50  import org.eclipse.text.edits.MalformedTreeException;
51  import org.eclipse.text.edits.TextEdit;
52  
53  
54  /***
55   * @author fgiust
56   * @version $Revision: 1.7 $ ($Author: fgiust $)
57   */
58  public abstract class Generator
59  {
60  
61      /***
62       * line separator.
63       */
64      private static final String LINE_SEPARATOR = System.getProperty("line.separator"); //$NON-NLS-1$
65  
66      /***
67       * Generates the appropriate method in <code>type</code>.
68       * @param type IType
69       * @param shell Shell
70       */
71      public void generate(IType type, Shell shell)
72      {
73  
74          ICompilationUnit cu = (ICompilationUnit) type.getAncestor(IJavaElement.COMPILATION_UNIT);
75  
76          // first check if file is writable
77          IResource resource;
78          try
79          {
80              resource = cu.getCorrespondingResource();
81          }
82          catch (JavaModelException e)
83          {
84              MessageDialog.openError(shell, CCMessages.getString("Generator.errortitle"), e.getMessage()); //$NON-NLS-1$
85              return;
86          }
87          if (resource != null && resource.getResourceAttributes().isReadOnly())
88          {
89              IStatus status = ResourcesPlugin.getWorkspace().validateEdit(new IFile[]{(IFile) resource}, shell);
90  
91              if (!status.isOK())
92              {
93                  MessageDialog.openError(shell, CCMessages.getString("Generator.errortitle"), CCMessages //$NON-NLS-1$
94                      .getString("Generator.readonly")); //$NON-NLS-1$
95                  return;
96              }
97          }
98  
99          resource = null;
100 
101         if (!validate(type, shell))
102         {
103             return;
104         }
105 
106         ProgressMonitorDialog progressDialog = new ProgressMonitorDialog(shell);
107         progressDialog.open();
108         try
109         {
110 
111             IProgressMonitor monitor = progressDialog.getProgressMonitor();
112             generateMethod(type, cu, shell, monitor);
113         }
114         catch (JavaModelException ex)
115         {
116             MessageDialog.openError(shell, CCMessages.getString("Generator.errortitle"), ex.getMessage()); //$NON-NLS-1$
117         }
118 
119         progressDialog.close();
120     }
121 
122     /***
123      * Checks if a corresponding method already exists and prompt the user for replacing it.
124      * @param type IType
125      * @param shell Shell
126      * @return <code>true</code> if the method doesn't exists or the user has choosen to overwrite it
127      */
128     protected boolean validate(IType type, Shell shell)
129     {
130 
131         IMethod method = getExistingMethod(type);
132 
133         if (method != null && method.exists())
134         {
135             boolean dontAsk = CCPluginPreferences.getPreferences().dontAskOnOverwrite();
136 
137             if (dontAsk
138                 || MessageDialog.openConfirm(shell, CCPlugin.PLUGIN_NAME, MessageFormat.format(CCMessages
139                     .getString("Generator.methodexists"), //$NON-NLS-1$
140                     new Object[]{getMethodName(), type.getElementName()})))
141             {
142                 try
143                 {
144                     method.delete(true, null);
145                     return true;
146                 }
147                 catch (JavaModelException e)
148                 {
149                     MessageDialog.openError(shell, CCMessages.getString("Generator.errortitle"), //$NON-NLS-1$
150                         MessageFormat.format(CCMessages.getString("Generator.unabletodelete"), //$NON-NLS-1$
151                             new Object[]{getMethodName(), e.getMessage()}));
152                 }
153             }
154             return false;
155         }
156 
157         return true;
158     }
159 
160     /***
161      * Generates the method by:
162      * <ul>
163      * <li>call createMethod</li>
164      * <li>format the given method</li>
165      * <li>add it to type</li>
166      * <li>call addImports</li>
167      * </ul>.
168      * @param type IType
169      * @param cu compilation unit
170      * @param shell Shell for messages
171      * @param monitor progress monitor, updated during processing
172      * @throws JavaModelException any exception in method generation
173      */
174     public void generateMethod(IType type, ICompilationUnit cu, Shell shell, IProgressMonitor monitor)
175         throws JavaModelException
176     {
177         String className = type.getElementName();
178 
179         String title = MessageFormat.format(CCMessages.getString("Generator.generating"), //$NON-NLS-1$
180             new Object[]{className});
181         monitor.beginTask(title, 100);
182         monitor.worked(10);
183 
184         monitor.setTaskName(title + CCMessages.getString("Generator.parsing")); //$NON-NLS-1$
185         String src = createMethod(type);
186         monitor.worked(30);
187 
188         monitor.setTaskName(title + CCMessages.getString("Generator.formatting")); //$NON-NLS-1$
189         Document document = new Document(src);
190 
191         TextEdit text = ToolFactory.createCodeFormatter(null).format(
192             CodeFormatter.K_UNKNOWN,
193             src,
194             0,
195             src.length(),
196             getIndentUsed(type, cu) + 1,
197             null);
198 
199         try
200         {
201             text.apply(document);
202         }
203         catch (MalformedTreeException ex)
204         {
205             MessageDialog.openError(shell, CCMessages.getString("Generator.errortitle"), ex.getMessage()); //$NON-NLS-1$
206         }
207         catch (BadLocationException ex)
208         {
209             MessageDialog.openError(shell, CCMessages.getString("Generator.errortitle"), ex.getMessage()); //$NON-NLS-1$
210         }
211 
212         monitor.worked(20);
213 
214         monitor.setTaskName(title + CCMessages.getString("Generator.adding")); //$NON-NLS-1$
215         type.createMethod(document.get() + LINE_SEPARATOR, null, false, null);
216 
217         monitor.worked(20);
218 
219         monitor.setTaskName(title + CCMessages.getString("Generator.imports")); //$NON-NLS-1$
220         addImports(type);
221         monitor.worked(20);
222 
223         monitor.done();
224     }
225 
226     /***
227      * Evaluates the indention used by a Java element.
228      * @param elem Java element
229      * @param cu compilation unit
230      * @return indentation level
231      * @throws JavaModelException model exception when trying to access source
232      */
233     public int getIndentUsed(IJavaElement elem, ICompilationUnit cu) throws JavaModelException
234     {
235         if (elem instanceof ISourceReference)
236         {
237 
238             if (cu != null)
239             {
240                 IBuffer buf = cu.getBuffer();
241                 int offset = ((ISourceReference) elem).getSourceRange().getOffset();
242                 int i = offset;
243                 // find beginning of line
244                 while (i > 0 && !isLineDelimiterChar(buf.getChar(i - 1)))
245                 {
246                     i--;
247                 }
248 
249                 return computeIndent(buf.getText(i, offset - i));
250             }
251         }
252         return 0;
253     }
254 
255     /***
256      * Line delimiter chars are '\n' and '\r'.
257      * @param ch char
258      * @return <code>true</code> if ch is '\n' or '\r'
259      */
260     private boolean isLineDelimiterChar(char ch)
261     {
262         return ch == '\n' || ch == '\r';
263     }
264 
265     /***
266      * Returns the indent of the given string.
267      * @param line the text line
268      * @return indent level
269      */
270     public int computeIndent(String line)
271     {
272         Preferences preferences = JavaCore.getPlugin().getPluginPreferences();
273         int tabWidth = preferences.getInt(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE);
274 
275         int result = 0;
276         int blanks = 0;
277         int size = line.length();
278         for (int i = 0; i < size; i++)
279         {
280             char c = line.charAt(i);
281             if (c == '\t')
282             {
283                 result++;
284                 blanks = 0;
285             }
286             else if (Character.isWhitespace(c) && !(c == '\n' || c == '\r'))
287             {
288                 blanks++;
289                 if (blanks == tabWidth)
290                 {
291                     result++;
292                     blanks = 0;
293                 }
294             }
295             else
296             {
297                 return result;
298             }
299         }
300         return result;
301     }
302 
303     /***
304      * Returns the generated method name.
305      * @return String method name
306      */
307     protected abstract String getMethodName();
308 
309     /***
310      * Creates the method for the ITYPE type.
311      * @param type Itype
312      * @return Method String
313      * @throws JavaModelException exception in creating method
314      */
315     protected abstract String createMethod(IType type) throws JavaModelException;
316 
317     /***
318      * Returns the existing method.
319      * @param type IType
320      * @return IMethod
321      */
322     protected abstract IMethod getExistingMethod(IType type);
323 
324     /***
325      * Adds required imports to type.
326      * @param type IType
327      * @throws JavaModelException exception in adding imports
328      */
329     protected abstract void addImports(IType type) throws JavaModelException;
330 
331     /***
332      * Iterates on fields and call getFieldString() on any match not in the configurable excluded list.
333      * @param type IType
334      * @return String
335      * @throws JavaModelException exception in analyzing fields
336      */
337     protected String buildAppenderList(IType type) throws JavaModelException
338     {
339         // temporary map for caching type and supertypes fields and avoid duplicated fields
340         Map fields = buildFieldMap(type);
341 
342         // start building method body
343         StringBuffer buffer = new StringBuffer();
344         Iterator fieldsIterator = fields.keySet().iterator();
345 
346         while (fieldsIterator.hasNext())
347         {
348             String fieldName = (String) fieldsIterator.next();
349 
350             // only add field if not excluded by user preferences
351             if (!isExcluded(fieldName))
352             {
353                 buffer.append(getFieldAppender(fieldName, fieldName));
354             }
355         }
356 
357         return buffer.toString();
358 
359     }
360 
361     /***
362      * Returns a Set containing all the names of fields visible by this type.
363      * @param type IType
364      * @return Map containg field names - IField objects
365      * @throws JavaModelException exception in analyzing type
366      */
367     protected Map buildFieldMap(IType type) throws JavaModelException
368     {
369         Map fieldNames = new HashMap();
370 
371         IField[] fields = type.getFields();
372 
373         for (int j = 0; j < fields.length; j++)
374         {
375             IField field = fields[j];
376             int flags = field.getFlags();
377 
378             if (!Flags.isStatic(flags))
379             {
380                 fieldNames.put(field.getElementName(), field);
381             }
382         }
383 
384         // get all the supertypes to look for public and protected fields
385         ITypeHierarchy hierarchy = type.newSupertypeHierarchy(null);
386         IType[] types = hierarchy.getSupertypes(type);
387 
388         for (int j = 0; j < types.length; j++)
389         {
390             IField[] superFields = types[j].getFields();
391 
392             for (int x = 0; x < superFields.length; x++)
393             {
394                 IField field = superFields[x];
395                 int flags = field.getFlags();
396 
397                 // no static and private
398                 if (!Flags.isStatic(flags) && !Flags.isPrivate(flags))
399                 {
400                     fieldNames.put(field.getElementName(), field);
401                 }
402             }
403         }
404         return fieldNames;
405     }
406 
407     /***
408      * Checks if a given field should be excluded from generated method.
409      * @param fieldName field/property name
410      * @return <code>true</code> if the field should not be included in generathed method
411      */
412     protected boolean isExcluded(String fieldName)
413     {
414         Pattern exclusion = CCPluginPreferences.getPreferences().getExcludedFielsPattern();
415         return exclusion.matcher(fieldName).matches();
416     }
417 
418     /***
419      * get the "append" statement for a field.
420      * @param fieldName name of the field
421      * @param accessor can be different for fieldname
422      * @return String
423      */
424     protected abstract String getFieldAppender(String fieldName, String accessor);
425 
426 }