private void autoInjectProperty(ModelClass modelClass) {

    // 连接数据库,自动获取所有信息,然后添加属性
    CtClass ctClass = modelClass.originClass;
    String entitySimpleName = ctClass.getSimpleName();
    List<String> skipFields = modelClass.notMappingColumns();

    try {
      DBType dbType = ServiceFramwork.injector.getInstance(DBType.class);
      DBInfo dbInfo = ServiceFramwork.injector.getInstance(DBInfo.class);

      Map<String, String> columns = dbInfo.tableColumns.get(entitySimpleName);
      if (columns == null) return;
      for (String columnName : columns.keySet()) {
        String fieldName = columnName;
        String fieldType = columns.get(columnName);
        if (skipFields.contains(fieldName)) continue;
        if (fieldName.equals("discriminator")) continue;

        // 对定义过的属性略过
        boolean pass = true;
        try {
          ctClass.getField(fieldName);
        } catch (Exception e) {
          pass = false;
        }
        if (pass) continue;

        ConstPool constPool = ctClass.getClassFile().getConstPool();
        AnnotationsAttribute attr =
            new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        CtField ctField =
            CtField.make(
                " private " + dbType.typeToJava(fieldType).v2() + " " + fieldName + " ;", ctClass);

        Tuple<Class, Map> tuple = dbType.dateType(fieldType, constPool);
        if (tuple != null) {
          createAnnotation(attr, tuple.v1(), tuple.v2());
        }

        if (fieldName.equals("id")) {
          createAnnotation(attr, javax.persistence.Id.class, map());
          createAnnotation(attr, javax.persistence.GeneratedValue.class, map());
        } else {
          createAnnotation(
              attr, Column.class, map("nullable", new BooleanMemberValue(true, constPool)));
        }

        if (attr.getAnnotations().length > 0) {
          ctField.getFieldInfo().addAttribute(attr);
        }

        ctClass.addField(ctField);
      }

    } catch (Exception e) {
      e.printStackTrace();
    }
    ctClass.defrost();
  }
 public static void defrostClassIfExists(AOPClassPool pool, String className) {
   // In some cases we get a class frozen exception. I guess if the invocation class
   // existed, method was unwrapped and the wrapped again
   CtClass existing = pool.getCached(className);
   if (existing != null) {
     existing.defrost();
   }
 }
  private void autoInjectGetSet(CtClass ctClass) throws Exception {

    // hibernate 可能需要 setter/getter 方法,好吧 我们为它添加这些方法

    for (CtField ctField : ctClass.getDeclaredFields()) {
      if (isFinal(ctField) || isStatic(ctField) || ctField.hasAnnotation(Validate.class)) continue;
      // Property name
      String propertyName =
          ctField.getName().substring(0, 1).toUpperCase() + ctField.getName().substring(1);
      String getter = "get" + propertyName;
      String setter = "set" + propertyName;

      try {
        CtMethod ctMethod = ctClass.getDeclaredMethod(getter);
        if (ctMethod.getParameterTypes().length > 0 || Modifier.isStatic(ctMethod.getModifiers())) {
          throw new NotFoundException("it's not a getter !");
        }
      } catch (NotFoundException noGetter) {

        String code =
            "public "
                + ctField.getType().getName()
                + " "
                + getter
                + "() { return this."
                + ctField.getName()
                + "; }";
        CtMethod getMethod = CtMethod.make(code, ctClass);
        getMethod.setModifiers(getMethod.getModifiers() | AccessFlag.SYNTHETIC);
        ctClass.addMethod(getMethod);
      }

      try {
        CtMethod ctMethod = ctClass.getDeclaredMethod(setter);
        if (ctMethod.getParameterTypes().length != 1
            || !ctMethod.getParameterTypes()[0].equals(ctField.getType())
            || Modifier.isStatic(ctMethod.getModifiers())) {
          throw new NotFoundException("it's not a setter !");
        }
      } catch (NotFoundException noSetter) {
        CtMethod setMethod =
            CtMethod.make(
                "public void "
                    + setter
                    + "("
                    + ctField.getType().getName()
                    + " value) { this."
                    + ctField.getName()
                    + " = value; }",
                ctClass);
        setMethod.setModifiers(setMethod.getModifiers() | AccessFlag.SYNTHETIC);
        ctClass.addMethod(setMethod);
      }
    }
    ctClass.defrost();
  }
  @Override
  public void enhanceThisClass(final ApplicationClass applicationClass) throws Exception {
    final CtClass ctClass = makeClass(applicationClass);
    if (ctClass.isInterface()) {
      return;
    }

    final Map<CtField, InjectionInfo> fieldsToInject = scanForInjections(ctClass);

    // in all methods, replace the field accesses with a call to spring
    for (final CtMethod ctMethod : ctClass.getDeclaredMethods()) {
      ctMethod.instrument(
          new ExprEditor() {
            @Override
            public void edit(final FieldAccess fieldAccess) {
              try {
                final InjectionInfo injectionInfo = fieldsToInject.get(fieldAccess.getField());

                if (injectionInfo != null && fieldAccess.isReader()) {

                  switch (injectionInfo.injectionMethod) {
                    case BY_NAME:
                      fieldAccess.replace(
                          "$_ = ($r)play.utils.Java.invokeStatic(play.modules.spring.Spring.class, \"getBean\", new Object[] {\""
                              + injectionInfo.beanName
                              + "\"});");
                      break;
                    case BY_TYPE:
                      fieldAccess.replace(
                          "$_ = ($r)play.utils.Java.invokeStatic(play.modules.spring.Spring.class, \"getBeanOfType\", new Object[] {$type});");
                      break;
                  }
                }
              } catch (final Exception e) {
                Logger.error(
                    e,
                    "Error in SpringEnhancer. %s.%s has not been properly enhanced (fieldAccess %s).",
                    applicationClass.name,
                    ctMethod.getName(),
                    fieldAccess);
                throw new UnexpectedException("Error enhancing injected field", e);
              }
            }
          });
    }

    applicationClass.enhancedByteCode = ctClass.toBytecode();
    ctClass.defrost();
  }
  /**
   * Retrieve the String value of an annotation which is not available at runtime.
   *
   * @param clazz The annotated class
   * @param annotation The annotation which is not visible at runtime
   * @param name The name of the String property of the annotation to retrieve
   * @return The String value of the annotation or null if the annotation or its property is not
   *     present
   */
  public static String getInvisibleAnnotationStringValue(
      Class<?> clazz, Class<? extends Annotation> annotation, String name) {
    CtClass ctClass = GwtClassPool.getCtClass(clazz);
    ctClass.defrost();

    AnnotationsAttribute attr =
        (AnnotationsAttribute)
            ctClass.getClassFile().getAttribute(AnnotationsAttribute.invisibleTag);
    if (attr == null) {
      return null;
    }
    javassist.bytecode.annotation.Annotation an = attr.getAnnotation(annotation.getName());

    ctClass.freeze();

    return an != null ? ((StringMemberValue) an.getMemberValue(name)).getValue() : null;
  }
  public void rewrite(final CtClass clazz) throws CannotCompileException {
    try {
      ClassPool cp = ClassPool.getDefault();

      byte[] b1 = clazz.toBytecode();
      clazz.defrost();
      ClassReader cr = new ClassReader(b1);
      ClassWriter cw = new ClassWriter(0); // new ClassWriter(cr, 0);
      // TraceClassVisitor ca = new TraceClassVisitor( cw, new PrintWriter(System.out, true) );
      // CheckClassAdapter cca = new CheckClassAdapter(ca);
      ConcurrencyControlRewriter.Adapter ca2 = new ConcurrencyControlRewriter.Adapter(cw, clazz);
      cr.accept(ca2, 0);
      byte[] b2 = cw.toByteArray();
      cp.makeClass(new ByteArrayInputStream(b2));
    } catch (IOException e) {
      throw new CannotCompileException(e);
    }
  }
  /*
     Hibernate 的关联关系太复杂了。要么你区分控制端和被控制端。要么你必须在使用的时候将两端都设置好关联关系。
     对于mappedBy也是一个无语的设计。为什么我要通过它来区分控制端?
  */
  public void inner_enhance(ModelClass modelClass) throws Exception {

    CtClass ctClass = modelClass.originClass;
    CtField[] fields = ctClass.getDeclaredFields();
    enhanceJPACallback(ctClass);

    OneToOneEnhancer oneToOneEnhancer = new OneToOneEnhancer(modelClass);
    oneToOneEnhancer.enhancer();

    OneToManyEnhancer oneToManyEnhancer = new OneToManyEnhancer(modelClass);
    oneToManyEnhancer.enhancer();

    ManyToOneEnhancer manyToOneEnhancer = new ManyToOneEnhancer(modelClass);
    manyToOneEnhancer.enhancer();

    ManyToManyEnhancer manyToManyEnhancer = new ManyToManyEnhancer(modelClass);
    manyToManyEnhancer.enhancer();

    ctClass.defrost();
  }
  @Override
  public void enhanceThisClass(ApplicationClass applicationClass) throws Exception {

    final CtClass ctClass = makeClass(applicationClass);
    if (ctClass.isInterface()) {
      return;
    }
    if (ctClass.getName().endsWith(".package")) {
      return;
    }

    // Add a default constructor if needed
    try {
      boolean hasDefaultConstructor = false;
      for (CtConstructor constructor : ctClass.getDeclaredConstructors()) {
        if (constructor.getParameterTypes().length == 0) {
          hasDefaultConstructor = true;
          break;
        }
      }
      if (!hasDefaultConstructor && !ctClass.isInterface()) {
        CtConstructor defaultConstructor =
            CtNewConstructor.make("public " + ctClass.getSimpleName() + "() {}", ctClass);
        ctClass.addConstructor(defaultConstructor);
      }
    } catch (Exception e) {
      Logger.error(e, "Error in PropertiesEnhancer");
      throw new UnexpectedException("Error in PropertiesEnhancer", e);
    }

    if (isScala(applicationClass)) {
      // Temporary hack for Scala. Done.
      applicationClass.enhancedByteCode = ctClass.toBytecode();
      ctClass.defrost();
      return;
    }

    for (CtField ctField : ctClass.getDeclaredFields()) {
      try {

        if (isProperty(ctField)) {

          // Property name
          String propertyName =
              ctField.getName().substring(0, 1).toUpperCase() + ctField.getName().substring(1);
          String getter = "get" + propertyName;
          String setter = "set" + propertyName;

          try {
            CtMethod ctMethod = ctClass.getDeclaredMethod(getter);
            if (ctMethod.getParameterTypes().length > 0
                || Modifier.isStatic(ctMethod.getModifiers())) {
              throw new NotFoundException("it's not a getter !");
            }
          } catch (NotFoundException noGetter) {

            // Créé le getter
            String code =
                "public "
                    + ctField.getType().getName()
                    + " "
                    + getter
                    + "() { return this."
                    + ctField.getName()
                    + "; }";
            CtMethod getMethod = CtMethod.make(code, ctClass);
            getMethod.setModifiers(getMethod.getModifiers() | AccessFlag.SYNTHETIC);
            ctClass.addMethod(getMethod);
          }

          if (!isFinal(ctField)) {
            try {
              CtMethod ctMethod = ctClass.getDeclaredMethod(setter);
              if (ctMethod.getParameterTypes().length != 1
                  || !ctMethod.getParameterTypes()[0].equals(ctField.getType())
                  || Modifier.isStatic(ctMethod.getModifiers())) {
                throw new NotFoundException("it's not a setter !");
              }
            } catch (NotFoundException noSetter) {
              // Créé le setter
              CtMethod setMethod =
                  CtMethod.make(
                      "public void "
                          + setter
                          + "("
                          + ctField.getType().getName()
                          + " value) { this."
                          + ctField.getName()
                          + " = value; }",
                      ctClass);
              setMethod.setModifiers(setMethod.getModifiers() | AccessFlag.SYNTHETIC);
              ctClass.addMethod(setMethod);
              createAnnotation(getAnnotations(setMethod), PlayPropertyAccessor.class);
            }
          }
        }

      } catch (Exception e) {
        Logger.error(e, "Error in PropertiesEnhancer");
        throw new UnexpectedException("Error in PropertiesEnhancer", e);
      }
    }

    // Add a default constructor if needed
    try {
      boolean hasDefaultConstructor = false;
      for (CtConstructor constructor : ctClass.getDeclaredConstructors()) {
        if (constructor.getParameterTypes().length == 0) {
          hasDefaultConstructor = true;
          break;
        }
      }
      if (!hasDefaultConstructor) {
        CtConstructor defaultConstructor = CtNewConstructor.defaultConstructor(ctClass);
        ctClass.addConstructor(defaultConstructor);
      }
    } catch (Exception e) {
      Logger.error(e, "Error in PropertiesEnhancer");
      throw new UnexpectedException("Error in PropertiesEnhancer", e);
    }

    // Intercept all fields access
    for (final CtBehavior ctMethod : ctClass.getDeclaredBehaviors()) {
      ctMethod.instrument(
          new ExprEditor() {

            @Override
            public void edit(FieldAccess fieldAccess) throws CannotCompileException {
              try {

                // Acces à une property ?
                if (isProperty(fieldAccess.getField())) {

                  // TODO : vérifier que c'est bien un champ d'une classe de l'application
                  // (fieldAccess.getClassName())

                  // Si c'est un getter ou un setter
                  String propertyName = null;
                  if (fieldAccess
                          .getField()
                          .getDeclaringClass()
                          .equals(ctMethod.getDeclaringClass())
                      || ctMethod
                          .getDeclaringClass()
                          .subclassOf(fieldAccess.getField().getDeclaringClass())) {
                    if ((ctMethod.getName().startsWith("get")
                            || (!isFinal(fieldAccess.getField())
                                && ctMethod.getName().startsWith("set")))
                        && ctMethod.getName().length() > 3) {
                      propertyName = ctMethod.getName().substring(3);
                      propertyName =
                          propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1);
                    }
                  }

                  // On n'intercepte pas le getter de sa propre property
                  if (propertyName == null || !propertyName.equals(fieldAccess.getFieldName())) {

                    String invocationPoint =
                        ctClass.getName()
                            + "."
                            + ctMethod.getName()
                            + ", line "
                            + fieldAccess.getLineNumber();

                    if (fieldAccess.isReader()) {

                      // Réécris l'accés en lecture à la property
                      fieldAccess.replace(
                          "$_ = ($r)play.classloading.enhancers.PropertiesEnhancer.FieldAccessor.invokeReadProperty($0, \""
                              + fieldAccess.getFieldName()
                              + "\", \""
                              + fieldAccess.getClassName()
                              + "\", \""
                              + invocationPoint
                              + "\");");

                    } else if (!isFinal(fieldAccess.getField()) && fieldAccess.isWriter()) {

                      // Réécris l'accés en ecriture à la property
                      fieldAccess.replace(
                          "play.classloading.enhancers.PropertiesEnhancer.FieldAccessor.invokeWriteProperty($0, \""
                              + fieldAccess.getFieldName()
                              + "\", "
                              + fieldAccess.getField().getType().getName()
                              + ".class, $1, \""
                              + fieldAccess.getClassName()
                              + "\", \""
                              + invocationPoint
                              + "\");");
                    }
                  }
                }

              } catch (Exception e) {
                throw new UnexpectedException("Error in PropertiesEnhancer", e);
              }
            }
          });
    }

    // Done.
    applicationClass.enhancedByteCode = ctClass.toBytecode();
    ctClass.defrost();
  }
  @Override
  public byte[] transform(
      ClassLoader loader,
      String className,
      Class<?> classBeingRedefined,
      ProtectionDomain protectionDomain,
      byte[] classfileBuffer)
      throws IllegalClassFormatException {
    // Be careful with Apache library usage in this class (e.g. ArrayUtils). Usage will likely cause
    // a ClassCircularityError
    // under JRebel. Favor not including outside libraries and unnecessary classes.
    CtClass clazz = null;
    try {
      boolean mySkipOverlaps = skipOverlaps;
      boolean myRenameMethodOverlaps = renameMethodOverlaps;
      String convertedClassName = className.replace('/', '.');
      ClassPool classPool = null;
      String xformKey = convertedClassName;
      String[] xformVals = null;
      Boolean[] xformSkipOverlaps = null;
      Boolean[] xformRenameMethodOverlaps = null;
      if (!xformTemplates.isEmpty()) {
        if (xformTemplates.containsKey(xformKey)) {
          xformVals = xformTemplates.get(xformKey).split(",");
          classPool = ClassPool.getDefault();
          clazz = classPool.makeClass(new ByteArrayInputStream(classfileBuffer), false);
        }
      } else {
        if (annotationTransformedClasses.contains(convertedClassName)) {
          logger.warn(
              convertedClassName
                  + " has already been transformed by a previous instance of DirectCopyTransfomer. "
                  + "Skipping this annotation based transformation. Generally, annotation-based transformation is handled "
                  + "by bean id blAnnotationDirectCopyClassTransformer with template tokens being added to "
                  + "blDirectCopyTransformTokenMap via EarlyStageMergeBeanPostProcessor.");
        }
        boolean isValidPattern = true;
        List<DirectCopyIgnorePattern> matchedPatterns = new ArrayList<DirectCopyIgnorePattern>();
        for (DirectCopyIgnorePattern pattern : ignorePatterns) {
          boolean isPatternMatch = false;
          for (String patternString : pattern.getPatterns()) {
            isPatternMatch = convertedClassName.matches(patternString);
            if (isPatternMatch) {
              break;
            }
          }
          if (isPatternMatch) {
            matchedPatterns.add(pattern);
          }
          isValidPattern = !(isPatternMatch && pattern.getTemplateTokenPatterns() == null);
          if (!isValidPattern) {
            return null;
          }
        }
        if (isValidPattern) {
          classPool = ClassPool.getDefault();
          clazz = classPool.makeClass(new ByteArrayInputStream(classfileBuffer), false);
          List<?> attributes = clazz.getClassFile().getAttributes();
          Iterator<?> itr = attributes.iterator();
          List<String> templates = new ArrayList<String>();
          List<Boolean> skips = new ArrayList<Boolean>();
          List<Boolean> renames = new ArrayList<Boolean>();
          check:
          {
            while (itr.hasNext()) {
              Object object = itr.next();
              if (AnnotationsAttribute.class.isAssignableFrom(object.getClass())) {
                AnnotationsAttribute attr = (AnnotationsAttribute) object;
                Annotation[] items = attr.getAnnotations();
                for (Annotation annotation : items) {
                  String typeName = annotation.getTypeName();
                  if (typeName.equals(DirectCopyTransform.class.getName())) {
                    ArrayMemberValue arrayMember =
                        (ArrayMemberValue) annotation.getMemberValue("value");
                    for (MemberValue arrayMemberValue : arrayMember.getValue()) {
                      AnnotationMemberValue member = (AnnotationMemberValue) arrayMemberValue;
                      Annotation memberAnnot = member.getValue();
                      ArrayMemberValue annot =
                          (ArrayMemberValue) memberAnnot.getMemberValue("templateTokens");
                      for (MemberValue memberValue : annot.getValue()) {
                        String val = ((StringMemberValue) memberValue).getValue();
                        if (val != null && templateTokens.containsKey(val)) {
                          templateCheck:
                          {
                            for (DirectCopyIgnorePattern matchedPattern : matchedPatterns) {
                              for (String ignoreToken : matchedPattern.getTemplateTokenPatterns()) {
                                if (val.matches(ignoreToken)) {
                                  break templateCheck;
                                }
                              }
                            }
                            templates.add(templateTokens.get(val));
                          }
                        }
                      }
                      BooleanMemberValue skipAnnot =
                          (BooleanMemberValue) memberAnnot.getMemberValue("skipOverlaps");
                      if (skipAnnot != null) {
                        skips.add(skipAnnot.getValue());
                      } else {
                        skips.add(mySkipOverlaps);
                      }
                      BooleanMemberValue renameAnnot =
                          (BooleanMemberValue) memberAnnot.getMemberValue("renameMethodOverlaps");
                      if (renameAnnot != null) {
                        renames.add(renameAnnot.getValue());
                      } else {
                        renames.add(myRenameMethodOverlaps);
                      }
                    }
                    xformVals = templates.toArray(new String[templates.size()]);
                    xformSkipOverlaps = skips.toArray(new Boolean[skips.size()]);
                    xformRenameMethodOverlaps = renames.toArray(new Boolean[renames.size()]);
                    break check;
                  }
                }
              }
            }
          }
        }
      }
      if (xformVals != null && xformVals.length > 0) {
        logger.lifecycle(
            LifeCycleEvent.START,
            String.format(
                "Transform - Copying into [%s] from [%s]",
                xformKey, StringUtils.join(xformVals, ",")));
        // Load the destination class and defrost it so it is eligible for modifications
        clazz.defrost();

        int index = 0;
        for (String xformVal : xformVals) {
          // Load the source class
          String trimmed = xformVal.trim();
          classPool.appendClassPath(new LoaderClassPath(Class.forName(trimmed).getClassLoader()));
          CtClass template = classPool.get(trimmed);

          // Add in extra interfaces
          CtClass[] interfacesToCopy = template.getInterfaces();
          for (CtClass i : interfacesToCopy) {
            checkInterfaces:
            {
              CtClass[] myInterfaces = clazz.getInterfaces();
              for (CtClass myInterface : myInterfaces) {
                if (myInterface.getName().equals(i.getName())) {
                  if (xformSkipOverlaps[index]) {
                    break checkInterfaces;
                  } else {
                    throw new RuntimeException(
                        "Duplicate interface detected " + myInterface.getName());
                  }
                }
              }
              logger.debug(String.format("Adding interface [%s]", i.getName()));
              clazz.addInterface(i);
            }
          }

          // copy over any EntityListeners
          ClassFile classFile = clazz.getClassFile();
          ClassFile templateFile = template.getClassFile();
          ConstPool constantPool = classFile.getConstPool();
          buildClassLevelAnnotations(classFile, templateFile, constantPool);

          // Copy over all declared fields from the template class
          // Note that we do not copy over fields with the @NonCopiedField annotation
          CtField[] fieldsToCopy = template.getDeclaredFields();
          for (CtField field : fieldsToCopy) {
            if (field.hasAnnotation(NonCopied.class)) {
              logger.debug(String.format("Not adding field [%s]", field.getName()));
            } else {
              try {
                CtField ctField = clazz.getDeclaredField(field.getName());
                String originalSignature = ctField.getSignature();
                String mySignature = field.getSignature();
                if (!originalSignature.equals(mySignature)) {
                  throw new IllegalArgumentException(
                      "Field with name ("
                          + field.getName()
                          + ") and signature "
                          + "("
                          + field.getSignature()
                          + ") is targeted for weaving into ("
                          + clazz.getName()
                          + "). "
                          + "An incompatible field of the same name and signature of ("
                          + ctField.getSignature()
                          + ") "
                          + "already exists. The field in the target class should be updated to a different name, "
                          + "or made to have a matching type.");
                }
                if (xformSkipOverlaps[index]) {
                  logger.debug(String.format("Skipping overlapped field [%s]", field.getName()));
                  continue;
                }
              } catch (NotFoundException e) {
                // do nothing -- field does not exist
              }
              logger.debug(String.format("Adding field [%s]", field.getName()));
              CtField copiedField = new CtField(field, clazz);

              boolean defaultConstructorFound = false;

              String implClass = getImplementationType(field.getType().getName());

              // Look through all of the constructors in the implClass to see
              // if there is one that takes zero parameters
              try {
                CtConstructor[] implConstructors = classPool.get(implClass).getConstructors();
                if (implConstructors != null) {
                  for (CtConstructor cons : implConstructors) {
                    if (cons.getParameterTypes().length == 0) {
                      defaultConstructorFound = true;
                      break;
                    }
                  }
                }
              } catch (NotFoundException e) {
                // Do nothing -- if we don't find this implementation, it's probably because it's
                // an array. In this case, we will not initialize the field.
              }

              if (defaultConstructorFound) {
                clazz.addField(copiedField, "new " + implClass + "()");
              } else {
                clazz.addField(copiedField);
              }
            }
          }

          // Copy over all declared methods from the template class
          CtMethod[] methodsToCopy = template.getDeclaredMethods();
          for (CtMethod method : methodsToCopy) {
            if (method.hasAnnotation(NonCopied.class)) {
              logger.debug(String.format("Not adding method [%s]", method.getName()));
            } else {
              try {
                CtClass[] paramTypes = method.getParameterTypes();
                CtMethod originalMethod = clazz.getDeclaredMethod(method.getName(), paramTypes);

                if (xformSkipOverlaps[index]) {
                  logger.debug(
                      String.format(
                          "Skipping overlapped method [%s]", methodDescription(originalMethod)));
                  continue;
                }

                if (transformedMethods.contains(methodDescription(originalMethod))) {
                  throw new RuntimeException(
                      "Method already replaced " + methodDescription(originalMethod));
                } else {
                  logger.debug(
                      String.format("Marking as replaced [%s]", methodDescription(originalMethod)));
                  transformedMethods.add(methodDescription(originalMethod));
                }

                logger.debug(String.format("Removing method [%s]", method.getName()));
                if (xformRenameMethodOverlaps[index]) {
                  originalMethod.setName(renameMethodPrefix + method.getName());
                } else {
                  clazz.removeMethod(originalMethod);
                }
              } catch (NotFoundException e) {
                // Do nothing -- we don't need to remove a method because it doesn't exist
              }

              logger.debug(String.format("Adding method [%s]", method.getName()));
              CtMethod copiedMethod = new CtMethod(method, clazz, null);
              clazz.addMethod(copiedMethod);
            }
          }
          index++;
        }

        if (xformTemplates.isEmpty()) {
          annotationTransformedClasses.add(convertedClassName);
        }
        logger.lifecycle(
            LifeCycleEvent.END,
            String.format(
                "Transform - Copying into [%s] from [%s]",
                xformKey, StringUtils.join(xformVals, ",")));
        return clazz.toBytecode();
      }
    } catch (ClassCircularityError error) {
      error.printStackTrace();
      throw error;
    } catch (Exception e) {
      throw new RuntimeException("Unable to transform class", e);
    } finally {
      if (clazz != null) {
        clazz.detach();
      }
    }

    return null;
  }
  @Override
  public byte[] transform(
      ClassLoader loader,
      String className,
      Class<?> classBeingRedefined,
      ProtectionDomain protectionDomain,
      byte[] classfileBuffer)
      throws IllegalClassFormatException {
    String convertedClassName = className.replace('/', '.');

    if (xformTemplates.containsKey(convertedClassName)) {
      String xformKey = convertedClassName;
      String[] xformVals = xformTemplates.get(xformKey).split(",");
      logger.lifecycle(
          LifeCycleEvent.START,
          String.format(
              "Transform - Copying into [%s] from [%s]",
              xformKey, StringUtils.join(xformVals, ",")));

      try {
        // Load the destination class and defrost it so it is eligible for modifications
        ClassPool classPool = ClassPool.getDefault();
        CtClass clazz = classPool.makeClass(new ByteArrayInputStream(classfileBuffer), false);
        clazz.defrost();

        for (String xformVal : xformVals) {
          // Load the source class
          String trimmed = xformVal.trim();
          classPool.appendClassPath(new LoaderClassPath(Class.forName(trimmed).getClassLoader()));
          CtClass template = classPool.get(trimmed);

          // Add in extra interfaces
          CtClass[] interfacesToCopy = template.getInterfaces();
          for (CtClass i : interfacesToCopy) {
            logger.debug(String.format("Adding interface [%s]", i.getName()));
            clazz.addInterface(i);
          }

          // Copy over all declared fields from the template class
          // Note that we do not copy over fields with the @NonCopiedField annotation
          CtField[] fieldsToCopy = template.getDeclaredFields();
          for (CtField field : fieldsToCopy) {
            if (field.hasAnnotation(NonCopied.class)) {
              logger.debug(String.format("Not adding field [%s]", field.getName()));
            } else {
              logger.debug(String.format("Adding field [%s]", field.getName()));
              CtField copiedField = new CtField(field, clazz);

              boolean defaultConstructorFound = false;

              String implClass = getImplementationType(field.getType().getName());

              // Look through all of the constructors in the implClass to see
              // if there is one that takes zero parameters
              try {
                CtConstructor[] implConstructors = classPool.get(implClass).getConstructors();
                if (implConstructors != null) {
                  for (CtConstructor cons : implConstructors) {
                    if (cons.getParameterTypes().length == 0) {
                      defaultConstructorFound = true;
                      break;
                    }
                  }
                }
              } catch (NotFoundException e) {
                // Do nothing -- if we don't find this implementation, it's probably because it's
                // an array. In this case, we will not initialize the field.
              }

              if (defaultConstructorFound) {
                clazz.addField(copiedField, "new " + implClass + "()");
              } else {
                clazz.addField(copiedField);
              }
            }
          }

          // Copy over all declared methods from the template class
          CtMethod[] methodsToCopy = template.getDeclaredMethods();
          for (CtMethod method : methodsToCopy) {
            if (method.hasAnnotation(NonCopied.class)) {
              logger.debug(String.format("Not adding method [%s]", method.getName()));
            } else {
              try {
                CtClass[] paramTypes = method.getParameterTypes();
                CtMethod originalMethod = clazz.getDeclaredMethod(method.getName(), paramTypes);

                if (transformedMethods.contains(methodDescription(originalMethod))) {
                  throw new RuntimeException(
                      "Method already replaced " + methodDescription(originalMethod));
                } else {
                  logger.debug(
                      String.format("Marking as replaced [%s]", methodDescription(originalMethod)));
                  transformedMethods.add(methodDescription(originalMethod));
                }

                logger.debug(String.format("Removing method [%s]", method.getName()));
                clazz.removeMethod(originalMethod);
              } catch (NotFoundException e) {
                // Do nothing -- we don't need to remove a method because it doesn't exist
              }

              logger.debug(String.format("Adding method [%s]", method.getName()));
              CtMethod copiedMethod = new CtMethod(method, clazz, null);
              clazz.addMethod(copiedMethod);
            }
          }
        }

        logger.lifecycle(
            LifeCycleEvent.END,
            String.format(
                "Transform - Copying into [%s] from [%s]",
                xformKey, StringUtils.join(xformVals, ",")));
        return clazz.toBytecode();
      } catch (Exception e) {
        throw new RuntimeException("Unable to transform class", e);
      }
    }

    return null;
  }
  @Override
  public void enhanceThisClass(ApplicationClass applicationClass) throws Exception {
    CtClass ctClass = makeClass(applicationClass);
    String entityName = ctClass.getName();
    Logger.debug("Enhance class " + entityName);

    // Only enhance Neo4jModel classes.
    if (!ctClass.subtypeOf(classPool.get("play.modules.neo4j.model.Neo4jModel"))) {
      return;
    }

    // Add a default constructor if needed
    try {
      for (CtConstructor constructor : ctClass.getDeclaredConstructors()) {
        if (constructor.getParameterTypes().length == 0) {
          ctClass.removeConstructor(constructor);
        }
        if (constructor.getParameterTypes().length == 1
            && constructor.getParameterTypes()[0].getClass().isInstance(Node.class)) {
          ctClass.removeConstructor(constructor);
        }
      }
      if (!ctClass.isInterface()) {
        Logger.debug("Adding default constructor");
        CtConstructor defaultConstructor =
            CtNewConstructor.make("public " + ctClass.getSimpleName() + "() { super();}", ctClass);
        ctClass.addConstructor(defaultConstructor);
      }
    } catch (Exception e) {
      Logger.error(e, "Error in PropertiesEnhancer");
      throw new UnexpectedException("Error in PropertiesEnhancer", e);
    }

    // for all field, we add getter / setter
    for (CtField ctField : ctClass.getDeclaredFields()) {
      try {
        // Property name
        String propertyName =
            ctField.getName().substring(0, 1).toUpperCase() + ctField.getName().substring(1);
        String getter = "get" + propertyName;
        String setter = "set" + propertyName;

        Logger.debug("Field " + ctField.getName() + " is a property ?");
        if (isProperty(ctField)) {
          Logger.debug("true");

          // ~~~~~~~~~
          // GETTER
          // ~~~~~~~
          try {
            CtMethod ctMethod = ctClass.getDeclaredMethod(getter);
            if (!ctMethod.getName().equalsIgnoreCase("getShouldBeSave")) {
              ctClass.removeMethod(ctMethod);
              throw new NotFoundException("it's not a true getter !");
            }
          } catch (NotFoundException noGetter) {
            // create getter
            Logger.debug("Adding getter " + getter + " for class " + entityName);
            // @formatter:off
            String code =
                "public "
                    + ctField.getType().getName()
                    + " "
                    + getter
                    + "() {"
                    + "if(this.shouldBeSave == Boolean.FALSE && this.node != null){"
                    + "return (("
                    + ctField.getType().getName()
                    + ") play.modules.neo4j.util.Binder.bindFromNeo4jFormat(this.node.getProperty(\""
                    + ctField.getName()
                    + "\", null),"
                    + ctField.getType().getName()
                    + ".class ));"
                    + "}else{"
                    + "return "
                    + ctField.getName()
                    + ";"
                    + "}"
                    + "}";
            // @formatter:on
            Logger.debug(code);
            CtMethod getMethod = CtMethod.make(code, ctClass);
            ctClass.addMethod(getMethod);
          }

          // ~~~~~~~~~
          // SETTER
          // ~~~~~~~
          try {
            CtMethod ctMethod = ctClass.getDeclaredMethod(setter);
            if (ctMethod.getParameterTypes().length != 1
                || !ctMethod.getParameterTypes()[0].equals(ctField.getType())
                || Modifier.isStatic(ctMethod.getModifiers())
                || hasPlayPropertiesAccessorAnnotation(ctMethod)) {
              if (hasPlayPropertiesAccessorAnnotation(ctMethod)) {
                ctClass.removeMethod(ctMethod);
              }
              throw new NotFoundException("it's not a true setter !");
            }
          } catch (NotFoundException noSetter) {
            // create setter
            Logger.debug("Adding setter " + setter + " for class " + entityName);
            // @formatter:off
            String code =
                "public void "
                    + setter
                    + "("
                    + ctField.getType().getName()
                    + " value) { "
                    + "this."
                    + ctField.getName()
                    + " = value;"
                    + "this.shouldBeSave = Boolean.TRUE;"
                    + "}";
            // formatter:on
            CtMethod setMethod = CtMethod.make(code, ctClass);
            Logger.debug(code);
            ctClass.addMethod(setMethod);
          }
        } else {
          // ~~~~~~~~~
          // GETTER for neo4j relation property
          // ~~~~~~~
          if (hasNeo4jRelationAnnotation(ctField)) {
            // test for related annotation
            Neo4jRelatedTo relatedTo = getRelatedAnnotation(ctField);
            if (relatedTo != null) {
              CtMethod ctMethod = ctClass.getDeclaredMethod(getter);
              ctClass.removeMethod(ctMethod);
              String code;
              if (relatedTo.lazy()) {
                // @formatter:off
                code =
                    "public "
                        + ctField.getType().getName()
                        + " "
                        + getter
                        + "() {"
                        + "if(this."
                        + ctField.getName()
                        + " == null){"
                        + "java.lang.reflect.Field field = this.getClass().getField(\""
                        + ctField.getName()
                        + "\");"
                        + "this."
                        + ctField.getName()
                        + "=play.modules.neo4j.relationship.Neo4jRelationFactory.getModelsFromRelation(\""
                        + relatedTo.value()
                        + "\", \""
                        + relatedTo.direction()
                        + "\", field, this.node);"
                        + "}"
                        + "return "
                        + ctField.getName()
                        + ";"
                        + "}";
                // @formatter:on
              } else {
                // @formatter:off
                code =
                    "public "
                        + ctField.getType().getName()
                        + " "
                        + getter
                        + "() {"
                        + "return "
                        + ctField.getName()
                        + ";"
                        + "}";
                // @formatter:on
              }
              Logger.debug(code);
              CtMethod method = CtMethod.make(code, ctClass);
              ctClass.addMethod(method);
            }
            // test for unique relation annotation
            Neo4jUniqueRelation uniqueRelation = getUniqueRelationAnnotation(ctField);
            if (uniqueRelation != null) {
              CtMethod ctMethod = ctClass.getDeclaredMethod(getter);
              ctClass.removeMethod(ctMethod);
              String code;
              // @formatter:off
              code =
                  "public "
                      + ctField.getType().getName()
                      + " "
                      + getter
                      + "() {"
                      + "return ("
                      + ctField.getType().getName()
                      + ")"
                      + ctField.getName()
                      + ";"
                      + "}";
              // @formatter:on
              Logger.debug(code);
              CtMethod method = CtMethod.make(code, ctClass);
              ctClass.addMethod(method);
            }
          }
        }
      } catch (Exception e) {
        Logger.error(e, "Error in PropertiesEnhancer");
        throw new UnexpectedException("Error in PropertiesEnhancer", e);
      }
    }

    // Adding getByKey() method
    Logger.debug("Adding getByKey() method for class " + entityName);
    // @formatter:off
    String codeGetByKey =
        "public static play.modules.neo4j.model.Neo4jModel getByKey(Long key) throws play.modules.neo4j.exception.Neo4jException {"
            + "return ("
            + entityName
            + ")_getByKey(key, \""
            + entityName
            + "\");"
            + "}";
    // @formatter:on
    Logger.debug(codeGetByKey);
    CtMethod getByKeyMethod = CtMethod.make(codeGetByKey, ctClass);
    ctClass.addMethod(getByKeyMethod);

    // ~~~~~~~~~~~~~~~
    // Adding findAll() method
    // @formatter:off
    String codeFindAll =
        "public static java.util.List findAll() {"
            + "return "
            + entityName
            + "._findAll(\""
            + entityName
            + "\");"
            + "}";
    // @formatter:on
    Logger.debug(codeFindAll);
    CtMethod findAllMethod = CtMethod.make(codeFindAll, ctClass);
    ctClass.addMethod(findAllMethod);

    // ~~~~~~~~~~~~~~~
    // Adding queryIndex() method
    // @formatter:off
    String queryIndex =
        "public static java.util.List queryIndex(String indexname, String query) {"
            + "return "
            + entityName
            + "._queryIndex(indexname, query);"
            + "}";
    // @formatter:on
    Logger.debug(queryIndex);
    CtMethod queryIndexMethod = CtMethod.make(queryIndex, ctClass);
    ctClass.addMethod(queryIndexMethod);

    // Done.
    applicationClass.enhancedByteCode = ctClass.toBytecode();
    ctClass.defrost();
  }
