1 /** 2 * Redistribution and use of this software and associated documentation ("Software"), with or 3 * without modification, are permitted provided that the following conditions are met: 4 * 5 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions 6 * must also contain a copy of this document. 7 * 8 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 9 * conditions and the following disclaimer in the documentation and/or other materials provided with 10 * the distribution. 11 * 12 * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software 13 * without prior written permission of Intalio, Inc. For written permission, please contact 14 * info@exolab.org. 15 * 16 * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in 17 * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of 18 * Intalio, Inc. 19 * 20 * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/). 21 * 22 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR 23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 24 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS 25 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 28 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 29 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 * 31 * Copyright 2001-2002 (C) Intalio, Inc. All Rights Reserved. 32 */ 33 package org.exolab.javasource; 34 35 import java.io.File; 36 import java.io.FileWriter; 37 import java.util.Enumeration; 38 import java.util.SortedSet; 39 import java.util.TreeSet; 40 import java.util.Vector; 41 42 /** 43 * A representation of the Java Source code for a Java compilation unit. This is a useful utility 44 * when creating in memory source code. This package was modelled after the Java Reflection API as 45 * much as possible to reduce the learning curve. 46 * 47 * @author <a href="mailto:shea AT gtsdesign DOT com">Gary Shea</a> 48 * @version $Revision$ $Date: 2005-03-05 06:42:06 -0700 (Sat, 05 Mar 2005) $ 49 */ 50 public final class JCompUnit { 51 52 /** 53 * Initial size for {@link StringBuilder} instances. 54 */ 55 private static final int INITIAL_STRING_BUILDER_SIZE = 32; 56 57 /** 58 * The Id for Source control systems I needed to break the String to prevent CVS from expanding it 59 * here! ;-) 60 */ 61 private static final String DEFAULT_HEADER = "$" + "Id$"; 62 63 /** Public header. */ 64 private static final String[] PUBLIC_HEADER = {" //-----------------------------/", 65 " //- Public Class / Interface -/", "//-----------------------------/",}; 66 67 /** Private header. */ 68 private static final String[] NON_PUBLIC_HEADER = {" //-------------------------------------/", 69 " //- Non-Public Classes / Interfaces -/", "//-------------------------------------/",}; 70 71 /** JavaDoc comment for this compilation unit. */ 72 private JComment _header = null; 73 74 /** The package for this JCompUnit. */ 75 private final String _packageName; 76 77 /** The file to which this JCompUnit will be written. */ 78 private final String _fileName; 79 80 /** The set of top-level classes that live in this compilation unit. */ 81 private final Vector<JClass> _classes = new Vector<JClass>(); 82 83 /** The set of top-level interfaces that live in this compilation unit. */ 84 private final Vector<JInterface> _interfaces = new Vector<JInterface>(); 85 86 /** 87 * Creates a new JCompUnit. 88 * 89 * @param packageName The name of the package for this JCompUnit. If packageName is null or empty, 90 * no 'package' line will be generated. 91 * @param fileName The name of the file to which this JCompUnit will be written. 92 */ 93 public JCompUnit(final String packageName, final String fileName) { 94 _packageName = packageName; 95 _fileName = fileName; 96 } 97 98 /** 99 * Creates a new JCompUnit with the given JClass (which must have been created with either a full 100 * class name or package/local name) as the public class. Package and file name are taken from 101 * jClass. 102 * 103 * @param jClass The public class for this JCompUnit. 104 */ 105 public JCompUnit(final JClass jClass) { 106 this(jClass.getPackageName(), jClass.getLocalName() + ".java"); 107 108 // The outer name is the package plus the simple name of the 109 // outermost enclosing class. The file name is just the 110 // simple name of the outermost enclosing class, so the 111 // package name part must be stripped off. 112 113 // Commented out until inner-class support has been added. 114 // kvisco - 20021211 115 // 116 // String outer = jClass.getOuterName(); 117 // int lastDot = outer.lastIndexOf("."); 118 // String filePrefix; 119 // if (lastDot != -1) { 120 // filePrefix = outer.substring (lastDot + 1); 121 // } else { 122 // filePrefix = outer; 123 // } 124 125 _classes.add(jClass); 126 } 127 128 /** 129 * Creates a new JCompUnit with the given JInterface as public interface. Package and file name 130 * are taken from jInterface. 131 * 132 * @param jInterface The public interface for this JCompUnit. 133 */ 134 public JCompUnit(final JInterface jInterface) { 135 this(jInterface.getPackageName(), jInterface.getLocalName() + ".java"); 136 _interfaces.add(jInterface); 137 } 138 139 /** 140 * Sets the header comment for this JCompUnit. 141 * 142 * @param comment The comment to display at the top of the source file when printed. 143 */ 144 public void setHeader(final JComment comment) { 145 _header = comment; 146 } 147 148 /** 149 * Adds the given JStructure (either a JInterface or a JClass) to this JCompUnit. 150 * 151 * @param jStructure The JStructure to add. 152 */ 153 public void addStructure(final JStructure jStructure) { 154 if (jStructure instanceof JInterface) { 155 addInterface((JInterface) jStructure); 156 } else if (jStructure instanceof JClass) { 157 addClass((JClass) jStructure); 158 } else { 159 String err = "Unknown JStructure subclass '" + jStructure.getClass().getName() + "'."; 160 throw new IllegalArgumentException(err); 161 } 162 } 163 164 /** 165 * Adds a JClass to be printed in this file. 166 * 167 * @param jClass The JClass to be printed in this file. 168 */ 169 public void addClass(final JClass jClass) { 170 _classes.add(jClass); 171 } 172 173 /** 174 * Adds a JInterface to be printed in this file. 175 * 176 * @param jInterface The JInterface to be printed in this file. 177 */ 178 public void addInterface(final JInterface jInterface) { 179 _interfaces.add(jInterface); 180 } 181 182 /** 183 * Returns a array of String containing all imported classes/packages, also imports within the 184 * same package of this object. 185 * 186 * @return A array of String containing all import classes/packages, also imports within the same 187 * package of this object. 188 */ 189 public SortedSet<String> getImports() { 190 SortedSet<String> allImports = new TreeSet<String>(); 191 192 // add imports from classes 193 for (JClass jClass : _classes) { 194 Enumeration<String> enumeration = jClass.getImports(); 195 while (enumeration.hasMoreElements()) { 196 allImports.add(enumeration.nextElement()); 197 } 198 } 199 200 for (JInterface jInterface : _interfaces) { 201 Enumeration<String> enumeration = jInterface.getImports(); 202 while (enumeration.hasMoreElements()) { 203 allImports.add(enumeration.nextElement()); 204 } 205 } 206 207 return allImports; 208 } 209 210 /** 211 * Returns the name of the file that this JCompUnit would be printed to, given a call to 212 * {@link #print(String, String)}, or if destDir is null, a call to {@link #print()}. 213 * 214 * @param destDir The destination directory. This may be null. 215 * @return The name of the file that this JCompUnit would be printed to. 216 */ 217 public String getFilename(final String destDir) { 218 // -- Convert Java package to path string 219 String javaPackagePath = ""; 220 if (_packageName != null && !_packageName.isEmpty()) { 221 javaPackagePath = _packageName.replace('.', File.separatorChar); 222 } 223 224 // -- Create fully qualified path (including 'destDir') to file 225 File pathFile; 226 if (destDir == null) { 227 pathFile = new File(javaPackagePath); 228 } else { 229 pathFile = new File(destDir, javaPackagePath); 230 } 231 if (!pathFile.exists()) { 232 pathFile.mkdirs(); 233 } 234 235 // -- Prefix filename with path 236 String filename = _fileName; 237 final String pathStr = pathFile.toString(); 238 if (!pathStr.isEmpty()) { 239 filename = pathStr + File.separator + filename; 240 } 241 242 return filename; 243 } 244 245 /** 246 * Returns the name of the package that this JCompUnit is a member of. 247 * 248 * @return The name of the package that this JCompUnit is a member of, or null if there is no 249 * current package name defined. 250 */ 251 public String getPackageName() { 252 return _packageName; 253 } 254 255 /** 256 * Prints the source code for this JClass in the current directory with the default line seperator 257 * of the the runtime platform. 258 * 259 * @see #print(String, String) 260 */ 261 public void print() { 262 print(null, null); 263 } 264 265 /** 266 * Prints the source code for this JClass with the default line seperator of the the runtime 267 * platform. 268 * 269 * @param destDir The destination directory to use as the root directory for source generation. 270 * 271 * @see #print(String, String) 272 */ 273 public void print(final String destDir) { 274 print(destDir, null); 275 } 276 277 /** 278 * Prints the source code for this JCompUnit using the provided root directory and line separator. 279 * 280 * @param destDir The destination directory to use as the root directory for source generation. 281 * @param lineSeparator The line separator to use at the end of each line. If null, then the 282 * default line separator for the runtime platform will be used. 283 */ 284 public void print(final String destDir, final String lineSeparator) { 285 // -- open output file 286 String filename = getFilename(destDir); 287 288 File file = new File(filename); 289 JSourceWriter jsw = null; 290 try { 291 jsw = new JSourceWriter(new FileWriter(file)); 292 } catch (java.io.IOException ioe) { 293 System.out.println("unable to create compilation unit file: " + filename); 294 return; 295 } 296 297 if (lineSeparator == null) { 298 jsw.setLineSeparator(System.getProperty("line.separator")); 299 } else { 300 jsw.setLineSeparator(lineSeparator); 301 } 302 print(jsw); 303 jsw.flush(); 304 jsw.close(); 305 } 306 307 /** 308 * Prints the source code for this JClass to the provided JSourceWriter. 309 * 310 * @param jsw The JSourceWriter to print to. 311 */ 312 public void print(final JSourceWriter jsw) { 313 // Traverse the nested class and interface hiararchy and 314 // update the names to match the compilation unit. 315 316 // -- write file header 317 if (_header != null) { 318 _header.print(jsw); 319 } else { 320 jsw.writeln("/*"); 321 jsw.writeln(" * " + DEFAULT_HEADER); 322 jsw.writeln("*/"); 323 } 324 jsw.writeln(); 325 jsw.flush(); 326 327 // -- print package name 328 if ((_packageName != null) && (_packageName.length() > 0)) { 329 String buffer = new StringBuilder(INITIAL_STRING_BUILDER_SIZE).append("package ") 330 .append(_packageName).append(';').toString(); 331 jsw.writeln(buffer); 332 jsw.writeln(); 333 } 334 335 // -- print imports 336 jsw.writeln(" //---------------------------------------------/"); 337 jsw.writeln(" //- Imported classes, interfaces and packages -/"); 338 jsw.writeln("//---------------------------------------------/"); 339 jsw.writeln(); 340 SortedSet<String> allImports = getImports(); 341 String compUnitPackage = getPackageName(); 342 for (String importName : allImports) { 343 String importsPackage = JNaming.getPackageFromClassName(importName); 344 if (importsPackage != null && !importsPackage.equals(compUnitPackage)) { 345 jsw.write("import "); 346 jsw.write(importName); 347 jsw.writeln(';'); 348 } 349 } 350 jsw.writeln(); 351 352 // Print the public elements, interfaces first, then classes. 353 // There should only be one public element, but if there are 354 // more we let the compiler catch it. 355 printStructures(jsw, true); 356 357 // Print the remaining non-public elements, interfaces first. 358 printStructures(jsw, false); 359 360 jsw.flush(); 361 } 362 363 /** 364 * Print the source code for the contained JClass objects. 365 * 366 * @param jsw The JSourceWriter to print to. 367 * @param printPublic If true, print only public classes; if false, print only non-public classes. 368 */ 369 public void printStructures(final JSourceWriter jsw, final boolean printPublic) { 370 // -- print class information 371 // -- we need to add some JavaDoc API adding comments 372 373 boolean isFirst = true; 374 375 // SortedSet interfaceList = interfaces.sortedOnFullName(); 376 for (JInterface jInterface : _interfaces) { 377 if (jInterface.getModifiers().isPublic() == printPublic) { 378 if (isFirst) { 379 String[] header = printPublic ? PUBLIC_HEADER : NON_PUBLIC_HEADER; 380 for (String headerEntry : header) { 381 jsw.writeln(headerEntry); 382 } 383 jsw.writeln(); 384 isFirst = false; 385 } 386 jInterface.print(jsw, true); 387 jsw.writeln(); 388 } 389 } 390 391 // SortedSet classList = classes.sortedOnFullName(); 392 for (JClass jClass : _classes) { 393 if (jClass.getModifiers().isPublic() == printPublic) { 394 if (isFirst) { 395 String[] header = printPublic ? PUBLIC_HEADER : NON_PUBLIC_HEADER; 396 for (int j = 0; j < header.length; ++j) { 397 jsw.writeln(header[j]); 398 } 399 jsw.writeln(); 400 isFirst = false; 401 } 402 jClass.print(jsw, true); 403 jsw.writeln(); 404 } 405 } 406 } 407 408 }