diff --git a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/ElementUtils.java b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/ElementUtils.java index cb6fcec9..aa0cabbe 100644 --- a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/ElementUtils.java +++ b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/ElementUtils.java @@ -174,4 +174,10 @@ private static String getBuilderNamePrefix(Element element) { private ElementUtils() { } + + static RecordBuilder.Options getMetaData(ProcessingEnvironment processingEnv, Element element) { + var recordSpecificMetaData = element.getAnnotation(RecordBuilder.Options.class); + return (recordSpecificMetaData != null) ? recordSpecificMetaData + : RecordBuilderOptions.build(processingEnv.getOptions()); + } } diff --git a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/RecordBuilderCleaner.java b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/RecordBuilderCleaner.java new file mode 100644 index 00000000..7cf8a7bf --- /dev/null +++ b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/RecordBuilderCleaner.java @@ -0,0 +1,128 @@ +/* + * Copyright 2019 The original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.soabase.recordbuilder.processor; + +import io.soabase.recordbuilder.core.RecordBuilder; +import io.soabase.recordbuilder.core.RecordInterface; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.StandardLocation; +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName; +import static io.soabase.recordbuilder.processor.ElementUtils.getMetaData; +import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.RECORD_INTERFACE; +import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.RECORD_INTERFACE_INCLUDE; + +public class RecordBuilderCleaner extends AbstractProcessor { + private static final Set deletedSet = new HashSet<>(); + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + List results = annotations.stream() + .flatMap(annotation -> roundEnv.getElementsAnnotatedWith(annotation).stream().map(element -> process(annotation, element))) + .toList(); + boolean result = results.stream().allMatch(b -> b); + System.err.println("RecordBuilderCleaner - " + annotations + " - " + result); + return result; + } + + @Override + public Set getSupportedAnnotationTypes() { + return Set.of("*"); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + private boolean process(TypeElement annotation, Element element) { + String annotationClass = annotation.getQualifiedName().toString(); + if (annotationClass.equals(RECORD_INTERFACE)) { + var typeElement = (TypeElement) element; + return processRecordInterface(typeElement, element.getAnnotation(RecordInterface.class).addRecordBuilder(), + getMetaData(processingEnv, typeElement), Optional.empty(), false); + } else if (annotationClass.equals(RECORD_INTERFACE_INCLUDE)) { + // processIncludes(element, getMetaData(processingEnv, element), annotationClass); TODO + } else { + var recordBuilderTemplate = annotation.getAnnotation(RecordBuilder.Template.class); + if (recordBuilderTemplate != null) { + if (recordBuilderTemplate.asRecordInterface()) { + return processRecordInterface((TypeElement) element, true, recordBuilderTemplate.options(), + Optional.empty(), true); + } + } + } + return false; + } + + private boolean processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilder.Options metaData, + Optional packageName, boolean fromTemplate) { + ClassType ifaceClassType = ElementUtils.getClassType(element, element.getTypeParameters()); + String actualPackageName = packageName.orElseGet(() -> ElementUtils.getPackageName(element)); + getBuilderName(element, metaData, ifaceClassType, metaData.interfaceSuffix()); + + boolean b1 = deletePossibleClassFile(actualPackageName, ifaceClassType.name() + metaData.interfaceSuffix(), + StandardLocation.SOURCE_OUTPUT); + boolean b2 = deletePossibleClassFile(actualPackageName, + ifaceClassType.name() + metaData.interfaceSuffix() + metaData.suffix(), StandardLocation.SOURCE_OUTPUT); +/* + boolean b3 = deletePossibleClassFile(actualPackageName, ifaceClassType.name() + metaData.interfaceSuffix(), StandardLocation.CLASS_OUTPUT); + boolean b4 = deletePossibleClassFile(actualPackageName, ifaceClassType.name() + metaData.interfaceSuffix() + metaData.suffix(), StandardLocation.CLASS_OUTPUT); +*/ + + return b1 || b2;// || b3 || b4; + } + + private boolean deletePossibleClassFile(String packageName, String className, StandardLocation location) { + String extension = (location == StandardLocation.CLASS_OUTPUT) ? ".class" : ".java"; + + if (!deletedSet.add(packageName + "." + className + extension)) { + return false; + } + + try { + FileObject resource = processingEnv.getFiler().getResource(location, packageName, className + extension); + File file = new File(resource.toUri()); + System.err.println("XXXX Cleaner: Exists: %s - File %s".formatted(file.exists(), file)); + if (file.exists()) { + if (!file.delete()) { + System.err.println("Could not delete existing: Exists: %s - File %s".formatted(file.exists(), file)); + processingEnv.getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, + "Could not delete existing class file: %s".formatted(file)); + return false; + } + return true; + } + return false; + } catch (IOException e) { + // ignore + } + return false; + } +} diff --git a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/RecordBuilderProcessor.java b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/RecordBuilderProcessor.java index 643d8e5c..3c0e7a79 100644 --- a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/RecordBuilderProcessor.java +++ b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/RecordBuilderProcessor.java @@ -22,26 +22,31 @@ import io.soabase.recordbuilder.core.RecordBuilderGenerated; import io.soabase.recordbuilder.core.RecordInterface; -import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.Filer; -import javax.annotation.processing.Generated; -import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; +import javax.tools.FileObject; import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; +import java.io.File; import java.io.IOException; import java.io.Writer; +import java.util.HashSet; import java.util.Optional; import java.util.Set; +import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName; + public class RecordBuilderProcessor extends AbstractProcessor { - private static final String RECORD_BUILDER = RecordBuilder.class.getName(); - private static final String RECORD_BUILDER_INCLUDE = RecordBuilder.Include.class.getName().replace('$', '.'); - private static final String RECORD_INTERFACE = RecordInterface.class.getName(); - private static final String RECORD_INTERFACE_INCLUDE = RecordInterface.Include.class.getName().replace('$', '.'); + static final String RECORD_BUILDER = RecordBuilder.class.getName(); + static final String RECORD_BUILDER_INCLUDE = RecordBuilder.Include.class.getName().replace('$', '.'); + static final String RECORD_INTERFACE = RecordInterface.class.getName(); + static final String RECORD_INTERFACE_INCLUDE = RecordInterface.Include.class.getName().replace('$', '.'); + + private static final Set deletedSet = new HashSet<>(); static final AnnotationSpec generatedRecordBuilderAnnotation = AnnotationSpec.builder(Generated.class) .addMember("value", "$S", RecordBuilder.class.getName()).build(); @@ -52,6 +57,8 @@ public class RecordBuilderProcessor extends AbstractProcessor { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { + System.err.println("RecordBuilderProcessor - " + annotations); + annotations.forEach(annotation -> roundEnv.getElementsAnnotatedWith(annotation) .forEach(element -> process(annotation, element))); return false; @@ -75,13 +82,13 @@ private void process(TypeElement annotation, Element element) { String annotationClass = annotation.getQualifiedName().toString(); if (annotationClass.equals(RECORD_BUILDER)) { var typeElement = (TypeElement) element; - processRecordBuilder(typeElement, getMetaData(typeElement), Optional.empty()); + processRecordBuilder(typeElement, ElementUtils.getMetaData(processingEnv, typeElement), Optional.empty()); } else if (annotationClass.equals(RECORD_INTERFACE)) { var typeElement = (TypeElement) element; processRecordInterface(typeElement, element.getAnnotation(RecordInterface.class).addRecordBuilder(), - getMetaData(typeElement), Optional.empty(), false); + ElementUtils.getMetaData(processingEnv, typeElement), Optional.empty(), false); } else if (annotationClass.equals(RECORD_BUILDER_INCLUDE) || annotationClass.equals(RECORD_INTERFACE_INCLUDE)) { - processIncludes(element, getMetaData(element), annotationClass); + processIncludes(element, ElementUtils.getMetaData(processingEnv, element), annotationClass); } else { var recordBuilderTemplate = annotation.getAnnotation(RecordBuilder.Template.class); if (recordBuilderTemplate != null) { @@ -95,12 +102,6 @@ private void process(TypeElement annotation, Element element) { } } - private RecordBuilder.Options getMetaData(Element element) { - var recordSpecificMetaData = element.getAnnotation(RecordBuilder.Options.class); - return (recordSpecificMetaData != null) ? recordSpecificMetaData - : RecordBuilderOptions.build(processingEnv.getOptions()); - } - private void processIncludes(Element element, RecordBuilder.Options metaData, String annotationClass) { var isRecordBuilderInclude = annotationClass.equals(RECORD_BUILDER_INCLUDE); var annotationMirrorOpt = ElementUtils.findAnnotationMirror(processingEnv, element, annotationClass); @@ -166,12 +167,26 @@ private void processRecordInterface(TypeElement element, boolean addRecordBuilde validateMetaData(metaData, element); + ClassType ifaceClassType = ElementUtils.getClassType(element, element.getTypeParameters()); + String actualPackageName = packageName.orElseGet(() -> ElementUtils.getPackageName(element)); + getBuilderName(element, metaData, ifaceClassType, metaData.interfaceSuffix()); + + boolean b1 = deletePossibleClassFile(actualPackageName, ifaceClassType.name() + metaData.interfaceSuffix(), + StandardLocation.SOURCE_OUTPUT); + boolean b2 = deletePossibleClassFile(actualPackageName, + ifaceClassType.name() + metaData.interfaceSuffix() + metaData.suffix(), StandardLocation.SOURCE_OUTPUT); + // boolean b3 = deletePossibleClassFile(actualPackageName, ifaceClassType.name() + metaData.interfaceSuffix(), + // StandardLocation.CLASS_OUTPUT); + // boolean b4 = deletePossibleClassFile(actualPackageName, ifaceClassType.name() + metaData.interfaceSuffix() + + // metaData.suffix(), StandardLocation.CLASS_OUTPUT); + var internalProcessor = new InternalRecordInterfaceProcessor(processingEnv, element, addRecordBuilder, metaData, packageName, fromTemplate); if (!internalProcessor.isValid()) { return; } - writeRecordInterfaceJavaFile(element, internalProcessor.packageName(), internalProcessor.recordClassType(), + + writeJavaFile(element, internalProcessor.packageName(), internalProcessor.recordClassType(), internalProcessor.recordType(), metaData); } @@ -190,7 +205,7 @@ private void processRecordBuilder(TypeElement record, RecordBuilder.Options meta validateMetaData(metaData, record); var internalProcessor = new InternalRecordBuilderProcessor(processingEnv, record, metaData, packageName); - writeRecordBuilderJavaFile(record, internalProcessor.packageName(), internalProcessor.builderClassType(), + writeJavaFile(record, internalProcessor.packageName(), internalProcessor.builderClassType(), internalProcessor.builderType(), metaData); } @@ -210,8 +225,8 @@ private void validateMetaData(RecordBuilder.Options metaData, Element record) { } } - private void writeRecordBuilderJavaFile(TypeElement record, String packageName, ClassType builderClassType, - TypeSpec builderType, RecordBuilder.Options metaData) { + private void writeJavaFile(TypeElement record, String packageName, ClassType builderClassType, TypeSpec builderType, + RecordBuilder.Options metaData) { // produces the Java file JavaFile javaFile = javaFileBuilder(packageName, builderType, metaData); Filer filer = processingEnv.getFiler(); @@ -227,25 +242,6 @@ private void writeRecordBuilderJavaFile(TypeElement record, String packageName, } } - private void writeRecordInterfaceJavaFile(TypeElement element, String packageName, ClassType classType, - TypeSpec type, RecordBuilder.Options metaData) { - JavaFile javaFile = javaFileBuilder(packageName, type, metaData); - - String recordSourceCode = javaFile.toString(); - - Filer filer = processingEnv.getFiler(); - try { - String fullyQualifiedName = packageName.isEmpty() ? classType.name() - : (packageName + "." + classType.name()); - JavaFileObject sourceFile = filer.createSourceFile(fullyQualifiedName); - try (Writer writer = sourceFile.openWriter()) { - writer.write(recordSourceCode); - } - } catch (IOException e) { - handleWriteError(element, e); - } - } - private JavaFile javaFileBuilder(String packageName, TypeSpec type, RecordBuilder.Options metaData) { var javaFileBuilder = JavaFile.builder(packageName, type).skipJavaLangImports(true) .indent(metaData.fileIndent()); @@ -263,4 +259,31 @@ private void handleWriteError(TypeElement element, IOException e) { } processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, element); } + + private boolean deletePossibleClassFile(String packageName, String className, StandardLocation location) { + String extension = (location == StandardLocation.CLASS_OUTPUT) ? ".class" : ".java"; + + if (!deletedSet.add(packageName + "." + className + extension)) { + return false; + } + + try { + FileObject resource = processingEnv.getFiler().getResource(location, packageName, className + extension); + File file = new File(resource.toUri()); + processingEnv.getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, + "Exists: %s - File %s".formatted(file.exists(), file)); + if (file.exists()) { + if (!file.delete()) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, + "Could not delete existing class file: %s".formatted(file)); + return false; + } + return true; + } + return false; + } catch (IOException e) { + // ignore + } + return false; + } } diff --git a/record-builder-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/record-builder-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor index b1715822..9b813c9e 100644 --- a/record-builder-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/record-builder-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1 +1,2 @@ -io.soabase.recordbuilder.processor.RecordBuilderProcessor \ No newline at end of file +io.soabase.recordbuilder.processor.RecordBuilderCleaner +io.soabase.recordbuilder.processor.RecordBuilderProcessor diff --git a/record-builder-test/pom.xml b/record-builder-test/pom.xml index 1203ee42..07b8e73c 100644 --- a/record-builder-test/pom.xml +++ b/record-builder-test/pom.xml @@ -16,7 +16,8 @@ limitations under the License. --> - + io.soabase.record-builder record-builder