7

This is a question about something that I am not sure how to solve in Java. I want to make triple statements based on three types of data, URI, String or Literal, each type is encoded differently. I have written encode methods that accept these types.

public static String makeStatement(URI subject, URI predicate, String object) {
    return " " + encode(subject) + " " + encode(predicate) + " " + encode(object) + ".\n";
}

public static String makeStatement(String subject, URI predicate, String object) {
    return " " + encode(subject) + " " + encode(predicate) + " " + encode(object) + ".\n";
}

public static String makeStatement(URI subject, URI predicate, Literal object) {
    return " " + encode(subject) + " " + encode(predicate) + " " + encode(object) + ".\n";
}

private static String encode(String binding) {
    return "?" + binding;
}

private static String encode(URI uri) {
    return "<" + uri.stringValue() + ">";
}

private static String encode(Literal literal) {
    return "\"" + literal.stringValue() + "\"" + literal.getDatatype();
}

But as I can accept any combination of these types this would require 9 makeStatement functions, which are basically doing the same thing and that seems like a bad idea, especially since it might be possible I want to add another type later on.

Normally I would answer such a question with the suggestion to create a superClass, but I cannot edit String, URI and Literal. Another option would be to define

public static String makeStatement(Object subject, Object predicate, Object object) {
    String encodedSubject = "", encodedPredicate = "", encodedObject = "";
    if (subject.getClass().equals(URI.class)) {
        encodedSubject = encode((URI) subject);
}
    return " " + encode(encodedSubject) + " " + encode(encodedPredicate) + " " + encode(encodedObject) + ".\n";
}

and then check the classes for each argument, but I consider this not very elegant. Another suggestion would be to define something like makeStatement(URI subjectURI, String subjectString, Literal subjectLiteral, URI predicateURI.. etc) and then check which arguments are null and go from there, but that would mean typing a lot of nulls when I call the function. A third option would be https://stackoverflow.com/a/12436592/1014666, but again this require quite some extra typing when calling the makeStatement function.

Any suggestions?

Community
  • 1
  • 1
Rhand
  • 901
  • 7
  • 20
  • A colleague faced a similar problem a while back and he wrote a Python script that writes all 9 combinations as text in the .java file – mbatchkarov Oct 11 '13 at 08:41
  • Nice, but not very elegant :) – Rhand Oct 11 '13 at 08:48
  • write a generic method to accept everything as a object and inside that do the instance of check and perform the operation as you need.It may give you other perspective. – Miko Oct 11 '13 at 09:31
  • @Mayilarun I already mentioned this possibility in my question. – Rhand Oct 11 '13 at 12:58
  • I like the answers from U Mad and Masoom Raza, but I am not sure what is best practice. I think in the end the solution from Masoon will result in the shortest makeStatement(...) lines, which is something I value highly. – Rhand Oct 11 '13 at 13:01
  • 1
    What Masoom Raza proposed is not considered a good practice, because a method with signature `makeStatement(Object, Object, Object)` prevents all compile time type checking. Sure, you can pass in any combination of URI, String or Literal, but you can also pass in Date, BigDecimal, Socket, anything really. – RokL Oct 11 '13 at 13:25

5 Answers5

3

You can use a builder pattern:

    public class StatementMaker {
    private static String encode(String binding) {
        return "?" + binding;
    }

    private static String encode(URI uri) {
        return "<" + uri.stringValue() + ">";
    }

    private static String encode(Literal literal) {
        return "\"" + literal.stringValue() + "\"" + literal.getDatatype();
    }

    public static Statement from(String b) {
        return new Statement(encode(b));
    }

    public static Statement from(URI b) {
        return new Statement(encode(b));
    }

    public static Statement from(Literal b) {
        return new Statement(encode(b));
    }

    public static class Statement {

        private StringBuilder buf;
        private Statement(String s) {
            buf = new StringBuilder(" ");
            buf.append(s);
        }

        public Statement with(String s) {
            buf.append(" ").append(encode(b));
            return this;
        }

        public Statement with(URI s) {
            buf.append(" ").append(encode(b));
            return this;
        }

        public Statement with(Literal s) {
            buf.append(" ").append(encode(b));
            return this;
        }

        public String toString() {
            return buf.toString() + ".\n";
        }

    }
}

You can now construct statement as such:

