/*______________________________________________________________________________ * * org.eidola.kernel.Container * * Part of the Eidola Kernel Reference Implementation * See http://eidola.org for oodles of relevant fun! * *______________________________________________________________________________ * * Copyright 1998,2000-2001 Paul Cantrell * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License version 2, as published by the * Free Software Foundation. See the file LICENSE.html for more information. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY, including the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc. / 59 Temple * Place, Suite 330 / Boston, MA 02111-1307 / USA. *_______________________________________________________________________________ */ package org.eidola.kernel; import org.eidola.kernel.event.*; import org.eidola.kernel.event.EventListener; import org.eidola.kernel.error.*; import org.eidola.util.Util; import java.util.*; /** The general structural unit of an Eidola program.

Every container has a structure, which is the set of properties which affect its semantic meaning. Containers notify listeners of changes to their structure by broadcasting {@link StructureChanged} events, and keep a modification count of the structure, accessible through {@link #getStructureVersion()}.

Some information about a container, such as its {@link Compilation#getContents() contents} or {@link Compilation#getErrors() errors}, is derived from the fundamental structure, and lives inside a {@link Compilation}. Compilations update in a lazy manner designed to make user interfaces as responsive as possible. When some structure change -- in this container or another -- requires a compilation, the container broadcasts a {@link CompileRequired} event. A separate thread then takes responsibility for building the new Compilation. For more information on the compile process, see {@link Compiler}.

Structure:

Note on synchronization:

Containers are concurrent-read-safe, meaning that you can safely call any of the get methods without doing any synchronization If a get method returns a data structure, then that structure is immutable and it's safe to keep a reference to it; any subsequent changes to the container will create a new structure.

However, you must always synchronize on a container when calling any of the set methods. This is the caller's responsibility, which helps avoid excessive and redundant synchronization. An attempt to call a set method from an unsynchronized block will thow an exception.

It is safe to use the get methods while a container is being modified, but you must be ready for inconsistent results. If you need to guarantee that a container is not being modified while you work with it, you can either (1) put it in a synchronized block, or (2) check modification counts before and after, as {@link #compile()} does.

@author Paul Cantrell @version [Development version] */ public abstract class Container extends EventBroadcaster { //------------------------------------------------------ // Constants //------------------------------------------------------ /** Part of a container. * @see org.eidola.kernel.event.StructureChanged */ static public final ContainerPart CONTENTS = new ContainerPart("contents"); //------------------------------------------------------ // Constructors //------------------------------------------------------ /** Creates a new empty container. */ public Container() { compileVersion = structureVersion = 0; compilationSync = new Object(); compilesPending = new LinkedList(); compilation = makeNewCompilation(); selfListener = new EventListener() { public void handleEvent(Event event) { handleSelfEvent((ContainerEvent) event); } }; contentListener = new EventListener() { public void handleEvent(Event event) { handleContentEvent((ContainerEvent) event); } }; Engine engine = Engine.getInstance(); engine.add(this); addListener(selfListener, engine.getPropagatorQ()); // synchronized(this) { broadcastEvent(new ContainerCreated(this)); } //¥ Compiler can pick this up before we're ready! } public void finalize() { System.out.println(this + " finalized"); } //------------------------------------------------------ // Structural properties //------------------------------------------------------ /** Returns the namespace in which this container lives. */ public abstract Namespace getNamespace(); //------------------------------------------------------ // Changes and events //------------------------------------------------------ /** Returns a modification count for all changes requiring compilations. * This is equivalent to the number of {@link CompileRequired}s * this object has broadcast. */ public long getCompileVersion() { return compileVersion; } /** Returns a modification count for this container's structure. * This is equivalent to the number of {@link StructureChanged}s * this object has broadcast. */ public long getStructureVersion() { return structureVersion; } /** Broadcasts an event concerning changes in this container, incrementing * appropriate modification counts. A {@link StructureChanged} * increments the structure version, and a {@link CompileRequired} * increments the compile version. * @param event The event to broadcast. */ public void broadcastEvent(Event event) { Container source = ((ContainerEvent) event).getSource(); if(source != this) throw new IllegalArgumentException( this.toString() + " cannot broadcast an event that comes from " + source); notify(); // Make sure we're synchronized if(event instanceof CompileRequired) { // Increment versions compileVersion++; if(event instanceof StructureChanged) structureVersion++; // We need to send out one CompileCompleted for every // CompileRequired, so we keep the latter in a pool // for compile() to handle later. compilesPending.add(event); } super.broadcastEvent(event); } /** Propagates events from this container. * This handler does nothing by default. */ protected void handleSelfEvent(ContainerEvent event) { } /** Propagates events from contents and other members. * This handler broadcasts a {@link CompileRequired} event when it receives a * a {@link StructureChanged} event signaling that a content's owner or name * has changed. */ protected void handleContentEvent(ContainerEvent event) { if(event instanceof CompileCompleted) { ContainerEvent prop = event.getPropagatedFrom(); if(prop instanceof StructureChanged) { ContainerPart part = ((StructureChanged) prop).getPart(); if(part == Element.OWNER || part == NamedElement.NAME) synchronized(this) { broadcastEvent(new CompileRequired(this, (CompileRequired) prop)); } } } } //------------------------------------------------------ // Semantic conditions and errors //------------------------------------------------------ /** Attachs a new, up-to-date {@link Compilation} to this container. * See {@link Compiler} for information about the compile process. *

* Compilation only happens if the container has changed since it was last * compiled, so redundant calls to this method are entirely fine. * When compilation is done, this method broadcasts a {@link CompileCompleted} * event for each {@link CompileRequired} event the container has broadcast * since the last compilation. *

* @throws CompileAbortedException If the container's structure is modified * during compilation. When this happens, calls to {@link #getCompilation()} * return the old compilation, even if some or all of the compile cycle finished. * The proper response to this exception is to call compile() again later -- * which is what generally happens, since the same structure change that caused * the abort will spawn another compilation! */ public void compile() throws CompileAbortedException { synchronized(compilationSync) { // If the compilation is up to date, then the appropriate // CompileCompleteds have already been sent, and our work // here is done. if(compilation != null && compilation.isCurrent()) return; // Instead of synchronizing on the whole object, Compilation // remembers what verison it's compiling. Then if this // object changes during compilation, we can abort with // a CompileAbortedException, and the compiler thread can // pick it up when the next CompileRequired comes in. Compilation newCompilation; synchronized(this) // Must synch to get version number { newCompilation = makeNewCompilation(); } CompileAbortedException abort = new CompileAbortedException(this, newCompilation.getCompileVersion()); if(debugCompile) System.out.println( " #(c) Compiling " + this + " (" + compilation.getCompileVersion() + " -> " + newCompilation.getCompileVersion() + ")"); try { // Away we go! // (This is where it happens; the rest of this method is just fluff.) newCompilation.run(); } catch(Error error) //¥ Is this OK? Prob. at least want to catch stack overflow { if(!newCompilation.isCurrent()) { if(Container.debugCompile) System.out.println(" #(c) " + abort + " [" + error + "]"); throw abort; } else throw error; } catch(RuntimeException rte) { if(!newCompilation.isCurrent()) { if(Container.debugCompile) System.out.println(" #(c) " + abort + " [" + rte + "]"); throw abort; } else throw rte; } synchronized(this) { // If it's changed, abort and pick it up later. if(!newCompilation.isCurrent()) { if(Container.debugCompile) System.out.println(" #(c) " + abort); throw abort; } // Commit changes compilation = newCompilation; // Send out a CompileCompleted for each CompileRequired // which this compilation covered (there may have been several) for(Iterator i = compilesPending.iterator(); i.hasNext(); ) broadcastEvent( new CompileCompleted( this, (CompileRequired) i.next())); compilesPending.clear(); if(debugCompile) System.out.println( " #(c) Compiled " + this + " (" + compilation.getCompileVersion() + " / " + getCompileVersion() + ")"); } } } /** Creates a new {@link Compilation} of an appropriate type for this container. * This method only exists because Java does not use runtime types to resolve * inner class names; in other words, there are no virtual inner classes. * Under normal circumstances, you will not need to call this method directly; * use {@link #compile()} instead. It is a good idea to synchronize on the * container when calling this method. */ protected abstract Compilation makeNewCompilation(); /** Returns the last completed compilation. This does not include any more recent * compile cycles that are in progress, or were aborted. */ public Compilation getCompilation() { return compilation; } /** Generates and holds potentially computationally expensive derived * structures, and checks the lazy rules of the semantics. * To access this derived information, call {@link Container#getCompilation()}. *

* See {@link Compiler} for information about the compile process. *

* This class is not synchronized, but is concurrent read safe. */ public abstract class Compilation { /** Creates an empty compilation. */ public Compilation() { compileVersion = Container.this.compileVersion; errors = Collections.EMPTY_LIST; contents = Collections.EMPTY_SET; } /** Returns the compile version of the container which this * compilation corresponds to. */ public long getCompileVersion() { return compileVersion; } /** Determines whether this compilation reflects the most recent * version of its associated container. Note that this does * not nececssarily mean that the Compilation is completely * up-to-date; under certain circumstances, dependencies between * the derived structures of two different containers can take * multiple compilations to resolve. */ public boolean isCurrent() { return compileVersion == Container.this.compileVersion; } /** Determines whether this compilation was successful. This * is the same as asking whether the list of errors is empty. */ public boolean hasErrors() { return !errors.isEmpty(); } /** Returns the LazySemanticViolations from this compilation, * or an empty list if the compilation was sucessful. */ public List/**/ getErrors() { return errors; } /** Returns the contents of this container. */ public Set/**/ getContents() { return contents; } public String toString() { return super.toString() + "Compilation of " + Container.this.toString() + " v" + compileVersion; } /** Performs the compilation. This calls {@link #calculateDerived()} and * {@link #checkLazy()}, in that order. Subclasses can add additional steps. */ public void run() { errors = new LinkedList(); calculateDerived(); checkLazy(); errors = Collections.unmodifiableList(errors); } /** Adds an error to the ones which cropped up during this compilation. */ protected void addError(LazySemanticViolation error) { errors.add(error); } /** Calculates any potentially computation-intensive derived structures. * For all containers, this should include the set of contents. */ protected abstract void calculateDerived(); /** Checks the structure of this container against the lazy rules of the semantics. *

* For all containers, this method checks that named contents have unique names. * Subclasses should override this to do additional checks. */ protected void checkLazy() { // Checking named contents for unique names incurs some overhead -- // it might be more efficient to distribute this to the few classes where // this check is really important. But it's much cleaner this way. if(!contents.isEmpty()) { Set names = new HashSet(); for(Iterator i = getContents().iterator(); i.hasNext(); ) { Element cur = (Element) i.next(); if(cur instanceof NamedElement) { String curName = ((NamedElement) cur).getName(); if(!names.contains(curName)) names.add(curName); else addError(new DuplicateName((NamedElement) cur, curName + " is not a unique name within " + Container.this)); } } } } protected Set/**/ contents; private List/**/ errors; private long compileVersion; } //------------------------------------------------------ // Debugging //------------------------------------------------------ /** Does a debug dump of this container to System.out. */ public abstract void dump(int indent); static public boolean debugCompile = false; //------------------------------------------------------ // Private //------------------------------------------------------ private Compilation compilation; private Object compilationSync; private Collection/**/ compilesPending; private long compileVersion, structureVersion; private EventListener selfListener, contentListener; }