Skip to content
Branch: master
Find file History
Latest commit 0796ce6 Aug 20, 2019
Permalink
Type Name Latest commit message Commit time
..
Failed to load latest commit information.
src/main Manifold project-wide changes: Aug 12, 2019
pom.xml bump version Aug 20, 2019
readme.md bump version Aug 20, 2019

readme.md

Gitter Maven Central

Java Preprocessor

Table of Contents

Overview

The Java Preprocessor is designed exclusively for conditional compilation of Java source code. It is directly integrated into the Java compiler via the Javac Plugin API. Unlike conventional preprocessors it does not incur separate build steps or additional file I/O, instead it directly contributes to the compilation pipeline.

javac

The preprocessor offers a simple and convenient way to support multiple build targets with a single codebase. It provides advanced features such as tiered symbol definition via build.properties files, -Akey[=value] compiler arguments, and environment settings symbols such as JAVA_9_OR_LATER and JPMS_NAMED. The preprocessor is also fully integrated into IntelliJ IDEA using the Manifold plugin:

preprocessor

Directives

The Manifold preprocessor uses familiar directives to conditionally filter source code before it is parsed. The preprocessor supports the following directives:

Note the nomenclature is borrowed from the C-family of preprocessors for the sake of familiarity.

#define

Use #define to define a symbol. When a symbol evaluates it is either true or false, it is true if and only if it is defined. You use symbols in expressions as conditions for compilation with #if and #elif directives.

The preprocessor's symbols are not accessible to Java code, likewise variables in Java code are not accessible to the preprocessor. This means symbols specified with #define never conflict with fields or variables of the same name.

The effects of #define are limited to the file scope, as such symbols defined in one file are not accessible to others.

#define directives can appear anywhere in a file following the package statement.

package com.example;

#define EXPERIMENTAL

import java.math.BigDecimal;
#if EXPERIMENTAL
import com.example.features.NewFeature;
#endif

public class MyClass #if EXPERIMENTAL implements NewFeature #endif {
  ...
#if EXPERIMENTAL
  @Override
  public void newFeatureMethod() {
    ...
  }
#endif
}

Note additional symbols are available to the preprocessor to access JVM and compiler settings as well as custom build properties. These are covered below in the Symbols section.

#undef

Use #undef to undefine a symbol so that when evaluated, such as in an #if expression, its value is false.

A symbol can be defined either with the #define directive or other means including -Akey[=value] compiler arguments, build.properties files, and environment settings such as the Java source mode (see Symbols). Regardless of a symbol's origin or scope, the #undef can be used to undefine it, however its effects are limited to the scope of the containing file.

package com.example;
  
#define FOO
#undef FOO
 
public class MyClass {  
  public static void main(Stirng[] args) {  
#if FOO  
    System.out.println("FOO is defined");
#else
    System.out.println("FOO is not defined");  
#endif  
  }  
} 

#if

Code between #if and #endif directives is included for compilation based on the expression used with #if -- if the expression is true the code compiles. The expression always evaluates as either true or false. If the expression is a symbol, such as one defined with #define, the symbol evaluates to true if the symbol is accessible and has not been undefined with #undef.

This example uses #if to conditionally compile code based on whether JAVA_8 is defined:

#if JAVA_8
  out.println("Compiled with Java source version 8");
#endif

The full structure of an #if directive looks like this:

#if <expression>
<code>
#elif <expression>
<code>
#else
<code>
#endif

Details concerning #elif, #else, and #endif directives are covered in separate sections below.

You can use more than symbols with #if. Condition expressions can have operators && (and), || (or), and ! (not) to evaluate whether multiple symbols have been defined. You can also group symbols and operators with parentheses.

Expressions can also test for equality with == and !=. Two expressions are equal if:

  1. They are both undefined or
  2. They are both defined and their string values are the same

The string value of a symbol defined with #define is the empty string "". Symbols defined with build.properties files or -Akey[=value] command line arguments may have values assigned to them explicitly.

Note it is impossible for a symbol to have a null value. When referenced in an equality expression, if a symbol is not assigned a value, its value is the empty string "".

#elif

Use #elif to divide an #if directive into multiple conditions. The first true condition in the series of #if/#elif directives determines which of the directives executes:

public class MyClass {}
  @Override 
