DataModelWorker.java
/*
** Module : DataModelWorker.java
** Abstract : Pattern worker with specific knowledge of the data model
**
** Copyright (c) 2005-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20050426 @21002 Created initial version. Modeled after
** uast.JavaPatternWorker.
** 002 ECF 20050526 @21303 Simplified worker library methods. The new
** expression engine implementation enables us
** to reference objects directly in expressions,
** so much of the worker library implementation
** which jumped through hoops to use AST ids in
** lieu of object references was eliminated.
** 003 ECF 20050614 @21536 Added capability to sort class properties.
** The order associated with the peer field's
** "order" value is used to sort into ascending
** order. Also removed visitAst to prevent root
** node creation when visiting a previously
** generated AST. This is now done from rulesets
** using the createRootAst user function.
** 004 ECF 20050714 @21703 Added capability to sort class ASTs within a
** data model root AST. Classes are sorted by
** dependency, with dependent classes ordered
** after those classes upon which they are
** dependent. Also removed unused methods.
** 005 ECF 20050720 @21776 Modified sortProperties method. Remove
** temporary "order" annotation from properties
** after the sort completes.
** 006 ECF 20050805 @22012 Rewrote algorithm to sort class ASTs by
** dependency. The old approach did not always
** properly resolve dependencies.
** 007 ECF 20051005 @22961 Change to P2O filename generation. Now ".p2o"
** replaces the ".schema" extension, instead of
** being appended to it.
** 008 ECF 20051027 @23215 Added user functions. normalizeUniqueIndexes
** calculates minimal set of unique constraints
** for a list of indexes. nonRedundantIndexes
** calculates the non-redundant subset of a list
** of indexes.
** 009 ECF 20060504 @25998 Fixed property sorting. Collections.sort()
** was not working properly in the temp table
** case. Changed collection type from TreeSet to
** ArrayList.
** 010 GES 20070629 @34339 Removed terse flag from AST persistence so
** that we retain line/column info in our ASTs.
** 011 ECF 20080730 @39270 Reduced memory footprint. Implemented finish
** method to clean up resources. Removed current
** instance variable, which held an unused, hard
** reference to a potentially very large AST.
** 012 GES 20090515 @42219 Moved to AstManager from AstRegistry/AstPersister.
** 013 GES 20090518 @42395 Import change.
** 014 ECF 20121220 Fixed sortClasses to accommodate sequence ASTs,
** which are siblings of class ASTs. This method
** assumed class ASTs were the only children of the
** data model root AST.
** 015 SVL 20130624 Added support for virtual files.
** 016 ECF 20131001 Added virtualFile parameter to additional methods; added
** loadRelatedTree user function.
** 017 ECF 20131031 Fixed regression for runtime conversion.
** 018 OM 20140117 Added fieldId annotation for runtime index access.
** 019 VMN 20140202 Added shift of order attribute for denormalized fields.
** 020 VMN 20140502 Added helper methods for processing backticks.
** 021 ECF 20150328 Added support for cross-schema natural joins.
** 022 ECF 20150715 Replace StringBuffer with StringBuilder.
** 023 ECF 20160215 Fixed cross-referencing of denormalized extent fields to original
** field ASTs in .schema file. If a peerid already exists, don't
** overwrite it. This effectively leaves field AST linked to first,
** exploded, property AST.
** 024 OM 20160905 Small optimization.
** 025 CA 20200412 Added incremental conversion support.
** 026 CA 20200416 Reduce the memory footprint for incremental conversion.
** 027 CA 20200714 Reserved properties have their 'fieldid' already set by SchemaDictionary.
** 028 OM 20200924 P2JIndexComponent carries multiple information to avoid map lookups for them.
** 029 CA 20211214 The AST manager plugin must be context-local, for runtime conversion, as the
** InMemoryRegistryPlugin is not thread-safe.
** All worker's state must be context-local, for runtime conversion to work in
** multi-context mode, as the pattern workers are singletons.
** OM 20220713 The fieldId is kept for all denormalized properties of same legacy field.
** OM 20220721 Added [propertyId] annotation for tables with denormalized fields.
** Fixed [PropertyComparator] implementation.
** 030 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.schema;
import java.io.*;
import java.util.*;
import java.util.function.*;
import java.util.logging.*;
import com.goldencode.ast.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.convert.db.*;
import com.goldencode.p2j.pattern.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.xml.*;
/**
* A pattern worker that provides services to support the conversion of Progress schema data into
* a relational data model AST.
*/
public final class DataModelWorker
extends AbstractConversionWorker
implements DataModelTokenTypes
{
/** Logger. */
private static final ConversionStatus LOG = ConversionStatus.get(DataModelWorker.class);
/** File extension appended to schema filenames */
private static final String P2O_POSTFIX = ".p2o";
/** Context local work area. */
private static final ContextLocal<WorkArea> context = new ContextLocal<WorkArea>()
{
protected WorkArea initialValue()
{
return new WorkArea();
};
};
/**
* Worker routine to centralize creation of new data model ASTs. All ID
* setup and parent linkages are established if the parent AST is passed
* as a parameter, otherwise only an unidentified, standalone AST is
* created. If a parent AST is passed, a resolver instance should also
* be provided.
*
* @param type
* The token type for the data model AST.
* @param text
* The text for the AST.
* @param parent
* The parent AST node or <code>null</code>.
* @param resolver
* The current instance of the {@link AstSymbolResolver} in use
* or <code>null</code>.
*
* @return The AST that was created.
*/
public static DataModelAst createAst(int type,
String text,
Aast parent,
AstSymbolResolver resolver)
{
DataModelAst ast = new DataModelAst();
initializeAst(ast, type, text, parent, resolver);
return ast;
}
/**
* Worker routine for peer AST creation. All ID setup and parent linkages
* are established and bidirectional peer ID annotations are written
* in source and target nodes.
*
* @param type
* The token type for the AST.
* @param text
* The text for the AST.
* @param parent
* The parent AST node.
* @param resolver
* The current instance of the {@link AstSymbolResolver} in use.
*
* @return The AST that was created.
*/
public static DataModelAst createPeerAst(Aast source,
int type,
String text,
Aast parent,
AstSymbolResolver resolver)
{
DataModelAst target = createAst(type, text, parent, resolver);
// save our cross-references; we do not use base class' crossReferencePeerAsts method,
// because we do not want to overwrite an existing peer id, if it exists
if (!source.isAnnotation(PEER_ID))
{
source.putAnnotation(PEER_ID, target.getId());
}
if (!target.isAnnotation(PEER_ID))
{
target.putAnnotation(PEER_ID, source.getId());
}
return target;
}
/**
* Default constructor which initializes libraries.
*/
public DataModelWorker()
{
super();
setLibrary(new Library());
}
/**
* Worker routine to centralize creation of a new data model AST that is
* a root node. A new file ID is registered in the {@link AstManager}
* and set into the AST.
*
* @param peerRoot
* Root node of Progress schema AST currently being processed.
* @param virtualFile
* If <code>true</code> the file is not supposed to be persisted into the file system,
* the file name is used to map some ID to the AST
*
* @return The root AST that was created.
*
* @throws ConfigurationException
* if P2J environment is not properly initialized.
*/
public Aast createRootAst(Aast peerRoot, boolean virtualFile)
throws ConfigurationException
{
String filename = (virtualFile ? peerRoot.getText() : generateFilename(peerRoot));
return createRootAst(peerRoot, filename, virtualFile);
}
/**
* Worker routine to centralize creation of a new data model AST that is
* a root node. A new file ID is registered in the {@link AstManager}
* and set into the AST.
*
* @param peerRoot
* Root node of Progress schema AST currently being processed.
* @param filename
* The file in which to persist the new AST.
* @param virtualFile
* If <code>true</code> the file is not supposed to be persisted into the file system,
* the file name is used to map some ID to the AST
*
* @return The root AST that was created.
*
* @throws ConfigurationException
* if P2J environment is not properly initialized.
*/
public Aast createRootAst(Aast peerRoot, String filename, boolean virtualFile)
throws ConfigurationException
{
WorkArea wa = context.get();
// reset the current/root AST to null
wa.root = null;
getResolver().setTargetRootAst(null);
// create a new target tree
wa.root = createAst(DATA_MODEL, "data model", null, null);
if (virtualFile)
{
initializeRootAst(wa.root, filename, getResolver());
}
else
{
File file = Configuration.toFile(filename);
initializeRootAst(wa.root, file, getResolver());
}
getResolver().setTargetRootAst(wa.root);
// cross-reference peerid annotations
peerRoot.putAnnotation("peerid", wa.root.getId());
wa.root.putAnnotation("peerid", peerRoot.getId());
return wa.root;
}
/**
* Resolve <code>constant</code> to a literal which can be compiled into
* expressions.
*
* @param constant
* Constant indicating a data model token name.
*
* @return Token type associated with the token name.
*/
public Object resolveConstant(String constant)
{
return DataModelAst.resolveConstant(constant);
}
/**
* Hook to provide termination processing for a pattern worker.
*/
public void finish()
{
context.get().root = null;
super.finish();
}
/**
* Generate a unique data model AST persistence filename based on the
* current source AST filename. The source file name's extension is
* removed and replaced with ".p2o".
*
* @param ast
* Current source AST.
*
* @return A unique filename based on the source file root name with
* ".p2o" appended.
*/
private String generateFilename(Aast ast)
{
String filename = ast.getFilename();
filename = filename.substring(0, filename.lastIndexOf("."));
StringBuilder buf = new StringBuilder(filename);
buf.append(P2O_POSTFIX);
return buf.toString();
}
/**
* Tests one class AST for dependence upon another. The dependency test
* includes the tested class <code>x</code>, as well as the entire tree
* of its dependencies. In other words, test whether <code>x</code> <i>or
* any class upon which it is dependent</i> is dependent upon
* <code>y</code>.
*
* @param x
* Class AST node whose dependencies are being checked.
* @param y
* Class AST node against which to test <code>x</code> for
* dependence.
*
* @return <code>true</code> if <code>x</code> (or any of its dependent
* classes) is dependent upon <code>y</code>.
*/
private boolean isXDependentUponY(Aast x, Aast y)
{
Long testId = y.getId();
AstSymbolResolver resolver = AstSymbolResolver.getResolver();
int len = x.annotationSize("dependency");
for (int i = 0; i < len; i++)
{
Long depId = (Long) x.getAnnotation("dependency", i);
Aast depAst = resolver.getAst(depId);
if (depAst == null)
{
// dependency references a different schema; we don't care about these for sorting
// purposes
return false;
}
if (depId.equals(testId) || isXDependentUponY(depAst, y))
{
return true;
}
}
return false;
}
/**
* Helper to create, edit, move and delete data model ASTs.
*/
public class Library
{
/** The XML <code>schema</code> element for the temporary schema. */
private XmlAst schemaElem = null;
/** The list of <code>class</code> elements in dmo_index.xml. */
private List<DMOElementInfo> tempSchema = new LinkedList<>();
/**
* Create a new data model AST that is the root for the data model
* hierarchy. This can later be retrieved with the {@link #getAstRoot}
* user function.
*
* @param peerRoot
* Root node of Progress schema AST currently being processed.
* @param virtualFile
* If <code>true</code> the file is not supposed to be persisted into the file
* system, the file name is used to map some ID to the AST
*
* @return The root AST that was created.
*
* @throws ConfigurationException
* if P2J environment is not properly initialized.
*/
public Aast createRootAst(Aast peerRoot, boolean virtualFile)
throws ConfigurationException
{
return DataModelWorker.this.createRootAst(peerRoot, virtualFile);
}
/**
* Create a new data model AST that is the root for the data model
* hierarchy. This can later be retrieved with the {@link #getAstRoot}
* user function.
*
* @param peerRoot
* Root node of Progress schema AST currently being processed.
* @param filename
* The file in which to persist the new AST.
* @param virtualFile
* If <code>true</code> the file is not supposed to be persisted into the file
* system, the file name is used to map some ID to the AST
*
* @return The root AST that was created.
*
* @throws ConfigurationException
* if P2J environment is not properly initialized.
*/
public Aast createRootAst(Aast peerRoot, String filename, boolean virtualFile)
throws ConfigurationException
{
return DataModelWorker.this.createRootAst(peerRoot, filename, virtualFile);
}
/**
* General purpose data model AST creation function which can create a
* <code>DataModelAst</code> node using the given token type and text.
* The created node will then be attached to the specified parent. This
* is used in cases where the conversion is simple enough to drive
* it completely from the rule set rather than from a custom worker.
*
* @param type
* The token type for the data model AST.
* @param text
* The text for the AST.
* @param parent
* The ID of the parent AST node.
*
* @return The unique ID if the AST was created successfully or
* <code>null</code> if the parent ID is invalid, if the type
* is not valid or on any unexpected error during AST
* creation.
*/
public Aast createAst(int type, String text, Aast parent)
{
return DataModelWorker.createAst(type, text, parent, getResolver());
}
/**
* General purpose data model AST creation function which can create a
* <code>DataModelAst</code> node using the given token type and text.
* The created node will then be attached to the specified parent. Most
* importantly, on success the ID of the generated AST node will be
* stored in the current source AST node's "peerid" annotation. This
* provides a standard mechanism to duplicate the structure of a source
* AST in the target data model AST. The source AST's ID will also
* be stored in the target AST's "peerid" annotation which allows
* subsequent bidirectional referencing between these peers. This
* is used in cases where the conversion is simple enough to drive
* it completely from the rule set rather than from a custom worker.
*
* @param type
* The token type for the data model AST.
* @param text
* The text for the AST.
* @param parent
* The ID of the parent AST node.
*
* @return The unique ID if the AST was created successfully or
* <code>null</code> if the parent ID is invalid, if the type
* is not valid or on any unexpected error during AST
* creation.
*/
public Aast createPeerAst(int type, String text, Aast parent)
{
return DataModelWorker.createPeerAst(getCopy(),
type,
text,
parent,
getResolver());
}
/**
* Return the AST node that is the root of the data model AST hierarchy.
*
* @return Data model AST root node.
*/
public Aast getAstRoot()
{
return context.get().root;
}
/**
* Set the AST node that is the root of the data model AST hierarchy and make it the target
* root AST of the resolver.
*/
public void setAstRoot(Aast root)
{
context.get().root = (DataModelAst) root;
getResolver().setTargetRootAst(root);
}
/**
* Common case for data model AST creation which creates a
* <code>DataModelAst</code> node using the passed token type and text
* and then attaches that node to the parent specified by the "peerid"
* annotation of the source AST's parent node. This greatly simplifies
* creation of entire hierarchies of structure that match between the
* source and target ASTs. This uses the services of {@link
* #createPeerAst(int,String,Aast)}.
*
* @param type
* The token type for the data model AST.
* @param text
* The text for the AST.
*
* @return The unique ID if the AST was created successfully or
* <code>null</code> if the parent ID is invalid, if the type
* is not valid or on any unexpected error during AST
* creation.
*/
public Aast createPeerAst(int type, String text)
{
Long id = (Long) getCopy().getParent().getAnnotation(PEER_ID);
if (id == null)
{
return null;
}
Aast parentPeer = getResolver().getAst(id);
return createPeerAst(type, text, parentPeer);
}
/**
* Returns a formatted string using the specified format string and parameter optionally
* containing backticks.
*
* @param format
* Format string.
* @param parameter
* String parameter optionally containing backticks..
*
* @return Returns a formatted string using the specified format string and parameter
* optionally containing backticks.
*/
public String formatWithEscapedParameter(String format, String parameter)
{
return DBUtils.formatWithEscapedParameter(format, parameter);
}
/**
* Returns a formatted string using the specified format string and arguments optionally
* containing backticks.
*
* @param format
* Format string.
* @param parameter
* String parameter optionally containing backticks..
* @param index
* Nullable value.
*
* @return Returns a formatted string using the specified format string and arguments
* optionally containing backticks.
*/
public String formatWithEscapedParameter(String format, String parameter, Long index)
{
return DBUtils.formatWithEscapedParameter(format, parameter, index);
}
/**
* Returns a formatted string using the specified format string and arguments optionally
* containing backticks.
*
* @param format
* Format string.
* @param table
* String parameter optionally containing backticks.
* @param param
* String parameter optionally containing backticks.
*
* @return Returns a formatted string using the specified format string and arguments
* optionally containing backticks.
*/
public String formatWithEscapedParameters(String format, String table, String param)
{
return DBUtils.formatWithEscapedParameters(format, table, param);
}
/**
* Shifts order attribute for denormalized fields and sorts <code>PROPERTY</code> children
* ASTs of <code>parent</code> in place into ascending order, according to the numeric
* value specified by the "order" annotation. Sibling ASTs of other types are left in
* place.
*
* @param parent
* Parent AST of type <code>CLASS</code>.
*/
public void sortProperties(Aast parent)
{
List<Aast> list = new ArrayList<>();
Aast child = parent.getImmediateChild(PROPERTY, null);
while (child != null)
{
list.add(child);
child = parent.getImmediateChild(PROPERTY, child);
}
list.sort(new PropertyComparator());
// Shift order if repeatable orders are found (used if some properties are denormalized)
Long lastOrder = Long.MIN_VALUE;
Long currentOrder;
long shift = 0;
Iterator<Aast> iter = list.iterator();
while (iter.hasNext())
{
child = iter.next();
currentOrder = (Long) child.getAnnotation("order");
if (currentOrder != null)
{
if (currentOrder.equals(lastOrder))
{
shift++;
}
if (shift > 0)
{
child.putAnnotation("order", shift + currentOrder);
}
lastOrder = currentOrder;
}
}
// Replace temporary "order" annotation with "fieldId" annotation
long fieldId = 0;
long propertyId = 0;
iter = list.iterator();
String lastLegacyName = null;
while (iter.hasNext())
{
child = iter.next();
if (child.isAnnotation("fieldId"))
{
continue;
}
// Commented because this order is used for metadata generation
// child.removeAnnotation("order");
String legacyName = (String) child.getAnnotation("historical");
if (!legacyName.equalsIgnoreCase(lastLegacyName))
{
lastLegacyName = legacyName;
++ fieldId;
}
child.putAnnotation("fieldId", fieldId);
// [fieldId] increments only for each filed but [propertyId] for each property
++ propertyId;
child.putAnnotation("propertyId", propertyId);
}
// if denormalized fields were encountered, the number of properties is larger than those of fields
parent.putAnnotation("hasDenormalizedFields", fieldId != propertyId);
// If list contains 1 or fewer entries, no need to sort.
if (list.size() <= 1)
{
return;
}
Aast preProp = null;
Aast postProp = null;
child = (Aast) parent.getFirstChild();
// Detect first non-PROPERTY child before PROPERTY ASTs.
while (child != null)
{
if (child.getType() == PROPERTY)
{
break;
}
preProp = child;
child = (Aast) child.getNextSibling();
}
// Detect first non-PROPERTY child after PROPERTY ASTs.
while (child != null)
{
if (child.getType() != PROPERTY)
{
postProp = child;
break;
}
child = (Aast) child.getNextSibling();
}
// Determine first and last PROPERTY ASTs, chaining together
// siblings in between.
Aast first = null;
Aast last = null;
iter = list.iterator();
for (int i = 0; iter.hasNext(); i++)
{
child = iter.next();
if (i == 0)
{
first = child;
}
if (last != null)
{
last.setNextSibling(child);
}
last = child;
}
// Link first PROPERTY AST to head of list or to parent.
if (preProp == null)
{
parent.setFirstChild(first);
}
else
{
preProp.setNextSibling(first);
}
// Link last PROPERTY AST to tail of list (may be null).
last.setNextSibling(postProp);
}
/**
* Sorts <code>CLASS</code> children ASTs of <code>parent</code>
* in place into ascending order, according to the association
* dependencies of each class. Classes are sorted after classes upon
* which they have dependencies. Within dependency levels, they are
* sorted alphabetically by class name.
*
* @param parent
* Parent AST of type <code>DATA_MODEL</code>.
*/
public void sortClasses(Aast parent)
{
SortedSet<Aast> set = new TreeSet<Aast>(new ClassComparator());
Aast child = parent.getImmediateChild(CLASS, null);
while (child != null)
{
set.add(child);
child = parent.getImmediateChild(CLASS, child);
}
// If set contains 1 or fewer entries, no need to sort.
if (set.size() <= 1)
{
return;
}
// Remove all CLASS nodes from their original locations.
Iterator<Aast> iter = set.iterator();
while (iter.hasNext())
{
Aast cls = iter.next();
cls.remove();
}
// Classes are now sorted into 2 broad categories: independent and
// dependent. They are also sorted alphabetically within those
// categories. We must now sort out the dependent classes to account
// for the actual dependencies.
List<Aast> list = new LinkedList<Aast>();
iter = set.iterator();
while (iter.hasNext())
{
Aast cls = iter.next();
if (cls.annotationSize("dependency") == 0)
{
// Because of the first level sort, all independent classes
// will always be added before any dependent classes.
list.add(cls);
continue;
}
// Walk backwards through the list until we find a class upon
// which the current class is dependent. Add the current class
// just after it. Not the most efficient algorithm, but it will
// do...
int pos = list.size();
ListIterator<Aast> subIter = list.listIterator(pos);
while (subIter.hasPrevious())
{
Aast next = subIter.previous();
if (isXDependentUponY(cls, next))
{
break;
}
pos--;
}
list.add(pos, cls);
}
/*
// Debug output.
AstSymbolResolver resolver = AstSymbolResolver.getResolver();
iter = list.iterator();
while (iter.hasNext())
{
Aast next = iter.next();
System.out.println(next.getText());
int size = next.annotationSize("dependency");
for (int i = 0; i < size; i++)
{
Long depId = (Long) next.getAnnotation("dependency", i);
System.out.println(" " + resolver.getAst(depId).getText());
}
}
*/
// Re-attach to tree in sorted order. Remove temporary list of
// dependencies from dependent class AST nodes.
iter = list.iterator();
while (iter.hasNext())
{
child = iter.next();
child.removeAnnotation("dependency");
parent.addChild(child);
}
}
/**
* Load an AST from a file with the same name as the currently loaded file, but with the
* given extension instead.
*
* @param extension
* Filename extension of related AST file.
*/
public void loadRelatedTree(String extension)
{
try
{
String file = getSource().getFilename();
int pos = file.lastIndexOf('.');
Aast root = AstManager.get().loadTree(file.substring(0, pos) + "." + extension);
registerTree(root);
}
catch (Exception exc)
{
LOG.log(Level.SEVERE, "", exc);
}
}
/**
* Save the current target data model AST to file if the tree is not
* empty (the root has at least 1 child). If this AST is empty, it
* will be removed from the {@link AstManager} which will essentially
* disable further use of this AST.
*
* @return <code>true</code> if the tree is persisted,
* <code>false</code> if the tree is empty or on any failure
* during persistence.
*/
public boolean persistTarget()
{
WorkArea wa = context.get();
String filename = generateFilename(getSource());
// bypass persistence if there is an empty tree
if (wa.root.getNumImmediateChildren() == 0)
{
try
{
String normal = Configuration.normalizeFilename(filename);
// no need to maintain this value in the registry
AstManager.get().removeTree(normal);
}
catch (Exception exc)
{
LOG.log(Level.SEVERE, "", exc);
}
return false;
}
try
{
filename = Configuration.normalizeFilename(filename);
// read the existing target tree
AstManager.get().saveTree(wa.root, filename, false);
// ensure the AST node values remain in the map as their ID, and not the full AST.
ConversionData.compact("tmpTabNodes", filename);
}
catch (Exception exc)
{
// some failure, we can't persist!
LOG.log(Level.SEVERE, "", exc);
return false;
}
return true;
}
/**
* Convenience method to expose to rulesets the {@link P2JIndex} static method of the same name.
*
* @param indexes
* The list of indexes.
*
* @see P2JIndex#normalizeUniqueIndexes
*/
public List<P2JIndex> normalizeUniqueIndexes(List<P2JIndex> indexes)
{
return P2JIndex.normalizeUniqueIndexes(indexes, P2JIndexComponent.PROPERTY);
}
/**
* Convenience method to expose to rulesets the {@link P2JIndex} static method of the same name.
*
* @param indexes
* The list of indexes.
*
* @see P2JIndex#nonRedundantIndexes
*/
public List<P2JIndex> nonRedundantIndexes(List<P2JIndex> indexes)
{
return P2JIndex.nonRedundantIndexes(indexes);
}
/**
* Give a XML structure, attach the {@link #schemaElem} element and populate it with all
* the DMO information stored in {@link #tempSchema}.
*
* @param indexRoot
* The root element for the dmo_index.xml file.
*/
public void storeTempSchema(XmlAst indexRoot)
{
if (schemaElem == null)
{
return;
}
XmlAst indexElem = (XmlAst) indexRoot.getImmediateChild(XmlTokenTypes.ELEMENT_NODE, null);
schemaElem.remove();
indexElem.addChild(schemaElem);
BiConsumer<XmlAst, DMOElementInfo> storeAttributes = (element, info) ->
{
XmlAst elAttrSet = (XmlAst) XmlPatternWorker.createAst(XmlTokenTypes.ATTR_SET,
"",
element);
for (String key : info.attributes.keySet())
{
String value = info.attributes.get(key);
XmlAst elAttr = (XmlAst) XmlPatternWorker.createAst(XmlTokenTypes.ATTRIBUTE_NODE,
key,
elAttrSet);
XmlPatternWorker.createAst(XmlTokenTypes.CONTENT, value, elAttr);
}
};
BiConsumer<XmlAst, DMOElementInfo>[] storeChildren = new BiConsumer[1];
storeChildren[0] = (parent, info) ->
{
if (info.children == null)
{
return;
}
for (DMOElementInfo childInfo : info.children)
{
XmlAst dmoElem = (XmlAst) XmlPatternWorker.createAst(XmlTokenTypes.ELEMENT_NODE,
childInfo.tag,
parent);
storeAttributes.accept(dmoElem, childInfo);
storeChildren[0].accept(dmoElem, childInfo);
}
};
Set<String> stored = new HashSet<>();
Iterator<DMOElementInfo> iter = tempSchema.iterator();
while (iter.hasNext())
{
DMOElementInfo dmoInfo = iter.next();
String iface = dmoInfo.attributes.get("interface");
if (stored.contains(iface))
{
continue;
}
stored.add(iface);
XmlAst dmoElem = (XmlAst) XmlPatternWorker.createAst(XmlTokenTypes.ELEMENT_NODE,
dmoInfo.tag,
schemaElem);
storeAttributes.accept(dmoElem, dmoInfo);
storeChildren[0].accept(dmoElem, dmoInfo);
}
}
/**
* Load the specified _temp schema from the XML structure into the {@link #tempSchema}.
*
* @param indexElem
* The XML element for the <code>dmo-index</code> node.
* @param schemaElem
* The schema element to load. May be <code>null</code>, if this does not exist
* already.
* @param schema
* The schema name.
*
* @return The newly created XML element for the schema, or the previously sent
* schemaElem instance.
*/
public XmlAst restoreTempSchema(XmlAst indexElem, XmlAst schemaElem, String schema)
{
if (this.schemaElem != null)
{
// do only once
return this.schemaElem;
}
if (schemaElem == null)
{
schemaElem = (XmlAst) XmlPatternWorker.createAst(XmlTokenTypes.ELEMENT_NODE,
"schema",
indexElem);
XmlAst elAttrSet = (XmlAst) XmlPatternWorker.createAst(XmlTokenTypes.ATTR_SET,
"",
schemaElem);
XmlAst elAttr = (XmlAst) XmlPatternWorker.createAst(XmlTokenTypes.ATTRIBUTE_NODE,
"name",
elAttrSet);
XmlPatternWorker.createAst(XmlTokenTypes.CONTENT, schema, elAttr);
elAttr = (XmlAst) XmlPatternWorker.createAst(XmlTokenTypes.ATTRIBUTE_NODE,
"impl",
elAttrSet);
XmlPatternWorker.createAst(XmlTokenTypes.CONTENT, schema + ".impl", elAttr);
}
this.schemaElem = schemaElem;
BiConsumer<XmlAst, DMOElementInfo> restoreAttributes = (parent, info) ->
{
XmlAst attrs = (XmlAst) parent.getImmediateChild(XmlTokenTypes.ATTR_SET, null);
XmlAst attr = (XmlAst) attrs.getImmediateChild(XmlTokenTypes.ATTRIBUTE_NODE, null);
while (attr != null)
{
String key = attr.getText();
String value = XmlPatternWorker.getAttributeValue(attrs, key);
info.putAttribute(key, value);
attr = (XmlAst) attrs.getImmediateChild(XmlTokenTypes.ATTRIBUTE_NODE, attr);
}
};
BiConsumer<XmlAst, DMOElementInfo>[] restoreChildren = new BiConsumer[1];
restoreChildren[0] =
(parent, info) ->
{
XmlAst child = (XmlAst) parent.getImmediateChild(XmlTokenTypes.ELEMENT_NODE, null);
while (child != null)
{
String tag = child.getText();
DMOElementInfo childInfo = createDMOChildInfo(info, tag);
restoreAttributes.accept(child, childInfo);
restoreChildren[0].accept(child, childInfo);
child = (XmlAst) parent.getImmediateChild(XmlTokenTypes.ELEMENT_NODE, child);
}
};
XmlAst dmoClass = (XmlAst) schemaElem.getImmediateChild(XmlTokenTypes.ELEMENT_NODE, null);
while (dmoClass != null)
{
if ("class".equals(dmoClass.getText()))
{
DMOElementInfo dmoInfo = createDMOClassInfo();
restoreAttributes.accept(dmoClass, dmoInfo);
restoreChildren[0].accept(dmoClass, dmoInfo);
}
dmoClass.remove();
dmoClass = (XmlAst) schemaElem.getImmediateChild(XmlTokenTypes.ELEMENT_NODE, null);
}
return schemaElem;
}
/**
* Create a structure to hold all the information for a <code>class</code> element in
* the dmo_index.xml file.
*
* @return See above.
*/
public DMOElementInfo createDMOClassInfo()
{
DMOElementInfo classInfo = new DMOElementInfo("class");
this.tempSchema.add(classInfo);
return classInfo;
}
/**
* Create a structure to hold additional information, and attach it to the given parent.
*
* @param parent
* The parent structure, from dmo_index.xml.
* @param tag
* The that for this structure.
*
* @return See above.
*/
public DMOElementInfo createDMOChildInfo(DMOElementInfo parent, String tag)
{
if (parent.children == null)
{
parent.children = new LinkedList<>();
}
DMOElementInfo child = new DMOElementInfo(tag);
parent.children.add(child);
return child;
}
}
/**
* A simple helper class to load the content of a dmo_index.xml file, without having to know
* all the semantics of the nodes.
*/
public static class DMOElementInfo
{
/** The XML element tag. */
private final String tag;
/** The XML element attributes. */
private Map<String, String> attributes = new LinkedHashMap<>();
/** The children for this node. */
private List<DMOElementInfo> children = null;
/**
* Create a new instance with the given tag.
*
* @param tag
* The XML element tag.
*/
public DMOElementInfo(String tag)
{
this.tag = tag;
}
/**
* Set an attribute for this XML element, in the {@link #attributes} map.
*
* @param name
* The attribute's name.
* @param val
* The attribute's value.
*/
public void putAttribute(String name, String val)
{
attributes.put(name, val);
}
}
/**
* Comparator implementation which sorts ASTs which have an "order"
* annotation into ascending order, according to the numeric value of
* that attribute.
*/
private static class PropertyComparator
implements Comparator
{
/**
* Compare two ASTs and return a value indicating their relative sort
* order. It is assumed that each object parameter represents an
* annotated AST, and that each such AST has an "order" annotation.
*
* @param o1
* First AST.
* @param o2
* Second AST.
*
* @return A negative number if <code>o1</code> < <code>o2</code>,
* a negative number if <code>o1</code> > <code>o2</code>,
* else <code>0</code>.
*/
public int compare(Object o1, Object o2)
{
if (o1 == o2)
{
return 0; // evidently
}
// the objects represent our data model ASTs.
Aast a1 = (Aast) o1;
Aast a2 = (Aast) o2;
Long order1 = (Long) a1.getAnnotation("order");
Long order2 = (Long) a2.getAnnotation("order");
if (order1 != null && order2 != null)
{
return (int) (order1 - order2);
}
Long index1 = (Long) a1.getAnnotation("customextent");
Long index2 = (Long) a2.getAnnotation("customextent");
if (index1 != null && index2 != null)
{
return (int) (index1 - index2);
}
System.out.println("Unexpected equal properties: " + a1.getDescriptiveTokenText() +
" and " + a2.getDescriptiveTokenText());
return 0;
}
}
/**
* Comparator implementation which sorts ASTs which represent data model
* object (DMO) classes. These classes are object representations of
* relational tables. Foreign key relations create dependencies between
* these tables. This comparator sorts classes such that independent
* classes appear first in the sequence, followed by classes which are
* dependent upon them.
* <p>
* This sort only separates completely independent classes from those
* which have dependencies on other classes, but it <i>does not</i> sort
* out the dependencies themselves. A second pass sort is used for that.
*/
private static class ClassComparator
implements Comparator
{
/**
* Compare two ASTs and return a value indicating their relative sort
* order. It is assumed that each object parameter represents an
* annotated AST, and that each such AST represents a data model object
* class.
*
* @param o1
* First AST.
* @param o2
* Second AST.
*
* @return A negative number if <code>o1</code> < <code>o2</code>,
* a negative number if <code>o1</code> > <code>o2</code>,
* else <code>0</code>.
*/
public int compare(Object o1, Object o2)
{
// Objects represent our data model ASTs.
Aast a1 = (Aast) o1;
Aast a2 = (Aast) o2;
// Independent classes sort earlier than dependent ones.
int len1 = a1.annotationSize("dependency");
int len2 = a2.annotationSize("dependency");
if (len1 == 0 && len2 > 0)
{
return -1;
}
if (len2 == 0 && len1 > 0)
{
return 1;
}
// No sorting could be determined by dependency, so just sort
// in ascending alpha order by name.
return (a1.getText().compareTo(a2.getText()));
}
}
/**
* Context local work area.
*/
private static class WorkArea
{
/** Root of the target tree currently being processed (created). */
private DataModelAst root = null;
}
}