001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.xml.tools.java.generator;
043:
044: import com.sun.source.tree.AnnotationTree;
045: import com.sun.source.tree.ClassTree;
046: import com.sun.source.tree.ExpressionTree;
047: import com.sun.source.tree.MemberSelectTree;
048: import com.sun.source.tree.MethodTree;
049: import com.sun.source.tree.ModifiersTree;
050: import com.sun.source.tree.Tree;
051: import com.sun.source.tree.TypeParameterTree;
052: import com.sun.source.tree.VariableTree;
053: import java.io.IOException;
054: import java.util.Collections;
055: import java.util.EnumSet;
056: import java.util.Iterator;
057: import java.util.List;
058: import java.util.Map;
059: import java.util.Set;
060: import javax.lang.model.element.ExecutableElement;
061: import javax.lang.model.element.Modifier;
062: import javax.lang.model.element.TypeElement;
063: import javax.lang.model.type.TypeKind;
064: import javax.lang.model.type.TypeMirror;
065: import org.netbeans.api.java.source.TreeMaker;
066: import org.netbeans.api.java.source.WorkingCopy;
067: import org.openide.filesystems.FileObject;
068: import org.openide.filesystems.FileSystem;
069: import org.openide.filesystems.Repository;
070: import org.openide.loaders.DataFolder;
071: import org.openide.loaders.DataObject;
072: import org.openide.util.Parameters;
073:
074: /**
075: * <code>GenerationUtils</code> is a helper class for creating classes,
076: * methods, variables, annotations and types using the Java source model.
077: *
078: * @author Andrei Badea
079: */
080: public final class GenerationUtils {
081:
082: // PENDING use CharSequence instead of String where possible
083:
084: /**
085: * The templates for regular Java class and interface.
086: */
087: static final String CLASS_TEMPLATE = "Templates/Classes/Class.java"; // NOI18N
088: static final String INTERFACE_TEMPLATE = "Templates/Classes/Interface.java"; // NOI18N
089:
090: private final WorkingCopy copy;
091:
092: // <editor-fold desc="Constructors and factory methods">
093:
094: private GenerationUtils(WorkingCopy copy) {
095: this .copy = copy;
096: }
097:
098: /**
099: * Creates a new instance of <code>GenerationUtils</code>.
100: *
101: * @param copy a <code>WorkingCopy</code>. It must be in {@link Phase#RESOLVED}.
102: * @return a new instance of <code>GenerationUtils</code>.
103: */
104: public static GenerationUtils newInstance(WorkingCopy copy) {
105: Parameters.notNull("copy", copy); // NOI18N
106: return new GenerationUtils(copy);
107: }
108:
109: // </editor-fold>
110:
111: // <editor-fold desc="Public static methods">
112:
113: /**
114: * Creates a new Java class based on the default template for classes.
115: *
116: * @param targetFolder the folder the new class should be created in;
117: * cannot be null.
118: * @param className the name of the new class (a valid Java identifier);
119: * cannot be null.
120: * @param javadoc the new class's Javadoc; can be null.
121: * @return the FileObject for the new Java class; never null.
122: * @throws IOException if an error occurred while creating the class.
123: */
124: public static FileObject createClass(FileObject targetFolder,
125: String className, final String javadoc) throws IOException {
126: return createClass(CLASS_TEMPLATE, targetFolder, className,
127: javadoc, Collections.emptyMap());
128: }
129:
130: /**
131: * Creates a new Java class based on the provided template.
132: *
133: * @param template the template to base the new class on.
134: * @param targetFolder the folder the new class should be created in;
135: * cannot be null.
136: * @param className the name of the new class (a valid Java identifier);
137: * cannot be null.
138: * @param javadoc the new class's Javadoc; can be null.
139: * @param parameters map of named objects that are going to be used when creating the new object
140: * @return the FileObject for the new Java class; never null.
141: * @throws IOException if an error occurred while creating the class.
142: */
143: public static FileObject createClass(String template,
144: FileObject targetFolder, String className,
145: final String javadoc, Map parameters) throws IOException {
146: Parameters.notNull("template", template); // NOI18N
147: Parameters.notNull("targetFolder", targetFolder); // NOI18N
148: Parameters.javaIdentifier("className", className); // NOI18N
149:
150: FileObject classFO = createDataObjectFromTemplate(template,
151: targetFolder, className, parameters).getPrimaryFile();
152: // JavaSource javaSource = JavaSource.forFileObject(classFO);
153: // final boolean[] commit = { false };
154: // ModificationResult modification = javaSource.runModificationTask(new AbstractTask<WorkingCopy>() {
155: // public void run(WorkingCopy copy) throws IOException {
156: // GenerationUtils genUtils = GenerationUtils.newInstance(copy);
157: // if (javadoc != null) {
158: // genUtils.setJavadoc(copy, mainType, javadoc);
159: // }
160: // }
161: // });
162: // if (commit[0]) {
163: // modification.commit();
164: // }
165:
166: return classFO;
167: }
168:
169: /**
170: * Creates a new Java class based on the default template for interfaces.
171: *
172: * @param targetFolder the folder the new interface should be created in;
173: * cannot be null.
174: * @param interfaceName the name of the new interface (a valid Java identifier);
175: * cannot be null.
176: * @param javadoc the new interface's Javadoc; can be null.
177: * @return the FileObject for the new Java interface; never null.
178: * @throws IOException if an error occurred while creating the class.
179: */
180: public static FileObject createInterface(FileObject targetFolder,
181: String interfaceName, final String javadoc)
182: throws IOException {
183: return createClass(INTERFACE_TEMPLATE, targetFolder,
184: interfaceName, javadoc, Collections.emptyMap());
185: }
186:
187: // </editor-fold>
188:
189: // <editor-fold defaultstate="collapsed" desc="Non-public static methods">
190:
191: /**
192: * Creates a data object from a given template path in the system
193: * file system.
194: *
195: * @return the <code>DataObject</code> of the newly created file.
196: * @throws IOException if an error occured while creating the file.
197: */
198: private static DataObject createDataObjectFromTemplate(
199: String template, FileObject targetFolder,
200: String targetName, Map parameters) throws IOException {
201: assert template != null;
202: assert targetFolder != null;
203: assert targetName != null && targetName.trim().length() > 0;
204:
205: FileSystem defaultFS = Repository.getDefault()
206: .getDefaultFileSystem();
207: FileObject templateFO = defaultFS.findResource(template);
208: DataObject templateDO = DataObject.find(templateFO);
209: DataFolder dataFolder = DataFolder.findFolder(targetFolder);
210: return templateDO.createFromTemplate(dataFolder, targetName,
211: parameters);
212: }
213:
214: // </editor-fold>
215:
216: // <editor-fold desc="Public methods">
217:
218: public Tree createType(String typeName, TypeElement scope) {
219: TreeMaker make = getTreeMaker();
220: TypeKind primitiveTypeKind = null;
221: if ("boolean".equals(typeName)) { // NOI18N
222: primitiveTypeKind = TypeKind.BOOLEAN;
223: } else if ("byte".equals(typeName)) { // NOI18N
224: primitiveTypeKind = TypeKind.BYTE;
225: } else if ("short".equals(typeName)) { // NOI18N
226: primitiveTypeKind = TypeKind.SHORT;
227: } else if ("int".equals(typeName)) { // NOI18N
228: primitiveTypeKind = TypeKind.INT;
229: } else if ("long".equals(typeName)) { // NOI18N
230: primitiveTypeKind = TypeKind.LONG;
231: } else if ("char".equals(typeName)) { // NOI18N
232: primitiveTypeKind = TypeKind.CHAR;
233: } else if ("float".equals(typeName)) { // NOI18N
234: primitiveTypeKind = TypeKind.FLOAT;
235: } else if ("double".equals(typeName)) { // NOI18N
236: primitiveTypeKind = TypeKind.DOUBLE;
237: } else if ("void".equals(typeName)) { // NOI18N
238: primitiveTypeKind = TypeKind.VOID;
239: }
240: if (primitiveTypeKind != null) {
241: return getTreeMaker().PrimitiveType(primitiveTypeKind);
242: }
243: Tree typeTree = tryCreateQualIdent(typeName);
244: if (typeTree == null) {
245: // XXX does not handle imports; temporary until issue 102149 is fixed
246: TypeMirror typeMirror = copy.getTreeUtilities().parseType(
247: typeName, scope);
248: typeTree = make.Type(typeMirror);
249: }
250: return typeTree;
251: }
252:
253: public ModifiersTree createModifiers(Modifier modifier) {
254: return getTreeMaker().Modifiers(EnumSet.of(modifier),
255: Collections.emptyList());
256: }
257:
258: public ClassTree ensureNoArgConstructor(ClassTree classTree) {
259: TypeElement typeElement = SourceUtils.classTree2TypeElement(
260: copy, classTree);
261: if (typeElement == null) {
262: throw new IllegalArgumentException(
263: "No TypeElement for ClassTree "
264: + classTree.getSimpleName());
265: }
266: ExecutableElement constructor = SourceUtils
267: .getNoArgConstructor(copy, typeElement);
268: MethodTree constructorTree = constructor != null ? copy
269: .getTrees().getTree(constructor) : null;
270: MethodTree newConstructorTree = null;
271: TreeMaker make = getTreeMaker();
272: if (constructor != null) {
273: if (!constructor.getModifiers().contains(Modifier.PUBLIC)) {
274: ModifiersTree oldModifiersTree = constructorTree
275: .getModifiers();
276: Set newModifiers = EnumSet.of(Modifier.PUBLIC);
277: // for (Modifier modifier : oldModifiersTree.getFlags()) {
278: // if (!Modifier.PROTECTED.equals(modifier) && !Modifier.PRIVATE.equals(modifier)) {
279: // newModifiers.add(modifier);
280: // }
281: //}
282: newConstructorTree = make.Constructor(make
283: .Modifiers(newModifiers), constructorTree
284: .getTypeParameters(), constructorTree
285: .getParameters(), constructorTree.getThrows(),
286: constructorTree.getBody());
287: }
288: } else {
289: newConstructorTree = make.Constructor(
290: createModifiers(Modifier.PUBLIC), Collections
291: .emptyList(), Collections.emptyList(),
292: Collections.emptyList(), "{ }"); // NOI18N
293: }
294: ClassTree newClassTree = classTree;
295: if (newConstructorTree != null) {
296: if (constructorTree != null) {
297: newClassTree = make.removeClassMember(newClassTree,
298: constructorTree);
299: }
300: newClassTree = make.addClassMember(newClassTree,
301: newConstructorTree);
302: }
303: return newClassTree;
304: }
305:
306: /**
307: * Creates a constructor which assigns its parameters to fields with the
308: * same names. For example it can be used to generate:
309: *
310: * <pre>
311: * public void Constructor(String field1, Object field2) {
312: * this.field1 = field1;
313: * this.field2 = field2;
314: * }
315: * </pre>
316: *
317: * @param modifiersTree the constructor modifiers.
318: * @param constructorName the constructor name; cannot be null.
319: * @param parameters the constructor parameters; cannot be null.
320: * @return the new constructor; never null.
321: */
322: public MethodTree createAssignmentConstructor(
323: ModifiersTree modifiersTree, String constructorName,
324: List parameters) {
325: Parameters.notNull("modifiersTree", modifiersTree);
326: Parameters.javaIdentifier("constructorName", constructorName); // NOI18N
327: Parameters.notNull("parameters", parameters); // NOI18N
328:
329: StringBuilder body = new StringBuilder(parameters.size() * 30);
330: body.append("{"); // NOI18N
331: for (int i = 0; i < parameters.size(); i++) {
332: VariableTree parameter = (VariableTree) parameters.get(i);
333: String parameterName = parameter.getName().toString();
334: body.append("this." + parameterName + " = " + parameterName
335: + ";"); // NOI18N
336: }
337: body.append("}"); // NOI18N
338:
339: TreeMaker make = getTreeMaker();
340: return make.Constructor(modifiersTree, Collections.emptyList(),
341: parameters, Collections.emptyList(), body.toString());
342: }
343:
344: public MethodTree createConstructor(ModifiersTree modifiersTree,
345: String constructorName, List parameters, String body) {
346: Parameters.notNull("modifiersTree", modifiersTree);
347: Parameters.javaIdentifier("constructorName", constructorName); // NOI18N
348: Parameters.notNull("parameters", parameters); // NOI18N
349:
350: TreeMaker make = getTreeMaker();
351: return make.Constructor(modifiersTree, Collections.emptyList(),
352: parameters, Collections.emptyList(), body);
353: }
354:
355: /**
356: * Creates a new field.
357: *
358: * @param scope the scope in which to create the field (will be e.g. used
359: * to parse <code>fieldType</code>).
360: * @param modifiersTree the field modifiers; cannot be null.
361: * @param fieldType the fully-qualified name of the field type; cannot be null.
362: * @param fieldName the field name; cannot be null.
363: * @param expressionTree expression to initialize the field; can be null.
364: * @return the new field; never null.
365: */
366: public VariableTree createField(TypeElement scope,
367: ModifiersTree modifiersTree, String fieldName,
368: String fieldType, ExpressionTree expressionTree) {
369: Parameters.notNull("modifiersTree", modifiersTree); // NOI18N
370: Parameters.javaIdentifier("fieldName", fieldName); // NOI18N
371: Parameters.notNull("fieldType", fieldType); // NOI18N
372:
373: return getTreeMaker().Variable(modifiersTree, fieldName,
374: createType(fieldType, scope), expressionTree);
375: }
376:
377: /**
378: * Creates a new variable (a <code>VariableTree</code> with no
379: * modifiers nor initializer).
380: *
381: * @param scope the scope in which to create the variable (will be e.g. used
382: * to parse <code>variableType</code>).
383: * @param variableType the fully-qualified name of the variable type; cannot be null.
384: * @param variableName the variable name; cannot be null.
385: * @return the new variable; never null.
386: */
387: public VariableTree createVariable(TypeElement scope,
388: String variableName, String variableType) {
389: Parameters.javaIdentifier("variableName", variableName); // NOI18N
390: Parameters.notNull("variableType", variableType); // NOI18N
391:
392: return createField(scope, createEmptyModifiers(), variableName,
393: variableType, null);
394: }
395:
396: /**
397: * Creates a new variable (a <code>VariableTree</code> with no
398: * modifiers nor initializer).
399: *
400: * @param variableType the variable type; cannot be null.
401: * @param variableName the variable name; cannot be null.
402: * @return the new variable; never null.
403: */
404: public VariableTree createVariable(String variableName,
405: Tree variableType) {
406: Parameters.javaIdentifier("variableName", variableName); // NOI18N
407: Parameters.notNull("variableType", variableType); // NOI18N
408:
409: return getTreeMaker().Variable(createEmptyModifiers(),
410: variableName, variableType, null);
411: }
412:
413: /**
414: * Inserts the given fields in the given class after any fields already existing
415: * in the class (if any, otherwise the fields are inserted at the beginning
416: * of the class).
417: *
418: * @param classTree the class to add fields to; cannot be null.
419: * @param fieldTrees the fields to be added; cannot be null.
420: * @return the class containing the new fields; never null.
421: */
422: public ClassTree addClassFields(ClassTree classTree, List fieldTrees) {
423: Parameters.notNull("classTree", classTree); // NOI18N
424: Parameters.notNull("fieldTrees", fieldTrees); // NOI18N
425:
426: int firstNonFieldIndex = 0;
427: Iterator memberTrees = classTree.getMembers().iterator();
428: while (memberTrees.hasNext()
429: && ((Tree) memberTrees.next()).getKind() == Tree.Kind.VARIABLE) {
430: firstNonFieldIndex++;
431: }
432: TreeMaker make = getTreeMaker();
433: ClassTree newClassTree = classTree;
434: for (int i = 0; i < fieldTrees.size(); i++) {
435: VariableTree fieldTree = (VariableTree) fieldTrees.get(i);
436: newClassTree = make.insertClassMember(newClassTree,
437: firstNonFieldIndex, fieldTree);
438: firstNonFieldIndex++;
439: }
440: return newClassTree;
441: }
442:
443: // PENDING addClassConstructors(), addClassMethods()
444:
445: /**
446: * Adds the specified interface to the implements clause of
447: * {@link #getClassTree()}.
448: *
449: * @param classTree the class to add the implements clause to.
450: * @param interfaceType the fully-qualified name of the interface; cannot be null.
451: * @return the class implementing the new interface.
452: */
453: public ClassTree addImplementsClause(ClassTree classTree,
454: String interfaceType) {
455: Parameters.notNull("classTree", classTree); // NOI18N
456: Parameters.notNull("interfaceType", interfaceType); // NOI18N
457:
458: ExpressionTree interfaceTree = createQualIdent(interfaceType);
459: return getTreeMaker().addClassImplementsClause(classTree,
460: interfaceTree);
461: }
462:
463: // </editor-fold>
464:
465: // <editor-fold defaultstate="collapsed" desc="Non-public methods">
466:
467: private TreeMaker getTreeMaker() {
468: return copy.getTreeMaker();
469: }
470:
471: private ModifiersTree createEmptyModifiers() {
472: return getTreeMaker().Modifiers(Collections.emptySet(),
473: Collections.emptyList());
474: }
475:
476: private ExpressionTree tryCreateQualIdent(String typeName) {
477: TypeElement typeElement = copy.getElements().getTypeElement(
478: typeName);
479: if (typeElement != null) {
480: return getTreeMaker().QualIdent(typeElement);
481: }
482: return null;
483:
484: }
485:
486: public ExpressionTree createQualIdent(String typeName) {
487: ExpressionTree qualIdent = tryCreateQualIdent(typeName);
488: if (qualIdent == null) {
489: throw new IllegalArgumentException(
490: "Cannot create a QualIdent for " + typeName); // NOI18N
491: }
492: return qualIdent;
493: }
494:
495: public ExpressionTree makeQualIdent(String typeClass) {
496: TypeElement type = copy.getElements().getTypeElement(typeClass);// NOI18N
497: ExpressionTree tree = getTreeMaker().QualIdent(type);
498:
499: return tree;
500: }
501:
502: // </editor-fold>
503: }
|