Skip to content

Instantly share code, notes, and snippets.

@passionfruit18
Last active September 18, 2019 14:47
Show Gist options
  • Save passionfruit18/d0f78e1cc4aea41a0e4ab736df842ab3 to your computer and use it in GitHub Desktop.
Save passionfruit18/d0f78e1cc4aea41a0e4ab736df842ab3 to your computer and use it in GitHub Desktop.
Grails Join Class Generator– generates something with structure similar to the UserRole class generated by Spring Security Core Plugin 2.0.0
/**
* Generates something with structure similar to the UserRole class generated by Spring Security Core Plugin
* Except "removeAll" methods, which were overloaded, have been replaced by separately named methods for each field
* representing a Joined Class
*/
class GrailsJoinClassGenerator {
static String joinClassCode(JoinClassRep joinClassRep) {
String classInnerBlock =
[serialStuff(),
fields(joinClassRep.joinedClassRepList),
constructor(joinClassRep),
equalsAndHashCode(joinClassRep),
getExistsCriteria(joinClassRep),
create(joinClassRep),
removeMethods(joinClassRep),
constraints(joinClassRep),
mapping(joinClassRep)].join('\n\n')
List<String> imports = ["grails.gorm.DetachedCriteria",
"groovy.transform.ToString",
"org.apache.commons.lang.builder.HashCodeBuilder"].collect {"import ${it}"}
nestedBlock(
[imports.join('\n') + '\n\n' +
"@ToString(cache=true, includeNames=true, includePackage=false)",
"class ${joinClassRep.joinClassName} implements Serializable {"].join('\n'),
classInnerBlock,
"}"
)
}
static String serialStuff() {
"\nprivate static final long serialVersionUID = 1"
}
/**
* Joined Classes as fields of Join Class
* @param joinedClassRepList
* @return
*/
static String fields(List<JoinedClassRep> joinedClassRepList) {
joinedClassRepList
.collect {"${it.joinedClassName} ${it.joinedClassFieldName}"}
.join('\n')
}
/**
* Constructor using Joined Class Fields
* @param joinClassRep
* @return
*/
static String constructor(JoinClassRep joinClassRep) {
String arguments = joinClassRep.joinedClassRepList
.collect {"${it.joinedClassName} ${it.joinedClassAbbrev}" }
.join(', ')
String assignment = "this()\n" + joinClassRep.joinedClassRepList
.collect {"${it.joinedClassFieldName} = ${it.joinedClassAbbrev}" }
.join('\n')
nestedBlock(
"${joinClassRep.joinClassName}(${arguments}) {",
assignment,
"}"
)
}
/**
* Equals & Hashcode based on ids of Joined Classes
* @param joinClassRep
* @return
*/
static String equalsAndHashCode(JoinClassRep joinClassRep) {
equals(joinClassRep) + "\n\n" + hashCode(joinClassRep)
}
static String equals(JoinClassRep joinClassRep) {
String instanceofComparison = nestedBlock(
"if (!(other instanceof ${joinClassRep.joinClassName})) {",
"return false",
"}"
)
String idComparison = joinClassRep.joinedClassRepList.collect {
"other.${it.joinedClassFieldName}?.id == ${it.joinedClassFieldName}?.id"
}.join(" && ")
String comparisonCode = instanceofComparison + "\n\n" + idComparison
nestedBlock(
["@Override",
"boolean equals(other) {"].join('\n'),
comparisonCode,
"}"
)
}
static String hashCode(JoinClassRep joinClassRep) {
String hashBuildingCode = "def builder = new HashCodeBuilder()\n" +
joinClassRep.joinedClassRepList.collect {
"if (${it.joinedClassFieldName}) builder.append(${it.joinedClassFieldName}.id)"
}.join('\n') +
"\nbuilder.toHashCode()"
nestedBlock(
["@Override",
"int hashCode() {"].join('\n'),
hashBuildingCode,
"}"
)
}
/**
* Define the "get", "exists", and "criteriaFor" methods
* @param joinClassRep
* @return
*/
static String getExistsCriteria(JoinClassRep joinClassRep) {
String idArguments = idArguments(joinClassRep)
String idArgumentsPassedIn = joinClassRep.joinedClassRepList.collect {
"${it.joinedClassFieldNameIdForm}"
}.join(', ')
String getMethod = nestedBlock(
"static ${joinClassRep.joinClassName} get(${idArguments}) {",
"criteriaFor(${idArgumentsPassedIn}).get()",
"}")
String existsMethod = nestedBlock(
"static boolean exists(${idArguments}) {",
"criteriaFor(${idArgumentsPassedIn}).count()",
"}"
)
getMethod + "\n\n" + existsMethod + "\n\n" + criteriaForMethod(joinClassRep)
}
static String idArguments(JoinClassRep joinClassRep) {
joinClassRep.joinedClassRepList.collect {
"long ${it.joinedClassFieldNameIdForm}"
}.join(', ')
}
static String criteriaForMethod(JoinClassRep joinClassRep) {
String idArguments = idArguments(joinClassRep)
String equalityStatements = joinClassRep.joinedClassRepList.collect {
"${it.joinedClassFieldName} == ${it.joinedClassName}.load(${it.joinedClassFieldNameIdForm})"
}.join(' &&\n')
String criteriaDeclaration = nestedBlock(
"${joinClassRep.joinClassName}.where {",
equalityStatements,
"}"
)
nestedBlock(
"private static DetachedCriteria criteriaFor(${idArguments}) {",
criteriaDeclaration,
"}"
)
}
static String create(JoinClassRep joinClassRep) {
String createArgs = joinClassRep.joinedClassRepList.collect {
"${it.joinedClassName} ${it.joinedClassFieldName}"
}.join(', ') + booleanFlushFalse
String constructorArgs = joinClassRep.joinedClassRepList.collect {
"${it.joinedClassFieldName}: ${it.joinedClassFieldName}"
}.join(', ')
String newInstanceCode =[
"def instance = new ${joinClassRep.joinClassName}(${constructorArgs})",
"instance.save(flush: flush, insert: true)",
"instance"
].join('\n')
nestedBlock(
"static ${joinClassRep.joinClassName} create(${createArgs}) {",
newInstanceCode,
"}"
)
}
static String booleanFlushFalse = ", boolean flush = false"
static String removeMethods(JoinClassRep joinClassRep) {
removeMethod(joinClassRep) + "\n\n" + joinClassRep.joinedClassRepList.collect{
removeAllFor(it, joinClassRep)
}.join("\n\n")
}
/**
* Method to remove instance of Join Class
* @param joinClassRep
* @return
*/
static String removeMethod(JoinClassRep joinClassRep) {
String removeArgs = joinClassRep.joinedClassRepList.collect {
"${it.joinedClassName} ${it.joinedClassAbbrev}"
}.join(", ") + booleanFlushFalse
String anyNull = joinClassRep.joinedClassRepList.collect {
"${it.joinedClassAbbrev} == null"
}.join(" || ")
String criteriaMatching = joinClassRep.joinedClassRepList.collect {
"${it.joinedClassFieldName} == ${it.joinedClassAbbrev}"
}.join(" && ")
String removeCode = [
"if (${anyNull}) return false",
"int rowCount = ${joinClassRep.joinClassName}.where { ${criteriaMatching} }.deleteAll()",
"if (flush) { ${joinClassRep.joinClassName}.withSession { it.flush() } }",
"rowCount"
].join('\n\n')
nestedBlock(
"static boolean remove(${removeArgs}) {",
removeCode,
"}"
)
}
/**
* Method to remove all instance of Join Class with one particular field
* @param joinedClassRep
* @return
*/
static String removeAllFor(JoinedClassRep joinedClassRep, JoinClassRep joinClassRep) {
String removeAllCode = [
"if (${joinedClassRep.joinedClassAbbrev} == null) return",
"${joinClassRep.joinClassName}.where { ${joinedClassRep.joinedClassFieldName} == ${joinedClassRep.joinedClassAbbrev} }.deleteAll()",
"if (flush) { ${joinClassRep.joinClassName}.withSession { it.flush() } }"
].join('\n\n')
nestedBlock(
"static void removeAllFor${joinedClassRep.joinedClassFieldName.capitalize()}(" +
"${joinedClassRep.joinedClassName} ${joinedClassRep.joinedClassAbbrev}${booleanFlushFalse}) {",
removeAllCode,
"}"
)
}
static String constraints(JoinClassRep joinClassRep) {
String jcVar = joinClassRep.joinClassAbbrev
/**
* The first joined class is used as the field to which the validator is attached
*/
JoinedClassRep firstJoinedClass = joinClassRep.joinedClassRepList[0]
/**
* The rest of the joined classes
*/
List<JoinedClassRep> theRest = joinClassRep.joinedClassRepList.drop(1)
String nullCheck = theRest.collect {
String field = it.joinedClassFieldName
"${jcVar}.${field} == null || ${jcVar}.${field}.id == null"
}.join(" || ")
String ids = (["${firstJoinedClass.joinedClassAbbrev}.id"] + theRest.collect {
"${jcVar}.${it.joinedClassFieldName}.id"
}).join(', ')
String validationBlock = [
"if (${nullCheck}) return",
"boolean existing = false",
nestedBlock(
"${joinClassRep.joinClassName}.withNewSession {",
"existing = ${joinClassRep.joinClassName}.exists(${ids})",
"}"
),
nestedBlock(
"if (existing) {",
"return '${joinClassRep.joinClassName.uncapitalize()}.exists'",
"}"
)
].join('\n')
String validatorBlock = nestedBlock(
"${firstJoinedClass.joinedClassFieldName} validator: " +
"{${firstJoinedClass.joinedClassName} ${firstJoinedClass.joinedClassAbbrev}, " +
"${joinClassRep.joinClassName} ${jcVar} ->",
validationBlock,
"}"
)
nestedBlock(
"static constraints = {",
validatorBlock,
"}"
)
}
static String mapping(JoinClassRep joinClassRep) {
String fields = joinClassRep.joinedClassRepList.collect {
"'${it.joinedClassFieldName}'"
}.join(', ')
nestedBlock(
"static mapping = {",
"id composite: [${fields}]\n" +
"version false",
"}"
)
}
/**
* Returns string s with n tabs preceding it
* @param s
* @param n
* @return
*/
static String tabbedN(String s, int n) {
return '\t' * n + s
}
/**
* Pre-tabs each line of string s, n times
* @param s
* @param n
* @return
*/
static String linesTabbedN(String s, int n) {
return s.split('\n').collect{tabbedN(it, n)}.join('\n')
}
/**
* Pre-tabs each line of string s
* @param s
* @param n
* @return
*/
static String linesTabbed(String s) {
return s.split('\n').collect{'\t' + it}.join('\n')
}
/**
* Prints "begin", then "block" at one tab deeper, and then "end".
* @param begin
* @param block
* @param end
* @return
*/
static String nestedBlock(String begin, String block, String end) {
begin + '\n' + linesTabbed(block) + '\n' + end
}
}
/**
* Representation of the JoinClass to be generated
*/
class JoinClassRep {
/**
* Name of joinClass, e.g. "UserRole"
*/
String joinClassName
/**
* Abbreviated name of joinClass, to be used as variable name, e.g. "ur"
*/
String joinClassAbbrev
/**
* List of classes to be joined.
* This should have two or more entries, or else it doesn't really make sense to have a join class.
* Code generation is undefined for 0 or 1 entries.
*/
List<JoinedClassRep> joinedClassRepList
JoinClassRep(String joinClassName, String joinClassAbbrev, List<JoinedClassRep> joinedClassRepList) {
this.joinClassName = joinClassName
this.joinClassAbbrev = joinClassAbbrev
this.joinedClassRepList = joinedClassRepList
checkConsistency()
}
boolean checkConsistency() {
if (joinedClassRepList.size() < 2) {
throw new Exception("Joined Classes number less than 2– nonsense!")
}
Set<String> abbrevNames = [joinClassAbbrev]
abbrevNames.addAll(joinedClassRepList*.joinedClassAbbrev)
if (abbrevNames.size() < joinedClassRepList.size() + 1) {
throw new Exception("There is a duplicate abbreviated name among JoinClassRep & JoinedClassReps!")
}
Set<String> fieldNames = []
fieldNames.addAll(joinedClassRepList*.joinedClassFieldName)
if (fieldNames.size() < joinedClassRepList.size() ) {
throw new Exception("There is a duplicate field name among JoinedClassReps!")
}
}
}
/**
* Representation of class to be joined
*/
class JoinedClassRep {
/**
* e.g. "User"
*/
String joinedClassName
/**
* e.g. "u"– to be used as a variable name in some cases
*/
String joinedClassAbbrev
/**
* Field name for the joined class
* e.g. "user"
* If null, the uncapitalized version of joinedClassName will be used
* This can be used to define a field name in case e.g. the Join Class joins two instance of the same Joined Class.
* e.g. "user1" and "user2"
*/
String joinedClassFieldName = null
/**
* e.g. "user"
* @return
*/
String getJoinedClassFieldName() {
if (joinedClassFieldName) {
joinedClassFieldName
}
else {
joinedClassName.uncapitalize()
}
}
/**
* e.g. "userId"
* @return
*/
String getJoinedClassFieldNameIdForm() {
getJoinedClassFieldName() + "Id"
}
JoinedClassRep(String joinedClassName, String joinedClassAbbrev, String joinedClassFieldName = null) {
this.joinedClassName = joinedClassName
this.joinedClassAbbrev = joinedClassAbbrev
this.joinedClassFieldName = joinedClassFieldName
}
}
class Main {
static void run() {
roleHierarchy()
}
static void roleHierarchy() {
JoinClassRep roleHierarchyRep = new JoinClassRep(
"RoleHierarchy",
"rh",
[
new JoinedClassRep("Role", "r1", "role1"),
new JoinedClassRep("Role", "r2", "role2")
]
)
String roleHierarchyCode = GrailsJoinClassGenerator.joinClassCode(roleHierarchyRep)
println roleHierarchyCode
}
static void userRole() {
JoinClassRep userRoleJCRep = new JoinClassRep(
"UserRole",
"ur",
[
new JoinedClassRep("User", "u"),
// new JoinedClassRep("User", "u1", "user1"),
// new JoinedClassRep("User", "u2", "user2"),
new JoinedClassRep("Role", "r")
]
)
String userRoleCode = GrailsJoinClassGenerator.joinClassCode(userRoleJCRep)
println userRoleCode
}
}
Main.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment