diff --git a/codegen-test/build.gradle.kts b/codegen-test/build.gradle.kts index de28316..a2b9559 100644 --- a/codegen-test/build.gradle.kts +++ b/codegen-test/build.gradle.kts @@ -1,6 +1,4 @@ -import org.h2.Driver import org.seasar.doma.gradle.codegen.desc.LanguageType -import org.seasar.doma.gradle.codegen.jdbc.SimpleDataSource buildscript { repositories { @@ -11,10 +9,7 @@ buildscript { } } dependencies { - val h2Version: String = properties["h2Version"] as String classpath("org.domaframework.doma:codegen") - classpath("com.h2database:h2:$h2Version") - classpath("mysql:mysql-connector-java:8.0.33") } } @@ -23,6 +18,7 @@ plugins { id("org.jetbrains.kotlin.jvm") version "2.2.0" id("org.domaframework.doma.compile") version "4.0.0" id("org.domaframework.doma.codegen") + id("com.nocwriter.runsql") version "1.0.3" } java { @@ -40,13 +36,19 @@ dependencies { implementation("org.seasar.doma:doma-core:$domaVersion") annotationProcessor("org.seasar.doma:doma-processor:$domaVersion") - + // Use JUnit BOM for version management testImplementation(platform("org.junit:junit-bom:5.13.2")) testImplementation("org.junit.jupiter:junit-jupiter-api") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("com.h2database:h2:$h2Version") + + // Add H2 to the domaCodeGen configuration + domaCodeGen("com.h2database:h2:$h2Version") + + // Used by runsql-gradle-plugin + runtimeOnly("com.h2database:h2:$h2Version") } val _url = "jdbc:h2:file:$projectDir/data/db" @@ -92,15 +94,13 @@ tasks { } } - val createDb = register("createDb") { - doLast { - val ds = SimpleDataSource() - ds.setDriver(Driver()) - ds.setUrl(_url) - ds.setUser(_user) - ds.setPassword(_password) - - val sql = """ + val createDb = register("createDb") { + config { + username = _user + password = _password + url = _url + driverClassName = "org.h2.Driver" + script = """ drop all objects; create table department( @@ -115,13 +115,8 @@ tasks { employee_id integer not null primary key, employee_no integer not null, employee_name varchar(20) - ); - """ - ds.connection.use { - it.createStatement().use { - it.execute(sql) - } - } + ); + """.trimIndent() } } diff --git a/codegen/src/main/java/org/seasar/doma/gradle/codegen/CodeGenPlugin.java b/codegen/src/main/java/org/seasar/doma/gradle/codegen/CodeGenPlugin.java index a005e90..a5590da 100644 --- a/codegen/src/main/java/org/seasar/doma/gradle/codegen/CodeGenPlugin.java +++ b/codegen/src/main/java/org/seasar/doma/gradle/codegen/CodeGenPlugin.java @@ -20,6 +20,7 @@ public class CodeGenPlugin implements Plugin { public static final String EXTENSION_NAME = "domaCodeGen"; + public static final String CONFIGURATION_NAME = "domaCodeGen"; public static final String TASK_GROUP_NAME = "Doma Code Generation"; public static final String DB_META_TASK_NAME = "DbMeta"; public static final String DTO_TASK_NAME = "Dto"; @@ -33,6 +34,18 @@ public class CodeGenPlugin implements Plugin { @Override public void apply(Project project) { + project + .getConfigurations() + .create( + CONFIGURATION_NAME, + config -> { + config.setDescription( + "The libraries used by the Doma CodeGen plugin for JDBC drivers and other dependencies"); + config.setVisible(false); + config.setCanBeConsumed(false); + config.setCanBeResolved(true); + }); + NamedDomainObjectContainer container = project.container( CodeGenConfig.class, diff --git a/codegen/src/main/java/org/seasar/doma/gradle/codegen/extension/CodeGenConfig.java b/codegen/src/main/java/org/seasar/doma/gradle/codegen/extension/CodeGenConfig.java index 99e7b3b..5ef9fb6 100644 --- a/codegen/src/main/java/org/seasar/doma/gradle/codegen/extension/CodeGenConfig.java +++ b/codegen/src/main/java/org/seasar/doma/gradle/codegen/extension/CodeGenConfig.java @@ -1,14 +1,20 @@ package org.seasar.doma.gradle.codegen.extension; +import static org.seasar.doma.gradle.codegen.CodeGenPlugin.CONFIGURATION_NAME; + import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; import java.sql.Driver; import java.util.Collections; import java.util.List; +import java.util.Set; import javax.inject.Inject; import javax.sql.DataSource; import org.gradle.api.Action; import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.Directory; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileTree; @@ -31,6 +37,8 @@ public class CodeGenConfig { private final String name; + private final Configuration configuration; + private final Property globalFactory; private final Property dataSource; @@ -85,6 +93,8 @@ public class CodeGenConfig { public CodeGenConfig(String name, Project project) { this.name = name; + this.configuration = project.getConfigurations().getByName(CONFIGURATION_NAME); + ObjectFactory objects = project.getObjects(); globalFactory = objects.property(GlobalFactory.class); @@ -146,13 +156,32 @@ private Provider dataSourceProvider() { if (driverClassName == null) { throw new CodeGenException(Message.DOMAGEN0024); } - Driver driver = ClassUtil.newInstance(Driver.class, driverClassName, "driverClassName"); + ClassLoader classLoader = createClassLoader(); + Driver driver = + ClassUtil.newInstance(Driver.class, driverClassName, "driverClassName", classLoader); return globalFactory .get() .createDataSource(driver, user.getOrNull(), password.getOrNull(), url.get()); }); } + protected ClassLoader createClassLoader() { + if (configuration == null || configuration.isEmpty()) { + return getClass().getClassLoader(); + } + Set fileSet = configuration.getFiles(); + URL[] urls = new URL[fileSet.size()]; + int i = 0; + for (File file : fileSet) { + try { + urls[i++] = file.toURI().toURL(); + } catch (Exception e) { + throw new RuntimeException("Failed to convert file to URL: " + file, e); + } + } + return new URLClassLoader(urls, getClass().getClassLoader()); + } + private Provider codeGenDialectProvider() { return url.map( it -> { diff --git a/codegen/src/main/java/org/seasar/doma/gradle/codegen/message/Message.java b/codegen/src/main/java/org/seasar/doma/gradle/codegen/message/Message.java index 0491d8e..13c67c2 100644 --- a/codegen/src/main/java/org/seasar/doma/gradle/codegen/message/Message.java +++ b/codegen/src/main/java/org/seasar/doma/gradle/codegen/message/Message.java @@ -26,6 +26,11 @@ public enum Message implements MessageResource { "Cannot infer the driver class name from the property \"url\", so this plugin cannot create a DataSource instance. Correct the url property or specify the property \"dataSource\" explicitly."), DOMAGEN0025( "Cannot infer the dialect name from the property \"url\", so this plugin cannot create a CodeGenDialect instance. Correct the url property or specify the property \"codeGenDialect\" explicitly."), + DOMAGEN0033("The class \"{1}\" to which the parameter \"{0}\" refers is not found. {2}"), + DOMAGEN0034( + "The class \"{1}\" to which the parameter \"{0}\" refers must be a subtype of the class \"{2}\"."), + DOMAGEN0035( + "The class \"{1}\" to which the parameter \"{0}\" refers cannot be instantiated. The class \"{1}\" must have a public default constructor. {2}"), DOMAGEN5001( "The JDBC driver may not be loaded. Check that the JDBC driver is in the classpath. If the JDBC driver is not loaded automatically, load it explicitly using Class.forName. ex) Class.forName(\"oracle.jdbc.driver.OracleDriver\")"), diff --git a/codegen/src/main/java/org/seasar/doma/gradle/codegen/util/ClassUtil.java b/codegen/src/main/java/org/seasar/doma/gradle/codegen/util/ClassUtil.java index ba8e60a..47a4ba8 100644 --- a/codegen/src/main/java/org/seasar/doma/gradle/codegen/util/ClassUtil.java +++ b/codegen/src/main/java/org/seasar/doma/gradle/codegen/util/ClassUtil.java @@ -68,6 +68,20 @@ public static T newInstance(Class supertype, String className, String pro } } + public static T newInstance( + Class supertype, String className, String propertyName, ClassLoader classLoader) { + AssertionUtil.assertNotNull(supertype, className, propertyName, classLoader); + Class clazz = forName(className, propertyName, classLoader); + if (!supertype.isAssignableFrom(clazz)) { + throw new CodeGenException(Message.DOMAGEN0034, propertyName, className, supertype.getName()); + } + try { + return supertype.cast(clazz.getDeclaredConstructor().newInstance()); + } catch (ReflectiveOperationException e) { + throw new CodeGenException(Message.DOMAGEN0035, propertyName, className, e); + } + } + public static Class forName(String className, String propertyName) { AssertionUtil.assertNotNull(className, propertyName); try { @@ -76,4 +90,13 @@ public static Class forName(String className, String propertyName) { throw new CodeGenException(Message.DOMAGEN0013, propertyName, className, e); } } + + public static Class forName(String className, String propertyName, ClassLoader classLoader) { + AssertionUtil.assertNotNull(className, propertyName, classLoader); + try { + return classLoader.loadClass(className); + } catch (ClassNotFoundException e) { + throw new CodeGenException(Message.DOMAGEN0033, propertyName, className, e); + } + } } diff --git a/codegen/src/test/java/org/seasar/doma/gradle/codegen/util/ClassUtilTest.java b/codegen/src/test/java/org/seasar/doma/gradle/codegen/util/ClassUtilTest.java new file mode 100644 index 0000000..808509c --- /dev/null +++ b/codegen/src/test/java/org/seasar/doma/gradle/codegen/util/ClassUtilTest.java @@ -0,0 +1,130 @@ +package org.seasar.doma.gradle.codegen.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.seasar.doma.gradle.codegen.exception.CodeGenException; + +@SuppressWarnings("unchecked") +public class ClassUtilTest { + + @Test + public void testGetPackageName() { + assertEquals("java.lang", ClassUtil.getPackageName("java.lang.String")); + assertEquals("", ClassUtil.getPackageName("String")); + assertEquals("", ClassUtil.getPackageName("")); + } + + @Test + public void testGetSimpleName() { + assertEquals("String", ClassUtil.getSimpleName("java.lang.String")); + assertEquals("String", ClassUtil.getSimpleName("String")); + assertEquals("", ClassUtil.getSimpleName("")); + } + + @Test + public void testToBoxedPrimitiveTypeIfPossible() { + assertSame(Void.class, ClassUtil.toBoxedPrimitiveTypeIfPossible(void.class)); + assertSame(Character.class, ClassUtil.toBoxedPrimitiveTypeIfPossible(char.class)); + assertSame(Boolean.class, ClassUtil.toBoxedPrimitiveTypeIfPossible(boolean.class)); + assertSame(Byte.class, ClassUtil.toBoxedPrimitiveTypeIfPossible(byte.class)); + assertSame(Short.class, ClassUtil.toBoxedPrimitiveTypeIfPossible(short.class)); + assertSame(Integer.class, ClassUtil.toBoxedPrimitiveTypeIfPossible(int.class)); + assertSame(Long.class, ClassUtil.toBoxedPrimitiveTypeIfPossible(long.class)); + assertSame(Float.class, ClassUtil.toBoxedPrimitiveTypeIfPossible(float.class)); + assertSame(Double.class, ClassUtil.toBoxedPrimitiveTypeIfPossible(double.class)); + assertSame(String.class, ClassUtil.toBoxedPrimitiveTypeIfPossible(String.class)); + } + + @Test + public void testNewInstance() { + List list = ClassUtil.newInstance(List.class, "java.util.ArrayList", "testProperty"); + assertInstanceOf(ArrayList.class, list); + } + + @Test + public void testNewInstance_WithClassLoader() throws Exception { + URLClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader()); + List list = + ClassUtil.newInstance(List.class, "java.util.ArrayList", "testProperty", classLoader); + assertInstanceOf(ArrayList.class, list); + } + + @Test + public void testNewInstance_ClassNotFound() { + assertThrows( + CodeGenException.class, + () -> { + ClassUtil.newInstance(List.class, "non.existent.Class", "testProperty"); + }); + } + + @Test + public void testNewInstance_NotAssignable() { + assertThrows( + CodeGenException.class, + () -> { + ClassUtil.newInstance(List.class, "java.lang.String", "testProperty"); + }); + } + + @Test + public void testNewInstance_WithClassLoader_ClassNotFound() throws Exception { + URLClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader()); + assertThrows( + CodeGenException.class, + () -> { + ClassUtil.newInstance(List.class, "non.existent.Class", "testProperty", classLoader); + }); + } + + @Test + public void testNewInstance_WithClassLoader_NotAssignable() throws Exception { + try (URLClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader())) { + assertThrows( + CodeGenException.class, + () -> { + ClassUtil.newInstance(List.class, "java.lang.String", "testProperty", classLoader); + }); + } + } + + @Test + public void testForName() { + Class clazz = ClassUtil.forName("java.lang.String", "testProperty"); + assertSame(String.class, clazz); + } + + @Test + public void testForName_WithClassLoader() throws Exception { + URLClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader()); + Class clazz = ClassUtil.forName("java.lang.String", "testProperty", classLoader); + assertSame(String.class, clazz); + } + + @Test + public void testForName_ClassNotFound() { + assertThrows( + CodeGenException.class, + () -> { + ClassUtil.forName("non.existent.Class", "testProperty"); + }); + } + + @Test + public void testForName_WithClassLoader_ClassNotFound() throws Exception { + URLClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader()); + assertThrows( + CodeGenException.class, + () -> { + ClassUtil.forName("non.existent.Class", "testProperty", classLoader); + }); + } +}