Skip to content
Branch: master
Find file History
Permalink
Type Name Latest commit message Commit time
..
Failed to load latest commit information.
src/main Manifold project-wide changes: Aug 12, 2019
README.md amend gradle build docs Aug 15, 2019
pom.xml bump version Aug 12, 2019

README.md

Manifold : Java Extensions

Table of Contents

Extension Classes via @Extension

Similar to other languages such as C#, Kotlin, and Gosu, with Manifold you can define methods and other features as logical extensions to existing Java classes. This is achieved using extension classes. An extension class is a normal Java class you define as a container for features you want to apply to another class, normally to one you can't modify directly, such as java.lang.String:

package extensions.java.lang.String;

import manifold.ext.api.*;

@Extension
public class MyStringExtension {

  public static void print(@This String thiz) {
    System.out.println(thiz);
  }

  @Extension
  public static String lineSeparator() {
    return System.lineSeparator();
  }
}

All extension classes must be sub-rooted in the extensions package where the remainder of the package must be the qualified name of the extended class. As the example illustrates, an extension class on java.lang.String must reside directly in a package equal to or ending with extensions.java.lang.String. Note this convention facilitates the extension discovery process and avoids the overhead and ceremony of alternative means such as annotation processors.

In Java 9 because a package must reside in a single module, you should prepend your module name to the extension package name to avoid illegal sharing of packages between modules. For example, if your module were named foo.widget you should define your extension class as foo.widget.extensions.java.lang.String. In Java 8 all extension classes can be directly rooted in the extensions package, however it is still best to qualify extension classes with your project or module name to prevent naming collisions.

Additionally, an extension class must be annotated with manifold.ext.api.Extension, which distinguishes extension classes from other classes that may reside in the same package.

Extension Method Basics

An extension method must be declared static and non-private. As the receiver of the call, the first parameter of an extension instance method must have the same type as the extended class. The MyStringExtension example illustrates this; the first parameter of instance method print is java.lang.String. Note the parameter name thiz is conventional, you can use any name you like. Finally, the receiver parameter must be annotated with manifold.ext.api.This to distinguish it from regular methods in the class.

That's all there is to it. You can use extensions just like normal methods on the extended class:

String name = "Manifold";
name.print();

You can define static extension methods too. Since static methods don't have a receiver, the method itself must be annotated with manifold.ext.api.Extension:

@Extension
public static String lineSeparator() {
  return System.lineSeparator();
}

Call static extensions just as if they were on the extended class:

String.lineSeparator()

Generics

You can extend generic classes too and define generic methods. This is how Manifold extension libraries work with collections and other generic classes. For example, here is the first() extension method on Iterable:

public static <T> T first(@This Iterable<T> thiz, Predicate<T> predicate) {
  for (T element: thiz) {
    if (predicate.test(element)) {
      return element;
    }
  }
  throw new NoSuchElementException();
}

Notice the extension is a generic method with the same type variable designation as the extended class: T from Iterable<T>. Since extension methods are static this is how we convey type variables from the extended class to the extension method. Note type variable names must match the extended type's type variables and must be declared in the same order.

To define a generic extension method you append the type variables of the method to the list of the extended class' type variables. Manifold's map() extension illustrates this format:

public static <E, R> Stream<R> map(@This Collection<E> thiz, Function<? super E, R> mapper) {
  return thiz.stream().map(mapper);
}

Here map is a generic extension method having type variable R and conveying Collection's type variable E.

Static Dispatching

An extension class does not physically alter its extended class; the methods defined in an extension are not really inserted into the extended class. Instead the Java compiler and Manifold cooperate to make a call to a static method in an extension look like a call to an instance method on the extended class. As a consequence extension calls dispatch statically.

So unlike a virtual method call an extension call is always made based on the extended type declared in the extension, not the runtime type of the left hand side of the call. To illustrate:

public class Tree {
}

public class Dogwood extends Tree {
}

public static void bark(@This Tree thiz) {
  println("rough");
}
public static void bark(@This Dogwood thiz) {
  println("ruff");
}

Tree tree = new Dogwood();
tree.bark(); // "rough"

At compile-time tree is of type Tree, therefore it transforms to a static invocation of bark(Tree), which prints "rough".

Another consequence of static dispatching is that an extension method can receive a call even if the value of the extended object is null at the call site. Manifold extension libraries exploit this feature to improve readability and null-safety. For example, CharSequence.isNullOrEmpty() compares the receiver's value to null so you don't have to:

