diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a4519d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Project exclude paths +/target/ +.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5ba0a90 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Mohamed Ashraf Bayor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..44cbb71 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +## JISEL: Java Interface Segregation Library +Interface Segregation Library for Java 17 + +Pitch Video: +... + +### How to Install ? + +If you are running a Maven project, add the latest release dependency to your pom.xml +```xml + + org.jisel + jisel + 1.0 + +``` +For other build tools, please check: [Maven Central](https://search.maven.org/artifact/org.jisel/jisel/1.0/jar). + +### Use on your declared bloated interfaces methods + +```java +import SealForProfile; + +public interface InterfaceA { + @SealForProfile + void something(); +} +``` + +### Use on your declared child classes implementing generated sealed interfaces + +```java +import AddToProfile; + +@AddToProfile("PROFILE_NAME") +public final class ClientA implements SealedInterfaceA { + // ... +} +``` + +### Sample classes for testing +[https://github.com/mohamed-ashraf-bayor/jisel-annotation-client](https://github.com/mohamed-ashraf-bayor/jisel-annotation-client) + +### Invalid Uses of Jisel +The annotation should be used ONLY on interfaces created in your own project. + +### Issues, Bugs, Suggestions +Contribute to the project's growth by reporting issues or making improvement suggestions [here](https://github.com/mohamed-ashraf-bayor/jisel/issues/new/choose) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4959b0c --- /dev/null +++ b/pom.xml @@ -0,0 +1,175 @@ + + + + 4.0.0 + org.jisel + jisel + 1.0-SNAPSHOT + jar + + Jisel + Interface Segregation Library for Java 17 + http://jisel.org + + + + MIT License + https://opensource.org/licenses/MIT + repo + + + + + scm:git:git://github.com/mohamed-ashraf-bayor/froporec.git + scm:git:git@github.com:mohamed-ashraf-bayor/mohamed-ashraf-bayor.git + https://github.com/mohamed-ashraf-bayor/jisel + HEAD + + + + + Mohamed Ashraf Bayor + Jisel.org + https://jisel.org/ + + + + + UTF-8 + UTF-8 + 17 + 1.0.1 + 3.8.1 + 2.8.2 + 2.5.3 + 1.6.7 + 3.2.1 + 1.5 + 3.3.1 + + + + + com.google.auto.service + auto-service + ${auto-service.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + 17 + -Xlint:unchecked + + + + maven-deploy-plugin + ${maven-deploy-plugin.version} + + + default-deploy + deploy + + deploy + + + + + + org.apache.maven.plugins + maven-release-plugin + ${maven-release-plugin.version} + + true + false + forked-path + -Dgpg.passphrase=${gpg.passphrase} + true + false + release + deploy + + + + org.apache.maven.scm + maven-scm-provider-gitexe + 1.9.5 + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + true + + ossrh + https://s01.oss.sonatype.org/ + true + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + sign-artifacts + verify + + sign + + + ${gpg.keyname} + ${gpg.keyname} + + + + + + + + + + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + \ No newline at end of file diff --git a/src/main/java/org/jisel/AddToProfile.java b/src/main/java/org/jisel/AddToProfile.java new file mode 100644 index 0000000..81bd070 --- /dev/null +++ b/src/main/java/org/jisel/AddToProfile.java @@ -0,0 +1,21 @@ +package org.jisel; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE}) +@Repeatable(AddToProfile.AddToProfiless.class) +public @interface AddToProfile { + + String value(); + + @Retention(RetentionPolicy.SOURCE) + @Target(ElementType.TYPE) + @interface AddToProfiless { + AddToProfile[] value(); + } +} \ No newline at end of file diff --git a/src/main/java/org/jisel/AddToProfiles.java b/src/main/java/org/jisel/AddToProfiles.java new file mode 100644 index 0000000..c89af8b --- /dev/null +++ b/src/main/java/org/jisel/AddToProfiles.java @@ -0,0 +1,12 @@ +package org.jisel; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE}) +public @interface AddToProfiles { + String[] value(); +} \ No newline at end of file diff --git a/src/main/java/org/jisel/JiselAnnotationProcessor.java b/src/main/java/org/jisel/JiselAnnotationProcessor.java new file mode 100644 index 0000000..89890cf --- /dev/null +++ b/src/main/java/org/jisel/JiselAnnotationProcessor.java @@ -0,0 +1,103 @@ +package org.jisel; + +import com.google.auto.service.AutoService; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.tools.JavaFileObject; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; +import java.util.Map; +import java.util.Set; + +// @Slf4j +@SupportedAnnotationTypes("com.bayor.jisel.annotation.SealFor") +@SupportedSourceVersion(SourceVersion.RELEASE_17) +@AutoService(Processor.class) +public class JiselAnnotationProcessor extends AbstractProcessor { + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnvironment) { + + for (TypeElement annotation : annotations) { + + if (!annotation.getSimpleName().toString().contains("SealFor")) + continue; + + Set annotatedElements = roundEnvironment.getElementsAnnotatedWith(annotation); + + System.out.println(">>>>>>>> annotatedElements >>>>>>>>>>>" + annotatedElements); + + List annotatedClasses = annotatedElements.stream() + //.filter(element -> element.getClass().getClassLoader().isRecord()) + .filter(element -> ElementKind.INTERFACE.equals(element.getKind())) + .toList(); + + System.out.println(">>>>>>>> annotatedClasses >>>>>>>>>>>" + annotatedClasses); + + List annotatedMethods = annotatedElements.stream() + .filter(element -> !element.getClass().isRecord()) + .filter(element -> ElementKind.METHOD.equals(element.getKind())) + .toList(); + + System.out.println(">>>>>>>> annotateMethods >>>>>>>>>>>" + annotatedMethods); + } + + return true; + } + + private void writeSealedInterfaceFile(String className, List gettersList, Map getterMap, Set allAnnotatedElements) throws IOException { + String recordClassString = buildSealedInterfaceContent(className, gettersList, getterMap, allAnnotatedElements); + JavaFileObject recordClassFile = processingEnv.getFiler().createSourceFile(className + "Record"); + try (PrintWriter out = new PrintWriter(recordClassFile.openWriter())) { + out.println(recordClassString); + } + } + + private String buildSealedInterfaceContent(String className, List gettersList, Map getterMap, Set allAnnotatedElements) { + + StringBuilder recordClassContent = new StringBuilder(); + + String packageName = null; + + int lastDot = className.lastIndexOf('.'); + + if (lastDot > 0) { + packageName = className.substring(0, lastDot); + } + + String simpleClassName = className.substring(lastDot + 1); + String recordClassName = className + "Record"; + String recordSimpleClassName = recordClassName.substring(lastDot + 1); + + if (packageName != null) { + recordClassContent.append("package "); + recordClassContent.append(packageName); + recordClassContent.append(";"); + recordClassContent.append("\n\n"); + } + + recordClassContent.append("public record "); + recordClassContent.append(recordSimpleClassName); + recordClassContent.append("("); + +// // System.out.println("################################" + recordSimpleClassName); +// buildRecordAttributesFromGettersList(recordClassContent, getterMap, gettersList, allAnnotatedElements); + + recordClassContent.append(") {\n\n"); + + // buildRecordCustom1ArgConstructor(recordClassContent, simpleClassName, gettersList, allAnnotatedElements); + + recordClassContent.append("}"); + + return recordClassContent.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/org/jisel/SealForProfile.java b/src/main/java/org/jisel/SealForProfile.java new file mode 100644 index 0000000..9a9a628 --- /dev/null +++ b/src/main/java/org/jisel/SealForProfile.java @@ -0,0 +1,21 @@ +package org.jisel; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.METHOD}) +@Repeatable(SealForProfile.SealForProfiless.class) +public @interface SealForProfile { + + String value(); + + @Retention(RetentionPolicy.SOURCE) + @Target(ElementType.METHOD) + @interface SealForProfiless { + SealForProfile[] value(); + } +} \ No newline at end of file diff --git a/src/main/java/org/jisel/SealForProfiles.java b/src/main/java/org/jisel/SealForProfiles.java new file mode 100644 index 0000000..93294c9 --- /dev/null +++ b/src/main/java/org/jisel/SealForProfiles.java @@ -0,0 +1,12 @@ +package org.jisel; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.METHOD}) +public @interface SealForProfiles { + String[] value(); +} \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java b/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java new file mode 100644 index 0000000..b426f83 --- /dev/null +++ b/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java @@ -0,0 +1,4 @@ +package org.jisel.generator; + +public class SealedInterfaceSourceFileGenerator { +} diff --git a/src/main/java/org/jisel/generator/helpers/CodeGenerator.java b/src/main/java/org/jisel/generator/helpers/CodeGenerator.java new file mode 100644 index 0000000..66b0ea5 --- /dev/null +++ b/src/main/java/org/jisel/generator/helpers/CodeGenerator.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2021 Mohamed Ashraf Bayor + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.jisel.generator.helpers; + +import java.util.Map; + +/** + * Exposes contract for a CodeGenerator class to fulfill + */ +public sealed interface CodeGenerator permits JavaxGeneratedGenerator { + + // List of the parameters expected in the params Map object of the generateCode method: + + /** + * parameter name: "qualifiedClassName", expected type: String + */ + String QUALIFIED_CLASS_NAME = "qualifiedClassName"; + + /** + * parameter name: "fieldName", expected type: String + */ + String FIELD_NAME = "fieldName"; + + /** + * parameter name: "getterReturnType", expected type: String + */ + String GETTER_RETURN_TYPE = "getterReturnType"; + + /** + * parameter name: "getterAsString", expected type: String + */ + String GETTER_AS_STRING = "getterAsString"; + + /** + * parameter name: "gettersList", expected type: List<? extends javax.lang.model.element.Element>, ex:[getLastname(), getAge(), getMark(), getGrade(), getSchool()] + */ + String GETTERS_LIST = "gettersList"; + + /** + * parameter name: "gettersMap", expected type: Map<String, String>, ex: {getAge=int, getSchool=org.froporec.data1.School, getLastname=java.lang.String} + */ + String GETTERS_MAP = "gettersMap"; + + /** + * Generates piece of code requested, based on the parameters provided in the params object and appends it to the provided recordClassContent param + * @param recordClassContent Stringbuilder object containing the record class code being generated + * @param params expected parameters. restricted to what is expected by the implementing class. the expected parameters names are defined as constants in the CodeGenerator interface. + */ + void generateCode(StringBuilder recordClassContent, Map params); +} \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/helpers/ExtendsGenerator.java b/src/main/java/org/jisel/generator/helpers/ExtendsGenerator.java new file mode 100644 index 0000000..21bfa1c --- /dev/null +++ b/src/main/java/org/jisel/generator/helpers/ExtendsGenerator.java @@ -0,0 +1,4 @@ +package org.jisel.generator.helpers; + +public final class ExtendsGenerator { +} diff --git a/src/main/java/org/jisel/generator/helpers/JavaxGeneratedGenerator.java b/src/main/java/org/jisel/generator/helpers/JavaxGeneratedGenerator.java new file mode 100644 index 0000000..1d5eedf --- /dev/null +++ b/src/main/java/org/jisel/generator/helpers/JavaxGeneratedGenerator.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2021 Mohamed Ashraf Bayor + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.jisel.generator.helpers; + +import org.jisel.JiselAnnotationProcessor; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Map; +import java.util.Properties; + +import static java.lang.String.format; + +/** + * Generates the @javax.annotation.processing.Generated annotation section at the top of the generated record class with the attributes: value, date and comments
+ * The generateRecord() method params map is not required + */ +public final class JavaxGeneratedGenerator implements CodeGenerator { + + private static final String DEFAULT_APP_VERSION = "1.0.0"; + + private void buildGeneratedAnnotationSection(final StringBuilder recordClassContent) { + recordClassContent.append(format(""" + @javax.annotation.processing.Generated( + value = "%s", + date = "%s", + comments = "version: %s" + ) + """ + , JiselAnnotationProcessor.class.getName() + , ZonedDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) + , getAppVersion() + )); + } + + private String getAppVersion() { + var properties = new Properties(); + var in = this.getClass().getClassLoader().getResourceAsStream("application.properties"); + try { + properties.load(in); + } catch (IOException e) { + return DEFAULT_APP_VERSION; + } + return properties.getProperty("info.app.version"); + } + + @Override + public void generateCode(final StringBuilder recordClassContent, final Map params) { + buildGeneratedAnnotationSection(recordClassContent); + } +} \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/helpers/MethodsGenerator.java b/src/main/java/org/jisel/generator/helpers/MethodsGenerator.java new file mode 100644 index 0000000..be5eecb --- /dev/null +++ b/src/main/java/org/jisel/generator/helpers/MethodsGenerator.java @@ -0,0 +1,4 @@ +package org.jisel.generator.helpers; + +public final class MethodsGenerator { +} diff --git a/src/main/java/org/jisel/generator/helpers/PermitsGenerator.java b/src/main/java/org/jisel/generator/helpers/PermitsGenerator.java new file mode 100644 index 0000000..529feba --- /dev/null +++ b/src/main/java/org/jisel/generator/helpers/PermitsGenerator.java @@ -0,0 +1,4 @@ +package org.jisel.generator.helpers; + +public final class PermitsGenerator { +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..3c4359c --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,5 @@ +info.app.name = Jisel +info.app.version = 1.0 +info.app.description = Interface Segregation Library for Java 17 +info.app.contact.name = Mohamed Ashraf Bayor +info.app.contact.url = https://jisel.org/ \ No newline at end of file