12

I'm trying to implement a class that isolates a part of a object, and replaces the part with with something else. The part of the object may not have the same type as the object itself.

A simple example would be a class that takes the string "--12--", isolates the ascii number, and replaces it with the next natural number. So, the whole sequence would be "--12--" -> "12" -> 12 -> 13 -> "13" -> "--13--".

With this in mind, I implemented the following:

public abstract class Replacer<Outer, Inner>
    {
    protected abstract Inner decompose(Outer something);
    protected abstract Outer compose(Inner something);
    protected abstract Inner inner_replace(Inner something);
    public Outer replace(Outer something)
        {
        Inner s = decompose(something);
        s = inner_replace(s);
        return compose(s);
        }
    }

Now, I want to be able to compose a series of Replacers - stack them, so that each one computes its inner_replace by using the "lower" Replacer:

public abstract class ComposableReplacer<Outer, Inner> extends Replacer<Outer, Inner>
    {
    protected Replacer<Inner, ?> child;

    @Override
    public Outer replace(Outer something)
        {
        Inner s = decompose(something);
        s = inner_replace(s);
        if (child!=null)
            s= child.replace(s);
        return compose(s);
        }

    }

So, far, this works correctly, but now I'm trying to write a convenience method to take a couple of ComposableReplacers and stack them automatically:

public static <I, O> ComposableReplacer<I, O> compose(ComposableReplacer<?, ?>... rs)
    {
    for (int i=0; i<rs.length-1; i++)
        rs[i].child= rs[i+1];
    return rs[0];
    }

This fails, since each ComposableReplacer's inner type must be the outer type of its child and the compiler can't infer that from a array of ComposableReplacer<?, ?>.

Is it possible to do this in java (and still have type safety)?

EDIT To be clear, the problem is declaring a method that takes an array of ComposableReplacer and stacks/chains them, with type safety.