public static boolean isNullOrEmpty(@This CharSequence thiz) {
  return thiz == null || thiz.length() == 0;
}

String name = null;
if (name.isNullOrEmpty()) {
  println("empty");
}

Here the example doesn't check for null and instead shifts the burden to the extension.

Accessibility and Scope

An extension method never shadows or overrides a class method; when an extension method's name and parameters match a class method, the class method always has precedence over the extension. For example:

public class Tree {
  public void kind() {
    println("evergreen");
  }
}

public static void kind(@This Tree thiz) {
  println("binary");
}

The extension method never wins, a call to kind() always prints "evergreen". Additionally, if at compile-time Tree and the extension conflict as in the example, the compiler warns of the conflict in the extension class.

An extension method can still overload a class method where the method names are the same, but the parameter types are different:

public class Tree {
  public void harvest() {
    println("nuts");
  }
}

public static void harvest(@This Tree thiz, boolean all) {
  println(all ? "wood" : thiz.harvest());
}

A call to tree.harvest(true) prints "wood".

Since extension method references resolve at compile-time, you can limit the compile-time accessibility of an extension class simply by limiting the scope of the JAR file containing the extension. For example, if you're using Maven the scope of an extension matches the dependency relationship you assign in your pom.xml file. Similarly in module-aware IDEs such as IntelliJ IDEA, an extension's scope is the same as the module's.

Annotation Extensions

In addition to adding new methods, extension classes can also add annotations to a class. At present annotation extensions are limited to the extended class; you can't yet add annotations to members of the class.

Beware, extensions are limited to a compile-time existence. Therefore, even if an annotation has RUNTIME retention, it will only be present on the extended class at compile-time. This feature is most useful when using annotation processors and you need to annotate classes you otherwise can't modify.

Also it's worth pointing out you can make existing interfaces structural using annotation extensions:

package extensions.abc.Widget;
@Extension
@Structural // makes the interface structural
public class MyWidgetExtension {
}

This extension effectively changes the abc.Widget nominal interface to a structural interface. In the context of your project classes no longer have to declare they implement it nominally.

See Structural Interfaces later in this guide for fuller coverage of the topic.

Extension Interfaces

An extension class can add structural interfaces to its extended class. This feature helps with a variety of use-cases. For example, let's say we have a class Foo and interface Hello:

public final class Foo {
  public String sayHello() {
    return "hello";      
  }
}

@Structural
public interface Hello {
  String sayHello();
}

Although Foo does not implement Hello nominally, it defines the sayHello() method that otherwise satisfies the interface. Let's assume we don't control Foo's implementation, but we need it to implement Hello nominally. We can do that with an extension interface:

@Extension
public class MyFooExtension implements Hello {
}

Now the compiler believes Foo directly implements Hello:

Hello hello = new Foo();
hello.sayHello();

Note Hello is structural, so even without the extension interface, instances of Foo are still compatible with Hello. It's less convenient, though, because you have to explicitly cast Foo to Hello -- a purely structural relationship in Manifold requires a cast. Basically extension interfaces save you from casting. This not only improves readability, it also prevents confusion in cases involving type inference where it may not be obvious that casting is necessary.

It's worth pointing out you can both add an interface and implement its methods by extension:

public final class Shy {
}

@Extension
public abstract class MyShyExtension implements Hello {
  public static String sayHello(@This Shy thiz) {
    return "hi";    
  }
}

This example extends Shy to nominally implement Hello and provides the Hello implementation. Note the abstract modifier on the extension class. This is necessary because it doesn't really implement the interface, the extended class does.

You can also use extension interfaces to extract interfaces from classes you don't control. For example, if you want to provide an immutable view of a collection class such as java.util.List, you could use extension interfaces to extract immutable and mutable interfaces from the class. As such your code is better suited to confine List operations on otherwise fully mutable lists.

Extension Libraries

