/*
* Reference ETL Parser for Java
* Copyright (c) 2000-2009 Constantine A Plotnikov
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.sf.etl.parsers.internal.term_parser.flattened;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import net.sf.etl.parsers.ObjectName;
import net.sf.etl.parsers.internal.term_parser.grammar.BlankSyntaxStatement;
import net.sf.etl.parsers.internal.term_parser.grammar.Element;
import net.sf.etl.parsers.internal.term_parser.grammar.ExpressionStatement;
import net.sf.etl.parsers.internal.term_parser.grammar.Let;
import net.sf.etl.parsers.internal.term_parser.grammar.ObjectOp;
import net.sf.etl.parsers.internal.term_parser.grammar.RefOp;
import net.sf.etl.parsers.internal.term_parser.grammar.Sequence;
import net.sf.etl.parsers.internal.term_parser.grammar.Syntax;
import net.sf.etl.parsers.internal.term_parser.grammar.SyntaxDefinition;
import net.sf.etl.parsers.internal.term_parser.grammar.SyntaxStatement;
/**
* A view of definition that must have a root object. This object might
* available directly or through definition. This class provides utility methods
* that are used to extract this object.
*
* @author const
*/
public abstract class ObjectDefinitionView extends DefinitionView {
/**
* Top object of the definition, key is context and value is object. This
* structure is used because due to possible def redefinition actual top
* object might change from context to context.
*/
private final HashMap<ContextView, Syntax> topObjects = new HashMap<ContextView, Syntax>();
/**
* a definition that actually holds a top object. See comment to
* {@link #topObject} for explanation why it is a hash map.
*/
private final HashMap<ContextView, DefinitionView> topObjectDefinitions = new HashMap<ContextView, DefinitionView>();
/**
* The syntax field in syntax definition.
*/
private static final Field DEFINITION_SYNTAX_FIELD;
static {
try {
DEFINITION_SYNTAX_FIELD = SyntaxDefinition.class.getField("syntax");
} catch (Exception e) {
throw new Error("Unexpected failure for getting field syntax", e);
}
}
/**
* The syntax field in sequence.
*/
private static final Field SEQUENCE_SYNTAX_FIELD;
static {
try {
SEQUENCE_SYNTAX_FIELD = Sequence.class.getField("syntax");
} catch (Exception e) {
throw new Error("Unexpected failure for getting field syntax", e);
}
}
/**
* The name field in syntax definition.
*/
private static final Field NAME_FIELD;
static {
try {
NAME_FIELD = SyntaxDefinition.class.getField("syntax");
} catch (Exception e) {
throw new Error("Unexpected failure for getting field syntax", e);
}
}
/**
* A constructor
*
* @param context
* a defining context
* @param definition
* a definition
*/
public ObjectDefinitionView(ContextView context, SyntaxDefinition definition) {
super(context, definition);
}
/**
* A constructor used to implement grammar includes
*
* @param context
* a including context
* @param definition
* a definition view
*/
public ObjectDefinitionView(ContextView context, DefinitionView definition) {
super(context, definition);
}
/**
* This method gets top object for definition. Object definitions must have
* exactly one top object. However this object might be created either
* directly in the object definition or indirectly through def/ref
* construct.
*
* @param context
* a context use to resolve definitions
* @return a top object of the statement.
*/
public ObjectOp topObject(ContextView context) {
ObjectOp rc = (ObjectOp) topObjects.get(context);
if (rc == null) {
extractTopObject(context);
rc = (ObjectOp) topObjects.get(context);
}
return rc;
}
/**
* Get name of top object
*
* @param context
* a context to get name
* @return an top object
*/
public ObjectName topObjectName(ContextView context) {
ObjectOp topObject = topObject(context);
if (topObject == null) {
return null;
}
net.sf.etl.parsers.internal.term_parser.grammar.ObjectName name = topObject.name;
return topObjectDefinition(context).convertName(name);
}
/**
* Get a definition that actually defines top object
*
* @param context
* a context use to resolve definitions
* @return a definition that contains the top object of the statement. It
* might be either statement or def referenced in the statement.
*/
public DefinitionView topObjectDefinition(ContextView context) {
DefinitionView rc = topObjectDefinitions.get(context);
if (rc == null) {
extractTopObject(context);
rc = topObjectDefinitions.get(context);
}
return rc;
}
/**
* This method extracts and finds top object and saves it and its definition
* to variables. The method ignored blank statements. The method tried
* default namespace and if not found, it reports as error if it encounters
* anything beyond object or blank statement at that level. However the
* check is not deep right now.
*
* @param context
* a context use to resolve definitions
*/
private void extractTopObject(ContextView context) {
extractTopObject(new HashSet<DefinitionView>(), this, context);
}
/**
* Extract top object from view. If object cannot be extracted, follow def
* chain further.
*
* @param visited
* a set that is used to detect loops in the definitions
* @param view
* a view to extract.
* @param context
* a context use to resolve definitions
*/
private void extractTopObject(Set<DefinitionView> visited,
DefinitionView view, ContextView context) {
if (!enterDefContext(visited, view)) {
return;
}
for (final Object o : view.definition().syntax) {
final SyntaxStatement stmt = (SyntaxStatement) o;
if (stmt instanceof BlankSyntaxStatement) {
// Ignore the thing. Blank statements do not contain
// anything significant.
continue;
} else if (stmt instanceof Let) {
ObjectOp op = makeDefaultObject(context, view, stmt);
if (op == null) {
error(view, stmt, "grammar.ObjectDefinition.misplacedLet",
definition().name, view.includingContext().name(),
view.includingContext().grammar().getSystemId());
}
break;
} else if (stmt instanceof ExpressionStatement) {
final ExpressionStatement exprStmt = (ExpressionStatement) stmt;
final Syntax s = exprStmt.syntax;
if (s instanceof ObjectOp) {
if (topObjects.containsKey(context)) {
// Additional top object has been found.
// This is an error because only one top object
// is allowed for object definitions.
error(view, stmt,
"grammar.ObjectDefinition.duplicateTopObject",
definition().name, view.includingContext()
.name(), view.includingContext()
.grammar().getSystemId());
} else {
topObjects.put(context, s); // object expression
topObjectDefinitions.put(context, view); // actual
// definition
}
continue;
} else if (s instanceof RefOp) {
final RefOp r = (RefOp) s;
final DefView d = context.def(r.name);
if (d == null) {
error(view, r, "grammar.Ref.danglingRef", r.name);
continue;
} else {
extractTopObject(visited, d, context);
}
} else {
makeDefaultObject(context, view, s);
break;
}
}
}
leaveDefContext(visited, view);
if (visited.size() == 0 && topObjects.get(context) == null) {
makeDefaultObject(context, view, view.definition());
}
}
/**
* Leave definition context
*
* @param visited
* a set of visited definitions
* @param view
* a definition view that was visited
*/
protected void leaveDefContext(Set<DefinitionView> visited,
DefinitionView view) {
visited.remove(view);
}
/**
* Enter definition context
*
* @param visited
* a set of visited definitions
* @param view
* a definition view that is about to be visited
* @return true if visiting unvisited definition.
*/
protected boolean enterDefContext(Set<DefinitionView> visited,
DefinitionView view) {
if (visited.contains(view)) {
error(view, view.definition(), "grammar.Def.cyclicDefinition", view
.includingContext().name(), view.includingContext()
.grammar().getSystemId());
return false;
}
visited.add(view);
return true;
}
/**
* Make a default object expression in the case when the top level element
* is not an object.
*
* @param context
* an object context
* @param view
* the definition that contains element
* @param element
* the element that forces object creation
* @return the object create expression or null if the grammar does not have
* a default namespace
*/
private ObjectOp makeDefaultObject(ContextView context,
DefinitionView view, Element element) {
GrammarView grammar = definingContext().grammar();
String defaultNamespacePrefix = grammar.defaultNamespacePrefix();
SyntaxDefinition definition = definition();
if (defaultNamespacePrefix == null) {
error(view, element, "grammar.ObjectDefinition.noDefaultGrammar",
definition.name, view.definingContext().name(), view
.definingContext().grammar().getSystemId());
return null;
}
ObjectOp o = new ObjectOp();
o.ownerObject = definition;
o.ownerFeature = DEFINITION_SYNTAX_FIELD;
o.start = definition.start;
o.end = definition.end;
net.sf.etl.parsers.internal.term_parser.grammar.ObjectName name = new net.sf.etl.parsers.internal.term_parser.grammar.ObjectName();
name.ownerObject = o;
name.ownerFeature = NAME_FIELD;
name.start = o.start;
name.end = o.end;
name.prefix = defaultNamespacePrefix;
name.name = definition.name;
o.name = name;
Sequence s = new Sequence();
s.syntax.addAll(definition.syntax);
s.start = o.start;
s.end = o.end;
s.ownerObject = o;
s.ownerFeature = SEQUENCE_SYNTAX_FIELD;
o.syntax = s;
topObjects.put(context, o); // object expression
topObjectDefinitions.put(context, view); // actual
// definition
return o;
}
}