PageRenderTime 55ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/src/sys/java/fan/sys/Unit.java

https://bitbucket.org/bedlaczech/fan-1.0
Java | 531 lines | 385 code | 77 blank | 69 comment | 109 complexity | 387deefb97de8a6ba053813662fb8a99 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. //
  2. // Copyright (c) 2008, Brian Frank and Andy Frank
  3. // Licensed under the Academic Free License version 3.0
  4. //
  5. // History:
  6. // 20 Dec 08 Brian Frank Creation
  7. //
  8. package fan.sys;
  9. import java.util.HashMap;
  10. import java.util.ArrayList;
  11. /**
  12. * Unit
  13. */
  14. public final class Unit
  15. extends FanObj
  16. {
  17. //////////////////////////////////////////////////////////////////////////
  18. // Database
  19. //////////////////////////////////////////////////////////////////////////
  20. public static Unit fromStr(String name) { return fromStr(name, true); }
  21. public static Unit fromStr(String name, boolean checked)
  22. {
  23. synchronized (byId)
  24. {
  25. Unit unit = (Unit)byId.get(name);
  26. if (unit != null || !checked) return unit;
  27. throw Err.make("Unit not found: " + name);
  28. }
  29. }
  30. public static List list()
  31. {
  32. synchronized (list)
  33. {
  34. return list.dup().ro();
  35. }
  36. }
  37. public static List quantities()
  38. {
  39. return quantityNames;
  40. }
  41. public static List quantity(String quantity)
  42. {
  43. List list = (List)quantities.get(quantity);
  44. if (list == null) throw Err.make("Unknown unit database quantity: " + quantity);
  45. return list;
  46. }
  47. private static List loadDatabase()
  48. {
  49. InStream in = null;
  50. List quantityNames = new List(Sys.StrType);
  51. try
  52. {
  53. // open etc/sys/units.txt
  54. String path = "etc/sys/units.txt";
  55. if (Sys.isJarDist)
  56. in = new SysInStream(Unit.class.getClassLoader().getResourceAsStream(path));
  57. else
  58. in = Env.cur().findFile(Uri.fromStr(path)).in();
  59. // parse each line
  60. String curQuantityName = null;
  61. List curQuantityList = null;
  62. String line;
  63. while ((line = in.readLine()) != null)
  64. {
  65. // skip comment and blank lines
  66. line = line.trim();
  67. if (line.startsWith("//") || line.length() == 0) continue;
  68. // quanity sections delimited as "-- name (dim)"
  69. if (line.startsWith("--"))
  70. {
  71. if (curQuantityName != null) quantities.put(curQuantityName, curQuantityList.toImmutable());
  72. curQuantityName = line.substring(2, line.indexOf('(')).trim();
  73. curQuantityList = new List(Sys.UnitType);
  74. quantityNames.add(curQuantityName);
  75. continue;
  76. }
  77. // must be a unit
  78. try
  79. {
  80. Unit unit = Unit.define(line);
  81. curQuantityList.add(unit);
  82. }
  83. catch (Exception e)
  84. {
  85. System.out.println("WARNING: Init unit in etc/sys/units.txt: " + line);
  86. System.out.println(" " + e);
  87. }
  88. }
  89. quantities.put(curQuantityName, curQuantityList.toImmutable());
  90. }
  91. catch (Throwable e)
  92. {
  93. try { in.close(); } catch (Exception e2) {}
  94. System.out.println("WARNING: Cannot load etc/sys/units.txt");
  95. e.printStackTrace();
  96. }
  97. return (List)quantityNames.toImmutable();
  98. }
  99. //////////////////////////////////////////////////////////////////////////
  100. // Definition
  101. //////////////////////////////////////////////////////////////////////////
  102. public static Unit define(String str)
  103. {
  104. // parse
  105. Unit unit = null;
  106. try
  107. {
  108. unit = parseUnit(str);
  109. }
  110. catch (Throwable e)
  111. {
  112. String msg = str;
  113. if (e instanceof ParseErr) msg += ": " + ((ParseErr)e).msg();
  114. throw ParseErr.make("Unit", msg);
  115. }
  116. // register
  117. synchronized (byId)
  118. {
  119. // check that none of the units are defined
  120. for (int i=0; i<unit.ids.sz(); ++i)
  121. {
  122. String id = (String)unit.ids.get(i);
  123. if (byId.get(id) != null) throw Err.make("Unit id already defined: " + id);
  124. }
  125. // this is a new definition
  126. for (int i=0; i<unit.ids.sz(); ++i)
  127. {
  128. String id = (String)unit.ids.get(i);
  129. byId.put(id, unit);
  130. }
  131. list.add(unit);
  132. }
  133. return unit;
  134. }
  135. /**
  136. * Parse an un-interned unit:
  137. * unit := <ids> [";" <dim> [";" <scale> [";" <offset>]]]
  138. */
  139. private static Unit parseUnit(String s)
  140. {
  141. String idStrs = s;
  142. int c = s.indexOf(';');
  143. if (c > 0) idStrs = s.substring(0, c);
  144. List ids = FanStr.split(idStrs, Long.valueOf(','));
  145. if (c < 0) return new Unit(ids, dimensionless, 1, 0);
  146. String dim = s = s.substring(c+1).trim();
  147. c = s.indexOf(';');
  148. if (c < 0) return new Unit(ids, parseDim(dim), 1, 0);
  149. dim = s.substring(0, c).trim();
  150. String scale = s = s.substring(c+1).trim();
  151. c = s.indexOf(';');
  152. if (c < 0) return new Unit(ids, parseDim(dim), Double.parseDouble(scale), 0);
  153. scale = s.substring(0, c).trim();
  154. String offset = s.substring(c+1).trim();
  155. return new Unit(ids, parseDim(dim), Double.parseDouble(scale), Double.parseDouble(offset));
  156. }
  157. /**
  158. * Parse an dimension string and intern it:
  159. * dim := <ratio> ["*" <ratio>]*
  160. * ratio := <base> <exp>
  161. * base := "kg" | "m" | "sec" | "K" | "A" | "mol" | "cd"
  162. */
  163. private static Dimension parseDim(String s)
  164. {
  165. // handle empty string as dimensionless
  166. if (s.length() == 0) return dimensionless;
  167. // parse dimension
  168. Dimension dim = new Dimension();
  169. List ratios = FanStr.split(s, (long)'*', true);
  170. for (int i=0; i<ratios.sz(); ++i)
  171. {
  172. String r = (String)ratios.get(i);
  173. if (r.startsWith("kg")) { dim.kg = Byte.parseByte(r.substring(2).trim()); continue; }
  174. if (r.startsWith("sec")) { dim.sec = Byte.parseByte(r.substring(3).trim()); continue; }
  175. if (r.startsWith("mol")) { dim.mol = Byte.parseByte(r.substring(3).trim()); continue; }
  176. if (r.startsWith("m")) { dim.m = Byte.parseByte(r.substring(1).trim()); continue; }
  177. if (r.startsWith("K")) { dim.K = Byte.parseByte(r.substring(1).trim()); continue; }
  178. if (r.startsWith("A")) { dim.A = Byte.parseByte(r.substring(1).trim()); continue; }
  179. if (r.startsWith("cd")) { dim.cd = Byte.parseByte(r.substring(2).trim()); continue; }
  180. throw ParseErr.make("Bad ratio '" + r + "'");
  181. }
  182. // intern
  183. return dim.intern();
  184. }
  185. /**
  186. * Private constructor.
  187. */
  188. private Unit(List ids, Dimension dim, double scale, double offset)
  189. {
  190. this.ids = checkIds(ids);
  191. this.dim = dim;
  192. this.scale = scale;
  193. this.offset = offset;
  194. }
  195. static List checkIds(List ids)
  196. {
  197. if (ids.sz() == 0) throw ParseErr.make("No unit ids defined");
  198. for (int i=0; i<ids.sz(); ++i) checkId((String)ids.get(i));
  199. return (List)ids.toImmutable();
  200. }
  201. static void checkId(String id)
  202. {
  203. if (id.length() == 0) throw ParseErr.make("Invalid unit id length 0");
  204. for (int i=0; i<id.length(); ++i)
  205. {
  206. int c = id.charAt(i);
  207. if (FanInt.isAlpha(c) || c == '_' || c == '%' || c == '/' || c == '$' || c > 128) continue;
  208. throw ParseErr.make("Invalid unit id " + id + " (invalid char '" + (char)c + "')");
  209. }
  210. }
  211. //////////////////////////////////////////////////////////////////////////
  212. // Identity
  213. //////////////////////////////////////////////////////////////////////////
  214. public final boolean equals(Object obj) { return this == obj; }
  215. public final int hashCode() { return toStr().hashCode(); }
  216. public final long hash() { return FanObj.hash(toStr()); }
  217. public final Type typeof() { return Sys.UnitType; }
  218. public final String toStr() { return (String)ids.last(); }
  219. public final List ids() { return ids; }
  220. public final String name() { return (String)ids.first(); }
  221. public final String symbol() { return (String)ids.last(); }
  222. public final double scale() { return scale; }
  223. public final double offset() { return offset; }
  224. public final String definition()
  225. {
  226. StringBuilder s = new StringBuilder();
  227. for (int i=0; i<ids.sz(); ++i)
  228. {
  229. if (i > 0) s.append(", ");
  230. s.append(ids.get(i));
  231. }
  232. if (dim != dimensionless)
  233. {
  234. s.append("; ").append(dim);
  235. if (scale != 1.0 || offset != 0.0)
  236. {
  237. s.append("; ").append(scale);
  238. if (offset != 0.0) s.append("; ").append(offset);
  239. }
  240. }
  241. return s.toString();
  242. }
  243. //////////////////////////////////////////////////////////////////////////
  244. // Dimension
  245. //////////////////////////////////////////////////////////////////////////
  246. public final long kg() { return dim.kg; }
  247. public final long m() { return dim.m; }
  248. public final long sec() { return dim.sec; }
  249. public final long K() { return dim.K; }
  250. public final long A() { return dim.A; }
  251. public final long mol() { return dim.mol; }
  252. public final long cd() { return dim.cd; }
  253. static class Dimension
  254. {
  255. public int hashCode()
  256. {
  257. return (kg << 28) ^ (m << 23) ^ (sec << 18) ^
  258. (K << 13) ^ (A << 8) ^ (mol << 3) ^ cd;
  259. }
  260. public boolean equals(Object o)
  261. {
  262. Dimension x = (Dimension)o;
  263. return kg == x.kg && m == x.m && sec == x.sec && K == x.K &&
  264. A == x.A && mol == x.mol && cd == x.cd;
  265. }
  266. public String toString()
  267. {
  268. if (str == null)
  269. {
  270. StringBuilder s = new StringBuilder();
  271. append(s, "kg", kg); append(s, "m", m);
  272. append(s, "sec", sec); append(s, "K", K);
  273. append(s, "A", A); append(s, "mol", mol);
  274. append(s, "cd", cd);
  275. str = s.toString();
  276. }
  277. return str;
  278. }
  279. private void append(StringBuilder s, String key, int val)
  280. {
  281. if (val == 0) return;
  282. if (s.length() > 0) s.append('*');
  283. s.append(key).append(val);
  284. }
  285. public Dimension add(Dimension b)
  286. {
  287. Dimension r = new Dimension();
  288. r.kg = (byte)(kg + b.kg);
  289. r.m = (byte)(m + b.m);
  290. r.sec = (byte)(sec + b.sec);
  291. r.K = (byte)(K + b.K);
  292. r.A = (byte)(A + b.A);
  293. r.mol = (byte)(mol + b.mol);
  294. r.cd = (byte)(cd + b.cd);
  295. return r;
  296. }
  297. public Dimension subtract(Dimension b)
  298. {
  299. Dimension r = new Dimension();
  300. r.kg = (byte)(kg - b.kg);
  301. r.m = (byte)(m - b.m);
  302. r.sec = (byte)(sec - b.sec);
  303. r.K = (byte)(K - b.K);
  304. r.A = (byte)(A - b.A);
  305. r.mol = (byte)(mol - b.mol);
  306. r.cd = (byte)(cd - b.cd);
  307. return r;
  308. }
  309. public Dimension intern()
  310. {
  311. // intern
  312. synchronized (dims)
  313. {
  314. Dimension cached = (Dimension)dims.get(this);
  315. if (cached != null) return cached;
  316. dims.put(this, this);
  317. return this;
  318. }
  319. }
  320. public boolean isDimensionless()
  321. {
  322. return toString().length() == 0;
  323. }
  324. String str;
  325. byte kg, m, sec, K, A, mol, cd;
  326. }
  327. //////////////////////////////////////////////////////////////////////////
  328. // Arithmetic
  329. //////////////////////////////////////////////////////////////////////////
  330. public final Unit mult(Unit b)
  331. {
  332. synchronized (combos)
  333. {
  334. Combo key = new Combo(this, "*", b);
  335. Unit r = (Unit)combos.get(key);
  336. if (r == null)
  337. {
  338. r = findMult(this, b);
  339. combos.put(key, r);
  340. }
  341. return r;
  342. }
  343. }
  344. private static Unit findMult(Unit a, Unit b)
  345. {
  346. // if either is dimensionless give up immediately
  347. if (a.dim.isDimensionless() || b.dim.isDimensionless())
  348. throw Err.make("Cannot compute dimensionless: " + a + " * " + b);
  349. // compute dim/scale of a * b
  350. Dimension dim = a.dim.add(b.dim).intern();
  351. double scale = a.scale * b.scale;
  352. // find all the matches
  353. Unit[] matches = match(dim, scale);
  354. if (matches.length == 1) return matches[0];
  355. // right how our technique for resolving multiple matches is lame
  356. String expectedName = a.name() + "_" + b.name();
  357. for (int i=0; i<matches.length; ++i)
  358. if (matches[i].name().equals(expectedName))
  359. return matches[i];
  360. // for now just give up
  361. throw Err.make("Cannot match to db: " + a + " * " + b);
  362. }
  363. public final Unit div(Unit b)
  364. {
  365. synchronized (combos)
  366. {
  367. Combo key = new Combo(this, "/", b);
  368. Unit r = (Unit)combos.get(key);
  369. if (r == null)
  370. {
  371. r = findDiv(this, b);
  372. combos.put(key, r);
  373. }
  374. return r;
  375. }
  376. }
  377. public final Unit findDiv(Unit a, Unit b)
  378. {
  379. // if either is dimensionless give up immediately
  380. if (a.dim.isDimensionless() || b.dim.isDimensionless())
  381. throw Err.make("Cannot compute dimensionless: " + a + " / " + b);
  382. // compute dim/scale of a / b
  383. Dimension dim = a.dim.subtract(b.dim).intern();
  384. double scale = a.scale / b.scale;
  385. // find all the matches
  386. Unit[] matches = match(dim, scale);
  387. if (matches.length == 1) return matches[0];
  388. // right how our technique for resolving multiple matches is lame
  389. String expectedName = a.name() + "_per_" + b.name();
  390. for (int i=0; i<matches.length; ++i)
  391. if (matches[i].name().contains(expectedName))
  392. return matches[i];
  393. // for now just give up
  394. throw Err.make("Cannot match to db: " + a + " / " + b);
  395. }
  396. private static Unit[] match(Dimension dim, double scale)
  397. {
  398. ArrayList acc = new ArrayList();
  399. synchronized (list)
  400. {
  401. for (int i=0; i<list.sz(); ++i)
  402. {
  403. Unit x = (Unit)list.get(i);
  404. if (x.dim == dim && approx(x.scale, scale))
  405. acc.add(x);
  406. }
  407. }
  408. return (Unit[])acc.toArray(new Unit[acc.size()]);
  409. }
  410. private static boolean approx(double a, double b)
  411. {
  412. // pretty loose with our approximation because the database
  413. // doesn't have super great resolution for some normalizations
  414. if (a == b) return true;
  415. double t = Math.min( Math.abs(a/1e3), Math.abs(b/1e3) );
  416. return Math.abs(a - b) <= t;
  417. }
  418. static class Combo
  419. {
  420. Combo(Unit a, String op, Unit b) { this.a = a; this.op = op; this.b = b; }
  421. public int hashCode() { return a.hashCode() ^ op.hashCode() ^ (b.hashCode() << 13); }
  422. public boolean equals(Object that) { Combo x = (Combo)that; return a == x.a && op == x.op && b == x.b; }
  423. final Unit a;
  424. final String op;
  425. final Unit b;
  426. }
  427. //////////////////////////////////////////////////////////////////////////
  428. // Conversion
  429. //////////////////////////////////////////////////////////////////////////
  430. public final double convertTo(double scalar, Unit to)
  431. {
  432. if (dim != to.dim) throw Err.make("Incovertable units: " + this + " and " + to);
  433. return ((scalar * this.scale + this.offset) - to.offset) / to.scale;
  434. }
  435. //////////////////////////////////////////////////////////////////////////
  436. // Fields
  437. //////////////////////////////////////////////////////////////////////////
  438. private static final List list = new List(Sys.UnitType);
  439. private static final HashMap byId = new HashMap(); // String id-> Unit
  440. private static final HashMap dims = new HashMap(); // Dimension -> Dimension
  441. private static final HashMap quantities = new HashMap(); // String -> List
  442. private static final HashMap combos = new HashMap(); // Combo -> Unit
  443. private static final List quantityNames;
  444. private static final Dimension dimensionless = new Dimension();
  445. static
  446. {
  447. dims.put(dimensionless, dimensionless);
  448. quantityNames = loadDatabase();
  449. }
  450. private final List ids;
  451. private final double scale;
  452. private final double offset;
  453. private final Dimension dim;
  454. }