An extension library is a logical grouping of functionality defined by a set of extension classes. Manifold includes several extension libraries for commonly used classes, many of which are adapted from Kotlin extensions. Each library is available as a separate module or Jar file you can add to your project separately depending on its needs.

  • Collections

    Defined in module manifold-collections this library extends:

    • java.lang.Iterable
    • java.util.Collection
    • java.util.List
    • java.util.stream.Stream
  • Text

    Defined in module manifold-text this library extends:

    • java.lang.CharSequence
    • java.lang.String
  • I/O

    Defined in module manifold-io this library extends:

    • java.io.BufferedReader
    • java.io.File
    • java.io.InputStream
    • java.io.OutputStream
    • java.io.Reader
    • java.io.Writer
  • Web/JSON/YAML

    Defined in module manifold-json this library extends:

    • java.net.URL
    • javax.script.Bindings

🛈 IMPORTANT!
You can create your own custom extension libraries. There's nothing special about a "library", it's just a normal dependency in a project. However for manifold to recognize extensions, as a performance measure, the library must declare it has extensions to process. Do that using the Contains-Sources manifest entry.

With Maven use the maven-jar-plugin to add the Contains-Sources manifest entry to your Jar file:

<build>
 <plugins>
   <plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-jar-plugin</artifactId>
     <configuration>
       <archive>
         <manifestEntries>
           <!--class files as source must be available for extension method classes-->
           <Contains-Sources>java,class</Contains-Sources>
         </manifestEntries>
       </archive>
     </configuration>
   </plugin>
 </plugins>
</build>

Similarly with Gradle add the Contains-Sources manifest attribute:

jar {
 manifest {
   attributes('Contains-Sources':'java,class')
 }
}

Generating Extension Classes

Sometimes the contents of an extension class reflect metadata from other resources. In this case rather than painstakingly writing such classes by hand it's easier and less error prone to produce them via type manifold. To facilitate this use-case, your type manifold must implement the IExtensionClassProducer interface so that the ExtensionManifold can discover information about the classes your type manifold produces. For the typical use case your type manifold should extend AbstractExtensionProducer.

See the manifold-ext-producer-sample module for a sample type manifold implementing IExtensionClassProvider.

Structural Interfaces via @Structural

Java is a nominally typed language -- types are assignable based on the names declared in their definitions. For example:

public class Foo {
  public void hello() {
    println("hello");
  }
}

public interface Greeting {
  void hello();
}

Greeting foo = new Foo(); // error

This does not compile because Foo does not explicitly implement Greeting by name in its implements clause.

By contrast a structurally typed language has no problem with this example. Basically, structural typing requires only that a class implement interface methods, there is no need for a class to declare that it implements an interface.

Although nominal typing is perhaps more sound and easier for both people and machines to digest, in some circumstances the flexibility of structural typing makes it more suitable. Take the following classes:

public class Rectangle {
  public double getX();
  public double getY();
  ...
}

public class Point {
  public double getX();
  public double getY();
  ...
}

public class Component {
  public int getX();
  public int getY();
  ...
}

Let's say we're tasked with sorting instances of these according to location in the coordinate plane, say as a Comparator implementation. Each class defines methods for obtaining X, Y coordinates, but these classes don't implement a common interface. We don't control the implementation of the classes, so we're faced with having to write three distinct, yet nearly identical, Comparators.

This is where the flexibility of structural interfaces could really help. If Java supported it, we'd declare a structural interface with getX() and getY() methods and write only one Comparator:

public interface Coordinate {
  double getX();
  double getY();
}

Comparator<Coordinate> coordSorter = new Comparator<>() {
  public int compare(Coordinate c1, Coordinate c2) {
    int res = c1.Y == c2.Y ? c1.X - c2.X : c2.Y - c1.Y;
    return res < 0 ? -1 : res > 0 ? 1 : 0;
  }
}

List<Point> points = Arrays.asList(new Point(2, 1), new Point(3, 5), new Point(1, 1));
Collections.sort(points, coordSorter); // error

Of course Java is not happy with this because because Point does not nominally implement Coordinate.

This is where Manifold can help with structural interfaces:

@Structural
public interface Coordinate {
  double getX();
  double getY();
}

Adding @Structural to Coordinate effectively changes it to behave structurally -- Java no longer requires classes to implement it by name, only its methods must be implemented.

Note a class can still implement a structural interface nominally. Doing so helps both people and tooling comprehend your code faster. The general idea is to use an interface structurally when you otherwise can't use it nominally or doing so overcomplicates your code.

Type Assignability and Variance

A type is assignable to a structural interface if it provides compatible versions of all the methods declared in the interface. The use of the term compatible here instead of identical is deliberate. The looser term concerns the notion that a structural interface method is variant with respect to the types in its signature:

