001: /*
002: * Package.java
003: *
004: * Copyright (c) 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
005: *
006: * See the file "LICENSE.txt" for information on usage and redistribution
007: * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
008: */
009: package pnuts.lang;
010:
011: import java.io.Serializable;
012: import java.util.Enumeration;
013: import java.util.HashSet;
014: import java.util.Map;
015: import java.util.Hashtable;
016: import java.util.Vector;
017: import org.pnuts.lang.MapPackage;
018: import org.pnuts.lang.PackageMap;
019:
020: /**
021: * This class represents a Pnuts' package (not Java's).
022: */
023: public class Package extends SymbolTable implements Property,
024: Serializable, Cloneable {
025: private final static boolean DEBUG = false;
026:
027: static final long serialVersionUID = 2542832545950985547L;
028:
029: /**
030: * All packages with a non-null name
031: */
032: protected transient Hashtable packages;
033:
034: /**
035: * The package with name "".
036: */
037: public final static Package globalPackage;
038:
039: transient protected Package parent;
040:
041: transient private Vector children;
042:
043: protected transient Package root;
044:
045: protected SymbolTable autoloadTable;
046:
047: protected SymbolTable exportedSymbols; // non-null if the package is used as
048: // a module.
049:
050: protected Vector requiredModuleNames;
051:
052: protected Vector providedModuleNames;
053:
054: protected boolean exports; // true if export() has been explicitly called
055:
056: protected boolean usedAsModule = false;
057:
058: protected boolean initialized = false;
059:
060: transient Object moduleIntializationLock = new Object();
061:
062: private HashSet autoloadingSymbols = new HashSet();
063:
064: /**
065: * The name of the package.
066: *
067: * @serial
068: */
069: protected String name;
070:
071: private static PackageFactory factory;
072:
073: static {
074: try {
075: String prop = Runtime.getProperty("pnuts.package.factory");
076: if (prop != null) {
077: Object obj = Class.forName(prop).newInstance();
078: if (obj instanceof PackageFactory) {
079: factory = (PackageFactory) obj;
080: }
081: }
082: } catch (Throwable e) {/* skip */
083: }
084:
085: globalPackage = getInstance("", null, null);
086: }
087:
088: /**
089: * @return the global package
090: */
091: public static Package getGlobalPackage() {
092: return globalPackage;
093: }
094:
095: /**
096: * Returns a Package object that wrap the specified Map
097: */
098: public static Package wrap(Map map) {
099: return new MapPackage(map);
100: }
101:
102: /**
103: * Returns a Map object that wraps this package
104: */
105: public Map asMap() {
106: return new PackageMap(this );
107: }
108:
109: /**
110: * Creates an uninitialized instance of a Package subclass When a
111: * sub-package is created, this method is called.
112: *
113: * @param name
114: * the package name
115: * @return a Package object
116: */
117: public Package newInstance(String name) {
118: return new Package(name);
119: }
120:
121: /*
122: * Creates a package specifying the parent package. If parent is not null,
123: * an instance of the same class as parent is created.
124: */
125: static Package getInstance(String name, Package parent,
126: Context context) {
127: Package p = null;
128: if (parent != null) {
129: if (name != null) {
130: Hashtable tab = context.rootPackage.packages;
131: p = (Package) tab.get(name);
132: if (p != null) {
133: return p;
134: }
135: }
136: p = parent.newInstance(name);
137:
138: p.parent = parent;
139: p.root = parent.root;
140:
141: if (p.name != null) {
142: context.rootPackage.addPackage(p, context);
143: }
144: return p;
145: } else {
146: if (factory != null) {
147: return factory.createPackage(name, null);
148: } else {
149: return new Package(name, null);
150: }
151: }
152: }
153:
154: /*
155: * Registers a sub-package to a root package. This method is called only by
156: * a root package.
157: *
158: * @param pkg the sub-package @param context the context in which this
159: * operation is executed
160: */
161: protected void addPackage(Package pkg, Context context) {
162: packages.put(pkg.name, pkg);
163: Package parent = pkg.parent;
164: if (parent != null) {
165: Vector children = parent.children;
166: if (children == null) {
167: parent.children = children = new Vector(10);
168: children.addElement(pkg);
169: } else if (!children.contains(pkg)) {
170: children.addElement(pkg);
171: }
172: }
173: }
174:
175: /*
176: * Unregisters a sub-package from a root package. This method is called only
177: * by a root package.
178: *
179: * @param pkg the sub-package @param context the context in which this
180: * operation is executed
181: */
182: protected void removePackage(Package pkg, Context context) {
183: Package p2 = pkg.parent;
184: if (p2 == null) {
185: return;
186: }
187: if (p2.children != null) {
188: p2.children.removeElement(pkg);
189: }
190: packages.remove(pkg.name);
191: }
192:
193: /**
194: * If package "pkg" exists returns the package, otherwise creates and
195: * returns it.
196: *
197: */
198: public static Package getPackage(String pkg) {
199: return getPackage(pkg, null);
200: }
201:
202: /**
203: * If package "pkg" exists returns the package, otherwise creates and
204: * returns it.
205: */
206: public static Package getPackage(String pkg, Context context) {
207: Package rootPackage;
208: if (context == null) {
209: context = Runtime.getThreadContext();
210: }
211: if (context == null) {
212: rootPackage = globalPackage;
213: } else {
214: rootPackage = context.rootPackage;
215: }
216: Hashtable packages = rootPackage.packages;
217: Package p = rootPackage;
218: Package existing = (Package) packages.get(pkg);
219: if (existing != null) {
220: return existing;
221: }
222: int index = pkg.indexOf("::");
223: String rest = pkg;
224: StringBuffer sbuf = new StringBuffer();
225: while (index > 0) {
226: sbuf.append(rest.substring(0, index));
227: String s = sbuf.toString();
228: if (rootPackage.get(s, null) instanceof Class) {
229: throw new PnutsException(
230: "package name and Class conflicted: " + pkg,
231: context);
232: }
233: p = getInstance(s, p, context);
234: rest = rest.substring(index + 2);
235: index = rest.indexOf("::");
236: sbuf.append("::");
237: }
238: sbuf.append(rest);
239: return getInstance(sbuf.toString(), p, context);
240: }
241:
242: /*
243: * Returns the module initialization script name.
244: */
245: String getInitScript() {
246: String s = getInitScript(name, "::");
247: return getInitScript(s, ".") + "/init";
248: }
249:
250: static String getInitScript(String name, String delimiter) {
251: int index = name.indexOf(delimiter);
252: int len = delimiter.length();
253: int start = 0;
254: StringBuffer sbuf = new StringBuffer();
255: while (index > 0) {
256: sbuf.append(name.substring(start, index));
257: sbuf.append('/');
258: start = index + len;
259: index = name.indexOf(delimiter, start);
260: }
261: sbuf.append(name.substring(start));
262: return sbuf.toString();
263: }
264:
265: /**
266: * Checks if the specified name is already defined in this package.
267: *
268: * @return true if name is defined in the package.
269: */
270: public boolean defined(String name, Context context) {
271: return lookup0(name) != null;
272: }
273:
274: /**
275: * Get the value of a symbol in the package. When the symbol is not defined
276: * in the package, first, the associated autoloading hook is invoked if any,
277: * second, get the value in the parent package.
278: *
279: * @param symbol
280: * an interned name in the package
281: * @param context
282: * the context in which the symbol is referenced. null means "not
283: * specified".
284: * @return the value of specified variable in the package.
285: */
286: public Object get(String symbol, Context context) {
287: Value b = lookupRecursively(symbol, context);
288: if (b == null) {
289: return null;
290: } else {
291: return b.get();
292: }
293: }
294:
295: /**
296: * Set a value of a symbol in the package. If this package is "used" in the
297: * specified context, and if the target object is either a Class object or a
298: * function whose name matches the symbol, then the symbol is imported to
299: * the context.
300: *
301: * @param symbol
302: * an interned name of variable
303: * @param obj
304: * the value of the variable
305: */
306: public void set(String symbol, Object obj, Context context) {
307: try {
308: set(symbol, obj);
309: } catch (IllegalStateException e) {
310: throw new PnutsException("constant.modification",
311: new Object[] { symbol }, context);
312: }
313: if (DEBUG) {
314: System.out.println(symbol + "<=" + obj + "; " + getClass()
315: + "@" + hashCode());
316: }
317: if (usedAsModule) {
318: Binding b = exportedSymbols.lookup0(symbol);
319: if (b != null) {
320: b.value = obj;
321: }
322: }
323: }
324:
325: /**
326: * Exports a symbol of the module
327: *
328: * @param name
329: * the symbol
330: * @exception IllegalStateException
331: * when the package is not used as a module.
332: */
333: public void export(String name) {
334: if (!usedAsModule) {
335: throw new IllegalStateException("exporting " + name
336: + " in " + getName());
337: }
338: name = name.intern();
339: Binding b = lookup0(name);
340:
341: if (b != null) {
342: if (DEBUG) {
343: System.out.println("export " + name + "<=" + b.value
344: + "; " + exportedSymbols);
345: }
346: exportedSymbols.set(name, b.value);
347: } else {
348: if (autoloadTable != null) {
349: b = autoloadTable.lookup0(name);
350: if (b != null) {
351: b.value = new DelayedExports(
352: (AutoloadHook) b.value, false);
353: }
354: }
355: }
356: exports = true;
357: }
358:
359: class DelayedExports implements AutoloadHook, Serializable {
360: private AutoloadHook hook;
361:
362: private boolean func;
363:
364: DelayedExports(AutoloadHook hook, boolean onlyFunction) {
365: this .hook = hook;
366: this .func = onlyFunction;
367: }
368:
369: public void load(String name, Context context) {
370: if (DEBUG) {
371: System.out.println("invoking autoload hook: " + hook);
372: }
373: Context c = (Context) context.clone();
374: c.setCurrentPackage(Package.this );
375: hook.load(name, c);
376: if (func) {
377: Binding v = lookup0(name);
378: if (v != null) {
379: exportFunction(name, v.value);
380: }
381: } else {
382: if (DEBUG) {
383: System.out.println("export: " + name + "; "
384: + Package.this .getClass() + "@"
385: + Package.this .hashCode());
386: }
387: export(name);
388: }
389: }
390: }
391:
392: /*
393: * Exports all functions in this package, as well as autoloaded symbols.
394: * This method is called only when a module is initialized but export() has
395: * not been called to specify exported symbols of the module.
396: */
397: void exportFunctions() {
398: for (Enumeration e = bindings(); e.hasMoreElements();) {
399: Binding b = (Binding) e.nextElement();
400: exportFunction(b.name, b.value);
401: }
402: if (autoloadTable != null) {
403: for (Enumeration e = autoloadTable.bindings(); e
404: .hasMoreElements();) {
405: Binding b = (Binding) e.nextElement();
406: final AutoloadHook hook = (AutoloadHook) b.value;
407: b.value = new DelayedExports(hook, true);
408: }
409: }
410: }
411:
412: void exportFunction(String sym, Object value) {
413: if (value instanceof PnutsFunction) {
414: PnutsFunction f = (PnutsFunction) value;
415: if (f.getName() == sym) {
416: exportedSymbols.set(sym, f);
417: }
418: }
419: }
420:
421: /**
422: * Deletes a symbol from the package.
423: *
424: * @param symbol
425: * a name of variable to be deleted
426: */
427: public void clear(String symbol, Context context) {
428: removeBinding(symbol);
429: }
430:
431: /**
432: * Removes the specified package.
433: *
434: * @deprecated replaced by remove(String, Context)
435: */
436: public static void remove(String name) {
437: Package root = globalPackage;
438: Package p = (Package) root.packages.get(name);
439: if (p == null) {
440: return;
441: }
442: root.removePackage(p, null);
443: }
444:
445: /**
446: * Removes the specified package.
447: */
448: public static void remove(String name, Context context) {
449: Package root = context.rootPackage;
450: Package p = (Package) root.packages.get(name);
451: if (p == null) {
452: return;
453: }
454: root.removePackage(p, context);
455: }
456:
457: /**
458: * Find a named package.
459: *
460: * @param pkg
461: * a name of package to look.
462: * @return a package with name "pkg" if it exits.
463: *
464: * @deprecated replaced by find(String, Context)
465: */
466: public static Package find(String pkg) {
467: return (Package) globalPackage.packages.get(pkg);
468: }
469:
470: /**
471: * Find a named package.
472: *
473: * @param pkg
474: * a name of package to look.
475: * @return a package with name "pkg" if it exits.
476: */
477: public static Package find(String pkg, Context context) {
478: return (Package) context.rootPackage.packages.get(pkg);
479: }
480:
481: /**
482: * Creates a package that is not visible from other packages.
483: */
484: public Package() {
485: this (null);
486: }
487:
488: /**
489: * Creates a package and register it in a static hashtable.
490: *
491: * @param name
492: * the name of the package
493: */
494: public Package(String name) {
495: this (name, globalPackage);
496: }
497:
498: /**
499: * Creates a package and register it in a static hashtable. The method
500: * <tt>get()</tt> tries to find a symbol in this package and then consult
501: * the parent package. Other instance methods, such as
502: * <tt>set(), defined()</tt>, operates on this package only. Other
503: * constructors implicitly specify the global package as the parent package.
504: *
505: * @param name
506: * the name of the package
507: * @param parent
508: * the parent package.
509: */
510: public Package(String name, Package parent) {
511: if (name != null) {
512: this .name = name.intern();
513: }
514: this .parent = parent;
515: this .root = (parent == null) ? this : parent.root;
516: }
517:
518: /**
519: * Creates a package and register it in a static hashtable. The method
520: * <tt>get()</tt> tries to find a symbol in this package and then consult
521: * the parent package. Other instance methods, such as
522: * <tt>set(), defined()</tt>, operates on this package only. Other
523: * constructors implicitly specify the global package as the parent package.
524: *
525: * @param name
526: * the name of the package
527: * @param parent
528: * the parent package.
529: * @param root
530: * the root package.
531: */
532: protected Package(String name, Package parent, Package root) {
533: if (name != null) {
534: this .name = name.intern();
535: }
536: this .parent = parent;
537: this .root = root;
538: }
539:
540: /**
541: * This method is called when the package become the current package with
542: * package() function. This method in a subclass must call
543: * super.init(context) first.
544: */
545: protected synchronized void init(Context context) {
546: if (this .root == this && this .packages == null) {
547: Hashtable tab = new Hashtable(10);
548: if (name != null) {
549: tab.put(name, this );
550: }
551: this .packages = tab;
552: }
553: }
554:
555: /**
556: * @return the name of the package.
557: */
558: public String getName() {
559: return name;
560: }
561:
562: /**
563: * Returns the parent package.
564: */
565: public Package getParent() {
566: return parent;
567: }
568:
569: /**
570: * Looks up a symbol in this package. If not defined, the associated
571: * autoloading hook is invoked if any.
572: *
573: * @param symbol
574: * an interned String
575: * @param context
576: * the context
577: * @return a NamedValue
578: */
579: public NamedValue lookup(String symbol, Context context) {
580: NamedValue v = lookup0(symbol);
581: if (v != null) {
582: return v;
583: }
584: synchronized (this ) {
585: SymbolTable tab = autoloadTable;
586: if (tab != null) {
587: AutoloadHook hook = (AutoloadHook) tab.get(symbol);
588: if (hook != null) {
589: if (DEBUG) {
590: System.out.println("invoking autoload hook: "
591: + hook);
592: }
593: if (autoloadingSymbols.contains(symbol)) {
594: return null;
595: }
596: autoloadingSymbols.add(symbol);
597: try {
598: hook.load(symbol, context);
599: } finally {
600: autoloadingSymbols.remove(symbol);
601: }
602: v = lookup0(symbol);
603: if (v != null) {
604: return v;
605: }
606: }
607: }
608: }
609: return null;
610: }
611:
612: /**
613: * Lookup the symbol in the package. When the symbol is not defined in the
614: * package and this.parent is not null, lookup the symbol in the parent
615: * package.
616: *
617: * Resulting value is an instance of NamedValue.
618: *
619: * @param symbol
620: * intern'ed string
621: */
622: protected NamedValue lookupRecursively(String symbol,
623: Context context) {
624: NamedValue v = lookup(symbol, context);
625: if (v == null && parent != null) {
626: return parent.lookupRecursively(symbol, context);
627: } else {
628: return v;
629: }
630: }
631:
632: NamedValue lookupExportedSymbol(String symbol, Context context) {
633: if (!usedAsModule) {
634: return null;
635: }
636: if (DEBUG) {
637: System.out.println("lookup " + symbol + " in " + getName()
638: + ":" + getClass() + "@" + hashCode());
639: }
640:
641: Binding v = exportedSymbols.lookup0(symbol);
642: if (v != null) {
643: return v;
644: }
645: synchronized (this ) {
646: SymbolTable tab = autoloadTable;
647: if (tab != null) {
648: AutoloadHook hook = (AutoloadHook) tab.get(symbol);
649: if (hook != null) {
650: if (DEBUG) {
651: System.out.println("invoking autoload hook: "
652: + hook + "; " + getClass() + "@"
653: + hashCode());
654: }
655: if (autoloadingSymbols.contains(symbol)) {
656: return null;
657: }
658: autoloadingSymbols.add(symbol);
659: try {
660: hook.load(symbol, context);
661: } finally {
662: autoloadingSymbols.remove(symbol);
663: }
664: if (usedAsModule) {
665: v = exportedSymbols.lookup0(symbol);
666: if (DEBUG) {
667: System.out
668: .println("("
669: + exportedSymbols.size()
670: + "):" + v);
671: }
672: if (v != null) {
673: return v;
674: }
675: }
676: }
677: }
678: }
679: return null;
680: }
681:
682: /**
683: * Starts using this package as a module.
684: */
685: protected synchronized void initializeModule() {
686: if (DEBUG) {
687: System.out.println("initializeModule:" + getName() + "@"
688: + hashCode());
689: }
690: this .exportedSymbols = new SymbolTable();
691: this .requiredModuleNames = new Vector();
692: this .providedModuleNames = new Vector();
693: usedAsModule = true;
694: }
695:
696: /**
697: * Enumerates sub-packages
698: *
699: * @deprecated
700: */
701: public Enumeration elements() {
702: if (children == null) {
703: children = new Vector(10);
704: }
705: return children.elements();
706: }
707:
708: /**
709: * Returns a clone package.
710: *
711: *
712: */
713: public Object clone() {
714: if (autoloadTable == null) {
715: autoloadTable = new SymbolTable();
716: }
717: Package p = (Package) super .clone();
718: if (root == this ) {
719: p.root = p;
720: if (packages != null) {
721: Hashtable tab = (Hashtable) packages.clone();
722: for (Enumeration e = tab.keys(); e.hasMoreElements();) {
723: String pkgName = (String) e.nextElement();
724: Package pkg = (Package) tab.get(pkgName);
725: if (pkg.root == pkg) {
726: tab.put(pkgName, p);
727: } else if (pkg.root == root) {
728: pkg.root = p;
729: }
730: }
731: p.packages = tab;
732: if (name != null) {
733: tab.put(name, p);
734: }
735: }
736: if (children != null) {
737: p.children = (Vector) children.clone();
738: }
739: } else {
740: if (usedAsModule) {
741: p.exportedSymbols = (SymbolTable) exportedSymbols
742: .clone();
743: p.requiredModuleNames = (Vector) requiredModuleNames
744: .clone();
745: p.providedModuleNames = (Vector) providedModuleNames
746: .clone();
747: }
748: }
749: return p;
750: }
751:
752: /**
753: * Registers an autoload script for the <em>name</em>. If <em>name</em>
754: * is not defined when accessed, the registerred <em>file</em> is loaded.
755: *
756: * @param name
757: * variable name
758: * @param file
759: * the file
760: * @param context
761: * the context
762: */
763: public void autoload(String name, String file, Context context) {
764: if (file == null) {
765: autoload(name, (AutoloadHook) null);
766: } else {
767: autoload(name, new Runtime.AutoloadScript(file, context));
768: }
769: }
770:
771: /**
772: * Registers an AutoloadHook for the <em>name</em>. If <em>name</em> is
773: * not defined when accessed, the registerred AutoloadHook is executed.
774: *
775: * @param name
776: * variable name
777: * @param hook
778: * the AutoloadHook
779: */
780: public void autoload(String name, AutoloadHook hook) {
781: if (autoloadTable == null) {
782: autoloadTable = new SymbolTable();
783: }
784: name = name.intern();
785: if (hook == null) {
786: autoloadTable.removeBinding(name);
787: } else {
788: autoloadTable.set(name, hook);
789: }
790: }
791:
792: public String toString() {
793: if (name != null) {
794: return "package \"" + name + "\"";
795: } else {
796: return "package null";
797: }
798: }
799: }
|