Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #36: enabled custom file extensions at package level #46

Merged
merged 1 commit into from
Aug 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// //
// Copyright 2020 Mirko Raner //
// //
// 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 pro.projo.interfaces.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.tools.StandardLocation;
import static java.lang.annotation.ElementType.PACKAGE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static javax.tools.StandardLocation.SOURCE_OUTPUT;

/**
* The {@link Options @Options} annotation captures various settings that affect the compile-time
* code generation triggered by the {@link Interface @Interface} and {@link Enum @Enum} annotations.
* An {@link Options @Options} annotation can be used stand-alone at package level, where it will
* affect code generation for the entire package, or as an argument to individual
* {@link Interface @Interface} or {@link Enum @Enum} annotations, where it will define (or override)
* the settings for just that annotation.
*
* @author Mirko Raner
**/
@Target(PACKAGE)
@Retention(RUNTIME)
public @interface Options
{
/**
* The file extension to be used for generated files. Default value is {@code ".java"}.
*
* @return the file extension
**/
String fileExtension() default ".java";

/**
* The output location for generated files with a custom file extension.
* <b>NOTE:</b> this setting only has an effect when {@link #fileExtension()} is set to
* a non-default value; {@code .java} files will always be generated in the
* {@link StandardLocation#SOURCE_OUTPUT} location (which is also the default value for
* this option).
*
* @return the output location
**/
StandardLocation outputLocation() default SOURCE_OUTPUT;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// //
// Copyright 2020 Mirko Raner //
// //
// 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. //
// //
@Options
package pro.projo.interfaces.annotation;

/**
* This {@code package-info} file serves the only purpose of providing easy access
* to an {@link Options} instance that has all default values.
*
* @author Mirko Raner
**/
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// //
// Copyright 2018 Mirko Raner //
// Copyright 2018 - 2020 Mirko Raner //
// //
// Licensed under the Apache License, Version 2.0 (the "License"); //
// you may not use this file except in compliance with the License. //
Expand All @@ -16,6 +16,8 @@
package pro.projo.template.annotation;

import java.util.Map;
import java.util.function.Function;
import pro.projo.interfaces.annotation.Options;

/**
* The {@link Configuration} interface describes the fully-qualified class name and the input
Expand All @@ -26,7 +28,42 @@
**/
public interface Configuration
{
/**
* @return the fully qualified name of the class to be generated
**/
String fullyQualifiedClassName();

/**
* @return additional parameters that need to be inserted into the code generation template
**/
Map<String, Object> parameters();

/**
* Retrieves additional configuration options that were provided as an annotation parameter or
* package annotation.
*
* @return the specified {@link Options} or a default {@link Options} object if not specified
**/
default Options options()
{
return defaults(null);
}

/**
* Checks if the specified option is set to the default value. This method will also return true
* if an option parameter was specified but happens to be identical to the default value
* (e.g., {@code @Option(fileExtension=".java")}).
*
* @param option the option to check (typically as a method reference)
* @return {@code true} if the option value is identical to the default value, {@code false} otherwise
**/
default boolean isDefault(Function<Options, ?> option)
{
return option.apply(defaults(null)).equals(option.apply(options()));
}

default Options defaults(PackagePrivate doNotImplementThisMethod)
{
return Options.class.getPackage().getAnnotation(Options.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// //
// Copyright 2020 Mirko Raner //
// //
// 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 pro.projo.template.annotation;

/**
* The {@link PackagePrivate} interface is designed to prevent implementors from
* implementing/overriding certain methods that are intended to be left with their
* original implementation.
*
* @author Mirko Raner
**/
interface PackagePrivate
{
// Marker interface; no methods defined...
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// //
package pro.projo.generation.interfaces;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
Expand Down Expand Up @@ -164,4 +165,12 @@ public void testShadowedTypeVariablesAreUsedInMethodSignature() throws Exception
List<String> expected = asList(methodLevelTypeVariable, methodLevelTypeVariable);
assertEquals(expected, actual);
}

@Test
public void resourcesWithCustomFileExtensionAreGenerated() throws Exception
{
ClassLoader classLoader = getClass().getClassLoader();
InputStream file = classLoader.getResourceAsStream("pro/projo/generation/interfaces/test/options/Runnable.kava");
assertNotNull(file);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// //
// Copyright 2020 Mirko Raner //
// //
// 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. //
// //
@Options(fileExtension=".kava", outputLocation=CLASS_OUTPUT)
@Interface(generate="Runnable", from=Runnable.class)
package pro.projo.generation.interfaces.test.options;

import pro.projo.interfaces.annotation.Interface;
import pro.projo.interfaces.annotation.Options;
import static javax.tools.StandardLocation.CLASS_OUTPUT;

/**
* This {@code package-info} class contains additional annotations that are tested by the
* {@link pro.projo.generation.interfaces.InterfaceTemplateProcessorTest} class.
*
* <b>NOTE:</b> generating files in the {@link StandardLocation#CLASS_OUTPUT} location
* (instead of {@link StandardLocation#SOURCE_OUTPUT}) is the only option that allows for
* verifying the generated files as resources (via {@link ClassLoader#getResourceAsStream(String)})
* in both Maven and Eclipse without necessitating additional configuration or build steps.
*
* @author Mirko Raner
**/
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
import javax.lang.model.util.ElementScanner8;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.JavaFileObject;
import javax.tools.FileObject;
import pro.projo.generation.ProjoProcessor;
import pro.projo.generation.ProjoTemplateFactoryGenerator;
import pro.projo.generation.utilities.DefaultNameComparator;
Expand All @@ -70,6 +70,7 @@
import pro.projo.interfaces.annotation.Enum;
import pro.projo.interfaces.annotation.Enums;
import pro.projo.interfaces.annotation.Interface;
import pro.projo.interfaces.annotation.Options;
import pro.projo.template.annotation.Configuration;
import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
Expand All @@ -89,7 +90,6 @@
import static pro.projo.generation.interfaces.InterfaceTemplateProcessor.Interface;
import static pro.projo.generation.interfaces.InterfaceTemplateProcessor.Interfaces;


/**
* The {@link InterfaceTemplateProcessor} is an annotation processor that, at compile time, detects source files
* that have an {@link Interface @Interface}/{@link Enum @Enum} annotation and will use these sources for
Expand Down Expand Up @@ -155,7 +155,7 @@ private <_Annotation_ extends Annotation> void process(RoundEnvironment round,
try
{
String templateClassName = templateClass.getName();
JavaFileObject sourceFile = filer.createSourceFile(className, typeElement);
FileObject sourceFile = createFile(filer, configuration, className, typeElement);
String resourceName = "/" + templateClassName.replace('.', '/') + ".java";
try (PrintWriter writer = new PrintWriter(sourceFile.openWriter(), true))
{
Expand All @@ -177,6 +177,20 @@ private <_Annotation_ extends Annotation> void process(RoundEnvironment round,
}
}

private FileObject createFile(Filer filer, Configuration configuration, String className, Element typeElement)
throws IOException
{
if (configuration.isDefault(Options::fileExtension))
{
return filer.createSourceFile(className, typeElement);
}
Options options = configuration.options();
String fileExtension = options.fileExtension();
String sourceFileName = className.substring(className.lastIndexOf('.')+1) + fileExtension;
String packageName = className.substring(0, className.lastIndexOf('.'));
return filer.createResource(options.outputLocation(), packageName, sourceFileName, typeElement);
}

private <_Annotation_ extends Annotation> List<_Annotation_> getAnnotations(Element packageElement,
Class<_Annotation_> single, Class<? extends Annotation> repeated)
{
Expand Down Expand Up @@ -277,6 +291,13 @@ public String fullyQualifiedClassName()
return packageName.toString() + '.' + annotation.generate();
}

@Override
public Options options()
{
Options packageOptions = element.getAnnotation(Options.class);
return packageOptions != null? packageOptions:Configuration.super.options();
}

private String interfaceSignature()
{
String signature = annotation.generate();
Expand Down