loopbackbee
  • 21,962
  • 10
  • 62
  • 97
  • 1
    Use a Decorator Design Pattern to chain Replacer implementations: http://en.wikipedia.org/wiki/Decorator_design_pattern – Boris Pavlović Nov 08 '13 at 12:08
  • 2
    You won't be able to do that, each `Replacer` should either take a method in the constructor and maybe have an `and` method - this way you can type safely deduce return types. Something like a Chain of Responsibility pattern ought to work. – Boris the Spider Nov 08 '13 at 12:11
  • @BorisPavlović I tried a decorator pattern, but the same problem arises - there's no way to tell the compiler the types in the array of `ComposableReplacer`: [here's a link to the implementation](http://pastebin.com/ReRSwTuA) – loopbackbee Nov 08 '13 at 13:57
  • 1
    @BoristheSpider I'm not sure what you mean by "taking a method in the constructor". A Chain of Responsibility works well because all the objects have the same type, which is not the case here, since generics are used. A "and" method is definitely feasible, but there's still no way (AFAICT) to make an method take an **array** of `ComposableReplacer` and chain them. – loopbackbee Nov 08 '13 at 13:59
  • I don't think Java is going to let you do that in a general case. I think you'll have to use a Builder Pattern. – Ian McLaird Nov 08 '13 at 14:38

3 Answers3

3

Even if there was support for generic arrays your code would fail due to a logical error. An array consists of elements of the same type but what you want to do does not work with items of the same type. This becomes clear if you try to implement your method with just two parameters instead of varargs:

// won’t work
public static <I, O> ComposableReplacer<I, O> compose(
  ComposableReplacer<I, O> rs1, ComposableReplacer<I, O> rs2) {
  rs1.child=rs2;
  return rs1;
}

This code still doesn’t compile as rs1.child requires a ComposableReplacer<O,?> instead of a ComposableReplacer<I,O> if you fix this, your method becomes

public static <I, O> ComposableReplacer<I, O> compose(
  ComposableReplacer<I, O> rs1, ComposableReplacer<O,?> rs2) {
  rs1.child=rs2;
  return rs1;
}

Now it works, but the two parameters have a different type. If Java had type-safe arrays they had to prevent containing a ComposableReplacer<I, O> and a ComposableReplacer<O,?> at the same time. (Unless you enforce O and I to be the same.)

To illustrate it further, here is the method for three parameter:

public static <I, O, X> ComposableReplacer<I, O> compose(
  ComposableReplacer<I, O> rs1, ComposableReplacer<O,X> rs2,
  ComposableReplacer<X, ?> rs3) {
  rs1.child=rs2;
  rs2.child=rs3;
  return rs1;
}

Here you see that every argument has a different type and you need an additional type parameter, so using a “type safe array” (read a java.util.List) can’t provide that. The simplest solution is to keep a two argument method and let the caller invoke it multiple times. Or n-arg if you know that the use case of n args will be required very often.

Holger
  • 285,553
  • 42
  • 434
  • 765
0

I would do somethign like that to achieve the objective

Composition can be done :

  • if Inner and Outer are different : passing child replacer to parent replacer
  • if Inner and Outer are equal :

    1. ssing child replacer to parent replacer,
    2. using the compose static method which sets child as required.

.

abstract class Replacer<Outer, Inner> {

    private Replacer<Inner, ?> child;

    protected abstract Inner decompose(Outer something);

    protected abstract Outer compose(Inner something);

    protected abstract Inner inner_replace(Inner something);

    public Replacer(Replacer<Inner, ?> child) {
        this.child = child;
    }

    public Outer replace(Outer something) {
        Inner s = decompose(something);
        s = inner_replace(s);
        if (child != null)
            s = child.replace(s);
        return compose(s);
    }

    public void setChild(Replacer<Inner, ?> child) {
        this.child = child;
    }

    @SafeVarargs
    public static <T> Replacer<T, T> compose(Replacer<T, T>... replacers) {
        if (replacers.length == 0)
            return new DummyReplacer<>();
        else {
            Replacer<T, T> current = replacers[0];
            for (int i = 1; i < replacers.length; ++i) {
                current.setChild(replacers[i]);
                current = replacers[i];
            }
            return replacers[0];
        }
    }
}

class DummyReplacer<Outer> extends Replacer<Outer, Outer> {

    public DummyReplacer(Replacer<Outer, ?> child) {
        super(child);
    }

    public DummyReplacer() {
        super(null);
    }

    @Override
    protected Outer decompose(Outer something) {
        return something;
    }

    @Override
    protected Outer compose(Outer something) {
        return something;
    }

    @Override
    protected Outer inner_replace(Outer something) {
        return something;
    }

}

class Multiply extends Replacer<Integer, Integer> {

    private int factor;

    public Multiply(int factor, Replacer<Integer, ?> child) {
        super(child);
        this.factor = factor;
    }

    public Multiply(int factor) {
        super(null);
        this.factor = factor;
    }

    @Override
    protected Integer decompose(Integer something) {
        return something;
    }

    @Override
    protected Integer compose(Integer something) {
        return something;
    }

    @Override
    protected Integer inner_replace(Integer something) {
        return something * factor;
    }

}

class Negate extends Replacer<String, Integer> {
    public Negate(Replacer<Integer, ?> child) {
        super(child);
    }

    public Negate() {
        super(null);
    }

    @Override
    protected Integer inner_replace(Integer something) {
        return -something;
    }

    @Override
    protected Integer decompose(String something) {
        return Integer.parseInt(something);
    }

    @Override
    protected String compose(Integer something) {
        return something.toString();
    }
}

class SharpToTildeExtract extends Replacer<String, String> {
    public SharpToTildeExtract(Replacer<String, ?> child) {
        super(child);
    }

    public SharpToTildeExtract() {
        super(null);
    }

    @Override
    protected String decompose(String something) {
        return something.substring(1, something.length() - 1);
    }

    @Override
    protected String compose(String something) {
        return "~" + something + "~";
    }

    @Override
    protected String inner_replace(String something) {
        return something;
    }
}

class UpperCaseReplacer extends Replacer<String, String> {

    public UpperCaseReplacer(Replacer<String, ?> child) {
        super(child);
    }

    public UpperCaseReplacer() {
        super(null);
    }

    @Override
    protected String decompose(String something) {
        return something;
    }

    @Override
    protected String compose(String something) {
        return something;
    }

    @Override
    protected String inner_replace(String something) {
        return something.toUpperCase();
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(new SharpToTildeExtract().replace("#abc#"));
        System.out.println(new SharpToTildeExtract(new UpperCaseReplacer()).replace("#abc#"));
        System.out.println(Replacer.compose(new SharpToTildeExtract(), new UpperCaseReplacer()).replace("#abc#"));

        System.out.println(new SharpToTildeExtract(new Negate(new Multiply(2))).replace("#5#"));
    }
}
sebtic
  • 198
  • 1
  • 6
0

ComposableReplacer is the problem.

Try this:

public abstract class Replacer< Outer, Inner > {
    private static class DelegatingReplacer< Outer, Inner > {
        private final Replacer< Outer, Inner > rplDelegate;
        public DelegatingReplacer( Replacer< Outer, Inner > rplDelegate ) { this.rplDelegate = rplDelegate; }

        @Override protected Inner decompose( Outer something ) { return rplDelegate.decompose( something ); }
        @Override protected Outer compose( Inner something ) { return rplDelegate.compose( something ); }
        @Override protected Inner inner_replace( Inner something ) { return rplDelegate.inner_replace( something ); }
    }
    protected abstract Inner decompose( Outer something );
    protected abstract Outer compose( Inner something );
    protected abstract Inner inner_replace( Inner something );
    public final Outer replace( Outer something ) {
        return compose( inner_replace( decompose( something ) ) );
    }
    public < Innerer > Replacer< Outer, Inner > chain( final Replacer< Inner, Innerer > rplChained ) {
        return new DelegatingReplacer< Outer, Inner >( this ) {
            @Override protected inner_replace( Inner something ) {
                 return rplChained.replace( super.inner_replace( something ) );
            }
        }
    }
}

Now you can do

r1.chain( r2.chain( r3 ) ).replace( foo ); // for r1< O, I >, r2< I, J >, r3< J, K > 
r1.chain( r2 ).chain( r3 ).replace( foo ); // for r1< O, I >, r2< I, J >, r3< I, K >

You can't expect a heterogeneous array of Replacers to be composed in a statically-type-safe way. But if the construction of said array was supposed to be guaranteed statically type-safe, that would mean that you could statically determine the elements' mutual type-compatibility, and could construct your chain as shown above.

The only reason for wanting an array-based method is if the replacers (and by extension their types) were being determined at run-time. So your question (how to guarantee compile-time type-safety of an array with types determined at runtime) seems to make no sense.

Judge Mental
  • 5,209
  • 17
  • 22