#if JAVA_8
  public void myJava8Method() {}
#elif JAVA_9
  public void myJava9Method() {}
#elif JAVA_10
  public void myJava10Method() {}
#else
  public void myJava11Method() {}
#endif  

Here if compiling with Java 10 source compatibility mode, only myJava10Method() will be compiled.

Note #elif is a more convenient and easier to read alternative to writing nested #if directives in #else:

#if FOO
  out.println("FOO");
#else
  #if BAR
  out.println("BAR");  
  #endif
#endif

It's easier on the eye to use #elif:

#if FOO
  out.println("FOO");
#elif BAR
  out.println("BAR");  
#endif

#else

If none of the conditions are true for #if and #elif directives, the code between #else and #endif is compiled:

#if DEV
  out.println("DEV mode");
#else
  out.println("Customer mode");
#endif  

#endif

The #endif directive marks the end of the series of directives beginning with #if. See the #if directive for more details and examples.

Note unlike conventional preprocessors, you can place more than one directive on the same line. Here the #if and #endif directives share the same line to conditionally implement an interface:

public class MyClass #if(JAVA_8) implements MyInterface #endif {
 ...
}

#error

Use the #error directive to generate a compiler error from a specific location in your code:

#if MODE_A
  out.println("MODE A");
#elif MODE_B
  out.println("MODE B");
#elif MODE_C
  out.println("MODE C");
#else
  #error "Expecting a MODE to be defined"
#endif

You can also generate a compiler warning with the #warning directive.

#warning

Use the #warning directive to generate a compiler warning from a specific location in your code:

#if MODE_A
  out.println("MODE A");
#elif MODE_B
  out.println("MODE B");
#else
  #warning "No MODE defined, defaulting to MODE_C"
#endif

You can also generate a compiler error with the #error directive.

Symbols

Similar to a variable in Java, a preprocessor symbol has a name and an optional value. There are four ways a symbol can be defined:

  1. Locally in the source file via #define
  2. Using a build.properties file in the directory ancestry beginning with the root source directory
  3. Using the -Akey[=value] option on the javac command line
  4. From compiler and JVM environment settings such as Java source version, JPMS mode, operating system, etc.

Symbol scoping rules model a hierarchy of maps, where symbols are accessed in leaf-first order where the leaf symbols are controlled by the #define and #undef directives in the compiling source file. Parent symbols correspond with 2 - 4 above.

Note the effects of #define and #undef are limited to the file scope. This means #define symbols are not available to other files. Similarly, parent symbols masked with #undef are unaffected in other files.

Note Manifold's preprocessor is designed exclusively for conditional compilation, you can't use #define for constant values or macro substitution as you can with a C/C++ preprocessor.

build.properties files

You can provide global symbols using build.properties files placed in the ancestry of directories beginning with a source root directory. Although a symbol defined as a property can have a string value, sometimes it is preferable to design property names to have the value encoded in the name.

Instead of this:

customer.type = ABC
customer.level = Ultimate

Do this:

CUSTOMER_TYPE_ABC =
CUSTUMER_LEVEL_ULTIMATE =

Symbols as compiler arguments

Similar to build.properties you can define symbols on the javac command line via the -Akey[=value] option. For example:

javac -Acustomer.level=Ultimate ...

or

javac -ACUSTOMER_LEVEL_ULTIMATE ...

Environment settings symbols

You get some symbols for free. These symbols come from compiler, JVM, and IDE settings. For instance, the Java source compatibility mode provided on the command line via -source or inherited from IDE settings translates to symbols having the following format:

JAVA_N
JAVA_N_OR_LATER

Where N is the source version obtained from the environment.

Symbols for the JPMS mode are defined as:

JPMS_NONE     // If compiling with Java 8, or Java 8 source compatibility
JPMS_UNNAMED  // If compiling with Java 9 or later and no module-info.java file is defined
JPMS_NAMED    // If compiling with Java 9 or later and a module-info.java file is defined

Symbols for the operating system on which javac is running:

OS_FREE_BSD
OS_LINUX
OS_MAC
OS_SOLARIS
OS_UNIX // Same as !OS_WINDOWS
OS_WINDOWS

The O/S architecture:

ARCH_32
ARCH_64

IDE Support

