/*______________________________________________________________________________ * * 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
However, you must always synchronize on a container when
calling any of the
It is safe to use the |
* 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/*
* 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/*