001: // Copyright (c) 1996-2000, 2002, 2004, 2006 Per M.A. Bothner.
002: // This is free software; for terms and warranty disclaimer see ./COPYING.
003:
004: package gnu.mapping;
005:
006: import java.io.*;
007:
008: // Enable JAXP-QName if Symbol should extend javax.xml.namespace.QName.
009: // This is not the default, even when JAXP-1.3 is defined, because it makes
010: // symbols bigger and some operations (such as equals and handling of
011: // uninterned symbols) slower, without much benefit.
012: // This option needs some work.
013: /* #ifdef JAXP-QName */
014: // import javax.xml.namespace.QName;
015: /* #endif */
016:
017: /** A Symbol is a name, usually in a specific Namespace.
018: * A Symbol is stateless: Comon Lisp-style "value", "function" and
019: * "property list" bindings are not part of the Symbol itself, but
020: * looked up in the current Environment.
021: * A <code>Symbol</code> may be viewed as an <code>EnvironmentKey</code>
022: * with a <code>null</code> property component.
023: */
024:
025: public class Symbol
026: /* #ifdef JAXP-QName */
027: // extends QName
028: /* #endif */
029: implements EnvironmentKey,
030: /* #ifdef JAVA2 */
031: Comparable,
032: /* #endif */
033: Externalizable {
034: /* #ifndef JAXP-QName */
035: protected String name;
036: /* #endif */
037: Namespace namespace;
038:
039: public final Symbol getKeySymbol() {
040: return this ;
041: }
042:
043: public final Object getKeyProperty() {
044: return null;
045: }
046:
047: public boolean matches(EnvironmentKey key) {
048: return equals(key.getKeySymbol(), this )
049: && key.getKeyProperty() == null;
050: }
051:
052: public boolean matches(Symbol symbol, Object property) {
053: return equals(symbol, this ) && property == null;
054: }
055:
056: /* #ifndef JAXP-QName */
057: public final String getNamespaceURI() {
058: Namespace ns = getNamespace();
059: return ns == null ? null : ns.getName();
060: }
061:
062: public final String getLocalPart() {
063: return name;
064: }
065:
066: public final String getPrefix() {
067: Namespace ns = namespace;
068: return ns == null ? "" : ns.prefix;
069: }
070:
071: /* #endif */
072:
073: public final boolean hasEmptyNamespace() {
074: Namespace ns = getNamespace();
075: String nsname;
076: return (ns == null || (nsname = ns.getName()) == null || nsname
077: .length() == 0);
078: }
079:
080: /** Synonym for getName - the "print name" of the symbol without Namespace.
081: * Useful when thinking of a Symbol as an XML QName. */
082: public final String getLocalName() {
083: /* #ifdef JAXP-QName */
084: // return getLocalPart();
085: /* #else */
086: return name;
087: /* #endif */
088: }
089:
090: public final String getName() {
091: /* #ifdef JAXP-QName */
092: // return getLocalPart();
093: /* #else */
094: return name;
095: /* #endif */
096: }
097:
098: /** Find or create a symbol in a specificed namespace.
099: * @param uri a namespace uri.
100: * @param name The "local name" or "print name" of the desired symbol.
101: * @param prefix namespace prefix, or {@code ""}
102: */
103: public static Symbol make(String uri, String name, String prefix) {
104: return Namespace.make(uri, prefix).getSymbol(name.intern());
105: }
106:
107: /** Find or create a symbol in a specificed namespace.
108: * @param namespace can be an Namespace, or a namespace/environment name
109: * (resolved using Namespace.getInstance), or null (in which case
110: * an uninterned symbol is created).
111: * @param name The "local name" or "print name" of the desired symbol.
112: */
113: public static Symbol make(Object namespace, String name) {
114: Namespace ns = namespace instanceof String ? Namespace
115: .getInstance((String) namespace)
116: : (Namespace) namespace;
117: if (ns == null || name == null)
118: return makeUninterned(name);
119: return ns.getSymbol(name.intern());
120: }
121:
122: /** Parse a String as a Symbol.
123: * Recognizes:
124: * <ul>
125: * <li>{@code "{namespace-uri}local-name"} - which creates a
126: * symbol with that namespace-uri and an empty prefix;
127: * </li>
128: * <li>{@code "prefix:local-name"}- which creates a symbol with that prefix
129: * and an "unknown" namespace-uri, using {@link #makeWithUnknownNamespace};
130: * </li>
131: * <li>and plain {@code "local-name"} - which creates a symbol in
132: * {@link Namespace#EmptyNamespace}.
133: * </li></ul>
134: */
135: public static Symbol parse(String symbol) {
136: if (symbol.length() > 0 && symbol.charAt(0) == '{') {
137: int rbrace = symbol.lastIndexOf('}');
138: if (rbrace <= 0) {
139: throw new RuntimeException(
140: "missing '}' in property name '" + symbol + "'");
141: }
142: return Symbol.make(symbol.substring(1, rbrace), symbol
143: .substring(rbrace + 1), "");
144: }
145: int colon = symbol.indexOf(':');
146: if (colon > 0) {
147: return Symbol.makeWithUnknownNamespace(symbol
148: .substring(colon + 1), symbol.substring(0, colon));
149: } else {
150: return Symbol.make("", symbol, "");
151: }
152: }
153:
154: /** Make a placeholder symbol with a known prefix and unknown namespace-uri.
155: * This is convenient for processing definition commands like
156: * {@code "prefix:name=value"} - such as on the Kawa command-line -
157: * where we don't yet know the namespace-uri. Code that later looks
158: * for a value should look both under the true namespace-uri and
159: less LOG.JA * using this method (or {@link Namespace#makeUnknownNamespace}).
160: */
161: public static Symbol makeWithUnknownNamespace(String local,
162: String prefix) {
163: return Namespace.makeUnknownNamespace(prefix).getSymbol(
164: local.intern());
165: }
166:
167: public Symbol() {
168: /* #ifdef JAXP-QName */
169: // super("");
170: /* #endif */
171: }
172:
173: public static Symbol makeUninterned(String name) {
174: /* #ifdef JAXP-QName */
175: // Namespace ns = Namespace.getInstance("kawa.gensym");
176: // String sname = name;
177: // int i = 0;
178: // for (;;)
179: // {
180: // int hash = sname.hashCode();
181: // synchronized (ns)
182: // {
183: // Symbol sym = ns.lookup(sname, hash, false);
184: // if (sym == null)
185: // return ns.add(new Symbol(ns, sname.intern()), hash);
186: // }
187: // sname = name + '.' + ++i;
188: // }
189: /* #else */
190: return new Symbol(null, name);
191: /* #endif */
192: }
193:
194: /** Create new Symbol in a given namespace.
195: * Does not enter the result in the namespace's symbol table.
196: * @param name an interned String
197: */
198: public Symbol(Namespace ns, String name) {
199: /* #ifdef JAXP-QName */
200: // super(ns == null ? "" : ns.getName(), name, ns == null ? "" : ns.prefix);
201: /* #else */
202: this .name = name;
203: /* #endif */
204: this .namespace = ns;
205: }
206:
207: public int compareTo(Object o) {
208: Symbol other = (Symbol) o;
209: if (getNamespaceURI() != other.getNamespaceURI())
210: throw new IllegalArgumentException(
211: "comparing Symbols in different namespaces");
212: return getLocalName().compareTo(other.getLocalName());
213: }
214:
215: public static boolean equals(Symbol sym1, Symbol sym2) {
216: if (sym1 == sym2)
217: return true;
218: if (sym1 == null || sym2 == null)
219: return false;
220: /* #ifdef JAXP-QName */
221: // if (sym1.getLocalPart() == sym2.getLocalPart())
222: /* #else */
223: if (sym1.name == sym2.name)
224: /* #endif */
225: {
226: Namespace namespace1 = sym1.namespace;
227: Namespace namespace2 = sym2.namespace;
228: return (namespace1 != null && namespace2 != null && (namespace1 == namespace2 || namespace1.name == namespace2.name));
229: }
230: return false;
231: }
232:
233: /* #ifndef JAXP-QName */
234: /** Just tests for identity.
235: * Otherwise hashTables that have Symbols as keys will break. */
236: public final boolean equals(Object o) {
237: return o instanceof Symbol && equals(this , (Symbol) o);
238: }
239:
240: public int hashCode() {
241: return name == null ? 0 : name.hashCode();
242: }
243:
244: /* #endif */
245:
246: public final Namespace getNamespace() {
247: return namespace;
248: }
249:
250: public final void setNamespace(Namespace ns) {
251: namespace = ns;
252: }
253:
254: /** Conventional value used as a property key for function bindings. */
255: public static final Symbol FUNCTION = makeUninterned("(function)");
256:
257: /** Conventional value used as a <code>Symbol</code> name to
258: * access an <code>Object</code>'s property list.
259: * A <dfn>property list</dfn> is a list with a even number of
260: * <code>Pair</code>s, containing alternating keys and values.
261: * They are used in Common Lisp and Emacs Lisp.
262: * Kawa (following XEmacs) allows arbitrary objects to have property lists,
263: * thus the PLIST as used as the name and the object as the property.
264: * (In the future we'll do somethingg clever so that get(SYMBOL, KEY)
265: * as the same as getf(get(PLIST, SYMBOL), KEY) - but much faster.)
266: */
267: public static final Symbol PLIST = makeUninterned("(property-list)");
268:
269: public String toString() {
270: // String h = "@"+Integer.toHexString(System.identityHashCode(this));
271: String uri = getNamespaceURI();
272: if (uri == null || uri.length() == 0)
273: return getName();
274: StringBuffer sbuf = new StringBuffer();
275: String prefix = getPrefix();
276: if (prefix == null || prefix.length() == 0) {
277: sbuf.append('{');
278: sbuf.append(getNamespaceURI());
279: sbuf.append('}');
280: } else {
281: sbuf.append(prefix);
282: sbuf.append(':');
283: }
284: sbuf.append(getName());
285: return sbuf.toString();
286: }
287:
288: public void writeExternal(ObjectOutput out) throws IOException {
289: Namespace ns = getNamespace();
290: out.writeObject(ns);
291: out.writeObject(getName());
292: }
293:
294: public void readExternal(ObjectInput in) throws IOException,
295: ClassNotFoundException {
296: /* #ifdef JAXP-QName */
297: // throw new Error("Symbol.readExternal not implemented"); // FIXME!
298: /* #else */
299: namespace = (Namespace) in.readObject();
300: name = (String) in.readObject();
301: /* #endif */
302: }
303:
304: public Object readResolve() throws ObjectStreamException {
305: if (namespace == null)
306: return this;
307: return make(namespace, getName());
308: }
309: }
|