Beispiel #12
0
  /**
   * Given a bean-style interface, generate an instance of the interface by implementing getters and
   * setters for each property. It will also add implementations to support the {@link
   * SupportsRdfId} interface and generate simple, default equals, toString and hashCode methods.
   *
   * <p>If there are other non-bean style (getter and/or setter's for properties) methods on the
   * interface, this will likely fail to generate the instance.
   *
   * @param theInterface the interface to build an instance of
   * @param <T> the type of the interface
   * @return New dynamically generated bytecode of a class that implements the given interface.
   * @throws Exception if there is an error while generating the bytecode of the new class.
   */
  public static synchronized <T> Class<T> generateInstanceClass(Class<T> theInterface)
      throws Exception {
    processedMethods.clear();

    // TODO: can we use some sort of template language for this?

    ClassPool aPool = ClassPool.getDefault();

    aPool.appendClassPath(new LoaderClassPath(theInterface.getClassLoader()));

    CtClass aInterface = aPool.get(theInterface.getName());
    CtClass aSupportsRdfIdInterface = aPool.get(SupportsRdfId.class.getName());
    CtClass aEmpireGeneratedInterface = aPool.get(EmpireGenerated.class.getName());

    if (!Arrays.asList(aInterface.getInterfaces()).contains(aSupportsRdfIdInterface)
        && !SupportsRdfId.class.isAssignableFrom(theInterface)) {
      throw new IllegalArgumentException(
          "Class '"
              + theInterface.getName()
              + "' does not implement SupportsRdfId, cannot generate Empire suitable implementation.");
    }

    String aName = aInterface.getPackageName() + ".impl." + aInterface.getSimpleName() + "Impl";
    CtClass aClass = null;

    try {
      //  i had a good reason for doing this, but i dont remember what it is.  when i do, i'll
      // explain it here =)

      aClass = aPool.get(aName);
      return (Class<T>) BeanReflectUtil.loadClass(aName);
    } catch (NotFoundException e) {
      aClass =
          aPool.makeClass(
              aInterface.getPackageName() + ".impl." + aInterface.getSimpleName() + "Impl");
    } catch (ClassNotFoundException e) {
      throw new Exception("Previously created class cannot be loaded.", e);
    }

    if (aClass.isFrozen()) {
      aClass.defrost();
    }

    if (aInterface.isInterface()) {
      aClass.addInterface(aInterface);
    } else {
      aClass.setSuperclass(aInterface);
    }

    aClass.addInterface(aSupportsRdfIdInterface);
    aClass.addInterface(aEmpireGeneratedInterface);

    CtField aInterfaceField =
        new CtField(aPool.get(Class.class.getName()), "mInterfaceClass", aClass);
    aClass.addField(
        aInterfaceField, CtField.Initializer.byExpr(theInterface.getName() + ".class;"));

    CtField aAllTriplesField = new CtField(aPool.get(Graph.class.getName()), "mAllTriples", aClass);
    aClass.addField(
        aAllTriplesField, CtField.Initializer.byExpr("new com.clarkparsia.openrdf.ExtGraph();"));

    CtField aInstanceTriplesField =
        new CtField(aPool.get(Graph.class.getName()), "mInstanceTriples", aClass);
    aClass.addField(
        aInstanceTriplesField,
        CtField.Initializer.byExpr("new com.clarkparsia.openrdf.ExtGraph();"));

    aClass.addConstructor(CtNewConstructor.defaultConstructor(aClass));

    generateMethods(theInterface, aPool, aClass);
    generateMethodsForSuperInterfaces(theInterface, aPool, aClass);

    CtField aIdField = new CtField(aPool.get(SupportsRdfId.class.getName()), "supportsId", aClass);
    aClass.addField(
        aIdField,
        CtField.Initializer.byExpr("new com.clarkparsia.empire.annotation.SupportsRdfIdImpl();"));

    if (!hasMethod(aClass, "getRdfId")) {
      aClass.addMethod(
          CtNewMethod.make(
              "public com.clarkparsia.empire.SupportsRdfId.RdfKey getRdfId() { return supportsId.getRdfId(); } ",
              aClass));
    }

    if (!hasMethod(aClass, "setRdfId")) {
      aClass.addMethod(
          CtNewMethod.make(
              "public void setRdfId(com.clarkparsia.empire.SupportsRdfId.RdfKey theURI) { supportsId.setRdfId(theURI); } ",
              aClass));
    }

    if (!hasMethod(aClass, "getAllTriples")) {
      aClass.addMethod(
          CtNewMethod.make(
              "public org.openrdf.model.Graph getAllTriples() { return mAllTriples; } ", aClass));
    }

    if (!hasMethod(aClass, "setAllTriples")) {
      aClass.addMethod(
          CtNewMethod.make(
              "public void setAllTriples(org.openrdf.model.Graph theGraph) { mAllTriples = theGraph; } ",
              aClass));
    }

    if (!hasMethod(aClass, "getInstanceTriples")) {
      aClass.addMethod(
          CtNewMethod.make(
              "public org.openrdf.model.Graph getInstanceTriples() { return mInstanceTriples; } ",
              aClass));
    }

    if (!hasMethod(aClass, "setInstanceTriples")) {
      aClass.addMethod(
          CtNewMethod.make(
              "public void setInstanceTriples(org.openrdf.model.Graph theGraph) { mInstanceTriples = theGraph; } ",
              aClass));
    }

    String equalsMethodBody =
        "public boolean equals(Object theObj) {\n"
            + "  if (theObj == this) return true;\n"
            + "  if (!(theObj instanceof com.clarkparsia.empire.SupportsRdfId)) return false;\n"
            + "  if (!(mInterfaceClass.isAssignableFrom(theObj.getClass()))) return false;\n"
            + "  return getRdfId().equals( ((com.clarkparsia.empire.SupportsRdfId) theObj).getRdfId()) && super.equals(theObj);\n"
            + "}\n";

    aClass.addMethod(CtNewMethod.make(equalsMethodBody, aClass));

    if (theInterface.isInterface()) {
      aClass.addMethod(
          CtNewMethod.make(
              "public String toString() { return getRdfId() != null ? getRdfId().toString() : super.toString(); } ",
              aClass));
      aClass.addMethod(
          CtNewMethod.make(
              "public int hashCode() { return getRdfId() != null ? getRdfId().hashCode() : 0; } ",
              aClass));
    }

    aClass.freeze();

    Class<T> aResult = (Class<T>) aClass.toClass();

    try {
      // make sure this is a valid class, that is, we can create instances of it!
      aResult.newInstance();
    } catch (Exception ex) {
      // TODO: log this?
      throw ex;
    }

    return aResult;
  }
  /*
     Hibernate 的关联关系太复杂了。要么你区分控制端和被控制端。要么你必须在使用的时候将两端都设置好关联关系。
     对于mappedBy也是一个无语的设计。为什么我要通过它来区分控制端?
  */
  @Override
  public void enhance(CtClass ctClass) throws Exception {
    CtField[] fields = ctClass.getDeclaredFields();
    //        String entityListener = "javax.persistence.EntityListeners";

    //        if (ctClass.hasAnnotation(EntityCallback.class)) {
    //            EntityCallback entityListeners = (EntityCallback)
    // ctClass.getAnnotation(EntityCallback.class);
    //            String clzzName = entityListeners.value();
    //            CtClass clzz = ServiceFramwork.classPool.get(clzzName);
    //            enhanceJPACallback(clzz);
    //            AnnotationsAttribute annotationsAttribute =
    // EnhancerHelper.getAnnotations(ctClass);
    //            ArrayMemberValue arrayMemberValue = new
    // ArrayMemberValue(annotationsAttribute.getConstPool());
    //            ClassMemberValue[] clzzes = new ClassMemberValue[]{new ClassMemberValue(clzzName,
    // annotationsAttribute.getConstPool())};
    //            arrayMemberValue.setValue(clzzes);
    //            EnhancerHelper.createAnnotation(annotationsAttribute, EntityListeners.class,
    // map("value", arrayMemberValue));
    //
    //        } else {
    //
    //        }

    enhanceJPACallback(ctClass);

    for (CtField ctField : fields) {

      if (EnhancerHelper.hasAnnotation(ctField, "javax.persistence.OneToOne")) {
        DBInfo dbInfo = ServiceFramwork.injector.getInstance(DBInfo.class);
        Map<String, String> columns = dbInfo.tableColumns.get(ctClass.getSimpleName());
        String clzzName = findAssociatedClassName(ctField);
        CtField mappedByField = findAssociatedField(ctClass, clzzName);
        if (!columns.containsKey(ctField.getName() + "_id")) {
          setMappedBy(ctField, mappedByField.getName(), "OneToOne");

        } else {
          setMappedBy(mappedByField, mappedByField.getName(), "OneToOne");
        }
        setCascad(mappedByField, "OneToOne");
        setCascad(ctField, "OneToOne");

        String mappedByClassName = clzzName;
        String mappedByFieldName = mappedByField.getName();

        findAndRemoveMethod(ctClass, ctField, mappedByClassName);
        findAndRemoveMethod(ctClass, ctField.getName());

        CtMethod wow =
            CtMethod.make(
                format(
                    "public net.csdn.jpa.association.Association {}() {"
                        + "net.csdn.jpa.association.Association obj = new net.csdn.jpa.association.Association(this,\"{}\",\"{}\",\"{}\");return obj;"
                        + "    }",
                    ctField.getName(),
                    ctField.getName(),
                    mappedByFieldName,
                    "javax.persistence.OneToOne"),
                ctClass);
        ctClass.addMethod(wow);

        CtMethod wow2 =
            CtMethod.make(
                format(
                    "public {} {}({} obj) {"
                        + "        this.attr(\"{}\",obj);"
                        + "        obj.attr(\"{}\",this);"
                        + "        return this;"
                        + "    }",
                    ctClass.getName(),
                    ctField.getName(),
                    mappedByClassName,
                    ctField.getName(),
                    mappedByFieldName),
                ctClass);
        ctClass.addMethod(wow2);
      }

      if (EnhancerHelper.hasAnnotation(ctField, "javax.persistence.OneToMany")) {

        String clzzName = findAssociatedClassName(ctField);

        String mappedByFieldName = findAssociatedFieldName(ctClass, clzzName);
        String mappedByClassName = ctClass.getName();

        // 如果没有设置mappedBy我们帮他设置吧
        setMappedBy(ctField, mappedByFieldName, "OneToMany");
        setCascad(ctField, "OneToMany");

        findAndRemoveMethod(ctClass, ctField, mappedByClassName);
        findAndRemoveMethod(ctClass, ctField.getName());
        String propertyName =
            mappedByFieldName.substring(0, 1).toUpperCase() + mappedByFieldName.substring(1);
        String getter = "set" + propertyName;

        CtMethod wow =
            CtMethod.make(
                format(
                    "public net.csdn.jpa.association.Association {}() {"
                        + "net.csdn.jpa.association.Association obj = new net.csdn.jpa.association.Association(this,\"{}\",\"{}\",\"{}\");return obj;"
                        + "    }",
                    ctField.getName(),
                    ctField.getName(),
                    mappedByFieldName,
                    "javax.persistence.OneToMany"),
                ctClass);
        ctClass.addMethod(wow);

        CtMethod wow2 =
            CtMethod.make(
                format(
                    "public {} {}({} obj) {"
                        + "        this.{}.add(obj);"
                        + "        obj.{}(this);"
                        + "        return this;"
                        + "    }",
                    ctClass.getName(),
                    ctField.getName(),
                    clzzName,
                    ctField.getName(),
                    getter),
                ctClass);
        ctClass.addMethod(wow2);
      }

      if (EnhancerHelper.hasAnnotation(ctField, "javax.persistence.ManyToOne")) {

        String clzzName = ctField.getType().getName();

        String mappedByFieldName = findAssociatedFieldName(ctClass, clzzName);
        String mappedByClassName = ctClass.getName();

        // 默认设置为cascade = CascadeType.PERSIST
        setCascad(ctField, "ManyToOne");

        findAndRemoveMethod(ctClass, ctField, mappedByClassName);
        findAndRemoveMethod(ctClass, ctField.getName());
        String propertyName =
            mappedByFieldName.substring(0, 1).toUpperCase() + mappedByFieldName.substring(1);
        String getter = "get" + propertyName;

        CtMethod wow =
            CtMethod.make(
                format(
                    "public net.csdn.jpa.association.Association {}() {"
                        + "net.csdn.jpa.association.Association obj = new net.csdn.jpa.association.Association(this,\"{}\",\"{}\",\"{}\");return obj;"
                        + "    }",
                    ctField.getName(),
                    ctField.getName(),
                    mappedByFieldName,
                    "javax.persistence.ManyToOne"),
                ctClass);
        ctClass.addMethod(wow);

        CtMethod wow2 =
            CtMethod.make(
                format(
                    "public {} {}({} obj) {"
                        + "        this.{} = obj;"
                        + "        obj.{}().add(this);"
                        + "        return this;"
                        + "    }",
                    ctClass.getName(),
                    ctField.getName(),
                    clzzName,
                    ctField.getName(),
                    getter),
                ctClass);
        ctClass.addMethod(wow2);
      }
      if (EnhancerHelper.hasAnnotation(ctField, "javax.persistence.ManyToMany")) {

        String clzzName = findAssociatedClassName(ctField);

        String mappedByFieldName = findAssociatedFieldName(ctClass, clzzName);
        String mappedByClassName = ctClass.getName();

        CtField other = findAssociatedField(ctClass, clzzName);

        DBInfo dbInfo = ServiceFramwork.injector.getInstance(DBInfo.class);
        String otherClassSimpleName =
            findAssociatedClass(ctClass.getClassPool(), ctField).getSimpleName();

        String maybeTable1 = ctClass.getSimpleName() + "_" + otherClassSimpleName;
        String maybeTable2 = otherClassSimpleName + "_" + ctClass.getSimpleName();
        String finalTableName = dbInfo.tableNames.contains(maybeTable1) ? maybeTable1 : maybeTable2;
        setCascad(ctField, "ManyToMany");
        boolean isMaster = false;
        if (!ctField.hasAnnotation(ManyToManyHint.class)) {
          if (dbInfo.tableNames.contains(maybeTable1)) {
            setMappedBy(other, ctField.getName(), "ManyToMany");
            isMaster = true;
            finalTableName = maybeTable1;
          }

          if (dbInfo.tableNames.contains(maybeTable2)) {
            setMappedBy(ctField, mappedByFieldName, "ManyToMany");
            finalTableName = maybeTable2;
          }
          setManyToManyHint(other);
        }

        findAndRemoveMethod(ctClass, ctField, mappedByClassName);
        findAndRemoveMethod(ctClass, ctField.getName());
        String propertyName =
            mappedByFieldName.substring(0, 1).toUpperCase() + mappedByFieldName.substring(1);
        String getter = "get" + propertyName;

        CtMethod wow =
            CtMethod.make(
                format(
                    "public net.csdn.jpa.association.Association {}() {"
                        + "net.csdn.jpa.association.Association obj = new net.csdn.jpa.association.Association(this,\"{}\",\"{}\",\"{}\",\"{}\",\"{}\");return obj;"
                        + "    }",
                    ctField.getName(),
                    ctField.getName(),
                    mappedByFieldName,
                    "javax.persistence.ManyToMany",
                    finalTableName,
                    isMaster),
                ctClass);
        ctClass.addMethod(wow);

        CtMethod wow2 =
            CtMethod.make(
                format(
                    "public {} {}({} obj) {"
                        + "        {}.add(obj);"
                        + "        obj.{}().add(this);"
                        + "        return this;"
                        + "    }",
                    ctClass.getName(),
                    ctField.getName(),
                    clzzName,
                    ctField.getName(),
                    getter),
                ctClass);
        ctClass.addMethod(wow2);
      }
    }
    ctClass.defrost();
  }
  private void enhanceModelMethods(CtClass ctClass) throws Exception {
    String entityName = ctClass.getName();
    String simpleEntityName = ctClass.getSimpleName();

    // count
    CtMethod count =
        CtMethod.make(
            "public static long count() { return getJPAContext().jpql().count(\""
                + entityName
                + "\"); }",
            ctClass);
    ctClass.addMethod(count);

    // count2
    CtMethod count2 =
        CtMethod.make(
            "public static long count(String query, Object[] params) { return  getJPAContext().jpql().count(\""
                + entityName
                + "\", query, params); }",
            ctClass);
    ctClass.addMethod(count2);

    // findAll
    CtMethod findAll =
        CtMethod.make(
            "public static java.util.List findAll() { return  getJPAContext().jpql().findAll(\""
                + entityName
                + "\"); }",
            ctClass);
    ctClass.addMethod(findAll);

    // findById
    CtMethod findById =
        CtMethod.make(
            "public static net.csdn.jpa.model.JPABase findById(Object id) { return  getJPAContext().jpql().findById("
                + entityName
                + ".class, id); }",
            ctClass);
    ctClass.addMethod(findById);

    // find
    CtMethod find =
        CtMethod.make(
            "public static net.csdn.jpa.model.Model.JPAQuery find(String query, Object[] params) { return  getJPAContext().jpql().find(\""
                + entityName
                + "\", query, params); }",
            ctClass);
    ctClass.addMethod(find);

    // find
    CtMethod find2 =
        CtMethod.make(
            "public static net.csdn.jpa.model.Model.JPAQuery find() { return  getJPAContext().jpql().find(\""
                + entityName
                + "\"); }",
            ctClass);
    ctClass.addMethod(find2);

    // all
    CtMethod all =
        CtMethod.make(
            "public static net.csdn.jpa.model.Model.JPAQuery all() { return  getJPAContext().jpql().all(\""
                + entityName
                + "\"); }",
            ctClass);
    ctClass.addMethod(all);

    // delete
    CtMethod delete =
        CtMethod.make(
            "public static int delete(String query, Object[] params) { return  getJPAContext().jpql().delete(\""
                + entityName
                + "\", query, params); }",
            ctClass);
    ctClass.addMethod(delete);

    // deleteAll
    CtMethod deleteAll =
        CtMethod.make(
            "public static int deleteAll() { return  getJPAContext().jpql().deleteAll(\""
                + entityName
                + "\"); }",
            ctClass);
    ctClass.addMethod(deleteAll);

    // findOneBy
    CtMethod findOneBy =
        CtMethod.make(
            "public static net.csdn.jpa.model.JPABase findOneBy(String query, Object[] params) { return  getJPAContext().jpql().findOneBy(\""
                + entityName
                + "\", query, params); }",
            ctClass);
    ctClass.addMethod(findOneBy);

    // create
    CtMethod create =
        CtMethod.make(
            "public static net.csdn.jpa.model.JPABase create(java.util.Map params) { return  getJPAContext().jpql().create("
                + entityName
                + ".class, params); }",
            ctClass);
    ctClass.addMethod(create);

    // where
    CtMethod where =
        CtMethod.make(
            "public static net.csdn.jpa.model.JPQL where(String cc){return getJPAContext().jpql(\""
                + simpleEntityName
                + "\").where(cc);}",
            ctClass);
    ctClass.addMethod(where);

    // where2
    CtMethod where2 =
        CtMethod.make(
            "public static net.csdn.jpa.model.JPQL where(String cc,java.util.Map params){return getJPAContext().jpql(\""
                + simpleEntityName
                + "\").where(cc,params);}",
            ctClass);
    ctClass.addMethod(where2);

    // select
    CtMethod select =
        CtMethod.make(
            "public static net.csdn.jpa.model.JPQL select(String cc){return getJPAContext().jpql(\""
                + simpleEntityName
                + "\").select(cc);}",
            ctClass);
    ctClass.addMethod(select);
    // joins
    CtMethod joins =
        CtMethod.make(
            "public static net.csdn.jpa.model.JPQL joins(String cc){return getJPAContext().jpql(\""
                + simpleEntityName
                + "\").joins(cc);}",
            ctClass);
    ctClass.addMethod(joins);

    // order
    CtMethod order =
        CtMethod.make(
            "public static net.csdn.jpa.model.JPQL order(String cc){return getJPAContext().jpql(\""
                + simpleEntityName
                + "\").order(cc);}",
            ctClass);
    ctClass.addMethod(order);
    // limit
    CtMethod limit =
        CtMethod.make(
            "public static net.csdn.jpa.model.JPQL limit(int cc){return getJPAContext().jpql(\""
                + simpleEntityName
                + "\").limit(cc);}",
            ctClass);
    ctClass.addMethod(limit);
    // offset
    CtMethod offset =
        CtMethod.make(
            "public static net.csdn.jpa.model.JPQL offset(int cc){return getJPAContext().jpql(\""
                + simpleEntityName
                + "\").offset(cc);}",
            ctClass);
    ctClass.addMethod(offset);

    CtMethod findWithSingleId =
        CtMethod.make(
            "public static net.csdn.jpa.model.JPABase  find(Integer cc){return getJPAContext().jpql(\""
                + simpleEntityName
                + "\").find(cc);}",
            ctClass);
    ctClass.addMethod(findWithSingleId);

    CtMethod findWithMultiId =
        CtMethod.make(
            "public static java.util.List find(java.util.List cc){return getJPAContext().jpql(\""
                + simpleEntityName
                + "\").find(cc);}",
            ctClass);
    ctClass.addMethod(findWithMultiId);

    ctClass.defrost();
  }