Manifold is best experienced using IntelliJ IDEA.

Install

Get the Manifold plugin for IntelliJ IDEA directly from IntelliJ via:

SettingsPluginsMarketplace ➜ search: Manifold

echo method

Sample Project

Experiment with the Manifold Sample Project via:

FileNewProject from Version ControlGit

echo method

Enter: https://github.com/manifold-systems/manifold-sample-project.git

echo method

Use the plugin to really boost your productivity. The plugin fully supports the Manifold Preprocessor. It provides an interactive mode in which you can see the effects of the directives and symbols you define and use in your code.

Building

Building this project

The manifold-preprocessor project is defined with Maven. To build it install Maven and run the following command.

mvn compile

Using this project

The manifold-preprocessor dependency works with all build tooling, including Maven and Gradle. It also works with Java versions 8 - 12.

Here are some sample build configurations references.

Note you can replace the manifold-preprocessor dependency with manifold-all as a quick way to gain access to all of Manifold's features.

Gradle

Java 8

Here is a sample build.gradle file using manifold-preprocessor with Java 8:

plugins {
    id 'java'
}

group 'com.example'
version '1.0-SNAPSHOT'

targetCompatibility = 1.8
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'systems.manifold', name: 'manifold-preprocessor', version: '2019.1.12'
}

tasks.withType(JavaCompile) {
    options.compilerArgs += '-Xplugin:Manifold'
    options.fork = true
}

Use with accompanying settings.gradle file:

rootProject.name = 'MyPreprocessorProject'

Java 11+

Here is a sample build.gradle file using manifold-preprocessor with Java 11:

plugins {
    id 'java'
}

group 'com.example'
version '1.0-SNAPSHOT'

targetCompatibility = 11
sourceCompatibility = 11

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'systems.manifold', name: 'manifold-preprocessor', version: '2019.1.12'

    // Add manifold-preprocessor to -processorpath for javac
    annotationProcessor group: 'systems.manifold', name: 'manifold-preprocessor', version: '2019.1.12'
}

tasks.withType(JavaCompile) {
    options.compilerArgs += '-Xplugin:Manifold'
    options.fork = true
}

Use with accompanying settings.gradle file:

rootProject.name = 'MyPreprocessorProject'

Maven

Java 8

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>my-preprocessor-app</artifactId>
    <version>0.1-SNAPSHOT</version>

    <name>My Preprocessor App</name>

    <properties>
        <!-- set latest manifold version here --> 
        <manifold.version>2019.1.12</manifold.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>systems.manifold</groupId>
            <artifactId>manifold-preprocessor</artifactId>
            <version>${manifold.version}</version>
        </dependency>
    </dependencies>

    <!--Add the -Xplugin:Manifold argument for the javac compiler-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                    <encoding>UTF-8</encoding>
                    <compilerArgs>
                        <!-- Configure manifold plugin-->
                        <arg>-Xplugin:Manifold</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Java 11+

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>my-preprocessor-app</artifactId>
    <version>0.1-SNAPSHOT</version>

    <name>My Preprocessor App</name>

    <properties>
        <!-- set latest manifold version here --> 
        <manifold.version>2019.1.12</manifold.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>systems.manifold</groupId>
            <artifactId>manifold-preprocessor</artifactId>
            <version>${manifold.version}</version>
        </dependency>
    </dependencies>

    <!--Add the -Xplugin:Manifold argument for the javac compiler-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                    <encoding>UTF-8</encoding>
                    <compilerArgs>
                        <!-- Configure manifold plugin -->
                        <arg>-Xplugin:Manifold</arg>
                    </compilerArgs>
                    <!-- Add the processor path for the plugin (required for Java 9+) -->
                    <annotationProcessorPaths>
                        <path>
                            <groupId>systems.manifold</groupId>
                            <artifactId>manifold-preprocessor</artifactId>
                            <version>${manifold.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

License

Open Source

Open source Manifold is free and licensed under the Apache 2.0 license.

Commercial

Commercial licenses for this work are available. These replace the above ASL 2.0 and offer limited warranties, support, maintenance, and commercial server integrations.

For more information, please visit: http://manifold.systems//licenses

Contact: admin@manifold.systems

Versioning

For the versions available, see the tags on this repository.

Author

You can’t perform that action at this time.