From aa8ffe8b35a0b7595ee331505ebad4ad552fed63 Mon Sep 17 00:00:00 2001 From: Mohamed Ashraf Bayor Date: Mon, 6 Dec 2021 18:59:49 -0500 Subject: [PATCH 01/11] JIS-01: initial commit jisel 1.0 --- src/main/java/org/jisel/AddToProfile.java | 4 +- .../org/jisel/JiselAnnotationProcessor.java | 114 +++++------ src/main/java/org/jisel/SealForProfile.java | 4 +- .../jisel/handlers/AddToProfileHandler.java | 54 +++++ .../handlers/JiselAnnotationHandler.java | 185 ++++++++++++++++++ .../jisel/handlers/SealForProfileHandler.java | 141 +++++++++++++ 6 files changed, 430 insertions(+), 72 deletions(-) create mode 100644 src/main/java/org/jisel/handlers/AddToProfileHandler.java create mode 100644 src/main/java/org/jisel/handlers/JiselAnnotationHandler.java create mode 100644 src/main/java/org/jisel/handlers/SealForProfileHandler.java diff --git a/src/main/java/org/jisel/AddToProfile.java b/src/main/java/org/jisel/AddToProfile.java index 81bd070..617cc03 100644 --- a/src/main/java/org/jisel/AddToProfile.java +++ b/src/main/java/org/jisel/AddToProfile.java @@ -8,14 +8,14 @@ @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE}) -@Repeatable(AddToProfile.AddToProfiless.class) +@Repeatable(AddToProfile.AddToProfilez.class) public @interface AddToProfile { String value(); @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) - @interface AddToProfiless { + @interface AddToProfilez { AddToProfile[] 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 index 89890cf..0bc14c7 100644 --- a/src/main/java/org/jisel/JiselAnnotationProcessor.java +++ b/src/main/java/org/jisel/JiselAnnotationProcessor.java @@ -1,6 +1,9 @@ package org.jisel; import com.google.auto.service.AutoService; +import org.jisel.handlers.AddToProfileHandler; +import org.jisel.handlers.JiselAnnotationHandler; +import org.jisel.handlers.SealForProfileHandler; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Processor; @@ -9,95 +12,70 @@ 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.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Logger; -// @Slf4j -@SupportedAnnotationTypes("com.bayor.jisel.annotation.SealFor") +import static java.util.Collections.unmodifiableMap; +import static java.util.Collections.unmodifiableSet; + +@SupportedAnnotationTypes({"org.jisel.SealForProfile", "org.jisel.SealForProfiles", "org.jisel.SealForProfile.SealForProfilez", + "org.jisel.AddToProfile", "org.jisel.AddToProfiles", "org.jisel.AddToProfile.AddToProfilez"}) @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); + private final Logger log = Logger.getLogger(JiselAnnotationProcessor.class.getName()); - List annotatedClasses = annotatedElements.stream() - //.filter(element -> element.getClass().getClassLoader().isRecord()) - .filter(element -> ElementKind.INTERFACE.equals(element.getKind())) - .toList(); + private static final String SEAL_FOR_PROFILE = "SealForProfile"; + private static final String ADD_TO_PROFILE = "AddToProfile"; - System.out.println(">>>>>>>> annotatedClasses >>>>>>>>>>>" + annotatedClasses); + private JiselAnnotationHandler sealForProfileHandler; - List annotatedMethods = annotatedElements.stream() - .filter(element -> !element.getClass().isRecord()) - .filter(element -> ElementKind.METHOD.equals(element.getKind())) - .toList(); + private JiselAnnotationHandler addToProfileHandler; - System.out.println(">>>>>>>> annotateMethods >>>>>>>>>>>" + annotatedMethods); - } - - return true; + public JiselAnnotationProcessor() { + this.sealForProfileHandler = new SealForProfileHandler(); + this.addToProfileHandler = new AddToProfileHandler(); } - 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); + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + + var allAnnotatedSealForProfileElements = new HashSet(); + var allAnnotatedAddToProfileElements = new HashSet(); + var sealedInterfacesToGenerate = new HashMap>>(); + var sealedInterfacesPermits = new HashMap>>(); + + for (var annotation : annotations) { + if (annotation.getSimpleName().toString().contains(SEAL_FOR_PROFILE)) { + allAnnotatedSealForProfileElements.addAll(roundEnv.getElementsAnnotatedWith(annotation)); + } + if (annotation.getSimpleName().toString().contains(ADD_TO_PROFILE)) { + allAnnotatedAddToProfileElements.addAll(roundEnv.getElementsAnnotatedWith(annotation)); + } } - } - 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); + // process all annotated interface methods annotated with @SealForProfile + var statusReport = sealForProfileHandler.handleAnnotatedElements(processingEnv, unmodifiableSet(allAnnotatedSealForProfileElements), sealedInterfacesToGenerate, sealedInterfacesPermits); + if (!statusReport.isEmpty()) { + // TODO output log - create priv meth browse the map and output report per interface } + // TODO sealedInterfacesPermits here shld be getting only qlfd names of child classes - 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"); + // process all annotated child class or interfaces annotated with @AddToProfile + statusReport = addToProfileHandler.handleAnnotatedElements(processingEnv, unmodifiableSet(allAnnotatedAddToProfileElements), unmodifiableMap(sealedInterfacesToGenerate), sealedInterfacesPermits); + if (!statusReport.isEmpty()) { + // output log - create priv meth } - recordClassContent.append("public record "); - recordClassContent.append(recordSimpleClassName); - recordClassContent.append("("); +// System.out.println(" \n\n@@@@@@@@@@@@@ sealedInterfacesToGenerate: " + sealedInterfacesToGenerate); +// System.out.println("\n~~~~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~ \n sealedInterfacesPermits" + sealedInterfacesPermits); -// // System.out.println("################################" + recordSimpleClassName); -// buildRecordAttributesFromGettersList(recordClassContent, getterMap, gettersList, allAnnotatedElements); - - recordClassContent.append(") {\n\n"); - - // buildRecordCustom1ArgConstructor(recordClassContent, simpleClassName, gettersList, allAnnotatedElements); - - recordClassContent.append("}"); - - return recordClassContent.toString(); + return true; } } \ No newline at end of file diff --git a/src/main/java/org/jisel/SealForProfile.java b/src/main/java/org/jisel/SealForProfile.java index 9a9a628..761093c 100644 --- a/src/main/java/org/jisel/SealForProfile.java +++ b/src/main/java/org/jisel/SealForProfile.java @@ -8,14 +8,14 @@ @Retention(RetentionPolicy.SOURCE) @Target({ElementType.METHOD}) -@Repeatable(SealForProfile.SealForProfiless.class) +@Repeatable(SealForProfile.SealForProfilez.class) public @interface SealForProfile { String value(); @Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) - @interface SealForProfiless { + @interface SealForProfilez { SealForProfile[] value(); } } \ No newline at end of file diff --git a/src/main/java/org/jisel/handlers/AddToProfileHandler.java b/src/main/java/org/jisel/handlers/AddToProfileHandler.java new file mode 100644 index 0000000..484d4a6 --- /dev/null +++ b/src/main/java/org/jisel/handlers/AddToProfileHandler.java @@ -0,0 +1,54 @@ +package org.jisel.handlers; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public final class AddToProfileHandler implements JiselAnnotationHandler { + + @Override + public Map handleAnnotatedElements(ProcessingEnvironment processingEnv, + Set allAnnotatedElements, + Map>> sealedInterfacesToGenerate, + Map>> sealedInterfacesPermits) { + // handle child interfaces as well not just classes + return Map.of(); + } + + private void processAnnotatedChildrenClassesOrInterfaces(final Set allAnnotatedElements) { + var annotatedClasses = allAnnotatedElements.stream() + .filter(element -> !element.getClass().isEnum()) + .filter(element -> ElementKind.CLASS.equals(element.getKind()) || ElementKind.INTERFACE.equals(element.getKind()) || ElementKind.RECORD.equals(element.getKind())) + //.map(element -> processingEnv.getTypeUtils().asElement(element.asType())) + .collect(Collectors.toSet()); + // List annotatedClasses = annotatedElements.stream() +// .filter(element -> ElementKind.INTERFACE.equals(element.getKind()) || ElementKind.INTERFACE.equals(element.getKind()) || ElementKind.RECORD.equals(element.getKind())) +// //TODO here accept both classes and interfaces to use @AddToProfile +// .toList(); + annotatedClasses.forEach(annotatedClass -> processAnnotatedElement(annotatedClass, allAnnotatedElements)); + } + + private void processAnnotatedElement(final Element annotatedElement, final Set allAnnotatedElements) { +// processingEnv.getElementUtils().getAllAnnotationMirrors(annotatedElement).forEach(annotationMirror -> { +// annotationMirror.getElementValues().entrySet().forEach(k -> System.out.println("#####k: " + k.getKey() + ", #####v: " + k.getValue().toString())); +// }); +// var gettersList = processingEnv.getElementUtils().getAllMembers((TypeElement) processingEnv.getTypeUtils().asElement(annotatedElement.asType())).stream() +// .filter(element -> element.getSimpleName().toString().startsWith("get") || element.getSimpleName().toString().startsWith("is")) +// .filter(element -> !element.getSimpleName().toString().startsWith("getClass")) +// .toList(); +// var qualifiedClassName = ((TypeElement) processingEnv.getTypeUtils().asElement(annotatedElement.asType())).getQualifiedName().toString(); +// var gettersMap = gettersList.stream().collect(Collectors.toMap(getter -> getter.getSimpleName().toString(), getter -> ((ExecutableType) getter.asType()).getReturnType().toString())); +// try { +// new RecordSourceFileGenerator(processingEnv, allAnnotatedElements).writeRecordSourceFile(qualifiedClassName, gettersList, gettersMap); +// log.info(() -> "\t> Successfully generated " + qualifiedClassName + "Record"); +// } catch (FilerException e) { +// // Skipped generating " + qualifiedClassName + "Record - file already exists" +// } catch (IOException e) { +// log.log(Level.SEVERE, format("Error generating %sRecord", qualifiedClassName), e); +// } + } +} \ No newline at end of file diff --git a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java new file mode 100644 index 0000000..e9fe409 --- /dev/null +++ b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java @@ -0,0 +1,185 @@ +package org.jisel.handlers; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; +import static java.util.stream.Stream.concat; + +public sealed interface JiselAnnotationHandler permits SealForProfileHandler, AddToProfileHandler, AnnotationInfoCollectionHandler, UniqueParentInterfaceHandler, ParentChildInheritanceHandler { + + String SEPARATOR = ","; + + Map handleAnnotatedElements(ProcessingEnvironment processingEnv, + Set allAnnotatedElements, + Map>> sealedInterfacesToGenerate, + Map>> sealedInterfacesPermits); +} + +sealed interface AnnotationInfoCollectionHandler extends JiselAnnotationHandler permits SealForProfileInfoCollectionHandler { + + void populateSealedInterfacesMap(ProcessingEnvironment processingEnv, + Set allAnnotatedElements, + Map>> sealedInterfacesToGenerate); + + default void createParentInterfacesBasedOnCommonMethods(final Map>> annotatedMethodsByProfileByInterface, + final Map>> sealedInterfacesToGenerate) { + annotatedMethodsByProfileByInterface.forEach((interfaceElement, annotatedMethodsByProfile) -> { + var profilesList = new ArrayList(); + var methodsSetsList = new ArrayList>(); + annotatedMethodsByProfile.forEach((profileName, methodsSet) -> { + profilesList.add(profileName); + methodsSetsList.add(methodsSet); + }); + var totalProfiles = profilesList.size(); + for (int i = 0; i < totalProfiles - 1; i++) { + var allProcessedCommonMethodsByConcatenatedProfiles = concatenateProfilesBasedOnCommonMethods(profilesList.get(i), profilesList, methodsSetsList); + // remove all commonMethodElmnts 1 by 1 and in each profile + allProcessedCommonMethodsByConcatenatedProfiles.values().stream().flatMap(Collection::stream).collect(toSet()).forEach(method -> { + methodsSetsList.forEach(methodsSets -> methodsSets.remove(method)); + annotatedMethodsByProfileByInterface.get(interfaceElement).forEach((profileName, methodsSets) -> methodsSets.remove(method)); + }); + // + if (!sealedInterfacesToGenerate.containsKey(interfaceElement)) { + sealedInterfacesToGenerate.put(interfaceElement, new HashMap<>()); + } + sealedInterfacesToGenerate.get(interfaceElement).putAll(allProcessedCommonMethodsByConcatenatedProfiles); + sealedInterfacesToGenerate.get(interfaceElement).putAll(annotatedMethodsByProfileByInterface.get(interfaceElement)); + } + }); + } + + private Map> concatenateProfilesBasedOnCommonMethods(final String processProfileName, final List profilesList, List> methodsSetsList) { + var allProcessedCommonMethodsByConcatenatedProfiles = new HashMap>(); + var totalProfiles = profilesList.size(); + var processProfileIndex = profilesList.indexOf(processProfileName); + for (var methodElement : methodsSetsList.get(processProfileIndex)) { + var concatenatedProfiles = new StringBuilder(processProfileName); + var found = false; + for (int j = processProfileIndex + 1; j < totalProfiles; j++) { + if (methodsSetsList.get(j).contains(methodElement)) { + concatenatedProfiles.append(SEPARATOR).append(profilesList.get(j)); + found = true; + } + } + if (found) { + allProcessedCommonMethodsByConcatenatedProfiles.merge( + concatenatedProfiles.toString(), + new HashSet<>(Set.of(methodElement)), + (currentSet, newSet) -> concat(currentSet.stream(), newSet.stream()).collect(toSet()) + ); + } + } + return allProcessedCommonMethodsByConcatenatedProfiles; + } + + @Override + default Map handleAnnotatedElements(final ProcessingEnvironment processingEnv, + final Set allAnnotatedElements, + final Map>> sealedInterfacesToGenerate, + final Map>> sealedInterfacesPermits) { + populateSealedInterfacesMap(processingEnv, allAnnotatedElements, sealedInterfacesToGenerate); + return Map.of(); // return empty map instead of null + } +} + +sealed interface ParentChildInheritanceHandler extends JiselAnnotationHandler permits SealForProfileParentChildInheritanceHandler { + + void buildInheritanceRelations(Map>> sealedInterfacesToGenerate, + Map>> sealedInterfacesPermits); + + default void buildSealedInterfacesPermitsMap(final Element interfaceElement, + final Map>> sealedInterfacesToGenerate, + final Map>> sealedInterfacesPermits) { + sealedInterfacesPermits.get(interfaceElement).putAll(sealedInterfacesToGenerate.get(interfaceElement).keySet().stream() + .filter(profiles -> profiles.contains(SEPARATOR)) + .collect(toMap(profiles -> profiles, profiles -> asList(profiles.split(SEPARATOR))))); + // parent interf permits all, if there are other permits already existing then the profiles in those permits lists should be removed from parent interf permits list + var parentInterfaceSimpleName = interfaceElement.getSimpleName().toString(); + var allPermittedProfiles = sealedInterfacesPermits.get(interfaceElement).values().stream().flatMap(Collection::stream).collect(toSet()); + sealedInterfacesPermits.get(interfaceElement).put( + parentInterfaceSimpleName, + sealedInterfacesToGenerate.get(interfaceElement).keySet().stream() + .filter(profile -> !parentInterfaceSimpleName.equals(profile)) + .filter(profile -> !allPermittedProfiles.contains(profile)) + .toList() + ); + } + + @Override + default Map handleAnnotatedElements(final ProcessingEnvironment processingEnv, + final Set allAnnotatedElements, + final Map>> sealedInterfacesToGenerate, + final Map>> sealedInterfacesPermits) { + buildInheritanceRelations(sealedInterfacesToGenerate, sealedInterfacesPermits); + return Map.of(); + } +} + +sealed interface UniqueParentInterfaceHandler extends JiselAnnotationHandler permits SealForProfileUniqueParentInterfaceHandler { + + String REPORT_MSG = "More than 1 Parent Sealed Interfaces will be generated. Check your profiles mapping"; + + default Map> checkUniqueParentInterfacePresence(final Map>> sealedInterfacesToGenerate) { + + var providedProfilesListByInterface = new HashMap>(); + sealedInterfacesToGenerate.forEach( + (interfaceElement, annotatedMethodsByProfile) -> annotatedMethodsByProfile.keySet().stream() + .filter(concatenatedProfiles -> !concatenatedProfiles.contains(SEPARATOR)).forEach( + profileName -> providedProfilesListByInterface.merge( + interfaceElement, + asList(profileName), + (currentList, newList) -> concat(currentList.stream(), newList.stream()).toList()) + ) + ); + + var uniqueParentInterfaceByInterface = new HashMap>(); + + providedProfilesListByInterface.forEach((interfaceElement, profilesList) -> { + // if only 1 key of sealedInterfacesToGenerate map contains all of the profiles names and its total length = all profiles names lenghts combined -> use fat interface name for that key and move on + var totalProfilesNamesLengths = profilesList.stream().mapToInt(String::length).sum(); + var longestConcatenedProfilesStringOpt = sealedInterfacesToGenerate.get(interfaceElement).keySet().stream() + .sorted(Comparator.comparingInt(String::length).reversed()).findFirst(); + if (longestConcatenedProfilesStringOpt.isPresent()) { + var foundUniqueParentInterface = false; + for (var profileName : profilesList) { + foundUniqueParentInterface = removeSeparator(longestConcatenedProfilesStringOpt.get()).contains(profileName); + } + uniqueParentInterfaceByInterface.put( + interfaceElement, + foundUniqueParentInterface && removeSeparator(longestConcatenedProfilesStringOpt.get()).length() == totalProfilesNamesLengths + ? Optional.of(longestConcatenedProfilesStringOpt.get()) + : Optional.empty() + ); + } + }); + + return uniqueParentInterfaceByInterface; + } + + private String removeSeparator(final String text) { + return asList(text.split(SEPARATOR)).stream().collect(joining()); + } + + Map checkAndHandleUniqueParentInterface(Map>> sealedInterfacesToGenerate); + + @Override + default Map handleAnnotatedElements(final ProcessingEnvironment processingEnv, + final Set allAnnotatedElements, + final Map>> sealedInterfacesToGenerate, + final Map>> sealedInterfacesPermits) { + return checkAndHandleUniqueParentInterface(sealedInterfacesToGenerate); + } +} \ No newline at end of file diff --git a/src/main/java/org/jisel/handlers/SealForProfileHandler.java b/src/main/java/org/jisel/handlers/SealForProfileHandler.java new file mode 100644 index 0000000..d19384a --- /dev/null +++ b/src/main/java/org/jisel/handlers/SealForProfileHandler.java @@ -0,0 +1,141 @@ +package org.jisel.handlers; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Pattern; + +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toSet; +import static java.util.stream.Stream.concat; + +public final class SealForProfileHandler implements JiselAnnotationHandler { + + @Override + public Map handleAnnotatedElements(final ProcessingEnvironment processingEnv, + final Set allAnnotatedElements, + final Map>> sealedInterfacesToGenerate, + final Map>> sealedInterfacesPermits) { + new SealForProfileInfoCollectionHandler().populateSealedInterfacesMap(processingEnv, allAnnotatedElements, sealedInterfacesToGenerate); + var statusReport = new SealForProfileUniqueParentInterfaceHandler().checkAndHandleUniqueParentInterface(sealedInterfacesToGenerate); + new SealForProfileParentChildInheritanceHandler().buildInheritanceRelations(sealedInterfacesToGenerate, sealedInterfacesPermits); + System.out.println(" \n\n>>>$$>>>>>>$$$>>>>$$>>>>> sealedInterfacesPermits: " + sealedInterfacesPermits); + System.out.println(" \n\n>>>>>>>>>>>>>>>>>> sealedInterfacesToGenerate: " + sealedInterfacesToGenerate); + return statusReport; + } +} + +final class SealForProfileInfoCollectionHandler implements AnnotationInfoCollectionHandler { + + @Override + public void populateSealedInterfacesMap(final ProcessingEnvironment processingEnv, + final Set allAnnotatedElements, + final Map>> sealedInterfacesToGenerate) { + + var annotatedMethodsByInterface = allAnnotatedElements.stream() + .filter(element -> ElementKind.METHOD.equals(element.getKind())) + .filter(element -> ElementKind.INTERFACE.equals(element.getEnclosingElement().getKind())) + .collect(groupingBy(Element::getEnclosingElement, toSet())); + + if (annotatedMethodsByInterface.isEmpty()) { + return; + } + + var annotatedMethodsByProfileByInterface = new HashMap>>(); + annotatedMethodsByInterface.forEach( + (interfaceElement, annotatedMethodsElements) -> annotatedMethodsElements.forEach( + annotatedMethod -> processingEnv.getElementUtils().getAllAnnotationMirrors(annotatedMethod).forEach( + annotationMirror -> annotationMirror.getElementValues().entrySet().forEach( + entry -> extractProfilesAndPopulateMaps(interfaceElement, entry.getValue().toString(), annotatedMethod, annotatedMethodsByProfileByInterface) + ) + ) + ) + ); + + createParentInterfacesBasedOnCommonMethods(annotatedMethodsByProfileByInterface, sealedInterfacesToGenerate); + } + + // annotationValueAsString: sample values: singl value "profile1name", array: {@org.jisel.SealForProfile("profile2name"), @org.jisel.SealForProfile("profile3name"),...} + private void extractProfilesAndPopulateMaps(final Element interfaceElement, + final String annotationValueAsString, + final Element annotatedMethod, + final Map>> annotatedMethodsByProfileByInterface) { + var matcher = Pattern.compile("\"([^\"]*)\"").matcher(annotationValueAsString); + while (matcher.find()) { + var profile = matcher.group(1).trim(); + if (profile.isBlank()) { + continue; + } + if (annotatedMethodsByProfileByInterface.containsKey(interfaceElement)) { + annotatedMethodsByProfileByInterface.get(interfaceElement).merge( + profile, + new HashSet<>(Set.of(annotatedMethod)), + (currentSet, newSet) -> concat(currentSet.stream(), newSet.stream()).collect(toSet()) + ); + } else { + annotatedMethodsByProfileByInterface.put(interfaceElement, new HashMap<>(Map.of(profile, new HashSet<>(Set.of(annotatedMethod))))); + } + } + } +} + +final class SealForProfileParentChildInheritanceHandler implements ParentChildInheritanceHandler { + + @Override + public void buildInheritanceRelations(final Map>> sealedInterfacesToGenerate, + final Map>> sealedInterfacesPermits) { + + sealedInterfacesToGenerate.keySet().forEach(interfaceElement -> { + + sealedInterfacesPermits.put(interfaceElement, new HashMap<>()); // start with initializing sealedInterfacesPermits with empty mutable maps + + // promote profiles with empty methods to parent level + var allProfilesToRemove = new HashSet(); + sealedInterfacesToGenerate.get(interfaceElement).keySet().forEach(concatenatedProfiles -> { + var profilesArray = concatenatedProfiles.split(SEPARATOR); + if (profilesArray.length > 1) { + for (var profile : profilesArray) { + var profileMethodsOpt = Optional.ofNullable(sealedInterfacesToGenerate.get(interfaceElement).get(profile)); + if (profileMethodsOpt.isPresent() && profileMethodsOpt.get().isEmpty()) { + sealedInterfacesToGenerate.get(interfaceElement).put(profile, sealedInterfacesToGenerate.get(interfaceElement).get(concatenatedProfiles)); + sealedInterfacesPermits.get(interfaceElement).put(profile, Arrays.stream(profilesArray).filter(profileName -> !profile.equals(profileName)).toList()); + allProfilesToRemove.add(concatenatedProfiles); + break; + } + } + } + }); + + allProfilesToRemove.forEach(sealedInterfacesToGenerate.get(interfaceElement)::remove); + + // and completing building sealedInterfacesPermits map + buildSealedInterfacesPermitsMap(interfaceElement, sealedInterfacesToGenerate, sealedInterfacesPermits); + }); + } +} + +final class SealForProfileUniqueParentInterfaceHandler implements UniqueParentInterfaceHandler { + + @Override + public Map checkAndHandleUniqueParentInterface(final Map>> sealedInterfacesToGenerate) { + var statusReport = new HashMap(); + Map> longestConcatenedProfilesStringOptByInterface = checkUniqueParentInterfacePresence(sealedInterfacesToGenerate); + sealedInterfacesToGenerate.keySet().forEach(interfaceElement -> { + var longestConcatenedProfilesStringOpt = longestConcatenedProfilesStringOptByInterface.get(interfaceElement); + if (longestConcatenedProfilesStringOptByInterface.containsKey(interfaceElement) && longestConcatenedProfilesStringOpt.isPresent()) { + sealedInterfacesToGenerate.get(interfaceElement).put(interfaceElement.getSimpleName().toString(), sealedInterfacesToGenerate.get(interfaceElement).get(longestConcatenedProfilesStringOpt.get())); + sealedInterfacesToGenerate.get(interfaceElement).remove(longestConcatenedProfilesStringOpt.get()); + } else { + statusReport.put(interfaceElement, REPORT_MSG); + } + }); + return statusReport; + } +} \ No newline at end of file From b486d58b92399b551cd1728d534ab6d139c6c24e Mon Sep 17 00:00:00 2001 From: Mohamed Ashraf Bayor Date: Wed, 8 Dec 2021 10:59:26 -0500 Subject: [PATCH 02/11] JIS-01: finished sealforprofilehandler and addtoprofilehandler. about to start codegenerator classes dev --- src/main/java/org/jisel/AddToProfile.java | 21 +++ src/main/java/org/jisel/AddToProfiles.java | 21 +++ .../org/jisel/JiselAnnotationProcessor.java | 39 ++++- src/main/java/org/jisel/SealForProfile.java | 21 +++ src/main/java/org/jisel/SealForProfiles.java | 21 +++ .../generator/JiselFinalClassGenerator.java | 4 + .../jisel/generator/JiselReportGenerator.java | 4 + .../SealedInterfaceSourceFileGenerator.java | 21 +++ .../generator/helpers/CodeGenerator.java | 2 +- .../generator/helpers/ExtendsGenerator.java | 21 +++ .../helpers/JavaxGeneratedGenerator.java | 2 +- .../generator/helpers/MethodsGenerator.java | 21 +++ .../generator/helpers/PermitsGenerator.java | 21 +++ .../jisel/handlers/AddToProfileHandler.java | 141 +++++++++++++----- .../handlers/JiselAnnotationHandler.java | 86 ++++++++--- .../jisel/handlers/SealForProfileHandler.java | 87 ++++++----- 16 files changed, 429 insertions(+), 104 deletions(-) create mode 100644 src/main/java/org/jisel/generator/JiselFinalClassGenerator.java create mode 100644 src/main/java/org/jisel/generator/JiselReportGenerator.java diff --git a/src/main/java/org/jisel/AddToProfile.java b/src/main/java/org/jisel/AddToProfile.java index 617cc03..48312a8 100644 --- a/src/main/java/org/jisel/AddToProfile.java +++ b/src/main/java/org/jisel/AddToProfile.java @@ -1,3 +1,24 @@ +/** + * Copyright (c) 2021-2022 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; import java.lang.annotation.ElementType; diff --git a/src/main/java/org/jisel/AddToProfiles.java b/src/main/java/org/jisel/AddToProfiles.java index c89af8b..a884297 100644 --- a/src/main/java/org/jisel/AddToProfiles.java +++ b/src/main/java/org/jisel/AddToProfiles.java @@ -1,3 +1,24 @@ +/** + * Copyright (c) 2021-2022 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; import java.lang.annotation.ElementType; diff --git a/src/main/java/org/jisel/JiselAnnotationProcessor.java b/src/main/java/org/jisel/JiselAnnotationProcessor.java index 0bc14c7..4ff9a2a 100644 --- a/src/main/java/org/jisel/JiselAnnotationProcessor.java +++ b/src/main/java/org/jisel/JiselAnnotationProcessor.java @@ -1,3 +1,24 @@ +/** + * Copyright (c) 2021-2022 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; import com.google.auto.service.AutoService; @@ -22,6 +43,7 @@ import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableSet; +import static java.util.stream.Collectors.joining; @SupportedAnnotationTypes({"org.jisel.SealForProfile", "org.jisel.SealForProfiles", "org.jisel.SealForProfile.SealForProfilez", "org.jisel.AddToProfile", "org.jisel.AddToProfiles", "org.jisel.AddToProfile.AddToProfilez"}) @@ -48,8 +70,8 @@ public boolean process(Set annotations, RoundEnvironment var allAnnotatedSealForProfileElements = new HashSet(); var allAnnotatedAddToProfileElements = new HashSet(); - var sealedInterfacesToGenerate = new HashMap>>(); - var sealedInterfacesPermits = new HashMap>>(); + var sealedInterfacesToGenerateByBloatedInterface = new HashMap>>(); + var sealedInterfacesPermitsByBloatedInterface = new HashMap>>(); for (var annotation : annotations) { if (annotation.getSimpleName().toString().contains(SEAL_FOR_PROFILE)) { @@ -61,20 +83,21 @@ public boolean process(Set annotations, RoundEnvironment } // process all annotated interface methods annotated with @SealForProfile - var statusReport = sealForProfileHandler.handleAnnotatedElements(processingEnv, unmodifiableSet(allAnnotatedSealForProfileElements), sealedInterfacesToGenerate, sealedInterfacesPermits); + var statusReport = sealForProfileHandler.handleAnnotatedElements(processingEnv, unmodifiableSet(allAnnotatedSealForProfileElements), sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface); if (!statusReport.isEmpty()) { // TODO output log - create priv meth browse the map and output report per interface } - // TODO sealedInterfacesPermits here shld be getting only qlfd names of child classes + // TODO sealedInterfacesPermitsByBloatedInterface here shld be getting only qlfd names of child classes // process all annotated child class or interfaces annotated with @AddToProfile - statusReport = addToProfileHandler.handleAnnotatedElements(processingEnv, unmodifiableSet(allAnnotatedAddToProfileElements), unmodifiableMap(sealedInterfacesToGenerate), sealedInterfacesPermits); - if (!statusReport.isEmpty()) { + statusReport = addToProfileHandler.handleAnnotatedElements(processingEnv, unmodifiableSet(allAnnotatedAddToProfileElements), unmodifiableMap(sealedInterfacesToGenerateByBloatedInterface), sealedInterfacesPermitsByBloatedInterface); + if (!statusReport.values().stream().collect(joining()).isBlank()) { // output log - create priv meth + log.warning("\t>\t" + statusReport); } -// System.out.println(" \n\n@@@@@@@@@@@@@ sealedInterfacesToGenerate: " + sealedInterfacesToGenerate); -// System.out.println("\n~~~~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~ \n sealedInterfacesPermits" + sealedInterfacesPermits); + System.out.println(" \n\n@@@@@@@@@@@@@ sealedInterfacesToGenerateByBloatedInterface: " + sealedInterfacesToGenerateByBloatedInterface); + System.out.println("\n~~~~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~ \n sealedInterfacesPermitsByBloatedInterface" + sealedInterfacesPermitsByBloatedInterface); return true; } diff --git a/src/main/java/org/jisel/SealForProfile.java b/src/main/java/org/jisel/SealForProfile.java index 761093c..7160db6 100644 --- a/src/main/java/org/jisel/SealForProfile.java +++ b/src/main/java/org/jisel/SealForProfile.java @@ -1,3 +1,24 @@ +/** + * Copyright (c) 2021-2022 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; import java.lang.annotation.ElementType; diff --git a/src/main/java/org/jisel/SealForProfiles.java b/src/main/java/org/jisel/SealForProfiles.java index 93294c9..6384fa4 100644 --- a/src/main/java/org/jisel/SealForProfiles.java +++ b/src/main/java/org/jisel/SealForProfiles.java @@ -1,3 +1,24 @@ +/** + * Copyright (c) 2021-2022 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; import java.lang.annotation.ElementType; diff --git a/src/main/java/org/jisel/generator/JiselFinalClassGenerator.java b/src/main/java/org/jisel/generator/JiselFinalClassGenerator.java new file mode 100644 index 0000000..de18f56 --- /dev/null +++ b/src/main/java/org/jisel/generator/JiselFinalClassGenerator.java @@ -0,0 +1,4 @@ +package org.jisel.generator; + +public class JiselFinalClassGenerator { +} diff --git a/src/main/java/org/jisel/generator/JiselReportGenerator.java b/src/main/java/org/jisel/generator/JiselReportGenerator.java new file mode 100644 index 0000000..e58a3e4 --- /dev/null +++ b/src/main/java/org/jisel/generator/JiselReportGenerator.java @@ -0,0 +1,4 @@ +package org.jisel.generator; + +public class JiselReportGenerator { +} diff --git a/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java b/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java index b426f83..0a2f0a8 100644 --- a/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java +++ b/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java @@ -1,3 +1,24 @@ +/** + * Copyright (c) 2021-2022 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; 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 index 66b0ea5..36cc207 100644 --- a/src/main/java/org/jisel/generator/helpers/CodeGenerator.java +++ b/src/main/java/org/jisel/generator/helpers/CodeGenerator.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 Mohamed Ashraf Bayor + * Copyright (c) 2021-2022 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 diff --git a/src/main/java/org/jisel/generator/helpers/ExtendsGenerator.java b/src/main/java/org/jisel/generator/helpers/ExtendsGenerator.java index 21bfa1c..d3c27d4 100644 --- a/src/main/java/org/jisel/generator/helpers/ExtendsGenerator.java +++ b/src/main/java/org/jisel/generator/helpers/ExtendsGenerator.java @@ -1,3 +1,24 @@ +/** + * Copyright (c) 2021-2022 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; 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 index 1d5eedf..477ff6e 100644 --- a/src/main/java/org/jisel/generator/helpers/JavaxGeneratedGenerator.java +++ b/src/main/java/org/jisel/generator/helpers/JavaxGeneratedGenerator.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 Mohamed Ashraf Bayor + * Copyright (c) 2021-2022 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 diff --git a/src/main/java/org/jisel/generator/helpers/MethodsGenerator.java b/src/main/java/org/jisel/generator/helpers/MethodsGenerator.java index be5eecb..27cd355 100644 --- a/src/main/java/org/jisel/generator/helpers/MethodsGenerator.java +++ b/src/main/java/org/jisel/generator/helpers/MethodsGenerator.java @@ -1,3 +1,24 @@ +/** + * Copyright (c) 2021-2022 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; 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 index 529feba..8a985c6 100644 --- a/src/main/java/org/jisel/generator/helpers/PermitsGenerator.java +++ b/src/main/java/org/jisel/generator/helpers/PermitsGenerator.java @@ -1,3 +1,24 @@ +/** + * Copyright (c) 2021-2022 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; public final class PermitsGenerator { diff --git a/src/main/java/org/jisel/handlers/AddToProfileHandler.java b/src/main/java/org/jisel/handlers/AddToProfileHandler.java index 484d4a6..d4e0bba 100644 --- a/src/main/java/org/jisel/handlers/AddToProfileHandler.java +++ b/src/main/java/org/jisel/handlers/AddToProfileHandler.java @@ -1,54 +1,125 @@ +/** + * Copyright (c) 2021-2022 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.handlers; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; + +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toSet; +import static java.util.stream.Stream.concat; public final class AddToProfileHandler implements JiselAnnotationHandler { @Override public Map handleAnnotatedElements(ProcessingEnvironment processingEnv, - Set allAnnotatedElements, - Map>> sealedInterfacesToGenerate, - Map>> sealedInterfacesPermits) { - // handle child interfaces as well not just classes - return Map.of(); + Set allAnnotatedElements, + Map>> sealedInterfacesToGenerateByBloatedInterface, + Map>> sealedInterfacesPermitsByBloatedInterface) { + var annotatedClassesAndInterfaces = allAnnotatedElements.stream() + .filter(element -> !element.getClass().isEnum()) + .filter(element -> ElementKind.CLASS.equals(element.getKind()) + || ElementKind.INTERFACE.equals(element.getKind()) + || ElementKind.RECORD.equals(element.getKind())) + .collect(toSet()); + var statusReport = new HashMap(); + annotatedClassesAndInterfaces.forEach(annotatedClassOrInterface -> statusReport.put( + annotatedClassOrInterface, + processAnnotatedElement(processingEnv, annotatedClassOrInterface, sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface) + )); + return statusReport; } - private void processAnnotatedChildrenClassesOrInterfaces(final Set allAnnotatedElements) { - var annotatedClasses = allAnnotatedElements.stream() - .filter(element -> !element.getClass().isEnum()) - .filter(element -> ElementKind.CLASS.equals(element.getKind()) || ElementKind.INTERFACE.equals(element.getKind()) || ElementKind.RECORD.equals(element.getKind())) - //.map(element -> processingEnv.getTypeUtils().asElement(element.asType())) - .collect(Collectors.toSet()); - // List annotatedClasses = annotatedElements.stream() -// .filter(element -> ElementKind.INTERFACE.equals(element.getKind()) || ElementKind.INTERFACE.equals(element.getKind()) || ElementKind.RECORD.equals(element.getKind())) -// //TODO here accept both classes and interfaces to use @AddToProfile -// .toList(); - annotatedClasses.forEach(annotatedClass -> processAnnotatedElement(annotatedClass, allAnnotatedElements)); + private String processAnnotatedElement(final ProcessingEnvironment processingEnv, + final Element annotatedClassOrInterface, + final Map>> sealedInterfacesToGenerateByBloatedInterface, + final Map>> sealedInterfacesPermitsByBloatedInterface) { + var statusReport = new StringBuilder(); + var addToProfileProvidedProfilesSet = buildProvidedProfilesSet(processingEnv, annotatedClassOrInterface); + if (addToProfileProvidedProfilesSet.isEmpty()) { + // do not process if no profiles are provided + return statusReport.toString(); + } + var providedSuperInterfacesSet = buildProvidedInterfacesSet(processingEnv, annotatedClassOrInterface); + // browse sealedInterfacesToGenerate 1st and then add more info to sealedInterfacesPermits + var found = false; + for (var mapEntry : sealedInterfacesToGenerateByBloatedInterface.entrySet()) { + var bloatedInterfaceElement = mapEntry.getKey(); + var annotatedMethodsByProfile = mapEntry.getValue(); + for (var profile : annotatedMethodsByProfile.keySet()) { + for (var superInterfaceQualifiedName : providedSuperInterfacesSet) { + var sealedInterfaceName = sealedInterfaceNameConvention(profile, bloatedInterfaceElement); + if (superInterfaceQualifiedName.contains(sealedInterfaceName)) { + // add permits for each profile only if the profiles exist for the superinterface + var bloatedInterfaceProvidedProfilesList = annotatedMethodsByProfile.keySet().stream() + .filter(bloatedInterfaceProvidedProfile -> sealedInterfaceNameConvention(bloatedInterfaceProvidedProfile, bloatedInterfaceElement).equals(sealedInterfaceName)) + .toList(); + found = updateSealedInterfacesPermitsMapWithProvidedProfiles( + bloatedInterfaceProvidedProfilesList, addToProfileProvidedProfilesSet, annotatedClassOrInterface, + bloatedInterfaceElement, sealedInterfacesPermitsByBloatedInterface + ); + } + } + } + } + if (!found) { + statusReport.append(ADD_TO_PROFILE_REPORT_MSG); + } + return statusReport.toString(); + } + + private Set buildProvidedInterfacesSet(final ProcessingEnvironment processingEnv, final Element annotatedClassOrInterface) { + return processingEnv.getTypeUtils().directSupertypes(annotatedClassOrInterface.asType()).stream() + .map(Objects::toString) + .filter(typeString -> !typeString.contains(JAVA_LANG_OBJECT)) + .collect(toSet()); } - private void processAnnotatedElement(final Element annotatedElement, final Set allAnnotatedElements) { -// processingEnv.getElementUtils().getAllAnnotationMirrors(annotatedElement).forEach(annotationMirror -> { -// annotationMirror.getElementValues().entrySet().forEach(k -> System.out.println("#####k: " + k.getKey() + ", #####v: " + k.getValue().toString())); -// }); -// var gettersList = processingEnv.getElementUtils().getAllMembers((TypeElement) processingEnv.getTypeUtils().asElement(annotatedElement.asType())).stream() -// .filter(element -> element.getSimpleName().toString().startsWith("get") || element.getSimpleName().toString().startsWith("is")) -// .filter(element -> !element.getSimpleName().toString().startsWith("getClass")) -// .toList(); -// var qualifiedClassName = ((TypeElement) processingEnv.getTypeUtils().asElement(annotatedElement.asType())).getQualifiedName().toString(); -// var gettersMap = gettersList.stream().collect(Collectors.toMap(getter -> getter.getSimpleName().toString(), getter -> ((ExecutableType) getter.asType()).getReturnType().toString())); -// try { -// new RecordSourceFileGenerator(processingEnv, allAnnotatedElements).writeRecordSourceFile(qualifiedClassName, gettersList, gettersMap); -// log.info(() -> "\t> Successfully generated " + qualifiedClassName + "Record"); -// } catch (FilerException e) { -// // Skipped generating " + qualifiedClassName + "Record - file already exists" -// } catch (IOException e) { -// log.log(Level.SEVERE, format("Error generating %sRecord", qualifiedClassName), e); -// } + private boolean updateSealedInterfacesPermitsMapWithProvidedProfiles(final List bloatedInterfaceProvidedProfilesList, + final Set addToProfileProvidedProfilesSet, + final Element annotatedClassOrInterface, + final Element bloatedInterfaceElement, + final Map>> sealedInterfacesPermitsByBloatedInterface) { + var found = false; + for (var bloatedInterfaceProvidedProfile : bloatedInterfaceProvidedProfilesList) { // might contain SEPARATOR + for (var addToProfileProvidedProfile : addToProfileProvidedProfilesSet) { + if (asList(bloatedInterfaceProvidedProfile.split(SEPARATOR)).contains(addToProfileProvidedProfile)) { + sealedInterfacesPermitsByBloatedInterface.get(bloatedInterfaceElement).merge( + bloatedInterfaceProvidedProfile, + asList(annotatedClassOrInterface.toString()), + (currentList, newList) -> concat(currentList.stream(), newList.stream()).toList() + ); + found = true; + } else { + found = false; + } + } + } + return found; } } \ No newline at end of file diff --git a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java index e9fe409..d04932e 100644 --- a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java +++ b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java @@ -1,3 +1,24 @@ +/** + * Copyright (c) 2021-2022 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.handlers; import javax.annotation.processing.ProcessingEnvironment; @@ -11,7 +32,9 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.regex.Pattern; +import static java.lang.String.format; import static java.util.Arrays.asList; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toMap; @@ -21,11 +44,42 @@ public sealed interface JiselAnnotationHandler permits SealForProfileHandler, AddToProfileHandler, AnnotationInfoCollectionHandler, UniqueParentInterfaceHandler, ParentChildInheritanceHandler { String SEPARATOR = ","; + String SEALED_PREFIX = "Sealed"; + String ADD_TO_PROFILE_REPORT_MSG = "1 or many provided profiles are not found in provided parent interfaces. Check your profiles names."; + String JAVA_LANG_OBJECT = "java.lang.Object"; + + String ANNOTATION_VALUES_REGEX = "\"([^\"]*)\""; Map handleAnnotatedElements(ProcessingEnvironment processingEnv, Set allAnnotatedElements, - Map>> sealedInterfacesToGenerate, - Map>> sealedInterfacesPermits); + Map>> sealedInterfacesToGenerateByBloatedInterface, + Map>> sealedInterfacesPermitsByBloatedInterface); + + default Set buildProvidedProfilesSet(final ProcessingEnvironment processingEnv, final Element annotatedClassOrInterface) { + var providedProfilesSet = new HashSet(); + processingEnv.getElementUtils().getAllAnnotationMirrors(annotatedClassOrInterface).stream() + .flatMap(annotationMirror -> annotationMirror.getElementValues().entrySet().stream()) + .map(entry -> entry.getValue().toString()) + .forEach(annotationRawValueAsString -> { // annotationRawValueAsString: sample values: singl value "profile1name", array: {@org.jisel.SealForProfile("profile2name"), @org.jisel.SealForProfile("profile3name"),...} + var matcher = Pattern.compile(ANNOTATION_VALUES_REGEX).matcher(annotationRawValueAsString); + while (matcher.find()) { + var profile = matcher.group(1).trim(); + if (profile.isBlank()) { + continue; + } + providedProfilesSet.add(profile); + } + }); + return providedProfilesSet; + } + + default String removeSeparator(final String text) { + return asList(text.split(SEPARATOR)).stream().collect(joining()); + } + + default String sealedInterfaceNameConvention(final String profile, final Element interfaceElement) { + return format("%s%s%s", SEALED_PREFIX, removeSeparator(profile), interfaceElement.getSimpleName().toString()); + } } sealed interface AnnotationInfoCollectionHandler extends JiselAnnotationHandler permits SealForProfileInfoCollectionHandler { @@ -88,17 +142,17 @@ private Map> concatenateProfilesBasedOnCommonMethods(final @Override default Map handleAnnotatedElements(final ProcessingEnvironment processingEnv, final Set allAnnotatedElements, - final Map>> sealedInterfacesToGenerate, - final Map>> sealedInterfacesPermits) { - populateSealedInterfacesMap(processingEnv, allAnnotatedElements, sealedInterfacesToGenerate); + final Map>> sealedInterfacesToGenerateByBloatedInterface, + final Map>> sealedInterfacesPermitsByBloatedInterface) { + populateSealedInterfacesMap(processingEnv, allAnnotatedElements, sealedInterfacesToGenerateByBloatedInterface); return Map.of(); // return empty map instead of null } } sealed interface ParentChildInheritanceHandler extends JiselAnnotationHandler permits SealForProfileParentChildInheritanceHandler { - void buildInheritanceRelations(Map>> sealedInterfacesToGenerate, - Map>> sealedInterfacesPermits); + void buildInheritanceRelations(Map>> sealedInterfacesToGenerateByBloatedInterface, + Map>> sealedInterfacesPermitsByBloatedInterface); default void buildSealedInterfacesPermitsMap(final Element interfaceElement, final Map>> sealedInterfacesToGenerate, @@ -121,16 +175,16 @@ default void buildSealedInterfacesPermitsMap(final Element interfaceElement, @Override default Map handleAnnotatedElements(final ProcessingEnvironment processingEnv, final Set allAnnotatedElements, - final Map>> sealedInterfacesToGenerate, - final Map>> sealedInterfacesPermits) { - buildInheritanceRelations(sealedInterfacesToGenerate, sealedInterfacesPermits); + final Map>> sealedInterfacesToGenerateByBloatedInterface, + final Map>> sealedInterfacesPermitsByBloatedInterface) { + buildInheritanceRelations(sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface); return Map.of(); } } sealed interface UniqueParentInterfaceHandler extends JiselAnnotationHandler permits SealForProfileUniqueParentInterfaceHandler { - String REPORT_MSG = "More than 1 Parent Sealed Interfaces will be generated. Check your profiles mapping"; + String REPORT_MSG = "More than 1 Parent Sealed Interfaces will be generated. Check your profiles mapping."; default Map> checkUniqueParentInterfacePresence(final Map>> sealedInterfacesToGenerate) { @@ -169,17 +223,13 @@ foundUniqueParentInterface && removeSeparator(longestConcatenedProfilesStringOpt return uniqueParentInterfaceByInterface; } - private String removeSeparator(final String text) { - return asList(text.split(SEPARATOR)).stream().collect(joining()); - } - Map checkAndHandleUniqueParentInterface(Map>> sealedInterfacesToGenerate); @Override default Map handleAnnotatedElements(final ProcessingEnvironment processingEnv, final Set allAnnotatedElements, - final Map>> sealedInterfacesToGenerate, - final Map>> sealedInterfacesPermits) { - return checkAndHandleUniqueParentInterface(sealedInterfacesToGenerate); + final Map>> sealedInterfacesToGenerateByBloatedInterface, + final Map>> sealedInterfacesPermitsByBloatedInterface) { + return checkAndHandleUniqueParentInterface(sealedInterfacesToGenerateByBloatedInterface); } } \ No newline at end of file diff --git a/src/main/java/org/jisel/handlers/SealForProfileHandler.java b/src/main/java/org/jisel/handlers/SealForProfileHandler.java index d19384a..759261d 100644 --- a/src/main/java/org/jisel/handlers/SealForProfileHandler.java +++ b/src/main/java/org/jisel/handlers/SealForProfileHandler.java @@ -1,3 +1,24 @@ +/** + * Copyright (c) 2021-2022 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.handlers; import javax.annotation.processing.ProcessingEnvironment; @@ -10,7 +31,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.regex.Pattern; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toSet; @@ -21,13 +41,11 @@ public final class SealForProfileHandler implements JiselAnnotationHandler { @Override public Map handleAnnotatedElements(final ProcessingEnvironment processingEnv, final Set allAnnotatedElements, - final Map>> sealedInterfacesToGenerate, - final Map>> sealedInterfacesPermits) { - new SealForProfileInfoCollectionHandler().populateSealedInterfacesMap(processingEnv, allAnnotatedElements, sealedInterfacesToGenerate); - var statusReport = new SealForProfileUniqueParentInterfaceHandler().checkAndHandleUniqueParentInterface(sealedInterfacesToGenerate); - new SealForProfileParentChildInheritanceHandler().buildInheritanceRelations(sealedInterfacesToGenerate, sealedInterfacesPermits); - System.out.println(" \n\n>>>$$>>>>>>$$$>>>>$$>>>>> sealedInterfacesPermits: " + sealedInterfacesPermits); - System.out.println(" \n\n>>>>>>>>>>>>>>>>>> sealedInterfacesToGenerate: " + sealedInterfacesToGenerate); + final Map>> sealedInterfacesToGenerateByBloatedInterface, + final Map>> sealedInterfacesPermitsByBloatedInterface) { + new SealForProfileInfoCollectionHandler().populateSealedInterfacesMap(processingEnv, allAnnotatedElements, sealedInterfacesToGenerateByBloatedInterface); + var statusReport = new SealForProfileUniqueParentInterfaceHandler().checkAndHandleUniqueParentInterface(sealedInterfacesToGenerateByBloatedInterface); + new SealForProfileParentChildInheritanceHandler().buildInheritanceRelations(sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface); return statusReport; } } @@ -38,41 +56,33 @@ final class SealForProfileInfoCollectionHandler implements AnnotationInfoCollect public void populateSealedInterfacesMap(final ProcessingEnvironment processingEnv, final Set allAnnotatedElements, final Map>> sealedInterfacesToGenerate) { - var annotatedMethodsByInterface = allAnnotatedElements.stream() .filter(element -> ElementKind.METHOD.equals(element.getKind())) .filter(element -> ElementKind.INTERFACE.equals(element.getEnclosingElement().getKind())) .collect(groupingBy(Element::getEnclosingElement, toSet())); - if (annotatedMethodsByInterface.isEmpty()) { return; } - + // ... var annotatedMethodsByProfileByInterface = new HashMap>>(); annotatedMethodsByInterface.forEach( (interfaceElement, annotatedMethodsElements) -> annotatedMethodsElements.forEach( - annotatedMethod -> processingEnv.getElementUtils().getAllAnnotationMirrors(annotatedMethod).forEach( - annotationMirror -> annotationMirror.getElementValues().entrySet().forEach( - entry -> extractProfilesAndPopulateMaps(interfaceElement, entry.getValue().toString(), annotatedMethod, annotatedMethodsByProfileByInterface) - ) + annotatedMethod -> extractProfilesAndPopulateMaps( + interfaceElement, + buildProvidedProfilesSet(processingEnv, annotatedMethod), + annotatedMethod, + annotatedMethodsByProfileByInterface ) ) ); - createParentInterfacesBasedOnCommonMethods(annotatedMethodsByProfileByInterface, sealedInterfacesToGenerate); } - // annotationValueAsString: sample values: singl value "profile1name", array: {@org.jisel.SealForProfile("profile2name"), @org.jisel.SealForProfile("profile3name"),...} private void extractProfilesAndPopulateMaps(final Element interfaceElement, - final String annotationValueAsString, + final Set providedProfilesSet, final Element annotatedMethod, final Map>> annotatedMethodsByProfileByInterface) { - var matcher = Pattern.compile("\"([^\"]*)\"").matcher(annotationValueAsString); - while (matcher.find()) { - var profile = matcher.group(1).trim(); - if (profile.isBlank()) { - continue; - } + providedProfilesSet.forEach(profile -> { if (annotatedMethodsByProfileByInterface.containsKey(interfaceElement)) { annotatedMethodsByProfileByInterface.get(interfaceElement).merge( profile, @@ -82,41 +92,36 @@ private void extractProfilesAndPopulateMaps(final Element interfaceElement, } else { annotatedMethodsByProfileByInterface.put(interfaceElement, new HashMap<>(Map.of(profile, new HashSet<>(Set.of(annotatedMethod))))); } - } + }); } } final class SealForProfileParentChildInheritanceHandler implements ParentChildInheritanceHandler { @Override - public void buildInheritanceRelations(final Map>> sealedInterfacesToGenerate, - final Map>> sealedInterfacesPermits) { - - sealedInterfacesToGenerate.keySet().forEach(interfaceElement -> { - - sealedInterfacesPermits.put(interfaceElement, new HashMap<>()); // start with initializing sealedInterfacesPermits with empty mutable maps - + public void buildInheritanceRelations(final Map>> sealedInterfacesToGenerateByBloatedInterface, + final Map>> sealedInterfacesPermitsByBloatedInterface) { + sealedInterfacesToGenerateByBloatedInterface.keySet().forEach(interfaceElement -> { + sealedInterfacesPermitsByBloatedInterface.put(interfaceElement, new HashMap<>()); // start with initializing sealedInterfacesPermitsByBloatedInterface with empty mutable maps // promote profiles with empty methods to parent level var allProfilesToRemove = new HashSet(); - sealedInterfacesToGenerate.get(interfaceElement).keySet().forEach(concatenatedProfiles -> { + sealedInterfacesToGenerateByBloatedInterface.get(interfaceElement).keySet().forEach(concatenatedProfiles -> { var profilesArray = concatenatedProfiles.split(SEPARATOR); if (profilesArray.length > 1) { for (var profile : profilesArray) { - var profileMethodsOpt = Optional.ofNullable(sealedInterfacesToGenerate.get(interfaceElement).get(profile)); + var profileMethodsOpt = Optional.ofNullable(sealedInterfacesToGenerateByBloatedInterface.get(interfaceElement).get(profile)); if (profileMethodsOpt.isPresent() && profileMethodsOpt.get().isEmpty()) { - sealedInterfacesToGenerate.get(interfaceElement).put(profile, sealedInterfacesToGenerate.get(interfaceElement).get(concatenatedProfiles)); - sealedInterfacesPermits.get(interfaceElement).put(profile, Arrays.stream(profilesArray).filter(profileName -> !profile.equals(profileName)).toList()); + sealedInterfacesToGenerateByBloatedInterface.get(interfaceElement).put(profile, sealedInterfacesToGenerateByBloatedInterface.get(interfaceElement).get(concatenatedProfiles)); + sealedInterfacesPermitsByBloatedInterface.get(interfaceElement).put(profile, Arrays.stream(profilesArray).filter(profileName -> !profile.equals(profileName)).toList()); allProfilesToRemove.add(concatenatedProfiles); break; } } } }); - - allProfilesToRemove.forEach(sealedInterfacesToGenerate.get(interfaceElement)::remove); - - // and completing building sealedInterfacesPermits map - buildSealedInterfacesPermitsMap(interfaceElement, sealedInterfacesToGenerate, sealedInterfacesPermits); + allProfilesToRemove.forEach(sealedInterfacesToGenerateByBloatedInterface.get(interfaceElement)::remove); + // and completing building sealedInterfacesPermitsByBloatedInterface map + buildSealedInterfacesPermitsMap(interfaceElement, sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface); }); } } From eb0ab7c59f9b1560470e79785a5b968f96f4c27b Mon Sep 17 00:00:00 2001 From: Mohamed Ashraf Bayor Date: Thu, 9 Dec 2021 20:30:21 -0500 Subject: [PATCH 03/11] JIS-01: work in progress --- .../org/jisel/JiselAnnotationProcessor.java | 69 ++++++--- .../org/jisel/generator/CodeGenerator.java | 86 +++++++++++ .../JavaxGeneratedGenerator.java | 16 +- .../generator/JiselFinalClassGenerator.java | 21 +++ .../jisel/generator/JiselReportGenerator.java | 21 +++ .../SealedInterfaceContentGenerator.java | 141 ++++++++++++++++++ .../SealedInterfaceSourceFileGenerator.java | 42 +++++- .../org/jisel/generator/StringGenerator.java | 62 ++++++++ .../generator/helpers/CodeGenerator.java | 69 --------- .../generator/helpers/ExtendsGenerator.java | 25 ---- .../generator/helpers/MethodsGenerator.java | 25 ---- .../generator/helpers/PermitsGenerator.java | 25 ---- .../handlers/JiselAnnotationHandler.java | 17 +-- .../jisel/handlers/SealForProfileHandler.java | 16 +- 14 files changed, 448 insertions(+), 187 deletions(-) create mode 100644 src/main/java/org/jisel/generator/CodeGenerator.java rename src/main/java/org/jisel/generator/{helpers => }/JavaxGeneratedGenerator.java (85%) create mode 100644 src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java create mode 100644 src/main/java/org/jisel/generator/StringGenerator.java delete mode 100644 src/main/java/org/jisel/generator/helpers/CodeGenerator.java delete mode 100644 src/main/java/org/jisel/generator/helpers/ExtendsGenerator.java delete mode 100644 src/main/java/org/jisel/generator/helpers/MethodsGenerator.java delete mode 100644 src/main/java/org/jisel/generator/helpers/PermitsGenerator.java diff --git a/src/main/java/org/jisel/JiselAnnotationProcessor.java b/src/main/java/org/jisel/JiselAnnotationProcessor.java index 4ff9a2a..c3a7816 100644 --- a/src/main/java/org/jisel/JiselAnnotationProcessor.java +++ b/src/main/java/org/jisel/JiselAnnotationProcessor.java @@ -22,11 +22,14 @@ package org.jisel; import com.google.auto.service.AutoService; +import org.jisel.generator.SealedInterfaceSourceFileGenerator; import org.jisel.handlers.AddToProfileHandler; import org.jisel.handlers.JiselAnnotationHandler; import org.jisel.handlers.SealForProfileHandler; import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Filer; +import javax.annotation.processing.FilerException; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; @@ -34,13 +37,16 @@ import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; +import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Level; import java.util.logging.Logger; +import static java.lang.String.format; import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableSet; import static java.util.stream.Collectors.joining; @@ -56,6 +62,8 @@ public class JiselAnnotationProcessor extends AbstractProcessor { private static final String SEAL_FOR_PROFILE = "SealForProfile"; private static final String ADD_TO_PROFILE = "AddToProfile"; + private static final String STATUS_REPORT_TITLE = "JISEL GENERATION REPORT"; + private JiselAnnotationHandler sealForProfileHandler; private JiselAnnotationHandler addToProfileHandler; @@ -66,13 +74,47 @@ public JiselAnnotationProcessor() { } @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { + public boolean process(final Set annotations, final RoundEnvironment roundEnv) { var allAnnotatedSealForProfileElements = new HashSet(); var allAnnotatedAddToProfileElements = new HashSet(); var sealedInterfacesToGenerateByBloatedInterface = new HashMap>>(); var sealedInterfacesPermitsByBloatedInterface = new HashMap>>(); + populateAllAnnotatedElementsSets(annotations, roundEnv, allAnnotatedSealForProfileElements, allAnnotatedAddToProfileElements); + + // process all interface methods annotated with @SealForProfile + var statusReport = sealForProfileHandler.handleAnnotatedElements( + processingEnv, + unmodifiableSet(allAnnotatedSealForProfileElements), + sealedInterfacesToGenerateByBloatedInterface, + sealedInterfacesPermitsByBloatedInterface + ); + displayStatusReport(statusReport, SealForProfile.class); + + // process all child classes or interfaces annotated with @AddToProfile + statusReport = addToProfileHandler.handleAnnotatedElements( + processingEnv, + unmodifiableSet(allAnnotatedAddToProfileElements), + unmodifiableMap(sealedInterfacesToGenerateByBloatedInterface), + sealedInterfacesPermitsByBloatedInterface + ); + displayStatusReport(statusReport, AddToProfile.class); + + try { + new SealedInterfaceSourceFileGenerator(processingEnv).createSourceFiles(sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface); + } catch (IOException e) { + log.log(Level.SEVERE, format("Error generating sealed interfaces", e)); + } + + System.out.println(" \n\n@@@@@@@@@@@@@ sealedInterfacesToGenerateByBloatedInterface: " + sealedInterfacesToGenerateByBloatedInterface); + System.out.println("\n~~~~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~ \n sealedInterfacesPermitsByBloatedInterface" + sealedInterfacesPermitsByBloatedInterface); + + return true; + } + + private void populateAllAnnotatedElementsSets(final Set annotations, final RoundEnvironment roundEnv, + final Set allAnnotatedSealForProfileElements, final Set allAnnotatedAddToProfileElements) { for (var annotation : annotations) { if (annotation.getSimpleName().toString().contains(SEAL_FOR_PROFILE)) { allAnnotatedSealForProfileElements.addAll(roundEnv.getElementsAnnotatedWith(annotation)); @@ -81,24 +123,17 @@ public boolean process(Set annotations, RoundEnvironment allAnnotatedAddToProfileElements.addAll(roundEnv.getElementsAnnotatedWith(annotation)); } } + } - // process all annotated interface methods annotated with @SealForProfile - var statusReport = sealForProfileHandler.handleAnnotatedElements(processingEnv, unmodifiableSet(allAnnotatedSealForProfileElements), sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface); - if (!statusReport.isEmpty()) { - // TODO output log - create priv meth browse the map and output report per interface - } - // TODO sealedInterfacesPermitsByBloatedInterface here shld be getting only qlfd names of child classes - - // process all annotated child class or interfaces annotated with @AddToProfile - statusReport = addToProfileHandler.handleAnnotatedElements(processingEnv, unmodifiableSet(allAnnotatedAddToProfileElements), unmodifiableMap(sealedInterfacesToGenerateByBloatedInterface), sealedInterfacesPermitsByBloatedInterface); + private void displayStatusReport(final Map statusReport, final Class annotation) { if (!statusReport.values().stream().collect(joining()).isBlank()) { - // output log - create priv meth - log.warning("\t>\t" + statusReport); + var output = new StringBuilder(format("%n%s - @%s(s)%n", STATUS_REPORT_TITLE, annotation.getSimpleName())); + statusReport.entrySet().forEach(mapEntry -> { + if (!mapEntry.getValue().isBlank()) { + output.append(format("\t> %s: %s%n", mapEntry.getKey().getSimpleName().toString(), mapEntry.getValue())); + } + }); + log.warning(output.toString()); } - - System.out.println(" \n\n@@@@@@@@@@@@@ sealedInterfacesToGenerateByBloatedInterface: " + sealedInterfacesToGenerateByBloatedInterface); - System.out.println("\n~~~~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~ \n sealedInterfacesPermitsByBloatedInterface" + sealedInterfacesPermitsByBloatedInterface); - - return true; } } \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/CodeGenerator.java b/src/main/java/org/jisel/generator/CodeGenerator.java new file mode 100644 index 0000000..3621937 --- /dev/null +++ b/src/main/java/org/jisel/generator/CodeGenerator.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2021-2022 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; + +import javax.lang.model.element.Element; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.lang.String.format; +import static java.util.stream.Collectors.joining; + +/** + * Exposes contract for a CodeGenerator class to fulfill + */ +public sealed interface CodeGenerator permits JavaxGeneratedGenerator, ExtendsGenerator, PermitsGenerator, MethodsGenerator { + + /** + * Generates piece of code requested, based on the parameters provided in the params object and appends it to the provided recordClassContent param + * + * @param sealedInterfaceContent Stringbuilder object containing the record class code being generated + * @param params expected parameters. restricted to paremeters and values expected by the implementing class + */ + void generateCode(StringBuilder sealedInterfaceContent, List params); +} + +sealed interface ExtendsGenerator extends CodeGenerator, StringGenerator permits JiselExtendsGenerator { + + void generateExtendsClauseFromPermitsMapAndProcessedProfile(StringBuilder sealedInterfaceContent, Map> permitsMap, String processedProfile, Element bloatedInterfaceElement); + + @Override + default void generateCode(final StringBuilder sealedInterfaceContent, final List params) { + sealedInterfaceContent.append(format( + " %s %s ", + EXTENDS, + params.stream().map(listEntry -> removeSeparator(listEntry)).collect(joining(COMMA_SEPARATOR + WHITESPACE)) + )); + } +} + +sealed interface PermitsGenerator extends CodeGenerator, StringGenerator permits JiselPermitsGenerator { + + void generatePermitsClauseFromPermitsMapAndProcessedProfile(StringBuilder sealedInterfaceContent, Map> permitsMap, String processedProfile, Element bloatedInterfaceElement); + + @Override + default void generateCode(final StringBuilder sealedInterfaceContent, final List params) { + sealedInterfaceContent.append(format( + " %s %s ", + PERMITS, + params.stream().map(listEntry -> removeSeparator(listEntry)).collect(joining(COMMA_SEPARATOR + WHITESPACE)) + )); + } +} + +sealed interface MethodsGenerator extends CodeGenerator, StringGenerator permits JiselMethodsGenerator { + + void generateMethodsFromElementsSet(StringBuilder sealedInterfaceContent, Set methodsSet); + + @Override + default void generateCode(final StringBuilder sealedInterfaceContent, final List params) { + params.forEach(methodDefinition -> sealedInterfaceContent.append(format("\t%s;%n", methodDefinition))); + } + + default boolean methodHasArguments(final Element element) { + return element.toString().indexOf(CLOSING_PARENTHESIS) - element.toString().indexOf(OPENING_PARENTHESIS) > 1; + } +} \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/helpers/JavaxGeneratedGenerator.java b/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java similarity index 85% rename from src/main/java/org/jisel/generator/helpers/JavaxGeneratedGenerator.java rename to src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java index 477ff6e..b46c2c1 100644 --- a/src/main/java/org/jisel/generator/helpers/JavaxGeneratedGenerator.java +++ b/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java @@ -19,14 +19,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package org.jisel.generator.helpers; +package org.jisel.generator; import org.jisel.JiselAnnotationProcessor; import java.io.IOException; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.Map; +import java.util.List; import java.util.Properties; import static java.lang.String.format; @@ -46,10 +46,10 @@ private void buildGeneratedAnnotationSection(final StringBuilder recordClassCont date = "%s", comments = "version: %s" ) - """ - , JiselAnnotationProcessor.class.getName() - , ZonedDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) - , getAppVersion() + """, + JiselAnnotationProcessor.class.getName(), + ZonedDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), + getAppVersion() )); } @@ -65,7 +65,7 @@ private String getAppVersion() { } @Override - public void generateCode(final StringBuilder recordClassContent, final Map params) { - buildGeneratedAnnotationSection(recordClassContent); + public void generateCode(final StringBuilder sealedInterfaceContent, final List params) { + buildGeneratedAnnotationSection(sealedInterfaceContent); } } \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/JiselFinalClassGenerator.java b/src/main/java/org/jisel/generator/JiselFinalClassGenerator.java index de18f56..5b8325a 100644 --- a/src/main/java/org/jisel/generator/JiselFinalClassGenerator.java +++ b/src/main/java/org/jisel/generator/JiselFinalClassGenerator.java @@ -1,3 +1,24 @@ +/** + * Copyright (c) 2021-2022 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; public class JiselFinalClassGenerator { diff --git a/src/main/java/org/jisel/generator/JiselReportGenerator.java b/src/main/java/org/jisel/generator/JiselReportGenerator.java index e58a3e4..ba7c954 100644 --- a/src/main/java/org/jisel/generator/JiselReportGenerator.java +++ b/src/main/java/org/jisel/generator/JiselReportGenerator.java @@ -1,3 +1,24 @@ +/** + * Copyright (c) 2021-2022 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; public class JiselReportGenerator { diff --git a/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java b/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java new file mode 100644 index 0000000..13b41b5 --- /dev/null +++ b/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2021-2022 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; + +import javax.lang.model.element.Element; +import javax.lang.model.type.ExecutableType; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import static java.lang.String.format; +import static java.util.stream.Collectors.joining; + +public final class SealedInterfaceContentGenerator implements StringGenerator { + + private final CodeGenerator javaxGeneratedGenerator; + private final ExtendsGenerator extendsGenerator; + private final PermitsGenerator permitsGenerator; + private final MethodsGenerator methodsGenerator; + + public SealedInterfaceContentGenerator() { + this.javaxGeneratedGenerator = new JavaxGeneratedGenerator(); + this.extendsGenerator = new JiselExtendsGenerator(); + this.permitsGenerator = new JiselPermitsGenerator(); + this.methodsGenerator = new JiselMethodsGenerator(); + } + + public String generateContent(final Map.Entry> sealedInterfacesToGenerateEntrySet, final Element bloatedInterfaceElement, + final Map>> sealedInterfacesPermitsByBloatedInterface) { + var sealedInterfaceContent = new StringBuilder(); + // package name + generatePackageName(bloatedInterfaceElement).ifPresent(name -> sealedInterfaceContent.append(format("%s %s;%n%n", PACKAGE, name))); + // javaxgenerated + javaxGeneratedGenerator.generateCode(sealedInterfaceContent, null); + // public sealed interface + var profile = sealedInterfacesToGenerateEntrySet.getKey(); + sealedInterfaceContent.append(format( + "%s %s ", + PUBLIC_SEALED_INTERFACE, + sealedInterfaceNameConvention(profile, bloatedInterfaceElement) + )); + // list of extends + extendsGenerator.generateExtendsClauseFromPermitsMapAndProcessedProfile(sealedInterfaceContent, sealedInterfacesPermitsByBloatedInterface.get(bloatedInterfaceElement), profile, bloatedInterfaceElement); + // list of permits + permitsGenerator.generatePermitsClauseFromPermitsMapAndProcessedProfile(sealedInterfaceContent, sealedInterfacesPermitsByBloatedInterface.get(bloatedInterfaceElement), profile, bloatedInterfaceElement); + // TODO generate permits for Final classes + // opening bracket after permits list + sealedInterfaceContent.append(format(" %s%n ", OPENING_BRACKET)); + // list of methods + methodsGenerator.generateMethodsFromElementsSet(sealedInterfaceContent, sealedInterfacesToGenerateEntrySet.getValue()); + // closing bracket + sealedInterfaceContent.append(CLOSING_BRACKET); + // + return removeDoubleSpaceOccurrences(sealedInterfaceContent.toString()); + } +} + +final class JiselExtendsGenerator implements ExtendsGenerator { + @Override + public void generateExtendsClauseFromPermitsMapAndProcessedProfile(final StringBuilder sealedInterfaceContent, final Map> permitsMap, final String processedProfile, final Element bloatedInterfaceElement) { + Optional.ofNullable(permitsMap).ifPresent(nonNullPermitsMap -> { + var childrenList = nonNullPermitsMap.entrySet().stream() + .filter(permitsMapEntry -> permitsMapEntry.getValue().contains(processedProfile)) + .map(permitsMapEntry -> sealedInterfaceNameConvention(permitsMapEntry.getKey(), bloatedInterfaceElement)) + .toList(); + if (!childrenList.isEmpty()) { + generateCode(sealedInterfaceContent, childrenList); + } + }); + } +} + +final class JiselPermitsGenerator implements PermitsGenerator { + @Override + public void generatePermitsClauseFromPermitsMapAndProcessedProfile(final StringBuilder sealedInterfaceContent, final Map> permitsMap, final String processedProfile, final Element bloatedInterfaceElement) { + var permitsMapOpt = Optional.ofNullable(permitsMap); + if (permitsMapOpt.isPresent() && !permitsMapOpt.get().isEmpty()) { + Optional.ofNullable(permitsMapOpt.get().get(processedProfile)).ifPresent(childrenList -> generateCode(sealedInterfaceContent, sealedInterfaceNameConventionForList(childrenList, bloatedInterfaceElement))); + } + } +} + +final class JiselMethodsGenerator implements MethodsGenerator { + + @Override + public void generateMethodsFromElementsSet(final StringBuilder sealedInterfaceContent, final Set methodsSet) { + generateCode( + sealedInterfaceContent, + methodsSet.stream() + .map(element -> format( + "%s %s%s", + generateReturnType(element), + generateMethodNameAndParameters(element), + generateThrownExceptions(element).isEmpty() ? EMPTY_STRING : format(" throws %s", generateThrownExceptions(element)) + )) + .toList() + ); + } + + private String generateReturnType(final Element methodElement) { + return ((ExecutableType) methodElement.asType()).getReturnType().toString(); + } + + private String generateMethodNameAndParameters(final Element methodElement) { + var output = methodElement.toString(); + if (methodHasArguments(methodElement)) { + int paramIdx = 0; + while (output.contains(COMMA_SEPARATOR)) { + output = output.replace(COMMA_SEPARATOR, WHITESPACE + PARAMETER_PREFIX + paramIdx + TEMP_PLACEHOLDER + WHITESPACE); + paramIdx++; + } + output = output.replace(CLOSING_PARENTHESIS, WHITESPACE + PARAMETER_PREFIX + paramIdx + CLOSING_PARENTHESIS).replaceAll(TEMP_PLACEHOLDER, COMMA_SEPARATOR); + } + return output; + } + + private String generateThrownExceptions(final Element methodElement) { + return ((ExecutableType) methodElement.asType()).getThrownTypes().stream().map(Objects::toString).collect(joining(COMMA_SEPARATOR + WHITESPACE)); + } +} \ 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 index 0a2f0a8..865e64f 100644 --- a/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java +++ b/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java @@ -21,5 +21,43 @@ */ package org.jisel.generator; -public class SealedInterfaceSourceFileGenerator { -} +import javax.annotation.processing.FilerException; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class SealedInterfaceSourceFileGenerator implements StringGenerator { + + private final ProcessingEnvironment processingEnvironment; + private final SealedInterfaceContentGenerator sealedInterfaceContentGenerator; + + public SealedInterfaceSourceFileGenerator(final ProcessingEnvironment processingEnvironment) { + this.processingEnvironment = processingEnvironment; + this.sealedInterfaceContentGenerator = new SealedInterfaceContentGenerator(); + } + + public void createSourceFiles(final Map>> sealedInterfacesToGenerateByBloatedInterface, + final Map>> sealedInterfacesPermitsByBloatedInterface) throws IOException { + for (var sealedInterfacesToGenerateMapEntry : sealedInterfacesToGenerateByBloatedInterface.entrySet()) { + var bloatedInterfaceElement = sealedInterfacesToGenerateMapEntry.getKey(); + var packageNameOpt = generatePackageName(bloatedInterfaceElement); + for (var sealedInterfacesToGenerate : sealedInterfacesToGenerateMapEntry.getValue().entrySet()) { + var profile = sealedInterfacesToGenerate.getKey(); + var generatedSealedInterfaceName = sealedInterfaceNameConvention(profile, bloatedInterfaceElement); + try { + var fileObject = processingEnvironment.getFiler().createSourceFile(packageNameOpt.isPresent() ? packageNameOpt.get() + DOT + generatedSealedInterfaceName : generatedSealedInterfaceName); + try (var out = new PrintWriter(fileObject.openWriter())) { + out.println(sealedInterfaceContentGenerator.generateContent(sealedInterfacesToGenerate, sealedInterfacesToGenerateMapEntry.getKey(), sealedInterfacesPermitsByBloatedInterface)); + } + } catch (FilerException e) { + // File was already generated - do nothing + } + } + // TODO run the finalclass logic here before generating the files - 1 per bloatedinterf: "_" + BloatedInterfceName + "FinalClass" + } + } +} \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/StringGenerator.java b/src/main/java/org/jisel/generator/StringGenerator.java new file mode 100644 index 0000000..962c13e --- /dev/null +++ b/src/main/java/org/jisel/generator/StringGenerator.java @@ -0,0 +1,62 @@ +package org.jisel.generator; + +import javax.lang.model.element.Element; +import java.util.List; +import java.util.Optional; + +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.joining; + +public interface StringGenerator { + + String COMMA_SEPARATOR = ","; + String WHITESPACE = " "; + String SEALED_PREFIX = "Sealed"; + String PACKAGE = "package"; + String PUBLIC_SEALED_INTERFACE = "public sealed interface"; + String EXTENDS = "extends"; + String PERMITS = "permits"; + String OPENING_BRACKET = "{"; + String CLOSING_BRACKET = "}"; + String OPENING_PARENTHESIS = "("; + String CLOSING_PARENTHESIS = ")"; + String EMPTY_STRING = ""; + String DOT = "."; + String PARAMETER_PREFIX = "param"; + String TEMP_PLACEHOLDER = "@"; + + default String removeSeparator(final String text) { + return asList(text.split(COMMA_SEPARATOR)).stream().collect(joining()); + } + + default String sealedInterfaceNameConvention(final String profile, final Element interfaceElement) { + return format( + "%s%s%s", + SEALED_PREFIX, + removeSeparator(profile), + removeSeparator(profile).equals(interfaceElement.getSimpleName().toString()) ? EMPTY_STRING : interfaceElement.getSimpleName().toString() + ); + } + + default List sealedInterfaceNameConventionForList(final List profiles, final Element interfaceElement) { + return profiles.stream() + .map(profile -> + format( + "%s%s%s", + SEALED_PREFIX, + removeSeparator(profile), + removeSeparator(profile).equals(interfaceElement.getSimpleName().toString()) ? EMPTY_STRING : interfaceElement.getSimpleName().toString() + )).toList(); + } + + default Optional generatePackageName(final Element bloatedInterfaceName) { + var qualifiedClassName = bloatedInterfaceName.toString(); + int lastDot = qualifiedClassName.lastIndexOf('.'); + return lastDot > 0 ? Optional.of(qualifiedClassName.substring(0, lastDot)) : Optional.empty(); + } + + default String removeDoubleSpaceOccurrences(final String text) { + return text.replaceAll(WHITESPACE + WHITESPACE, WHITESPACE); + } +} \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/helpers/CodeGenerator.java b/src/main/java/org/jisel/generator/helpers/CodeGenerator.java deleted file mode 100644 index 36cc207..0000000 --- a/src/main/java/org/jisel/generator/helpers/CodeGenerator.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (c) 2021-2022 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 deleted file mode 100644 index d3c27d4..0000000 --- a/src/main/java/org/jisel/generator/helpers/ExtendsGenerator.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2021-2022 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; - -public final class ExtendsGenerator { -} diff --git a/src/main/java/org/jisel/generator/helpers/MethodsGenerator.java b/src/main/java/org/jisel/generator/helpers/MethodsGenerator.java deleted file mode 100644 index 27cd355..0000000 --- a/src/main/java/org/jisel/generator/helpers/MethodsGenerator.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2021-2022 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; - -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 deleted file mode 100644 index 8a985c6..0000000 --- a/src/main/java/org/jisel/generator/helpers/PermitsGenerator.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2021-2022 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; - -public final class PermitsGenerator { -} diff --git a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java index d04932e..345ea24 100644 --- a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java +++ b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java @@ -21,6 +21,8 @@ */ package org.jisel.handlers; +import org.jisel.generator.StringGenerator; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import java.util.ArrayList; @@ -34,17 +36,14 @@ import java.util.Set; import java.util.regex.Pattern; -import static java.lang.String.format; import static java.util.Arrays.asList; -import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet; import static java.util.stream.Stream.concat; -public sealed interface JiselAnnotationHandler permits SealForProfileHandler, AddToProfileHandler, AnnotationInfoCollectionHandler, UniqueParentInterfaceHandler, ParentChildInheritanceHandler { +public sealed interface JiselAnnotationHandler extends StringGenerator permits SealForProfileHandler, AddToProfileHandler, AnnotationInfoCollectionHandler, UniqueParentInterfaceHandler, ParentChildInheritanceHandler { String SEPARATOR = ","; - String SEALED_PREFIX = "Sealed"; String ADD_TO_PROFILE_REPORT_MSG = "1 or many provided profiles are not found in provided parent interfaces. Check your profiles names."; String JAVA_LANG_OBJECT = "java.lang.Object"; @@ -72,14 +71,6 @@ default Set buildProvidedProfilesSet(final ProcessingEnvironment process }); return providedProfilesSet; } - - default String removeSeparator(final String text) { - return asList(text.split(SEPARATOR)).stream().collect(joining()); - } - - default String sealedInterfaceNameConvention(final String profile, final Element interfaceElement) { - return format("%s%s%s", SEALED_PREFIX, removeSeparator(profile), interfaceElement.getSimpleName().toString()); - } } sealed interface AnnotationInfoCollectionHandler extends JiselAnnotationHandler permits SealForProfileInfoCollectionHandler { @@ -184,7 +175,7 @@ default Map handleAnnotatedElements(final ProcessingEnvironment sealed interface UniqueParentInterfaceHandler extends JiselAnnotationHandler permits SealForProfileUniqueParentInterfaceHandler { - String REPORT_MSG = "More than 1 Parent Sealed Interfaces will be generated. Check your profiles mapping."; + String REPORT_MSG = "More than 1 Top-Level Parent Sealed Interfaces will be generated. Check your profiles mapping."; default Map> checkUniqueParentInterfacePresence(final Map>> sealedInterfacesToGenerate) { diff --git a/src/main/java/org/jisel/handlers/SealForProfileHandler.java b/src/main/java/org/jisel/handlers/SealForProfileHandler.java index 759261d..890708b 100644 --- a/src/main/java/org/jisel/handlers/SealForProfileHandler.java +++ b/src/main/java/org/jisel/handlers/SealForProfileHandler.java @@ -38,14 +38,24 @@ public final class SealForProfileHandler implements JiselAnnotationHandler { + final private AnnotationInfoCollectionHandler annotationInfoCollectionHandler; + final private UniqueParentInterfaceHandler uniqueParentInterfaceHandler; + final private ParentChildInheritanceHandler parentChildInheritanceHandler; + + public SealForProfileHandler() { + this.annotationInfoCollectionHandler = new SealForProfileInfoCollectionHandler(); + this.uniqueParentInterfaceHandler = new SealForProfileUniqueParentInterfaceHandler(); + this.parentChildInheritanceHandler = new SealForProfileParentChildInheritanceHandler(); + } + @Override public Map handleAnnotatedElements(final ProcessingEnvironment processingEnv, final Set allAnnotatedElements, final Map>> sealedInterfacesToGenerateByBloatedInterface, final Map>> sealedInterfacesPermitsByBloatedInterface) { - new SealForProfileInfoCollectionHandler().populateSealedInterfacesMap(processingEnv, allAnnotatedElements, sealedInterfacesToGenerateByBloatedInterface); - var statusReport = new SealForProfileUniqueParentInterfaceHandler().checkAndHandleUniqueParentInterface(sealedInterfacesToGenerateByBloatedInterface); - new SealForProfileParentChildInheritanceHandler().buildInheritanceRelations(sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface); + annotationInfoCollectionHandler.populateSealedInterfacesMap(processingEnv, allAnnotatedElements, sealedInterfacesToGenerateByBloatedInterface); + var statusReport = uniqueParentInterfaceHandler.checkAndHandleUniqueParentInterface(sealedInterfacesToGenerateByBloatedInterface); + parentChildInheritanceHandler.buildInheritanceRelations(sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface); return statusReport; } } From d4cc9e4b89a59fe18ac436a223263cc18c846fcb Mon Sep 17 00:00:00 2001 From: Mohamed Ashraf Bayor Date: Fri, 10 Dec 2021 17:47:38 -0500 Subject: [PATCH 04/11] JIS-01: completed final class generation logic + parent interfaces of bloatedinterf. about to start report generation --- .../org/jisel/JiselAnnotationProcessor.java | 47 +++++----- .../org/jisel/generator/CodeGenerator.java | 70 +++++++++++--- .../jisel/generator/FinalClassGenerator.java | 77 ++++++++++++++++ .../generator/JavaxGeneratedGenerator.java | 4 +- .../generator/JiselFinalClassGenerator.java | 25 ----- ...ortGenerator.java => ReportGenerator.java} | 2 +- .../SealedInterfaceContentGenerator.java | 92 +++++++++++-------- .../SealedInterfaceSourceFileGenerator.java | 70 ++++++++++---- .../org/jisel/generator/StringGenerator.java | 68 +++++++++++--- .../jisel/handlers/AddToProfileHandler.java | 5 +- .../handlers/JiselAnnotationHandler.java | 23 ++--- .../jisel/handlers/SealForProfileHandler.java | 8 +- 12 files changed, 343 insertions(+), 148 deletions(-) create mode 100644 src/main/java/org/jisel/generator/FinalClassGenerator.java delete mode 100644 src/main/java/org/jisel/generator/JiselFinalClassGenerator.java rename src/main/java/org/jisel/generator/{JiselReportGenerator.java => ReportGenerator.java} (97%) diff --git a/src/main/java/org/jisel/JiselAnnotationProcessor.java b/src/main/java/org/jisel/JiselAnnotationProcessor.java index c3a7816..7b4f088 100644 --- a/src/main/java/org/jisel/JiselAnnotationProcessor.java +++ b/src/main/java/org/jisel/JiselAnnotationProcessor.java @@ -23,13 +23,12 @@ import com.google.auto.service.AutoService; import org.jisel.generator.SealedInterfaceSourceFileGenerator; +import org.jisel.generator.StringGenerator; import org.jisel.handlers.AddToProfileHandler; import org.jisel.handlers.JiselAnnotationHandler; import org.jisel.handlers.SealForProfileHandler; import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.Filer; -import javax.annotation.processing.FilerException; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; @@ -50,27 +49,31 @@ import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableSet; import static java.util.stream.Collectors.joining; - -@SupportedAnnotationTypes({"org.jisel.SealForProfile", "org.jisel.SealForProfiles", "org.jisel.SealForProfile.SealForProfilez", - "org.jisel.AddToProfile", "org.jisel.AddToProfiles", "org.jisel.AddToProfile.AddToProfilez"}) +import static org.jisel.generator.StringGenerator.ORG_JISEL_ADD_TO_PROFILE; +import static org.jisel.generator.StringGenerator.ORG_JISEL_ADD_TO_PROFILES; +import static org.jisel.generator.StringGenerator.ORG_JISEL_ADD_TO_PROFILEZ; +import static org.jisel.generator.StringGenerator.ORG_JISEL_SEAL_FOR_PROFILE; +import static org.jisel.generator.StringGenerator.ORG_JISEL_SEAL_FOR_PROFILES; +import static org.jisel.generator.StringGenerator.ORG_JISEL_SEAL_FOR_PROFILEZ; + +@SupportedAnnotationTypes({ORG_JISEL_SEAL_FOR_PROFILE, ORG_JISEL_SEAL_FOR_PROFILES, ORG_JISEL_SEAL_FOR_PROFILEZ, + ORG_JISEL_ADD_TO_PROFILE, ORG_JISEL_ADD_TO_PROFILES, ORG_JISEL_ADD_TO_PROFILEZ}) @SupportedSourceVersion(SourceVersion.RELEASE_17) @AutoService(Processor.class) -public class JiselAnnotationProcessor extends AbstractProcessor { +public class JiselAnnotationProcessor extends AbstractProcessor implements StringGenerator { private final Logger log = Logger.getLogger(JiselAnnotationProcessor.class.getName()); - private static final String SEAL_FOR_PROFILE = "SealForProfile"; - private static final String ADD_TO_PROFILE = "AddToProfile"; - - private static final String STATUS_REPORT_TITLE = "JISEL GENERATION REPORT"; + private final JiselAnnotationHandler sealForProfileHandler; - private JiselAnnotationHandler sealForProfileHandler; + private final JiselAnnotationHandler addToProfileHandler; - private JiselAnnotationHandler addToProfileHandler; + private final SealedInterfaceSourceFileGenerator sealedInterfaceSourceFileGenerator; public JiselAnnotationProcessor() { this.sealForProfileHandler = new SealForProfileHandler(); this.addToProfileHandler = new AddToProfileHandler(); + this.sealedInterfaceSourceFileGenerator = new SealedInterfaceSourceFileGenerator(); } @Override @@ -90,7 +93,7 @@ public boolean process(final Set annotations, final Round sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface ); - displayStatusReport(statusReport, SealForProfile.class); + displayStatusReport(statusReport, SEAL_FOR_PROFILE); // process all child classes or interfaces annotated with @AddToProfile statusReport = addToProfileHandler.handleAnnotatedElements( @@ -99,17 +102,17 @@ public boolean process(final Set annotations, final Round unmodifiableMap(sealedInterfacesToGenerateByBloatedInterface), sealedInterfacesPermitsByBloatedInterface ); - displayStatusReport(statusReport, AddToProfile.class); + displayStatusReport(statusReport, ADD_TO_PROFILE); try { - new SealedInterfaceSourceFileGenerator(processingEnv).createSourceFiles(sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface); + var generatedFiles = sealedInterfaceSourceFileGenerator.createSourceFiles(processingEnv, sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface); + if (!generatedFiles.isEmpty()) { + log.info(() -> format("%s:%n%s", FILE_GENERATION_SUCCESS, generatedFiles.stream().collect(joining("\n")))); + } } catch (IOException e) { - log.log(Level.SEVERE, format("Error generating sealed interfaces", e)); + log.log(Level.SEVERE, FILE_GENERATION_ERROR, e); } - System.out.println(" \n\n@@@@@@@@@@@@@ sealedInterfacesToGenerateByBloatedInterface: " + sealedInterfacesToGenerateByBloatedInterface); - System.out.println("\n~~~~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~ \n sealedInterfacesPermitsByBloatedInterface" + sealedInterfacesPermitsByBloatedInterface); - return true; } @@ -125,15 +128,15 @@ private void populateAllAnnotatedElementsSets(final Set a } } - private void displayStatusReport(final Map statusReport, final Class annotation) { + private void displayStatusReport(final Map statusReport, final String annotationName) { if (!statusReport.values().stream().collect(joining()).isBlank()) { - var output = new StringBuilder(format("%n%s - @%s(s)%n", STATUS_REPORT_TITLE, annotation.getSimpleName())); + var output = new StringBuilder(format("%n%s - @%s(s)%n", STATUS_REPORT_TITLE, annotationName)); statusReport.entrySet().forEach(mapEntry -> { if (!mapEntry.getValue().isBlank()) { output.append(format("\t> %s: %s%n", mapEntry.getKey().getSimpleName().toString(), mapEntry.getValue())); } }); - log.warning(output.toString()); + log.warning(output::toString); } } } \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/CodeGenerator.java b/src/main/java/org/jisel/generator/CodeGenerator.java index 3621937..8ccc9ce 100644 --- a/src/main/java/org/jisel/generator/CodeGenerator.java +++ b/src/main/java/org/jisel/generator/CodeGenerator.java @@ -21,12 +21,16 @@ */ package org.jisel.generator; +import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; +import javax.lang.model.type.ExecutableType; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import static java.lang.String.format; +import static java.util.Arrays.asList; import static java.util.stream.Collectors.joining; /** @@ -37,24 +41,28 @@ public sealed interface CodeGenerator permits JavaxGeneratedGenerator, ExtendsGe /** * Generates piece of code requested, based on the parameters provided in the params object and appends it to the provided recordClassContent param * - * @param sealedInterfaceContent Stringbuilder object containing the record class code being generated - * @param params expected parameters. restricted to paremeters and values expected by the implementing class + * @param classOrInterfaceContent Stringbuilder object containing the record class code being generated + * @param params expected parameters. restricted to paremeters and values expected by the implementing class */ - void generateCode(StringBuilder sealedInterfaceContent, List params); + void generateCode(StringBuilder classOrInterfaceContent, List params); } sealed interface ExtendsGenerator extends CodeGenerator, StringGenerator permits JiselExtendsGenerator { - void generateExtendsClauseFromPermitsMapAndProcessedProfile(StringBuilder sealedInterfaceContent, Map> permitsMap, String processedProfile, Element bloatedInterfaceElement); + void generateExtendsClauseFromPermitsMapAndProcessedProfile(ProcessingEnvironment processingEnvironment, StringBuilder sealedInterfaceContent, Map> permitsMap, String processedProfile, Element bloatedInterfaceElement); @Override - default void generateCode(final StringBuilder sealedInterfaceContent, final List params) { - sealedInterfaceContent.append(format( + default void generateCode(final StringBuilder classOrInterfaceContent, final List params) { + classOrInterfaceContent.append(format( " %s %s ", - EXTENDS, - params.stream().map(listEntry -> removeSeparator(listEntry)).collect(joining(COMMA_SEPARATOR + WHITESPACE)) + isInterface(classOrInterfaceContent.toString()) ? EXTENDS : IMPLEMENTS, + params.stream().map(StringGenerator::removeSeparator).collect(joining(COMMA_SEPARATOR + WHITESPACE)) )); } + + private boolean isInterface(final String classOrInterfaceContent) { + return classOrInterfaceContent.contains(INTERFACE + WHITESPACE) && !classOrInterfaceContent.contains(CLASS + WHITESPACE); + } } sealed interface PermitsGenerator extends CodeGenerator, StringGenerator permits JiselPermitsGenerator { @@ -62,25 +70,59 @@ sealed interface PermitsGenerator extends CodeGenerator, StringGenerator permits void generatePermitsClauseFromPermitsMapAndProcessedProfile(StringBuilder sealedInterfaceContent, Map> permitsMap, String processedProfile, Element bloatedInterfaceElement); @Override - default void generateCode(final StringBuilder sealedInterfaceContent, final List params) { - sealedInterfaceContent.append(format( + default void generateCode(final StringBuilder classOrInterfaceContent, final List params) { + classOrInterfaceContent.append(format( " %s %s ", PERMITS, - params.stream().map(listEntry -> removeSeparator(listEntry)).collect(joining(COMMA_SEPARATOR + WHITESPACE)) + params.stream().map(StringGenerator::removeSeparator).collect(joining(COMMA_SEPARATOR + WHITESPACE)) )); } + + default void addFinalClassToPermitsMap(final Map> permitsMap, final Element bloatedInterfaceElement) { + var finalClassName = UNDERSCORE + bloatedInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX; + var childlessProfiles = permitsMap.values().stream() + .flatMap(Collection::stream) + .distinct() + .filter(childProfileName -> permitsMap.keySet().stream().noneMatch(parentProfile -> parentProfile.equals(childProfileName))) + .filter(childProfileName -> !finalClassName.equals(childProfileName)) // if finalClassName found remove it from the new list + .toList(); + childlessProfiles.forEach(childLessProfile -> permitsMap.put(childLessProfile, asList(finalClassName))); + } } sealed interface MethodsGenerator extends CodeGenerator, StringGenerator permits JiselMethodsGenerator { - void generateMethodsFromElementsSet(StringBuilder sealedInterfaceContent, Set methodsSet); + void generateAbstractMethodsFromElementsSet(StringBuilder sealedInterfaceContent, Set methodsSet); + + void generateEmptyConcreteMethodsFromElementsSet(StringBuilder sealedInterfaceContent, Set methodsSet); @Override - default void generateCode(final StringBuilder sealedInterfaceContent, final List params) { - params.forEach(methodDefinition -> sealedInterfaceContent.append(format("\t%s;%n", methodDefinition))); + default void generateCode(final StringBuilder classOrInterfaceContent, final List params) { + params.forEach(methodDefinition -> classOrInterfaceContent.append(format("\t%s%n", methodDefinition))); } default boolean methodHasArguments(final Element element) { return element.toString().indexOf(CLOSING_PARENTHESIS) - element.toString().indexOf(OPENING_PARENTHESIS) > 1; } + + default String generateReturnType(final Element methodElement) { + return ((ExecutableType) methodElement.asType()).getReturnType().toString(); + } + + default String generateMethodNameAndParameters(final Element methodElement) { + var output = methodElement.toString(); + if (methodHasArguments(methodElement)) { + int paramIdx = 0; + while (output.contains(COMMA_SEPARATOR)) { + output = output.replace(COMMA_SEPARATOR, WHITESPACE + PARAMETER_PREFIX + paramIdx + TEMP_PLACEHOLDER + WHITESPACE); + paramIdx++; + } + output = output.replace(CLOSING_PARENTHESIS, WHITESPACE + PARAMETER_PREFIX + paramIdx + CLOSING_PARENTHESIS).replaceAll(TEMP_PLACEHOLDER, COMMA_SEPARATOR); + } + return output; + } + + default String generateThrownExceptions(final Element methodElement) { + return ((ExecutableType) methodElement.asType()).getThrownTypes().stream().map(Object::toString).collect(joining(COMMA_SEPARATOR + WHITESPACE)); + } } \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/FinalClassGenerator.java b/src/main/java/org/jisel/generator/FinalClassGenerator.java new file mode 100644 index 0000000..a770c71 --- /dev/null +++ b/src/main/java/org/jisel/generator/FinalClassGenerator.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2021-2022 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; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import java.util.List; +import java.util.Map; + +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toSet; + +public class FinalClassGenerator implements StringGenerator { + + private final CodeGenerator javaxGeneratedGenerator; + private final ExtendsGenerator extendsGenerator; + private final MethodsGenerator methodsGenerator; + + public FinalClassGenerator() { + this.javaxGeneratedGenerator = new JavaxGeneratedGenerator(); + this.extendsGenerator = new JiselExtendsGenerator(); + this.methodsGenerator = new JiselMethodsGenerator(); + } + + public String generateFinalClassContent(final ProcessingEnvironment processingEnvironment, final Element bloatedInterfaceElement, final Map> sealedInterfacesPermitsMap) { + var finalClassName = UNDERSCORE + bloatedInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX; + var finalClassContent = new StringBuilder(); + // package name + generatePackageName(bloatedInterfaceElement).ifPresent(name -> finalClassContent.append(format("%s %s;%n%n", PACKAGE, name))); + // javaxgenerated + javaxGeneratedGenerator.generateCode(finalClassContent, null); + // public final class + finalClassContent.append(format( + "%s %s ", + PUBLIC_FINAL_CLASS, + finalClassName + )); + // list of implements + extendsGenerator.generateExtendsClauseFromPermitsMapAndProcessedProfile(processingEnvironment, finalClassContent, sealedInterfacesPermitsMap, finalClassName, bloatedInterfaceElement); + // opening bracket after permits list + finalClassContent.append(format(" %s%n ", OPENING_BRACKET)); + // list of methods + methodsGenerator.generateEmptyConcreteMethodsFromElementsSet( + finalClassContent, + processingEnvironment.getElementUtils().getAllMembers((TypeElement) bloatedInterfaceElement).stream() + .filter(element -> ElementKind.METHOD.equals(element.getKind())) + .filter(element -> asList(METHODS_TO_EXCLUDE).stream().noneMatch(excludedMeth -> element.toString().contains(excludedMeth + OPENING_PARENTHESIS))) + .collect(toSet()) + ); + // closing bracket + finalClassContent.append(CLOSING_BRACKET); + // + return removeDoubleSpaceOccurrences(finalClassContent.toString()); + } +} diff --git a/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java b/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java index b46c2c1..636ca4b 100644 --- a/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java +++ b/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java @@ -65,7 +65,7 @@ private String getAppVersion() { } @Override - public void generateCode(final StringBuilder sealedInterfaceContent, final List params) { - buildGeneratedAnnotationSection(sealedInterfaceContent); + public void generateCode(final StringBuilder classOrInterfaceContent, final List params) { + buildGeneratedAnnotationSection(classOrInterfaceContent); } } \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/JiselFinalClassGenerator.java b/src/main/java/org/jisel/generator/JiselFinalClassGenerator.java deleted file mode 100644 index 5b8325a..0000000 --- a/src/main/java/org/jisel/generator/JiselFinalClassGenerator.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2021-2022 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; - -public class JiselFinalClassGenerator { -} diff --git a/src/main/java/org/jisel/generator/JiselReportGenerator.java b/src/main/java/org/jisel/generator/ReportGenerator.java similarity index 97% rename from src/main/java/org/jisel/generator/JiselReportGenerator.java rename to src/main/java/org/jisel/generator/ReportGenerator.java index ba7c954..8b3da49 100644 --- a/src/main/java/org/jisel/generator/JiselReportGenerator.java +++ b/src/main/java/org/jisel/generator/ReportGenerator.java @@ -21,5 +21,5 @@ */ package org.jisel.generator; -public class JiselReportGenerator { +public class ReportGenerator { } diff --git a/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java b/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java index 13b41b5..05c65d9 100644 --- a/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java +++ b/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java @@ -21,16 +21,14 @@ */ package org.jisel.generator; +import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; -import javax.lang.model.type.ExecutableType; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Set; import static java.lang.String.format; -import static java.util.stream.Collectors.joining; public final class SealedInterfaceContentGenerator implements StringGenerator { @@ -46,29 +44,30 @@ public SealedInterfaceContentGenerator() { this.methodsGenerator = new JiselMethodsGenerator(); } - public String generateContent(final Map.Entry> sealedInterfacesToGenerateEntrySet, final Element bloatedInterfaceElement, - final Map>> sealedInterfacesPermitsByBloatedInterface) { + public String generateSealedInterfaceContent(final ProcessingEnvironment processingEnvironment, + final Map.Entry> sealedInterfacesToGenerateMapEntrySet, + final Element bloatedInterfaceElement, + final Map> sealedInterfacesPermitsMap) { var sealedInterfaceContent = new StringBuilder(); // package name generatePackageName(bloatedInterfaceElement).ifPresent(name -> sealedInterfaceContent.append(format("%s %s;%n%n", PACKAGE, name))); // javaxgenerated javaxGeneratedGenerator.generateCode(sealedInterfaceContent, null); // public sealed interface - var profile = sealedInterfacesToGenerateEntrySet.getKey(); + var profile = sealedInterfacesToGenerateMapEntrySet.getKey(); sealedInterfaceContent.append(format( "%s %s ", PUBLIC_SEALED_INTERFACE, sealedInterfaceNameConvention(profile, bloatedInterfaceElement) )); // list of extends - extendsGenerator.generateExtendsClauseFromPermitsMapAndProcessedProfile(sealedInterfaceContent, sealedInterfacesPermitsByBloatedInterface.get(bloatedInterfaceElement), profile, bloatedInterfaceElement); + extendsGenerator.generateExtendsClauseFromPermitsMapAndProcessedProfile(processingEnvironment, sealedInterfaceContent, sealedInterfacesPermitsMap, profile, bloatedInterfaceElement); // list of permits - permitsGenerator.generatePermitsClauseFromPermitsMapAndProcessedProfile(sealedInterfaceContent, sealedInterfacesPermitsByBloatedInterface.get(bloatedInterfaceElement), profile, bloatedInterfaceElement); - // TODO generate permits for Final classes + permitsGenerator.generatePermitsClauseFromPermitsMapAndProcessedProfile(sealedInterfaceContent, sealedInterfacesPermitsMap, profile, bloatedInterfaceElement); // opening bracket after permits list sealedInterfaceContent.append(format(" %s%n ", OPENING_BRACKET)); // list of methods - methodsGenerator.generateMethodsFromElementsSet(sealedInterfaceContent, sealedInterfacesToGenerateEntrySet.getValue()); + methodsGenerator.generateAbstractMethodsFromElementsSet(sealedInterfaceContent, sealedInterfacesToGenerateMapEntrySet.getValue()); // closing bracket sealedInterfaceContent.append(CLOSING_BRACKET); // @@ -78,14 +77,29 @@ public String generateContent(final Map.Entry> sealedInterf final class JiselExtendsGenerator implements ExtendsGenerator { @Override - public void generateExtendsClauseFromPermitsMapAndProcessedProfile(final StringBuilder sealedInterfaceContent, final Map> permitsMap, final String processedProfile, final Element bloatedInterfaceElement) { + public void generateExtendsClauseFromPermitsMapAndProcessedProfile(final ProcessingEnvironment processingEnvironment, + final StringBuilder sealedInterfaceContent, + final Map> permitsMap, + final String processedProfile, + final Element bloatedInterfaceElement) { Optional.ofNullable(permitsMap).ifPresent(nonNullPermitsMap -> { - var childrenList = nonNullPermitsMap.entrySet().stream() + var parentList = nonNullPermitsMap.entrySet().stream() .filter(permitsMapEntry -> permitsMapEntry.getValue().contains(processedProfile)) .map(permitsMapEntry -> sealedInterfaceNameConvention(permitsMapEntry.getKey(), bloatedInterfaceElement)) .toList(); - if (!childrenList.isEmpty()) { - generateCode(sealedInterfaceContent, childrenList); + if (!parentList.isEmpty()) { + generateCode(sealedInterfaceContent, parentList); + } else { + // only for bloatedInterface sealed interface generation, add interfaces it extends if any + if (bloatedInterfaceElement.getSimpleName().toString().equals(processedProfile)) { + generateCode( + sealedInterfaceContent, + processingEnvironment.getTypeUtils().directSupertypes(bloatedInterfaceElement.asType()).stream() + .map(Object::toString) + .filter(superType -> !superType.contains(JAVA_LANG_OBJECT)) + .toList() + ); + } } }); } @@ -93,10 +107,16 @@ public void generateExtendsClauseFromPermitsMapAndProcessedProfile(final StringB final class JiselPermitsGenerator implements PermitsGenerator { @Override - public void generatePermitsClauseFromPermitsMapAndProcessedProfile(final StringBuilder sealedInterfaceContent, final Map> permitsMap, final String processedProfile, final Element bloatedInterfaceElement) { + public void generatePermitsClauseFromPermitsMapAndProcessedProfile(final StringBuilder sealedInterfaceContent, + final Map> permitsMap, + final String processedProfile, + final Element bloatedInterfaceElement) { + addFinalClassToPermitsMap(permitsMap, bloatedInterfaceElement); var permitsMapOpt = Optional.ofNullable(permitsMap); if (permitsMapOpt.isPresent() && !permitsMapOpt.get().isEmpty()) { - Optional.ofNullable(permitsMapOpt.get().get(processedProfile)).ifPresent(childrenList -> generateCode(sealedInterfaceContent, sealedInterfaceNameConventionForList(childrenList, bloatedInterfaceElement))); + Optional.ofNullable(permitsMapOpt.get().get(processedProfile)).ifPresent( + childrenList -> generateCode(sealedInterfaceContent, sealedInterfaceNameConventionForList(childrenList, bloatedInterfaceElement)) + ); } } } @@ -104,7 +124,7 @@ public void generatePermitsClauseFromPermitsMapAndProcessedProfile(final StringB final class JiselMethodsGenerator implements MethodsGenerator { @Override - public void generateMethodsFromElementsSet(final StringBuilder sealedInterfaceContent, final Set methodsSet) { + public void generateAbstractMethodsFromElementsSet(final StringBuilder sealedInterfaceContent, final Set methodsSet) { generateCode( sealedInterfaceContent, methodsSet.stream() @@ -112,30 +132,28 @@ public void generateMethodsFromElementsSet(final StringBuilder sealedInterfaceCo "%s %s%s", generateReturnType(element), generateMethodNameAndParameters(element), - generateThrownExceptions(element).isEmpty() ? EMPTY_STRING : format(" throws %s", generateThrownExceptions(element)) + generateThrownExceptions(element).isEmpty() + ? SEMICOLON + : format(" throws %s", generateThrownExceptions(element) + SEMICOLON) )) .toList() ); } - private String generateReturnType(final Element methodElement) { - return ((ExecutableType) methodElement.asType()).getReturnType().toString(); - } - - private String generateMethodNameAndParameters(final Element methodElement) { - var output = methodElement.toString(); - if (methodHasArguments(methodElement)) { - int paramIdx = 0; - while (output.contains(COMMA_SEPARATOR)) { - output = output.replace(COMMA_SEPARATOR, WHITESPACE + PARAMETER_PREFIX + paramIdx + TEMP_PLACEHOLDER + WHITESPACE); - paramIdx++; - } - output = output.replace(CLOSING_PARENTHESIS, WHITESPACE + PARAMETER_PREFIX + paramIdx + CLOSING_PARENTHESIS).replaceAll(TEMP_PLACEHOLDER, COMMA_SEPARATOR); - } - return output; - } - - private String generateThrownExceptions(final Element methodElement) { - return ((ExecutableType) methodElement.asType()).getThrownTypes().stream().map(Objects::toString).collect(joining(COMMA_SEPARATOR + WHITESPACE)); + @Override + public void generateEmptyConcreteMethodsFromElementsSet(final StringBuilder sealedInterfaceContent, final Set methodsSet) { + generateCode( + sealedInterfaceContent, + methodsSet.stream() + .map(methodElement -> format( + "public %s %s %s", + generateReturnType(methodElement), + generateMethodNameAndParameters(methodElement), + generateThrownExceptions(methodElement).isEmpty() + ? OPENING_BRACKET + generateDefaultReturnValueForMethod(methodElement) + SEMICOLON + CLOSING_BRACKET + : format("throws %s", generateThrownExceptions(methodElement) + WHITESPACE + OPENING_BRACKET + generateDefaultReturnValueForMethod(methodElement) + SEMICOLON + CLOSING_BRACKET) + )) + .toList() + ); } } \ 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 index 865e64f..265a561 100644 --- a/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java +++ b/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java @@ -26,38 +26,72 @@ import javax.lang.model.element.Element; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; public class SealedInterfaceSourceFileGenerator implements StringGenerator { - private final ProcessingEnvironment processingEnvironment; private final SealedInterfaceContentGenerator sealedInterfaceContentGenerator; + private final FinalClassGenerator finalClassGenerator; - public SealedInterfaceSourceFileGenerator(final ProcessingEnvironment processingEnvironment) { - this.processingEnvironment = processingEnvironment; + public SealedInterfaceSourceFileGenerator() { this.sealedInterfaceContentGenerator = new SealedInterfaceContentGenerator(); + this.finalClassGenerator = new FinalClassGenerator(); } - public void createSourceFiles(final Map>> sealedInterfacesToGenerateByBloatedInterface, - final Map>> sealedInterfacesPermitsByBloatedInterface) throws IOException { - for (var sealedInterfacesToGenerateMapEntry : sealedInterfacesToGenerateByBloatedInterface.entrySet()) { - var bloatedInterfaceElement = sealedInterfacesToGenerateMapEntry.getKey(); - var packageNameOpt = generatePackageName(bloatedInterfaceElement); - for (var sealedInterfacesToGenerate : sealedInterfacesToGenerateMapEntry.getValue().entrySet()) { + public List createSourceFiles(final ProcessingEnvironment processingEnvironment, + final Map>> sealedInterfacesToGenerateByBloatedInterface, + final Map>> sealedInterfacesPermitsByBloatedInterface) throws IOException { + var generatedFiles = new ArrayList(); + for (var sealedInterfacesToGenerateMapEntrySet : sealedInterfacesToGenerateByBloatedInterface.entrySet()) { + var bloatedInterfaceElement = sealedInterfacesToGenerateMapEntrySet.getKey(); + for (var sealedInterfacesToGenerate : sealedInterfacesToGenerateMapEntrySet.getValue().entrySet()) { var profile = sealedInterfacesToGenerate.getKey(); var generatedSealedInterfaceName = sealedInterfaceNameConvention(profile, bloatedInterfaceElement); - try { - var fileObject = processingEnvironment.getFiler().createSourceFile(packageNameOpt.isPresent() ? packageNameOpt.get() + DOT + generatedSealedInterfaceName : generatedSealedInterfaceName); - try (var out = new PrintWriter(fileObject.openWriter())) { - out.println(sealedInterfaceContentGenerator.generateContent(sealedInterfacesToGenerate, sealedInterfacesToGenerateMapEntry.getKey(), sealedInterfacesPermitsByBloatedInterface)); - } - } catch (FilerException e) { - // File was already generated - do nothing - } + createSealedInterfaceFile(processingEnvironment, generatedFiles, sealedInterfacesPermitsByBloatedInterface.get(bloatedInterfaceElement), bloatedInterfaceElement, sealedInterfacesToGenerate, generatedSealedInterfaceName); } - // TODO run the finalclass logic here before generating the files - 1 per bloatedinterf: "_" + BloatedInterfceName + "FinalClass" + createFinalClassFile(processingEnvironment, generatedFiles, bloatedInterfaceElement, sealedInterfacesPermitsByBloatedInterface.get(bloatedInterfaceElement)); + } + return generatedFiles; + } + + private void createSealedInterfaceFile(final ProcessingEnvironment processingEnvironment, + final List generatedFiles, + final Map> sealedInterfacesPermitsMap, + final Element bloatedInterfaceElement, + final Map.Entry> sealedInterfacesToGenerateMapEntrySet, + final String generatedSealedInterfaceName) throws IOException { + var packageNameOpt = generatePackageName(bloatedInterfaceElement); + try { + var qualifiedName = packageNameOpt.isPresent() ? packageNameOpt.get() + DOT + generatedSealedInterfaceName : generatedSealedInterfaceName; + var fileObject = processingEnvironment.getFiler().createSourceFile(qualifiedName); + try (var out = new PrintWriter(fileObject.openWriter())) { + out.println(sealedInterfaceContentGenerator.generateSealedInterfaceContent(processingEnvironment, sealedInterfacesToGenerateMapEntrySet, bloatedInterfaceElement, sealedInterfacesPermitsMap)); + } + generatedFiles.add(qualifiedName); + } catch (FilerException e) { + // File was already generated - do nothing + } + } + + private void createFinalClassFile(final ProcessingEnvironment processingEnvironment, + final List generatedFiles, + final Element bloatedInterfaceElement, + final Map> sealedInterfacesPermitsMap) throws IOException { + var packageNameOpt = generatePackageName(bloatedInterfaceElement); + try { + var qualifiedName = packageNameOpt.isPresent() + ? packageNameOpt.get() + DOT + UNDERSCORE + bloatedInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX + : UNDERSCORE + bloatedInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX; + var fileObject = processingEnvironment.getFiler().createSourceFile(qualifiedName); + try (var out = new PrintWriter(fileObject.openWriter())) { + out.println(finalClassGenerator.generateFinalClassContent(processingEnvironment, bloatedInterfaceElement, sealedInterfacesPermitsMap)); + } + generatedFiles.add(qualifiedName); + } catch (FilerException e) { + // File was already generated - do nothing } } } \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/StringGenerator.java b/src/main/java/org/jisel/generator/StringGenerator.java index 962c13e..77bd735 100644 --- a/src/main/java/org/jisel/generator/StringGenerator.java +++ b/src/main/java/org/jisel/generator/StringGenerator.java @@ -1,8 +1,10 @@ package org.jisel.generator; import javax.lang.model.element.Element; +import javax.lang.model.type.ExecutableType; import java.util.List; import java.util.Optional; +import java.util.function.UnaryOperator; import static java.lang.String.format; import static java.util.Arrays.asList; @@ -11,11 +13,16 @@ public interface StringGenerator { String COMMA_SEPARATOR = ","; + String SEMICOLON = ";"; String WHITESPACE = " "; String SEALED_PREFIX = "Sealed"; String PACKAGE = "package"; + String INTERFACE = "interface"; + String CLASS = "class"; String PUBLIC_SEALED_INTERFACE = "public sealed interface"; + String PUBLIC_FINAL_CLASS = "public final class"; String EXTENDS = "extends"; + String IMPLEMENTS = "implements"; String PERMITS = "permits"; String OPENING_BRACKET = "{"; String CLOSING_BRACKET = "}"; @@ -25,29 +32,59 @@ public interface StringGenerator { String DOT = "."; String PARAMETER_PREFIX = "param"; String TEMP_PLACEHOLDER = "@"; + String UNDERSCORE = "_"; + String FINAL_CLASS_SUFFIX = "FinalCass"; + String RETURN = "return"; + String JAVA_LANG_OBJECT = "java.lang.Object"; - default String removeSeparator(final String text) { + String SEAL_FOR_PROFILE = "SealForProfile"; + String ADD_TO_PROFILE = "AddToProfile"; + + String STATUS_REPORT_TITLE = "JISEL GENERATION REPORT"; + + String FILE_GENERATION_ERROR = "Error generating sealed interfaces"; + String FILE_GENERATION_SUCCESS = "Successfully generated"; + + String ORG_JISEL_SEAL_FOR_PROFILE = "org.jisel.SealForProfile"; + String ORG_JISEL_SEAL_FOR_PROFILES = "org.jisel.SealForProfiles"; + String ORG_JISEL_SEAL_FOR_PROFILEZ = "org.jisel.SealForProfile.SealForProfilez"; + String ORG_JISEL_ADD_TO_PROFILE = "org.jisel.AddToProfile"; + String ORG_JISEL_ADD_TO_PROFILES = "org.jisel.AddToProfiles"; + String ORG_JISEL_ADD_TO_PROFILEZ = "org.jisel.AddToProfile.AddToProfilez"; + + String DEFAULT_BOOLEAN_VALUE = "false"; + String DEFAULT_NUMBER_VALUE = "0"; + String DEFAULT_NULL_VALUE = "null"; + + String[] METHODS_TO_EXCLUDE = {"getClass", "wait", "notifyAll", "hashCode", "equals", "notify", "toString"}; + + String ADD_TO_PROFILE_REPORT_MSG = "1 or many provided profiles are not found in provided parent interfaces. Check your profiles names."; + String REPORT_MSG = "More than 1 Top-Level Parent Sealed Interfaces will be generated. Check your profiles mapping."; + + static String removeSeparator(final String text) { return asList(text.split(COMMA_SEPARATOR)).stream().collect(joining()); } default String sealedInterfaceNameConvention(final String profile, final Element interfaceElement) { - return format( + var nameSuffix = removeSeparator(profile).equals(interfaceElement.getSimpleName().toString()) ? EMPTY_STRING : interfaceElement.getSimpleName().toString(); + // any profile name starting w _ is returned as it is + return removeSeparator(profile).startsWith(UNDERSCORE) ? removeSeparator(profile) : format( "%s%s%s", SEALED_PREFIX, removeSeparator(profile), - removeSeparator(profile).equals(interfaceElement.getSimpleName().toString()) ? EMPTY_STRING : interfaceElement.getSimpleName().toString() + nameSuffix ); } default List sealedInterfaceNameConventionForList(final List profiles, final Element interfaceElement) { + final UnaryOperator nameSuffix = profile -> removeSeparator(profile).equals(interfaceElement.getSimpleName().toString()) ? EMPTY_STRING : interfaceElement.getSimpleName().toString(); return profiles.stream() - .map(profile -> - format( - "%s%s%s", - SEALED_PREFIX, - removeSeparator(profile), - removeSeparator(profile).equals(interfaceElement.getSimpleName().toString()) ? EMPTY_STRING : interfaceElement.getSimpleName().toString() - )).toList(); + .map(profile -> removeSeparator(profile).startsWith(UNDERSCORE) ? removeSeparator(profile) : format( + "%s%s%s", + SEALED_PREFIX, + removeSeparator(profile), + nameSuffix.apply(profile) + )).toList(); } default Optional generatePackageName(final Element bloatedInterfaceName) { @@ -57,6 +94,15 @@ default Optional generatePackageName(final Element bloatedInterfaceName) } default String removeDoubleSpaceOccurrences(final String text) { - return text.replaceAll(WHITESPACE + WHITESPACE, WHITESPACE); + return text.replace(WHITESPACE + WHITESPACE, WHITESPACE); + } + + default String generateDefaultReturnValueForMethod(final Element methodElement) { + return switch (((ExecutableType) methodElement.asType()).getReturnType().getKind()) { + case BOOLEAN -> RETURN + WHITESPACE + DEFAULT_BOOLEAN_VALUE; + case VOID -> RETURN; + case BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, CHAR -> RETURN + WHITESPACE + DEFAULT_NUMBER_VALUE; + default -> RETURN + WHITESPACE + DEFAULT_NULL_VALUE; + }; } } \ No newline at end of file diff --git a/src/main/java/org/jisel/handlers/AddToProfileHandler.java b/src/main/java/org/jisel/handlers/AddToProfileHandler.java index d4e0bba..56a69e2 100644 --- a/src/main/java/org/jisel/handlers/AddToProfileHandler.java +++ b/src/main/java/org/jisel/handlers/AddToProfileHandler.java @@ -27,7 +27,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import static java.util.Arrays.asList; @@ -95,7 +94,7 @@ private String processAnnotatedElement(final ProcessingEnvironment processingEnv private Set buildProvidedInterfacesSet(final ProcessingEnvironment processingEnv, final Element annotatedClassOrInterface) { return processingEnv.getTypeUtils().directSupertypes(annotatedClassOrInterface.asType()).stream() - .map(Objects::toString) + .map(Object::toString) .filter(typeString -> !typeString.contains(JAVA_LANG_OBJECT)) .collect(toSet()); } @@ -108,7 +107,7 @@ private boolean updateSealedInterfacesPermitsMapWithProvidedProfiles(final List< var found = false; for (var bloatedInterfaceProvidedProfile : bloatedInterfaceProvidedProfilesList) { // might contain SEPARATOR for (var addToProfileProvidedProfile : addToProfileProvidedProfilesSet) { - if (asList(bloatedInterfaceProvidedProfile.split(SEPARATOR)).contains(addToProfileProvidedProfile)) { + if (asList(bloatedInterfaceProvidedProfile.split(COMMA_SEPARATOR)).contains(addToProfileProvidedProfile)) { sealedInterfacesPermitsByBloatedInterface.get(bloatedInterfaceElement).merge( bloatedInterfaceProvidedProfile, asList(annotatedClassOrInterface.toString()), diff --git a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java index 345ea24..1cb3986 100644 --- a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java +++ b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java @@ -40,13 +40,10 @@ import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet; import static java.util.stream.Stream.concat; +import static org.jisel.generator.StringGenerator.removeSeparator; public sealed interface JiselAnnotationHandler extends StringGenerator permits SealForProfileHandler, AddToProfileHandler, AnnotationInfoCollectionHandler, UniqueParentInterfaceHandler, ParentChildInheritanceHandler { - String SEPARATOR = ","; - String ADD_TO_PROFILE_REPORT_MSG = "1 or many provided profiles are not found in provided parent interfaces. Check your profiles names."; - String JAVA_LANG_OBJECT = "java.lang.Object"; - String ANNOTATION_VALUES_REGEX = "\"([^\"]*)\""; Map handleAnnotatedElements(ProcessingEnvironment processingEnv, @@ -54,12 +51,18 @@ Map handleAnnotatedElements(ProcessingEnvironment processingEnv Map>> sealedInterfacesToGenerateByBloatedInterface, Map>> sealedInterfacesPermitsByBloatedInterface); + /** + * @param processingEnv + * @param annotatedClassOrInterface + * @return + */ default Set buildProvidedProfilesSet(final ProcessingEnvironment processingEnv, final Element annotatedClassOrInterface) { + // annotationRawValueAsString: sample values: singl value "profile1name", array: {@org.jisel.SealForProfile("profile2name"), @org.jisel.SealForProfile("profile3name"),...} var providedProfilesSet = new HashSet(); processingEnv.getElementUtils().getAllAnnotationMirrors(annotatedClassOrInterface).stream() .flatMap(annotationMirror -> annotationMirror.getElementValues().entrySet().stream()) .map(entry -> entry.getValue().toString()) - .forEach(annotationRawValueAsString -> { // annotationRawValueAsString: sample values: singl value "profile1name", array: {@org.jisel.SealForProfile("profile2name"), @org.jisel.SealForProfile("profile3name"),...} + .forEach(annotationRawValueAsString -> { var matcher = Pattern.compile(ANNOTATION_VALUES_REGEX).matcher(annotationRawValueAsString); while (matcher.find()) { var profile = matcher.group(1).trim(); @@ -115,7 +118,7 @@ private Map> concatenateProfilesBasedOnCommonMethods(final var found = false; for (int j = processProfileIndex + 1; j < totalProfiles; j++) { if (methodsSetsList.get(j).contains(methodElement)) { - concatenatedProfiles.append(SEPARATOR).append(profilesList.get(j)); + concatenatedProfiles.append(COMMA_SEPARATOR).append(profilesList.get(j)); found = true; } } @@ -149,8 +152,8 @@ default void buildSealedInterfacesPermitsMap(final Element interfaceElement, final Map>> sealedInterfacesToGenerate, final Map>> sealedInterfacesPermits) { sealedInterfacesPermits.get(interfaceElement).putAll(sealedInterfacesToGenerate.get(interfaceElement).keySet().stream() - .filter(profiles -> profiles.contains(SEPARATOR)) - .collect(toMap(profiles -> profiles, profiles -> asList(profiles.split(SEPARATOR))))); + .filter(profiles -> profiles.contains(COMMA_SEPARATOR)) + .collect(toMap(profiles -> profiles, profiles -> asList(profiles.split(COMMA_SEPARATOR))))); // parent interf permits all, if there are other permits already existing then the profiles in those permits lists should be removed from parent interf permits list var parentInterfaceSimpleName = interfaceElement.getSimpleName().toString(); var allPermittedProfiles = sealedInterfacesPermits.get(interfaceElement).values().stream().flatMap(Collection::stream).collect(toSet()); @@ -175,14 +178,12 @@ default Map handleAnnotatedElements(final ProcessingEnvironment sealed interface UniqueParentInterfaceHandler extends JiselAnnotationHandler permits SealForProfileUniqueParentInterfaceHandler { - String REPORT_MSG = "More than 1 Top-Level Parent Sealed Interfaces will be generated. Check your profiles mapping."; - default Map> checkUniqueParentInterfacePresence(final Map>> sealedInterfacesToGenerate) { var providedProfilesListByInterface = new HashMap>(); sealedInterfacesToGenerate.forEach( (interfaceElement, annotatedMethodsByProfile) -> annotatedMethodsByProfile.keySet().stream() - .filter(concatenatedProfiles -> !concatenatedProfiles.contains(SEPARATOR)).forEach( + .filter(concatenatedProfiles -> !concatenatedProfiles.contains(COMMA_SEPARATOR)).forEach( profileName -> providedProfilesListByInterface.merge( interfaceElement, asList(profileName), diff --git a/src/main/java/org/jisel/handlers/SealForProfileHandler.java b/src/main/java/org/jisel/handlers/SealForProfileHandler.java index 890708b..1cbe483 100644 --- a/src/main/java/org/jisel/handlers/SealForProfileHandler.java +++ b/src/main/java/org/jisel/handlers/SealForProfileHandler.java @@ -38,9 +38,9 @@ public final class SealForProfileHandler implements JiselAnnotationHandler { - final private AnnotationInfoCollectionHandler annotationInfoCollectionHandler; - final private UniqueParentInterfaceHandler uniqueParentInterfaceHandler; - final private ParentChildInheritanceHandler parentChildInheritanceHandler; + private final AnnotationInfoCollectionHandler annotationInfoCollectionHandler; + private final UniqueParentInterfaceHandler uniqueParentInterfaceHandler; + private final ParentChildInheritanceHandler parentChildInheritanceHandler; public SealForProfileHandler() { this.annotationInfoCollectionHandler = new SealForProfileInfoCollectionHandler(); @@ -116,7 +116,7 @@ public void buildInheritanceRelations(final Map(); sealedInterfacesToGenerateByBloatedInterface.get(interfaceElement).keySet().forEach(concatenatedProfiles -> { - var profilesArray = concatenatedProfiles.split(SEPARATOR); + var profilesArray = concatenatedProfiles.split(COMMA_SEPARATOR); if (profilesArray.length > 1) { for (var profile : profilesArray) { var profileMethodsOpt = Optional.ofNullable(sealedInterfacesToGenerateByBloatedInterface.get(interfaceElement).get(profile)); From 99a75f8accd15fc50fca8f4a629de10146f8b743 Mon Sep 17 00:00:00 2001 From: Mohamed Ashraf Bayor Date: Sat, 11 Dec 2021 20:57:12 -0500 Subject: [PATCH 05/11] JIS-01: redesigned @addtoprofile + refactoring --- src/main/java/org/jisel/AddToProfile.java | 3 +- src/main/java/org/jisel/AddToProfiles.java | 33 ------- .../org/jisel/JiselAnnotationProcessor.java | 21 ++-- .../org/jisel/generator/CodeGenerator.java | 20 +++- .../jisel/generator/FinalClassGenerator.java | 10 +- .../org/jisel/generator/ReportGenerator.java | 42 +++++++- .../SealedInterfaceContentGenerator.java | 26 ++--- .../SealedInterfaceSourceFileGenerator.java | 54 ++++++++--- .../org/jisel/generator/StringGenerator.java | 34 +++---- .../jisel/handlers/AddToProfileHandler.java | 97 +++++++++---------- .../handlers/JiselAnnotationHandler.java | 56 +++++++---- .../jisel/handlers/SealForProfileHandler.java | 36 +++---- 12 files changed, 245 insertions(+), 187 deletions(-) delete mode 100644 src/main/java/org/jisel/AddToProfiles.java diff --git a/src/main/java/org/jisel/AddToProfile.java b/src/main/java/org/jisel/AddToProfile.java index 48312a8..9762bd3 100644 --- a/src/main/java/org/jisel/AddToProfile.java +++ b/src/main/java/org/jisel/AddToProfile.java @@ -32,7 +32,8 @@ @Repeatable(AddToProfile.AddToProfilez.class) public @interface AddToProfile { - String value(); + String profileName(); + String interfaceName(); @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) diff --git a/src/main/java/org/jisel/AddToProfiles.java b/src/main/java/org/jisel/AddToProfiles.java deleted file mode 100644 index a884297..0000000 --- a/src/main/java/org/jisel/AddToProfiles.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2021-2022 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; - -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 index 7b4f088..f9ce102 100644 --- a/src/main/java/org/jisel/JiselAnnotationProcessor.java +++ b/src/main/java/org/jisel/JiselAnnotationProcessor.java @@ -50,14 +50,13 @@ import static java.util.Collections.unmodifiableSet; import static java.util.stream.Collectors.joining; import static org.jisel.generator.StringGenerator.ORG_JISEL_ADD_TO_PROFILE; -import static org.jisel.generator.StringGenerator.ORG_JISEL_ADD_TO_PROFILES; import static org.jisel.generator.StringGenerator.ORG_JISEL_ADD_TO_PROFILEZ; import static org.jisel.generator.StringGenerator.ORG_JISEL_SEAL_FOR_PROFILE; import static org.jisel.generator.StringGenerator.ORG_JISEL_SEAL_FOR_PROFILES; import static org.jisel.generator.StringGenerator.ORG_JISEL_SEAL_FOR_PROFILEZ; @SupportedAnnotationTypes({ORG_JISEL_SEAL_FOR_PROFILE, ORG_JISEL_SEAL_FOR_PROFILES, ORG_JISEL_SEAL_FOR_PROFILEZ, - ORG_JISEL_ADD_TO_PROFILE, ORG_JISEL_ADD_TO_PROFILES, ORG_JISEL_ADD_TO_PROFILEZ}) + ORG_JISEL_ADD_TO_PROFILE, ORG_JISEL_ADD_TO_PROFILEZ}) @SupportedSourceVersion(SourceVersion.RELEASE_17) @AutoService(Processor.class) public class JiselAnnotationProcessor extends AbstractProcessor implements StringGenerator { @@ -81,8 +80,8 @@ public boolean process(final Set annotations, final Round var allAnnotatedSealForProfileElements = new HashSet(); var allAnnotatedAddToProfileElements = new HashSet(); - var sealedInterfacesToGenerateByBloatedInterface = new HashMap>>(); - var sealedInterfacesPermitsByBloatedInterface = new HashMap>>(); + var sealedInterfacesToGenerateByLargeInterface = new HashMap>>(); + var sealedInterfacesPermitsByLargeInterface = new HashMap>>(); populateAllAnnotatedElementsSets(annotations, roundEnv, allAnnotatedSealForProfileElements, allAnnotatedAddToProfileElements); @@ -90,8 +89,8 @@ public boolean process(final Set annotations, final Round var statusReport = sealForProfileHandler.handleAnnotatedElements( processingEnv, unmodifiableSet(allAnnotatedSealForProfileElements), - sealedInterfacesToGenerateByBloatedInterface, - sealedInterfacesPermitsByBloatedInterface + sealedInterfacesToGenerateByLargeInterface, + sealedInterfacesPermitsByLargeInterface ); displayStatusReport(statusReport, SEAL_FOR_PROFILE); @@ -99,15 +98,15 @@ public boolean process(final Set annotations, final Round statusReport = addToProfileHandler.handleAnnotatedElements( processingEnv, unmodifiableSet(allAnnotatedAddToProfileElements), - unmodifiableMap(sealedInterfacesToGenerateByBloatedInterface), - sealedInterfacesPermitsByBloatedInterface + unmodifiableMap(sealedInterfacesToGenerateByLargeInterface), + sealedInterfacesPermitsByLargeInterface ); displayStatusReport(statusReport, ADD_TO_PROFILE); try { - var generatedFiles = sealedInterfaceSourceFileGenerator.createSourceFiles(processingEnv, sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface); + var generatedFiles = sealedInterfaceSourceFileGenerator.createSourceFiles(processingEnv, sealedInterfacesToGenerateByLargeInterface, sealedInterfacesPermitsByLargeInterface); if (!generatedFiles.isEmpty()) { - log.info(() -> format("%s:%n%s", FILE_GENERATION_SUCCESS, generatedFiles.stream().collect(joining("\n")))); + log.info(() -> format("%s:%n%s", FILE_GENERATION_SUCCESS, generatedFiles.stream().collect(joining(format("%n"))))); } } catch (IOException e) { log.log(Level.SEVERE, FILE_GENERATION_ERROR, e); @@ -130,7 +129,7 @@ private void populateAllAnnotatedElementsSets(final Set a private void displayStatusReport(final Map statusReport, final String annotationName) { if (!statusReport.values().stream().collect(joining()).isBlank()) { - var output = new StringBuilder(format("%n%s - @%s(s)%n", STATUS_REPORT_TITLE, annotationName)); + var output = new StringBuilder(format("%n%s - @%s%n", STATUS_REPORT_TITLE, annotationName)); statusReport.entrySet().forEach(mapEntry -> { if (!mapEntry.getValue().isBlank()) { output.append(format("\t> %s: %s%n", mapEntry.getKey().getSimpleName().toString(), mapEntry.getValue())); diff --git a/src/main/java/org/jisel/generator/CodeGenerator.java b/src/main/java/org/jisel/generator/CodeGenerator.java index 8ccc9ce..fd29ca3 100644 --- a/src/main/java/org/jisel/generator/CodeGenerator.java +++ b/src/main/java/org/jisel/generator/CodeGenerator.java @@ -49,7 +49,7 @@ public sealed interface CodeGenerator permits JavaxGeneratedGenerator, ExtendsGe sealed interface ExtendsGenerator extends CodeGenerator, StringGenerator permits JiselExtendsGenerator { - void generateExtendsClauseFromPermitsMapAndProcessedProfile(ProcessingEnvironment processingEnvironment, StringBuilder sealedInterfaceContent, Map> permitsMap, String processedProfile, Element bloatedInterfaceElement); + void generateExtendsClauseFromPermitsMapAndProcessedProfile(ProcessingEnvironment processingEnvironment, StringBuilder sealedInterfaceContent, Map> permitsMap, String processedProfile, Element largeInterfaceElement); @Override default void generateCode(final StringBuilder classOrInterfaceContent, final List params) { @@ -67,7 +67,7 @@ private boolean isInterface(final String classOrInterfaceContent) { sealed interface PermitsGenerator extends CodeGenerator, StringGenerator permits JiselPermitsGenerator { - void generatePermitsClauseFromPermitsMapAndProcessedProfile(StringBuilder sealedInterfaceContent, Map> permitsMap, String processedProfile, Element bloatedInterfaceElement); + void generatePermitsClauseFromPermitsMapAndProcessedProfile(StringBuilder sealedInterfaceContent, Map> permitsMap, String processedProfile, Element largeInterfaceElement); @Override default void generateCode(final StringBuilder classOrInterfaceContent, final List params) { @@ -78,15 +78,16 @@ default void generateCode(final StringBuilder classOrInterfaceContent, final Lis )); } - default void addFinalClassToPermitsMap(final Map> permitsMap, final Element bloatedInterfaceElement) { - var finalClassName = UNDERSCORE + bloatedInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX; + default void addFinalClassToPermitsMap(final Map> permitsMap, final Element largeInterfaceElement) { + var finalClassName = UNDERSCORE + largeInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX; var childlessProfiles = permitsMap.values().stream() .flatMap(Collection::stream) .distinct() .filter(childProfileName -> permitsMap.keySet().stream().noneMatch(parentProfile -> parentProfile.equals(childProfileName))) .filter(childProfileName -> !finalClassName.equals(childProfileName)) // if finalClassName found remove it from the new list + .filter(childProfileName -> !childProfileName.contains(DOT)) // also skip all qualifiedname classes add by @addToProfile .toList(); - childlessProfiles.forEach(childLessProfile -> permitsMap.put(childLessProfile, asList(finalClassName))); + childlessProfiles.forEach(childlessProfile -> permitsMap.put(childlessProfile, asList(finalClassName))); } } @@ -125,4 +126,13 @@ default String generateMethodNameAndParameters(final Element methodElement) { default String generateThrownExceptions(final Element methodElement) { return ((ExecutableType) methodElement.asType()).getThrownTypes().stream().map(Object::toString).collect(joining(COMMA_SEPARATOR + WHITESPACE)); } + + default String generateDefaultReturnValueForMethod(final Element methodElement) { + return switch (((ExecutableType) methodElement.asType()).getReturnType().getKind()) { + case BOOLEAN -> RETURN + WHITESPACE + DEFAULT_BOOLEAN_VALUE; + case VOID -> RETURN; + case BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, CHAR -> RETURN + WHITESPACE + DEFAULT_NUMBER_VALUE; + default -> RETURN + WHITESPACE + DEFAULT_NULL_VALUE; + }; + } } \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/FinalClassGenerator.java b/src/main/java/org/jisel/generator/FinalClassGenerator.java index a770c71..9f17f8a 100644 --- a/src/main/java/org/jisel/generator/FinalClassGenerator.java +++ b/src/main/java/org/jisel/generator/FinalClassGenerator.java @@ -44,11 +44,11 @@ public FinalClassGenerator() { this.methodsGenerator = new JiselMethodsGenerator(); } - public String generateFinalClassContent(final ProcessingEnvironment processingEnvironment, final Element bloatedInterfaceElement, final Map> sealedInterfacesPermitsMap) { - var finalClassName = UNDERSCORE + bloatedInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX; + public String generateFinalClassContent(final ProcessingEnvironment processingEnvironment, final Element largeInterfaceElement, final Map> sealedInterfacesPermitsMap) { + var finalClassName = UNDERSCORE + largeInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX; var finalClassContent = new StringBuilder(); // package name - generatePackageName(bloatedInterfaceElement).ifPresent(name -> finalClassContent.append(format("%s %s;%n%n", PACKAGE, name))); + generatePackageName(largeInterfaceElement).ifPresent(name -> finalClassContent.append(format("%s %s;%n%n", PACKAGE, name))); // javaxgenerated javaxGeneratedGenerator.generateCode(finalClassContent, null); // public final class @@ -58,13 +58,13 @@ public String generateFinalClassContent(final ProcessingEnvironment processingEn finalClassName )); // list of implements - extendsGenerator.generateExtendsClauseFromPermitsMapAndProcessedProfile(processingEnvironment, finalClassContent, sealedInterfacesPermitsMap, finalClassName, bloatedInterfaceElement); + extendsGenerator.generateExtendsClauseFromPermitsMapAndProcessedProfile(processingEnvironment, finalClassContent, sealedInterfacesPermitsMap, finalClassName, largeInterfaceElement); // opening bracket after permits list finalClassContent.append(format(" %s%n ", OPENING_BRACKET)); // list of methods methodsGenerator.generateEmptyConcreteMethodsFromElementsSet( finalClassContent, - processingEnvironment.getElementUtils().getAllMembers((TypeElement) bloatedInterfaceElement).stream() + processingEnvironment.getElementUtils().getAllMembers((TypeElement) largeInterfaceElement).stream() .filter(element -> ElementKind.METHOD.equals(element.getKind())) .filter(element -> asList(METHODS_TO_EXCLUDE).stream().noneMatch(excludedMeth -> element.toString().contains(excludedMeth + OPENING_PARENTHESIS))) .collect(toSet()) diff --git a/src/main/java/org/jisel/generator/ReportGenerator.java b/src/main/java/org/jisel/generator/ReportGenerator.java index 8b3da49..4ba8abf 100644 --- a/src/main/java/org/jisel/generator/ReportGenerator.java +++ b/src/main/java/org/jisel/generator/ReportGenerator.java @@ -21,5 +21,43 @@ */ package org.jisel.generator; -public class ReportGenerator { -} +import javax.lang.model.element.Element; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static java.lang.String.format; +import static java.util.stream.Collectors.joining; + +public class ReportGenerator implements StringGenerator { + + public String generateReportForBloatedInterface(final Element largeInterfaceElement, + final Map> sealedInterfacesToGenerateMap, + final Map> sealedInterfacesPermitsMap) { + var reportContent = new StringBuilder(); + var packageNameOpt = generatePackageName(largeInterfaceElement); + var qualifiedName = packageNameOpt.isPresent() + ? packageNameOpt.get() + DOT + largeInterfaceElement.getSimpleName().toString() + : largeInterfaceElement.getSimpleName().toString(); + reportContent.append(format("%s%n", qualifiedName)); + reportContent.append(format("%s%n", JISEL_REPORT_CREATED_SEALED_INTERFACES_HEADER)); + sealedInterfacesToGenerateMap.entrySet().forEach(entrySet -> { + var sealedInterfaceName = sealedInterfaceNameConvention(entrySet.getKey(), largeInterfaceElement); + reportContent.append(format("\t%s%n", sealedInterfaceName)); + var sealedInterfaceChildrenOpt = Optional.ofNullable(sealedInterfacesPermitsMap.get(entrySet.getKey())); + if (sealedInterfaceChildrenOpt.isPresent() && !sealedInterfaceChildrenOpt.get().isEmpty()) { + reportContent.append(format("\t - %s%n", JISEL_REPORT_CHILDREN_HEADER)); + if (!sealedInterfaceChildrenOpt.get().isEmpty()) { + reportContent.append(format( + "\t\t%s%n", + sealedInterfaceChildrenOpt.get().stream() + .map(childName -> sealedInterfaceNameConvention(childName, largeInterfaceElement)) + .collect(joining(format("%n\t\t"))) + )); + } + } + }); + return reportContent.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java b/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java index 05c65d9..9845d17 100644 --- a/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java +++ b/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java @@ -46,11 +46,11 @@ public SealedInterfaceContentGenerator() { public String generateSealedInterfaceContent(final ProcessingEnvironment processingEnvironment, final Map.Entry> sealedInterfacesToGenerateMapEntrySet, - final Element bloatedInterfaceElement, + final Element largeInterfaceElement, final Map> sealedInterfacesPermitsMap) { var sealedInterfaceContent = new StringBuilder(); // package name - generatePackageName(bloatedInterfaceElement).ifPresent(name -> sealedInterfaceContent.append(format("%s %s;%n%n", PACKAGE, name))); + generatePackageName(largeInterfaceElement).ifPresent(name -> sealedInterfaceContent.append(format("%s %s;%n%n", PACKAGE, name))); // javaxgenerated javaxGeneratedGenerator.generateCode(sealedInterfaceContent, null); // public sealed interface @@ -58,12 +58,12 @@ public String generateSealedInterfaceContent(final ProcessingEnvironment process sealedInterfaceContent.append(format( "%s %s ", PUBLIC_SEALED_INTERFACE, - sealedInterfaceNameConvention(profile, bloatedInterfaceElement) + sealedInterfaceNameConvention(profile, largeInterfaceElement) )); // list of extends - extendsGenerator.generateExtendsClauseFromPermitsMapAndProcessedProfile(processingEnvironment, sealedInterfaceContent, sealedInterfacesPermitsMap, profile, bloatedInterfaceElement); + extendsGenerator.generateExtendsClauseFromPermitsMapAndProcessedProfile(processingEnvironment, sealedInterfaceContent, sealedInterfacesPermitsMap, profile, largeInterfaceElement); // list of permits - permitsGenerator.generatePermitsClauseFromPermitsMapAndProcessedProfile(sealedInterfaceContent, sealedInterfacesPermitsMap, profile, bloatedInterfaceElement); + permitsGenerator.generatePermitsClauseFromPermitsMapAndProcessedProfile(sealedInterfaceContent, sealedInterfacesPermitsMap, profile, largeInterfaceElement); // opening bracket after permits list sealedInterfaceContent.append(format(" %s%n ", OPENING_BRACKET)); // list of methods @@ -81,20 +81,20 @@ public void generateExtendsClauseFromPermitsMapAndProcessedProfile(final Process final StringBuilder sealedInterfaceContent, final Map> permitsMap, final String processedProfile, - final Element bloatedInterfaceElement) { + final Element largeInterfaceElement) { Optional.ofNullable(permitsMap).ifPresent(nonNullPermitsMap -> { var parentList = nonNullPermitsMap.entrySet().stream() .filter(permitsMapEntry -> permitsMapEntry.getValue().contains(processedProfile)) - .map(permitsMapEntry -> sealedInterfaceNameConvention(permitsMapEntry.getKey(), bloatedInterfaceElement)) + .map(permitsMapEntry -> sealedInterfaceNameConvention(permitsMapEntry.getKey(), largeInterfaceElement)) .toList(); if (!parentList.isEmpty()) { generateCode(sealedInterfaceContent, parentList); } else { - // only for bloatedInterface sealed interface generation, add interfaces it extends if any - if (bloatedInterfaceElement.getSimpleName().toString().equals(processedProfile)) { + // only for largeInterface sealed interface generation, add interfaces it extends if any + if (largeInterfaceElement.getSimpleName().toString().equals(processedProfile)) { generateCode( sealedInterfaceContent, - processingEnvironment.getTypeUtils().directSupertypes(bloatedInterfaceElement.asType()).stream() + processingEnvironment.getTypeUtils().directSupertypes(largeInterfaceElement.asType()).stream() .map(Object::toString) .filter(superType -> !superType.contains(JAVA_LANG_OBJECT)) .toList() @@ -110,12 +110,12 @@ final class JiselPermitsGenerator implements PermitsGenerator { public void generatePermitsClauseFromPermitsMapAndProcessedProfile(final StringBuilder sealedInterfaceContent, final Map> permitsMap, final String processedProfile, - final Element bloatedInterfaceElement) { - addFinalClassToPermitsMap(permitsMap, bloatedInterfaceElement); + final Element largeInterfaceElement) { + addFinalClassToPermitsMap(permitsMap, largeInterfaceElement); var permitsMapOpt = Optional.ofNullable(permitsMap); if (permitsMapOpt.isPresent() && !permitsMapOpt.get().isEmpty()) { Optional.ofNullable(permitsMapOpt.get().get(processedProfile)).ifPresent( - childrenList -> generateCode(sealedInterfaceContent, sealedInterfaceNameConventionForList(childrenList, bloatedInterfaceElement)) + childrenList -> generateCode(sealedInterfaceContent, sealedInterfaceNameConventionForList(childrenList, largeInterfaceElement)) ); } } diff --git a/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java b/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java index 265a561..8672b46 100644 --- a/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java +++ b/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java @@ -24,6 +24,7 @@ import javax.annotation.processing.FilerException; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; +import javax.tools.StandardLocation; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; @@ -35,24 +36,27 @@ public class SealedInterfaceSourceFileGenerator implements StringGenerator { private final SealedInterfaceContentGenerator sealedInterfaceContentGenerator; private final FinalClassGenerator finalClassGenerator; + private final ReportGenerator reportGenerator; public SealedInterfaceSourceFileGenerator() { this.sealedInterfaceContentGenerator = new SealedInterfaceContentGenerator(); this.finalClassGenerator = new FinalClassGenerator(); + this.reportGenerator = new ReportGenerator(); } public List createSourceFiles(final ProcessingEnvironment processingEnvironment, - final Map>> sealedInterfacesToGenerateByBloatedInterface, - final Map>> sealedInterfacesPermitsByBloatedInterface) throws IOException { + final Map>> sealedInterfacesToGenerateByLargeInterface, + final Map>> sealedInterfacesPermitsByLargeInterface) throws IOException { var generatedFiles = new ArrayList(); - for (var sealedInterfacesToGenerateMapEntrySet : sealedInterfacesToGenerateByBloatedInterface.entrySet()) { - var bloatedInterfaceElement = sealedInterfacesToGenerateMapEntrySet.getKey(); + for (var sealedInterfacesToGenerateMapEntrySet : sealedInterfacesToGenerateByLargeInterface.entrySet()) { + var largeInterfaceElement = sealedInterfacesToGenerateMapEntrySet.getKey(); for (var sealedInterfacesToGenerate : sealedInterfacesToGenerateMapEntrySet.getValue().entrySet()) { var profile = sealedInterfacesToGenerate.getKey(); - var generatedSealedInterfaceName = sealedInterfaceNameConvention(profile, bloatedInterfaceElement); - createSealedInterfaceFile(processingEnvironment, generatedFiles, sealedInterfacesPermitsByBloatedInterface.get(bloatedInterfaceElement), bloatedInterfaceElement, sealedInterfacesToGenerate, generatedSealedInterfaceName); + var generatedSealedInterfaceName = sealedInterfaceNameConvention(profile, largeInterfaceElement); + createSealedInterfaceFile(processingEnvironment, generatedFiles, sealedInterfacesPermitsByLargeInterface.get(largeInterfaceElement), largeInterfaceElement, sealedInterfacesToGenerate, generatedSealedInterfaceName); } - createFinalClassFile(processingEnvironment, generatedFiles, bloatedInterfaceElement, sealedInterfacesPermitsByBloatedInterface.get(bloatedInterfaceElement)); + createFinalClassFile(processingEnvironment, generatedFiles, largeInterfaceElement, sealedInterfacesPermitsByLargeInterface.get(largeInterfaceElement)); + createJiselReportFile(processingEnvironment, generatedFiles, largeInterfaceElement, sealedInterfacesToGenerateByLargeInterface.get(largeInterfaceElement), sealedInterfacesPermitsByLargeInterface.get(largeInterfaceElement)); } return generatedFiles; } @@ -60,15 +64,15 @@ public List createSourceFiles(final ProcessingEnvironment processingEnvi private void createSealedInterfaceFile(final ProcessingEnvironment processingEnvironment, final List generatedFiles, final Map> sealedInterfacesPermitsMap, - final Element bloatedInterfaceElement, + final Element largeInterfaceElement, final Map.Entry> sealedInterfacesToGenerateMapEntrySet, final String generatedSealedInterfaceName) throws IOException { - var packageNameOpt = generatePackageName(bloatedInterfaceElement); + var packageNameOpt = generatePackageName(largeInterfaceElement); try { var qualifiedName = packageNameOpt.isPresent() ? packageNameOpt.get() + DOT + generatedSealedInterfaceName : generatedSealedInterfaceName; var fileObject = processingEnvironment.getFiler().createSourceFile(qualifiedName); try (var out = new PrintWriter(fileObject.openWriter())) { - out.println(sealedInterfaceContentGenerator.generateSealedInterfaceContent(processingEnvironment, sealedInterfacesToGenerateMapEntrySet, bloatedInterfaceElement, sealedInterfacesPermitsMap)); + out.println(sealedInterfaceContentGenerator.generateSealedInterfaceContent(processingEnvironment, sealedInterfacesToGenerateMapEntrySet, largeInterfaceElement, sealedInterfacesPermitsMap)); } generatedFiles.add(qualifiedName); } catch (FilerException e) { @@ -78,16 +82,36 @@ private void createSealedInterfaceFile(final ProcessingEnvironment processingEnv private void createFinalClassFile(final ProcessingEnvironment processingEnvironment, final List generatedFiles, - final Element bloatedInterfaceElement, + final Element largeInterfaceElement, final Map> sealedInterfacesPermitsMap) throws IOException { - var packageNameOpt = generatePackageName(bloatedInterfaceElement); + var packageNameOpt = generatePackageName(largeInterfaceElement); try { var qualifiedName = packageNameOpt.isPresent() - ? packageNameOpt.get() + DOT + UNDERSCORE + bloatedInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX - : UNDERSCORE + bloatedInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX; + ? packageNameOpt.get() + DOT + UNDERSCORE + largeInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX + : UNDERSCORE + largeInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX; var fileObject = processingEnvironment.getFiler().createSourceFile(qualifiedName); try (var out = new PrintWriter(fileObject.openWriter())) { - out.println(finalClassGenerator.generateFinalClassContent(processingEnvironment, bloatedInterfaceElement, sealedInterfacesPermitsMap)); + out.println(finalClassGenerator.generateFinalClassContent(processingEnvironment, largeInterfaceElement, sealedInterfacesPermitsMap)); + } + generatedFiles.add(qualifiedName); + } catch (FilerException e) { + // File was already generated - do nothing + } + } + + private void createJiselReportFile(final ProcessingEnvironment processingEnvironment, + final List generatedFiles, + final Element largeInterfaceElement, + final Map> sealedInterfacesToGenerateMap, + final Map> sealedInterfacesPermitsMap) throws IOException { + var packageNameOpt = generatePackageName(largeInterfaceElement); + try { + var qualifiedName = packageNameOpt.isPresent() + ? packageNameOpt.get() + DOT + UNDERSCORE + largeInterfaceElement.getSimpleName().toString() + JISEL_REPORT_SUFFIX + : UNDERSCORE + largeInterfaceElement.getSimpleName().toString() + JISEL_REPORT_SUFFIX; + var fileObject = processingEnvironment.getFiler().createResource(StandardLocation.SOURCE_OUTPUT, packageNameOpt.isPresent() ? packageNameOpt.get() : EMPTY_STRING, UNDERSCORE + largeInterfaceElement.getSimpleName().toString() + JISEL_REPORT_SUFFIX); + try (var out = new PrintWriter(fileObject.openWriter())) { + out.println(reportGenerator.generateReportForBloatedInterface(largeInterfaceElement, sealedInterfacesToGenerateMap, sealedInterfacesPermitsMap)); } generatedFiles.add(qualifiedName); } catch (FilerException e) { diff --git a/src/main/java/org/jisel/generator/StringGenerator.java b/src/main/java/org/jisel/generator/StringGenerator.java index 77bd735..214778d 100644 --- a/src/main/java/org/jisel/generator/StringGenerator.java +++ b/src/main/java/org/jisel/generator/StringGenerator.java @@ -1,7 +1,6 @@ package org.jisel.generator; import javax.lang.model.element.Element; -import javax.lang.model.type.ExecutableType; import java.util.List; import java.util.Optional; import java.util.function.UnaryOperator; @@ -39,6 +38,9 @@ public interface StringGenerator { String SEAL_FOR_PROFILE = "SealForProfile"; String ADD_TO_PROFILE = "AddToProfile"; + String PROFILE_NAME_ATTRIBUTE = "profileName()"; + String INTERFACE_NAME_ATTRIBUTE = "interfaceName()"; + String ANNOTATION_VALUES_REGEX = "\"([^\"]*)\""; String STATUS_REPORT_TITLE = "JISEL GENERATION REPORT"; @@ -49,7 +51,6 @@ public interface StringGenerator { String ORG_JISEL_SEAL_FOR_PROFILES = "org.jisel.SealForProfiles"; String ORG_JISEL_SEAL_FOR_PROFILEZ = "org.jisel.SealForProfile.SealForProfilez"; String ORG_JISEL_ADD_TO_PROFILE = "org.jisel.AddToProfile"; - String ORG_JISEL_ADD_TO_PROFILES = "org.jisel.AddToProfiles"; String ORG_JISEL_ADD_TO_PROFILEZ = "org.jisel.AddToProfile.AddToProfilez"; String DEFAULT_BOOLEAN_VALUE = "false"; @@ -58,8 +59,14 @@ public interface StringGenerator { String[] METHODS_TO_EXCLUDE = {"getClass", "wait", "notifyAll", "hashCode", "equals", "notify", "toString"}; - String ADD_TO_PROFILE_REPORT_MSG = "1 or many provided profiles are not found in provided parent interfaces. Check your profiles names."; - String REPORT_MSG = "More than 1 Top-Level Parent Sealed Interfaces will be generated. Check your profiles mapping."; + String ADD_TO_PROFILE_REPORT_MSG = "1 or many provided profiles are not found in the provided parent interfaces. Check your profiles and/or parent interfaces names."; + String SEAL_FOR_PROFILE_REPORT_MSG = "More than 1 Top-Level Parent Sealed Interfaces will be generated. Check your profiles mapping."; + + String JISEL_REPORT_SUFFIX = "Report.txt"; + + String JISEL_REPORT_CREATED_SEALED_INTERFACES_HEADER = "Created sealed interfaces:"; + + String JISEL_REPORT_CHILDREN_HEADER = "Children:"; static String removeSeparator(final String text) { return asList(text.split(COMMA_SEPARATOR)).stream().collect(joining()); @@ -67,8 +74,8 @@ static String removeSeparator(final String text) { default String sealedInterfaceNameConvention(final String profile, final Element interfaceElement) { var nameSuffix = removeSeparator(profile).equals(interfaceElement.getSimpleName().toString()) ? EMPTY_STRING : interfaceElement.getSimpleName().toString(); - // any profile name starting w _ is returned as it is - return removeSeparator(profile).startsWith(UNDERSCORE) ? removeSeparator(profile) : format( + // any profile name starting w _ (final classes names) or containing a dot (classes annotated with addtoprofile) is returned as is + return removeSeparator(profile).startsWith(UNDERSCORE) || profile.contains(DOT) ? removeSeparator(profile) : format( "%s%s%s", SEALED_PREFIX, removeSeparator(profile), @@ -79,7 +86,7 @@ default String sealedInterfaceNameConvention(final String profile, final Element default List sealedInterfaceNameConventionForList(final List profiles, final Element interfaceElement) { final UnaryOperator nameSuffix = profile -> removeSeparator(profile).equals(interfaceElement.getSimpleName().toString()) ? EMPTY_STRING : interfaceElement.getSimpleName().toString(); return profiles.stream() - .map(profile -> removeSeparator(profile).startsWith(UNDERSCORE) ? removeSeparator(profile) : format( + .map(profile -> removeSeparator(profile).startsWith(UNDERSCORE) || profile.contains(DOT) ? removeSeparator(profile) : format( "%s%s%s", SEALED_PREFIX, removeSeparator(profile), @@ -87,8 +94,8 @@ default List sealedInterfaceNameConventionForList(final List pro )).toList(); } - default Optional generatePackageName(final Element bloatedInterfaceName) { - var qualifiedClassName = bloatedInterfaceName.toString(); + default Optional generatePackageName(final Element largeInterfaceName) { + var qualifiedClassName = largeInterfaceName.toString(); int lastDot = qualifiedClassName.lastIndexOf('.'); return lastDot > 0 ? Optional.of(qualifiedClassName.substring(0, lastDot)) : Optional.empty(); } @@ -96,13 +103,4 @@ default Optional generatePackageName(final Element bloatedInterfaceName) default String removeDoubleSpaceOccurrences(final String text) { return text.replace(WHITESPACE + WHITESPACE, WHITESPACE); } - - default String generateDefaultReturnValueForMethod(final Element methodElement) { - return switch (((ExecutableType) methodElement.asType()).getReturnType().getKind()) { - case BOOLEAN -> RETURN + WHITESPACE + DEFAULT_BOOLEAN_VALUE; - case VOID -> RETURN; - case BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, CHAR -> RETURN + WHITESPACE + DEFAULT_NUMBER_VALUE; - default -> RETURN + WHITESPACE + DEFAULT_NULL_VALUE; - }; - } } \ No newline at end of file diff --git a/src/main/java/org/jisel/handlers/AddToProfileHandler.java b/src/main/java/org/jisel/handlers/AddToProfileHandler.java index 56a69e2..2f4ead3 100644 --- a/src/main/java/org/jisel/handlers/AddToProfileHandler.java +++ b/src/main/java/org/jisel/handlers/AddToProfileHandler.java @@ -25,8 +25,10 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import static java.util.Arrays.asList; @@ -38,8 +40,8 @@ public final class AddToProfileHandler implements JiselAnnotationHandler { @Override public Map handleAnnotatedElements(ProcessingEnvironment processingEnv, Set allAnnotatedElements, - Map>> sealedInterfacesToGenerateByBloatedInterface, - Map>> sealedInterfacesPermitsByBloatedInterface) { + Map>> sealedInterfacesToGenerateByLargeInterface, + Map>> sealedInterfacesPermitsByLargeInterface) { var annotatedClassesAndInterfaces = allAnnotatedElements.stream() .filter(element -> !element.getClass().isEnum()) .filter(element -> ElementKind.CLASS.equals(element.getKind()) @@ -49,76 +51,73 @@ public Map handleAnnotatedElements(ProcessingEnvironment proces var statusReport = new HashMap(); annotatedClassesAndInterfaces.forEach(annotatedClassOrInterface -> statusReport.put( annotatedClassOrInterface, - processAnnotatedElement(processingEnv, annotatedClassOrInterface, sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface) + processAnnotatedElement(processingEnv, annotatedClassOrInterface, sealedInterfacesToGenerateByLargeInterface, sealedInterfacesPermitsByLargeInterface) )); return statusReport; } private String processAnnotatedElement(final ProcessingEnvironment processingEnv, final Element annotatedClassOrInterface, - final Map>> sealedInterfacesToGenerateByBloatedInterface, - final Map>> sealedInterfacesPermitsByBloatedInterface) { + final Map>> sealedInterfacesToGenerateByLargeInterface, + final Map>> sealedInterfacesPermitsByLargeInterface) { var statusReport = new StringBuilder(); - var addToProfileProvidedProfilesSet = buildProvidedProfilesSet(processingEnv, annotatedClassOrInterface); - if (addToProfileProvidedProfilesSet.isEmpty()) { + var addToProfileProvidedProfilesMap = buildAddToProfileProvidedProfilesMap(processingEnv, annotatedClassOrInterface); + if (addToProfileProvidedProfilesMap.isEmpty()) { // do not process if no profiles are provided return statusReport.toString(); } - var providedSuperInterfacesSet = buildProvidedInterfacesSet(processingEnv, annotatedClassOrInterface); - // browse sealedInterfacesToGenerate 1st and then add more info to sealedInterfacesPermits - var found = false; - for (var mapEntry : sealedInterfacesToGenerateByBloatedInterface.entrySet()) { - var bloatedInterfaceElement = mapEntry.getKey(); - var annotatedMethodsByProfile = mapEntry.getValue(); - for (var profile : annotatedMethodsByProfile.keySet()) { - for (var superInterfaceQualifiedName : providedSuperInterfacesSet) { - var sealedInterfaceName = sealedInterfaceNameConvention(profile, bloatedInterfaceElement); - if (superInterfaceQualifiedName.contains(sealedInterfaceName)) { - // add permits for each profile only if the profiles exist for the superinterface - var bloatedInterfaceProvidedProfilesList = annotatedMethodsByProfile.keySet().stream() - .filter(bloatedInterfaceProvidedProfile -> sealedInterfaceNameConvention(bloatedInterfaceProvidedProfile, bloatedInterfaceElement).equals(sealedInterfaceName)) - .toList(); - found = updateSealedInterfacesPermitsMapWithProvidedProfiles( - bloatedInterfaceProvidedProfilesList, addToProfileProvidedProfilesSet, annotatedClassOrInterface, - bloatedInterfaceElement, sealedInterfacesPermitsByBloatedInterface - ); - } - } + var profileFound = false; + var providedLargeInterfaceTypeNotFound = false; + for (var mapEntrySet : addToProfileProvidedProfilesMap.entrySet()) { + var providedLargeInterfaceQualifiedName = mapEntrySet.getKey(); + var providedProfilesForProvidedLargeInterface = mapEntrySet.getValue(); + // 1st check if the provided superinterf type exists + var providedLargeInterfaceTypeOpt = Optional.ofNullable(processingEnv.getElementUtils().getTypeElement(providedLargeInterfaceQualifiedName)); + if (providedLargeInterfaceTypeOpt.isPresent()) { + var providedLargeInterfaceElement = processingEnv.getTypeUtils().asElement(processingEnv.getElementUtils().getTypeElement(providedLargeInterfaceQualifiedName).asType()); + var annotatedMethodsByProfile = sealedInterfacesToGenerateByLargeInterface.get(providedLargeInterfaceElement); + profileFound = updateSealedInterfacesPermitsMapWithProvidedProfiles( + annotatedMethodsByProfile.keySet(), + providedLargeInterfaceElement, + annotatedClassOrInterface, + providedProfilesForProvidedLargeInterface, + sealedInterfacesPermitsByLargeInterface + ); + } else { + providedLargeInterfaceTypeNotFound = true; } } - if (!found) { + if (!profileFound || providedLargeInterfaceTypeNotFound) { statusReport.append(ADD_TO_PROFILE_REPORT_MSG); } return statusReport.toString(); } - private Set buildProvidedInterfacesSet(final ProcessingEnvironment processingEnv, final Element annotatedClassOrInterface) { - return processingEnv.getTypeUtils().directSupertypes(annotatedClassOrInterface.asType()).stream() - .map(Object::toString) - .filter(typeString -> !typeString.contains(JAVA_LANG_OBJECT)) - .collect(toSet()); - } - - private boolean updateSealedInterfacesPermitsMapWithProvidedProfiles(final List bloatedInterfaceProvidedProfilesList, - final Set addToProfileProvidedProfilesSet, + private boolean updateSealedInterfacesPermitsMapWithProvidedProfiles(final Set largeInterfaceProfilesSet, + final Element providedLargeInterfaceElement, final Element annotatedClassOrInterface, - final Element bloatedInterfaceElement, - final Map>> sealedInterfacesPermitsByBloatedInterface) { - var found = false; - for (var bloatedInterfaceProvidedProfile : bloatedInterfaceProvidedProfilesList) { // might contain SEPARATOR - for (var addToProfileProvidedProfile : addToProfileProvidedProfilesSet) { - if (asList(bloatedInterfaceProvidedProfile.split(COMMA_SEPARATOR)).contains(addToProfileProvidedProfile)) { - sealedInterfacesPermitsByBloatedInterface.get(bloatedInterfaceElement).merge( - bloatedInterfaceProvidedProfile, + final Set providedProfilesForProvidedLargeInterface, + final Map>> sealedInterfacesPermitsByLargeInterface) { + var notFoundProfiles = new HashSet(); + for (var providedProfile : providedProfilesForProvidedLargeInterface) { + var foundProvidedProfile = false; + for (var profile : largeInterfaceProfilesSet) { + if (profile.equals(providedLargeInterfaceElement.getSimpleName().toString())) { + continue; + } + if (sealedInterfaceNameConvention(providedProfile, providedLargeInterfaceElement).equals(sealedInterfaceNameConvention(profile, providedLargeInterfaceElement))) { + sealedInterfacesPermitsByLargeInterface.get(providedLargeInterfaceElement).merge( + profile, asList(annotatedClassOrInterface.toString()), (currentList, newList) -> concat(currentList.stream(), newList.stream()).toList() ); - found = true; - } else { - found = false; + foundProvidedProfile = true; } } + if (!foundProvidedProfile) { + notFoundProfiles.add(providedProfile); + } } - return found; + return notFoundProfiles.isEmpty(); } } \ No newline at end of file diff --git a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java index 1cb3986..64eb7be 100644 --- a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java +++ b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java @@ -37,6 +37,7 @@ import java.util.regex.Pattern; import static java.util.Arrays.asList; +import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet; import static java.util.stream.Stream.concat; @@ -44,20 +45,41 @@ public sealed interface JiselAnnotationHandler extends StringGenerator permits SealForProfileHandler, AddToProfileHandler, AnnotationInfoCollectionHandler, UniqueParentInterfaceHandler, ParentChildInheritanceHandler { - String ANNOTATION_VALUES_REGEX = "\"([^\"]*)\""; - Map handleAnnotatedElements(ProcessingEnvironment processingEnv, Set allAnnotatedElements, - Map>> sealedInterfacesToGenerateByBloatedInterface, - Map>> sealedInterfacesPermitsByBloatedInterface); + Map>> sealedInterfacesToGenerateByLargeInterface, + Map>> sealedInterfacesPermitsByLargeInterface); + + default Map> buildAddToProfileProvidedProfilesMap(final ProcessingEnvironment processingEnv, final Element annotatedClassOrInterface) { + var providedProfilesMap = new HashMap>(); + var annotationRawValueAsString = processingEnv.getElementUtils().getAllAnnotationMirrors(annotatedClassOrInterface).stream() + .map(Object::toString) + .collect(joining(COMMA_SEPARATOR)); + var matcher = Pattern.compile(ANNOTATION_VALUES_REGEX).matcher(annotationRawValueAsString); + var interfaceName = EMPTY_STRING; + var profileName = EMPTY_STRING; + while (matcher.find()) { + var profileOrInterface = matcher.group(1).trim(); + if (profileOrInterface.contains(DOT)) { + interfaceName = profileOrInterface; + } else { + profileName = profileOrInterface; + } + if (!interfaceName.isBlank() && !profileName.isBlank()) { + providedProfilesMap.merge(interfaceName, new HashSet<>(Set.of(profileName)), (currentSet, newSet) -> concat(currentSet.stream(), newSet.stream()).collect(toSet())); + profileName = interfaceName = EMPTY_STRING; + } + } + return providedProfilesMap; + } /** * @param processingEnv * @param annotatedClassOrInterface * @return */ - default Set buildProvidedProfilesSet(final ProcessingEnvironment processingEnv, final Element annotatedClassOrInterface) { - // annotationRawValueAsString: sample values: singl value "profile1name", array: {@org.jisel.SealForProfile("profile2name"), @org.jisel.SealForProfile("profile3name"),...} + default Set buildSealForProfileProvidedProfilesSet(final ProcessingEnvironment processingEnv, final Element annotatedClassOrInterface) { + // annotationRawValueAsString: sample values: singl value "profile1name", array: {@org.jisel.SealForProfile("profile2name"), @org.jisel.SealForProfile("profile3name"),...} var providedProfilesSet = new HashSet(); processingEnv.getElementUtils().getAllAnnotationMirrors(annotatedClassOrInterface).stream() .flatMap(annotationMirror -> annotationMirror.getElementValues().entrySet().stream()) @@ -136,17 +158,17 @@ private Map> concatenateProfilesBasedOnCommonMethods(final @Override default Map handleAnnotatedElements(final ProcessingEnvironment processingEnv, final Set allAnnotatedElements, - final Map>> sealedInterfacesToGenerateByBloatedInterface, - final Map>> sealedInterfacesPermitsByBloatedInterface) { - populateSealedInterfacesMap(processingEnv, allAnnotatedElements, sealedInterfacesToGenerateByBloatedInterface); + final Map>> sealedInterfacesToGenerateByLargeInterface, + final Map>> sealedInterfacesPermitsByLargeInterface) { + populateSealedInterfacesMap(processingEnv, allAnnotatedElements, sealedInterfacesToGenerateByLargeInterface); return Map.of(); // return empty map instead of null } } sealed interface ParentChildInheritanceHandler extends JiselAnnotationHandler permits SealForProfileParentChildInheritanceHandler { - void buildInheritanceRelations(Map>> sealedInterfacesToGenerateByBloatedInterface, - Map>> sealedInterfacesPermitsByBloatedInterface); + void buildInheritanceRelations(Map>> sealedInterfacesToGenerateByLargeInterface, + Map>> sealedInterfacesPermitsByLargeInterface); default void buildSealedInterfacesPermitsMap(final Element interfaceElement, final Map>> sealedInterfacesToGenerate, @@ -169,9 +191,9 @@ default void buildSealedInterfacesPermitsMap(final Element interfaceElement, @Override default Map handleAnnotatedElements(final ProcessingEnvironment processingEnv, final Set allAnnotatedElements, - final Map>> sealedInterfacesToGenerateByBloatedInterface, - final Map>> sealedInterfacesPermitsByBloatedInterface) { - buildInheritanceRelations(sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface); + final Map>> sealedInterfacesToGenerateByLargeInterface, + final Map>> sealedInterfacesPermitsByLargeInterface) { + buildInheritanceRelations(sealedInterfacesToGenerateByLargeInterface, sealedInterfacesPermitsByLargeInterface); return Map.of(); } } @@ -220,8 +242,8 @@ foundUniqueParentInterface && removeSeparator(longestConcatenedProfilesStringOpt @Override default Map handleAnnotatedElements(final ProcessingEnvironment processingEnv, final Set allAnnotatedElements, - final Map>> sealedInterfacesToGenerateByBloatedInterface, - final Map>> sealedInterfacesPermitsByBloatedInterface) { - return checkAndHandleUniqueParentInterface(sealedInterfacesToGenerateByBloatedInterface); + final Map>> sealedInterfacesToGenerateByLargeInterface, + final Map>> sealedInterfacesPermitsByLargeInterface) { + return checkAndHandleUniqueParentInterface(sealedInterfacesToGenerateByLargeInterface); } } \ No newline at end of file diff --git a/src/main/java/org/jisel/handlers/SealForProfileHandler.java b/src/main/java/org/jisel/handlers/SealForProfileHandler.java index 1cbe483..6a45fa8 100644 --- a/src/main/java/org/jisel/handlers/SealForProfileHandler.java +++ b/src/main/java/org/jisel/handlers/SealForProfileHandler.java @@ -51,11 +51,11 @@ public SealForProfileHandler() { @Override public Map handleAnnotatedElements(final ProcessingEnvironment processingEnv, final Set allAnnotatedElements, - final Map>> sealedInterfacesToGenerateByBloatedInterface, - final Map>> sealedInterfacesPermitsByBloatedInterface) { - annotationInfoCollectionHandler.populateSealedInterfacesMap(processingEnv, allAnnotatedElements, sealedInterfacesToGenerateByBloatedInterface); - var statusReport = uniqueParentInterfaceHandler.checkAndHandleUniqueParentInterface(sealedInterfacesToGenerateByBloatedInterface); - parentChildInheritanceHandler.buildInheritanceRelations(sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface); + final Map>> sealedInterfacesToGenerateByLargeInterface, + final Map>> sealedInterfacesPermitsByLargeInterface) { + annotationInfoCollectionHandler.populateSealedInterfacesMap(processingEnv, allAnnotatedElements, sealedInterfacesToGenerateByLargeInterface); + var statusReport = uniqueParentInterfaceHandler.checkAndHandleUniqueParentInterface(sealedInterfacesToGenerateByLargeInterface); + parentChildInheritanceHandler.buildInheritanceRelations(sealedInterfacesToGenerateByLargeInterface, sealedInterfacesPermitsByLargeInterface); return statusReport; } } @@ -79,7 +79,7 @@ public void populateSealedInterfacesMap(final ProcessingEnvironment processingEn (interfaceElement, annotatedMethodsElements) -> annotatedMethodsElements.forEach( annotatedMethod -> extractProfilesAndPopulateMaps( interfaceElement, - buildProvidedProfilesSet(processingEnv, annotatedMethod), + buildSealForProfileProvidedProfilesSet(processingEnv, annotatedMethod), annotatedMethod, annotatedMethodsByProfileByInterface ) @@ -109,29 +109,29 @@ private void extractProfilesAndPopulateMaps(final Element interfaceElement, final class SealForProfileParentChildInheritanceHandler implements ParentChildInheritanceHandler { @Override - public void buildInheritanceRelations(final Map>> sealedInterfacesToGenerateByBloatedInterface, - final Map>> sealedInterfacesPermitsByBloatedInterface) { - sealedInterfacesToGenerateByBloatedInterface.keySet().forEach(interfaceElement -> { - sealedInterfacesPermitsByBloatedInterface.put(interfaceElement, new HashMap<>()); // start with initializing sealedInterfacesPermitsByBloatedInterface with empty mutable maps + public void buildInheritanceRelations(final Map>> sealedInterfacesToGenerateByLargeInterface, + final Map>> sealedInterfacesPermitsByLargeInterface) { + sealedInterfacesToGenerateByLargeInterface.keySet().forEach(interfaceElement -> { + sealedInterfacesPermitsByLargeInterface.put(interfaceElement, new HashMap<>()); // start with initializing sealedInterfacesPermitsByLargeInterface with empty mutable maps // promote profiles with empty methods to parent level var allProfilesToRemove = new HashSet(); - sealedInterfacesToGenerateByBloatedInterface.get(interfaceElement).keySet().forEach(concatenatedProfiles -> { + sealedInterfacesToGenerateByLargeInterface.get(interfaceElement).keySet().forEach(concatenatedProfiles -> { var profilesArray = concatenatedProfiles.split(COMMA_SEPARATOR); if (profilesArray.length > 1) { for (var profile : profilesArray) { - var profileMethodsOpt = Optional.ofNullable(sealedInterfacesToGenerateByBloatedInterface.get(interfaceElement).get(profile)); + var profileMethodsOpt = Optional.ofNullable(sealedInterfacesToGenerateByLargeInterface.get(interfaceElement).get(profile)); if (profileMethodsOpt.isPresent() && profileMethodsOpt.get().isEmpty()) { - sealedInterfacesToGenerateByBloatedInterface.get(interfaceElement).put(profile, sealedInterfacesToGenerateByBloatedInterface.get(interfaceElement).get(concatenatedProfiles)); - sealedInterfacesPermitsByBloatedInterface.get(interfaceElement).put(profile, Arrays.stream(profilesArray).filter(profileName -> !profile.equals(profileName)).toList()); + sealedInterfacesToGenerateByLargeInterface.get(interfaceElement).put(profile, sealedInterfacesToGenerateByLargeInterface.get(interfaceElement).get(concatenatedProfiles)); + sealedInterfacesPermitsByLargeInterface.get(interfaceElement).put(profile, Arrays.stream(profilesArray).filter(profileName -> !profile.equals(profileName)).toList()); allProfilesToRemove.add(concatenatedProfiles); break; } } } }); - allProfilesToRemove.forEach(sealedInterfacesToGenerateByBloatedInterface.get(interfaceElement)::remove); - // and completing building sealedInterfacesPermitsByBloatedInterface map - buildSealedInterfacesPermitsMap(interfaceElement, sealedInterfacesToGenerateByBloatedInterface, sealedInterfacesPermitsByBloatedInterface); + allProfilesToRemove.forEach(sealedInterfacesToGenerateByLargeInterface.get(interfaceElement)::remove); + // and completing building sealedInterfacesPermitsByLargeInterface map + buildSealedInterfacesPermitsMap(interfaceElement, sealedInterfacesToGenerateByLargeInterface, sealedInterfacesPermitsByLargeInterface); }); } } @@ -148,7 +148,7 @@ public Map checkAndHandleUniqueParentInterface(final Map Date: Sun, 12 Dec 2021 17:00:16 -0500 Subject: [PATCH 06/11] JIS-01: major bug fix - extends clause for large interfaces empty by default --- .../generator/SealedInterfaceContentGenerator.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java b/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java index 9845d17..31b274b 100644 --- a/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java +++ b/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java @@ -91,13 +91,14 @@ public void generateExtendsClauseFromPermitsMapAndProcessedProfile(final Process generateCode(sealedInterfaceContent, parentList); } else { // only for largeInterface sealed interface generation, add interfaces it extends if any - if (largeInterfaceElement.getSimpleName().toString().equals(processedProfile)) { + var superInterfacesList = processingEnvironment.getTypeUtils().directSupertypes(largeInterfaceElement.asType()).stream() + .map(Object::toString) + .filter(superType -> !superType.contains(JAVA_LANG_OBJECT)) + .toList(); + if (largeInterfaceElement.getSimpleName().toString().equals(processedProfile) && !superInterfacesList.isEmpty()) { generateCode( sealedInterfaceContent, - processingEnvironment.getTypeUtils().directSupertypes(largeInterfaceElement.asType()).stream() - .map(Object::toString) - .filter(superType -> !superType.contains(JAVA_LANG_OBJECT)) - .toList() + superInterfacesList ); } } From 2cbe46e17f85d58bad9b66be9de63a57f26e6493 Mon Sep 17 00:00:00 2001 From: Mohamed Ashraf Bayor Date: Mon, 13 Dec 2021 14:39:30 -0500 Subject: [PATCH 07/11] JIS-01: modularized the project --- pom.xml | 9 ++++++++- src/main/java/module-info.java | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 src/main/java/module-info.java diff --git a/pom.xml b/pom.xml index 4959b0c..42df985 100644 --- a/pom.xml +++ b/pom.xml @@ -52,7 +52,7 @@ com.google.auto.service - auto-service + auto-service-annotations ${auto-service.version} @@ -66,6 +66,13 @@ 17 -Xlint:unchecked + + + com.google.auto.service + auto-service + ${auto-service.version} + + diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..fc1f3ce --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,6 @@ +module jisel { + requires java.compiler; + requires com.google.auto.service; + requires java.logging; + exports org.jisel; +} \ No newline at end of file From 9fe497436681417a491c18c29afaceb6bba9b9c5 Mon Sep 17 00:00:00 2001 From: Mohamed Ashraf Bayor Date: Sun, 26 Dec 2021 21:08:42 -0600 Subject: [PATCH 08/11] JIS-01: bug fixes + javadoc in progress --- LICENSE | 2 +- src/main/java/module-info.java | 25 ++- src/main/java/org/jisel/AddToProfile.java | 37 +++- src/main/java/org/jisel/AddToProfiles.java | 74 ++++++++ .../org/jisel/JiselAnnotationProcessor.java | 30 +++- src/main/java/org/jisel/SealForProfile.java | 23 ++- src/main/java/org/jisel/SealForProfiles.java | 31 +++- .../org/jisel/generator/CodeGenerator.java | 8 +- .../jisel/generator/FinalClassGenerator.java | 6 +- .../generator/JavaxGeneratedGenerator.java | 2 +- .../org/jisel/generator/ReportGenerator.java | 2 +- .../SealedInterfaceContentGenerator.java | 14 +- .../SealedInterfaceSourceFileGenerator.java | 2 +- .../org/jisel/generator/StringGenerator.java | 36 +++- .../jisel/handlers/AddToProfileHandler.java | 13 +- .../handlers/JiselAnnotationHandler.java | 167 +++++++++++++----- .../jisel/handlers/SealForProfileHandler.java | 13 +- 17 files changed, 398 insertions(+), 87 deletions(-) create mode 100644 src/main/java/org/jisel/AddToProfiles.java diff --git a/LICENSE b/LICENSE index 5ba0a90..9493604 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Mohamed Ashraf Bayor +Copyright (c) 2022 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 diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index fc1f3ce..9e74b14 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,6 +1,27 @@ +/** + * Copyright (c) 2022 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. + */ module jisel { + exports org.jisel; requires java.compiler; - requires com.google.auto.service; requires java.logging; - exports org.jisel; + requires com.google.auto.service; } \ No newline at end of file diff --git a/src/main/java/org/jisel/AddToProfile.java b/src/main/java/org/jisel/AddToProfile.java index 9762bd3..1d548bf 100644 --- a/src/main/java/org/jisel/AddToProfile.java +++ b/src/main/java/org/jisel/AddToProfile.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Mohamed Ashraf Bayor. + * Copyright (c) 2022 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 @@ -27,17 +27,48 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import static org.jisel.generator.StringGenerator.EMPTY_STRING; + +/** + * Annotation to be applied on top of a class, an interface or a record which is implementing or extending a sealed interface generated by Jisel.

+ * All sealed interfaces generated by Jisel follow the naming convention: Sealed<ProfileName><LargeInterfaceSimpleName>.
+ * See @AddToProfile attributes documentation.

+ */ @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE}) @Repeatable(AddToProfile.AddToProfilez.class) public @interface AddToProfile { - String profileName(); - String interfaceName(); + /** + * Not Required - specifies the name of one of the profiles used with the @SealForProfile annotations in the large interface definition. + * Also corresponds to the <ProfileName> from the sealed interfaces naming convention.
+ * If not provided or empty, the annotated class, interface or record will be added to the permits list of the generated parent sealed interface.
+ * Also, the provided profile attribute value MUST be one of the profiles defined in the large interface definition using @SealForProfile. If not, + * the specified profile will be ignored and an informational message regarding provided incorrect profiles will be printed during the compilation.
+ * + * @return the profile name + */ + String profile() default EMPTY_STRING; + + /** + * Required - MUST be the Fully Qualified Name of the large interface. That would be the <LargeInterfaceSimpleName> as seen in + * the sealed interface name convention, preceded by the package name.
+ * + * @return the large interface fully qualified name + */ + String largeInterface(); + /** + * Internal annotation allowing @AddToProfile to be repeatable + */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) @interface AddToProfilez { + /** + * array attribute allowing @AddToProfile to be repeatable + * + * @return array of @AddToProfile instances + */ 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..4a76102 --- /dev/null +++ b/src/main/java/org/jisel/AddToProfiles.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2022 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; + +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; + +import static org.jisel.generator.StringGenerator.EMPTY_STRING; + +/** + * Annotation to be applied on top of a class, an interface or a record which is implementing or extending a sealed interface generated by Jisel.

+ * All sealed interfaces generated by Jisel follow the naming convention: Sealed<ProfileName><LargeInterfaceSimpleName>.
+ * See @AddToProfile attributes documentation.

+ */ +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE}) +@Repeatable(AddToProfiles.AddToProfilezz.class) +public @interface AddToProfiles { + + /** + * Not Required - specifies an array containing the names of any of the profiles used with the @SealForProfile annotations in the large interface definition. + * Each one of the profiles names corresponds to the <ProfileName> from the sealed interfaces naming convention.
+ * If not provided or empty, the annotated class, interface or record will be added to the permits list of the generated parent sealed interface.
+ * Also, each one of the provided profiles names MUST be one of the profiles defined in the large interface definition using @SealForProfile. If not, + * the specified profile will be ignored and an informational message regarding provided incorrect profiles will be printed during the compilation.
+ * + * @return an array of the profiles names + */ + String[] profiles() default EMPTY_STRING; + + /** + * Required - MUST be the Fully Qualified Name of the large interface. That would be the <LargeInterfaceSimpleName> as seen + * in the sealed interface name convention, preceded by the package name.
+ * + * @return the large interface fully qualified name + */ + String largeInterface(); + + /** + * Internal annotation allowing @AddToProfiles to be repeatable + */ + @Retention(RetentionPolicy.SOURCE) + @Target(ElementType.TYPE) + @interface AddToProfilezz { + /** + * array attribute allowing @AddToProfiles to be repeatable + * + * @return array of @AddToProfiles instances + */ + AddToProfiles[] 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 index f9ce102..8b4c5f4 100644 --- a/src/main/java/org/jisel/JiselAnnotationProcessor.java +++ b/src/main/java/org/jisel/JiselAnnotationProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Mohamed Ashraf Bayor. + * Copyright (c) 2022 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 @@ -50,13 +50,19 @@ import static java.util.Collections.unmodifiableSet; import static java.util.stream.Collectors.joining; import static org.jisel.generator.StringGenerator.ORG_JISEL_ADD_TO_PROFILE; +import static org.jisel.generator.StringGenerator.ORG_JISEL_ADD_TO_PROFILES; import static org.jisel.generator.StringGenerator.ORG_JISEL_ADD_TO_PROFILEZ; +import static org.jisel.generator.StringGenerator.ORG_JISEL_ADD_TO_PROFILEZZ; import static org.jisel.generator.StringGenerator.ORG_JISEL_SEAL_FOR_PROFILE; import static org.jisel.generator.StringGenerator.ORG_JISEL_SEAL_FOR_PROFILES; import static org.jisel.generator.StringGenerator.ORG_JISEL_SEAL_FOR_PROFILEZ; +import static org.jisel.generator.StringGenerator.ORG_JISEL_SEAL_FOR_PROFILEZZ; -@SupportedAnnotationTypes({ORG_JISEL_SEAL_FOR_PROFILE, ORG_JISEL_SEAL_FOR_PROFILES, ORG_JISEL_SEAL_FOR_PROFILEZ, - ORG_JISEL_ADD_TO_PROFILE, ORG_JISEL_ADD_TO_PROFILEZ}) +/** + * Jisel annotation processor class. Picks up and processes all elements annotated with @SealForProfile, @SealForProfiles, @AddToProfile and @AddToProfiles
+ */ +@SupportedAnnotationTypes({ORG_JISEL_SEAL_FOR_PROFILE, ORG_JISEL_SEAL_FOR_PROFILES, ORG_JISEL_SEAL_FOR_PROFILEZ, ORG_JISEL_SEAL_FOR_PROFILEZZ, + ORG_JISEL_ADD_TO_PROFILE, ORG_JISEL_ADD_TO_PROFILES, ORG_JISEL_ADD_TO_PROFILEZ, ORG_JISEL_ADD_TO_PROFILEZZ}) @SupportedSourceVersion(SourceVersion.RELEASE_17) @AutoService(Processor.class) public class JiselAnnotationProcessor extends AbstractProcessor implements StringGenerator { @@ -69,6 +75,10 @@ public class JiselAnnotationProcessor extends AbstractProcessor implements Strin private final SealedInterfaceSourceFileGenerator sealedInterfaceSourceFileGenerator; + /** + * Constructor of JiselAnnotationProcessor. + * Initializes unique instances of SealForProfileHandler, AddToProfileHandler and SealedInterfaceSourceFileGenerator to be used within the class + */ public JiselAnnotationProcessor() { this.sealForProfileHandler = new SealForProfileHandler(); this.addToProfileHandler = new AddToProfileHandler(); @@ -104,7 +114,11 @@ public boolean process(final Set annotations, final Round displayStatusReport(statusReport, ADD_TO_PROFILE); try { - var generatedFiles = sealedInterfaceSourceFileGenerator.createSourceFiles(processingEnv, sealedInterfacesToGenerateByLargeInterface, sealedInterfacesPermitsByLargeInterface); + var generatedFiles = sealedInterfaceSourceFileGenerator.createSourceFiles( + processingEnv, + sealedInterfacesToGenerateByLargeInterface, + sealedInterfacesPermitsByLargeInterface + ); if (!generatedFiles.isEmpty()) { log.info(() -> format("%s:%n%s", FILE_GENERATION_SUCCESS, generatedFiles.stream().collect(joining(format("%n"))))); } @@ -115,8 +129,10 @@ public boolean process(final Set annotations, final Round return true; } - private void populateAllAnnotatedElementsSets(final Set annotations, final RoundEnvironment roundEnv, - final Set allAnnotatedSealForProfileElements, final Set allAnnotatedAddToProfileElements) { + private void populateAllAnnotatedElementsSets(final Set annotations, + final RoundEnvironment roundEnv, + final Set allAnnotatedSealForProfileElements, + final Set allAnnotatedAddToProfileElements) { for (var annotation : annotations) { if (annotation.getSimpleName().toString().contains(SEAL_FOR_PROFILE)) { allAnnotatedSealForProfileElements.addAll(roundEnv.getElementsAnnotatedWith(annotation)); @@ -132,7 +148,7 @@ private void displayStatusReport(final Map statusReport, final var output = new StringBuilder(format("%n%s - @%s%n", STATUS_REPORT_TITLE, annotationName)); statusReport.entrySet().forEach(mapEntry -> { if (!mapEntry.getValue().isBlank()) { - output.append(format("\t> %s: %s%n", mapEntry.getKey().getSimpleName().toString(), mapEntry.getValue())); + output.append(format("\t> %s: %s%n", mapEntry.getKey().toString(), mapEntry.getValue())); } }); log.warning(output::toString); diff --git a/src/main/java/org/jisel/SealForProfile.java b/src/main/java/org/jisel/SealForProfile.java index 7160db6..93c0194 100644 --- a/src/main/java/org/jisel/SealForProfile.java +++ b/src/main/java/org/jisel/SealForProfile.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Mohamed Ashraf Bayor. + * Copyright (c) 2022 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 @@ -27,16 +27,37 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Annotation to be applied only on top of abstract methods of an interface you intend to segregate.

+ * For the specified profile name, a sealed interface will be generated following the naming convention: + * Sealed<ProfileName><LargeInterfaceSimpleName>.

+ * <LargeInterfaceSimpleName> corresponds to the simplename of the interface being segregated.

+ * See @SealForProfile attributes documentation.

+ */ @Retention(RetentionPolicy.SOURCE) @Target({ElementType.METHOD}) @Repeatable(SealForProfile.SealForProfilez.class) public @interface SealForProfile { + /** + * Profile name to use while segregation the large interface. A sealed interface file will be generated following the naming convention: + * Sealed<ProfileName><LargeInterfaceSimpleName> + * + * @return a string containing the profile name + */ String value(); + /** + * Internal annotation allowing @SealForProfile to be repeatable + */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) @interface SealForProfilez { + /** + * array attribute allowing @SealForProfile to be repeatable + * + * @return array of @SealForProfile instances + */ 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 index 6384fa4..41db517 100644 --- a/src/main/java/org/jisel/SealForProfiles.java +++ b/src/main/java/org/jisel/SealForProfiles.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Mohamed Ashraf Bayor. + * Copyright (c) 2022 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 @@ -22,12 +22,41 @@ 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; +/** + * Annotation to be applied only on top of abstract methods of an interface you intend to segregate.

+ * For each one of the specified profiles names, a sealed interface will be generated following the naming convention: Sealed<ProfileName><LargeInterfaceSimpleName>.

+ * <LargeInterfaceSimpleName> corresponds to the simplename of the interface being segregated.

+ * See @SealForProfiles attributes documentation.

+ */ @Retention(RetentionPolicy.SOURCE) @Target({ElementType.METHOD}) +@Repeatable(SealForProfiles.SealForProfilezz.class) public @interface SealForProfiles { + + /** + * Profile name to use while segregation the large interface. A sealed interface file will be generated following the naming convention: + * Sealed<ProfileName><LargeInterfaceSimpleName> + * + * @return an array of the profiles names + */ String[] value(); + + /** + * Internal annotation allowing @SealForProfiles to be repeatable + */ + @Retention(RetentionPolicy.SOURCE) + @Target(ElementType.METHOD) + @interface SealForProfilezz { + /** + * array attribute allowing @SealForProfiles to be repeatable + * + * @return array of @SealForProfiles instances + */ + SealForProfiles[] value(); + } } \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/CodeGenerator.java b/src/main/java/org/jisel/generator/CodeGenerator.java index fd29ca3..d6ed8db 100644 --- a/src/main/java/org/jisel/generator/CodeGenerator.java +++ b/src/main/java/org/jisel/generator/CodeGenerator.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Mohamed Ashraf Bayor. + * Copyright (c) 2022 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 @@ -47,7 +47,7 @@ public sealed interface CodeGenerator permits JavaxGeneratedGenerator, ExtendsGe void generateCode(StringBuilder classOrInterfaceContent, List params); } -sealed interface ExtendsGenerator extends CodeGenerator, StringGenerator permits JiselExtendsGenerator { +sealed interface ExtendsGenerator extends CodeGenerator, StringGenerator permits SealedInterfaceExtendsGenerator { void generateExtendsClauseFromPermitsMapAndProcessedProfile(ProcessingEnvironment processingEnvironment, StringBuilder sealedInterfaceContent, Map> permitsMap, String processedProfile, Element largeInterfaceElement); @@ -65,7 +65,7 @@ private boolean isInterface(final String classOrInterfaceContent) { } } -sealed interface PermitsGenerator extends CodeGenerator, StringGenerator permits JiselPermitsGenerator { +sealed interface PermitsGenerator extends CodeGenerator, StringGenerator permits SealedInterfacePermitsGenerator { void generatePermitsClauseFromPermitsMapAndProcessedProfile(StringBuilder sealedInterfaceContent, Map> permitsMap, String processedProfile, Element largeInterfaceElement); @@ -91,7 +91,7 @@ default void addFinalClassToPermitsMap(final Map> permitsMa } } -sealed interface MethodsGenerator extends CodeGenerator, StringGenerator permits JiselMethodsGenerator { +sealed interface MethodsGenerator extends CodeGenerator, StringGenerator permits SealedInterfaceMethodsGenerator { void generateAbstractMethodsFromElementsSet(StringBuilder sealedInterfaceContent, Set methodsSet); diff --git a/src/main/java/org/jisel/generator/FinalClassGenerator.java b/src/main/java/org/jisel/generator/FinalClassGenerator.java index 9f17f8a..3d38fdc 100644 --- a/src/main/java/org/jisel/generator/FinalClassGenerator.java +++ b/src/main/java/org/jisel/generator/FinalClassGenerator.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Mohamed Ashraf Bayor. + * Copyright (c) 2022 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 @@ -40,8 +40,8 @@ public class FinalClassGenerator implements StringGenerator { public FinalClassGenerator() { this.javaxGeneratedGenerator = new JavaxGeneratedGenerator(); - this.extendsGenerator = new JiselExtendsGenerator(); - this.methodsGenerator = new JiselMethodsGenerator(); + this.extendsGenerator = new SealedInterfaceExtendsGenerator(); + this.methodsGenerator = new SealedInterfaceMethodsGenerator(); } public String generateFinalClassContent(final ProcessingEnvironment processingEnvironment, final Element largeInterfaceElement, final Map> sealedInterfacesPermitsMap) { diff --git a/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java b/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java index 636ca4b..a018c7a 100644 --- a/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java +++ b/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Mohamed Ashraf Bayor. + * Copyright (c) 2022 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 diff --git a/src/main/java/org/jisel/generator/ReportGenerator.java b/src/main/java/org/jisel/generator/ReportGenerator.java index 4ba8abf..db5c114 100644 --- a/src/main/java/org/jisel/generator/ReportGenerator.java +++ b/src/main/java/org/jisel/generator/ReportGenerator.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Mohamed Ashraf Bayor. + * Copyright (c) 2022 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 diff --git a/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java b/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java index 31b274b..5405f4a 100644 --- a/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java +++ b/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Mohamed Ashraf Bayor. + * Copyright (c) 2022 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 @@ -39,9 +39,9 @@ public final class SealedInterfaceContentGenerator implements StringGenerator { public SealedInterfaceContentGenerator() { this.javaxGeneratedGenerator = new JavaxGeneratedGenerator(); - this.extendsGenerator = new JiselExtendsGenerator(); - this.permitsGenerator = new JiselPermitsGenerator(); - this.methodsGenerator = new JiselMethodsGenerator(); + this.extendsGenerator = new SealedInterfaceExtendsGenerator(); + this.permitsGenerator = new SealedInterfacePermitsGenerator(); + this.methodsGenerator = new SealedInterfaceMethodsGenerator(); } public String generateSealedInterfaceContent(final ProcessingEnvironment processingEnvironment, @@ -75,7 +75,7 @@ public String generateSealedInterfaceContent(final ProcessingEnvironment process } } -final class JiselExtendsGenerator implements ExtendsGenerator { +final class SealedInterfaceExtendsGenerator implements ExtendsGenerator { @Override public void generateExtendsClauseFromPermitsMapAndProcessedProfile(final ProcessingEnvironment processingEnvironment, final StringBuilder sealedInterfaceContent, @@ -106,7 +106,7 @@ public void generateExtendsClauseFromPermitsMapAndProcessedProfile(final Process } } -final class JiselPermitsGenerator implements PermitsGenerator { +final class SealedInterfacePermitsGenerator implements PermitsGenerator { @Override public void generatePermitsClauseFromPermitsMapAndProcessedProfile(final StringBuilder sealedInterfaceContent, final Map> permitsMap, @@ -122,7 +122,7 @@ public void generatePermitsClauseFromPermitsMapAndProcessedProfile(final StringB } } -final class JiselMethodsGenerator implements MethodsGenerator { +final class SealedInterfaceMethodsGenerator implements MethodsGenerator { @Override public void generateAbstractMethodsFromElementsSet(final StringBuilder sealedInterfaceContent, final Set methodsSet) { diff --git a/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java b/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java index 8672b46..ddf3788 100644 --- a/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java +++ b/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Mohamed Ashraf Bayor. + * Copyright (c) 2022 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 diff --git a/src/main/java/org/jisel/generator/StringGenerator.java b/src/main/java/org/jisel/generator/StringGenerator.java index 214778d..99b3bc5 100644 --- a/src/main/java/org/jisel/generator/StringGenerator.java +++ b/src/main/java/org/jisel/generator/StringGenerator.java @@ -1,3 +1,24 @@ +/** + * Copyright (c) 2022 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; import javax.lang.model.element.Element; @@ -38,9 +59,13 @@ public interface StringGenerator { String SEAL_FOR_PROFILE = "SealForProfile"; String ADD_TO_PROFILE = "AddToProfile"; - String PROFILE_NAME_ATTRIBUTE = "profileName()"; - String INTERFACE_NAME_ATTRIBUTE = "interfaceName()"; + String PROFILE_ATTRIBUTE_REGEX = "profile=\"([^\"]*)\""; + // String PROFILES_ATTRIBUTE_REGEX = "profiles=\"([^\"]*)\""; // not working when { } present ? + String LARGE_INTERFACE_ATTRIBUTE_REGEX = "largeInterface=\"([^\"]*)\""; String ANNOTATION_VALUES_REGEX = "\"([^\"]*)\""; + String ADD_TO_PROFILE_REGEX = "AddToProfile\\((.*?)\\)"; // @org.jisel.AddToProfile(profile="ActiveWorker", largeInterface="com.bayor.jisel.annotation.client.data.Sociable") + String ADD_TO_PROFILES_REGEX = "AddToProfiles\\((.*?)\\)"; // @org.jisel.AddToProfiles(profiles={"Student", "Worker"}, largeInterface="com.bayor.jisel.annotation.client.data.Sociable"), + String STATUS_REPORT_TITLE = "JISEL GENERATION REPORT"; @@ -50,8 +75,11 @@ public interface StringGenerator { String ORG_JISEL_SEAL_FOR_PROFILE = "org.jisel.SealForProfile"; String ORG_JISEL_SEAL_FOR_PROFILES = "org.jisel.SealForProfiles"; String ORG_JISEL_SEAL_FOR_PROFILEZ = "org.jisel.SealForProfile.SealForProfilez"; + String ORG_JISEL_SEAL_FOR_PROFILEZZ = "org.jisel.SealForProfiles.SealForProfilezz"; String ORG_JISEL_ADD_TO_PROFILE = "org.jisel.AddToProfile"; + String ORG_JISEL_ADD_TO_PROFILES = "org.jisel.AddToProfiles"; String ORG_JISEL_ADD_TO_PROFILEZ = "org.jisel.AddToProfile.AddToProfilez"; + String ORG_JISEL_ADD_TO_PROFILEZZ = "org.jisel.AddToProfiles.AddToProfilezz"; String DEFAULT_BOOLEAN_VALUE = "false"; String DEFAULT_NUMBER_VALUE = "0"; @@ -103,4 +131,8 @@ default Optional generatePackageName(final Element largeInterfaceName) { default String removeDoubleSpaceOccurrences(final String text) { return text.replace(WHITESPACE + WHITESPACE, WHITESPACE); } + + default String removeCurlyBraces(final String text) { + return text.replace(OPENING_BRACKET, EMPTY_STRING).replace(CLOSING_BRACKET, EMPTY_STRING); + } } \ No newline at end of file diff --git a/src/main/java/org/jisel/handlers/AddToProfileHandler.java b/src/main/java/org/jisel/handlers/AddToProfileHandler.java index 2f4ead3..f916dd5 100644 --- a/src/main/java/org/jisel/handlers/AddToProfileHandler.java +++ b/src/main/java/org/jisel/handlers/AddToProfileHandler.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Mohamed Ashraf Bayor. + * Copyright (c) 2022 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 @@ -100,6 +100,15 @@ private boolean updateSealedInterfacesPermitsMapWithProvidedProfiles(final Set>> sealedInterfacesPermitsByLargeInterface) { var notFoundProfiles = new HashSet(); for (var providedProfile : providedProfilesForProvidedLargeInterface) { + if (providedProfile.isBlank()) { + // only for provided empty profiles, add to the largeInterface sealed profile permits list + sealedInterfacesPermitsByLargeInterface.get(providedLargeInterfaceElement).merge( + providedLargeInterfaceElement.getSimpleName().toString(), + asList(annotatedClassOrInterface.toString()), + (currentList, newList) -> concat(currentList.stream(), newList.stream()).toList() + ); + continue; + } var foundProvidedProfile = false; for (var profile : largeInterfaceProfilesSet) { if (profile.equals(providedLargeInterfaceElement.getSimpleName().toString())) { @@ -107,7 +116,7 @@ private boolean updateSealedInterfacesPermitsMapWithProvidedProfiles(final Set concat(currentList.stream(), newList.stream()).toList() ); diff --git a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java index 64eb7be..7cf0aa2 100644 --- a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java +++ b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Mohamed Ashraf Bayor. + * Copyright (c) 2022 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 @@ -43,52 +43,108 @@ import static java.util.stream.Stream.concat; import static org.jisel.generator.StringGenerator.removeSeparator; -public sealed interface JiselAnnotationHandler extends StringGenerator permits SealForProfileHandler, AddToProfileHandler, AnnotationInfoCollectionHandler, UniqueParentInterfaceHandler, ParentChildInheritanceHandler { +/** + * Interface exposing contract to fulfill by any class handling the elements annotated with @SealForProfile and @AddToProfile annotations + */ +public sealed interface JiselAnnotationHandler extends StringGenerator permits SealForProfileHandler, AddToProfileHandler, + AnnotationInfoCollectionHandler, UniqueParentInterfaceHandler, ParentChildInheritanceHandler { + /** + * Reads values of all attributes provided through the use of @SealForProfile and @AddToProfile annotations and + * populates the provided Map arguments + * + * @param processingEnv needed to access low-level information regarding the used annotations + * @param allAnnotatedElements Set of Element instances representing all classes annotated with @AddToProfile and + * all abstract methods annotated with @SealForProfile + * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. + * To be populated and/or modified if needed. The key represents the Element instance of + * each one of the large interfaces to be segregated, while the associated value is + * a Map of profile name as the key and a Set of Element instances as the value. + * The Element instances represent each one of the abstract methods to be + * added to the generated sealed interface corresponding to a profile. + * @param sealedInterfacesPermitsByLargeInterface Map containing information about the subtypes permitted by each one of the sealed interfaces to begenerated. + * To be populated and/or modified if needed. The key represents the Element instance of + * each one of the large interfaces to be segregated, while the associated value is + * a Map of profile name as the key and a List of profiles names as the value. + * @return a status report as a string value for each one of the large interfaces to be segregated + */ Map handleAnnotatedElements(ProcessingEnvironment processingEnv, Set allAnnotatedElements, Map>> sealedInterfacesToGenerateByLargeInterface, Map>> sealedInterfacesPermitsByLargeInterface); + /** + * For a specified class or interface annotated with @AddToProfile, constructs a Map storing a Set of all the provided + * profiles names (as the Map value) for each one of the large interfaces names (as the Map key) provided through @AddToProfile. + * + * @param processingEnv needed to access low-level information regarding the used annotations + * @param annotatedClassOrInterface Element instance representing the annotated class or interface + * @return a Map storing a Set of all the provided profiles names (as the Map value) for each one of the large interfaces names (as the Map key) + */ default Map> buildAddToProfileProvidedProfilesMap(final ProcessingEnvironment processingEnv, final Element annotatedClassOrInterface) { var providedProfilesMap = new HashMap>(); var annotationRawValueAsString = processingEnv.getElementUtils().getAllAnnotationMirrors(annotatedClassOrInterface).stream() .map(Object::toString) .collect(joining(COMMA_SEPARATOR)); - var matcher = Pattern.compile(ANNOTATION_VALUES_REGEX).matcher(annotationRawValueAsString); - var interfaceName = EMPTY_STRING; - var profileName = EMPTY_STRING; - while (matcher.find()) { - var profileOrInterface = matcher.group(1).trim(); - if (profileOrInterface.contains(DOT)) { - interfaceName = profileOrInterface; - } else { - profileName = profileOrInterface; + // sample values for annotationRawValueAsString: + // @org.jisel.AddToProfiles(profiles={"Student", "Worker"}, largeInterface="com.bayor.jisel.annotation.client.data.Sociable"),@org.jisel.AddToProfile(largeInterface="com.bayor.jisel.annotation.client.data.Sociable") + // process addToProfile + var addToProfileMatcher = Pattern.compile(ADD_TO_PROFILE_REGEX).matcher(annotationRawValueAsString); + while (addToProfileMatcher.find()) { + var attributesWithValues = addToProfileMatcher.group(1).strip(); // profile="ActiveWorker", largeInterface="com.bayor.jisel.annotation.client.data.Sociable" + var profilesSet = new HashSet(); + var profileAttributeMatcher = Pattern.compile(PROFILE_ATTRIBUTE_REGEX).matcher(attributesWithValues); + if (profileAttributeMatcher.find()) { // get only the first occurence + profilesSet.add(profileAttributeMatcher.group(1).strip()); } - if (!interfaceName.isBlank() && !profileName.isBlank()) { - providedProfilesMap.merge(interfaceName, new HashSet<>(Set.of(profileName)), (currentSet, newSet) -> concat(currentSet.stream(), newSet.stream()).collect(toSet())); - profileName = interfaceName = EMPTY_STRING; + updateProvidedProfilesMapBasedOnProfilesSet(providedProfilesMap, profilesSet, attributesWithValues); + } + // process addToProfiles + var addToProfilesMatcher = Pattern.compile(ADD_TO_PROFILES_REGEX).matcher(annotationRawValueAsString); + while (addToProfilesMatcher.find()) { + var attributesWithValues = addToProfilesMatcher.group(1).strip(); + // sample values for annotationRawValueAsString: + // profiles={"Student", "Worker"}, largeInterface="com.bayor.jisel.annotation.client.data.Sociable") + var commaSeparatedProfiles = attributesWithValues.substring(attributesWithValues.indexOf(OPENING_BRACKET) + 1, attributesWithValues.indexOf(CLOSING_BRACKET)); + var profilesSet = new HashSet(); + var profilesNamesMatcher = Pattern.compile(ANNOTATION_VALUES_REGEX).matcher(commaSeparatedProfiles); + while (profilesNamesMatcher.find()) { + profilesSet.add(profilesNamesMatcher.group(1).strip()); } + updateProvidedProfilesMapBasedOnProfilesSet(providedProfilesMap, profilesSet, attributesWithValues); } return providedProfilesMap; } + private void updateProvidedProfilesMapBasedOnProfilesSet(final Map> providedProfilesMap, final Set profilesSet, final String attributesWithValues) { + var largeInterface = EMPTY_STRING; + var largeInterfaceAttributeMatcher = Pattern.compile(LARGE_INTERFACE_ATTRIBUTE_REGEX).matcher(attributesWithValues); + if (largeInterfaceAttributeMatcher.find()) { // get only the first occurence + largeInterface = largeInterfaceAttributeMatcher.group(1).strip(); + } + providedProfilesMap.merge(largeInterface, profilesSet.isEmpty() ? new HashSet<>(Set.of(EMPTY_STRING)) : profilesSet, (currentSet, newSet) -> concat(currentSet.stream(), newSet.stream()).collect(toSet())); + } + /** - * @param processingEnv - * @param annotatedClassOrInterface - * @return + * For a specified large interface abstract method annotated with @SealForProfile, constructs a Set storing all the provided profiles names + * + * @param processingEnv needed to access low-level information regarding the used annotations + * @param annotatedMethod Element instance representing the annotated method of the large interface + * @return a Set storing all the provided profiles names */ - default Set buildSealForProfileProvidedProfilesSet(final ProcessingEnvironment processingEnv, final Element annotatedClassOrInterface) { - // annotationRawValueAsString: sample values: singl value "profile1name", array: {@org.jisel.SealForProfile("profile2name"), @org.jisel.SealForProfile("profile3name"),...} + default Set buildSealForProfileProvidedProfilesSet(final ProcessingEnvironment processingEnv, final Element annotatedMethod) { var providedProfilesSet = new HashSet(); - processingEnv.getElementUtils().getAllAnnotationMirrors(annotatedClassOrInterface).stream() + processingEnv.getElementUtils().getAllAnnotationMirrors(annotatedMethod).stream() .flatMap(annotationMirror -> annotationMirror.getElementValues().entrySet().stream()) .map(entry -> entry.getValue().toString()) .forEach(annotationRawValueAsString -> { + // sample values for annotationRawValueAsString: + // single value: "profile1name" + // multiple: {@org.jisel.SealForProfile("profile2name"), @org.jisel.SealForProfile("profile3name"),...} var matcher = Pattern.compile(ANNOTATION_VALUES_REGEX).matcher(annotationRawValueAsString); while (matcher.find()) { - var profile = matcher.group(1).trim(); - if (profile.isBlank()) { + var profile = matcher.group(1).strip(); + if (profile.isBlank()) { // blank profiles ignored continue; } providedProfilesSet.add(profile); @@ -98,15 +154,32 @@ default Set buildSealForProfileProvidedProfilesSet(final ProcessingEnvir } } +/** + * Interface exposing contract to fulfill by any class dedicated to collecting necessary information from the annotated elements, + * in order to populate the Map containing the sealed interfaces information to be generated + */ sealed interface AnnotationInfoCollectionHandler extends JiselAnnotationHandler permits SealForProfileInfoCollectionHandler { + /** + * Populates the Map containing the sealed interfaces information to be generated + * + * @param processingEnv needed to access low-level information regarding the used annotations + * @param allAnnotatedElements Set of Element instances representing all classes annotated with @AddToProfile and + * all abstract methods annotated with @SealForProfile + * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. + * To be populated and/or modified if needed. The key represents the Element instance of + * each one of the large interfaces to be segregated, while the associated value is + * a Map of profile name as the key and a Set of Element instances as the value. + * The Element instances represent each one of the abstract methods to be + * added to the generated sealed interface corresponding to a profile. + */ void populateSealedInterfacesMap(ProcessingEnvironment processingEnv, Set allAnnotatedElements, - Map>> sealedInterfacesToGenerate); + Map>> sealedInterfacesToGenerateByLargeInterface); - default void createParentInterfacesBasedOnCommonMethods(final Map>> annotatedMethodsByProfileByInterface, - final Map>> sealedInterfacesToGenerate) { - annotatedMethodsByProfileByInterface.forEach((interfaceElement, annotatedMethodsByProfile) -> { + default void createParentInterfacesBasedOnCommonMethods(final Map>> annotatedMethodsByProfileByLargeInterface, + final Map>> sealedInterfacesToGenerateByLargeInterface) { + annotatedMethodsByProfileByLargeInterface.forEach((interfaceElement, annotatedMethodsByProfile) -> { var profilesList = new ArrayList(); var methodsSetsList = new ArrayList>(); annotatedMethodsByProfile.forEach((profileName, methodsSet) -> { @@ -119,14 +192,14 @@ default void createParentInterfacesBasedOnCommonMethods(final Map { methodsSetsList.forEach(methodsSets -> methodsSets.remove(method)); - annotatedMethodsByProfileByInterface.get(interfaceElement).forEach((profileName, methodsSets) -> methodsSets.remove(method)); + annotatedMethodsByProfileByLargeInterface.get(interfaceElement).forEach((profileName, methodsSets) -> methodsSets.remove(method)); }); // - if (!sealedInterfacesToGenerate.containsKey(interfaceElement)) { - sealedInterfacesToGenerate.put(interfaceElement, new HashMap<>()); + if (!sealedInterfacesToGenerateByLargeInterface.containsKey(interfaceElement)) { + sealedInterfacesToGenerateByLargeInterface.put(interfaceElement, new HashMap<>()); } - sealedInterfacesToGenerate.get(interfaceElement).putAll(allProcessedCommonMethodsByConcatenatedProfiles); - sealedInterfacesToGenerate.get(interfaceElement).putAll(annotatedMethodsByProfileByInterface.get(interfaceElement)); + sealedInterfacesToGenerateByLargeInterface.get(interfaceElement).putAll(allProcessedCommonMethodsByConcatenatedProfiles); + sealedInterfacesToGenerateByLargeInterface.get(interfaceElement).putAll(annotatedMethodsByProfileByLargeInterface.get(interfaceElement)); } }); } @@ -161,31 +234,35 @@ default Map handleAnnotatedElements(final ProcessingEnvironment final Map>> sealedInterfacesToGenerateByLargeInterface, final Map>> sealedInterfacesPermitsByLargeInterface) { populateSealedInterfacesMap(processingEnv, allAnnotatedElements, sealedInterfacesToGenerateByLargeInterface); - return Map.of(); // return empty map instead of null + return Map.of(); // returning empty map instead of null } } sealed interface ParentChildInheritanceHandler extends JiselAnnotationHandler permits SealForProfileParentChildInheritanceHandler { void buildInheritanceRelations(Map>> sealedInterfacesToGenerateByLargeInterface, - Map>> sealedInterfacesPermitsByLargeInterface); + Map>> sealedInterfacesPermitsByLargeInterface, + Map uniqueParentInterfaceStatusReport); default void buildSealedInterfacesPermitsMap(final Element interfaceElement, final Map>> sealedInterfacesToGenerate, - final Map>> sealedInterfacesPermits) { + final Map>> sealedInterfacesPermits, + final Map uniqueParentInterfaceStatusReport) { sealedInterfacesPermits.get(interfaceElement).putAll(sealedInterfacesToGenerate.get(interfaceElement).keySet().stream() .filter(profiles -> profiles.contains(COMMA_SEPARATOR)) .collect(toMap(profiles -> profiles, profiles -> asList(profiles.split(COMMA_SEPARATOR))))); - // parent interf permits all, if there are other permits already existing then the profiles in those permits lists should be removed from parent interf permits list - var parentInterfaceSimpleName = interfaceElement.getSimpleName().toString(); - var allPermittedProfiles = sealedInterfacesPermits.get(interfaceElement).values().stream().flatMap(Collection::stream).collect(toSet()); - sealedInterfacesPermits.get(interfaceElement).put( - parentInterfaceSimpleName, - sealedInterfacesToGenerate.get(interfaceElement).keySet().stream() - .filter(profile -> !parentInterfaceSimpleName.equals(profile)) - .filter(profile -> !allPermittedProfiles.contains(profile)) - .toList() - ); + // if uniq parnt intrf prsnt, parent interf permits all, if there are other permits already existing then the profiles in those permits lists should be removed from parent interf permits list + if (Optional.ofNullable(uniqueParentInterfaceStatusReport).isPresent() && !uniqueParentInterfaceStatusReport.containsKey(interfaceElement)) { + var parentInterfaceSimpleName = interfaceElement.getSimpleName().toString(); + var allPermittedProfiles = sealedInterfacesPermits.get(interfaceElement).values().stream().flatMap(Collection::stream).collect(toSet()); + sealedInterfacesPermits.get(interfaceElement).put( + parentInterfaceSimpleName, + sealedInterfacesToGenerate.get(interfaceElement).keySet().stream() + .filter(profile -> !parentInterfaceSimpleName.equals(profile)) + .filter(profile -> !allPermittedProfiles.contains(profile)) + .toList() + ); + } } @Override @@ -193,7 +270,7 @@ default Map handleAnnotatedElements(final ProcessingEnvironment final Set allAnnotatedElements, final Map>> sealedInterfacesToGenerateByLargeInterface, final Map>> sealedInterfacesPermitsByLargeInterface) { - buildInheritanceRelations(sealedInterfacesToGenerateByLargeInterface, sealedInterfacesPermitsByLargeInterface); + buildInheritanceRelations(sealedInterfacesToGenerateByLargeInterface, sealedInterfacesPermitsByLargeInterface, null); return Map.of(); } } diff --git a/src/main/java/org/jisel/handlers/SealForProfileHandler.java b/src/main/java/org/jisel/handlers/SealForProfileHandler.java index 6a45fa8..67cc8ed 100644 --- a/src/main/java/org/jisel/handlers/SealForProfileHandler.java +++ b/src/main/java/org/jisel/handlers/SealForProfileHandler.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Mohamed Ashraf Bayor. + * Copyright (c) 2022 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 @@ -55,7 +55,7 @@ public Map handleAnnotatedElements(final ProcessingEnvironment final Map>> sealedInterfacesPermitsByLargeInterface) { annotationInfoCollectionHandler.populateSealedInterfacesMap(processingEnv, allAnnotatedElements, sealedInterfacesToGenerateByLargeInterface); var statusReport = uniqueParentInterfaceHandler.checkAndHandleUniqueParentInterface(sealedInterfacesToGenerateByLargeInterface); - parentChildInheritanceHandler.buildInheritanceRelations(sealedInterfacesToGenerateByLargeInterface, sealedInterfacesPermitsByLargeInterface); + parentChildInheritanceHandler.buildInheritanceRelations(sealedInterfacesToGenerateByLargeInterface, sealedInterfacesPermitsByLargeInterface, statusReport); return statusReport; } } @@ -65,7 +65,7 @@ final class SealForProfileInfoCollectionHandler implements AnnotationInfoCollect @Override public void populateSealedInterfacesMap(final ProcessingEnvironment processingEnv, final Set allAnnotatedElements, - final Map>> sealedInterfacesToGenerate) { + final Map>> sealedInterfacesToGenerateByLargeInterface) { var annotatedMethodsByInterface = allAnnotatedElements.stream() .filter(element -> ElementKind.METHOD.equals(element.getKind())) .filter(element -> ElementKind.INTERFACE.equals(element.getEnclosingElement().getKind())) @@ -85,7 +85,7 @@ public void populateSealedInterfacesMap(final ProcessingEnvironment processingEn ) ) ); - createParentInterfacesBasedOnCommonMethods(annotatedMethodsByProfileByInterface, sealedInterfacesToGenerate); + createParentInterfacesBasedOnCommonMethods(annotatedMethodsByProfileByInterface, sealedInterfacesToGenerateByLargeInterface); } private void extractProfilesAndPopulateMaps(final Element interfaceElement, @@ -110,7 +110,8 @@ final class SealForProfileParentChildInheritanceHandler implements ParentChildIn @Override public void buildInheritanceRelations(final Map>> sealedInterfacesToGenerateByLargeInterface, - final Map>> sealedInterfacesPermitsByLargeInterface) { + final Map>> sealedInterfacesPermitsByLargeInterface, + final Map uniqueParentInterfaceStatusReport) { sealedInterfacesToGenerateByLargeInterface.keySet().forEach(interfaceElement -> { sealedInterfacesPermitsByLargeInterface.put(interfaceElement, new HashMap<>()); // start with initializing sealedInterfacesPermitsByLargeInterface with empty mutable maps // promote profiles with empty methods to parent level @@ -131,7 +132,7 @@ public void buildInheritanceRelations(final Map Date: Mon, 27 Dec 2021 18:19:12 -0600 Subject: [PATCH 09/11] JIS-01: javadoc in progress --- src/main/java/org/jisel/AddToProfile.java | 2 +- src/main/java/org/jisel/AddToProfiles.java | 2 +- src/main/java/org/jisel/SealForProfile.java | 2 +- src/main/java/org/jisel/SealForProfiles.java | 2 +- .../org/jisel/generator/CodeGenerator.java | 123 +++++++++- .../org/jisel/generator/StringGenerator.java | 221 ++++++++++++++++-- .../jisel/handlers/AddToProfileHandler.java | 3 + .../handlers/JiselAnnotationHandler.java | 120 ++++++++-- .../jisel/handlers/SealForProfileHandler.java | 17 +- 9 files changed, 436 insertions(+), 56 deletions(-) diff --git a/src/main/java/org/jisel/AddToProfile.java b/src/main/java/org/jisel/AddToProfile.java index 1d548bf..9577e1f 100644 --- a/src/main/java/org/jisel/AddToProfile.java +++ b/src/main/java/org/jisel/AddToProfile.java @@ -31,7 +31,7 @@ /** * Annotation to be applied on top of a class, an interface or a record which is implementing or extending a sealed interface generated by Jisel.

- * All sealed interfaces generated by Jisel follow the naming convention: Sealed<ProfileName><LargeInterfaceSimpleName>.
+ * All sealed interfaces generated by Jisel follow the naming convention: Sealed<ProfileName><LargeInterfaceSimpleName>
* See @AddToProfile attributes documentation.

*/ @Retention(RetentionPolicy.SOURCE) diff --git a/src/main/java/org/jisel/AddToProfiles.java b/src/main/java/org/jisel/AddToProfiles.java index 4a76102..5d60a99 100644 --- a/src/main/java/org/jisel/AddToProfiles.java +++ b/src/main/java/org/jisel/AddToProfiles.java @@ -31,7 +31,7 @@ /** * Annotation to be applied on top of a class, an interface or a record which is implementing or extending a sealed interface generated by Jisel.

- * All sealed interfaces generated by Jisel follow the naming convention: Sealed<ProfileName><LargeInterfaceSimpleName>.
+ * All sealed interfaces generated by Jisel follow the naming convention: Sealed<ProfileName><LargeInterfaceSimpleName>
* See @AddToProfile attributes documentation.

*/ @Retention(RetentionPolicy.SOURCE) diff --git a/src/main/java/org/jisel/SealForProfile.java b/src/main/java/org/jisel/SealForProfile.java index 93c0194..d7b0729 100644 --- a/src/main/java/org/jisel/SealForProfile.java +++ b/src/main/java/org/jisel/SealForProfile.java @@ -30,7 +30,7 @@ /** * Annotation to be applied only on top of abstract methods of an interface you intend to segregate.

* For the specified profile name, a sealed interface will be generated following the naming convention: - * Sealed<ProfileName><LargeInterfaceSimpleName>.

+ * Sealed<ProfileName><LargeInterfaceSimpleName>

* <LargeInterfaceSimpleName> corresponds to the simplename of the interface being segregated.

* See @SealForProfile attributes documentation.

*/ diff --git a/src/main/java/org/jisel/SealForProfiles.java b/src/main/java/org/jisel/SealForProfiles.java index 41db517..c4ae86d 100644 --- a/src/main/java/org/jisel/SealForProfiles.java +++ b/src/main/java/org/jisel/SealForProfiles.java @@ -29,7 +29,7 @@ /** * Annotation to be applied only on top of abstract methods of an interface you intend to segregate.

- * For each one of the specified profiles names, a sealed interface will be generated following the naming convention: Sealed<ProfileName><LargeInterfaceSimpleName>.

+ * For each one of the specified profiles names, a sealed interface will be generated following the naming convention: Sealed<ProfileName><LargeInterfaceSimpleName>

* <LargeInterfaceSimpleName> corresponds to the simplename of the interface being segregated.

* See @SealForProfiles attributes documentation.

*/ diff --git a/src/main/java/org/jisel/generator/CodeGenerator.java b/src/main/java/org/jisel/generator/CodeGenerator.java index d6ed8db..82ceeef 100644 --- a/src/main/java/org/jisel/generator/CodeGenerator.java +++ b/src/main/java/org/jisel/generator/CodeGenerator.java @@ -34,29 +34,52 @@ import static java.util.stream.Collectors.joining; /** - * Exposes contract for a CodeGenerator class to fulfill + * Exposes contract to be fulfilled by a class generating the code of a sealed interface */ public sealed interface CodeGenerator permits JavaxGeneratedGenerator, ExtendsGenerator, PermitsGenerator, MethodsGenerator { /** - * Generates piece of code requested, based on the parameters provided in the params object and appends it to the provided recordClassContent param + * Generates piece of code requested, based on the parameters provided in the params object and appends it to the provided classOrInterfaceContent param * - * @param classOrInterfaceContent Stringbuilder object containing the record class code being generated - * @param params expected parameters. restricted to paremeters and values expected by the implementing class + * @param classOrInterfaceContent Stringbuilder object containing the sealed interface code being generated + * @param params expected parameters */ void generateCode(StringBuilder classOrInterfaceContent, List params); } +/** + * Exposes contract to be fulfilled by a class generating the "extends" clause of a sealed interface definition, along with + * the list of the parent classes or interfaces + */ sealed interface ExtendsGenerator extends CodeGenerator, StringGenerator permits SealedInterfaceExtendsGenerator { - void generateExtendsClauseFromPermitsMapAndProcessedProfile(ProcessingEnvironment processingEnvironment, StringBuilder sealedInterfaceContent, Map> permitsMap, String processedProfile, Element largeInterfaceElement); + /** + * Generates the "extends" clause of a sealed interface being generated, along with the list of parent interfaces, based on + * a provided Map containing parents/subtypes information (the permits Map) and the name of the profile for which the + * sealed interface will be generated + * + * @param processingEnvironment needed to access low-level information regarding the used annotations + * @param sealedInterfaceContent StringBuilder object containing the sealed interface code being generated + * @param permitsMap Map containing parents/subtypes information. The Map key is the profile name whose generated + * sealed interface will be a parent interface, while the value is the list of profiles names whose + * sealed interfaces will be generated as subtypes + * @param processedProfile name of the profile whose sealed interface is being generated + * @param largeInterfaceElement Element instance of the large interface being segregated + */ + void generateExtendsClauseFromPermitsMapAndProcessedProfile( + ProcessingEnvironment processingEnvironment, + StringBuilder sealedInterfaceContent, + Map> permitsMap, + String processedProfile, + Element largeInterfaceElement + ); @Override default void generateCode(final StringBuilder classOrInterfaceContent, final List params) { classOrInterfaceContent.append(format( " %s %s ", isInterface(classOrInterfaceContent.toString()) ? EXTENDS : IMPLEMENTS, - params.stream().map(StringGenerator::removeSeparator).collect(joining(COMMA_SEPARATOR + WHITESPACE)) + params.stream().map(StringGenerator::removeCommaSeparator).collect(joining(COMMA_SEPARATOR + WHITESPACE)) )); } @@ -65,19 +88,50 @@ private boolean isInterface(final String classOrInterfaceContent) { } } +/** + * Exposes contract to be fulfilled by a class generating the "permits" clause of a sealed interface definition, along with + * the list of the subtypes classes or interfaces permitted by the sealed interface being generated + */ sealed interface PermitsGenerator extends CodeGenerator, StringGenerator permits SealedInterfacePermitsGenerator { - void generatePermitsClauseFromPermitsMapAndProcessedProfile(StringBuilder sealedInterfaceContent, Map> permitsMap, String processedProfile, Element largeInterfaceElement); + /** + * Generates the "permits" clause of a sealed interface being generated, along with the list of parent interfaces, based on + * a provided Map containing parents/subtypes information (the permits Map) and the name of the profile for which the + * sealed interface will be generated + * + * @param sealedInterfaceContent StringBuilder object containing the sealed interface code being generated + * @param permitsMap Map containing parents/subtypes information. The Map key is the profile name whose generated + * sealed interface will be a parent interface, while the value is the list of profiles names whose + * sealed interfaces will be generated as subtypes + * @param processedProfile name of the profile whose sealed interface is being generated + * @param largeInterfaceElement Element instance of the large interface being segregated + */ + void generatePermitsClauseFromPermitsMapAndProcessedProfile( + StringBuilder sealedInterfaceContent, + Map> permitsMap, + String processedProfile, + Element largeInterfaceElement + ); @Override default void generateCode(final StringBuilder classOrInterfaceContent, final List params) { classOrInterfaceContent.append(format( " %s %s ", PERMITS, - params.stream().map(StringGenerator::removeSeparator).collect(joining(COMMA_SEPARATOR + WHITESPACE)) + params.stream().map(StringGenerator::removeCommaSeparator).collect(joining(COMMA_SEPARATOR + WHITESPACE)) )); } + /** + * Adds a generated final class to the Map containing parents/subtypes information, only for the sealed interfaces at the + * lowest-level of the generated hierarchy (also know as childless interfaces).
+ * Practice proper to Jisel only to avoid compilation errors for sealed interfaces not having any existing subtypes + * + * @param permitsMap Map containing parents/subtypes information. The Map key is the profile name whose generated + * sealed interface will be a parent interface, while the value is the list of profiles names whose + * sealed interfaces will be generated as subtypes + * @param largeInterfaceElement Element instance of the large interface being segregated + */ default void addFinalClassToPermitsMap(final Map> permitsMap, final Element largeInterfaceElement) { var finalClassName = UNDERSCORE + largeInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX; var childlessProfiles = permitsMap.values().stream() @@ -85,16 +139,33 @@ default void addFinalClassToPermitsMap(final Map> permitsMa .distinct() .filter(childProfileName -> permitsMap.keySet().stream().noneMatch(parentProfile -> parentProfile.equals(childProfileName))) .filter(childProfileName -> !finalClassName.equals(childProfileName)) // if finalClassName found remove it from the new list - .filter(childProfileName -> !childProfileName.contains(DOT)) // also skip all qualifiedname classes add by @addToProfile + .filter(childProfileName -> !childProfileName.contains(DOT)) // also skip all qualifiedname classes added by @addToProfile .toList(); childlessProfiles.forEach(childlessProfile -> permitsMap.put(childlessProfile, asList(finalClassName))); } } +/** + * Exposes contract to be fulfilled by a class generating the list of abstracts methods of a sealed interface being generated.
+ * Same is also used to generate the list of concrete methods of the convenience final class generated for the bottom-level generated sealed interfaces + */ sealed interface MethodsGenerator extends CodeGenerator, StringGenerator permits SealedInterfaceMethodsGenerator { + /** + * Generates a list of abstracts methods definitions and appends it to the sealed interface code being generated + * + * @param sealedInterfaceContent StringBuilder object containing the sealed interface code being generated + * @param methodsSet Set of {@link Element} instances representing each one of the abstract methods to generate + */ void generateAbstractMethodsFromElementsSet(StringBuilder sealedInterfaceContent, Set methodsSet); + /** + * Mainly used for a final class generation.
+ * Generates a list of concrete methods definitions (signature and body), and appends it to the final class being generated + * + * @param sealedInterfaceContent StringBuilder object containing the sealed interface code being generated + * @param methodsSet Set of {@link Element} instances representing each one of the abstract methods to generate + */ void generateEmptyConcreteMethodsFromElementsSet(StringBuilder sealedInterfaceContent, Set methodsSet); @Override @@ -102,14 +173,32 @@ default void generateCode(final StringBuilder classOrInterfaceContent, final Lis params.forEach(methodDefinition -> classOrInterfaceContent.append(format("\t%s%n", methodDefinition))); } - default boolean methodHasArguments(final Element element) { - return element.toString().indexOf(CLOSING_PARENTHESIS) - element.toString().indexOf(OPENING_PARENTHESIS) > 1; + /** + * Checks whether the provided method {@link Element} instance has any arguments + * + * @param methodElement method {@link Element} instance + * @return true if the provided method has any arguments + */ + default boolean methodHasArguments(final Element methodElement) { + return methodElement.toString().indexOf(CLOSING_PARENTHESIS) - methodElement.toString().indexOf(OPENING_PARENTHESIS) > 1; } + /** + * Returns a string representing the fully qualified name of a method return type + * + * @param methodElement method {@link Element} instance + * @return a string representing the fully qualified name of the provided method's return type + */ default String generateReturnType(final Element methodElement) { return ((ExecutableType) methodElement.asType()).getReturnType().toString(); } + /** + * Constructs a string containing a method name and parameters formatted as in the method signature + * + * @param methodElement method {@link Element} instance + * @return a string containing a method name and parameters formatted as in the method signature + */ default String generateMethodNameAndParameters(final Element methodElement) { var output = methodElement.toString(); if (methodHasArguments(methodElement)) { @@ -123,10 +212,22 @@ default String generateMethodNameAndParameters(final Element methodElement) { return output; } + /** + * Constructs a string containing a comma-separated list of exceptions classes thrown by the provided method {@link Element} instance + * + * @param methodElement method {@link Element} instance + * @return a comma-separated list of exceptions classes thrown by the provided method {@link Element} instance + */ default String generateThrownExceptions(final Element methodElement) { return ((ExecutableType) methodElement.asType()).getThrownTypes().stream().map(Object::toString).collect(joining(COMMA_SEPARATOR + WHITESPACE)); } + /** + * Constructs a string containing the default value for the provided method's return type + * + * @param methodElement method {@link Element} instance + * @return a string containing the default value for the provided method's return type + */ default String generateDefaultReturnValueForMethod(final Element methodElement) { return switch (((ExecutableType) methodElement.asType()).getReturnType().getKind()) { case BOOLEAN -> RETURN + WHITESPACE + DEFAULT_BOOLEAN_VALUE; diff --git a/src/main/java/org/jisel/generator/StringGenerator.java b/src/main/java/org/jisel/generator/StringGenerator.java index 99b3bc5..7389dbf 100644 --- a/src/main/java/org/jisel/generator/StringGenerator.java +++ b/src/main/java/org/jisel/generator/StringGenerator.java @@ -30,109 +30,296 @@ import static java.util.Arrays.asList; import static java.util.stream.Collectors.joining; +/** + * A bunch of String literals and commonly used string handling functions + */ public interface StringGenerator { + /** + * "," comma separator + */ String COMMA_SEPARATOR = ","; + /** + * ";" semi-colon + */ String SEMICOLON = ";"; + /** + * " " whitespace + */ String WHITESPACE = " "; + /** + * "Sealed" + */ String SEALED_PREFIX = "Sealed"; + /** + * "package" + */ String PACKAGE = "package"; + /** + * "interface" + */ String INTERFACE = "interface"; + /** + * "class" + */ String CLASS = "class"; + /** + * "public sealed interface" + */ String PUBLIC_SEALED_INTERFACE = "public sealed interface"; + /** + * "public final class" + */ String PUBLIC_FINAL_CLASS = "public final class"; + /** + * "extends" + */ String EXTENDS = "extends"; + /** + * "implements" + */ String IMPLEMENTS = "implements"; + /** + * "permits" + */ String PERMITS = "permits"; + /** + * "{" + */ String OPENING_BRACKET = "{"; + /** + * "}" + */ String CLOSING_BRACKET = "}"; + /** + * "(" + */ String OPENING_PARENTHESIS = "("; + /** + * ")" + */ String CLOSING_PARENTHESIS = ")"; + /** + * "" + */ String EMPTY_STRING = ""; + /** + * "." + */ String DOT = "."; + /** + * "param" + */ String PARAMETER_PREFIX = "param"; + /** + * "@" + */ String TEMP_PLACEHOLDER = "@"; + /** + * "_" + */ String UNDERSCORE = "_"; + /** + * "FinalClass" + */ String FINAL_CLASS_SUFFIX = "FinalCass"; + /** + * "return" + */ String RETURN = "return"; + /** + * "java.lang.Object" + */ String JAVA_LANG_OBJECT = "java.lang.Object"; + /** + * "SealForProfile" + */ String SEAL_FOR_PROFILE = "SealForProfile"; + /** + * "AddToProfile" + */ String ADD_TO_PROFILE = "AddToProfile"; + /** + * Regex expression to read the string value of the "profile" attribute + */ String PROFILE_ATTRIBUTE_REGEX = "profile=\"([^\"]*)\""; - // String PROFILES_ATTRIBUTE_REGEX = "profiles=\"([^\"]*)\""; // not working when { } present ? + /** + * Regex expression to read the string value of the "largeInterface" attribute + */ String LARGE_INTERFACE_ATTRIBUTE_REGEX = "largeInterface=\"([^\"]*)\""; + /** + * Regex expression to read any attribute value provided within "" + */ String ANNOTATION_VALUES_REGEX = "\"([^\"]*)\""; - String ADD_TO_PROFILE_REGEX = "AddToProfile\\((.*?)\\)"; // @org.jisel.AddToProfile(profile="ActiveWorker", largeInterface="com.bayor.jisel.annotation.client.data.Sociable") - String ADD_TO_PROFILES_REGEX = "AddToProfiles\\((.*?)\\)"; // @org.jisel.AddToProfiles(profiles={"Student", "Worker"}, largeInterface="com.bayor.jisel.annotation.client.data.Sociable"), - + /** + * Regex expression to read attributes information provided using the AddToProfile annotation.
+ * Sample value to be parsed by the regex: @org.jisel.AddToProfile(profile="ActiveWorker", largeInterface="com.bayor.jisel.annotation.client.data.Sociable") + */ + String ADD_TO_PROFILE_REGEX = "AddToProfile\\((.*?)\\)"; + /** + * Regex expression to read attributes information provided using the AddToProfiles annotation.
+ * Sample value to be parsed by the regex: @org.jisel.AddToProfiles(profiles={"Student", "Worker"}, largeInterface="com.bayor.jisel.annotation.client.data.Sociable") + */ + String ADD_TO_PROFILES_REGEX = "AddToProfiles\\((.*?)\\)"; + /** + * Title of the text report displayed in the logs during compilation.
+ * The report is displayed only when an unexpected scenario was encountered (ex: More than 1 top-level parent interfaces found, profile not existing,...) + */ String STATUS_REPORT_TITLE = "JISEL GENERATION REPORT"; + /** + * Displayed only when a "severe" error occured while a sealed interface file was being generated + */ String FILE_GENERATION_ERROR = "Error generating sealed interfaces"; + /** + * Displayed as a header while listing the successfully generated files + */ String FILE_GENERATION_SUCCESS = "Successfully generated"; + /** + * Fully qualified name of the SealForProfile annotation + */ String ORG_JISEL_SEAL_FOR_PROFILE = "org.jisel.SealForProfile"; + /** + * Fully qualified name of the SealForProfiles annotation + */ String ORG_JISEL_SEAL_FOR_PROFILES = "org.jisel.SealForProfiles"; + /** + * Fully qualified name of the SealForProfilez annotation + */ String ORG_JISEL_SEAL_FOR_PROFILEZ = "org.jisel.SealForProfile.SealForProfilez"; + /** + * Fully qualified name of the SealForProfilezz annotation + */ String ORG_JISEL_SEAL_FOR_PROFILEZZ = "org.jisel.SealForProfiles.SealForProfilezz"; + /** + * Fully qualified name of the AddToProfile annotation + */ String ORG_JISEL_ADD_TO_PROFILE = "org.jisel.AddToProfile"; + /** + * Fully qualified name of the AddToProfiles annotation + */ String ORG_JISEL_ADD_TO_PROFILES = "org.jisel.AddToProfiles"; + /** + * Fully qualified name of the AddToProfilez annotation + */ String ORG_JISEL_ADD_TO_PROFILEZ = "org.jisel.AddToProfile.AddToProfilez"; + /** + * Fully qualified name of the AddToProfilezz annotation + */ String ORG_JISEL_ADD_TO_PROFILEZZ = "org.jisel.AddToProfiles.AddToProfilezz"; + /** + * Default value to use for boolean returned values + */ String DEFAULT_BOOLEAN_VALUE = "false"; + /** + * Default value to use for numeric returned values (int, long, float, double,...) + */ String DEFAULT_NUMBER_VALUE = "0"; + /** + * Default value to use for Object returned values + */ String DEFAULT_NULL_VALUE = "null"; + /** + * Array of methods to exclude while pulling the list of all inherited methods of a class or interface + */ String[] METHODS_TO_EXCLUDE = {"getClass", "wait", "notifyAll", "hashCode", "equals", "notify", "toString"}; + /** + * Message displayed during compilation when 1 or many provided profiles are not found in the provided parent interfaces. + */ String ADD_TO_PROFILE_REPORT_MSG = "1 or many provided profiles are not found in the provided parent interfaces. Check your profiles and/or parent interfaces names."; + /** + * Message displayed during compilation when more than 1 top-level parent sealed interfaces was encountered based on provided profiles + */ String SEAL_FOR_PROFILE_REPORT_MSG = "More than 1 Top-Level Parent Sealed Interfaces will be generated. Check your profiles mapping."; + /** + * "Report.txt" + */ String JISEL_REPORT_SUFFIX = "Report.txt"; + /** + * Header displayed above the list of the generated sealed interfaces, in the Jisel Report file + */ String JISEL_REPORT_CREATED_SEALED_INTERFACES_HEADER = "Created sealed interfaces:"; + /** + * Header displayed above the list of the sub-types of the generated sealed interfaces, in the Jisel Report file + */ String JISEL_REPORT_CHILDREN_HEADER = "Children:"; - static String removeSeparator(final String text) { + /** + * Removes all commas from the provided string + * + * @param text contains commas as a string separator + * @return provided text with all commas removed + */ + static String removeCommaSeparator(final String text) { return asList(text.split(COMMA_SEPARATOR)).stream().collect(joining()); } + /** + * Constructs a string based on the provided profile and a large interface Element instance, according to the naming convention:
+ * Sealed<ProfileName><LargeInterfaceSimpleName>

+ * + * @param profile name of the profile + * @param interfaceElement Element instance of the large interface to be segregated + * @return a string following Jisel sealed interface naming convention + */ default String sealedInterfaceNameConvention(final String profile, final Element interfaceElement) { - var nameSuffix = removeSeparator(profile).equals(interfaceElement.getSimpleName().toString()) ? EMPTY_STRING : interfaceElement.getSimpleName().toString(); + var nameSuffix = removeCommaSeparator(profile).equals(interfaceElement.getSimpleName().toString()) ? EMPTY_STRING : interfaceElement.getSimpleName().toString(); // any profile name starting w _ (final classes names) or containing a dot (classes annotated with addtoprofile) is returned as is - return removeSeparator(profile).startsWith(UNDERSCORE) || profile.contains(DOT) ? removeSeparator(profile) : format( + return removeCommaSeparator(profile).startsWith(UNDERSCORE) || profile.contains(DOT) ? removeCommaSeparator(profile) : format( "%s%s%s", SEALED_PREFIX, - removeSeparator(profile), + removeCommaSeparator(profile), nameSuffix ); } + /** + * Constructs a string based on the provided profiles and a large interface Element instance, according to the naming convention:
+ * Sealed<ProfileName><LargeInterfaceSimpleName>

+ * + * @param profiles List of profiles names + * @param interfaceElement Element instance of the large interface to be segregated + * @return a List of string literals following Jisel sealed interface naming convention + */ default List sealedInterfaceNameConventionForList(final List profiles, final Element interfaceElement) { - final UnaryOperator nameSuffix = profile -> removeSeparator(profile).equals(interfaceElement.getSimpleName().toString()) ? EMPTY_STRING : interfaceElement.getSimpleName().toString(); + final UnaryOperator nameSuffix = profile -> removeCommaSeparator(profile).equals(interfaceElement.getSimpleName().toString()) ? EMPTY_STRING : interfaceElement.getSimpleName().toString(); return profiles.stream() - .map(profile -> removeSeparator(profile).startsWith(UNDERSCORE) || profile.contains(DOT) ? removeSeparator(profile) : format( + .map(profile -> removeCommaSeparator(profile).startsWith(UNDERSCORE) || profile.contains(DOT) ? removeCommaSeparator(profile) : format( "%s%s%s", SEALED_PREFIX, - removeSeparator(profile), + removeCommaSeparator(profile), nameSuffix.apply(profile) )).toList(); } - default Optional generatePackageName(final Element largeInterfaceName) { - var qualifiedClassName = largeInterfaceName.toString(); + /** + * Constructs the java package name based on an Element instance of the large interface to be segregated. + * + * @param largeInterfaceElement Element instance of the large interface to be segregated + * @return the package name if any + */ + default Optional generatePackageName(final Element largeInterfaceElement) { + var qualifiedClassName = largeInterfaceElement.toString(); int lastDot = qualifiedClassName.lastIndexOf('.'); return lastDot > 0 ? Optional.of(qualifiedClassName.substring(0, lastDot)) : Optional.empty(); } + /** + * Replace all double occurences of whitespace (" ") into a single whitespace (" ") + * + * @param text contains double occurences of whitespace + * @return the provided text with all double occurences of whitespace replaced with a single occurence + */ default String removeDoubleSpaceOccurrences(final String text) { return text.replace(WHITESPACE + WHITESPACE, WHITESPACE); } - - default String removeCurlyBraces(final String text) { - return text.replace(OPENING_BRACKET, EMPTY_STRING).replace(CLOSING_BRACKET, EMPTY_STRING); - } } \ No newline at end of file diff --git a/src/main/java/org/jisel/handlers/AddToProfileHandler.java b/src/main/java/org/jisel/handlers/AddToProfileHandler.java index f916dd5..bc284d7 100644 --- a/src/main/java/org/jisel/handlers/AddToProfileHandler.java +++ b/src/main/java/org/jisel/handlers/AddToProfileHandler.java @@ -35,6 +35,9 @@ import static java.util.stream.Collectors.toSet; import static java.util.stream.Stream.concat; +/** + * Implementation class of {@link JiselAnnotationHandler}. Handles @AddToProfile annotated elements + */ public final class AddToProfileHandler implements JiselAnnotationHandler { @Override diff --git a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java index 7cf0aa2..c3156c3 100644 --- a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java +++ b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java @@ -41,7 +41,7 @@ import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet; import static java.util.stream.Stream.concat; -import static org.jisel.generator.StringGenerator.removeSeparator; +import static org.jisel.generator.StringGenerator.removeCommaSeparator; /** * Interface exposing contract to fulfill by any class handling the elements annotated with @SealForProfile and @AddToProfile annotations @@ -62,7 +62,7 @@ public sealed interface JiselAnnotationHandler extends StringGenerator permits S * a Map of profile name as the key and a Set of Element instances as the value. * The Element instances represent each one of the abstract methods to be * added to the generated sealed interface corresponding to a profile. - * @param sealedInterfacesPermitsByLargeInterface Map containing information about the subtypes permitted by each one of the sealed interfaces to begenerated. + * @param sealedInterfacesPermitsByLargeInterface Map containing information about the subtypes permitted by each one of the sealed interfaces to be generated. * To be populated and/or modified if needed. The key represents the Element instance of * each one of the large interfaces to be segregated, while the associated value is * a Map of profile name as the key and a List of profiles names as the value. @@ -177,6 +177,18 @@ void populateSealedInterfacesMap(ProcessingEnvironment processingEnv, Set allAnnotatedElements, Map>> sealedInterfacesToGenerateByLargeInterface); + /** + * Creates intermediate parent interfaces based on common methods of provided profiles, then stores the created intermediate + * parent interfaces in the Map containing the sealed interfaces information to be generated + * + * @param annotatedMethodsByProfileByLargeInterface Set of all annotated abstract methods for a specified profile + * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. + * To be populated and/or modified if needed. The key represents the Element instance of + * each one of the large interfaces to be segregated, while the associated value is + * a Map of profile name as the key and a Set of Element instances as the value. + * The Element instances represent each one of the abstract methods to be + * added to the generated sealed interface corresponding to a profile. + */ default void createParentInterfacesBasedOnCommonMethods(final Map>> annotatedMethodsByProfileByLargeInterface, final Map>> sealedInterfacesToGenerateByLargeInterface) { annotatedMethodsByProfileByLargeInterface.forEach((interfaceElement, annotatedMethodsByProfile) -> { @@ -238,26 +250,66 @@ default Map handleAnnotatedElements(final ProcessingEnvironment } } +/** + * Interface exposing contract to fulfill by any class dedicated to building parent-children relations based on information provided in + * the Map containing the sealed interfaces information to be generated + */ sealed interface ParentChildInheritanceHandler extends JiselAnnotationHandler permits SealForProfileParentChildInheritanceHandler { + /** + * Reads information stored in the Map containing the sealed interfaces information to be generated, and populates another Map storing subtypes of the provided profiles + * + * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. + * To be populated and/or modified if needed. The key represents the Element instance of + * each one of the large interfaces to be segregated, while the associated value is + * a Map of profile name as the key and a Set of Element instances as the value. + * The Element instances represent each one of the abstract methods to be + * added to the generated sealed interface corresponding to a profile. + * @param sealedInterfacesPermitsByLargeInterface Map containing information about the subtypes permitted by each one of the sealed interfaces to be generated. + * To be populated and/or modified if needed. The key represents the Element instance of + * each one of the large interfaces to be segregated, while the associated value is + * a Map of profile name as the key and a List of profiles names as the value. + * @param uniqueParentInterfaceStatusReport Map returned by a call to {@link org.jisel.handlers.UniqueParentInterfaceHandler#checkAndHandleUniqueParentInterface(java.util.Map)}, + * providing information about the presence of a unique parent interface detected based on common methods of profiles + */ void buildInheritanceRelations(Map>> sealedInterfacesToGenerateByLargeInterface, Map>> sealedInterfacesPermitsByLargeInterface, Map uniqueParentInterfaceStatusReport); + /** + * Populates a Map storing subtypes of the provided profiles + * + * @param interfaceElement large interface Element instance + * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. + * To be populated and/or modified if needed. The key represents the Element instance of + * each one of the large interfaces to be segregated, while the associated value is + * a Map of profile name as the key and a Set of Element instances as the value. + * The Element instances represent each one of the abstract methods to be + * added to the generated sealed interface corresponding to a profile. + * @param sealedInterfacesPermitsByLargeInterface Map containing information about the subtypes permitted by each one of the sealed interfaces to be generated. + * To be populated and/or modified if needed. The key represents the Element instance of + * each one of the large interfaces to be segregated, while the associated value is + * a Map of profile name as the key and a List of profiles names as the value. + * @param uniqueParentInterfaceStatusReport Map returned by a call to {@link org.jisel.handlers.UniqueParentInterfaceHandler#checkAndHandleUniqueParentInterface(java.util.Map)}, + * providing information about the presence of a unique parent sealed interface detected based on common methods of profiles + */ default void buildSealedInterfacesPermitsMap(final Element interfaceElement, - final Map>> sealedInterfacesToGenerate, - final Map>> sealedInterfacesPermits, + final Map>> sealedInterfacesToGenerateByLargeInterface, + final Map>> sealedInterfacesPermitsByLargeInterface, final Map uniqueParentInterfaceStatusReport) { - sealedInterfacesPermits.get(interfaceElement).putAll(sealedInterfacesToGenerate.get(interfaceElement).keySet().stream() - .filter(profiles -> profiles.contains(COMMA_SEPARATOR)) - .collect(toMap(profiles -> profiles, profiles -> asList(profiles.split(COMMA_SEPARATOR))))); - // if uniq parnt intrf prsnt, parent interf permits all, if there are other permits already existing then the profiles in those permits lists should be removed from parent interf permits list + sealedInterfacesPermitsByLargeInterface.get(interfaceElement).putAll( + sealedInterfacesToGenerateByLargeInterface.get(interfaceElement).keySet().stream() + .filter(profiles -> profiles.contains(COMMA_SEPARATOR)) + .collect(toMap(profiles -> profiles, profiles -> asList(profiles.split(COMMA_SEPARATOR)))) + ); + // if unique parent interface is present, the parent interface permits all, + // if there are other permits already existing then the profiles in those permits lists should be removed from parent interf permits list if (Optional.ofNullable(uniqueParentInterfaceStatusReport).isPresent() && !uniqueParentInterfaceStatusReport.containsKey(interfaceElement)) { var parentInterfaceSimpleName = interfaceElement.getSimpleName().toString(); - var allPermittedProfiles = sealedInterfacesPermits.get(interfaceElement).values().stream().flatMap(Collection::stream).collect(toSet()); - sealedInterfacesPermits.get(interfaceElement).put( + var allPermittedProfiles = sealedInterfacesPermitsByLargeInterface.get(interfaceElement).values().stream().flatMap(Collection::stream).collect(toSet()); + sealedInterfacesPermitsByLargeInterface.get(interfaceElement).put( parentInterfaceSimpleName, - sealedInterfacesToGenerate.get(interfaceElement).keySet().stream() + sealedInterfacesToGenerateByLargeInterface.get(interfaceElement).keySet().stream() .filter(profile -> !parentInterfaceSimpleName.equals(profile)) .filter(profile -> !allPermittedProfiles.contains(profile)) .toList() @@ -275,12 +327,44 @@ default Map handleAnnotatedElements(final ProcessingEnvironment } } +/** + * Interface exposing contract to fulfill by any class dedicated to checking for the presence of an unique parent interface + * based on information provided in the Map containing the sealed interfaces information to be generated + */ sealed interface UniqueParentInterfaceHandler extends JiselAnnotationHandler permits SealForProfileUniqueParentInterfaceHandler { - default Map> checkUniqueParentInterfacePresence(final Map>> sealedInterfacesToGenerate) { + /** + * Checks for the presence of an unique parent interface based on information provided in the Map containing the sealed interfaces information to be generated, + * and updates the provided map with the parent sealed interface to be generated + * + * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. + * To be populated and/or modified if needed. The key represents the Element instance of + * each one of the large interfaces to be segregated, while the associated value is + * a Map of profile name as the key and a Set of Element instances as the value. + * The Element instances represent each one of the abstract methods to be + * added to the generated sealed interface corresponding to a profile. + * @return Map providing information about the presence of an unique parent sealed interface detected based on common methods of profiles.
+ * The informational message provided is a String literal describing the absence of an unique parent sealed interface, + * for each one of the provided large interfces to be segregated + */ + Map checkAndHandleUniqueParentInterface(Map>> sealedInterfacesToGenerateByLargeInterface); + + /** + * Checks for the presence of an unique parent interface based on information provided in the Map containing the sealed interfaces information to be generated + * + * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. + * To be populated and/or modified if needed. The key represents the Element instance of + * each one of the large interfaces to be segregated, while the associated value is + * a Map of profile name as the key and a Set of Element instances as the value. + * The Element instances represent each one of the abstract methods to be + * added to the generated sealed interface corresponding to a profile. + * @return Map providing information about the presence of an unique parent sealed interface detected based on common methods of profiles.
+ * If an unique parent sealed interface is detected, the Map value is an Optional containing a comma-separated concatenation of all profiles sharing same common methods + */ + default Map> checkUniqueParentInterfacePresence(final Map>> sealedInterfacesToGenerateByLargeInterface) { var providedProfilesListByInterface = new HashMap>(); - sealedInterfacesToGenerate.forEach( + sealedInterfacesToGenerateByLargeInterface.forEach( (interfaceElement, annotatedMethodsByProfile) -> annotatedMethodsByProfile.keySet().stream() .filter(concatenatedProfiles -> !concatenatedProfiles.contains(COMMA_SEPARATOR)).forEach( profileName -> providedProfilesListByInterface.merge( @@ -293,18 +377,18 @@ default Map> checkUniqueParentInterfacePresence(final var uniqueParentInterfaceByInterface = new HashMap>(); providedProfilesListByInterface.forEach((interfaceElement, profilesList) -> { - // if only 1 key of sealedInterfacesToGenerate map contains all of the profiles names and its total length = all profiles names lenghts combined -> use fat interface name for that key and move on + // if only 1 key of sealedInterfacesToGenerateByLargeInterface map contains all of the profiles names and its total length = all profiles names lenghts combined -> use fat interface name for that key and move on var totalProfilesNamesLengths = profilesList.stream().mapToInt(String::length).sum(); - var longestConcatenedProfilesStringOpt = sealedInterfacesToGenerate.get(interfaceElement).keySet().stream() + var longestConcatenedProfilesStringOpt = sealedInterfacesToGenerateByLargeInterface.get(interfaceElement).keySet().stream() .sorted(Comparator.comparingInt(String::length).reversed()).findFirst(); if (longestConcatenedProfilesStringOpt.isPresent()) { var foundUniqueParentInterface = false; for (var profileName : profilesList) { - foundUniqueParentInterface = removeSeparator(longestConcatenedProfilesStringOpt.get()).contains(profileName); + foundUniqueParentInterface = removeCommaSeparator(longestConcatenedProfilesStringOpt.get()).contains(profileName); } uniqueParentInterfaceByInterface.put( interfaceElement, - foundUniqueParentInterface && removeSeparator(longestConcatenedProfilesStringOpt.get()).length() == totalProfilesNamesLengths + foundUniqueParentInterface && removeCommaSeparator(longestConcatenedProfilesStringOpt.get()).length() == totalProfilesNamesLengths ? Optional.of(longestConcatenedProfilesStringOpt.get()) : Optional.empty() ); @@ -314,8 +398,6 @@ foundUniqueParentInterface && removeSeparator(longestConcatenedProfilesStringOpt return uniqueParentInterfaceByInterface; } - Map checkAndHandleUniqueParentInterface(Map>> sealedInterfacesToGenerate); - @Override default Map handleAnnotatedElements(final ProcessingEnvironment processingEnv, final Set allAnnotatedElements, diff --git a/src/main/java/org/jisel/handlers/SealForProfileHandler.java b/src/main/java/org/jisel/handlers/SealForProfileHandler.java index 67cc8ed..3ea2c94 100644 --- a/src/main/java/org/jisel/handlers/SealForProfileHandler.java +++ b/src/main/java/org/jisel/handlers/SealForProfileHandler.java @@ -36,12 +36,19 @@ import static java.util.stream.Collectors.toSet; import static java.util.stream.Stream.concat; +/** + * Implementation class of {@link JiselAnnotationHandler}. Handles @SealForProfile annotated elements + */ public final class SealForProfileHandler implements JiselAnnotationHandler { private final AnnotationInfoCollectionHandler annotationInfoCollectionHandler; private final UniqueParentInterfaceHandler uniqueParentInterfaceHandler; private final ParentChildInheritanceHandler parentChildInheritanceHandler; + /** + * SealForProfileHandler constructor. Instantiates needed instances of {@link SealForProfileInfoCollectionHandler}, + * {@link SealForProfileUniqueParentInterfaceHandler} and {@link SealForProfileParentChildInheritanceHandler} + */ public SealForProfileHandler() { this.annotationInfoCollectionHandler = new SealForProfileInfoCollectionHandler(); this.uniqueParentInterfaceHandler = new SealForProfileUniqueParentInterfaceHandler(); @@ -140,14 +147,14 @@ public void buildInheritanceRelations(final Map checkAndHandleUniqueParentInterface(final Map>> sealedInterfacesToGenerate) { + public Map checkAndHandleUniqueParentInterface(final Map>> sealedInterfacesToGenerateByLargeInterface) { var statusReport = new HashMap(); - Map> longestConcatenedProfilesStringOptByInterface = checkUniqueParentInterfacePresence(sealedInterfacesToGenerate); - sealedInterfacesToGenerate.keySet().forEach(interfaceElement -> { + Map> longestConcatenedProfilesStringOptByInterface = checkUniqueParentInterfacePresence(sealedInterfacesToGenerateByLargeInterface); + sealedInterfacesToGenerateByLargeInterface.keySet().forEach(interfaceElement -> { var longestConcatenedProfilesStringOpt = longestConcatenedProfilesStringOptByInterface.get(interfaceElement); if (longestConcatenedProfilesStringOptByInterface.containsKey(interfaceElement) && longestConcatenedProfilesStringOpt.isPresent()) { - sealedInterfacesToGenerate.get(interfaceElement).put(interfaceElement.getSimpleName().toString(), sealedInterfacesToGenerate.get(interfaceElement).get(longestConcatenedProfilesStringOpt.get())); - sealedInterfacesToGenerate.get(interfaceElement).remove(longestConcatenedProfilesStringOpt.get()); + sealedInterfacesToGenerateByLargeInterface.get(interfaceElement).put(interfaceElement.getSimpleName().toString(), sealedInterfacesToGenerateByLargeInterface.get(interfaceElement).get(longestConcatenedProfilesStringOpt.get())); + sealedInterfacesToGenerateByLargeInterface.get(interfaceElement).remove(longestConcatenedProfilesStringOpt.get()); } else { statusReport.put(interfaceElement, SEAL_FOR_PROFILE_REPORT_MSG); } From e14a9e00e9caa8a110876d57965036649793e775 Mon Sep 17 00:00:00 2001 From: Mohamed Ashraf Bayor Date: Mon, 27 Dec 2021 19:43:33 -0600 Subject: [PATCH 10/11] JIS-01: javadoc in progress --- .../org/jisel/generator/CodeGenerator.java | 2 +- ...r.java => FinalClassContentGenerator.java} | 36 ++++++++++++++++--- .../generator/JavaxGeneratedGenerator.java | 3 +- .../org/jisel/generator/ReportGenerator.java | 28 +++++++++++++++ .../SealedInterfaceContentGenerator.java | 26 ++++++++++++++ .../SealedInterfaceSourceFileGenerator.java | 6 ++-- .../jisel/handlers/AddToProfileHandler.java | 2 +- .../handlers/JiselAnnotationHandler.java | 16 ++++----- .../jisel/handlers/SealForProfileHandler.java | 14 ++++++-- 9 files changed, 110 insertions(+), 23 deletions(-) rename src/main/java/org/jisel/generator/{FinalClassGenerator.java => FinalClassContentGenerator.java} (67%) diff --git a/src/main/java/org/jisel/generator/CodeGenerator.java b/src/main/java/org/jisel/generator/CodeGenerator.java index 82ceeef..a4ebf68 100644 --- a/src/main/java/org/jisel/generator/CodeGenerator.java +++ b/src/main/java/org/jisel/generator/CodeGenerator.java @@ -58,7 +58,7 @@ sealed interface ExtendsGenerator extends CodeGenerator, StringGenerator permits * a provided Map containing parents/subtypes information (the permits Map) and the name of the profile for which the * sealed interface will be generated * - * @param processingEnvironment needed to access low-level information regarding the used annotations + * @param processingEnvironment {@link ProcessingEnvironment} object, needed to access low-level information regarding the used annotations * @param sealedInterfaceContent StringBuilder object containing the sealed interface code being generated * @param permitsMap Map containing parents/subtypes information. The Map key is the profile name whose generated * sealed interface will be a parent interface, while the value is the list of profiles names whose diff --git a/src/main/java/org/jisel/generator/FinalClassGenerator.java b/src/main/java/org/jisel/generator/FinalClassContentGenerator.java similarity index 67% rename from src/main/java/org/jisel/generator/FinalClassGenerator.java rename to src/main/java/org/jisel/generator/FinalClassContentGenerator.java index 3d38fdc..8705b5e 100644 --- a/src/main/java/org/jisel/generator/FinalClassGenerator.java +++ b/src/main/java/org/jisel/generator/FinalClassContentGenerator.java @@ -32,19 +32,39 @@ import static java.util.Arrays.asList; import static java.util.stream.Collectors.toSet; -public class FinalClassGenerator implements StringGenerator { +/** + * Generates content for a final class.
+ * A final class is always generated by Jisel for the bottom-level generated sealed interfaces.
+ * The generated final class implements the provided large interface and implements all its methods by providing default return values for each. + */ +public class FinalClassContentGenerator implements StringGenerator { private final CodeGenerator javaxGeneratedGenerator; private final ExtendsGenerator extendsGenerator; private final MethodsGenerator methodsGenerator; - public FinalClassGenerator() { + /** + * FinalClassContentGenerator constructor. Instantiates needed instances of {@link JavaxGeneratedGenerator}, {@link SealedInterfaceExtendsGenerator} + * and {@link SealedInterfaceMethodsGenerator} + */ + public FinalClassContentGenerator() { this.javaxGeneratedGenerator = new JavaxGeneratedGenerator(); this.extendsGenerator = new SealedInterfaceExtendsGenerator(); this.methodsGenerator = new SealedInterfaceMethodsGenerator(); } - public String generateFinalClassContent(final ProcessingEnvironment processingEnvironment, final Element largeInterfaceElement, final Map> sealedInterfacesPermitsMap) { + /** + * Generates content of the final class generated for the provided large interface + * + * @param processingEnvironment {@link ProcessingEnvironment} object, needed to access low-level information regarding the used annotations + * @param largeInterfaceElement {@link Element} instance of the large interface being segregated + * @param sealedInterfacesPermitsMap Map containing information about the subtypes permitted by each one of the sealed interfaces to be generated + * @return the final class string content + */ + public String generateFinalClassContent( + final ProcessingEnvironment processingEnvironment, + final Element largeInterfaceElement, + final Map> sealedInterfacesPermitsMap) { var finalClassName = UNDERSCORE + largeInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX; var finalClassContent = new StringBuilder(); // package name @@ -58,7 +78,13 @@ public String generateFinalClassContent(final ProcessingEnvironment processingEn finalClassName )); // list of implements - extendsGenerator.generateExtendsClauseFromPermitsMapAndProcessedProfile(processingEnvironment, finalClassContent, sealedInterfacesPermitsMap, finalClassName, largeInterfaceElement); + extendsGenerator.generateExtendsClauseFromPermitsMapAndProcessedProfile( + processingEnvironment, + finalClassContent, + sealedInterfacesPermitsMap, + finalClassName, + largeInterfaceElement + ); // opening bracket after permits list finalClassContent.append(format(" %s%n ", OPENING_BRACKET)); // list of methods @@ -74,4 +100,4 @@ public String generateFinalClassContent(final ProcessingEnvironment processingEn // return removeDoubleSpaceOccurrences(finalClassContent.toString()); } -} +} \ No newline at end of file diff --git a/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java b/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java index a018c7a..d54d79c 100644 --- a/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java +++ b/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java @@ -32,8 +32,7 @@ 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 + * Generates the @javax.annotation.processing.Generated annotation section at the top of the generated interfaces or classes with the attributes: value, date and comments
*/ public final class JavaxGeneratedGenerator implements CodeGenerator { diff --git a/src/main/java/org/jisel/generator/ReportGenerator.java b/src/main/java/org/jisel/generator/ReportGenerator.java index db5c114..ac31fcb 100644 --- a/src/main/java/org/jisel/generator/ReportGenerator.java +++ b/src/main/java/org/jisel/generator/ReportGenerator.java @@ -30,8 +30,36 @@ import static java.lang.String.format; import static java.util.stream.Collectors.joining; +/** + * Generates a Report file listing all generated sealed interfaces for the provided large interfaces.
+ * Sample report:
+ * com.bayor.jisel.annotation.client.hierarchicalinheritance.Sociable
+ * Created sealed interfaces:
+ * SealedActiveWorkerSociable
+ * - Children:
+ * _SociableFinalCass
+ * SealedWorkerSociable
+ * - Children:
+ * SealedActiveWorkerSociable
+ * com.bayor.jisel.annotation.client.hierarchicalinheritance.subclasses.StudentWorkerHybrid
+ * SealedStudentSociable
+ * - Children:
+ * com.bayor.jisel.annotation.client.hierarchicalinheritance.subclasses.StudentWorkerHybrid
+ * SealedSociable
+ * - Children:
+ * SealedWorkerSociable
+ * SealedStudentSociable
+ */ public class ReportGenerator implements StringGenerator { + /** + * Generates a Report file listing all generated sealed interfaces for the provided large interfaces. + * + * @param largeInterfaceElement {@link Element} instance of the large interface being segregated + * @param sealedInterfacesToGenerateMap Map containing information about the sealed interfaces to be generated + * @param sealedInterfacesPermitsMap Map containing information about the subtypes permitted by each one of the sealed interfaces to be generated + * @return a string containing the text report + */ public String generateReportForBloatedInterface(final Element largeInterfaceElement, final Map> sealedInterfacesToGenerateMap, final Map> sealedInterfacesPermitsMap) { diff --git a/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java b/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java index 5405f4a..14a9dc1 100644 --- a/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java +++ b/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java @@ -30,6 +30,9 @@ import static java.lang.String.format; +/** + * Generates the content of a sealed interface + */ public final class SealedInterfaceContentGenerator implements StringGenerator { private final CodeGenerator javaxGeneratedGenerator; @@ -37,6 +40,10 @@ public final class SealedInterfaceContentGenerator implements StringGenerator { private final PermitsGenerator permitsGenerator; private final MethodsGenerator methodsGenerator; + /** + * SealedInterfaceContentGenerator constructor. Instantiates needed instances of {@link JavaxGeneratedGenerator}, {@link SealedInterfaceExtendsGenerator}, + * {@link SealedInterfacePermitsGenerator} and {@link SealedInterfaceMethodsGenerator} + */ public SealedInterfaceContentGenerator() { this.javaxGeneratedGenerator = new JavaxGeneratedGenerator(); this.extendsGenerator = new SealedInterfaceExtendsGenerator(); @@ -44,6 +51,15 @@ public SealedInterfaceContentGenerator() { this.methodsGenerator = new SealedInterfaceMethodsGenerator(); } + /** + * Generates the content of a sealed interface + * + * @param processingEnvironment {@link ProcessingEnvironment} object, needed to access low-level information regarding the used annotations + * @param sealedInterfacesToGenerateMapEntrySet Map.Entry instance containing information about the sealed interfaces to be generated + * @param largeInterfaceElement {@link Element} instance of the large interface being segregated + * @param sealedInterfacesPermitsMap Map containing information about the subtypes permitted by each one of the sealed interfaces to be generated + * @return the string content of the sealed interface to generate + */ public String generateSealedInterfaceContent(final ProcessingEnvironment processingEnvironment, final Map.Entry> sealedInterfacesToGenerateMapEntrySet, final Element largeInterfaceElement, @@ -75,6 +91,9 @@ public String generateSealedInterfaceContent(final ProcessingEnvironment process } } +/** + * Generates the "extends" clause of a sealed interface definition, along with the list of the parent interfaces + */ final class SealedInterfaceExtendsGenerator implements ExtendsGenerator { @Override public void generateExtendsClauseFromPermitsMapAndProcessedProfile(final ProcessingEnvironment processingEnvironment, @@ -106,6 +125,10 @@ public void generateExtendsClauseFromPermitsMapAndProcessedProfile(final Process } } +/** + * Generates the "permits" clause of a sealed interface definition, along with the list of the subtypes classes or + * interfaces permitted by the sealed interface being generated + */ final class SealedInterfacePermitsGenerator implements PermitsGenerator { @Override public void generatePermitsClauseFromPermitsMapAndProcessedProfile(final StringBuilder sealedInterfaceContent, @@ -122,6 +145,9 @@ public void generatePermitsClauseFromPermitsMapAndProcessedProfile(final StringB } } +/** + * Generates the list of abstracts methods of a sealed interface being generated + */ final class SealedInterfaceMethodsGenerator implements MethodsGenerator { @Override diff --git a/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java b/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java index ddf3788..1697e78 100644 --- a/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java +++ b/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java @@ -35,12 +35,12 @@ public class SealedInterfaceSourceFileGenerator implements StringGenerator { private final SealedInterfaceContentGenerator sealedInterfaceContentGenerator; - private final FinalClassGenerator finalClassGenerator; + private final FinalClassContentGenerator finalClassContentGenerator; private final ReportGenerator reportGenerator; public SealedInterfaceSourceFileGenerator() { this.sealedInterfaceContentGenerator = new SealedInterfaceContentGenerator(); - this.finalClassGenerator = new FinalClassGenerator(); + this.finalClassContentGenerator = new FinalClassContentGenerator(); this.reportGenerator = new ReportGenerator(); } @@ -91,7 +91,7 @@ private void createFinalClassFile(final ProcessingEnvironment processingEnvironm : UNDERSCORE + largeInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX; var fileObject = processingEnvironment.getFiler().createSourceFile(qualifiedName); try (var out = new PrintWriter(fileObject.openWriter())) { - out.println(finalClassGenerator.generateFinalClassContent(processingEnvironment, largeInterfaceElement, sealedInterfacesPermitsMap)); + out.println(finalClassContentGenerator.generateFinalClassContent(processingEnvironment, largeInterfaceElement, sealedInterfacesPermitsMap)); } generatedFiles.add(qualifiedName); } catch (FilerException e) { diff --git a/src/main/java/org/jisel/handlers/AddToProfileHandler.java b/src/main/java/org/jisel/handlers/AddToProfileHandler.java index bc284d7..40898f4 100644 --- a/src/main/java/org/jisel/handlers/AddToProfileHandler.java +++ b/src/main/java/org/jisel/handlers/AddToProfileHandler.java @@ -36,7 +36,7 @@ import static java.util.stream.Stream.concat; /** - * Implementation class of {@link JiselAnnotationHandler}. Handles @AddToProfile annotated elements + * Handles all elements annotated with @{@link org.jisel.AddToProfile} */ public final class AddToProfileHandler implements JiselAnnotationHandler { diff --git a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java index c3156c3..6372f13 100644 --- a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java +++ b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java @@ -53,7 +53,7 @@ public sealed interface JiselAnnotationHandler extends StringGenerator permits S * Reads values of all attributes provided through the use of @SealForProfile and @AddToProfile annotations and * populates the provided Map arguments * - * @param processingEnv needed to access low-level information regarding the used annotations + * @param processingEnv {@link ProcessingEnvironment} object, needed to access low-level information regarding the used annotations * @param allAnnotatedElements Set of Element instances representing all classes annotated with @AddToProfile and * all abstract methods annotated with @SealForProfile * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. @@ -77,7 +77,7 @@ Map handleAnnotatedElements(ProcessingEnvironment processingEnv * For a specified class or interface annotated with @AddToProfile, constructs a Map storing a Set of all the provided * profiles names (as the Map value) for each one of the large interfaces names (as the Map key) provided through @AddToProfile. * - * @param processingEnv needed to access low-level information regarding the used annotations + * @param processingEnv {@link ProcessingEnvironment} object, needed to access low-level information regarding the used annotations * @param annotatedClassOrInterface Element instance representing the annotated class or interface * @return a Map storing a Set of all the provided profiles names (as the Map value) for each one of the large interfaces names (as the Map key) */ @@ -128,7 +128,7 @@ private void updateProvidedProfilesMapBasedOnProfilesSet(final Map buildSealForProfileProvidedProfilesSet(final ProcessingEnvir /** * Interface exposing contract to fulfill by any class dedicated to collecting necessary information from the annotated elements, - * in order to populate the Map containing the sealed interfaces information to be generated + * in order to populate the {@link Map} containing the sealed interfaces information to be generated */ sealed interface AnnotationInfoCollectionHandler extends JiselAnnotationHandler permits SealForProfileInfoCollectionHandler { /** * Populates the Map containing the sealed interfaces information to be generated * - * @param processingEnv needed to access low-level information regarding the used annotations - * @param allAnnotatedElements Set of Element instances representing all classes annotated with @AddToProfile and + * @param processingEnv {@link ProcessingEnvironment} object, needed to access low-level information regarding the used annotations + * @param allAnnotatedElements {@link Set} of Element instances representing all classes annotated with @AddToProfile and * all abstract methods annotated with @SealForProfile * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. - * To be populated and/or modified if needed. The key represents the Element instance of + * To be populated and/or modified if needed. The key represents the {@link Element} instance of * each one of the large interfaces to be segregated, while the associated value is - * a Map of profile name as the key and a Set of Element instances as the value. + * a {@link Map} of profile name as the key and a Set of {@link Element} instances as the value. * The Element instances represent each one of the abstract methods to be * added to the generated sealed interface corresponding to a profile. */ diff --git a/src/main/java/org/jisel/handlers/SealForProfileHandler.java b/src/main/java/org/jisel/handlers/SealForProfileHandler.java index 3ea2c94..424b911 100644 --- a/src/main/java/org/jisel/handlers/SealForProfileHandler.java +++ b/src/main/java/org/jisel/handlers/SealForProfileHandler.java @@ -37,7 +37,7 @@ import static java.util.stream.Stream.concat; /** - * Implementation class of {@link JiselAnnotationHandler}. Handles @SealForProfile annotated elements + * Handles all elements annotated with @{@link org.jisel.SealForProfile} */ public final class SealForProfileHandler implements JiselAnnotationHandler { @@ -67,6 +67,10 @@ public Map handleAnnotatedElements(final ProcessingEnvironment } } +/** + * Collects necessary information from the annotated elements, in order to populate the Map containing the sealed + * interfaces information to be generated + */ final class SealForProfileInfoCollectionHandler implements AnnotationInfoCollectionHandler { @Override @@ -113,8 +117,10 @@ private void extractProfilesAndPopulateMaps(final Element interfaceElement, } } +/** + * Builds parent-children relations based on information provided in the Map containing the sealed interfaces information to be generated + */ final class SealForProfileParentChildInheritanceHandler implements ParentChildInheritanceHandler { - @Override public void buildInheritanceRelations(final Map>> sealedInterfacesToGenerateByLargeInterface, final Map>> sealedInterfacesPermitsByLargeInterface, @@ -144,8 +150,10 @@ public void buildInheritanceRelations(final Map checkAndHandleUniqueParentInterface(final Map>> sealedInterfacesToGenerateByLargeInterface) { var statusReport = new HashMap(); From 8b6fe6fcdaf125d943cb743403dc4af8dcd9f8ed Mon Sep 17 00:00:00 2001 From: Mohamed Ashraf Bayor Date: Tue, 28 Dec 2021 16:43:54 -0600 Subject: [PATCH 11/11] JIS-01: completed 1.0 - ready to merge --- README.md | 102 ++++++++++++++---- pom.xml | 6 +- src/main/java/org/jisel/AddToProfile.java | 4 +- src/main/java/org/jisel/AddToProfiles.java | 8 +- .../org/jisel/JiselAnnotationProcessor.java | 5 +- src/main/java/org/jisel/SealForProfile.java | 6 +- src/main/java/org/jisel/SealForProfiles.java | 6 +- .../org/jisel/generator/CodeGenerator.java | 34 +++--- .../generator/FinalClassContentGenerator.java | 2 +- .../generator/JavaxGeneratedGenerator.java | 2 +- .../org/jisel/generator/ReportGenerator.java | 4 +- .../SealedInterfaceContentGenerator.java | 2 +- .../SealedInterfaceSourceFileGenerator.java | 23 ++++ .../org/jisel/generator/StringGenerator.java | 40 +++---- .../handlers/JiselAnnotationHandler.java | 44 ++++---- src/main/resources/application.properties | 4 +- 16 files changed, 188 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index 44cbb71..de20e29 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ -## JISEL: Java Interface Segregation Library -Interface Segregation Library for Java 17 +# JISEL: Java Interface Segregation Library +Minimum Java 17 Required -Pitch Video: -... +Pitch Video: https://youtu.be/nkbu6zxV3R0 -### How to Install ? +## Installation If you are running a Maven project, add the latest release dependency to your pom.xml ```xml @@ -14,35 +13,98 @@ If you are running a Maven project, add the latest release dependency to your po 1.0 ``` +You will also need to include the same dependency as an additional annotation processor in the Maven Compiler plugin of your project +```xml + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + 17 + -Xlint:unchecked + + + 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 +## Provided Annotations +### @SealForProfile / @SealForProfiles +To be used only on abstract methods of the large interfaces you need to segregate ```java -import SealForProfile; +public interface Sociable { + + String STUDENT = "Student"; + String WORKER = "Worker"; + String ACTIVE_WORKER = "ActiveWorker"; + + @SealForProfiles({STUDENT, WORKER, ACTIVE_WORKER}) + String startConversation(); + + @SealForProfile(STUDENT) + boolean attendClass(String fieldOfStudy); + + @SealForProfile(STUDENT) + void askForHelpWhenNeeded(); + + @SealForProfile(WORKER) + @SealForProfile(ACTIVE_WORKER) + // both annotations above can be replaced with: @SealForProfiles({WORKER, ACTIVE_WORKER}) + boolean[] joinOfficeSocialGroups(String[] groups, int maximum); -public interface InterfaceA { - @SealForProfile - void something(); + @SealForProfile(ACTIVE_WORKER) + void leadOfficeSocialGroup(String groupName); + + @SealForProfile(ACTIVE_WORKER) + double createNewOfficeSocialGroup(String groupName, List starters); } ``` -### Use on your declared child classes implementing generated sealed interfaces - +### @AddToProfile / @AddToProfiles +To be used only on classes (or interfaces) implementing (or extending) any of the sealed interfaces generated by the use of @SealForProfile(s) ```java -import AddToProfile; +@AddToProfiles(profiles = {STUDENT, WORKER}, largeInterface = "com.bayor.jisel.annotation.client.hierarchicalinheritance.Sociable") +// the above line can be replaced with the following 2: +// @AddToProfile(profile = STUDENT, largeInterface = "com.bayor.jisel.annotation.client.hierarchicalinheritance.Sociable") +// @AddToProfile(profile = WORKER, largeInterface = "com.bayor.jisel.annotation.client.hierarchicalinheritance.Sociable") +public final class StudentWorkerHybrid implements SealedStudentSociable, SealedWorkerSociable { + @Override + public String startConversation() throws IllegalStateException { + return null; + } + + @Override + public void askForHelpWhenNeeded() { + + } -@AddToProfile("PROFILE_NAME") -public final class ClientA implements SealedInterfaceA { - // ... + @Override + public boolean attendClass(String param0) throws IllegalArgumentException { + return false; + } + + @Override + public boolean[] joinOfficeSocialGroups(String[] param0, int param1) { + return new boolean[0]; + } } + ``` -### Sample classes for testing +### Sample interfaces and 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 index 42df985..e8cfd51 100644 --- a/pom.xml +++ b/pom.xml @@ -9,8 +9,8 @@ jar Jisel - Interface Segregation Library for Java 17 - http://jisel.org + Java Interface Segregation Library + https://jisel.org/ @@ -21,7 +21,7 @@ - scm:git:git://github.com/mohamed-ashraf-bayor/froporec.git + scm:git:git://github.com/mohamed-ashraf-bayor/jisel.git scm:git:git@github.com:mohamed-ashraf-bayor/mohamed-ashraf-bayor.git https://github.com/mohamed-ashraf-bayor/jisel HEAD diff --git a/src/main/java/org/jisel/AddToProfile.java b/src/main/java/org/jisel/AddToProfile.java index 9577e1f..db8d434 100644 --- a/src/main/java/org/jisel/AddToProfile.java +++ b/src/main/java/org/jisel/AddToProfile.java @@ -32,7 +32,7 @@ /** * Annotation to be applied on top of a class, an interface or a record which is implementing or extending a sealed interface generated by Jisel.

* All sealed interfaces generated by Jisel follow the naming convention: Sealed<ProfileName><LargeInterfaceSimpleName>
- * See @AddToProfile attributes documentation.

+ * See @{@link AddToProfile} attributes documentation.

*/ @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE}) @@ -65,7 +65,7 @@ @Target(ElementType.TYPE) @interface AddToProfilez { /** - * array attribute allowing @AddToProfile to be repeatable + * array attribute allowing @{@link AddToProfile} to be repeatable * * @return array of @AddToProfile instances */ diff --git a/src/main/java/org/jisel/AddToProfiles.java b/src/main/java/org/jisel/AddToProfiles.java index 5d60a99..4a7d201 100644 --- a/src/main/java/org/jisel/AddToProfiles.java +++ b/src/main/java/org/jisel/AddToProfiles.java @@ -32,7 +32,7 @@ /** * Annotation to be applied on top of a class, an interface or a record which is implementing or extending a sealed interface generated by Jisel.

* All sealed interfaces generated by Jisel follow the naming convention: Sealed<ProfileName><LargeInterfaceSimpleName>
- * See @AddToProfile attributes documentation.

+ * See @{@link AddToProfiles} attributes documentation.

*/ @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE}) @@ -40,7 +40,7 @@ public @interface AddToProfiles { /** - * Not Required - specifies an array containing the names of any of the profiles used with the @SealForProfile annotations in the large interface definition. + * Not Required - specifies an array containing the names of any of the profiles used with the @{@link SealForProfile} annotations in the large interface definition. * Each one of the profiles names corresponds to the <ProfileName> from the sealed interfaces naming convention.
* If not provided or empty, the annotated class, interface or record will be added to the permits list of the generated parent sealed interface.
* Also, each one of the provided profiles names MUST be one of the profiles defined in the large interface definition using @SealForProfile. If not, @@ -59,13 +59,13 @@ String largeInterface(); /** - * Internal annotation allowing @AddToProfiles to be repeatable + * Internal annotation allowing @{@link AddToProfiles} to be repeatable */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) @interface AddToProfilezz { /** - * array attribute allowing @AddToProfiles to be repeatable + * array attribute allowing @{@link AddToProfiles} to be repeatable * * @return array of @AddToProfiles instances */ diff --git a/src/main/java/org/jisel/JiselAnnotationProcessor.java b/src/main/java/org/jisel/JiselAnnotationProcessor.java index 8b4c5f4..f54981b 100644 --- a/src/main/java/org/jisel/JiselAnnotationProcessor.java +++ b/src/main/java/org/jisel/JiselAnnotationProcessor.java @@ -59,7 +59,7 @@ import static org.jisel.generator.StringGenerator.ORG_JISEL_SEAL_FOR_PROFILEZZ; /** - * Jisel annotation processor class. Picks up and processes all elements annotated with @SealForProfile, @SealForProfiles, @AddToProfile and @AddToProfiles
+ * Jisel annotation processor class. Picks up and processes all elements annotated with @{@link SealForProfile}, @{@link SealForProfiles}, @{@link AddToProfile} and @{@link AddToProfiles}
*/ @SupportedAnnotationTypes({ORG_JISEL_SEAL_FOR_PROFILE, ORG_JISEL_SEAL_FOR_PROFILES, ORG_JISEL_SEAL_FOR_PROFILEZ, ORG_JISEL_SEAL_FOR_PROFILEZZ, ORG_JISEL_ADD_TO_PROFILE, ORG_JISEL_ADD_TO_PROFILES, ORG_JISEL_ADD_TO_PROFILEZ, ORG_JISEL_ADD_TO_PROFILEZZ}) @@ -76,8 +76,7 @@ public class JiselAnnotationProcessor extends AbstractProcessor implements Strin private final SealedInterfaceSourceFileGenerator sealedInterfaceSourceFileGenerator; /** - * Constructor of JiselAnnotationProcessor. - * Initializes unique instances of SealForProfileHandler, AddToProfileHandler and SealedInterfaceSourceFileGenerator to be used within the class + * JiselAnnotationProcessor constructor. Initializes needed instances of {@link SealForProfileHandler}, {@link AddToProfileHandler} and {@link SealedInterfaceSourceFileGenerator} */ public JiselAnnotationProcessor() { this.sealForProfileHandler = new SealForProfileHandler(); diff --git a/src/main/java/org/jisel/SealForProfile.java b/src/main/java/org/jisel/SealForProfile.java index d7b0729..ed0c289 100644 --- a/src/main/java/org/jisel/SealForProfile.java +++ b/src/main/java/org/jisel/SealForProfile.java @@ -32,7 +32,7 @@ * For the specified profile name, a sealed interface will be generated following the naming convention: * Sealed<ProfileName><LargeInterfaceSimpleName>

* <LargeInterfaceSimpleName> corresponds to the simplename of the interface being segregated.

- * See @SealForProfile attributes documentation.

+ * See @{@link SealForProfile} attributes documentation.

*/ @Retention(RetentionPolicy.SOURCE) @Target({ElementType.METHOD}) @@ -48,13 +48,13 @@ String value(); /** - * Internal annotation allowing @SealForProfile to be repeatable + * Internal annotation allowing @{@link SealForProfile} to be repeatable */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) @interface SealForProfilez { /** - * array attribute allowing @SealForProfile to be repeatable + * array attribute allowing @{@link SealForProfile} to be repeatable * * @return array of @SealForProfile instances */ diff --git a/src/main/java/org/jisel/SealForProfiles.java b/src/main/java/org/jisel/SealForProfiles.java index c4ae86d..7160cd0 100644 --- a/src/main/java/org/jisel/SealForProfiles.java +++ b/src/main/java/org/jisel/SealForProfiles.java @@ -31,7 +31,7 @@ * Annotation to be applied only on top of abstract methods of an interface you intend to segregate.

* For each one of the specified profiles names, a sealed interface will be generated following the naming convention: Sealed<ProfileName><LargeInterfaceSimpleName>

* <LargeInterfaceSimpleName> corresponds to the simplename of the interface being segregated.

- * See @SealForProfiles attributes documentation.

+ * See @{@link SealForProfiles} attributes documentation.

*/ @Retention(RetentionPolicy.SOURCE) @Target({ElementType.METHOD}) @@ -47,13 +47,13 @@ String[] value(); /** - * Internal annotation allowing @SealForProfiles to be repeatable + * Internal annotation allowing @{@link SealForProfiles} to be repeatable */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) @interface SealForProfilezz { /** - * array attribute allowing @SealForProfiles to be repeatable + * array attribute allowing @{@link SealForProfiles} to be repeatable * * @return array of @SealForProfiles instances */ diff --git a/src/main/java/org/jisel/generator/CodeGenerator.java b/src/main/java/org/jisel/generator/CodeGenerator.java index a4ebf68..dee78ce 100644 --- a/src/main/java/org/jisel/generator/CodeGenerator.java +++ b/src/main/java/org/jisel/generator/CodeGenerator.java @@ -55,16 +55,16 @@ sealed interface ExtendsGenerator extends CodeGenerator, StringGenerator permits /** * Generates the "extends" clause of a sealed interface being generated, along with the list of parent interfaces, based on - * a provided Map containing parents/subtypes information (the permits Map) and the name of the profile for which the + * a provided {@link Map} containing parents/subtypes information (the permits Map) and the name of the profile for which the * sealed interface will be generated * * @param processingEnvironment {@link ProcessingEnvironment} object, needed to access low-level information regarding the used annotations - * @param sealedInterfaceContent StringBuilder object containing the sealed interface code being generated - * @param permitsMap Map containing parents/subtypes information. The Map key is the profile name whose generated + * @param sealedInterfaceContent {@link StringBuilder} object containing the sealed interface code being generated + * @param permitsMap {@link Map} containing parents/subtypes information. The Map key is the profile name whose generated * sealed interface will be a parent interface, while the value is the list of profiles names whose * sealed interfaces will be generated as subtypes * @param processedProfile name of the profile whose sealed interface is being generated - * @param largeInterfaceElement Element instance of the large interface being segregated + * @param largeInterfaceElement {@link Element} instance of the large interface being segregated */ void generateExtendsClauseFromPermitsMapAndProcessedProfile( ProcessingEnvironment processingEnvironment, @@ -96,15 +96,15 @@ sealed interface PermitsGenerator extends CodeGenerator, StringGenerator permits /** * Generates the "permits" clause of a sealed interface being generated, along with the list of parent interfaces, based on - * a provided Map containing parents/subtypes information (the permits Map) and the name of the profile for which the + * a provided {@link Map} containing parents/subtypes information (the permits Map) and the name of the profile for which the * sealed interface will be generated * - * @param sealedInterfaceContent StringBuilder object containing the sealed interface code being generated - * @param permitsMap Map containing parents/subtypes information. The Map key is the profile name whose generated + * @param sealedInterfaceContent {@link StringBuilder} object containing the sealed interface code being generated + * @param permitsMap {@link Map} containing parents/subtypes information. The Map key is the profile name whose generated * sealed interface will be a parent interface, while the value is the list of profiles names whose * sealed interfaces will be generated as subtypes * @param processedProfile name of the profile whose sealed interface is being generated - * @param largeInterfaceElement Element instance of the large interface being segregated + * @param largeInterfaceElement {@link Element} instance of the large interface being segregated */ void generatePermitsClauseFromPermitsMapAndProcessedProfile( StringBuilder sealedInterfaceContent, @@ -123,14 +123,14 @@ default void generateCode(final StringBuilder classOrInterfaceContent, final Lis } /** - * Adds a generated final class to the Map containing parents/subtypes information, only for the sealed interfaces at the - * lowest-level of the generated hierarchy (also know as childless interfaces).
+ * Adds a generated final class to the {@link Map} containing parents/subtypes information, only for the sealed interfaces at the + * lowest-level of the generated hierarchy (also known as childless interfaces).
* Practice proper to Jisel only to avoid compilation errors for sealed interfaces not having any existing subtypes * - * @param permitsMap Map containing parents/subtypes information. The Map key is the profile name whose generated + * @param permitsMap {@link Map} containing parents/subtypes information. The Map key is the profile name whose generated * sealed interface will be a parent interface, while the value is the list of profiles names whose * sealed interfaces will be generated as subtypes - * @param largeInterfaceElement Element instance of the large interface being segregated + * @param largeInterfaceElement {@link Element} instance of the large interface being segregated */ default void addFinalClassToPermitsMap(final Map> permitsMap, final Element largeInterfaceElement) { var finalClassName = UNDERSCORE + largeInterfaceElement.getSimpleName().toString() + FINAL_CLASS_SUFFIX; @@ -154,8 +154,8 @@ sealed interface MethodsGenerator extends CodeGenerator, StringGenerator permits /** * Generates a list of abstracts methods definitions and appends it to the sealed interface code being generated * - * @param sealedInterfaceContent StringBuilder object containing the sealed interface code being generated - * @param methodsSet Set of {@link Element} instances representing each one of the abstract methods to generate + * @param sealedInterfaceContent {@link StringBuilder} object containing the sealed interface code being generated + * @param methodsSet {@link Set} of {@link Element} instances representing each one of the abstract methods to generate */ void generateAbstractMethodsFromElementsSet(StringBuilder sealedInterfaceContent, Set methodsSet); @@ -163,8 +163,8 @@ sealed interface MethodsGenerator extends CodeGenerator, StringGenerator permits * Mainly used for a final class generation.
* Generates a list of concrete methods definitions (signature and body), and appends it to the final class being generated * - * @param sealedInterfaceContent StringBuilder object containing the sealed interface code being generated - * @param methodsSet Set of {@link Element} instances representing each one of the abstract methods to generate + * @param sealedInterfaceContent {@link StringBuilder} object containing the sealed interface code being generated + * @param methodsSet {@link Set} of {@link Element} instances representing each one of the abstract methods to generate */ void generateEmptyConcreteMethodsFromElementsSet(StringBuilder sealedInterfaceContent, Set methodsSet); @@ -177,7 +177,7 @@ default void generateCode(final StringBuilder classOrInterfaceContent, final Lis * Checks whether the provided method {@link Element} instance has any arguments * * @param methodElement method {@link Element} instance - * @return true if the provided method has any arguments + * @return true if the provided method has any arguments, false otherwise */ default boolean methodHasArguments(final Element methodElement) { return methodElement.toString().indexOf(CLOSING_PARENTHESIS) - methodElement.toString().indexOf(OPENING_PARENTHESIS) > 1; diff --git a/src/main/java/org/jisel/generator/FinalClassContentGenerator.java b/src/main/java/org/jisel/generator/FinalClassContentGenerator.java index 8705b5e..1dfb540 100644 --- a/src/main/java/org/jisel/generator/FinalClassContentGenerator.java +++ b/src/main/java/org/jisel/generator/FinalClassContentGenerator.java @@ -58,7 +58,7 @@ public FinalClassContentGenerator() { * * @param processingEnvironment {@link ProcessingEnvironment} object, needed to access low-level information regarding the used annotations * @param largeInterfaceElement {@link Element} instance of the large interface being segregated - * @param sealedInterfacesPermitsMap Map containing information about the subtypes permitted by each one of the sealed interfaces to be generated + * @param sealedInterfacesPermitsMap {@link Map} containing information about the subtypes permitted by each one of the sealed interfaces to be generated * @return the final class string content */ public String generateFinalClassContent( diff --git a/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java b/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java index d54d79c..0f153d5 100644 --- a/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java +++ b/src/main/java/org/jisel/generator/JavaxGeneratedGenerator.java @@ -32,7 +32,7 @@ import static java.lang.String.format; /** - * Generates the @javax.annotation.processing.Generated annotation section at the top of the generated interfaces or classes with the attributes: value, date and comments
+ * Generates the {@link javax.annotation.processing.Generated} annotation section at the top of the generated interfaces or classes with the attributes: value, date and comments
*/ public final class JavaxGeneratedGenerator implements CodeGenerator { diff --git a/src/main/java/org/jisel/generator/ReportGenerator.java b/src/main/java/org/jisel/generator/ReportGenerator.java index ac31fcb..924f863 100644 --- a/src/main/java/org/jisel/generator/ReportGenerator.java +++ b/src/main/java/org/jisel/generator/ReportGenerator.java @@ -56,8 +56,8 @@ public class ReportGenerator implements StringGenerator { * Generates a Report file listing all generated sealed interfaces for the provided large interfaces. * * @param largeInterfaceElement {@link Element} instance of the large interface being segregated - * @param sealedInterfacesToGenerateMap Map containing information about the sealed interfaces to be generated - * @param sealedInterfacesPermitsMap Map containing information about the subtypes permitted by each one of the sealed interfaces to be generated + * @param sealedInterfacesToGenerateMap {@link Map} containing information about the sealed interfaces to be generated + * @param sealedInterfacesPermitsMap {@link Map} containing information about the subtypes permitted by each one of the sealed interfaces to be generated * @return a string containing the text report */ public String generateReportForBloatedInterface(final Element largeInterfaceElement, diff --git a/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java b/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java index 14a9dc1..68503d6 100644 --- a/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java +++ b/src/main/java/org/jisel/generator/SealedInterfaceContentGenerator.java @@ -55,7 +55,7 @@ public SealedInterfaceContentGenerator() { * Generates the content of a sealed interface * * @param processingEnvironment {@link ProcessingEnvironment} object, needed to access low-level information regarding the used annotations - * @param sealedInterfacesToGenerateMapEntrySet Map.Entry instance containing information about the sealed interfaces to be generated + * @param sealedInterfacesToGenerateMapEntrySet {@link java.util.Map.Entry} instance containing information about the sealed interfaces to be generated * @param largeInterfaceElement {@link Element} instance of the large interface being segregated * @param sealedInterfacesPermitsMap Map containing information about the subtypes permitted by each one of the sealed interfaces to be generated * @return the string content of the sealed interface to generate diff --git a/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java b/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java index 1697e78..7860490 100644 --- a/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java +++ b/src/main/java/org/jisel/generator/SealedInterfaceSourceFileGenerator.java @@ -32,18 +32,41 @@ import java.util.Map; import java.util.Set; +/** + * Creates the content of a sealed interface and writes it to the filesystem.
+ * The sealed interface generation process also includes the final class and report generation + */ public class SealedInterfaceSourceFileGenerator implements StringGenerator { private final SealedInterfaceContentGenerator sealedInterfaceContentGenerator; private final FinalClassContentGenerator finalClassContentGenerator; private final ReportGenerator reportGenerator; + /** + * SealedInterfaceSourceFileGenerator constructor. Creates needed instances of {@link SealedInterfaceContentGenerator}, + * {@link FinalClassContentGenerator} and {@link ReportGenerator} + */ public SealedInterfaceSourceFileGenerator() { this.sealedInterfaceContentGenerator = new SealedInterfaceContentGenerator(); this.finalClassContentGenerator = new FinalClassContentGenerator(); this.reportGenerator = new ReportGenerator(); } + /** + * @param processingEnvironment {@link ProcessingEnvironment} object, needed to access low-level information regarding the used annotations + * @param sealedInterfacesToGenerateByLargeInterface {@link Map} containing information about the sealed interfaces to be generated. + * To be populated and/or modified if needed. The key represents the {@link Element} instance of + * each one of the large interfaces to be segregated, while the associated value is + * a Map of profile name as the key and a Set of Element instances as the value. + * The Element instances represent each one of the abstract methods to be + * added to the generated sealed interface corresponding to a profile. + * @param sealedInterfacesPermitsByLargeInterface Map containing information about the subtypes permitted by each one of the sealed interfaces to be generated. + * To be populated and/or modified if needed. The key represents the Element instance of + * each one of the large interfaces to be segregated, while the associated value is + * a Map of profile name as the key and a List of profiles names as the value. + * @return List of all created source files + * @throws IOException if an I/O error occured + */ public List createSourceFiles(final ProcessingEnvironment processingEnvironment, final Map>> sealedInterfacesToGenerateByLargeInterface, final Map>> sealedInterfacesPermitsByLargeInterface) throws IOException { diff --git a/src/main/java/org/jisel/generator/StringGenerator.java b/src/main/java/org/jisel/generator/StringGenerator.java index 7389dbf..5b93c80 100644 --- a/src/main/java/org/jisel/generator/StringGenerator.java +++ b/src/main/java/org/jisel/generator/StringGenerator.java @@ -153,12 +153,12 @@ public interface StringGenerator { */ String ANNOTATION_VALUES_REGEX = "\"([^\"]*)\""; /** - * Regex expression to read attributes information provided using the AddToProfile annotation.
+ * Regex expression to read attributes information provided using the {@link org.jisel.AddToProfile} annotation.
* Sample value to be parsed by the regex: @org.jisel.AddToProfile(profile="ActiveWorker", largeInterface="com.bayor.jisel.annotation.client.data.Sociable") */ String ADD_TO_PROFILE_REGEX = "AddToProfile\\((.*?)\\)"; /** - * Regex expression to read attributes information provided using the AddToProfiles annotation.
+ * Regex expression to read attributes information provided using the {@link org.jisel.AddToProfiles} annotation.
* Sample value to be parsed by the regex: @org.jisel.AddToProfiles(profiles={"Student", "Worker"}, largeInterface="com.bayor.jisel.annotation.client.data.Sociable") */ String ADD_TO_PROFILES_REGEX = "AddToProfiles\\((.*?)\\)"; @@ -170,7 +170,7 @@ public interface StringGenerator { String STATUS_REPORT_TITLE = "JISEL GENERATION REPORT"; /** - * Displayed only when a "severe" error occured while a sealed interface file was being generated + * Displayed only when a "severe" error occurred while a sealed interface file was being generated */ String FILE_GENERATION_ERROR = "Error generating sealed interfaces"; /** @@ -179,35 +179,35 @@ public interface StringGenerator { String FILE_GENERATION_SUCCESS = "Successfully generated"; /** - * Fully qualified name of the SealForProfile annotation + * Fully qualified name of the {@link org.jisel.SealForProfile} annotation */ String ORG_JISEL_SEAL_FOR_PROFILE = "org.jisel.SealForProfile"; /** - * Fully qualified name of the SealForProfiles annotation + * Fully qualified name of the {@link org.jisel.SealForProfiles} annotation */ String ORG_JISEL_SEAL_FOR_PROFILES = "org.jisel.SealForProfiles"; /** - * Fully qualified name of the SealForProfilez annotation + * Fully qualified name of the {@link org.jisel.SealForProfile.SealForProfilez} annotation */ String ORG_JISEL_SEAL_FOR_PROFILEZ = "org.jisel.SealForProfile.SealForProfilez"; /** - * Fully qualified name of the SealForProfilezz annotation + * Fully qualified name of the {@link org.jisel.SealForProfiles.SealForProfilezz} annotation */ String ORG_JISEL_SEAL_FOR_PROFILEZZ = "org.jisel.SealForProfiles.SealForProfilezz"; /** - * Fully qualified name of the AddToProfile annotation + * Fully qualified name of the {@link org.jisel.AddToProfile} annotation */ String ORG_JISEL_ADD_TO_PROFILE = "org.jisel.AddToProfile"; /** - * Fully qualified name of the AddToProfiles annotation + * Fully qualified name of the {@link org.jisel.AddToProfiles} annotation */ String ORG_JISEL_ADD_TO_PROFILES = "org.jisel.AddToProfiles"; /** - * Fully qualified name of the AddToProfilez annotation + * Fully qualified name of the {@link org.jisel.AddToProfile.AddToProfilez} annotation */ String ORG_JISEL_ADD_TO_PROFILEZ = "org.jisel.AddToProfile.AddToProfilez"; /** - * Fully qualified name of the AddToProfilezz annotation + * Fully qualified name of the {@link org.jisel.AddToProfiles.AddToProfilezz} annotation */ String ORG_JISEL_ADD_TO_PROFILEZZ = "org.jisel.AddToProfiles.AddToProfilezz"; @@ -264,11 +264,11 @@ static String removeCommaSeparator(final String text) { } /** - * Constructs a string based on the provided profile and a large interface Element instance, according to the naming convention:
+ * Constructs a string based on the provided profile and a large interface {@link Element} instance, according to the naming convention:
* Sealed<ProfileName><LargeInterfaceSimpleName>

* * @param profile name of the profile - * @param interfaceElement Element instance of the large interface to be segregated + * @param interfaceElement {@link Element} instance of the large interface to be segregated * @return a string following Jisel sealed interface naming convention */ default String sealedInterfaceNameConvention(final String profile, final Element interfaceElement) { @@ -283,11 +283,11 @@ default String sealedInterfaceNameConvention(final String profile, final Element } /** - * Constructs a string based on the provided profiles and a large interface Element instance, according to the naming convention:
+ * Constructs a string based on the provided profiles and a large interface {@link Element} instance, according to the naming convention:
* Sealed<ProfileName><LargeInterfaceSimpleName>

* - * @param profiles List of profiles names - * @param interfaceElement Element instance of the large interface to be segregated + * @param profiles {@link List} of profiles names + * @param interfaceElement {@link Element} instance of the large interface to be segregated * @return a List of string literals following Jisel sealed interface naming convention */ default List sealedInterfaceNameConventionForList(final List profiles, final Element interfaceElement) { @@ -302,9 +302,9 @@ default List sealedInterfaceNameConventionForList(final List pro } /** - * Constructs the java package name based on an Element instance of the large interface to be segregated. + * Constructs the java package name based on an {@link Element} instance of the large interface to be segregated. * - * @param largeInterfaceElement Element instance of the large interface to be segregated + * @param largeInterfaceElement {@link Element} instance of the large interface to be segregated * @return the package name if any */ default Optional generatePackageName(final Element largeInterfaceElement) { @@ -316,8 +316,8 @@ default Optional generatePackageName(final Element largeInterfaceElement /** * Replace all double occurences of whitespace (" ") into a single whitespace (" ") * - * @param text contains double occurences of whitespace - * @return the provided text with all double occurences of whitespace replaced with a single occurence + * @param text contains double occurrences of whitespace + * @return the provided text with all double occurrences of whitespace replaced with a single occurence */ default String removeDoubleSpaceOccurrences(final String text) { return text.replace(WHITESPACE + WHITESPACE, WHITESPACE); diff --git a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java index 6372f13..e08a5eb 100644 --- a/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java +++ b/src/main/java/org/jisel/handlers/JiselAnnotationHandler.java @@ -44,17 +44,17 @@ import static org.jisel.generator.StringGenerator.removeCommaSeparator; /** - * Interface exposing contract to fulfill by any class handling the elements annotated with @SealForProfile and @AddToProfile annotations + * Exposes contract to fulfill by any class handling all elements annotated with @{@link org.jisel.SealForProfile}(s) and @{@link org.jisel.AddToProfile}(s) annotations */ public sealed interface JiselAnnotationHandler extends StringGenerator permits SealForProfileHandler, AddToProfileHandler, AnnotationInfoCollectionHandler, UniqueParentInterfaceHandler, ParentChildInheritanceHandler { /** - * Reads values of all attributes provided through the use of @SealForProfile and @AddToProfile annotations and + * Reads values of all attributes provided through the use of @{@link org.jisel.SealForProfile} and @{@link org.jisel.AddToProfile} annotations and * populates the provided Map arguments * * @param processingEnv {@link ProcessingEnvironment} object, needed to access low-level information regarding the used annotations - * @param allAnnotatedElements Set of Element instances representing all classes annotated with @AddToProfile and + * @param allAnnotatedElements {@link Set} of {@link Element} instances representing all classes annotated with @AddToProfile and * all abstract methods annotated with @SealForProfile * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. * To be populated and/or modified if needed. The key represents the Element instance of @@ -74,11 +74,11 @@ Map handleAnnotatedElements(ProcessingEnvironment processingEnv Map>> sealedInterfacesPermitsByLargeInterface); /** - * For a specified class or interface annotated with @AddToProfile, constructs a Map storing a Set of all the provided + * For a specified class or interface annotated with @{@link org.jisel.AddToProfile}, constructs a Map storing a Set of all the provided * profiles names (as the Map value) for each one of the large interfaces names (as the Map key) provided through @AddToProfile. * * @param processingEnv {@link ProcessingEnvironment} object, needed to access low-level information regarding the used annotations - * @param annotatedClassOrInterface Element instance representing the annotated class or interface + * @param annotatedClassOrInterface {@link Element} instance representing the annotated class or interface * @return a Map storing a Set of all the provided profiles names (as the Map value) for each one of the large interfaces names (as the Map key) */ default Map> buildAddToProfileProvidedProfilesMap(final ProcessingEnvironment processingEnv, final Element annotatedClassOrInterface) { @@ -126,10 +126,10 @@ private void updateProvidedProfilesMapBasedOnProfilesSet(final Map buildSealForProfileProvidedProfilesSet(final ProcessingEnvironment processingEnv, final Element annotatedMethod) { @@ -155,7 +155,7 @@ default Set buildSealForProfileProvidedProfilesSet(final ProcessingEnvir } /** - * Interface exposing contract to fulfill by any class dedicated to collecting necessary information from the annotated elements, + * Exposes contract to fulfill by any class dedicated to collecting necessary information from the annotated elements, * in order to populate the {@link Map} containing the sealed interfaces information to be generated */ sealed interface AnnotationInfoCollectionHandler extends JiselAnnotationHandler permits SealForProfileInfoCollectionHandler { @@ -164,7 +164,7 @@ sealed interface AnnotationInfoCollectionHandler extends JiselAnnotationHandler * Populates the Map containing the sealed interfaces information to be generated * * @param processingEnv {@link ProcessingEnvironment} object, needed to access low-level information regarding the used annotations - * @param allAnnotatedElements {@link Set} of Element instances representing all classes annotated with @AddToProfile and + * @param allAnnotatedElements {@link Set} of {@link Element} instances representing all classes annotated with @AddToProfile and * all abstract methods annotated with @SealForProfile * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. * To be populated and/or modified if needed. The key represents the {@link Element} instance of @@ -181,9 +181,9 @@ void populateSealedInterfacesMap(ProcessingEnvironment processingEnv, * Creates intermediate parent interfaces based on common methods of provided profiles, then stores the created intermediate * parent interfaces in the Map containing the sealed interfaces information to be generated * - * @param annotatedMethodsByProfileByLargeInterface Set of all annotated abstract methods for a specified profile - * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. - * To be populated and/or modified if needed. The key represents the Element instance of + * @param annotatedMethodsByProfileByLargeInterface {@link Set} of all annotated abstract methods for a specified profile + * @param sealedInterfacesToGenerateByLargeInterface {@link Map} containing information about the sealed interfaces to be generated. + * To be populated and/or modified if needed. The key represents the {@link Element} instance of * each one of the large interfaces to be segregated, while the associated value is * a Map of profile name as the key and a Set of Element instances as the value. * The Element instances represent each one of the abstract methods to be @@ -251,7 +251,7 @@ default Map handleAnnotatedElements(final ProcessingEnvironment } /** - * Interface exposing contract to fulfill by any class dedicated to building parent-children relations based on information provided in + * Exposes contract to fulfill by any class dedicated to building parent-children relations based on information provided in * the Map containing the sealed interfaces information to be generated */ sealed interface ParentChildInheritanceHandler extends JiselAnnotationHandler permits SealForProfileParentChildInheritanceHandler { @@ -259,8 +259,8 @@ sealed interface ParentChildInheritanceHandler extends JiselAnnotationHandler pe /** * Reads information stored in the Map containing the sealed interfaces information to be generated, and populates another Map storing subtypes of the provided profiles * - * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. - * To be populated and/or modified if needed. The key represents the Element instance of + * @param sealedInterfacesToGenerateByLargeInterface {@link Map} containing information about the sealed interfaces to be generated. + * To be populated and/or modified if needed. The key represents the {@link Element} instance of * each one of the large interfaces to be segregated, while the associated value is * a Map of profile name as the key and a Set of Element instances as the value. * The Element instances represent each one of the abstract methods to be @@ -279,9 +279,9 @@ void buildInheritanceRelations(Map>> sealedInt /** * Populates a Map storing subtypes of the provided profiles * - * @param interfaceElement large interface Element instance - * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. - * To be populated and/or modified if needed. The key represents the Element instance of + * @param interfaceElement large interface {@link Element} instance + * @param sealedInterfacesToGenerateByLargeInterface {@link Map} containing information about the sealed interfaces to be generated. + * To be populated and/or modified if needed. The key represents the {@link Element} instance of * each one of the large interfaces to be segregated, while the associated value is * a Map of profile name as the key and a Set of Element instances as the value. * The Element instances represent each one of the abstract methods to be @@ -328,8 +328,8 @@ default Map handleAnnotatedElements(final ProcessingEnvironment } /** - * Interface exposing contract to fulfill by any class dedicated to checking for the presence of an unique parent interface - * based on information provided in the Map containing the sealed interfaces information to be generated + * Exposes contract to fulfill by any class dedicated to checking for the presence of an unique parent interface based on + * information provided in the Map containing the sealed interfaces information to be generated */ sealed interface UniqueParentInterfaceHandler extends JiselAnnotationHandler permits SealForProfileUniqueParentInterfaceHandler { @@ -345,7 +345,7 @@ sealed interface UniqueParentInterfaceHandler extends JiselAnnotationHandler per * added to the generated sealed interface corresponding to a profile. * @return Map providing information about the presence of an unique parent sealed interface detected based on common methods of profiles.
* The informational message provided is a String literal describing the absence of an unique parent sealed interface, - * for each one of the provided large interfces to be segregated + * for each one of the provided large interfaces to be segregated */ Map checkAndHandleUniqueParentInterface(Map>> sealedInterfacesToGenerateByLargeInterface); @@ -353,7 +353,7 @@ sealed interface UniqueParentInterfaceHandler extends JiselAnnotationHandler per * Checks for the presence of an unique parent interface based on information provided in the Map containing the sealed interfaces information to be generated * * @param sealedInterfacesToGenerateByLargeInterface Map containing information about the sealed interfaces to be generated. - * To be populated and/or modified if needed. The key represents the Element instance of + * To be populated and/or modified if needed. The key represents the {@link Element} instance of * each one of the large interfaces to be segregated, while the associated value is * a Map of profile name as the key and a Set of Element instances as the value. * The Element instances represent each one of the abstract methods to be diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3c4359c..a0fcd6a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,5 @@ -info.app.name = Jisel +info.app.name = JISEL info.app.version = 1.0 -info.app.description = Interface Segregation Library for Java 17 +info.app.description = Java Interface Segregation Library info.app.contact.name = Mohamed Ashraf Bayor info.app.contact.url = https://jisel.org/ \ No newline at end of file