0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one
0003: * or more contributor license agreements. See the NOTICE file
0004: * distributed with this work for additional information
0005: * regarding copyright ownership. The ASF licenses this file
0006: * to you under the Apache License, Version 2.0 (the
0007: * "License"); you may not use this file except in compliance
0008: * with the License. You may obtain a copy of the License at
0009: *
0010: * http://www.apache.org/licenses/LICENSE-2.0
0011: *
0012: * Unless required by applicable law or agreed to in writing,
0013: * software distributed under the License is distributed on an
0014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
0015: * KIND, either express or implied. See the License for the
0016: * specific language governing permissions and limitations
0017: * under the License.
0018: */
0019: package org.apache.openjpa.enhance;
0020:
0021: import java.io.File;
0022: import java.io.FileWriter;
0023: import java.io.IOException;
0024: import java.io.PrintWriter;
0025: import java.io.Writer;
0026: import java.lang.reflect.Modifier;
0027: import java.security.AccessController;
0028: import java.util.ArrayList;
0029: import java.util.Arrays;
0030: import java.util.Collection;
0031: import java.util.Date;
0032: import java.util.HashSet;
0033: import java.util.Iterator;
0034: import java.util.List;
0035: import java.util.Map;
0036: import java.util.Set;
0037: import java.util.TreeSet;
0038:
0039: import org.apache.commons.lang.StringUtils;
0040: import org.apache.openjpa.conf.OpenJPAConfiguration;
0041: import org.apache.openjpa.conf.OpenJPAConfigurationImpl;
0042: import org.apache.openjpa.lib.conf.Configuration;
0043: import org.apache.openjpa.lib.conf.Configurations;
0044: import org.apache.openjpa.lib.log.Log;
0045: import org.apache.openjpa.lib.meta.ClassArgParser;
0046: import org.apache.openjpa.lib.util.CodeFormat;
0047: import org.apache.openjpa.lib.util.Files;
0048: import org.apache.openjpa.lib.util.J2DoPrivHelper;
0049: import org.apache.openjpa.lib.util.JavaVersions;
0050: import org.apache.openjpa.lib.util.Localizer;
0051: import org.apache.openjpa.lib.util.Options;
0052: import org.apache.openjpa.meta.ClassMetaData;
0053: import org.apache.openjpa.meta.DelegatingMetaDataFactory;
0054: import org.apache.openjpa.meta.FieldMetaData;
0055: import org.apache.openjpa.meta.JavaTypes;
0056: import org.apache.openjpa.meta.MetaDataFactory;
0057: import org.apache.openjpa.meta.MetaDataRepository;
0058: import org.apache.openjpa.util.InvalidStateException;
0059: import org.apache.openjpa.util.UserException;
0060: import serp.bytecode.BCClass;
0061: import serp.bytecode.BCClassLoader;
0062: import serp.bytecode.Project;
0063: import serp.util.Strings;
0064:
0065: /**
0066: * Generates a class appropriate for use as an application identity class.
0067: *
0068: * @author Patrick Linskey
0069: * @author Abe White
0070: */
0071: public class ApplicationIdTool {
0072:
0073: public static final String TOKEN_DEFAULT = "::";
0074:
0075: private static final String TOKENIZER_CUSTOM = "Tokenizer";
0076: private static final String TOKENIZER_STD = "StringTokenizer";
0077:
0078: private static final Localizer _loc = Localizer
0079: .forPackage(ApplicationIdTool.class);
0080:
0081: private final Log _log;
0082: private final Class _type;
0083: private final ClassMetaData _meta;
0084: private boolean _abstract = false;
0085: private FieldMetaData[] _fields = null;
0086: private boolean _ignore = true;
0087: private File _dir = null;
0088: private Writer _writer = null;
0089: private String _code = null;
0090: private String _token = TOKEN_DEFAULT;
0091: private CodeFormat _format = null;
0092:
0093: /**
0094: * Constructs a new ApplicationIdTool capable of generating an
0095: * object id class for <code>type</code>.
0096: */
0097: public ApplicationIdTool(OpenJPAConfiguration conf, Class type) {
0098: _log = conf.getLog(OpenJPAConfiguration.LOG_ENHANCE);
0099: _type = type;
0100:
0101: MetaDataRepository repos = conf.newMetaDataRepositoryInstance();
0102: repos.setValidate(repos.VALIDATE_NONE);
0103: repos.setSourceMode(repos.MODE_MAPPING, false);
0104: loadObjectIds(repos, true);
0105: _meta = repos.getMetaData(type, null, false);
0106: if (_meta != null) {
0107: _abstract = Modifier.isAbstract(_meta.getDescribedType()
0108: .getModifiers());
0109: _fields = getDeclaredPrimaryKeyFields(_meta);
0110: }
0111: }
0112:
0113: /**
0114: * Constructs a new tool instance capable of generating an
0115: * object id class for <code>meta</code>.
0116: */
0117: public ApplicationIdTool(OpenJPAConfiguration conf, Class type,
0118: ClassMetaData meta) {
0119: _log = conf.getLog(OpenJPAConfiguration.LOG_ENHANCE);
0120:
0121: _type = type;
0122: _meta = meta;
0123: if (_meta != null) {
0124: _abstract = Modifier.isAbstract(_meta.getDescribedType()
0125: .getModifiers());
0126: _fields = getDeclaredPrimaryKeyFields(_meta);
0127: }
0128: }
0129:
0130: /**
0131: * Return metadata for primary key fields declared in the given class.
0132: */
0133: private static FieldMetaData[] getDeclaredPrimaryKeyFields(
0134: ClassMetaData meta) {
0135: if (meta.getPCSuperclass() == null)
0136: return meta.getPrimaryKeyFields();
0137:
0138: // remove the primary key fields that are not declared
0139: // in the current class
0140: FieldMetaData[] fields = meta.getPrimaryKeyFields();
0141: List decs = new ArrayList(fields.length);
0142: for (int i = 0; i < fields.length; i++)
0143: if (fields[i].getDeclaringType() == meta.getDescribedType())
0144: decs.add(fields[i]);
0145: return (FieldMetaData[]) decs.toArray(new FieldMetaData[decs
0146: .size()]);
0147: }
0148:
0149: /**
0150: * Return false if this tool is configured to throw an exception on
0151: * an attempt to generate an id class for a type that does not use
0152: * application identity.
0153: */
0154: public boolean getIgnoreErrors() {
0155: return _ignore;
0156: }
0157:
0158: /**
0159: * Set to false if this tool should throw an exception on
0160: * an attempt to generate an id class for a type that does not use
0161: * application identity.
0162: */
0163: public void setIgnoreErrors(boolean ignore) {
0164: _ignore = ignore;
0165: }
0166:
0167: /**
0168: * The code formatter for the generated Java code.
0169: */
0170: public CodeFormat getCodeFormat() {
0171: return _format;
0172: }
0173:
0174: /**
0175: * Set the code formatter for the generated Java code.
0176: */
0177: public void setCodeFormat(CodeFormat format) {
0178: _format = format;
0179: }
0180:
0181: /**
0182: * The directory to write source to. Defaults to the directory
0183: * of the Java file for the set type. If the given directory does not
0184: * match the package of the object id, the package structure will be
0185: * created below the directory.
0186: */
0187: public File getDirectory() {
0188: return _dir;
0189: }
0190:
0191: /**
0192: * The directory to write source to. Defaults to the directory
0193: * of the Java file for the set type. If the given directory does not
0194: * match the package of the object id, the package structure will be
0195: * created below the directory.
0196: */
0197: public void setDirectory(File dir) {
0198: _dir = dir;
0199: }
0200:
0201: /**
0202: * The token to use to separate stringified primary key field values.
0203: */
0204: public String getToken() {
0205: return _token;
0206: }
0207:
0208: /**
0209: * The token to use to separate stringified primary key field values.
0210: */
0211: public void setToken(String token) {
0212: _token = token;
0213: }
0214:
0215: /**
0216: * The writer to write source to, or null to write to default file.
0217: */
0218: public Writer getWriter() {
0219: return _writer;
0220: }
0221:
0222: /**
0223: * The writer to write source to, or null to write to default file.
0224: */
0225: public void setWriter(Writer writer) {
0226: _writer = writer;
0227: }
0228:
0229: /**
0230: * Return the type we are generating an application id for.
0231: */
0232: public Class getType() {
0233: return _type;
0234: }
0235:
0236: /**
0237: * Return metadata for the type we are generating an application id for.
0238: */
0239: public ClassMetaData getMetaData() {
0240: return _meta;
0241: }
0242:
0243: /**
0244: * Return the code generated for the application id, or null
0245: * if invalid class or the {@link #run} method has not been called.
0246: */
0247: public String getCode() {
0248: return _code;
0249: }
0250:
0251: /**
0252: * Returns true if the application identity class should be an inner class.
0253: */
0254: public boolean isInnerClass() {
0255: Class oidClass = _meta.getObjectIdType();
0256: return oidClass.getName().indexOf('$') != -1;
0257: }
0258:
0259: /**
0260: * Returns the short class name for the object id class.
0261: */
0262: private String getClassName() {
0263: if (_meta.isOpenJPAIdentity())
0264: return null;
0265:
0266: // convert from SomeClass$ID to ID
0267: String className = Strings
0268: .getClassName(_meta.getObjectIdType());
0269: if (isInnerClass())
0270: className = className
0271: .substring(className.lastIndexOf('$') + 1);
0272: return className;
0273: }
0274:
0275: /**
0276: * Generates the sourcecode for the application id class; returns
0277: * false if the class is invalid.
0278: */
0279: public boolean run() {
0280: if (_log.isInfoEnabled())
0281: _log.info(_loc.get("appid-start", _type));
0282:
0283: // ensure that this type is a candidate for application identity
0284: if (_meta == null
0285: || _meta.getIdentityType() != ClassMetaData.ID_APPLICATION
0286: || _meta.isOpenJPAIdentity()) {
0287: if (!_ignore)
0288: throw new UserException(_loc
0289: .get("appid-invalid", _type));
0290:
0291: // else just warn
0292: if (_log.isWarnEnabled())
0293: _log.warn(_loc.get("appid-warn", _type));
0294: return false;
0295: }
0296:
0297: Class oidClass = _meta.getObjectIdType();
0298: Class super OidClass = null;
0299:
0300: // allow diff oid class in subclass (horizontal)
0301: if (_meta.getPCSuperclass() != null) {
0302: super OidClass = _meta.getPCSuperclassMetaData()
0303: .getObjectIdType();
0304: if (oidClass == null || oidClass.equals(super OidClass)) {
0305: // just warn
0306: if (_log.isWarnEnabled())
0307: _log.warn(_loc.get("appid-warn", _type));
0308: return false;
0309: }
0310: }
0311:
0312: // ensure that an id class is declared
0313: if (oidClass == null)
0314: throw new UserException(_loc.get("no-id-class", _type))
0315: .setFatal(true);
0316:
0317: // ensure there is at least one pk field if we are
0318: // non-absract, and see if we have any byte[]
0319: boolean bytes = false;
0320: for (int i = 0; !bytes && i < _fields.length; i++)
0321: bytes = _fields[i].getDeclaredType() == byte[].class;
0322:
0323: // collect info on id type
0324: String className = getClassName();
0325: String packageName = Strings.getPackageName(oidClass);
0326: String packageDec = "";
0327: if (packageName.length() > 0)
0328: packageDec = "package " + packageName + ";";
0329:
0330: String imports = getImports();
0331: String fieldDecs = getFieldDeclarations();
0332: String constructor = getConstructor(super OidClass != null);
0333: String properties = getProperties();
0334: String fromStringCode = getFromStringCode(super OidClass != null);
0335: String toStringCode = getToStringCode(super OidClass != null);
0336: String equalsCode = getEqualsCode(super OidClass != null);
0337: String hashCodeCode = getHashCodeCode(super OidClass != null);
0338:
0339: // build the java code
0340: CodeFormat code = newCodeFormat();
0341: if (!isInnerClass() && packageDec.length() > 0)
0342: code.append(packageDec).afterSection();
0343:
0344: if (!isInnerClass() && imports.length() > 0)
0345: code.append(imports).afterSection();
0346:
0347: code.append("/**").endl().append(" * ").append(
0348: _loc.get("appid-comment-for", _type.getName())).endl()
0349: .append(" *").endl().append(" * ").append(
0350: _loc.get("appid-comment-gen")).endl().append(
0351: " * ").append(getClass().getName()).endl()
0352: .append(" */").endl();
0353: code.append("public ");
0354: if (isInnerClass())
0355: code.append("static ");
0356: code.append("class ").append(className);
0357: if (code.getBraceOnSameLine())
0358: code.append(" ");
0359: else
0360: code.endl().tab();
0361:
0362: if (super OidClass != null) {
0363: code.append("extends "
0364: + Strings.getClassName(super OidClass));
0365: if (code.getBraceOnSameLine())
0366: code.append(" ");
0367: else
0368: code.endl().tab();
0369: }
0370: code.append("implements Serializable").openBrace(1).endl();
0371:
0372: // if we use a byte array we need a static array for encoding to string
0373: if (bytes) {
0374: code.tab().append("private static final char[] HEX = ")
0375: .append("new char[] {").endl();
0376: code.tab(2).append(
0377: "'0', '1', '2', '3', '4', '5', '6', '7',").endl();
0378: code.tab(2)
0379: .append("'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'")
0380: .endl();
0381: code.tab().append("};").endl(2);
0382: }
0383:
0384: // static block to register class
0385: code.tab().append("static").openBrace(2).endl();
0386: code.tab(2).append("// register persistent class in JVM")
0387: .endl();
0388: if (JavaVersions.VERSION >= 5) {
0389: code.tab(2).append("try { Class.forName").openParen(true)
0390: .append("\"").append(_type.getName()).append("\"")
0391: .closeParen().append(";").append(" }").endl();
0392: code.tab(2).append("catch").openParen(true).append(
0393: "Exception e").closeParen().append(" {}").endl();
0394: } else
0395: code.tab(2).append("Class c = ").append(_type.getName())
0396: .append(".class;").endl();
0397: code.closeBrace(2);
0398:
0399: // field declarations
0400: if (fieldDecs.length() > 0)
0401: code.endl(2).append(fieldDecs);
0402:
0403: // default constructor
0404: code.afterSection().tab().append("public ").append(className)
0405: .parens().openBrace(2).endl();
0406: code.closeBrace(2);
0407:
0408: // string constructor
0409: code.afterSection().append(constructor);
0410:
0411: // properties
0412: if (properties.length() > 0)
0413: code.afterSection().append(properties);
0414:
0415: // toString, equals, hashCode methods
0416: if (toStringCode.length() > 0)
0417: code.afterSection().append(toStringCode);
0418: if (hashCodeCode.length() > 0)
0419: code.afterSection().append(hashCodeCode);
0420: if (equalsCode.length() > 0)
0421: code.afterSection().append(equalsCode);
0422: if (fromStringCode.length() > 0)
0423: code.afterSection().append(fromStringCode);
0424:
0425: // if we have any byte array fields, we have to add the extra
0426: // methods for handling byte arrays
0427: if (bytes) {
0428: code.afterSection().append(getToBytesByteArrayCode());
0429: code.afterSection().append(getToStringByteArrayCode());
0430: code.afterSection().append(getEqualsByteArrayCode());
0431: code.afterSection().append(getHashCodeByteArrayCode());
0432: }
0433:
0434: // base classes might need to define a custom tokenizer
0435: if (super OidClass == null
0436: && getTokenizer(false) == TOKENIZER_CUSTOM)
0437: code.afterSection().append(getCustomTokenizerClass());
0438:
0439: code.endl();
0440: code.closeBrace(1);
0441:
0442: _code = code.toString();
0443:
0444: // if this is an inner class, then indent the entire
0445: // code unit one tab level
0446: if (isInnerClass()) {
0447: // indent the entire code block one level to make it
0448: // a propertly indented innder class
0449: _code = code.getTab()
0450: + Strings.replace(_code, J2DoPrivHelper
0451: .getLineSeparator(), J2DoPrivHelper
0452: .getLineSeparator()
0453: + code.getTab());
0454: }
0455:
0456: return true;
0457: }
0458:
0459: /**
0460: * Writes the generated code to the proper file.
0461: */
0462: public void record() throws IOException {
0463: if (_code == null)
0464: return;
0465:
0466: Writer writer = _writer;
0467: if (writer == null) {
0468: File file = getFile();
0469: Files.backup(file, false);
0470: writer = new FileWriter(file);
0471: }
0472:
0473: PrintWriter printer = new PrintWriter(writer);
0474: printer.print(_code);
0475: printer.flush();
0476:
0477: if (_writer == null)
0478: writer.close();
0479: }
0480:
0481: /**
0482: * Return the necessary imports for the class.
0483: */
0484: private String getImports() {
0485: Set pkgs = getImportPackages();
0486:
0487: CodeFormat imports = newCodeFormat();
0488: String base = Strings.getPackageName(_meta.getObjectIdType());
0489: String pkg;
0490: for (Iterator itr = pkgs.iterator(); itr.hasNext();) {
0491: pkg = (String) itr.next();
0492: if (pkg.length() > 0 && !"java.lang".equals(pkg)
0493: && !base.equals(pkg)) {
0494: if (imports.length() > 0)
0495: imports.endl();
0496: imports.append("import ").append(pkg).append(".*;");
0497: }
0498: }
0499: return imports.toString();
0500: }
0501:
0502: /**
0503: * Returns the collection of packages that need to be imported.
0504: */
0505: public Set getImportPackages() {
0506: Set pkgs = new TreeSet();
0507: pkgs.add(Strings.getPackageName(_type));
0508:
0509: Class super OidClass = null;
0510: if (_meta != null && _meta.getPCSuperclassMetaData() != null)
0511: super OidClass = _meta.getPCSuperclassMetaData()
0512: .getObjectIdType();
0513: if (super OidClass != null)
0514: pkgs.add(Strings.getPackageName(super OidClass));
0515:
0516: pkgs.add("java.io");
0517: pkgs.add("java.util");
0518: Class type;
0519: for (int i = 0; i < _fields.length; i++) {
0520: type = _fields[i].getObjectIdFieldType();
0521: if (type != byte[].class && type != char[].class
0522: && !type.getName().startsWith("java.sql.")) {
0523: pkgs.add(Strings.getPackageName(type));
0524: }
0525: }
0526: return pkgs;
0527: }
0528:
0529: /**
0530: * Return the code to declare all primary key fields.
0531: */
0532: private String getFieldDeclarations() {
0533: CodeFormat code = newCodeFormat();
0534: for (int i = 0; i < _fields.length; i++) {
0535: if (i > 0)
0536: code.endl();
0537: code.tab().append("public ")
0538: .append(getTypeName(_fields[i])).append(" ")
0539: .append(_fields[i].getName()).append(";");
0540: }
0541: return code.toString();
0542: }
0543:
0544: /**
0545: * Return the type name to declare the given field as.
0546: */
0547: private String getTypeName(FieldMetaData fmd) {
0548: Class type = fmd.getObjectIdFieldType();
0549: if (type == byte[].class)
0550: return "byte[]";
0551: if (type == char[].class)
0552: return "char[]";
0553: if (type.getName().startsWith("java.sql."))
0554: return type.getName();
0555: return Strings.getClassName(type);
0556: }
0557:
0558: /**
0559: * Return the getters and setters for all primary key fields.
0560: */
0561: private String getProperties() {
0562: if (_meta.getAccessType() == ClassMetaData.ACCESS_FIELD)
0563: return "";
0564:
0565: CodeFormat code = newCodeFormat();
0566: String propName;
0567: String typeName;
0568: for (int i = 0; i < _fields.length; i++) {
0569: if (i > 0)
0570: code.afterSection();
0571: typeName = getTypeName(_fields[i]);
0572: propName = StringUtils.capitalize(_fields[i].getName());
0573:
0574: code.tab().append("public ").append(typeName).append(" ");
0575: if (_fields[i].getDeclaredTypeCode() == JavaTypes.BOOLEAN
0576: || _fields[i].getDeclaredTypeCode() == JavaTypes.BOOLEAN_OBJ)
0577: code.append("is");
0578: else
0579: code.append("get");
0580: code.append(propName).parens().openBrace(2).endl();
0581: code.tab(2).append("return ").append(_fields[i].getName())
0582: .append(";").endl();
0583: code.closeBrace(2);
0584: code.afterSection();
0585:
0586: code.tab().append("public void set").append(propName);
0587: code.openParen(true).append(typeName).append(" ").append(
0588: _fields[i].getName()).closeParen();
0589: code.openBrace(2).endl();
0590: code.tab(2).append("this.").append(_fields[i].getName())
0591: .append(" = ").append(_fields[i].getName()).append(
0592: ";").endl();
0593: code.closeBrace(2);
0594: }
0595: return code.toString();
0596: }
0597:
0598: /**
0599: * Return the string constructor code.
0600: */
0601: private String getConstructor(boolean hasSuperclass) {
0602: CodeFormat code = newCodeFormat();
0603: code.tab().append("public ");
0604: code.append(getClassName());
0605: code.openParen(true).append("String str").closeParen();
0606: code.openBrace(2).endl();
0607:
0608: if (_fields.length != 0
0609: || (hasSuperclass && _meta.getPrimaryKeyFields().length > 0)) {
0610: code.tab(2).append("fromString").openParen(true).append(
0611: "str").closeParen().append(";").endl();
0612: }
0613:
0614: code.closeBrace(2);
0615: return code.toString();
0616: }
0617:
0618: /**
0619: * Create the fromString method that parses the result of our toString
0620: * method. If we have superclasses with id fields, this will call
0621: * super.fromString() so that the parent class can parse its own fields.
0622: */
0623: private String getFromStringCode(boolean hasSuperclass) {
0624: // if we are below a concrete class then we cannot declare any
0625: // more primary key fields; thus, just use the parent invocation
0626: if (hasConcreteSuperclass())
0627: return "";
0628: if (_fields.length == 0)
0629: return "";
0630: hasSuperclass = hasSuperclass
0631: && getDeclaredPrimaryKeyFields(_meta
0632: .getPCSuperclassMetaData()).length > 0;
0633:
0634: String toke = getTokenizer(hasSuperclass);
0635: CodeFormat code = newCodeFormat();
0636: if (_abstract || hasSuperclass)
0637: code.tab().append("protected ").append(toke).append(
0638: " fromString");
0639: else
0640: code.tab().append("private void fromString");
0641: code.openParen(true).append("String str").closeParen();
0642: code.openBrace(2).endl();
0643:
0644: // if we have any Object-type fields, die immediately
0645: for (int i = 0; i < _fields.length; i++) {
0646: if (_fields[i].getObjectIdFieldType() != Object.class)
0647: continue;
0648: code.tab(2).append(
0649: "throw new UnsupportedOperationException").parens()
0650: .append(";").endl();
0651: code.closeBrace(2);
0652: return code.toString();
0653: }
0654:
0655: if (toke != null) {
0656: code.tab(2).append(toke).append(" toke = ");
0657: if (hasSuperclass) {
0658: // call super.fromString(str) to get the tokenizer that was
0659: // used to parse the superclass
0660: code.append("super.fromString").openParen(true).append(
0661: "str").closeParen();
0662: } else {
0663: // otherwise construct a new tokenizer with the string
0664: code.append("new ").append(toke).openParen(true)
0665: .append("str");
0666: if (toke == TOKENIZER_STD)
0667: code.append(", \"").append(_token).append("\"");
0668: code.closeParen();
0669: }
0670: code.append(";").endl();
0671: }
0672:
0673: for (int i = 0; i < _fields.length; i++) {
0674: if (toke != null) {
0675: code.tab(2).append("str = toke.nextToken").parens()
0676: .append(";").endl();
0677: }
0678: code.tab(2).append(getConversionCode(_fields[i], "str"))
0679: .endl();
0680: }
0681: if (_abstract || hasSuperclass)
0682: code.tab(2).append("return toke;").endl();
0683: code.closeBrace(2);
0684: return code.toString();
0685: }
0686:
0687: /**
0688: * Returns the type of tokenizer to use, or null if none.
0689: */
0690: private String getTokenizer(boolean hasSuperclass) {
0691: if (!_abstract && !hasSuperclass && _fields.length == 1)
0692: return null;
0693: if (_token.length() == 1)
0694: return TOKENIZER_STD;
0695: return TOKENIZER_CUSTOM;
0696: }
0697:
0698: /**
0699: * Get parsing code for the given field.
0700: */
0701: private String getConversionCode(FieldMetaData field, String var) {
0702: CodeFormat parse = newCodeFormat();
0703: if (field.getName().equals(var))
0704: parse.append("this.");
0705: parse.append(field.getName()).append(" = ");
0706:
0707: Class type = field.getObjectIdFieldType();
0708: if (type == Date.class) {
0709: parse.append("new Date").openParen(true).append(
0710: "Long.parseLong").openParen(true).append(var)
0711: .closeParen().closeParen();
0712: } else if (type == java.sql.Date.class
0713: || type == java.sql.Timestamp.class
0714: || type == java.sql.Time.class) {
0715: parse.append(type.getName()).append(".valueOf").openParen(
0716: true).append(var).closeParen();
0717: } else if (type == String.class)
0718: parse.append(var);
0719: else if (type == Character.class) {
0720: parse.append("new Character").openParen(true).append(var)
0721: .append(".charAt").openParen(true).append(0)
0722: .closeParen().closeParen();
0723: } else if (type == byte[].class)
0724: parse.append("toBytes").openParen(true).append(var)
0725: .closeParen();
0726: else if (type == char[].class)
0727: parse.append(var).append(".toCharArray").parens();
0728: else if (!type.isPrimitive()) {
0729: parse.append("new ").append(Strings.getClassName(type))
0730: .openParen(true).append(var).closeParen();
0731: } else // primitive
0732: {
0733: switch (type.getName().charAt(0)) {
0734: case 'b':
0735: if (type == boolean.class)
0736: parse.append("\"true\".equals").openParen(true)
0737: .append(var).closeParen();
0738: else
0739: parse.append("Byte.parseByte").openParen(true)
0740: .append(var).closeParen();
0741: break;
0742: case 'c':
0743: parse.append(var).append(".charAt").openParen(true)
0744: .append(0).closeParen();
0745: break;
0746: case 'd':
0747: parse.append("Double.parseDouble").openParen(true)
0748: .append(var).closeParen();
0749: break;
0750: case 'f':
0751: parse.append("Float.parseFloat").openParen(true)
0752: .append(var).closeParen();
0753: break;
0754: case 'i':
0755: parse.append("Integer.parseInt").openParen(true)
0756: .append(var).closeParen();
0757: break;
0758: case 'l':
0759: parse.append("Long.parseLong").openParen(true).append(
0760: var).closeParen();
0761: break;
0762: case 's':
0763: parse.append("Short.parseShort").openParen(true)
0764: .append(var).closeParen();
0765: break;
0766: }
0767: }
0768:
0769: if (!type.isPrimitive() && type != byte[].class) {
0770: CodeFormat isNull = newCodeFormat();
0771: isNull.append("if").openParen(true).append(
0772: "\"null\".equals").openParen(true).append(var)
0773: .closeParen().closeParen().endl().tab(3);
0774: if (field.getName().equals(var))
0775: isNull.append("this.");
0776: isNull.append(field.getName()).append(" = null;").endl();
0777: isNull.tab(2).append("else").endl();
0778: isNull.tab(3).append(parse);
0779: parse = isNull;
0780: }
0781:
0782: return parse.append(";").toString();
0783: }
0784:
0785: /**
0786: * Return an equality method that compares all pk variables.
0787: * Must deal correctly with both primitives and objects.
0788: */
0789: private String getEqualsCode(boolean hasSuperclass) {
0790: // if we are below a concrete class then we cannot declare any
0791: // more primary key fields; thus, just use the parent invocation
0792: if (hasConcreteSuperclass()
0793: || (hasSuperclass && _fields.length == 0))
0794: return "";
0795:
0796: CodeFormat code = newCodeFormat();
0797: code.tab().append("public boolean equals").openParen(true)
0798: .append("Object obj").closeParen().openBrace(2).endl();
0799:
0800: code.tab(2).append("if").openParen(true).append("this == obj")
0801: .closeParen().endl();
0802: code.tab(3).append("return true;").endl();
0803:
0804: // call super.equals() if we have a superclass
0805: String className = getClassName();
0806: if (hasSuperclass) {
0807: code.tab(2).append("if").openParen(true).append(
0808: "!super.equals").openParen(true).append("obj")
0809: .closeParen().closeParen().endl();
0810: code.tab(3).append("return false;").endl();
0811: } else {
0812: code.tab(2).append("if").openParen(true).append(
0813: "obj == null || obj.getClass").parens().append(
0814: " != ").append("getClass").parens().closeParen()
0815: .endl();
0816: code.tab(3).append("return false;").endl();
0817: }
0818:
0819: String name;
0820: Class type;
0821: for (int i = 0; i < _fields.length; i++) {
0822: if (i == 0) {
0823: code.endl().tab(2).append(className)
0824: .append(" other = ").openParen(false).append(
0825: className).closeParen().append(" obj;")
0826: .endl();
0827: }
0828:
0829: // if this is not the first field, add an &&
0830: if (i == 0)
0831: code.tab(2).append("return ");
0832: else
0833: code.endl().tab(3).append("&& ");
0834:
0835: name = _fields[i].getName();
0836: type = _fields[i].getObjectIdFieldType();
0837: if (type.isPrimitive()) {
0838: code.openParen(false).append(name).append(" == ")
0839: .append("other.").append(name).closeParen();
0840: } else if (type == byte[].class) {
0841: code.openParen(false).append("equals").openParen(true)
0842: .append(name).append(", ").append("other.")
0843: .append(name).closeParen().closeParen();
0844: } else if (type == char[].class) {
0845: // ((name == null && other.name == null)
0846: // || (name != null && String.valueOf (name).
0847: // equals (String.valueOf (other.name))))
0848: code.append("(").openParen(false).append(name).append(
0849: " == null && other.").append(name).append(
0850: " == null").closeParen().endl();
0851: code.tab(3).append("|| ");
0852: code.openParen(false).append(name).append(" != null ")
0853: .append("&& String.valueOf").openParen(true)
0854: .append(name).closeParen().append(".").endl();
0855: code.tab(3).append("equals").openParen(true).append(
0856: "String.valueOf").openParen(true).append(
0857: "other.").append(name).closeParen()
0858: .closeParen().closeParen().append(")");
0859: } else {
0860: // ((name == null && other.name == null)
0861: // || (name != null && name.equals (other.name)))
0862: code.append("(").openParen(false).append(name).append(
0863: " == null && other.").append(name).append(
0864: " == null").closeParen().endl();
0865: code.tab(3).append("|| ");
0866: code.openParen(false).append(name).append(" != null ")
0867: .append("&& ").append(name).append(".equals")
0868: .openParen(true).append("other.").append(name)
0869: .closeParen().closeParen().append(")");
0870: }
0871: }
0872:
0873: // no _fields: just return true after checking instanceof
0874: if (_fields.length == 0)
0875: code.tab(2).append("return true;").endl();
0876: else
0877: code.append(";").endl();
0878:
0879: code.closeBrace(2);
0880: return code.toString();
0881: }
0882:
0883: /**
0884: * Return a hashCode method that takes into account all
0885: * primary key values. Must deal correctly with both primitives and objects.
0886: */
0887: private String getHashCodeCode(boolean hasSuperclass) {
0888: // if we are below a concrete class then we cannot declare any
0889: // more primary key fields; thus, just use the parent invocation
0890: if (hasConcreteSuperclass()
0891: || (hasSuperclass && _fields.length == 0))
0892: return "";
0893:
0894: CodeFormat code = newCodeFormat();
0895: code.tab().append("public int hashCode").parens().openBrace(2)
0896: .endl();
0897:
0898: if (_fields.length == 0)
0899: code.tab(2).append("return 17;").endl();
0900: else if (_fields.length == 1 && !hasSuperclass) {
0901: code.tab(2).append("return ");
0902: appendHashCodeCode(_fields[0], code);
0903: code.append(";").endl();
0904: } else {
0905: code.tab(2).append("int rs = ");
0906: if (hasSuperclass) {
0907: // call super.hashCode() if we have a superclass
0908: code.append("super.hashCode").openParen(true)
0909: .closeParen().append(";");
0910: } else
0911: code.append("17;");
0912: code.endl();
0913:
0914: for (int i = 0; i < _fields.length; i++) {
0915: code.tab(2).append("rs = rs * 37 + ");
0916: appendHashCodeCode(_fields[i], code);
0917: code.append(";").endl();
0918: }
0919: code.tab(2).append("return rs;").endl();
0920: }
0921: code.closeBrace(2);
0922: return code.toString();
0923: }
0924:
0925: /**
0926: * Return true if this class has a concrete superclass.
0927: */
0928: private boolean hasConcreteSuperclass() {
0929: for (ClassMetaData sup = _meta.getPCSuperclassMetaData(); sup != null; sup = sup
0930: .getPCSuperclassMetaData()) {
0931: if (!Modifier.isAbstract(sup.getDescribedType()
0932: .getModifiers()))
0933: return true;
0934: }
0935: return false;
0936: }
0937:
0938: /**
0939: * Append code calculating the hashcode for the given field.
0940: */
0941: private void appendHashCodeCode(FieldMetaData field, CodeFormat code) {
0942: String name = field.getName();
0943: if ("rs".equals(name))
0944: name = "this." + name;
0945: Class type = field.getObjectIdFieldType();
0946: if (type.isPrimitive()) {
0947: if (type == boolean.class) {
0948: // ((name) ? 1 : 0)
0949: code.append("(").openParen(false).append(name)
0950: .closeParen().append(" ? 1 : 0").append(")");
0951: } else if (type == long.class) {
0952: // (int) (name ^ (name >>> 32))
0953: code.openParen(false).append("int").closeParen()
0954: .append(" ").openParen(false).append(name)
0955: .append(" ^ ").openParen(false).append(name)
0956: .append(" >>> 32").closeParen().closeParen();
0957: } else if (type == double.class) {
0958: // (int) (Double.doubleToLongBits (name)
0959: // ^ (Double.doubleToLongBits (name) >>> 32))
0960: code.openParen(false).append("int").closeParen()
0961: .append(" ").openParen(false).append(
0962: "Double.doubleToLongBits").openParen(
0963: true).append(name).closeParen().endl();
0964: code.tab(3).append("^ ").openParen(false).append(
0965: "Double.doubleToLongBits").openParen(true)
0966: .append(name).closeParen().append(" >>> 32")
0967: .closeParen().closeParen();
0968: } else if (type == float.class) {
0969: // Float.floatToIntBits (name)
0970: code.append("Float.floatToIntBits").openParen(true)
0971: .append(name).closeParen();
0972: } else if (type == int.class)
0973: code.append(name);
0974: else {
0975: // (int) name
0976: code.openParen(false).append("int").closeParen()
0977: .append(" ").append(name);
0978: }
0979: } else if (type == byte[].class) {
0980: // hashCode (name);
0981: code.append("hashCode").openParen(true).append(name)
0982: .closeParen();
0983: } else if (type == char[].class) {
0984: // ((name == null) ? 0 : String.valueOf (name).hashCode ())
0985: code.append("(").openParen(false).append(name).append(
0986: " == null").closeParen().append(" ? 0 : ").append(
0987: "String.valueOf").openParen(true).append(name)
0988: .closeParen().append(".hashCode").parens().append(
0989: ")");
0990: } else {
0991: // ((name == null) ? 0 : name.hashCode ())
0992: code.append("(").openParen(false).append(name).append(
0993: " == null").closeParen().append(" ? 0 : ").append(
0994: name).append(".hashCode").parens().append(")");
0995: }
0996: }
0997:
0998: /**
0999: * Return a method to create a string containing the primary key
1000: * values that define the application id object.
1001: */
1002: private String getToStringCode(boolean hasSuperclass) {
1003: // if we are below a concrete class then we cannot declare any
1004: // more primary key fields; thus, just use the parent invocation
1005: if (hasConcreteSuperclass()
1006: || (hasSuperclass && _fields.length == 0))
1007: return "";
1008:
1009: CodeFormat code = newCodeFormat();
1010: code.tab().append("public String toString").parens().openBrace(
1011: 2).endl();
1012:
1013: String name;
1014: Class type;
1015: String appendDelimiter = "+ \"" + _token + "\" + ";
1016: for (int i = 0; i < _fields.length; i++) {
1017: // if this is not the first field, add a +
1018: if (i == 0) {
1019: code.tab(2).append("return ");
1020:
1021: // add in the super.toString() if we have a parent
1022: if (hasSuperclass
1023: && getDeclaredPrimaryKeyFields(_meta
1024: .getPCSuperclassMetaData()).length > 0) {
1025: code.append("super.toString").parens();
1026: code.endl().tab(3).append(appendDelimiter);
1027: }
1028: } else
1029: code.endl().tab(3).append(appendDelimiter);
1030:
1031: name = _fields[i].getName();
1032: type = _fields[i].getObjectIdFieldType();
1033: if (type == String.class)
1034: code.append(name);
1035: else if (type == byte[].class)
1036: code.append("toString").openParen(true).append(name)
1037: .closeParen();
1038: else if (type == char[].class)
1039: code.openParen(true).openParen(true).append(name)
1040: .append(" == null").closeParen().append(
1041: " ? \"null\"").append(
1042: ": String.valueOf").openParen(true)
1043: .append(name).closeParen().closeParen();
1044: else if (type == Date.class)
1045: code.openParen(true).openParen(true).append(name)
1046: .append(" == null").closeParen().append(
1047: " ? \"null\"").endl().tab(4).append(
1048: ": String.valueOf").openParen(true)
1049: .append(name).append(".getTime").parens()
1050: .closeParen().closeParen();
1051: else
1052: code.append("String.valueOf").openParen(true).append(
1053: name).closeParen();
1054: }
1055:
1056: // no fields; just use ""
1057: if (_fields.length == 0)
1058: code.tab(2).append("return \"\"");
1059: code.append(";").endl();
1060: code.closeBrace(2);
1061: return code.toString();
1062: }
1063:
1064: /**
1065: * Code to convert a string to a byte array.
1066: *
1067: * @see org.apache.openjpa.lib.util.Base16Encoder#decode
1068: */
1069: private String getToBytesByteArrayCode() {
1070: CodeFormat code = newCodeFormat();
1071: code.tab().append("private static byte[] toBytes").openParen(
1072: true).append("String s").closeParen().openBrace(2)
1073: .endl();
1074:
1075: code.tab(2).append("if").openParen(true).append(
1076: "\"null\".equals").openParen(true).append("s")
1077: .closeParen().closeParen().endl();
1078: code.tab(3).append("return null;").endl(2);
1079:
1080: code.tab(2).append("int len = s.length").parens().append(";")
1081: .endl();
1082: code.tab(2).append("byte[] r = new byte[len / 2];").endl();
1083: code.tab(2).append("for").openParen(true).append(
1084: "int i = 0; i < r.length; i++").closeParen().openBrace(
1085: 3).endl();
1086: code.tab(3).append("int digit1 = s.charAt").openParen(true)
1087: .append("i * 2").closeParen().append(", ").append(
1088: "digit2 = s.charAt").openParen(true).append(
1089: "i * 2 + 1").closeParen().append(";").endl();
1090: code.tab(3).append("if").openParen(true).append(
1091: "digit1 >= '0' && digit1 <= '9'").closeParen().endl();
1092: code.tab(4).append("digit1 -= '0';").endl();
1093: code.tab(3).append("else if").openParen(true).append(
1094: "digit1 >= 'A' && digit1 <= 'F'").closeParen().endl();
1095: code.tab(4).append("digit1 -= 'A' - 10;").endl();
1096: code.tab(3).append("if").openParen(true).append(
1097: "digit2 >= '0' && digit2 <= '9'").closeParen().endl();
1098: code.tab(4).append("digit2 -= '0';").endl();
1099: code.tab(3).append("else if").openParen(true).append(
1100: "digit2 >= 'A' && digit2 <= 'F'").closeParen().endl();
1101: code.tab(4).append("digit2 -= 'A' - 10;").endl(2);
1102: code.tab(3).append("r[i] = (byte) ").openParen(false)
1103: .openParen(false).append("digit1 << 4").closeParen()
1104: .append(" + digit2").closeParen().append(";").endl();
1105: code.closeBrace(3).endl();
1106: code.tab(2).append("return r;").endl();
1107:
1108: code.closeBrace(2);
1109: return code.toString();
1110: }
1111:
1112: /**
1113: * Code to convert a byte array to a string.
1114: *
1115: * @see org.apache.openjpa.lib.util.Base16Encoder#encode
1116: */
1117: private String getToStringByteArrayCode() {
1118: CodeFormat code = newCodeFormat();
1119: code.tab().append("private static String toString").openParen(
1120: true).append("byte[] b").closeParen().openBrace(2)
1121: .endl();
1122:
1123: code.tab(2).append("if").openParen(true).append("b == null")
1124: .closeParen().endl();
1125: code.tab(3).append("return \"null\";").endl(2);
1126:
1127: code.tab(2).append("StringBuffer r = new StringBuffer")
1128: .openParen(true).append("b.length * 2").closeParen()
1129: .append(";").endl();
1130: code.tab(2).append("for").openParen(true).append(
1131: "int i = 0; i < b.length; i++").closeParen().endl();
1132: code.tab(3).append("for").openParen(true).append(
1133: "int j = 1; j >= 0; j--").closeParen().endl();
1134: code.tab(4).append("r.append").openParen(true).append("HEX[")
1135: .openParen(false).append("b[i] >> ").openParen(false)
1136: .append("j * 4").closeParen().closeParen().append(
1137: " & 0xF]").closeParen().append(";").endl();
1138: code.tab(2).append("return r.toString").parens().append(";")
1139: .endl();
1140:
1141: code.closeBrace(2);
1142: return code.toString();
1143: }
1144:
1145: /**
1146: * Code to test if two byte arrays are equal.
1147: */
1148: private String getEqualsByteArrayCode() {
1149: CodeFormat code = newCodeFormat();
1150: code.tab().append("private static boolean equals").openParen(
1151: true).append("byte[] b1, byte[] b2").closeParen()
1152: .openBrace(2).endl();
1153:
1154: code.tab(2).append("if").openParen(true).append(
1155: "b1 == null && b2 == null").closeParen().endl();
1156: code.tab(3).append("return true;").endl();
1157: code.tab(2).append("if").openParen(true).append(
1158: "b1 == null || b2 == null").closeParen().endl();
1159: code.tab(3).append("return false;").endl();
1160: code.tab(2).append("if").openParen(true).append(
1161: "b1.length != b2.length").closeParen().endl();
1162: code.tab(3).append("return false;").endl();
1163: code.tab(2).append("for").openParen(true).append(
1164: "int i = 0; i < b1.length; i++").closeParen().endl();
1165: code.tab(3).append("if").openParen(true).append(
1166: "b1[i] != b2[i]").closeParen().endl();
1167: code.tab(4).append("return false;").endl();
1168: code.tab(2).append("return true;").endl();
1169:
1170: code.closeBrace(2);
1171: return code.toString();
1172: }
1173:
1174: private String getHashCodeByteArrayCode() {
1175: CodeFormat code = newCodeFormat();
1176: code.tab().append("private static int hashCode")
1177: .openParen(true).append("byte[] b").closeParen()
1178: .openBrace(2).endl();
1179:
1180: code.tab(2).append("if").openParen(true).append("b == null")
1181: .closeParen().endl();
1182: code.tab(3).append("return 0;").endl();
1183: code.tab(2).append("int sum = 0;").endl();
1184: code.tab(2).append("for").openParen(true).append(
1185: "int i = 0; i < b.length; i++").closeParen().endl();
1186: code.tab(3).append("sum += b[i];").endl();
1187: code.tab(2).append("return sum;").endl();
1188:
1189: code.closeBrace(2);
1190: return code.toString();
1191: }
1192:
1193: /**
1194: * Code defining a tokenizer class.
1195: */
1196: private String getCustomTokenizerClass() {
1197: CodeFormat code = newCodeFormat();
1198: code.tab().append("protected static class ").append(
1199: TOKENIZER_CUSTOM).openBrace(2).endl();
1200:
1201: code.tab(2).append("private final String str;").endl();
1202: code.tab(2).append("private int last;").afterSection();
1203:
1204: code.tab(2).append("public Tokenizer (String str)")
1205: .openBrace(3).endl();
1206: code.tab(3).append("this.str = str;").endl();
1207: code.closeBrace(3).afterSection();
1208:
1209: code.tab(2).append("public String nextToken ()").openBrace(3)
1210: .endl();
1211: code.tab(3).append("int next = str.indexOf").openParen(true)
1212: .append("\"").append(_token).append("\", last")
1213: .closeParen().append(";").endl();
1214: code.tab(3).append("String part;").endl();
1215: code.tab(3).append("if").openParen(true).append("next == -1")
1216: .closeParen().openBrace(4).endl();
1217: code.tab(4).append("part = str.substring").openParen(true)
1218: .append("last").closeParen().append(";").endl();
1219: code.tab(4).append("last = str.length").parens().append(";")
1220: .endl().closeBrace(4);
1221: if (!code.getBraceOnSameLine())
1222: code.endl().tab(3);
1223: else
1224: code.append(" ");
1225: code.append("else").openBrace(4).endl();
1226: code.tab(4).append("part = str.substring").openParen(true)
1227: .append("last, next").closeParen().append(";").endl();
1228: code.tab(4).append("last = next + ").append(_token.length())
1229: .append(";").endl().closeBrace(4).endl();
1230:
1231: code.tab(3).append("return part;").endl();
1232: code.closeBrace(3);
1233: code.endl().closeBrace(2);
1234: return code.toString();
1235: }
1236:
1237: /**
1238: * Return the file that this tool should output to.
1239: */
1240: private File getFile() {
1241: if (_meta == null)
1242: return null;
1243:
1244: String packageName = Strings.getPackageName(_meta
1245: .getObjectIdType());
1246: String fileName = Strings.getClassName(_meta.getObjectIdType())
1247: + ".java";
1248:
1249: // if pc class in same package as oid class, try to find pc .java file
1250: File dir = null;
1251: if (_dir == null
1252: && Strings.getPackageName(_type).equals(packageName)) {
1253: dir = Files.getSourceFile(_type);
1254: if (dir != null)
1255: dir = dir.getParentFile();
1256: }
1257: if (dir == null)
1258: dir = Files.getPackageFile(_dir, packageName, true);
1259: return new File(dir, fileName);
1260: }
1261:
1262: /**
1263: * Return a copy of the correct code format.
1264: */
1265: private CodeFormat newCodeFormat() {
1266: return (_format == null) ? new CodeFormat()
1267: : (CodeFormat) _format.clone();
1268: }
1269:
1270: /**
1271: * Usage: java org.apache.openjpa.enhance.ApplicationIdTool [option]*
1272: * <class name | .java file | .class file | .jdo file>+
1273: * Where the following options are recognized.
1274: * <ul>
1275: * <li><i>-properties/-p <properties file></i>: The path to a OpenJPA
1276: * properties file containing information as outlined in
1277: * {@link Configuration}; optional.</li>
1278: * <li><i>-<property name> <property value></i>: All bean
1279: * properties of the standard OpenJPA {@link OpenJPAConfiguration} can be
1280: * set by using their names and supplying a value.</li>
1281: * <li><i>-directory/-d <output directory></i>: Path to the base
1282: * source directory. The package structure will be created beneath
1283: * this directory if necessary. If not specified, the tool will try
1284: * to locate the .java file in the CLASSPATH and output to the same
1285: * directory; failing that, it will use the current directory as
1286: * the base directory.
1287: * <li><i>-ignoreErrors/-i <true/t | false/f></i>: If false, an
1288: * exception will be thrown if the tool encounters any class that
1289: * does not use application identity or uses the identity class of
1290: * its superclass; defaults to true.</li>
1291: * <li><i>-token/-t <token></i>: The token to use to separate
1292: * stingified primary key field values in the stringified oid.</li>
1293: * <li><i>-name/-n <id class name></i>: The name of the identity
1294: * class to generate. If this option is specified, you must only
1295: * give a single class argument. If the class metadata names an object
1296: * id class, this argument is ignored.</li>
1297: * <li><i>-suffix/-s <id class suffix></i>: A string to suffix each
1298: * persistent class with to create the identity class name. This is
1299: * overridden by <code>-name</code> or by any identity class name
1300: * specified in metadata.</li>
1301: * <li><i>-codeFormat/-cf.<property name> < property value></i>
1302: * : Arguments like this will be used to configure the bean
1303: * properties of the internal {@link CodeFormat}.</li>
1304: * </ul>
1305: * Each additional argument can be either the full class name of the
1306: * type to create an id class for, the path to the .java file for the type,
1307: * the path to the .class file for the type, or the path to a .jdo file
1308: * listing one or more types. If a .java file already exists for an
1309: * application id, it will be backed up to a file named
1310: * <orig file name>~.
1311: */
1312: public static void main(String[] args) throws IOException,
1313: ClassNotFoundException {
1314: Options opts = new Options();
1315: final String[] arguments = opts.setFromCmdLine(args);
1316: boolean ret = Configurations.runAgainstAllAnchors(opts,
1317: new Configurations.Runnable() {
1318: public boolean run(Options opts)
1319: throws ClassNotFoundException, IOException {
1320: OpenJPAConfiguration conf = new OpenJPAConfigurationImpl();
1321: try {
1322: return ApplicationIdTool.run(conf,
1323: arguments, opts);
1324: } finally {
1325: conf.close();
1326: }
1327: }
1328: });
1329: if (!ret)
1330: System.err.println(_loc.get("appid-usage"));
1331: }
1332:
1333: /**
1334: * Run the application id tool with the given command-line and
1335: * given configuration. Returns false if invalid options were given.
1336: */
1337: public static boolean run(OpenJPAConfiguration conf, String[] args,
1338: Options opts) throws IOException, ClassNotFoundException {
1339: Flags flags = new Flags();
1340: flags.ignoreErrors = opts.removeBooleanProperty("ignoreErrors",
1341: "i", flags.ignoreErrors);
1342: flags.directory = Files.getFile(opts.removeProperty(
1343: "directory", "d", null), null);
1344: flags.token = opts.removeProperty("token", "t", flags.token);
1345: flags.name = opts.removeProperty("name", "n", flags.name);
1346: flags.suffix = opts.removeProperty("suffix", "s", flags.suffix);
1347:
1348: // separate the properties for the customizer and code format
1349: Options formatOpts = new Options();
1350: Map.Entry entry;
1351: String key;
1352: for (Iterator itr = opts.entrySet().iterator(); itr.hasNext();) {
1353: entry = (Map.Entry) itr.next();
1354: key = (String) entry.getKey();
1355: if (key.startsWith("codeFormat.")) {
1356: formatOpts.put(key.substring(11), entry.getValue());
1357: itr.remove();
1358: } else if (key.startsWith("cf.")) {
1359: formatOpts.put(key.substring(3), entry.getValue());
1360: itr.remove();
1361: }
1362: }
1363: if (!formatOpts.isEmpty()) {
1364: flags.format = new CodeFormat();
1365: formatOpts.setInto(flags.format);
1366: }
1367:
1368: Configurations.populateConfiguration(conf, opts);
1369: ClassLoader loader = conf.getClassResolverInstance()
1370: .getClassLoader(ApplicationIdTool.class, null);
1371: return run(conf, args, flags, loader);
1372: }
1373:
1374: /**
1375: * Run the tool. Returns false if invalid options were given.
1376: */
1377: public static boolean run(OpenJPAConfiguration conf, String[] args,
1378: Flags flags, ClassLoader loader) throws IOException,
1379: ClassNotFoundException {
1380: MetaDataRepository repos = conf.newMetaDataRepositoryInstance();
1381: repos.setValidate(repos.VALIDATE_NONE, true);
1382: loadObjectIds(repos, flags.name == null && flags.suffix == null);
1383:
1384: Log log = conf.getLog(OpenJPAConfiguration.LOG_TOOL);
1385: Collection classes;
1386: if (args.length == 0) {
1387: log.info(_loc.get("running-all-classes"));
1388: classes = repos.loadPersistentTypes(true, loader);
1389: } else {
1390: ClassArgParser cap = conf.getMetaDataRepositoryInstance()
1391: .getMetaDataFactory().newClassArgParser();
1392: cap.setClassLoader(loader);
1393: classes = new HashSet();
1394: for (int i = 0; i < args.length; i++)
1395: classes.addAll(Arrays.asList(cap.parseTypes(args[i])));
1396: }
1397: if (flags.name != null && classes.size() > 1)
1398: throw new UserException(_loc.get("name-mult-args", classes));
1399:
1400: ApplicationIdTool tool;
1401: Class cls;
1402: ClassMetaData meta;
1403: BCClassLoader bc = (BCClassLoader) AccessController
1404: .doPrivileged(J2DoPrivHelper
1405: .newBCClassLoaderAction(new Project()));
1406: for (Iterator itr = classes.iterator(); itr.hasNext();) {
1407: cls = (Class) itr.next();
1408: log.info(_loc.get("appid-running", cls));
1409:
1410: meta = repos.getMetaData(cls, null, false);
1411: setObjectIdType(meta, flags, bc);
1412:
1413: tool = new ApplicationIdTool(conf, cls, meta);
1414: tool.setDirectory(flags.directory);
1415: tool.setIgnoreErrors(flags.ignoreErrors);
1416: tool.setToken(flags.token);
1417: tool.setCodeFormat(flags.format);
1418: if (tool.run()) {
1419: log.info(_loc.get("appid-output", tool.getFile()));
1420: tool.record();
1421: } else
1422: log.info(_loc.get("appid-norun"));
1423: }
1424: bc.getProject().clear();
1425: return true;
1426: }
1427:
1428: /**
1429: * Set the object id type of the given metadata.
1430: */
1431: private static void setObjectIdType(ClassMetaData meta,
1432: Flags flags, BCClassLoader bc)
1433: throws ClassNotFoundException {
1434: if (meta == null
1435: || (meta.getObjectIdType() != null && (!meta
1436: .isOpenJPAIdentity() || flags.name == null))
1437: || getDeclaredPrimaryKeyFields(meta).length == 0)
1438: return;
1439:
1440: Class desc = meta.getDescribedType();
1441: Class cls = null;
1442: if (flags.name != null)
1443: cls = loadClass(desc, flags.name, bc);
1444: else if (flags.suffix != null)
1445: cls = loadClass(desc, desc.getName() + flags.suffix, bc);
1446: meta.setObjectIdType(cls, false);
1447: }
1448:
1449: /**
1450: * Load the given class name even if it does not exist.
1451: */
1452: private static Class loadClass(Class context, String name,
1453: BCClassLoader bc) throws ClassNotFoundException {
1454: if (name.indexOf('.') == -1
1455: && context.getName().indexOf('.') != -1)
1456: name = Strings.getPackageName(context) + "." + name;
1457:
1458: // first try with regular class loader
1459: ClassLoader loader = (ClassLoader) AccessController
1460: .doPrivileged(J2DoPrivHelper
1461: .getClassLoaderAction(context));
1462: if (loader == null)
1463: loader = (ClassLoader) AccessController
1464: .doPrivileged(J2DoPrivHelper
1465: .getContextClassLoaderAction());
1466: try {
1467: return Class.forName(name, false, loader);
1468: } catch (Throwable t) {
1469: }
1470:
1471: // create class
1472: BCClass oid = bc.getProject().loadClass(name, null);
1473: oid.addDefaultConstructor();
1474: return Class.forName(name, false, bc);
1475: }
1476:
1477: /**
1478: * Tell the metadata factory to load object id classes even if they don't
1479: * exist.
1480: */
1481: private static void loadObjectIds(MetaDataRepository repos,
1482: boolean fatal) {
1483: MetaDataFactory mdf = repos.getMetaDataFactory();
1484: if (mdf instanceof DelegatingMetaDataFactory)
1485: mdf = ((DelegatingMetaDataFactory) mdf)
1486: .getInnermostDelegate();
1487: if (mdf instanceof ObjectIdLoader)
1488: ((ObjectIdLoader) mdf).setLoadObjectIds();
1489: else if (fatal)
1490: throw new InvalidStateException(_loc
1491: .get("factory-not-oidloader")).setFatal(true);
1492: }
1493:
1494: /**
1495: * Run flags.
1496: */
1497: public static class Flags {
1498:
1499: public File directory = null;
1500: public boolean ignoreErrors = true;
1501: public String token = TOKEN_DEFAULT;
1502: public CodeFormat format = null;
1503: public String name = null;
1504: public String suffix = null;
1505: }
1506:
1507: /**
1508: * Interface implemented by metadata factories that can load non-existant
1509: * object id classes.
1510: */
1511: public static interface ObjectIdLoader {
1512: /**
1513: * Turn on the loading of all identity classes, even if they don't
1514: * exist.
1515: */
1516: public void setLoadObjectIds();
1517: }
1518: }
|