DaveAKing / guava-libraries

Automatically exported from code.google.com/p/guava-libraries
Apache License 2.0
0 stars 0 forks source link

Add interface for abstraction of being able to write something into an Appendable #1512

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
Please consider adding an interface as an abstraction for something that knows 
how to dump itself into an Appendable. I propose to call this interface 
Appender, although this name is maybe not the best. This interface would define 
one method

void appendTo(Appendable appendable) throws IOException;

The contract of an Appender would be such that calling

StringBuilder sb = new StringBuilder();
appender.appendTo(sb);
return sb.toString();

gives exactly the same result as appender.toString(), but possibly has better 
memory and performance characteristics if the passed Appendable is a stream 
into a file or socket, for example.

With this contract, all code that would usually call toString() on an object 
could alternatively use appendTo() if the object is an Appender.

Motivation:
I have lots of possibly large data structures that know how to create a String 
representation of themselves, possibly even in different formats. I also want 
to use utility methods for writing some object into a file or a stream, to have 
all the error handling etc. in a central place and not mixed with the code 
creating the String representation. Now my data structures are occasionally so 
large that first creating a String which is written to file afterwards becomes 
very slow or even impossible (there is a maximum size of Strings that is well 
reachable with memory of todays desktop machines, and I already hit it in 
practice once).

With the proposed interface, it becomes trivial to write code that dumps data 
structures etc. incrementally into a file or a stream without building a 
complete String representation in memory, and still have the IO code and the 
String creation nicely separated.

I propose to add this interface to Guava, because its usefulness greatly 
increases when more code uses it. Of course, some obvious helper methods like 
Files.write(Appender, File) and CharSink.write(Appender) could be added. The 
real benefit, however, is that all code that calls a toString() method and adds 
the result to some Appendable would check if the current object is an instance 
of Appender and call appendTo() in this case. All code that creates a 
potentially large String could provide an alternative lazy version that creates 
an Appender. (For new code, the Appender-returning version could even be 
preferred or be the only one, because if really a String is needed, all you 
need to do is to add .toString() to the calling chain.)

The main use case inside Guava is probably Joiner, at least that's my main 
point why I would like to see this interface be adopted in Guava. Currently I 
cannot use Joiner when the elements of my collection are potentially large, 
because it forces them to create a String representation in memory. If Joiner 
calls appendTo() on the collection elements instead of toString() if available, 
this would be possible. Even with smaller objects this would probably give some 
performance and memory benefit because less intermediate String(Builder)s need 
to be created. Joiner could also provide a lazy join method that returns an 
Appender instead of a String (this method would also double as a 
Appenders.concat() replacement).

If this interface is added to Guava, some utility methods that for example dump 
an Appender into a StringBuilder (ignoring IOExceptions) or even into a String 
(as a base implementation for an Appender's toString() method) would of course 
also be helpful.

Original issue reported on code.google.com by phwend...@gmail.com on 22 Aug 2013 at 10:37

GoogleCodeExporter commented 9 years ago

Original comment by cgdecker@google.com on 22 Aug 2013 at 8:00

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
interface Appender {
   <A extends Appendable> A writeTo(A a);
}

abstract class TemplateAppender {
   public <A extends Appendable> A writeTo(A a) {
      appendTo(DataAppendable.of(a));
      return a;
   }

   protected abstract void appendTo(DataAppendable a);

   @Override public String toString() {
      try {
         return writeTo(new StringBuilder()).toString();
      }
      catch (IOException unused) {
         throw new Error();
      }
   }
}

class DataAppendable implements Appendable {

   public static DataAppendable of(Appendable a) {
      // if already a DataAppendable, cast and return
      // possibly specialize in certain known Appendables
      //    such as StringBuilders/Buffers/Streams etc.
      // otherwise, wrap specified and return.
   }

   // Appendable only provides append(char|CharSequence)
   // methods in StringBuilder may be convenient all around...
   public DataAppendable append(Object|long|int|double|boolean) {...}

}

Original comment by jysjys1...@gmail.com on 1 Sep 2013 at 5:51

GoogleCodeExporter commented 9 years ago
> throw new Error();

Effective Java, Item 58 (Use checked exceptions for recoverable conditions and 
runtime exceptions for programming errors) advises against that:

> While the Java Language Specification does not require it, there is a strong
convention that errors are reserved for use by the JVM to indicate resource 
defi-
ciencies, invariant failures, or other conditions that make it impossible to 
continue
execution.

Original comment by SeanPFl...@googlemail.com on 3 Sep 2013 at 7:53

GoogleCodeExporter commented 9 years ago
One last note:

Appender sounds as if it should be some 2-arity function, whilst SelfAppending 
captures well that the implementation can append itself to an appender:

interface Appender<T> {
   public <A extends Appendable> A writeTo(T context, A appendable);
}

interface SelfAppending {
   public <A extends Appendable> A writeTo(A appendable);
}

Original comment by jysjys1...@gmail.com on 3 Sep 2013 at 5:54

GoogleCodeExporter commented 9 years ago
The suggested DataAppendable may be convenient. It exists in the Java API as 
Printwriter, but unfortunately that one swallows all exceptions (which is not 
desired here). However, its usefulness is orthogonal to Appender. We do not 
need a TemplateAppender just to use something like DataAppendable, because each 
implementation of appendTo(Appendable) can just wrap the Appendable itself it 
needed.

An abstract base implementation of Appender that provides a toString() 
implementation is indeed useful, I also have one. It is nice to have when you 
want to write an anonymous class that implements Appender.
I already mentioned that a base implementation of toString() should be 
available. I think the correct exception to throw in this method would be 
AssertionError, and of course it should wrap the IOException.

Original comment by phwend...@gmail.com on 4 Sep 2013 at 6:49

GoogleCodeExporter commented 9 years ago
I am not sure whether there is a usecase for a 2-arity Appender. I have never 
encountered one. What kind of stuff would be provided as context?

Also it is problematic because the desired equivalency of the appendTo/writeTo 
and toString methods cannot be provided, so such an interface is not as 
backwards compatible as the 1-arity interface (the nice thing about the latter 
is that you can use it everywhere in your code, and code that does know about 
it has benefits, but it also works everywhere else).

For example, a Joiner could normally not use a 2-arity appender interface, as 
long as it does not get methods added that also provide a context, and then the 
Joiner interface would blow up IMHO.

Original comment by phwend...@gmail.com on 4 Sep 2013 at 6:56

GoogleCodeExporter commented 9 years ago
> I am not sure whether there is a usecase for a 2-arity Appender.

I was only pointing out that the name "Appender" seems to suggest that the 
object would be appending "something" to an appendable object--the appended 
"something" seems vague; whereas, some name that captures appending oneself to 
an appendable object could clarify what's being appended: SelfAppending, for 
lack of a better name, gives the impression that the implementing object 
appends itself. It's just a name, but it's not bad for names to be as 
self-documenting as possible.

Original comment by jysjys1...@gmail.com on 5 Sep 2013 at 2:31

GoogleCodeExporter commented 9 years ago
This issue has been migrated to GitHub.

It can be found at https://github.com/google/guava/issues/<issue id>

Original comment by cgdecker@google.com on 1 Nov 2014 at 4:12

GoogleCodeExporter commented 9 years ago

Original comment by cgdecker@google.com on 1 Nov 2014 at 4:17

GoogleCodeExporter commented 9 years ago

Original comment by cgdecker@google.com on 3 Nov 2014 at 9:08