StatementMaker.from(subject).with(predicate).with(object).toString()

In code that needs the statements you can shorten the code further with static import:

import static my.package.StatementMaker.from;

Then the statement is reduced to:

from(subject).with(predicate).with(object).toString()

You can add 3 more methods to inner class:

public static class Statement {

    private StringBuilder buf;
    private Statement(String s) {
        buf = new StringBuilder(" ");
        buf.append(s);
    }

    public Statement with(String s) {
        buf.append(" ").append(encode(b));
        return this;
    }

    public Statement with(URI s) {
        buf.append(" ").append(encode(b));
        return this;
    }

    public Statement with(Literal s) {
        buf.append(" ").append(encode(b));
        return this;
    }

    public String and(String s) {
        buf.append(" ").append(encode(b));
        return buf.toString() + ".\n";
    }

    public String and(URI s) {
        buf.append(" ").append(encode(b));
        return buf.toString() + ".\n";
    }

    public String and(Literal s) {
        buf.append(" ").append(encode(b));
        return buf.toString() + ".\n";
    }


    public String toString() {
        return buf.toString() + ".\n";
    }

}

Then you can use avoid a toString() call like this:

String statement = from(subject).with(predicate).and(object);

RokL
  • 2,663
  • 3
  • 22
  • 26
2

Method overloading works great if there's only a few options. What you have here is a bit obsessive. You don't need to have all the options if there's an easy way to convert from one to another.

So forget about having every possible choice and make the most used ones available.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
  • Unfortunately there is no easy way to convert from one to another. For the software I am currently working on I find myself writing functions of which 80% of the lines could consist of makeStatement calls. – Rhand Oct 11 '13 at 08:48
  • No easy way to convert from URI to String and vice-versa? – Kayaman Oct 11 '13 at 11:50
  • Sure there is an easy way to convert from a URI to a String, but in my context I want to wrap a URI with <> and put a ? before a string. – Rhand Oct 11 '13 at 12:56
1

Normally I would answer such a question with the suggestion to create a superClass, but I cannot edit String, URI and Literal. Another option would be to define

I'd go for a similar approach, but instead of extracting a superclass, which as you stated, you cannot do, you could create a wrapper.

public class LiteralWrapper {
    private String string = null;
    private URI uri = null;
    private Literal literal = null;

    public LiteralWrapper(String sting) {
        this.string = string;
    }

    public LiteralWrapper(URI uri) {
        this.uri = uri;
    }

    public LiteralWrapper(Literal literal) {
        this.literal = literal;
    }

    // Note that this class is immutable, 
    // so you know you cannot have more than one non-null member. 
    // Probably not a bad idea to add some getters, though.


    /* The encode functions from your original question */

    private static String encode(String binding) {
        return "?" + binding;
    }

    private static String encode(URI uri) {
        return "<" + uri.stringValue() + ">";
    }

    private static String encode(Literal literal) {
        return "\"" + literal.stringValue() + "\"" + literal.getDatatype();
    }

    @Override
    public String toString() {
        if (literal != literal) {
            return encode(literal);
        }
        if (uri != null) {
            return encode(uri);
        }
        return encode(string);
    }
}

Now your makeStatement code becomes trivial:

public static String makeStatement(LiteralWrapper subject, LiteralWrapper predicate, LiteralWrapper object) {
    return " " + subject + " " + predicate + " " + object + ".\n";
}

EDIT:
As per the comment below, this makes calling makeStatement a bit annoying. Instead of being able to do makeStatement(myString, myUri, myLiteral), you're force to call makeStatement(new LiteralWrapper(myString), new LiteralWrapper(myUri), new LiteralWrapper(myLiteral)). This problem can't be completely avoided with the given solution, but it can be mitigated by introducing some syntactic sugar in the form of factory methods:

public static LiteralWrapper wrap(String string) {
    return new LiteralWrapper(string);
}

public static LiteralWrapper wrap(URI uri) {
    return new LiteralWrapper(uri);
}

public static LiteralWrapper wrap(Literal literal) {
    return new LiteralWrapper(literal);
}

Now, you can staticly import these wrappers wherever you need to use makeStatement:

import static org.some.package.LiteralWrapper.wrap;
/* other imports*/

