2 * Client to the webapp at http://loom.cc/
3 * See https://loom.cc/?function=help&topic=grid_tutorial&mode=advanced
5 * Requires Java 5 generics.
6 * Port to earlier versions by removing <String,String> from the
12 import java
.util
.LinkedHashMap
;
13 import java
.util
.Vector
;
14 import java
.util
.Iterator
;
15 import java
.util
.StringTokenizer
;
16 import java
.io
.InputStream
;
17 import java
.io
.InputStreamReader
;
19 import java
.net
.URLConnection
;
22 * A client interface to the webapp at http://loom.cc
24 public class LoomClient
{
27 * The beginning of the URL to access the server.
28 * Defaults to https://loom.cc/
30 String url_prefix
; // Who you gonna call?
33 * No-arg constructor. Defaults url_prefix to https://loom.cc/
36 this("https://loom.cc/");
40 * Constructor to explicitly specify the url_prefix.
41 * @param prefix The url_prefix
43 public LoomClient(String prefix
) {
44 if (!prefix
.endsWith("/")) prefix
= prefix
+ "/";
49 * This is all you really need to call.
50 * The functions below are just syntactic sugar for calling get()
51 * @param keys Map argument name strings to their value strings
53 public KV
get(KV keys
) {
54 return get(keys
, null);
58 * Implementation of get(KV) and debugging version
59 * @param keys Map argument name strings to their value strings
60 * @param urlv index 0 filled on output with the URL string
62 public KV
get(KV keys
, String
[] urlv
) {
63 String url
= url(keys
);
64 if (urlv
!= null) urlv
[0] = url
;
65 String kv
= fetchURLString(url
);
71 * @param type the asset type, an ID (32 character hex string)
72 * @param location the location to buy, an ID
73 * @param usage the usage location to debit, an ID
75 public KV
buy(String type
, String location
, String usage
) {
76 return buy(type
, location
, usage
, null);
80 * Buy a grid location, implementation and debugging version
81 * @param type the asset type, an ID (32 character hex string)
82 * @param location the location to buy, an ID
83 * @param usage the usage location to debit, an ID
84 * @param urlv index 0 filled on output with the URL string
86 public KV
buy(String type
, String location
, String usage
, String
[] urlv
) {
88 keys
.put("function", "grid");
89 keys
.put("action", "buy");
90 keys
.put("type", type
);
91 keys
.put("loc", location
);
92 keys
.put("usage", usage
);
93 return get(keys
, urlv
);
97 * Sell a grid location
98 * @param type the asset type, an ID (32 character hex string)
99 * @param location the location to buy, an ID
100 * @param usage the usage location to debit, an ID
102 public KV
sell(String type
, String location
, String usage
) {
103 return sell(type
, location
, usage
, null);
107 * Sell a grid location - implementation and debugging version
108 * @param type the asset type, an ID (32 character hex string)
109 * @param location the location to buy, an ID
110 * @param usage the usage location to debit, an ID
111 * @param urlv index 0 filled on output with the URL string
113 public KV
sell(String type
, String location
, String usage
, String
[] urlv
) {
115 keys
.put("function", "grid");
116 keys
.put("action", "sell");
117 keys
.put("type", type
);
118 keys
.put("loc", location
);
119 keys
.put("usage", usage
);
120 return get(keys
, urlv
);
124 * Change the issuer of a grid location, implementation and debugging version
125 * @param type the asset type, an ID (32 character hex string)
126 * @param orig the origin issuer location, an ID
127 * @param dest the destination issuer location, an ID
129 public KV
issuer(String type
, String orig
, String dest
) {
130 return issuer(type
, orig
, dest
, null);
134 * Change the issuer of a grid location, implementation and debugging version
135 * @param type the asset type, an ID (32 character hex string)
136 * @param orig the origin issuer location, an ID
137 * @param dest the destination issuer location, an ID
138 * @param urlv index 0 filled on output with the URL string
140 public KV
issuer(String type
, String orig
, String dest
, String
[] urlv
) {
142 keys
.put("function", "grid");
143 keys
.put("action", "issuer");
144 keys
.put("type", type
);
145 keys
.put("orig", orig
);
146 keys
.put("dest", dest
);
147 return get(keys
, urlv
);
151 * Return the value of a grid location
152 * @param type the asset type, an ID (32 character hex string)
153 * @param location the location to read
155 public KV
touch(String type
, String location
) {
156 return touch(type
, location
, null);
160 * Return the value of a grid location, implementation and debugging version
161 * @param type the asset type, an ID (32 character hex string)
162 * @param location the location to read, an ID
163 * @param urlv index 0 filled on output with the URL string
165 public KV
touch(String type
, String location
, String
[] urlv
) {
167 keys
.put("function", "grid");
168 keys
.put("action", "touch");
169 keys
.put("type", type
);
170 keys
.put("loc", location
);
171 return get(keys
, urlv
);
175 * Return the value of a grid location
176 * @param type the asset type, an ID (32 character hex string)
177 * @param hash the hash of the location to read, a 64 character hex string
179 public KV
look(String type
, String hash
) {
180 return look(type
, hash
, null);
184 * Return the value of a grid location, implementation and debugging version
185 * @param type the asset type, an ID (32 character hex string)
186 * @param hash the hash of the location to read, a 64 character hex string
187 * @param urlv index 0 filled on output with the URL string
189 public KV
look(String type
, String hash
, String
[] urlv
) {
191 keys
.put("function", "grid");
192 keys
.put("action", "look");
193 keys
.put("type", type
);
194 keys
.put("hash", hash
);
195 return get(keys
, urlv
);
199 * Move assets from one location to another
200 * @param type the asset type, an ID (32 character hex string)
201 * @param qty The quantity to move, a string of decimal digits
202 * @param orig the location to move from, an ID
203 * @param dest the location to move to, an ID
205 public KV
move(String type
, String qty
, String orig
, String dest
) {
206 return move(type
, qty
, orig
, dest
, null);
210 * Move assets from one location to another, implementation and debugging version
211 * @param type the asset type, an ID (32 character hex string)
212 * @param qty The quantity to move, a string of decimal digits
213 * @param orig the location to move from, an ID
214 * @param dest the location to move to, an ID
215 * @param urlv index 0 filled on output with the URL string
217 public KV
move(String type
, String qty
, String orig
, String dest
, String
[] urlv
) {
219 keys
.put("function", "grid");
220 keys
.put("action", "move");
221 keys
.put("type", type
);
222 keys
.put("qty", qty
);
223 keys
.put("orig", orig
);
224 keys
.put("dest", dest
);
225 return get(keys
, urlv
);
233 * Buy an archive location
234 * @param loc The location to buy
235 * @param usage The location of at least one usage token with which to pay
237 public KV
buyArchive(String loc
, String usage
) {
238 return buyArchive(loc
, usage
, null);
242 * Buy an archive location, implementation and debugging version
243 * @param loc The location to buy
244 * @param usage The location of at least one usage token with which to pay
245 * @param urlv index 0 filled on output with the URL string
247 public KV
buyArchive(String loc
, String usage
, String
[] urlv
) {
249 keys
.put("function", "archive");
250 keys
.put("action", "buy");
251 keys
.put("loc", loc
);
252 keys
.put("usage", usage
);
253 return get(keys
, urlv
);
257 * Sell an archive location.
258 * The location must be empty. Write "" to it before selling.
259 * @param loc The location to sell
260 * @param usage The location into which to put a usage token
262 public KV
sellArchive(String loc
, String usage
) {
263 return sellArchive(loc
, usage
, null);
267 * Sell an archive location, implementation and debugging version
268 * The location must be empty. Write "" to it before selling.
269 * @param loc The location to sell
270 * @param usage The location into which to put a usage token.
271 * @param urlv index 0 filled on output with the URL string
273 public KV
sellArchive(String loc
, String usage
, String
[] urlv
) {
275 keys
.put("function", "archive");
276 keys
.put("action", "sell");
277 keys
.put("loc", loc
);
278 keys
.put("usage", usage
);
279 return get(keys
, urlv
);
283 * Touch an archive location.
284 * @param loc The location to touch
286 public KV
touchArchive(String loc
) {
287 return touchArchive(loc
, null);
291 * Touch an archive location, implementation and debugging version
292 * @param loc The location to touch
293 * @param urlv index 0 filled on output with the URL string
295 public KV
touchArchive(String loc
, String
[] urlv
) {
297 keys
.put("function", "archive");
298 keys
.put("action", "touch");
299 keys
.put("loc", loc
);
300 return get(keys
, urlv
);
304 * Look at an archive location, using its location's hash
305 * @param hash The hash of the location to look at.
307 public KV
lookArchive(String hash
) {
308 return lookArchive(hash
, null);
312 * Look at an archive location, using its location's hash.
313 * Implementation and debugging version.
314 * @param hash The hash of the location to look at.
315 * @param urlv index 0 filled on output with the URL string
317 public KV
lookArchive(String hash
, String
[] urlv
) {
319 keys
.put("function", "archive");
320 keys
.put("action", "look");
321 keys
.put("hash", hash
);
322 return get(keys
, urlv
);
326 * Write at an archive location.
327 * @param loc The location to write
328 * @param usage The location of 1 usage token per 16 bytes written (delta from old value).
329 * @param content The content to write
331 public KV
writeArchive(String loc
, String usage
, String content
) {
332 return writeArchive(loc
, usage
, content
, null);
336 * Write at an archive location, implementation and debugging version.
337 * @param loc The location to write
338 * @param usage The location of 1 usage token per 16 bytes written (delta from old value).
339 * @param content The content to write
341 public KV
writeArchive(String loc
, String usage
, String content
, String
[] urlv
) {
343 keys
.put("function", "archive");
344 keys
.put("action", "write");
345 keys
.put("loc", loc
);
346 keys
.put("usage", usage
);
347 keys
.put("content", content
);
348 return get(keys
, urlv
);
352 * Return the URL corresponding to a set of keys
353 * @param keys the keys
355 public String
url(KV keys
) {
356 StringBuffer str
= new StringBuffer(url_prefix
);
358 Iterator i
= keys
.keySet().iterator();
359 while (i
.hasNext()) {
360 String key
= (String
)i
.next();
361 String value
= (String
)keys
.get(key
);
362 str
.append(delim
).append(key
).append("=").append(urlencode(value
));
365 return str
.toString();
369 * Parse the KV string returned by Loom
370 * @param kv The KV string
371 * @return null if there is no opening left paren on the first line.
373 // This needs to un-c-code the return.
374 // e.g. "\n" -> newline
375 public static KV
parsekv(String kv
) {
376 StringTokenizer tok
= new StringTokenizer(kv
, "\n");
377 boolean first
= true;
381 while(tok
.hasMoreTokens()) {
382 String line
= tok
.nextToken();
384 if (!line
.equals("(")) return res
;
388 if (line
.equals(")")) return res
;
389 if (line
.charAt(0) == ':') key
= line
.substring(1);
390 else if (line
.charAt(0) == '=') {
391 value
= unquoteCString(line
.substring(1));
399 * CQuote a string. Opposite of unquoteCstring()
400 * @param cstring the string to quote
402 public static String
quoteCString(String cstring
) {
403 StringBuffer buf
= new StringBuffer();
404 for (int i
=0; i
<cstring
.length(); i
++) {
405 char chr
= cstring
.charAt(i
);
406 if (chr
== '\n') buf
.append("\\n");
407 else if (chr
== '"') buf
.append("\\\"");
408 else if (chr
== '\t') buf
.append("\\t");
409 else if (chr
== '\\') buf
.append("\\\\");
410 else if (chr
< ' ' || chr
> '~') {
411 buf
.append('\\').append(String
.format("%03o", (int)chr
));
412 } else buf
.append(chr
);
414 return buf
.toString();
418 * Un-CQuote a string. The opposite of quoteCString()
419 * @param cstring The string to unquote.
421 public static String
unquoteCString(String cstring
) {
422 StringBuffer buf
= new StringBuffer();
423 int len
= cstring
.length();
426 char chr
= cstring
.charAt(i
);
433 chr
= cstring
.charAt(i
);
434 if (chr
== 'n') buf
.append('\n');
435 else if (chr
== '"') buf
.append('"');
436 else if (chr
== 't') buf
.append('\t');
437 else if (chr
== '\\') buf
.append('\\');
438 else if (chr
>= '0' && chr
<= '9') {
440 buf
.append(cstring
.substring(i
-1));
443 buf
.append((char)Integer
.parseInt(cstring
.substring(i
, i
+3), 8));
445 } else buf
.append('\\').append(chr
);
451 return buf
.toString();
455 * Fetch the contents of a URL as a string
457 public static String
fetchURLString(String address
) {
458 //println("URL: " + address);
459 char[] buf
= new char[4096];
461 URL url
= new URL(address
);
462 URLConnection connection
= url
.openConnection();
463 InputStreamReader in
= new InputStreamReader(connection
.getInputStream());
464 StringBuffer all
= new StringBuffer();
467 size
= in
.read(buf
, 0, 4096);
468 if (size
> 0) all
.append(buf
, 0, size
);
470 //println(all.toString());
471 return all
.toString();
472 } catch (Exception e
) {
478 * Encode a string for a URL
480 public static String
urlencode(String str
) {
482 return java
.net
.URLEncoder
.encode(str
, "UTF-8");
483 } catch (java
.io
.UnsupportedEncodingException e
) {
489 * I don't like typing System.out.println
491 public static void println(String line
) {
492 System
.out
.println(line
);
496 * Print the usage for the command line app
498 public static void usage() {
499 println("Usage: LoomClient function args...");
500 println(" buy type location usage");
501 println(" sell type location usage");
502 println(" issuer type orig dest");
503 println(" touch type location");
504 println(" look type hash");
505 println(" move type qty orig dest");
506 println(" buyarch loc usage");
507 println(" sellarch loc usage");
508 println(" toucharch loc");
509 println(" lookarch hash");
510 println(" writearch loc usage content");
514 * Print a key/value table to stdout
516 public static void printKV(KV kv
) {
517 Iterator keys
= kv
.keySet().iterator();
519 while(keys
.hasNext()) {
520 String key
= (String
)keys
.next();
521 String value
= kv
.get(key
);
522 println(":" + quoteCString(key
));
523 println("=" + quoteCString(value
));
529 * A little command line example of using the library.
531 public static void main(String
[] args
) {
532 if (args
.length
< 2) {
535 String function
= args
[0];
536 LoomClient client
= new LoomClient();
537 String
[] urlv
= new String
[1];
539 if (function
.equals("buy") || function
.equals("sell")) {
540 if (args
.length
!= 4) {
543 String type
= args
[1];
544 String location
= args
[2];
545 String usage
= args
[3];
546 kv
= function
.equals("buy") ?
547 client
.buy(type
, location
, usage
, urlv
) :
548 client
.sell(type
, location
, usage
, urlv
);
549 } else if (function
.equals("issuer")) {
550 if (args
.length
!= 4) {
553 String type
= args
[1];
554 String orig
= args
[2];
555 String dest
= args
[3];
556 kv
= client
.issuer(type
, orig
, dest
, urlv
);
557 } else if (function
.equals("touch")) {
558 if (args
.length
!= 3) {
561 String type
= args
[1];
562 String location
= args
[2];
563 kv
= client
.touch(type
, location
, urlv
);
564 } else if (function
.equals("look")) {
565 if (args
.length
!= 3) {
568 String type
= args
[1];
569 String hash
= args
[2];
570 kv
= client
.look(type
, hash
, urlv
);
571 } else if (function
.equals("move")) {
572 if (args
.length
!= 5) {
575 String type
= args
[1];
576 String qty
= args
[2];
577 String orig
= args
[3];
578 String dest
= args
[4];
579 kv
= client
.move(type
, qty
, orig
, dest
, urlv
);
580 } else if (function
.equals("buyarch")) {
581 if (args
.length
!= 3) {
584 String loc
= args
[1];
585 String usage
= args
[2];
586 kv
= client
.buyArchive(loc
, usage
, urlv
);
587 } else if (function
.equals("sellarch")) {
588 if (args
.length
!= 3) {
591 String loc
= args
[1];
592 String usage
= args
[2];
593 kv
= client
.sellArchive(loc
, usage
, urlv
);
594 } else if (function
.equals("toucharch")) {
595 if (args
.length
!= 2) {
598 String loc
= args
[1];
599 kv
= client
.touchArchive(loc
, urlv
);
600 } else if (function
.equals("lookarch")) {
601 if (args
.length
!= 2) {
604 String hash
= args
[1];
605 kv
= client
.lookArchive(hash
, urlv
);
606 } else if (function
.equals("writearch")) {
607 if (args
.length
!= 4) {
610 String loc
= args
[1];
611 String usage
= args
[2];
612 String content
= args
[3];
613 kv
= client
.writeArchive(loc
, usage
, content
, urlv
);
618 if (urlv
[0] != null) {
627 * A LinkedHashMap that maps strings to strings
629 public static class KV
extends LinkedHashMap
<String
,String
> {
634 public String
get(String key
) {
635 return super.get(key
);
638 public String
put(String key
, String value
) {
639 return super.put(key
, value
);
645 /* ***** BEGIN LICENSE BLOCK *****
646 * Version: MPL 1.1/GPL 2.0/LGPL 2.1/Apache 2.0
648 * The contents of this file are subject to the Mozilla Public License Version
649 * 1.1 (the "License"); you may not use this file except in compliance with
650 * the License. You may obtain a copy of the License at
651 * http://www.mozilla.org/MPL/
653 * Software distributed under the License is distributed on an "AS IS" basis,
654 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
655 * for the specific language governing rights and limitations under the
658 * The Original Code is Trubanc.com
660 * The Initial Developer of the Original Code is
662 * Portions created by the Initial Developer are Copyright (C) 2008
663 * the Initial Developer. All Rights Reserved.
666 * Bill St. Clair <bill@billstclair.com>
668 * Alternatively, the contents of this file may be used under the
669 * terms of the GNU General Public License Version 2 or later (the
670 * "GPL"), the GNU Lesser General Public License Version 2.1 or later
671 * (the "LGPL"), or The Apache License Version 2.0 (the "AL"), in
672 * which case the provisions of the GPL, LGPL, or AL are applicable
673 * instead of those above. If you wish to allow use of your version of
674 * this file only under the terms of the GPL, the LGPL, or the AL, and
675 * not to allow others to use your version of this file under the
676 * terms of the MPL, indicate your decision by deleting the provisions
677 * above and replace them with the notice and other provisions
678 * required by the GPL or the LGPL. If you do not delete the
679 * provisions above, a recipient may use your version of this file
680 * under the terms of any one of the MPL, the GPL the LGPL, or the AL.
681 ****** END LICENSE BLOCK ***** */