@Structural
public interface Capitalizer {
  CharSequence capitalize(String s);
}

public static class MyCapitalizer {
  public String capitalize(CharSequence s) {
    return s.isNullOrEmpty() ? "" : Character.toUpperCase(s.charAt(0)) + s.substring(1);
  }
}

At first glance it looks like MyCapitalizer does not satisfy the structure of Capitalizer, neither the parameter type nor the return type of the method match the interface. After careful inspection, however, it is clear the methods are call-compatible from the perspective of Capitalizer:

Capitalizer cap = (Capitalizer) new MyCapitalizer();
CharSequence properName = cap.capitalize("tigers");

MyCapitalizer's method can be called with Capitalizer's String parameter because MyCapitalizer's CharSequence parameter is assignable from String -- contravariant parameter types support call-compatibility. Similarly we can accept MyCapitalizer's String return type because it is assignable to Capitalizer's CharSequence return type -- covariant return types support call-compatibility. Therefore, even though their method signatures aren't identical, MyCapitalizer is structurally assignable to Capitalizer because it is safe to use in terms of Capitalizer's methods.

Signature variance also supports primitive types. You may have spotted this in the Component class referenced earlier in the Coordinate example where Component.getX() returns int, not double as declared in Coordinate.getX(). Because int coerces to double with no loss of precision the method is call-compatible. As a result signature variance holds for primitive types as well as reference types.

Implementation by Field

Another example where classes have wiggle room implementing structural interfaces involves property getter and setter methods, a.k.a. accessors and mutators. Essentially, a property represents a value you can access and/or change. Since a field is basically the same thing a class can implement a getter and/or a setter with a field:

@Structural
public interface Name {
  String getName();
  void setName(String name);
}

public class Person {
  public String name;
}

Name person = (Name) new Person();
person.setName("Bubba");
String name = person.getName();

Basically a field implements a property method if its name matches the method's name minus the is/get/set prefixes and taking into account field naming conventions. For example, fields Name, name, and _name all match the getName() property method and are weighted in that order.

Implementation by Extension

It's possible to implement methods of a structural interface via extension methods. Looking back at the Coordinate example, consider this class:

public class Vector {
  private double _magnitude;
  private double _direction;
  
  public Vector(double magnitude, double direction) {
    _magnitude = magnitude;
    _direction = direction;
  }
  
  // Does not have X, Y coordinate methods  :(
}

In physics a vector and a coordinate are different ways of expressing the same thing; they can be converted from one to another. So it follows the coordSorter example can sort Vector instances in terms of X, Y Coordinates... if Vector supplied getX() and getY() methods, which it does not.

What if an extension class supplied the methods?

@Extension
public class MyVectorExtension {
  public double getX(@This Vector thiz) {
    return thiz.getMagnitude() * Math.cos(thiz.getDirection()); 
  }
  public double getY(@This Vector thiz) {
    return thiz.getMagnitude() * Math.sin(thiz.getDirection()); 
  }
}

Voila! Vector now structurally implements Coordinate and can be used with coordSorter.

Generally implementation by extension is a powerful technique to provide a common API for classes your project does not control.

Nevertheless if you'd rather not add extension methods to Vector, or the extension class strategy is unsuitable for your use-case e.g., the Comparable<T> interface sometimes makes this impossible, you can instead go a more direct route and implement your own proxy factory...

Implementation by Proxy

You can provide your own proxies the compiler can use to delegate structural calls. This is especially useful to avoid the one-time runtime overhead of the first call through a structural interface. Consider the Coordinate structural interface earlier.

Coordinate coord = (Coordinate) new Point(4,5);
coord.getX();

The first time Point is called through a Coordinate Manifold dynamically generates and compiles a proxy for Point as a Coordinate. Most of the time this does not matter -- avoid premature optimization! -- but when it does matter the delay can be a problem. To address that you can provide your own proxy ahead of time via the IProxyFactory service.

public class Point_To_Coordinate implements IProxyFactory<Point, Coordinate> {
  @Override
  public Coordinate proxy(Point pt, Class<Coordinate> cls) {
    return new Proxy(pt);
  }

  public static class Proxy implements Coordinate
  {
    private final Point _delegate;

    public Proxy(Point pt) {
      _delegate = tp;
    }

    public double getX() {
      return _delegate.getX();
    }

    public double getY() {
      return _delegate.getY();
    }
  }
}