public class MyClass {
    public void someFunction() {
        /* some business logic */

        makeStatemet(wrap(myString), wrap(myURI), wrap(myLiteral));
    }
}
Mureinik
  • 297,002
  • 52
  • 306
  • 350
  • Thanks for your answer, this is similar to the answer to which I referred in my question. So far this is the most elegant option, however this would turn my makeStatement("subject", "predicate", "object") into makeStatement(new StatementWrapper("subject"), new StatementWrapper("predicate"), StatementWrapper("object")); which does not really improve the readability. – Rhand Oct 11 '13 at 09:17
  • @Rhand I agree this somewhat hinders readability. See my edit to the answer above for a way to mitigate the problem. IMHO, the solution results in pretty readable code, but it's all in the eye of the beholder. – Mureinik Oct 11 '13 at 09:26
  • I like the wrap functionality (this is expanded by some of the other answers) – Rhand Oct 11 '13 at 12:57
  • Thanks for your answer, this would work, but I prefer U Mad's answer – Rhand Oct 11 '13 at 13:21
1
public static String makeStatement(Object subject, Object predicate, Object object) {
    return " " + encode(subject) + " " + encode(predicate) + " " + encode(object) + ".\n";
}

private static String encode(Object obj) {
   String  encodedOj ="";
   if (obj.getClass().equals(URI.class)) {
        encodedOj = encode((URI) obj);
   }else if(obj.getClass().equals(Literal.class)){
      encodedOj = encode((Literal) obj);
   }else if(obj.getClass().equals(String.class)){
      encodedOj = encode((String) obj);
   } 
   return encodedOj;
}

private static String encode(String binding) {
    return "?" + binding;
}

private static String encode(URI uri) {
    return "<" + uri.stringValue() + ">";
}

private static String encode(Literal literal) {
    return "\"" + literal.stringValue() + "\"" + literal.getDatatype();
}
mRaza
  • 65
  • 1
  • 7
  • Do you know if there is a performance hit for checking the class of each Object obj? When compared to U Mad's answer this seems the main difference (and the makeStatement function is very different). – Rhand Oct 11 '13 at 13:04
  • Thanks for your answer, this would work, but I prefer U Mad's answer – Rhand Oct 11 '13 at 13:20
  • 1
    The problem is that any object can be passed in. That is itself is code smell, but what is worse, if `makeStatement` is called with something that is not `URI`, `Literal` or `String`, the `encode(Object)` method fails silently and returns an empty string. You should at least add `else throw new IllegalArgumentException` to that if block. – RokL Oct 11 '13 at 13:28
0

You can create fun and easy to use wrapper using static factory methods (see Effective Java, Item 1) and anonymous classes (acting as something like closures).

Here is how:

public class Item {

    private static interface Methods {
        public String encode();
    }

    private final Methods methods;

    private Item(Methods methods) {
        this.methods = methods;
    }

    public static Item of(final String binding) {
        return new Item(new Methods() {
            @Override
            public String encode() {
                return "?" + binding;
            }
        });
    }

    public static Item of(final URI uri) {
        return new Item(new Methods() {
            @Override
            public String encode() {
                return "<" + uri.stringValue() + ">";
            }
        });
    }

    public static Item of(final Literal literal) {
        return new Item(new Methods() {
            @Override
            public String encode() {
                return "\"" + literal.stringValue() + "\"" + literal.getDatatype();
            }
        });
    }

    public String encode() {
        return methods.encode();
    }
}

It's really easy to add new supported types (which was one of your requirements) with this approach: just create new static factory method accepting this type: Item.of(NewType val).

So you would have a method:

public static String makeStatement(Item subject, Item predicate, Item object) {
    return subject.encode() + " " + predicate.encode() + " " + object.encode();
}

And call it like that:

makeStatement(Item.of(subject), Item.of(predicate), Item.of(object));

Adding new methods is also quite easy (but it would be sort of like modification instead of extension) -- just add them to the Methods interface and implement in closures for all supported types. The compiler will make you to, anyway.

siledh
  • 3,268
  • 2
  • 16
  • 29
  • Thanks for your answer, this would work, but I prefer U Mad's answer – Rhand Oct 11 '13 at 13:20
  • 1
    Indeed, it's much better for your particular problem. I was trying to solve somewhat more general one (accepting different types of arguments). – siledh Oct 11 '13 at 13:52