| /******************************************************************************* |
| * Copyright (c) 2009, 2010 Oracle. All rights reserved. |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v1.0, which accompanies this distribution |
| * and is available at http://www.eclipse.org/legal/epl-v10.html. |
| * |
| * Contributors: |
| * Oracle - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.jpt.jpa.core.internal.jpa2; |
| |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IPackageFragment; |
| import org.eclipse.jdt.core.IPackageFragmentRoot; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jpt.common.core.utility.BodySourceWriter; |
| import org.eclipse.jpt.common.utility.internal.ClassName; |
| import org.eclipse.jpt.common.utility.internal.SimpleStack; |
| import org.eclipse.jpt.common.utility.internal.StringTools; |
| import org.eclipse.jpt.jpa.core.JptJpaCorePlugin; |
| import org.eclipse.jpt.jpa.core.context.AttributeMapping; |
| import org.eclipse.jpt.jpa.core.context.PersistentAttribute; |
| import org.eclipse.jpt.jpa.core.context.PersistentType; |
| import org.eclipse.jpt.jpa.core.jpa2.JpaProject2_0; |
| import org.eclipse.jpt.jpa.core.jpa2.context.AttributeMapping2_0; |
| import org.eclipse.jpt.jpa.core.jpa2.context.MetamodelField; |
| import org.eclipse.jpt.jpa.core.jpa2.context.MetamodelSourceType; |
| import org.eclipse.jpt.jpa.core.jpa2.resource.java.JPA2_0; |
| import org.eclipse.jpt.jpa.core.jpa2.resource.java.JavaResourcePersistentType2_0; |
| |
| import com.ibm.icu.text.DateFormat; |
| import com.ibm.icu.text.SimpleDateFormat; |
| |
| /** |
| * For now, the "synchronization" is simple brute-force: we generate the source |
| * code and then compare it with what is already present in the file. |
| * If the new source is different, we replace the file contents; otherwise, we |
| * leave the file unchanged. |
| */ |
| @SuppressWarnings("nls") |
| public class GenericMetamodelSynchronizer |
| implements MetamodelSourceType.Synchronizer |
| { |
| protected final MetamodelSourceType sourceType; |
| |
| |
| public GenericMetamodelSynchronizer(MetamodelSourceType sourceType) { |
| super(); |
| this.sourceType = sourceType; |
| } |
| |
| public IFile getFile() { |
| return (IFile) this.getPackageFragment().getCompilationUnit(this.getFileName()).getResource(); |
| } |
| |
| |
| // ********** synchronize ********** |
| |
| public void synchronize(Map<String, Collection<MetamodelSourceType>> memberTypeTree) { |
| try { |
| this.synchronize_(memberTypeTree); |
| } catch (JavaModelException ex) { |
| JptJpaCorePlugin.log(ex); |
| } |
| } |
| |
| protected void synchronize_(Map<String, Collection<MetamodelSourceType>> memberTypeTree) throws JavaModelException { |
| IPackageFragment pkg = this.getPackageFragment(); |
| String fileName = this.getFileName(); |
| |
| ICompilationUnit compilationUnit = pkg.getCompilationUnit(fileName); |
| if (compilationUnit.exists()) { |
| // overwrite existing file if it has changed (ignoring the timestamp) |
| String newSource = this.buildSource(compilationUnit, memberTypeTree); |
| if (newSource != null) { |
| pkg.createCompilationUnit(fileName, newSource, true, null); // true=force |
| } |
| } else { |
| // write a new file, creating the package folders if necessary |
| if ( ! pkg.exists()) { |
| this.getSourceFolder().createPackageFragment(pkg.getElementName(), true, null); // true=force |
| } |
| pkg.createCompilationUnit(fileName, this.buildSource(memberTypeTree), false, null); // false=no force |
| } |
| } |
| |
| /** |
| * pre-condition: the compilation unit exists |
| * |
| * return null if the old source is not to be replaced |
| */ |
| protected String buildSource(ICompilationUnit compilationUnit, Map<String, Collection<MetamodelSourceType>> memberTypeTree) throws JavaModelException { |
| IFile file = (IFile) compilationUnit.getResource(); |
| JavaResourcePersistentType2_0 genType = this.getJpaProject().getGeneratedMetamodelTopLevelType(file); |
| if (genType == null) { |
| return null; // the file exists, but its source is not a generated metamodel top-level class |
| } |
| |
| String oldSource = compilationUnit.getSource(); |
| int oldLength = oldSource.length(); |
| |
| String newSource = this.buildSource(memberTypeTree); |
| int newLength = newSource.length(); |
| if (newLength != oldLength) { |
| return newSource; |
| } |
| |
| String date = genType.getGeneratedAnnotation().getDate(); // if we get here, this will be non-empty |
| int dateBegin = oldSource.indexOf(date); |
| if (dateBegin == -1) { |
| return null; // hmmm... |
| } |
| int dateEnd = dateBegin + date.length(); |
| if (dateEnd > oldLength) { |
| return null; // hmmm... |
| } |
| |
| if (newSource.regionMatches(0, oldSource, 0, dateBegin) && |
| newSource.regionMatches(dateEnd, oldSource, dateEnd, oldLength - dateEnd)) { |
| return null; |
| } |
| return newSource; |
| } |
| |
| |
| // ********** package/file ********** |
| |
| protected IPackageFragment getPackageFragment() { |
| return this.getSourceFolder().getPackageFragment(this.getPackageName()); |
| } |
| |
| protected IPackageFragmentRoot getSourceFolder() { |
| return this.getJpaProject().getMetamodelPackageFragmentRoot(); |
| } |
| |
| protected JpaProject2_0 getJpaProject() { |
| return (JpaProject2_0) this.sourceType.getJpaProject(); |
| } |
| |
| protected String getPackageName() { |
| return this.buildPackageName(this.sourceType.getName()); |
| } |
| |
| protected String buildPackageName(String topLevelSourceTypeName) { |
| return this.buildPackageName_(ClassName.getPackageName(topLevelSourceTypeName)); |
| } |
| |
| // TODO |
| protected String buildPackageName_(String sourcePackageName) { |
| // the default is to store the metamodel class in the same package as the source type |
| return sourcePackageName; |
| } |
| |
| protected String getFileName() { |
| return ClassName.getSimpleName(this.getClassName()) + ".java"; |
| } |
| |
| protected String getClassName() { |
| return this.buildClassName(this.sourceType.getName()); |
| } |
| |
| protected String buildClassName(Map<String, Collection<MetamodelSourceType>> memberTypeTree) { |
| return this.buildClassName(this.sourceType.getName(), memberTypeTree); |
| } |
| |
| protected String buildClassName(String sourceTypeName, Map<String, Collection<MetamodelSourceType>> memberTypeTree) { |
| String current = sourceTypeName; |
| SimpleStack<String> stack = new SimpleStack<String>(); |
| |
| while (true) { |
| stack.push(ClassName.getSimpleName(current)); |
| String declaringTypeName = this.getDeclaringTypeName(current, memberTypeTree); |
| if (declaringTypeName == null) { |
| break; |
| } |
| current = declaringTypeName; |
| } |
| |
| StringBuilder sb = new StringBuilder(sourceTypeName.length() + 10); |
| sb.append(this.buildPackageName(current)); |
| while ( ! stack.isEmpty()) { |
| sb.append('.'); |
| sb.append(this.buildSimpleClassName(stack.pop())); |
| } |
| return sb.toString(); |
| } |
| |
| protected String buildClassName(String topLevelSourceTypeName) { |
| return this.buildPackageName(topLevelSourceTypeName) + '.' + this.buildSimpleClassName(ClassName.getSimpleName(topLevelSourceTypeName)); |
| } |
| |
| protected String getSimpleClassName() { |
| return this.buildSimpleClassName(ClassName.getSimpleName(this.sourceType.getName())); |
| } |
| |
| // TODO |
| protected String buildSimpleClassName(String simpleSourceTypeName) { |
| // the default is to simply append an underscore to the source type name |
| return simpleSourceTypeName + '_'; |
| } |
| |
| |
| // ********** source code ********** |
| |
| /** |
| * build the "body" source first; then build the "package" and "imports" source |
| * and concatenate the "body" source to it |
| */ |
| protected String buildSource(Map<String, Collection<MetamodelSourceType>> memberTypeTree) { |
| // build the body source first so we can gather up the import statements |
| BodySourceWriter bodySourceWriter = this.buildBodySourceWriter(memberTypeTree); |
| |
| StringWriter sw = new StringWriter(bodySourceWriter.getLength() + 2000); |
| PrintWriter pw = new PrintWriter(sw); |
| this.printPackageAndImportsOn(pw, bodySourceWriter); |
| pw.print(bodySourceWriter.getSource()); |
| return sw.toString(); |
| } |
| |
| protected BodySourceWriter buildBodySourceWriter(Map<String, Collection<MetamodelSourceType>> memberTypeTree) { |
| BodySourceWriter pw = new BodySourceWriter(this.getPackageName(), this.getClassName()); |
| this.printBodySourceOn(pw, memberTypeTree); |
| return pw; |
| } |
| |
| public void printBodySourceOn(BodySourceWriter pw, Map<String, Collection<MetamodelSourceType>> memberTypeTree) { |
| this.printClassDeclarationOn(pw, memberTypeTree); |
| pw.print(" {"); |
| pw.println(); |
| |
| pw.indent(); |
| boolean attributesPrinted = this.printAttributesOn(pw); |
| this.printMemberTypesOn(pw, memberTypeTree, attributesPrinted); |
| pw.undent(); |
| |
| pw.print('}'); |
| pw.println(); // EOF |
| } |
| |
| |
| // ********** class declaration ********** |
| |
| protected void printClassDeclarationOn(BodySourceWriter pw, Map<String, Collection<MetamodelSourceType>> memberTypeTree) { |
| boolean topLevel = this.sourceTypeIsTopLevel(memberTypeTree); |
| if (topLevel) { |
| this.printGeneratedAnnotationOn(pw); |
| } |
| if (this.sourceType.isManaged()) { |
| this.printStaticMetamodelAnnotationOn(pw); |
| } |
| |
| pw.print("public "); |
| if ( ! topLevel) { |
| pw.print("static "); |
| } |
| pw.print("class "); |
| pw.print(this.getSimpleClassName()); // this is always the simple name |
| PersistentType superPersistentType = this.sourceType.getSuperPersistentType(); |
| if (superPersistentType != null) { |
| pw.print(" extends "); |
| pw.printTypeDeclaration(this.buildClassName(superPersistentType.getName(), memberTypeTree)); |
| } |
| } |
| |
| /** |
| * Return whether the source type is a top level type. |
| * This can be inferred from the specified member type tree. |
| */ |
| protected boolean sourceTypeIsTopLevel(Map<String, Collection<MetamodelSourceType>> memberTypeTree) { |
| return this.sourceTypeIsTopLevel(this.sourceType.getName(), memberTypeTree); |
| } |
| |
| /** |
| * Return whether the specified source type is a top level type. |
| * This can be inferred from the specified member type tree. |
| */ |
| protected boolean sourceTypeIsTopLevel(String sourceTypeName, Map<String, Collection<MetamodelSourceType>> memberTypeTree) { |
| return this.getDeclaringTypeName(sourceTypeName, memberTypeTree) == null; |
| } |
| |
| /** |
| * Return the name of the specified source type's declaring type, as |
| * implied by the specified member type tree. Return null if the source |
| * type is a top-level type. |
| */ |
| protected String getDeclaringTypeName(String sourceTypeName, Map<String, Collection<MetamodelSourceType>> memberTypeTree) { |
| int lastPeriod = sourceTypeName.lastIndexOf('.'); |
| if (lastPeriod == -1) { |
| return null; // default package top-level type |
| } |
| String declaringTypeName = sourceTypeName.substring(0, lastPeriod); |
| return (memberTypeTree.get(declaringTypeName) == null) ? null : declaringTypeName; |
| } |
| |
| protected void printGeneratedAnnotationOn(BodySourceWriter pw) { |
| pw.printAnnotation("javax.annotation.Generated"); |
| pw.print('('); |
| pw.print("value="); |
| pw.printStringLiteral(JavaResourcePersistentType2_0.METAMODEL_GENERATED_ANNOTATION_VALUE); |
| pw.print(", "); |
| pw.print("date="); |
| pw.printStringLiteral(format(new Date())); |
| pw.print(')'); |
| pw.println(); |
| } |
| |
| protected void printStaticMetamodelAnnotationOn(BodySourceWriter pw) { |
| pw.printAnnotation(JPA2_0.STATIC_METAMODEL); |
| pw.print('('); |
| pw.printTypeDeclaration(this.sourceType.getName()); |
| pw.print(".class"); |
| pw.print(')'); |
| pw.println(); |
| } |
| |
| /** |
| * {@link SimpleDateFormat} is not thread-safe. |
| */ |
| protected static synchronized String format(Date date) { |
| return DATE_FORMAT.format(date); |
| } |
| /** |
| * Recommended date format is ISO 8601. |
| * See javax.annotation.Generated |
| */ |
| private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); |
| |
| |
| // ********** attributes ********** |
| |
| /** |
| * Return whether any attributes were printed. |
| */ |
| protected boolean printAttributesOn(BodySourceWriter pw) { |
| boolean printed = false; |
| for (Iterator<PersistentAttribute> stream = this.sourceType.attributes(); stream.hasNext(); ) { |
| this.printAttributeOn(stream.next(), pw); |
| printed = true; |
| } |
| return printed; |
| } |
| |
| protected void printAttributeOn(PersistentAttribute persistentAttribute, BodySourceWriter pw) { |
| AttributeMapping attributeMapping = persistentAttribute.getMapping(); |
| if (attributeMapping != null) { // probably shouldn't be null? |
| this.printAttributeMappingOn(attributeMapping, pw); |
| } |
| } |
| |
| protected void printAttributeMappingOn(AttributeMapping attributeMapping, BodySourceWriter pw) { |
| MetamodelField field = ((AttributeMapping2_0) attributeMapping).getMetamodelField(); |
| if (field != null) { |
| this.printFieldOn(field, pw); |
| } |
| } |
| |
| protected void printFieldOn(MetamodelField field, BodySourceWriter pw) { |
| for (String modifier : field.getModifiers()) { |
| pw.print(modifier); |
| pw.print(' '); |
| } |
| pw.printTypeDeclaration(field.getTypeName()); |
| pw.print('<'); |
| for (Iterator<String> stream = field.getTypeArgumentNames().iterator(); stream.hasNext(); ) { |
| pw.printTypeDeclaration(stream.next()); |
| if (stream.hasNext()) { |
| pw.print(", "); |
| } |
| } |
| pw.print('>'); |
| pw.print(' '); |
| pw.print(field.getName()); |
| pw.print(';'); |
| pw.println(); |
| } |
| |
| |
| // ********** member types ********** |
| |
| protected void printMemberTypesOn(BodySourceWriter pw, Map<String, Collection<MetamodelSourceType>> memberTypeTree, boolean attributesPrinted) { |
| Collection<MetamodelSourceType> memberTypes = memberTypeTree.get(this.sourceType.getName()); |
| if (memberTypes != null) { |
| if (attributesPrinted) { |
| pw.println(); |
| } |
| for (Iterator<MetamodelSourceType> stream = memberTypes.iterator(); stream.hasNext(); ) { |
| stream.next().printBodySourceOn(pw, memberTypeTree); |
| if (stream.hasNext()) { |
| pw.println(); |
| } |
| } |
| } |
| } |
| |
| |
| // ********** package and imports ********** |
| |
| protected void printPackageAndImportsOn(PrintWriter pw, BodySourceWriter bodySourceWriter) { |
| if (this.getPackageName().length() != 0) { |
| pw.print("package "); |
| pw.print(this.getPackageName()); |
| pw.print(';'); |
| pw.println(); |
| pw.println(); |
| } |
| |
| for (String import_ : bodySourceWriter.getImports()) { |
| pw.print("import "); |
| pw.print(import_); |
| pw.print(';'); |
| pw.println(); |
| } |
| pw.println(); |
| } |
| |
| |
| // ********** misc ********** |
| |
| @Override |
| public String toString() { |
| return StringTools.buildToStringFor(this, this.sourceType.getName()); |
| } |
| |
| } |