├── project ├── build.properties └── plugins.sbt ├── plugin └── src │ ├── sbt-test │ └── play-enhancer │ │ ├── enhanced │ │ ├── build.sbt │ │ ├── test │ │ ├── project │ │ │ ├── build.properties │ │ │ └── plugins.sbt │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── play │ │ │ │ └── test │ │ │ │ └── enhancer │ │ │ │ ├── MyBeanAccessor.java │ │ │ │ ├── MyBean.java │ │ │ │ └── ComplexBean.java │ │ │ └── test │ │ │ └── scala │ │ │ └── test │ │ │ └── Test.scala │ │ └── incremental │ │ ├── build.sbt │ │ ├── project │ │ ├── build.properties │ │ └── plugins.sbt │ │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── play │ │ │ └── test │ │ │ └── enhancer │ │ │ ├── MyBean.java │ │ │ └── MyBeanAccessor.java │ │ └── test │ └── main │ ├── scala-sbt-0.13 │ └── com │ │ └── typesafe │ │ └── play │ │ └── sbt │ │ └── enhancer │ │ └── PlayEnhancer.scala │ └── scala-sbt-1.0 │ └── com │ └── typesafe │ └── play │ └── sbt │ └── enhancer │ └── PlayEnhancer.scala ├── version.sbt ├── .gitignore ├── README.md ├── .travis.yml └── enhancer └── src └── main └── java └── play └── core └── enhancers └── PropertiesEnhancer.java /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.16 2 | -------------------------------------------------------------------------------- /plugin/src/sbt-test/play-enhancer/enhanced/build.sbt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "1.2.3-SNAPSHOT" 2 | -------------------------------------------------------------------------------- /plugin/src/sbt-test/play-enhancer/enhanced/test: -------------------------------------------------------------------------------- 1 | > test:run 2 | -------------------------------------------------------------------------------- /plugin/src/sbt-test/play-enhancer/incremental/build.sbt: -------------------------------------------------------------------------------- 1 | crossPaths := false -------------------------------------------------------------------------------- /plugin/src/sbt-test/play-enhancer/enhanced/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.15 2 | -------------------------------------------------------------------------------- /plugin/src/sbt-test/play-enhancer/incremental/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.15 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.play" % "interplay" % sys.props.getOrElse("interplay.version", "1.3.12")) 2 | -------------------------------------------------------------------------------- /plugin/src/sbt-test/play-enhancer/enhanced/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-play-enhancer" % sys.props("project.version")) 2 | -------------------------------------------------------------------------------- /plugin/src/sbt-test/play-enhancer/incremental/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-play-enhancer" % sys.props("project.version")) 2 | -------------------------------------------------------------------------------- /plugin/src/sbt-test/play-enhancer/incremental/src/main/java/play/test/enhancer/MyBean.java: -------------------------------------------------------------------------------- 1 | package play.test.enhancer; 2 | 3 | public class MyBean { 4 | public String prop; 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | dist/* 6 | target/ 7 | lib_managed/ 8 | src_managed/ 9 | project/boot/ 10 | project/plugins/project/ 11 | 12 | # Scala-IDE specific 13 | .scala_dependencies 14 | .idea 15 | -------------------------------------------------------------------------------- /plugin/src/sbt-test/play-enhancer/enhanced/src/main/java/play/test/enhancer/MyBeanAccessor.java: -------------------------------------------------------------------------------- 1 | package play.test.enhancer; 2 | 3 | public class MyBeanAccessor { 4 | public static String access(MyBean myBean) { 5 | return myBean.prop; 6 | } 7 | } -------------------------------------------------------------------------------- /plugin/src/sbt-test/play-enhancer/incremental/src/main/java/play/test/enhancer/MyBeanAccessor.java: -------------------------------------------------------------------------------- 1 | package play.test.enhancer; 2 | 3 | public class MyBeanAccessor { 4 | public static String access(MyBean myBean) { 5 | return myBean.prop; 6 | } 7 | } -------------------------------------------------------------------------------- /plugin/src/sbt-test/play-enhancer/enhanced/src/main/java/play/test/enhancer/MyBean.java: -------------------------------------------------------------------------------- 1 | package play.test.enhancer; 2 | 3 | public class MyBean { 4 | public String prop; 5 | 6 | public Boolean isAdmin; 7 | 8 | public Boolean b; 9 | 10 | public Boolean canDeleteAccount; 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # End of Life 2 | 3 | The active Playframework contributors consider this repository has reached End of Life and archived it. 4 | 5 | This repository is not being used anymore and won't get any further updates. 6 | 7 | Thank you to all contributors that worked on this repository! 8 | 9 | # Play Enhancer Plugin 10 | 11 | This plugin provides byte code enhancement to Play 2.4+ projects, generating getters and setters for any Java sources, and rewriting accessors for any classes that depend on them. 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | jdk: openjdk8 2 | language: scala 3 | script: sbt +publishLocal +test +scripted 4 | sudo: false 5 | cache: 6 | directories: 7 | - $HOME/.ivy2/cache 8 | before_cache: 9 | # Ensure changes to the cache aren't persisted 10 | - rm -rf $HOME/.ivy2/cache/com.typesafe.play/play-enhancer* 11 | - rm -rf $HOME/.ivy2/cache/scala_*/sbt_*/com.typesafe.sbt/sbt-play-enhancer 12 | # Delete all ivydata files since ivy touches them on each build 13 | - find $HOME/.ivy2/cache -name "ivydata-*.properties" | xargs rm 14 | notifications: 15 | webhooks: 16 | urls: 17 | - https://webhooks.gitter.im/e/d2c8a242a2615f659595 18 | on_success: always 19 | on_failure: always 20 | -------------------------------------------------------------------------------- /plugin/src/sbt-test/play-enhancer/enhanced/src/main/java/play/test/enhancer/ComplexBean.java: -------------------------------------------------------------------------------- 1 | package play.test.enhancer; 2 | 3 | public class ComplexBean { 4 | public String existingGetter; 5 | 6 | public String getExistingGetter() { 7 | return existingGetter; 8 | } 9 | 10 | public String existingSetter; 11 | 12 | public void setExistingSetter(String value) { 13 | this.existingSetter = value; 14 | } 15 | 16 | public String differentTypeSetter; 17 | 18 | public void setDifferentTypeSetter(int value) { 19 | this.differentTypeSetter = Integer.toString(value); 20 | } 21 | 22 | public String multipleSetters; 23 | 24 | public void setMultipleSetters(int value) { 25 | this.multipleSetters = Integer.toString(value); 26 | } 27 | 28 | public void setMultipleSetters(String value) { 29 | this.multipleSetters = value; 30 | } 31 | } -------------------------------------------------------------------------------- /plugin/src/sbt-test/play-enhancer/enhanced/src/test/scala/test/Test.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import play.test.enhancer._ 4 | 5 | object Test extends App { 6 | 7 | println("First check that the accessors have been generated, the code below won't compile if they weren't.") 8 | val myBean = new MyBean 9 | myBean.setProp("foo") 10 | assert(myBean.getProp == "foo") 11 | 12 | myBean.setIsAdmin(true) 13 | assert(myBean.isAdmin) 14 | 15 | myBean.setB(true) 16 | assert(myBean.isB) 17 | 18 | myBean.setB(false) 19 | assert(!myBean.getB) 20 | 21 | myBean.setCanDeleteAccount(true) 22 | assert(myBean.canDeleteAccount) 23 | 24 | myBean.setCanDeleteAccount(true) 25 | assert(myBean.getCanDeleteAccount) 26 | 27 | println("Now check that accessors have been rewritten. If they haven't been, then the accessor will go directly to the field, rather than getting our overridden value") 28 | class OverridingBean extends MyBean { 29 | override def getProp = "bar" 30 | } 31 | val overridingBean = new OverridingBean 32 | overridingBean.prop = "foo" 33 | assert(MyBeanAccessor.access(overridingBean) == "bar") 34 | 35 | println("All tests passed!") 36 | } 37 | -------------------------------------------------------------------------------- /plugin/src/sbt-test/play-enhancer/incremental/test: -------------------------------------------------------------------------------- 1 | # First, disable enhancement and compile. 2 | # This will ensure that the timestamps of the original class files are well before the timestamps of the enhanced 3 | # class files 4 | > set playEnhancerEnabled := false 5 | > compile 6 | 7 | $ touch target/stamp 8 | # Sleep for a second to make sure timestamps tick over 9 | $ sleep 1000 10 | 11 | # Now re-enable byte code enhancement and compile 12 | > set playEnhancerEnabled := true 13 | > compile 14 | 15 | # Make sure that we did actually update the class files 16 | $ newer target/classes/play/test/enhancer/MyBean.class target/stamp 17 | $ newer target/classes/play/test/enhancer/MyBeanAccessor.class target/stamp 18 | 19 | $ sleep 1000 20 | $ touch target/stamp 21 | 22 | # Recompile 23 | > compile 24 | 25 | # Make sure the class files didn't change 26 | $ newer target/stamp target/classes/play/test/enhancer/MyBean.class 27 | $ newer target/stamp target/classes/play/test/enhancer/MyBeanAccessor.class 28 | 29 | # Reboot, and make sure the change was persisted 30 | > reboot 31 | $ newer target/stamp target/classes/play/test/enhancer/MyBean.class 32 | $ newer target/stamp target/classes/play/test/enhancer/MyBeanAccessor.class 33 | -------------------------------------------------------------------------------- /plugin/src/main/scala-sbt-0.13/com/typesafe/play/sbt/enhancer/PlayEnhancer.scala: -------------------------------------------------------------------------------- 1 | package com.typesafe.play.sbt.enhancer 2 | 3 | import sbt._ 4 | import java.io.File 5 | import sbt.Keys._ 6 | import sbt.inc._ 7 | import play.core.enhancers.PropertiesEnhancer 8 | import sbt.plugins.JvmPlugin 9 | 10 | object Imports { 11 | val playEnhancerVersion = settingKey[String]("The version used for the play-enhancer dependency") 12 | val playEnhancerEnabled = settingKey[Boolean]("Whether the Play enhancer is enabled or not") 13 | val playEnhancerGenerateAccessors = taskKey[Compiler.CompileResult => Compiler.CompileResult]("Create the function that will generate accessors") 14 | val playEnhancerRewriteAccessors = taskKey[Compiler.CompileResult => Compiler.CompileResult]("Create the function that will rewrite accessors") 15 | } 16 | 17 | object PlayEnhancer extends AutoPlugin { 18 | 19 | override def requires = JvmPlugin 20 | override def trigger = allRequirements 21 | 22 | val autoImport = Imports 23 | 24 | import Imports._ 25 | 26 | override def projectSettings = Seq( 27 | playEnhancerVersion := readResourceProperty("play.enhancer.version.properties", "play.enhancer.version"), 28 | playEnhancerEnabled := true, 29 | libraryDependencies += "com.typesafe.play" % "play-enhancer" % playEnhancerVersion.value 30 | ) ++ inConfig(Compile)(scopedSettings) ++ inConfig(Test)(scopedSettings) 31 | 32 | private def scopedSettings: Seq[Setting[_]] = Seq( 33 | sources in playEnhancerGenerateAccessors := unmanagedSources.value.filter(_.getName.endsWith(".java")), 34 | manipulateBytecode := { 35 | // No need to use a dynTask here, since executing these tasks just return functions that do nothing unless 36 | // they are actually invoked 37 | if (playEnhancerEnabled.value) { 38 | playEnhancerRewriteAccessors.value(playEnhancerGenerateAccessors.value(manipulateBytecode.value)) 39 | } else { 40 | manipulateBytecode.value 41 | } 42 | }, 43 | playEnhancerGenerateAccessors := bytecodeEnhance(playEnhancerGenerateAccessors, (PropertiesEnhancer.generateAccessors _).curried).value, 44 | playEnhancerRewriteAccessors := bytecodeEnhance(playEnhancerRewriteAccessors, (PropertiesEnhancer.rewriteAccess _).curried).value 45 | ) 46 | 47 | private def bytecodeEnhance(task: TaskKey[_], generateTask: String => File => Boolean): Def.Initialize[Task[Compiler.CompileResult => Compiler.CompileResult]] = Def.task { 48 | { result => 49 | val analysis = result.analysis 50 | 51 | val deps: Classpath = dependencyClasspath.value 52 | val classes: File = classDirectory.value 53 | 54 | val classpath = (deps.map(_.data.getAbsolutePath).toArray :+ classes.getAbsolutePath).mkString(java.io.File.pathSeparator) 55 | 56 | val extra = if (crossPaths.value) s"_${scalaBinaryVersion.value}" else "" 57 | val timestampFile = streams.value.cacheDirectory / s"play_instrumentation$extra" 58 | val lastEnhanced = if (timestampFile.exists) IO.read(timestampFile).toLong else Long.MinValue 59 | 60 | def getClassesForSources(sources: Seq[File]) = { 61 | sources.flatMap { source => 62 | if (analysis.apis.internal(source).compilation.startTime > lastEnhanced) { 63 | analysis.relations.products(source) 64 | } else { 65 | Nil 66 | } 67 | } 68 | } 69 | 70 | val classesToEnhance = getClassesForSources((sources in task).value) 71 | val enhancedClasses = classesToEnhance.filter(generateTask(classpath)) 72 | 73 | IO.write(timestampFile, System.currentTimeMillis.toString) 74 | 75 | if (enhancedClasses.nonEmpty) { 76 | /** 77 | * Updates stamp of product (class file) by preserving the type of a passed stamp. 78 | * This way any stamp incremental compiler chooses to use to mark class files will 79 | * be supported. 80 | */ 81 | def updateStampForClassFile(classFile: File, stamp: Stamp): Stamp = stamp match { 82 | case _: Exists => Stamp.exists(classFile) 83 | case _: LastModified => Stamp.lastModified(classFile) 84 | case _: Hash => Stamp.hash(classFile) 85 | } 86 | // Since we may have modified some of the products of the incremental compiler, that is, the compiled template 87 | // classes and compiled Java sources, we need to update their timestamps in the incremental compiler, otherwise 88 | // the incremental compiler will see that they've changed since it last compiled them, and recompile them. 89 | val updatedAnalysis = analysis.copy(stamps = enhancedClasses.foldLeft(analysis.stamps) { 90 | (stamps, classFile) => 91 | val existingStamp = stamps.product(classFile) 92 | if (existingStamp == Stamp.notPresent) { 93 | throw new java.io.IOException("Tried to update a stamp for class file that is not recorded as " 94 | + s"product of incremental compiler: $classFile") 95 | } 96 | stamps.markProduct(classFile, updateStampForClassFile(classFile, existingStamp)) 97 | }) 98 | 99 | result.copy(analysis = updatedAnalysis, hasModified = true) 100 | } else { 101 | result 102 | } 103 | 104 | } 105 | } 106 | 107 | private def readResourceProperty(resource: String, property: String): String = { 108 | val props = new java.util.Properties 109 | val stream = getClass.getClassLoader.getResourceAsStream(resource) 110 | try { props.load(stream) } 111 | catch { case e: Exception => } 112 | finally { if (stream ne null) stream.close } 113 | props.getProperty(property) 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /plugin/src/main/scala-sbt-1.0/com/typesafe/play/sbt/enhancer/PlayEnhancer.scala: -------------------------------------------------------------------------------- 1 | package com.typesafe.play.sbt.enhancer 2 | 3 | import sbt._ 4 | import sbt.Keys._ 5 | import sbt.plugins.JvmPlugin 6 | 7 | import xsbti.compile.CompileResult 8 | import xsbti.compile.analysis.Stamp 9 | 10 | import sbt.internal.inc.Hash 11 | import sbt.internal.inc.LastModified 12 | 13 | import java.io.File 14 | 15 | import play.core.enhancers.PropertiesEnhancer 16 | 17 | object Imports { 18 | val playEnhancerVersion = settingKey[String]("The version used for the play-enhancer dependency") 19 | val playEnhancerEnabled = settingKey[Boolean]("Whether the Play enhancer is enabled or not") 20 | val playEnhancerGenerateAccessors = taskKey[CompileResult => CompileResult]("Create the function that will generate accessors") 21 | val playEnhancerRewriteAccessors = taskKey[CompileResult => CompileResult]("Create the function that will rewrite accessors") 22 | } 23 | 24 | object PlayEnhancer extends AutoPlugin { 25 | 26 | override def requires = JvmPlugin 27 | override def trigger = allRequirements 28 | 29 | val autoImport = Imports 30 | 31 | // This is replacement of old Stamp `Exists` representation 32 | private final val notPresent = "absent" 33 | 34 | import Imports._ 35 | 36 | override def projectSettings = Seq( 37 | playEnhancerVersion := readResourceProperty("play.enhancer.version.properties", "play.enhancer.version"), 38 | playEnhancerEnabled := true, 39 | libraryDependencies += "com.typesafe.play" % "play-enhancer" % playEnhancerVersion.value 40 | ) ++ inConfig(Compile)(scopedSettings) ++ inConfig(Test)(scopedSettings) 41 | 42 | private def scopedSettings: Seq[Setting[_]] = Seq( 43 | sources in playEnhancerGenerateAccessors := unmanagedSources.value.filter(_.getName.endsWith(".java")), 44 | manipulateBytecode := { 45 | // No need to use a dynTask here, since executing these tasks just return functions that do nothing unless 46 | // they are actually invoked 47 | val playEnhancerRewriteAccessorsValue = playEnhancerRewriteAccessors.value 48 | val playEnhancerGenerateAccessorsValue = playEnhancerGenerateAccessors.value 49 | val manipulateBytecodeValue = manipulateBytecode.value 50 | if (playEnhancerEnabled.value) { 51 | playEnhancerRewriteAccessorsValue(playEnhancerGenerateAccessorsValue(manipulateBytecodeValue)) 52 | } else { 53 | manipulateBytecodeValue 54 | } 55 | }, 56 | playEnhancerGenerateAccessors := bytecodeEnhance(playEnhancerGenerateAccessors, (PropertiesEnhancer.generateAccessors _).curried).value, 57 | playEnhancerRewriteAccessors := bytecodeEnhance(playEnhancerRewriteAccessors, (PropertiesEnhancer.rewriteAccess _).curried).value 58 | ) 59 | 60 | private def bytecodeEnhance(task: TaskKey[_], generateTask: String => File => Boolean): Def.Initialize[Task[CompileResult => CompileResult]] = Def.task { 61 | 62 | val deps: Classpath = dependencyClasspath.value 63 | val classes: File = classDirectory.value 64 | val sourcesInTask = (sources in task).value 65 | 66 | val classpath = (deps.map(_.data.getAbsolutePath).toArray :+ classes.getAbsolutePath).mkString(java.io.File.pathSeparator) 67 | val extra = if (crossPaths.value) s"_${scalaBinaryVersion.value}" else "" 68 | val timestampFile = streams.value.cacheDirectory / s"play_instrumentation$extra" 69 | val lastEnhanced = if (timestampFile.exists) IO.read(timestampFile).toLong else Long.MinValue 70 | 71 | { result => 72 | 73 | val analysis = result.analysis.asInstanceOf[sbt.internal.inc.Analysis] 74 | 75 | def getClassesForSources(sources: Seq[File]): Seq[File] = { 76 | sources.flatMap { source => 77 | // it won't be usual to have more than one enhanceable class 78 | // defined per file, but it is still possible. So we need to 79 | // get all the classes defined for a source. 80 | val classes = analysis.relations.classNames(source) 81 | classes.flatMap { c => 82 | if (analysis.apis.internal(c).compilationTimestamp() > lastEnhanced) { 83 | analysis.relations.products(source) 84 | } else { 85 | Nil 86 | } 87 | } 88 | }.distinct 89 | } 90 | 91 | val classesToEnhance = getClassesForSources(sourcesInTask) 92 | val enhancedClasses = classesToEnhance.filter(generateTask(classpath)) 93 | 94 | IO.write(timestampFile, System.currentTimeMillis.toString) 95 | 96 | if (enhancedClasses.nonEmpty) { 97 | /** 98 | * Updates stamp of product (class file) by preserving the type of a passed stamp. 99 | * This way any stamp incremental compiler chooses to use to mark class files will 100 | * be supported. 101 | */ 102 | def updateStampForClassFile(classFile: File, stamp: Stamp): Stamp = stamp match { 103 | //case _: Exists => sbt.internal.inc.Stamp.exists(classFile) 104 | case _: LastModified => sbt.internal.inc.Stamper.forLastModified(classFile) 105 | case _: Hash => sbt.internal.inc.Stamper.forHash(classFile) 106 | } 107 | // Since we may have modified some of the products of the incremental compiler, that is, the compiled template 108 | // classes and compiled Java sources, we need to update their timestamps in the incremental compiler, otherwise 109 | // the incremental compiler will see that they've changed since it last compiled them, and recompile them. 110 | val updatedAnalysis = analysis.copy(stamps = enhancedClasses.foldLeft(analysis.stamps) { 111 | (stamps, classFile) => 112 | val existingStamp = stamps.product(classFile) 113 | if (existingStamp.writeStamp == notPresent) { 114 | throw new java.io.IOException("Tried to update a stamp for class file that is not recorded as " 115 | + s"product of incremental compiler: $classFile") 116 | } 117 | stamps.markProduct(classFile, updateStampForClassFile(classFile, existingStamp)) 118 | }) 119 | 120 | result.withAnalysis(updatedAnalysis).withHasModified(true) 121 | } else { 122 | result 123 | } 124 | 125 | } 126 | } 127 | 128 | private def readResourceProperty(resource: String, property: String): String = { 129 | val props = new java.util.Properties 130 | val stream = getClass.getClassLoader.getResourceAsStream(resource) 131 | try { props.load(stream) } 132 | catch { case e: Exception => } 133 | finally { if (stream ne null) stream.close } 134 | props.getProperty(property) 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /enhancer/src/main/java/play/core/enhancers/PropertiesEnhancer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2013 Typesafe Inc. 3 | */ 4 | package play.core.enhancers; 5 | 6 | import javassist.*; 7 | import javassist.bytecode.AnnotationsAttribute; 8 | import javassist.bytecode.SignatureAttribute; 9 | import javassist.bytecode.annotation.Annotation; 10 | import javassist.bytecode.annotation.MemberValue; 11 | import javassist.expr.ExprEditor; 12 | import javassist.expr.FieldAccess; 13 | 14 | import java.io.File; 15 | import java.io.FileInputStream; 16 | import java.io.FileOutputStream; 17 | import java.lang.annotation.Retention; 18 | import java.lang.annotation.Target; 19 | import java.util.ArrayList; 20 | import java.util.HashMap; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | import static java.lang.annotation.ElementType.*; 25 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 26 | 27 | /** 28 | * provides property support for Java classes via byte code enhancement. 29 | */ 30 | public class PropertiesEnhancer { 31 | 32 | /** 33 | * Marks the given method, field or type as one with both a generated setter and getter. 34 | * PropertiesEnhancer creates this annotation for the enhanced method, field or type. 35 | */ 36 | @Target({METHOD, FIELD, TYPE}) 37 | @Retention(RUNTIME) 38 | public static @interface GeneratedAccessor {} 39 | 40 | /** 41 | * Marks the given method, field or type as one with a generated getter. 42 | * PropertiesEnhancer creates this annotation for the enhanced method, field or type. 43 | */ 44 | @Target({METHOD, FIELD, TYPE}) 45 | @Retention(RUNTIME) 46 | public static @interface GeneratedGetAccessor {} 47 | 48 | /** 49 | * Marks the given method, field or type as one with a generated setter. 50 | * PropertiesEnhancer creates this annotation for the enhanced method, field or type. 51 | */ 52 | @Target({METHOD, FIELD, TYPE}) 53 | @Retention(RUNTIME) 54 | public static @interface GeneratedSetAccessor {} 55 | 56 | /** 57 | * Marks the given method, field or type as one with a rewritten setter and getter. 58 | * PropertiesEnhancer creates this annotation for the enhanced method, field or type. 59 | */ 60 | @Target({METHOD, FIELD, TYPE}) 61 | @Retention(RUNTIME) 62 | public static @interface RewrittenAccessor {} 63 | 64 | public static boolean generateAccessors(final String classpath, final File classFile) throws Exception { 65 | final ClassPool classPool = new ClassPool(); 66 | classPool.appendSystemPath(); 67 | classPool.appendPathList(classpath); 68 | 69 | final FileInputStream is = new FileInputStream(classFile); 70 | try { 71 | final CtClass ctClass = classPool.makeClass(is); 72 | if (hasAnnotation(ctClass, GeneratedAccessor.class)) { 73 | is.close(); 74 | return false; 75 | } 76 | for (final CtField ctField : ctClass.getDeclaredFields()) { 77 | if (isProperty(ctField)) { 78 | 79 | final String propertyType = ctField.getType().getSimpleName(); 80 | final List getters = new ArrayList(); 81 | final List setters = new ArrayList(); 82 | 83 | if (propertyType.compareTo("boolean") == 0 || propertyType.compareTo("Boolean") == 0) { 84 | if (ctField.getName().matches("(?i)(is|has|can)[a-zA-Z0-9\\-_]+")) { 85 | getters.add(ctField.getName()); 86 | } else { 87 | final String propertyName = ctField.getName().substring(0, 1).toUpperCase() + ctField.getName().substring(1); 88 | getters.add("is" + propertyName); 89 | } 90 | } 91 | // Continue to add default getX and getY for boolean fields to avoid API breaking 92 | final String propertyName = ctField.getName().substring(0, 1).toUpperCase() + ctField.getName().substring(1); 93 | getters.add("get" + propertyName); 94 | setters.add("set" + propertyName); 95 | 96 | final SignatureAttribute signature = ((SignatureAttribute) ctField.getFieldInfo().getAttribute(SignatureAttribute.tag)); 97 | 98 | for (final String getter : getters) { 99 | try { 100 | final CtMethod ctMethod = ctClass.getDeclaredMethod(getter); 101 | if (ctMethod.getParameterTypes().length > 0 || Modifier.isStatic(ctMethod.getModifiers())) { 102 | throw new NotFoundException("it's not a getter !"); 103 | } 104 | } catch (NotFoundException noGetter) { 105 | // Create getter 106 | final CtMethod getMethod = CtMethod.make("public " + ctField.getType().getName() + " " + getter + "() { return this." + ctField.getName() + "; }", ctClass); 107 | ctClass.addMethod(getMethod); 108 | createAnnotation(getAnnotations(getMethod), GeneratedAccessor.class); 109 | createAnnotation(getAnnotations(ctField), GeneratedGetAccessor.class); 110 | if (signature != null) { 111 | final String fieldSignature = signature.getSignature(); 112 | final String getMethodSignature = "()" + fieldSignature; 113 | getMethod.getMethodInfo().addAttribute( 114 | new SignatureAttribute(getMethod.getMethodInfo().getConstPool(), getMethodSignature) 115 | ); 116 | } 117 | } 118 | } 119 | 120 | for (final String setter : setters) { 121 | try { 122 | final CtMethod ctMethod = ctClass.getDeclaredMethod(setter, new CtClass[]{ctField.getType()}); 123 | if (ctMethod.getParameterTypes().length != 1 || !ctMethod.getParameterTypes()[0].equals(ctField.getType()) || Modifier.isStatic(ctMethod.getModifiers())) { 124 | throw new NotFoundException("it's not a setter !"); 125 | } 126 | } catch (NotFoundException noSetter) { 127 | // Create setter 128 | final CtMethod setMethod = CtMethod.make("public void " + setter + "(" + ctField.getType().getName() + " value) { this." + ctField.getName() + " = value; }", ctClass); 129 | ctClass.addMethod(setMethod); 130 | createAnnotation(getAnnotations(setMethod), GeneratedAccessor.class); 131 | createAnnotation(getAnnotations(ctField), GeneratedSetAccessor.class); 132 | if (signature != null) { 133 | final String fieldSignature = signature.getSignature(); 134 | final String setMethodSignature = "(" + fieldSignature + ")V"; 135 | setMethod.getMethodInfo().addAttribute( 136 | new SignatureAttribute(setMethod.getMethodInfo().getConstPool(), setMethodSignature) 137 | ); 138 | } 139 | } 140 | } 141 | } 142 | } 143 | 144 | createAnnotation(getAnnotations(ctClass), GeneratedAccessor.class); 145 | 146 | is.close(); 147 | final FileOutputStream os = new FileOutputStream(classFile); 148 | os.write(ctClass.toBytecode()); 149 | os.close(); 150 | return true; 151 | 152 | } finally { 153 | try { 154 | is.close(); 155 | } catch (Exception ex) { 156 | // Ignore 157 | } 158 | } 159 | } 160 | 161 | public static boolean rewriteAccess(String classpath, File classFile) throws Exception { 162 | ClassPool classPool = new ClassPool(); 163 | classPool.appendSystemPath(); 164 | classPool.appendPathList(classpath); 165 | 166 | FileInputStream is = new FileInputStream(classFile); 167 | try { 168 | CtClass ctClass = classPool.makeClass(is); 169 | if(hasAnnotation(ctClass, RewrittenAccessor.class)) { 170 | is.close(); 171 | return false; 172 | } 173 | 174 | for (final CtBehavior ctMethod : ctClass.getDeclaredBehaviors()) { 175 | ctMethod.instrument(new ExprEditor() { 176 | 177 | @Override 178 | public void edit(FieldAccess fieldAccess) throws CannotCompileException { 179 | try { 180 | 181 | // Has accessor 182 | if (isAccessor(fieldAccess.getField())) { 183 | 184 | String propertyName = null; 185 | if (fieldAccess.getField().getDeclaringClass().equals(ctMethod.getDeclaringClass()) 186 | || ctMethod.getDeclaringClass().subclassOf(fieldAccess.getField().getDeclaringClass())) { 187 | if ((ctMethod.getName().startsWith("get") || ctMethod.getName().startsWith("set")) && ctMethod.getName().length() > 3) { 188 | propertyName = ctMethod.getName().substring(3); 189 | propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1); 190 | } 191 | } 192 | 193 | if (propertyName == null || !propertyName.equals(fieldAccess.getFieldName())) { 194 | 195 | String getSet = fieldAccess.getFieldName().substring(0,1).toUpperCase() + fieldAccess.getFieldName().substring(1); 196 | 197 | if (fieldAccess.isReader() && hasAnnotation(fieldAccess.getField(), GeneratedGetAccessor.class)) { 198 | // Rewrite read access 199 | fieldAccess.replace("$_ = $0.get" + getSet + "();"); 200 | } else if (fieldAccess.isWriter() && hasAnnotation(fieldAccess.getField(), GeneratedSetAccessor.class)) { 201 | // Rewrite write access 202 | fieldAccess.replace("$0.set" + getSet + "($1);"); 203 | } 204 | } 205 | } 206 | 207 | } catch (Exception e) { 208 | // Not sure why this is being ignored... but presumably there is a reason 209 | } 210 | } 211 | }); 212 | } 213 | 214 | createAnnotation(getAnnotations(ctClass), RewrittenAccessor.class); 215 | 216 | is.close(); 217 | FileOutputStream os = new FileOutputStream(classFile); 218 | os.write(ctClass.toBytecode()); 219 | os.close(); 220 | 221 | } finally { 222 | try { 223 | is.close(); 224 | } catch(Exception ex) { 225 | // Ignore 226 | } 227 | } 228 | return true; 229 | } 230 | 231 | // -- 232 | 233 | static boolean isProperty(CtField ctField) { 234 | if (ctField.getName().equals(ctField.getName().toUpperCase()) || ctField.getName().substring(0, 1).equals(ctField.getName().substring(0, 1).toUpperCase())) { 235 | return false; 236 | } 237 | return Modifier.isPublic(ctField.getModifiers()) 238 | && !Modifier.isFinal(ctField.getModifiers()) 239 | && !Modifier.isStatic(ctField.getModifiers()); 240 | } 241 | 242 | static boolean isAccessor(CtField ctField) throws Exception { 243 | return hasAnnotation(ctField, GeneratedGetAccessor.class) || hasAnnotation(ctField, GeneratedSetAccessor.class); 244 | } 245 | 246 | // -- 247 | 248 | /** 249 | * Test if a class has the provided annotation 250 | */ 251 | static boolean hasAnnotation(CtClass ctClass, Class annotationType) throws ClassNotFoundException { 252 | return getAnnotations(ctClass).getAnnotation(annotationType.getName()) != null; 253 | } 254 | 255 | /** 256 | * Test if a field has the provided annotation 257 | */ 258 | static boolean hasAnnotation(CtField ctField, Class annotationType) throws ClassNotFoundException { 259 | return getAnnotations(ctField).getAnnotation(annotationType.getName()) != null; 260 | } 261 | 262 | /** 263 | * Retrieve all class annotations. 264 | */ 265 | static AnnotationsAttribute getAnnotations(CtClass ctClass) { 266 | AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) ctClass.getClassFile().getAttribute(AnnotationsAttribute.visibleTag); 267 | if (annotationsAttribute == null) { 268 | annotationsAttribute = new AnnotationsAttribute(ctClass.getClassFile().getConstPool(), AnnotationsAttribute.visibleTag); 269 | ctClass.getClassFile().addAttribute(annotationsAttribute); 270 | } 271 | return annotationsAttribute; 272 | } 273 | 274 | /** 275 | * Retrieve all field annotations. 276 | */ 277 | static AnnotationsAttribute getAnnotations(CtField ctField) { 278 | AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) ctField.getFieldInfo().getAttribute(AnnotationsAttribute.visibleTag); 279 | if (annotationsAttribute == null) { 280 | annotationsAttribute = new AnnotationsAttribute(ctField.getFieldInfo().getConstPool(), AnnotationsAttribute.visibleTag); 281 | ctField.getFieldInfo().addAttribute(annotationsAttribute); 282 | } 283 | return annotationsAttribute; 284 | } 285 | 286 | /** 287 | * Retrieve all method annotations. 288 | */ 289 | static AnnotationsAttribute getAnnotations(CtMethod ctMethod) { 290 | AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) ctMethod.getMethodInfo().getAttribute(AnnotationsAttribute.visibleTag); 291 | if (annotationsAttribute == null) { 292 | annotationsAttribute = new AnnotationsAttribute(ctMethod.getMethodInfo().getConstPool(), AnnotationsAttribute.visibleTag); 293 | ctMethod.getMethodInfo().addAttribute(annotationsAttribute); 294 | } 295 | return annotationsAttribute; 296 | } 297 | 298 | /** 299 | * Create a new annotation to be dynamically inserted in the byte code. 300 | */ 301 | static void createAnnotation(AnnotationsAttribute attribute, Class annotationType, Map members) { 302 | Annotation annotation = new Annotation(annotationType.getName(), attribute.getConstPool()); 303 | for (Map.Entry member : members.entrySet()) { 304 | annotation.addMemberValue(member.getKey(), member.getValue()); 305 | } 306 | attribute.addAnnotation(annotation); 307 | } 308 | 309 | /** 310 | * Create a new annotation to be dynamically inserted in the byte code. 311 | */ 312 | static void createAnnotation(AnnotationsAttribute attribute, Class annotationType) { 313 | createAnnotation(attribute, annotationType, new HashMap()); 314 | } 315 | } 316 | --------------------------------------------------------------------------------