The compiler uses this proxy factory to make Point calls through Coordinate, which vastly improves the first time call performance since it saves Manifold from dynamically generating and compiling a similar class.

Your proxy factory must be registered as a service in META-INF directly like so:

src
-- main
---- resources
------ META-INF
-------- services
---------- manifold.ext.api.IProxyFactory

Following standard Java ServiceLoader protocol you create a text file called manifold.ext.api.IProxyFactory in the service directory under your META-INF directory. The file should contain the fully qualified name of your proxy factory class (the one that implements IProxyFactory) followed by a new blank line:

com.abc.Point_To_Coordinate

Using factoryClass

If you are the declarer of the structural interface, you can skip the Java service and specify a proxy factory directly in the @Structural call site:

@Structural(factoryClass = Point_To_Coordinate.class)
public interface Coordinate {
  ...
}

Manifold inspects the facotryClass to see whether or not it is appropriate for a given proxy. For instance, from the super class declaration IProxyFactory<Point, Coordinate> Manifold determines Point_To_Coordinate is exclusively a proxy factory for Point, other classes go through the default dynamic proxy generation/compilation.

Dynamic Typing with ICallHandler

Manifold supports a form of dynamic typing via manifold.ext.api.ICallHandler:

public interface ICallHandler {
  /**
   * A value resulting from #call() indicating the call could not be dispatched.
   */
  Object UNHANDLED = new Object() {
    @Override
    public String toString() {
      return "Unhandled";
    }
  };

  /**
   * Dispatch a call to an interface method.
   *
   * @param iface The extended interface and owner of the method
   * @param name The name of the method
   * @param returnType The return type of the method
   * @param paramTypes The parameter types of the method
   * @param args The arguments from the call site
   * @return The result of the method call or UNHANDLED if the method is not dispatched.  
   *   Null if the method's return type is void.
   */
  Object call(Class iface, String name, Class returnType, Class[] paramTypes, Object[] args);
}

A class can implement ICallHandler nominally or it can be made to implement it via extension class. Either way instances of the class can be cast to any structural interface where structural calls dispatch to ICallHandler.call(). The class' implementation of call() can delegate the call any way it chooses.

For instance, via class extension Manifold provides ICallHandler support for java.util.Map so that getter and setter calls work directly with values in the map:

Map<String,Object> map = new HashMap<>();
Name person = (Name) map;
person.setName("Manifold");
println(person.getName());

Because Map is a ICallHandler instances of it can be cast to any structural interface, such as Name from the earlier example. The ICallHandler implementation transforms get/set property calls to get/put calls into the map using the name of the property in the method. Additionally, method calls can be made on map entries where the entry key matches the name of the method and the value is an instance of a functional interface matching the signature of the call:

map.put( "run", (Runnable)()-> println("hello") );
Runnable runner = (Runnable) map;
runner.run();

This example prints "hello" because Map.call() dispatches the call to the "run" entry having a Runnable functional interface value.

Note the similarity of this functionality on Map with expando types in dynamic languages. The main difference is that invocations must be made through structural interfaces and not directly on the map, otherwise Map behaves much like an expando object.

See manifold.collections.extensions.java.util.Map.MapStructExt.java for details.

Type-safe Reflection via @Jailbreak

Sometimes you have to use Java reflection to access fields, methods, and types that are not directly accessible from your code. But writing reflection code is not only tedious and error-prone, it also loses type-safety in the process. Manifold mitigates these issues with the @Jailbreak annotation and the jailbreak() extension method. Use them to leverage the convenience and type-safety of the Java compiler and let Manifold generate reliable, efficient reflection code for you.

Using @Jailbreak

Annotate the type of any variable with @Jailbreak to gain direct, type-safe access to private fields, methods, and types.

Note, @Jailbreak is ideal for use within tests. It saves you from losing type-safety that is otherwise the case with reflection code and it enables you to maintain private methods and fields.

Basic Use

@Jailbreak Foo foo = new Foo(1);
foo.privateMethod();
foo.privateMethod("hey");
foo._privateField = 88;
public class Foo {
  private final int _privateField;
  
  public Foo(int value) {
    _privateField = value;
  }
  
  private String privateMethod() {
    return "hi";
  }
  
  private String privateMethod(String param) {
    return param;
  }
}

Use With Static Members

Since Java does not permit you to annotate the type in a static expression, you must use an instance:

@Jailbreak MyClass myClass = null; // value is insignificant
myClass.staticMethod();
myClass.Static_Field = "hi";
public class MyClass {
  private static String Static_Field = "hello";
  
  private static void staticMethod() {
  }
}

Use With Types and Constructors

Use @Jailbreak to access hidden types and constructors:

com.abc. @Jailbreak SecretClass secretClass = 
  new com.abc. @Jailbreak SecretClass("hi");
secretClass._data = "hey";
package com.abc;
// not public
class SecretClass {
  private final String _data;

  // not public
  SecretClass(String data){
    _data = data;
  }
}

Break JPMS Barriers

Access fields, methods, and constructors from packages otherwise prohibited for use in your module by the JPMS:

jdk.internal.misc. @Jailbreak VM vm = null;
String[] args = vm.getRuntimeArguments();

Using the jailbreak() Extension

Similar to @Jailbreak you can call the jailbreak() extension method from any expression to gain type-safe access to private fields, methods, and types.

Foo foo = new Foo();
foo.jailbreak().privateMethodOnFoo();

This method is especially handy when you have a chain of member access expressions and you want to quickly use inaccessible members:

something.foo().jailbreak().bar.jailbreak().baz = value;

The Self Type via @Self

The Self type is a common term used in the language community to mean "the subtype of this" and is most useful in situations where you want a return type or parameter type of a method in a base type to reflect the subtype i.e., the invoking type. For instance, consider the equals() method. We all know it suffers from Java's lack of a Self type:

public class MyClass {
  @Override
  public boolean equals(Object obj) { // why Object?!
    ...
  }
}

MyClass myClass = new MyClass();
myClass.equals("nope"); // this compiles! :(

What we want is to somehow override equals() to enforce MyClass symmetry:

public boolean equals(MyClass obj) {
  ...
}

But Java does not support covariance in parameter types, and for good reason. It would break if we called it like this:

((Object)myClass).equals("notMyClass"); // BOOM! String is not assignable to MyClass

Manifold's @Self type provides an elegant solution:

public boolean equals(@Self Object obj) {
  ...
}

Now we have the behavior we want:

MyClass myClass = new MyClass();
myClass.equals("notMyClass"); // Compile Error. YES!!!

Note although Java does not provide a Self type, it does provide some of its capabilities with recursive generic types. But this feature can be difficult to understand and use, and the syntax it imposes is often unsuitable for class hierarchies and APIs. Additionally, it is ineffective for cases like equals() -- it requires that we change the base class definition! E.g., public class Object<T extends Object<T>>... oy!

But as you'll see Manifold's @Self annotation altogether removes the need for recursive generics. It provides Java with a simpler, more versatile alternative. Use it on method return types, parameter types, and field types to enforce "the subtype of this" where suitable.

Builders

A common use-case for the Self type involves fluent APIs like the Builder pattern:

public class VehicleBuilder {
  private int _wheels;
  
  public VehicleBuilder withWheels(int wheels) {
    _wheels = wheels;
    return this; // returns THIS
  }
}

This is fine until we subclass it:

public class AirplaneBuilder extends VehicleBuilder {
  private int _wings;
  
  public AirplaneBuilder withWings(int wings) {
    _wings = wings;
    return this; // returns THIS
  }
}

...

Airplane airplane = new AirplaneBuilder()
  .withWheels(3) // returns VehicleBuilder :(
  .withWings(1)  // ERROR

withWheels() returns VehicleBuilder, not AirplaneBuilder. This is a classic example where we want to return the "the subtype of this". This is what the self type accomplishes:

  public @Self VehicleBuilder withWheels(int wheels) {
    _wheels = wheels;
    return this; // returns THIS
  }

Now with the return type annotated with @Self the example works as desired:

Airplane airplane = new AirplaneBuilder()
  .withWheels(2) // returns AirplaneBuilder :)
  .withWings(1)  // GOOD!

Annotate with @Self to preserve the "the subtype of this" anywhere on or in a method return type, parameter type, or field type.

Self + Generics

You can also use @Self to annotate a type argument. A nice example of this involves a typical graph or tree structure where the nodes in the structure are homogeneous:

public class Node {
  private List<Node> children;
  
  public List<@Self Node> getChildren() {
    return children;
  }

  public void addChild(@Self Node child) {
    children.add(child);
  }
}

