Symbol.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. // Copyright (c) 1996-2000, 2002, 2004, 2006 Per M.A. Bothner.
  2. // This is free software; for terms and warranty disclaimer see ./COPYING.
  3. package gnu.mapping;
  4. import java.io.*;
  5. // Enable JAXP-QName if Symbol should extend javax.xml.namespace.QName.
  6. // This is not the default, even when JAXP-1.3 is defined, because it makes
  7. // symbols bigger and some operations (such as equals and handling of
  8. // uninterned symbols) slower, without much benefit.
  9. // This option needs some work.
  10. /* #ifdef JAXP-QName */
  11. // import javax.xml.namespace.QName;
  12. /* #endif */
  13. /** A Symbol is a name, usually in a specific Namespace.
  14. * A Symbol is stateless: Common Lisp-style "value", "function" and
  15. * "property list" bindings are not part of the Symbol itself, but
  16. * looked up in the current Environment.
  17. * A <code>Symbol</code> may be viewed as an <code>EnvironmentKey</code>
  18. * with a <code>null</code> property component.
  19. */
  20. public class Symbol
  21. /* #ifdef JAXP-QName */
  22. // extends QName
  23. /* #endif */
  24. implements
  25. EnvironmentKey,
  26. /* #ifdef JAVA2 */
  27. Comparable,
  28. /* #endif */
  29. Externalizable
  30. {
  31. /* #ifndef JAXP-QName */
  32. protected String name;
  33. /* #endif */
  34. Namespace namespace;
  35. public final Symbol getKeySymbol () { return this; }
  36. public final Object getKeyProperty () { return null; }
  37. public boolean matches (EnvironmentKey key)
  38. {
  39. return equals(key.getKeySymbol(), this) && key.getKeyProperty() == null;
  40. }
  41. public boolean matches (Symbol symbol, Object property)
  42. {
  43. return equals(symbol, this) && property == null;
  44. }
  45. /* #ifndef JAXP-QName */
  46. public final String getNamespaceURI()
  47. {
  48. Namespace ns = getNamespace();
  49. String uri = ns == null ? null : ns.getName();
  50. return uri == Namespace.UNKNOWN_NAMESPACE ? "" : uri;
  51. }
  52. public final String getLocalPart()
  53. {
  54. return name;
  55. }
  56. public final String getPrefix ()
  57. {
  58. Namespace ns = namespace;
  59. return ns == null ? "" : ns.prefix;
  60. }
  61. /* #endif */
  62. public final boolean hasEmptyNamespace ()
  63. {
  64. Namespace ns = getNamespace();
  65. String nsname;
  66. return (ns == null
  67. || (nsname = ns.getName()) == null || nsname.length() == 0);
  68. }
  69. public final boolean hasUnknownNamespace() {
  70. Namespace ns = getNamespace();
  71. return ns != null && ns.isUnknownNamespace();
  72. }
  73. /** Synonym for getName - the "print name" of the symbol without Namespace.
  74. * Useful when thinking of a Symbol as an XML QName. */
  75. public final String getLocalName()
  76. {
  77. /* #ifdef JAXP-QName */
  78. // return getLocalPart();
  79. /* #else */
  80. return name;
  81. /* #endif */
  82. }
  83. public final String getName()
  84. {
  85. /* #ifdef JAXP-QName */
  86. // return getLocalPart();
  87. /* #else */
  88. return name;
  89. /* #endif */
  90. }
  91. /** Find or create a symbol in a specificed namespace.
  92. * @param uri a namespace uri.
  93. * @param name The "local name" or "print name" of the desired symbol.
  94. * @param prefix namespace prefix, or {@code ""}
  95. */
  96. public static Symbol make (String uri, String name, String prefix)
  97. {
  98. return Namespace.valueOf(uri, prefix).getSymbol(name.intern());
  99. }
  100. /** Find or create a symbol in a specificed namespace.
  101. * @param namespace can be an Namespace, or a namespace/environment name
  102. * (resolved using Namespace.getInstance), or null (in which case
  103. * an uninterned symbol is created).
  104. * @param name The "local name" or "print name" of the desired symbol.
  105. */
  106. public static Symbol make (Object namespace, String name)
  107. {
  108. Namespace ns = namespace instanceof String
  109. ? Namespace.valueOf((String) namespace)
  110. : (Namespace) namespace;
  111. if (ns == null || name == null)
  112. return makeUninterned(name);
  113. return ns.getSymbol(name.intern());
  114. }
  115. public static SimpleSymbol valueOf (String name)
  116. {
  117. return (SimpleSymbol) Namespace.EmptyNamespace.getSymbol(name.intern());
  118. }
  119. public static Symbol valueOf (String name, Object spec)
  120. {
  121. if (spec == null || spec == Boolean.FALSE)
  122. return makeUninterned(name);
  123. Namespace ns;
  124. if (spec instanceof Namespace)
  125. ns = (Namespace) spec;
  126. else if (spec == Boolean.TRUE)
  127. ns = Namespace.EmptyNamespace;
  128. else
  129. ns = Namespace.valueOf(((CharSequence) spec).toString());
  130. return ns.getSymbol(name.intern());
  131. }
  132. /* Redundant, and cause method invocation to pick the wrong method.
  133. public static Symbol valueOf (String name, Namespace namespace)
  134. {
  135. return namespace.getSymbol(name.intern());
  136. }
  137. public static Symbol valueOf (String name, String namespace)
  138. {
  139. return Namespace.valueOf(namespace).getSymbol(name.intern());
  140. }
  141. */
  142. public static Symbol valueOf (String name, String namespace, String prefix)
  143. {
  144. return Namespace.valueOf(namespace, prefix).getSymbol(name.intern());
  145. }
  146. /** Parse a String as a Symbol.
  147. * Recognizes:
  148. * <ul>
  149. * <li>{@code "{namespace-uri}:local-name"} - which creates a
  150. * symbol with that namespace-uri and an empty prefix;
  151. * <li>{@code "{namespace-uri}local-name"} - which is the same as above
  152. * <li>{@code "prefix{namespace-uri}:local-name"} - which creates a
  153. * symbok with that prefix and namespace-uri
  154. * </li>
  155. * <li>{@code "prefix:local-name"}- which creates a symbol with that prefix
  156. * and an "unknown" namespace-uri, using {@link #makeWithUnknownNamespace};
  157. * </li>
  158. * <li>and plain {@code "local-name"} - which creates a symbol in
  159. * {@link Namespace#EmptyNamespace}.
  160. * </li></ul>
  161. */
  162. public static Symbol parse (String symbol)
  163. {
  164. int slen = symbol.length();
  165. int lbr = -1, rbr = -1;
  166. int braceCount = 0;
  167. int mainStart = 0;
  168. int prefixEnd = 0;
  169. for (int i = 0; i < slen; i++)
  170. {
  171. char ch = symbol.charAt(i);
  172. if (ch == ':' && braceCount == 0)
  173. {
  174. prefixEnd = i;
  175. mainStart = i+1;
  176. break;
  177. }
  178. if (ch == '{')
  179. {
  180. if (lbr < 0)
  181. {
  182. prefixEnd = i;
  183. lbr = i;
  184. }
  185. braceCount++;
  186. }
  187. if (ch == '}')
  188. {
  189. braceCount--;
  190. if (braceCount == 0)
  191. {
  192. rbr = i;
  193. mainStart = (i < slen && symbol.charAt(i+1) == ':') ? i+2 : i+1;
  194. break;
  195. }
  196. if (braceCount < 0) // error
  197. {
  198. mainStart = prefixEnd;
  199. break;
  200. }
  201. }
  202. }
  203. if (lbr >= 0 && rbr > 0)
  204. {
  205. String uri = symbol.substring(lbr+1, rbr);
  206. String prefix = prefixEnd > 0 ? symbol.substring(0, prefixEnd) : null;
  207. return Symbol.valueOf(symbol.substring(mainStart), uri, prefix);
  208. }
  209. else if (prefixEnd > 0)
  210. {
  211. return Symbol.makeWithUnknownNamespace(symbol.substring(mainStart),
  212. symbol.substring(0, prefixEnd));
  213. }
  214. else
  215. {
  216. return Symbol.valueOf(symbol);
  217. }
  218. }
  219. /** Make a placeholder symbol with a known prefix and unknown namespace-uri.
  220. * This is convenient for processing definition commands like
  221. * {@code "prefix:name=value"} - such as on the Kawa command-line -
  222. * where we don't yet know the namespace-uri. Code that later looks
  223. * for a value should look both under the true namespace-uri and
  224. * the prefix combined with {@link Namespace#makeUnknownNamespace(String)}.
  225. */
  226. public static Symbol makeWithUnknownNamespace (String local, String prefix)
  227. {
  228. return Namespace.makeUnknownNamespace(prefix).getSymbol(local.intern());
  229. }
  230. public Symbol ()
  231. {
  232. /* #ifdef JAXP-QName */
  233. // super("");
  234. /* #endif */
  235. }
  236. public static Symbol makeUninterned (String name)
  237. {
  238. /* #ifdef JAXP-QName */
  239. // Namespace ns = Namespace.getInstance("kawa.gensym");
  240. // String sname = name;
  241. // int i = 0;
  242. // for (;;)
  243. // {
  244. // int hash = sname.hashCode();
  245. // synchronized (ns)
  246. // {
  247. // Symbol sym = ns.lookup(sname, hash, false);
  248. // if (sym == null)
  249. // return ns.add(new Symbol(sname.intern(), ns), hash);
  250. // }
  251. // sname = name + '.' + ++i;
  252. // }
  253. /* #else */
  254. return new Symbol(name, null);
  255. /* #endif */
  256. }
  257. public static Symbol makeUninterned (String name, Namespace namespace)
  258. {
  259. return new Symbol(name, namespace);
  260. }
  261. /** Create new Symbol in a given namespace.
  262. * Does not enter the result in the namespace's symbol table.
  263. * @param name an interned String
  264. */
  265. protected Symbol (String name, Namespace ns)
  266. {
  267. /* #ifdef JAXP-QName */
  268. // super(ns == null ? "" : ns.getName(), name, ns == null ? "" : ns.prefix);
  269. /* #else */
  270. this.name = name;
  271. /* #endif */
  272. this.namespace = ns;
  273. }
  274. public int compareTo(Object o)
  275. {
  276. Symbol other = (Symbol) o;
  277. if (getNamespaceURI() != other.getNamespaceURI())
  278. throw new IllegalArgumentException("comparing Symbols in different namespaces");
  279. return getLocalName().compareTo(other.getLocalName());
  280. }
  281. public static boolean equals (Symbol sym1, Symbol sym2)
  282. {
  283. if (sym1 == sym2)
  284. return true;
  285. if (sym1 == null || sym2 == null)
  286. return false;
  287. /* #ifdef JAXP-QName */
  288. // if (sym1.getLocalPart() == sym2.getLocalPart())
  289. /* #else */
  290. if (sym1.name == sym2.name)
  291. /* #endif */
  292. {
  293. Namespace namespace1 = sym1.namespace;
  294. Namespace namespace2 = sym2.namespace;
  295. // If a namespace is null, it means an uninterned symbol,
  296. // which is only equals to the same Symbol instance.
  297. if (namespace1 != null && namespace2 != null)
  298. return namespace1.name == namespace2.name;
  299. }
  300. return false;
  301. }
  302. /* #ifndef JAXP-QName */
  303. /** Just tests for identity.
  304. * Otherwise hashTables that have Symbols as keys will break. */
  305. public final boolean equals (Object o)
  306. {
  307. return o instanceof Symbol && equals(this, (Symbol) o);
  308. }
  309. public int hashCode ()
  310. {
  311. return name == null ? 0 : name.hashCode();
  312. }
  313. /* #endif */
  314. public final Namespace getNamespace()
  315. {
  316. return namespace;
  317. }
  318. public final void setNamespace (Namespace ns)
  319. {
  320. namespace = ns;
  321. }
  322. /** Conventional value used as a property key for function bindings. */
  323. public static final Symbol FUNCTION = makeUninterned("(function)");
  324. /** Conventional value used as a <code>Symbol</code> name to
  325. * access an <code>Object</code>'s property list.
  326. * A <dfn>property list</dfn> is a list with a even number of
  327. * <code>Pair</code>s, containing alternating keys and values.
  328. * They are used in Common Lisp and Emacs Lisp.
  329. * Kawa (following XEmacs) allows arbitrary objects to have property lists,
  330. * thus the PLIST as used as the name and the object as the property.
  331. * (In the future we'll do somethingg clever so that get(SYMBOL, KEY)
  332. * as the same as getf(get(PLIST, SYMBOL), KEY) - but much faster.)
  333. */
  334. public static final Symbol PLIST = makeUninterned("(property-list)");
  335. public String toString()
  336. {
  337. return toString('P');
  338. }
  339. /** Convert a Symbol to a printable String.
  340. * @param style if 'P' then print prefix if available (otherwise Uri),
  341. if 'U' then print Uri if available (otherwise prefix),
  342. if '+' then print both if available.
  343. */
  344. public String toString(char style)
  345. {
  346. // String h = "@"+Integer.toHexString(System.identityHashCode(this));
  347. String uri = getNamespaceURI();
  348. String prefix = getPrefix();
  349. boolean hasUri = uri != null && uri.length() > 0;
  350. boolean hasPrefix = prefix != null && prefix.length() > 0;
  351. String name = getName();
  352. if (hasUri || hasPrefix)
  353. {
  354. StringBuilder sbuf = new StringBuilder();
  355. if (hasPrefix && (style != 'U' || ! hasUri))
  356. sbuf.append(prefix);
  357. if (hasUri && (style != 'P' || ! hasPrefix))
  358. {
  359. sbuf.append('{');
  360. sbuf.append(getNamespaceURI());
  361. sbuf.append('}');
  362. }
  363. sbuf.append(':');
  364. sbuf.append(name);
  365. return sbuf.toString();
  366. }
  367. else
  368. return name;
  369. }
  370. public void writeExternal(ObjectOutput out) throws IOException
  371. {
  372. Namespace ns = getNamespace();
  373. out.writeObject(ns);
  374. out.writeObject(getName());
  375. }
  376. public void readExternal(ObjectInput in)
  377. throws IOException, ClassNotFoundException
  378. {
  379. /* #ifdef JAXP-QName */
  380. // throw new Error("Symbol.readExternal not implemented"); // FIXME!
  381. /* #else */
  382. namespace = (Namespace) in.readObject();
  383. name = (String) in.readObject();
  384. /* #endif */
  385. }
  386. public Object readResolve() throws ObjectStreamException
  387. {
  388. if (namespace == null)
  389. return this;
  390. return make(namespace, getName());
  391. }
  392. }