Java2XMI converter hacked
张钰彦 <[email protected]> Sat, 3 Sep 2005 07:43:08 +0800
| Newsgroups | gmane.linux.umbrello.user |
|---|---|
| Message-ID | <[email protected]> |
* Hacked by Hyacinth, Sep 2th, 2005 * * 1. Package distinguish is hacked. * The old program treats very sub-dir as a Java package except "CVS" dir. * The new program considers only when a sub-dir contains Java source files as Java package. * It causes Java string used. So the new program needs more memory for XMI code buffer. * * 2. The source codes are re-formatted by "astyle -b". * * 3. Folding marks are used, the source codes look fine in editors that support "Folding", such as * VIM/Emacs/jEdit/etc. * * mrkissinger _at_ gmail.com/msn.com/cmail.cn
J2XMI.java
(text/plain, 34.7 KB)
import java.io.*; /** * Java2XMI converter * * * Written by R.Rawson-Tetley, 9th Feb, 2005 * * Input: Source Directory containing Java files (as classpath root) * Output: XMI description of passed in source files, complete with * packages, classes, methods, etc * * Consider this class public domain - do what you want with it. * * [email protected] * * * Hacked by Hyacinth, Sep 2th, 2005 * * 1. Package distinguish is hacked. * The old program treats very sub-dir as a Java package except "CVS" dir. * The new program considers only when a sub-dir contains Java source files as Java package. * It causes Java string used. So the new program needs more memory for XMI code buffer. * * 2. The source codes are re-formatted by "astyle -b". * * 3. Folding marks are used, the source codes look fine in editors that support "Folding", such as * VIM/Emacs/jEdit/etc. * * mrkissinger _at_ gmail.com/msn.com/cmail.cn * * */ public class J2XMI { public static void main(String[] args)//{{{ { if (args.length == 0) { System.out.println("java2xmi 050218 - convert java source to XMI"); System.out.println("Written by R.Rawson-Tetley"); System.out.println(""); System.out.println("This library is distributed in the hope that it will be useful,"); System.out.println("but WITHOUT ANY WARRANTY; without even the implied warranty of"); System.out.println("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU"); System.out.println("General Public Licence for more details."); System.out.println(""); System.out.println("Usage: java2xmi [--noprivate] [--noprotected] <source folder>"); System.exit(1); } new J2XMI(args); }//}}} /** The number of spaces we are currently at to indent the * package/class/interface/operation XMI structures at * This value is incremented/decremented as we traverse * the file tree and find classes */ private int depth = 1; /** Whether we are excluding private accessors */ private boolean noprivate = false; /** Whether we are excluding protected accessors */ private boolean noprotected = false; /** The last accessor type return by <code>findNextAccessor</code> */ public String lastAc = "public"; private int nextID = 1000; public J2XMI(String[] args)//{{{ /** * Construct a new J2XMI with the specified folder and go. */ { try { String src = args[args.length-1]; for (int i = 0; i < args.length; i++) { if (args[i].equals("--noprivate")) noprivate = true; if (args[i].equals("--noprotected")) noprotected = true; } File f = new File(src); outputHeader(); System.out.println(traverse(f)); outputFooter(); } catch (Exception e) { e.printStackTrace(); System.exit(1); } }//}}} public String traverse(File f)//{{{ /** * Enumerates each file for the given directory * and processes it as XMI output: This routine will * call itself recursively for any directories * it finds whilst processing. * * @param f The <code>File</code> to look in * @param firstTime Whether or not it's the first time (and hence * whether we need to output UML package structure) */ { /* * Whether the current dir is a Java package. */ boolean isPackageDir=false; boolean isFirstJavaFile=true; String xmiCode=""; // Move up one depth for indenting XML depth++; try { // Output a package element for the directory. We // don't want to include the folder to start in though, so if it's // the first time, don't bother outputting package details. /* if (!firstTime) System.out.println(getSpaces(depth) + "<UML:Package visibility=\"public\" xmi.id=\"" + getXmiID() + "\" name=\"" + f.getName() + "\">"); */ File[] d = f.listFiles(); for (int i = 0; i < d.length; i++) { // If we have a directory, process it if (d[i].isDirectory()) { // Unless it's a CVS directory if (!d[i].getName().equals("CVS")) { //System.out.println(d[i].getName()); String code=traverse(d[i]); if ( (null!=code) )//&& (!f.getName().equals(".")) ) { //System.out.println(d[i].getName()); /* System.out.println( getSpaces(depth) + "<UML:Package visibility=\"public\" xmi.id=\"" + getXmiID() + "\" name=\"" + d[i].getName() + "\">\n"+ code+"\n"+ "</UML:Package>" ); */ xmiCode=xmiCode+getSpaces(depth) + "<UML:Package visibility=\"public\" xmi.id=\"" + getXmiID() + "\" name=\"" + d[i].getName() + "\">\n"+ code+"\n"+ "</UML:Package>\n"; } } } // If it's a java file, process it if (d[i].getName().toLowerCase().endsWith(".java")) { /* if (null==xmiCode) { xmiCode=new String(); } */ if (isFirstJavaFile) { /* System.out.println(getSpaces(depth) + "<UML:Package visibility=\"public\" xmi.id=\"" + getXmiID() + "\" name=\"" + f.getName() + "\">"); */ /* xmiCode=xmiCode+getSpaces(depth) + "<UML:Package visibility=\"public\" xmi.id=\"" + getXmiID() + "\" name=\"" + f.getName() + "\">"; */ isPackageDir=true; } xmiCode=xmiCode+processJava(d[i]); } } } catch (Exception e) {} // Close the package element /* if (!firstTime) System.out.println(getSpaces(depth) + "</UML:Package>"); */ //if (isPackageDir) { //System.out.println(getSpaces(depth) + "</UML:Package>"); //xmiCode=xmiCode+getSpaces(depth) + "</UML:Package>"; } // Move down a level for indenting XML depth--; return(xmiCode); }//}}} public String getSpaces(int num)//{{{ /** * Returns a string of the requested number of spaces. * * @param num The number of spaces to return */ { StringBuffer b = new StringBuffer(num); for (int i = 0; i < num; i++) b.append(" "); return b.toString(); }//}}} public String processJava(File f)//{{{ /** * Process a Java file, outputting details of the class/interface * and all methods * * This is really crude and probably should have been split out * into some delegated code that built a tree of objects and * then looped through them to generate the XML, but time is * a factor and I don't have much to waste working on this * * @param f The java <code>File</code> to process. */ { boolean wroteHeader = false; boolean isInterface = true; String xmiCode=""; try { String s = readFileToString(f); // Remove comments since they can confuse us if they // have code snippets and things in there s = stripComments(f.getName(), s); // Look for the first curly brace and then work back // to find an interface or class declaration int fcb = s.indexOf("{"); int ipos = s.lastIndexOf(" interface", fcb); if (ipos == -1) { ipos = s.lastIndexOf(" class", fcb); isInterface = false; } // We couldn't find an initial declaration. Abort. if (ipos == -1) { System.err.println(f.getName() + ": Couldn't find initial \"public interface\" or \"public class\" declaration"); return(xmiCode); } // Determine the name by looking for a space after // the class/interface type int namepos = s.indexOf(" ", ipos + 2) + 1; // Get the end of the name by looking for the first linebreak/space // after the name int nameend = getNextWhitespaceChar(s, namepos + 1); String name = asXMLAttribute(s.substring(namepos, nameend)); // Drop a depth level and output the class/interface XMI depth++; if (isInterface) { /* System.out.println(getSpaces(depth) + "<UML:Interface stereotype=\"42\" " + "visibility=\"public\" xmi.id=\"" + getXmiID() + "\" isAbstract=\"true\" " + "name=\"" + asXMLAttribute(name) + "\">"); */ xmiCode=xmiCode+getSpaces(depth) + "<UML:Interface stereotype=\"42\" " + "visibility=\"public\" xmi.id=\"" + getXmiID() + "\" isAbstract=\"true\" " + "name=\"" + asXMLAttribute(name) + "\">\n"; } else { /* System.out.println(getSpaces(depth) + "<UML:Class visibility=\"public\" xmi.id=\"" + getXmiID() + "\" name=\"" + asXMLAttribute(name) + "\">"); */ xmiCode=xmiCode+getSpaces(depth) + "<UML:Class visibility=\"public\" xmi.id=\"" + getXmiID() + "\" name=\"" + asXMLAttribute(name) + "\">\n"; } // Mark the header as written to make sure we write a closer should // the worst happen. wroteHeader = true; // Start looking for methods/fields from the first curly brace int pos = fcb; // Look for an accessor string that could be the start of a field/method // (public/private/protected) pos = findNextAccessor(s, pos + 1); // Whether we need to abandon during the loop - we need to do this if // we find something horrible or unexpected so we don't write crap XMI boolean abort = false; // Whether we're dealing with a static method/field boolean isStatic = false; boolean isTransient = false; boolean isSynchronized = false; boolean isAbstract = false; boolean isFinal = false; boolean isConstructor = false; // Whether this is a field or a method boolean isMethod = false; // The method/field name and type String mname = ""; String mtype = ""; // Where split strings go when breaking up the method/field signature String[] methodbits = null; while (pos != -1) { abort = false; isStatic = false; isTransient = false; isSynchronized = false; isAbstract = false; isFinal = false; isConstructor = false; mname = ""; mtype = ""; isMethod = false; // Get the next terminator (semi-colon or close bracket); // if the next one is a semi-colon, then we're dealing // with a field rather than a method. int sigterminator = -1; for (int i = pos; i < s.length()-1; i++) { if (s.substring(i, i+1).equals(";")) { sigterminator = i; isMethod = false; break; } else if (s.substring(i, i+1).equals(")")) { sigterminator = i; isMethod = true; break; } } if (isMethod) { // METHOD PARSER // ================================================================ // Read the method signature String msig = ""; try { msig = s.substring(pos, sigterminator).trim(); } catch (StringIndexOutOfBoundsException e) { // This shouldn't ever happen abort = true; System.err.println("WARN: Couldn't find the end of the method sig - aborting: " + pos + "-" + sigterminator); } // Verify that the method start we found wasn't // inside a string literal and some smart alec is // running java2xmi over it's own source if (s.substring(pos - 1, pos).equals("\"")) { abort = true; System.err.println("WARN: Almost confused by a string literal - aborting:\n " + msig); } if (!abort) { // Split the method sig on spaces methodbits = split(flattenWhiteSpace(msig), " "); // Loop through those bits and pick out // words we recognise. The first one that // we don't is the method type, followed // by the method name. for (int z = 1; z < methodbits.length; z++) { if (methodbits[z].equals("static")) isStatic = true; else if (methodbits[z].equals("abstract")) isAbstract = true; else if (methodbits[z].equals("transient")) isTransient = true; else if (methodbits[z].equals("synchronized")) isSynchronized = true; else if (methodbits[z].equals("final")) isFinal = true; else { try { // The next one must be the type mtype = methodbits[z]; // And the name... mname = methodbits[z + 1]; // Does the type contain a bracket? // if so, it's a constructor with // arguments. if (mtype.indexOf("(") != -1) { mtype = name; mname = name; isConstructor = true; } break; } catch (ArrayIndexOutOfBoundsException e) { // We've run out of bits - this is // a constructor. isConstructor = true; mtype = name; mname = name; break; } } } } if (!abort) { // Chop everything after that first bracket out of the method name // if there is no bracket, then that's because they put a space after // the method name and before the first bracket (which is ok) try { mname = mname.substring(0, mname.indexOf("(")); } catch (StringIndexOutOfBoundsException e) {} } if (!abort) { // Enter another depth and output the operation depth++; /* System.out.println(getSpaces(depth) + "<UML:Operation visibility=\"" + lastAc + "\" " + "xmi.id=\"" + getXmiID() + "\" type=\"" + unqualifyType(asXMLAttribute(mtype)) + "\" " + "name=\"" + asXMLAttribute(mname) + "\"" + (isStatic ? " ownerScope=\"classifier\"" : "") + (isAbstract ? " isAbstract=\"true\"" : "") + ">"); */ xmiCode=xmiCode+getSpaces(depth) + "<UML:Operation visibility=\"" + lastAc + "\" " + "xmi.id=\"" + getXmiID() + "\" type=\"" + unqualifyType(asXMLAttribute(mtype)) + "\" " + "name=\"" + asXMLAttribute(mname) + "\"" + (isStatic ? " ownerScope=\"classifier\"" : "") + (isAbstract ? " isAbstract=\"true\"" : "") + ">\n"; // Read the parameters String params = msig.substring(msig.indexOf("(") + 1, msig.length()); String[] pbits = split(params, ","); depth++; for (int z = 0; z < pbits.length; z++) { try { String pb = pbits[z].trim(); String[] sp = split(pb, " "); /* System.out.println(getSpaces(depth) + "<UML:Parameter " + "visibility=\"" + lastAc + "\" xmi.id=\"" + getXmiID() + "\" " + "value=\"\" type=\"" + unqualifyType(asXMLAttribute(sp[0])) + "\" name=\"" + asXMLAttribute(sp[1]) + "\"/>"); */ xmiCode=xmiCode+getSpaces(depth) + "<UML:Parameter " + "visibility=\"" + lastAc + "\" xmi.id=\"" + getXmiID() + "\" " + "value=\"\" type=\"" + unqualifyType(asXMLAttribute(sp[0])) + "\" name=\"" + asXMLAttribute(sp[1]) + "\"/>\n"; } catch (ArrayIndexOutOfBoundsException e) { // No params } catch (StringIndexOutOfBoundsException e) { // No params } } depth--; // Back up //System.out.println(getSpaces(depth) + "</UML:Operation>"); xmiCode=xmiCode+getSpaces(depth) + "</UML:Operation>\n"; depth--; } } else { // FIELD PARSER: // ============================================================== // Read the field signature String fsig = ""; try { fsig = s.substring(pos, sigterminator).trim(); } catch (StringIndexOutOfBoundsException e) { // This shouldn't ever happen abort = true; System.err.println("WARN: Couldn't find the end of the method sig - aborting: " + pos + "-" + sigterminator); } // Verify that the field start we found wasn't // inside a string literal and some smart alec is // running java2xmi over it's own source if (s.substring(pos - 1, pos).equals("\"")) { abort = true; System.err.println("WARN: Almost confused by a string literal - aborting:\n " + fsig); } if (!abort) { // Split the field sig on spaces methodbits = split(flattenWhiteSpace(fsig), " "); // Loop through those bits and pick out // words we recognise. The first one that // we don't is the field type, followed // by the field name. for (int z = 1; z < methodbits.length; z++) { if (methodbits[z].equals("static")) isStatic = true; else if (methodbits[z].equals("abstract")) isAbstract = true; else if (methodbits[z].equals("transient")) isTransient = true; else if (methodbits[z].equals("synchronized")) isSynchronized = true; else if (methodbits[z].equals("final")) isFinal = true; else if (methodbits[z].trim().equals("")) isFinal = isFinal; // Ignore empty space else { try { // The next one must be the type mtype = methodbits[z]; // And the name... mname = methodbits[z + 1]; break; } catch (ArrayIndexOutOfBoundsException e) { abort = true; System.err.println("WARN: Couldn't get enough parts for field type and name - aborting:\n " + fsig); } } } } if (!abort) { // Chop everything after that first equals out of the field name // if there is no equals, then that's because they put a space after // the field name and before the equals (which is ok). // Throw away upto the semicolon if we have one as well. try { if (mname.indexOf("=") != -1) mname = mname.substring(0, mname.indexOf("=")); else if (mname.indexOf(";") != -1) mname = mname.substring(0, mname.indexOf(";")); } catch (StringIndexOutOfBoundsException e) {} // TODO: Maybe add support for extracting the initial value // by looking at what's after the equals sign and before the // semi-colon? } if (!abort) { // Enter another depth and output the attribute depth++; /* System.out.println(getSpaces(depth) + "<UML:Attribute visibility=\"" + lastAc + "\" " + "xmi.id=\"" + getXmiID() + "\" type=\"" + unqualifyType(asXMLAttribute(mtype)) + "\" " + "name=\"" + asXMLAttribute(mname) + "\"" + (isStatic ? " ownerScope=\"classifier\"" : "") + "/>"); */ xmiCode=xmiCode+getSpaces(depth) + "<UML:Attribute visibility=\"" + lastAc + "\" " + "xmi.id=\"" + getXmiID() + "\" type=\"" + unqualifyType(asXMLAttribute(mtype)) + "\" " + "name=\"" + asXMLAttribute(mname) + "\"" + (isStatic ? " ownerScope=\"classifier\"" : "") + "/>\n"; depth--; } } // Look for a public method/field again pos = findNextAccessor(s, pos + 1); } } catch (Exception e) { System.err.println("Exception occurred, abandoning class parse: " + e.getMessage()); } // Output the closer for the class/interface and move back // up a depth level. if (wroteHeader) { if (isInterface) { //System.out.println(getSpaces(depth) + "</UML:Interface>"); xmiCode=xmiCode+"</UML:Interface>\n"; } else { //System.out.println(getSpaces(depth) + "</UML:Class>"); xmiCode=xmiCode+getSpaces(depth) + "</UML:Class>\n"; } depth--; } return(xmiCode); }//}}} public int getNextWhitespaceChar(String findin, int start)//{{{ /** * Finds the next line break/space character in the given string * from the given position. */ { int i = start; while (i < findin.length()) { if (findin.substring(i, i + 1).equals( new String(new byte[] { 13 }))) return i; else if (findin.substring(i, i + 1).equals( new String(new byte[] { 10 }))) return i; else if (findin.substring(i, i + 1).equals(" ")) return i; i++; } return -1; }//}}} public String unqualifyType(String type)//{{{ /** * Given a Java type, unqualifies (removes the package) from * the class name. * @param type The type to unqualify * @return the unqualified class name */ { // Do nothing if no package int lastDot = type.lastIndexOf("."); if (lastDot == -1) return type; else return type.substring(lastDot + 1, type.length()); }//}}} public String flattenWhiteSpace(String in)//{{{ /** * Given a string, loops through it and converts multiple * spaces/line breaks to one single space. */ { StringBuffer o = new StringBuffer(in.length()); boolean lastCharWasSpace = false; boolean outputOneSpace = false; for (int i = 0; i < in.length(); i++) { // Space or line break qualify as whitespace if (in.substring(i, i+1).equals(" ")) { lastCharWasSpace = true; } else if (in.substring(i, i+1).equals("\n")) { lastCharWasSpace = true; } // If the char isn't one, append it and flag // it as such else { o.append(in.substring(i, i+1)); lastCharWasSpace = false; outputOneSpace = false; } if (lastCharWasSpace && !outputOneSpace) { o.append(" "); outputOneSpace = true; } } return o.toString(); }//}}} public int findNextAccessor(String in, int pos)//{{{ /** * Finds the next accessor in a given string * An accessor is one of public/private/protected * @param in the string to find in * @param pos the position to start at * @return the position of the next accessor, or -1 if none was found */ { int pu = in.indexOf("public ", pos); int pr = in.indexOf("private ", pos); int pt = in.indexOf("protected ", pos); // If private/protected accessors are turned off, pretend we // didn't find any if (noprivate) pr = 99999; if (noprotected) pt = 99999; // Translate -1s to 99999 to effectively ignore if (pu == -1) pu = 99999; if (pr == -1) pr = 99999; if (pt == -1) pt = 99999; int lowest = 99999; lastAc = "none"; if (pu < lowest) { lowest = pu; lastAc = "public"; } if (pr < lowest) { lowest = pr; lastAc = "private"; } if (pt < lowest) { lowest = pt; lastAc = "protected"; } if (lowest == 99999) return -1; else return lowest; }//}}} public String stripComments(String filename, String s)//{{{ /** * Removes all C-Style java comments from a piece * of code. * @param s The code to look for comments in * @return The code without any comments */ { StringBuffer output = new StringBuffer(s.length()); int f = 0; for (int i = 0; i < s.length()-1; i++) { if (s.substring(i, i+2).equals("/*")) { f = s.indexOf("*/", i + 1); if (f != -1) { i = f; } else { System.err.println("WARN: Oops no closing */ tag found after /* in " + filename); i = s.length() - 1; } } if (s.substring(i, i+2).equals("//")) { f = s.indexOf("\n", i + 1); if (f != -1) { i = f; } else { System.err.println("WARN: Oops - no line break found after // in " + filename); i = s.length() - 1; } } try { output.append(s.substring(i, i+1)); } catch (StringIndexOutOfBoundsException e) { System.err.println("WARN: Error appending char whilst removing comments - " + e.getMessage()); } } return output.toString(); }//}}} public String asXMLAttribute(String v)//{{{ /** A final check before a value goes into an XML attribute * all XML-upsetting chars are removed. */ { v = v.replace('"', ' '); v = v.replace('&', ' '); return v.trim(); }//}}} public int getXmiID()//{{{ /** Returns the next unique ID for use with xmi.id tags */ { nextID++; return nextID; }//}}} public void outputHeader()//{{{ /** * Outputs the document header */ { String s = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<XMI xmlns:UML=\"org.omg/standards/UML\" verified=\"false\" timestamp=\"\" xmi.version=\"1.2\">\n" + " <XMI.header>\n" + " <XMI.documentation>\n" + " <XMI.exporter>Java2XMI http://www.rawsontetley.org</XMI.exporter>\n" + " <XMI.exporterVersion>1.0</XMI.exporterVersion>\n" + " <XMI.exporterEncoding>UnicodeUTF8</XMI.exporterEncoding>\n" + " </XMI.documentation>\n" + " <XMI.model xmi.name=\"java2xmi\" href=\"\"/>\n" + " <XMI.metamodel xmi.name=\"UML\" xmi.version=\"1.3\" />\n" + " </XMI.header>\n" + " <XMI.content>\n" + " <UML:Model>\n" + " <UML:Stereotype visibility=\"public\" xmi.id=\"3\" name=\"datatype\" />\n" + " <UML:Stereotype visibility=\"public\" xmi.id=\"42\" name=\"interface\" />"; System.out.println(s); }//}}} public void outputFooter()//{{{ /** * Outputs the document footer */ { String s = " </UML:Model>\n" + " </XMI.content>\n" + "</XMI>"; System.out.println(s); }//}}} public String readFileToString(File f)//{{{ /** * Given a <code>File</code>, reads it into a single * String and returns it for parsing. * * @param f the file to read. */ { try { FileInputStream in = new FileInputStream(f); byte[] buffer = new byte[in.available()]; in.read(buffer); in.close(); return new String(buffer); } catch (IOException e) { e.printStackTrace(); return null; } }//}}} public static String[] split(String splitstring, String splitchar)//{{{ /** * Splits a string by a particular char and returns an array of * strings. If there are no occurrences of the split char, the * original string is returned in an array of 1 item. * @param splitstring The string to be split * @param splitchar The char to split on * @return An array of strings */ { splitstring = splitstring.trim(); // If there is only one element, just return that if (splitstring.indexOf(splitchar) == -1) { String[] rets = new String[1]; rets[0] = splitstring; return rets; } // Find how many there are int tot = 0; int lpos = splitstring.indexOf(splitchar); while (lpos != -1) { tot++; lpos = splitstring.indexOf(splitchar, lpos + 1); } tot++; // Create our new array String[] rets = new String[tot]; tot = 0; lpos = 0; int spos = splitstring.indexOf(splitchar); while (spos != -1) { // Add into the array rets[tot] = splitstring.substring(lpos, spos); tot++; lpos = spos + 1; spos = splitstring.indexOf(splitchar, lpos); } // Include last word rets[tot] = splitstring.substring(lpos, splitstring.length()); // Return it return rets; }//}}} }