public class MyNode extends Node {
  ...
}

Here you can make the component type of List the Self type so you can use the getChildren method type-safely from subtypes of node:

MyNode myNode = findMyNode();
List<MyNode> = myNode.getChildren(); // wunderbar! 

Self + Extensions

You can use @Self with extension methods too. Here we make an extension method as a means to conveniently chain additions to Map while preserving its concrete type:

public static <K,V> @Self Map<K,V> add(@This Map<K,V> thiz, K key, V value) {
  thiz.put(key, value);
  return thiz;
}

HashMap<String, String> map = new HashMap<>()
  .add("nick", "grouper")
  .add("miles", "amberjack");
  .add("alec", "barracuda")

Overriding Methods

Using @Self in a method return type or parameter type has no effect on the method's override characteristics or binary signature:

public class SinglyNode {
  private @Self SinglyNode next;
  
  public void setNext(@Self SinglyNode next) {
    this.next = next;
  }
}

public class DoublyNode extends SinglyNode {
  private @Self DoublyNode prev;

  public void setNext(@Self SinglyNode next) {
    if(next instanceof DoublyNode) {
      super.setNext(next);
      next.prev = this;
    }
    else {
      throw new IllegalArgumentException();
    }
  }
}

Of particular interest is the @Self SinglyNode parameter in the DoublyNode override. As mentioned earlier Java does not permit covariant parameter overrides -- we can't override the method using a DoublyNode parameter type. To make up for this @Self informs the compiler that the parameter is indeed a DoublyNode when invoked from a DoublyNode:

doublyNode.setNext(singlyNode); // Compile Error :)
doublyNode.setNext(doublyNode); // OK :)

This is precisely the arrangement we want. Type-safety is enforced at the call site. But, equally important, the subclass handles the parameter as a base class. Why? Because this:

((SinglyNode)doublyNode).setNext(singlyNode);

Here setNext(), although invoked as a SinglyNode, dispatches to the DoublyNode override. Thus the SinglyNode parameter type enforces that a SinglyNode cannot be mistaken for DoublyNode, hence the necessity of the instanceof check in setNext().

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. Use code completion to conveniently access extension methods. Create extension methods using a convenient user interface. Make changes to your extensions and use the changes immediately, no compilation! Use extensions provided by extension library dependencies. Find usages of any extension. Use structural interfaces, @Jailbreak, @Self, etc. Perform rename refactors to quickly and safely make project-wide changes.

Building

Building this project

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

mvn compile

Using this project

The manifold-ext 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-ext 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-ext 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-ext', version: '2019.1.11'

    // tools.jar dependency (for Java 8 only), primarily to support structural typing without static proxies.
    // Thus if you are not using structural typing, you **don't** need tools.jar
    compile files("${System.properties['java.home']}/../lib/tools.jar")
}

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

Use with accompanying settings.gradle file:

rootProject.name = 'MyJavaExtensionsProject'

Java 11+

Here is a sample build.gradle file using manifold-ext 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-ext', version: '2019.1.11'

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

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

Use with accompanying settings.gradle file:

rootProject.name = 'MyJavaExtensionsProject'

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-ext-app</artifactId>
    <version>0.1-SNAPSHOT</version>

    <name>My Java Extension App</name>

    <properties>
        <!-- set latest manifold version here --> 
        <manifold.version>2019.1.11</manifold.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>systems.manifold</groupId>
            <artifactId>manifold-ext</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>

    <profiles>
        <!-- tools.jar dependency (for Java 8 only), primarily to support structural typing without static proxies.
             Thus if you are not using structural typing, you **don't** need tools.jar -->
        <profile>
            <id>internal.tools-jar</id>
            <activation>
                <file>
                    <exists>\${java.home}/../lib/tools.jar</exists>
                </file>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>com.sun</groupId>
                    <artifactId>tools</artifactId>
                    <version>1.8.0</version>
                    <scope>system</scope>
                    <systemPath>\${java.home}/../lib/tools.jar</systemPath>
                </dependency>
              </dependencies>
        </profile>
    </profiles>
</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-ext-app</artifactId>
    <version>0.1-SNAPSHOT</version>

    <name>My Java Extension App</name>

    <properties>
        <!-- set latest manifold version here --> 
        <manifold.version>2019.1.11</manifold.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>systems.manifold</groupId>
            <artifactId>manifold-ext</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-ext</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.