
org.enhydra.xml.xmlc.dom.generic.BuildMethodMappings Maven / Gradle / Ivy
/*
* Enhydra Java Application Server Project
*
* The contents of this file are subject to the Enhydra Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License on
* the Enhydra web site ( http://www.enhydra.org/ ).
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific terms governing rights and limitations
* under the License.
*
* The Initial Developer of the Enhydra Application Server is Lutris
* Technologies, Inc. The Enhydra Application Server and portions created
* by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
* All Rights Reserved.
*
* Contributor(s):
*
* $Id: BuildMethodMappings.java,v 1.2 2005/01/26 08:29:24 jkjome Exp $
*/
package org.enhydra.xml.xmlc.dom.generic;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import org.enhydra.xml.xmlc.XMLCError;
import org.enhydra.xml.xmlc.codegen.JavaLang;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Class to calculate which node are to have build methods created for
* them based on trying to optimize the amount of code (create-cost) in each
* build methods without overflowing the maximum create-cost.
*
* Build methods are normally rooted at a subtree. Costs are adjusted by
* moving the creation of children into sub-build methods. This leaves just
* the cost of calling the build method. If the build cost is till too high
* after moving all children into build methods, the methods are chained
* together to handle creating the children.
*
* Note that the tree is a bit more comple than just the child nodes.
* Elements have attributes and DocumentTypes have lists of nodes.
* Collectively, these are refered to as contained nodes.
*
* The advantage of this object determining where build methods are created in
* a seperate traversal from creating the build methods is that the
* determination is best down from the bottom up, while creating the methods
* is done top-down. The seperation also keeps the code cleaner and easier to
* understand. The cost is building an intermediate table.
*/
final class BuildMethodMappings {
/**
* Create-cost to call a build-method.
*/
public static final int BUILD_METHOD_CALL_CREATE_COST = 1;
/**
* Create-cost to create an element node. Slightly higher due
* to possibility of initializing access methods.
*/
public static final int CREATE_ELEMENT_CREATE_COST = 2;
/**
* Create-cost to create other nodes.
*/
public static final int CREATE_NODE_CREATE_COST = 1;
/**
* Document we are associated with.
*/
private Document fDocument;
/**
* Maximum creation-cost that a single build method can handle.
*/
private int fMaxCreateCostPerBuildMethod;
/*
* Table of node record.
*/
private HashMap fNodeRecordTable = new HashMap();
/**
* Node record.
*/
private class Record {
/** The node we are associated with. */
private Node fNode;
/**
* The total cost to create this node and its children in this build
* method. It includes calls to other build methods, but not the cost
* in other build methods.
*/
private int fCreateCost;
/**
* The cost to the parent to create this node. If the create cost is
* rooted at a method, then this is simply the cost to call the build
* method. If the node is not rooted at a method, the is the total
* cost to create the node.
*/
private int fParentCreateCost;
/** Should this node be at the root of a build method? */
private boolean fMethodRoot;
/** Should the children be created in a chained method? */
private boolean fChainedChildrenMethods;
/**
* Constructor. Initialize base costs.
*/
public Record(Node node) {
fNode = node;
fCreateCost = getNodeTypeCost(node);
fParentCreateCost = fCreateCost;
// Add self to table
fNodeRecordTable.put(node, this);
}
/* Mark this as a method root */
public void makeMethodRoot() {
fParentCreateCost = BUILD_METHOD_CALL_CREATE_COST;
fMethodRoot = true;
}
/* Mark this as a having it's children created in chained methods */
public void makeChainedChildrenMethods () {
fChainedChildrenMethods = true;
}
/** Determine if this can be a method root */
public boolean canBeMethodRoot() {
return BuildMethodMappings.this.canBeMethodRoot(fNode);
}
/**
* Adjust the creation cost, this also changes the parent cost
* if it's not a method root. It doesn't change the parent record,
* as this is down during the bottom-up build of the tree.
*/
public void adjustCreateCost(int amount) {
fCreateCost += amount;
if (!fMethodRoot) {
fParentCreateCost += amount;
}
}
/** Get the node */
public Node getNode() {
return fNode;
}
/** Get the create cost */
public int getCreateCost() {
return fCreateCost;
}
/** Get the cost to the parent to create this node */
public int getParentCreateCost() {
return fParentCreateCost;
}
/** Is the node the root of a build method? */
public boolean isMethodRoot() {
return fMethodRoot;
}
/** Should the children be created in a chained method? */
public boolean useChainedChildrenMethods() {
return fChainedChildrenMethods;
}
/** Get string description for debugging. */
public String toString() {
return JavaLang.simpleClassName(fNode)
+ " method=" + fMethodRoot
+ " chained=" + fChainedChildrenMethods
+ " cost=" + fCreateCost
+ " parentCost=" + fParentCreateCost;
}
}
/**
* Constructor.
*/
public BuildMethodMappings(int maxCreateCostPerBuildMethod,
Document document) {
fDocument = document;
fMaxCreateCostPerBuildMethod = maxCreateCostPerBuildMethod;
calculateTreeCosts(document);
}
/**
* Policy control: Determine if a node can be the root of a build method.
* Change the logic here maybe useful in getting better packing.
*/
boolean canBeMethodRoot(Node node) {
//FIXME: revisit
switch (node.getNodeType()) {
case Node.DOCUMENT_NODE:
case Node.DOCUMENT_TYPE_NODE:
case Node.ELEMENT_NODE:
return true;
default:
return false;
}
}
/**
* Policy control: Get the cost of creating a single node based on its
* type. This does not provide cumulative costs, its based on type only.
*/
public int getNodeTypeCost(Node node) {
//FIXME: need costs for DocType, maybe attr
if (node instanceof Element) {
return CREATE_ELEMENT_CREATE_COST;
} else {
return CREATE_NODE_CREATE_COST;
}
}
/**
* Get a record.
*/
private Record getRecord(Node node) {
Record rec = (Record)fNodeRecordTable.get(node);
if (rec == null) {
throw new XMLCError("BUG: record not found: " + node);
}
return rec;
}
/**
* Traverse the tree, calculating costs and determining where build methods
* should be created. Depth-first traversal, build records from the
* bottom-up. This builds the table of node records.
* @return The node record.
*/
private Record calculateTreeCosts(Node node) {
Record rec = new Record(node);
if (node instanceof Document) {
rec.makeMethodRoot();
}
// depth-fist traversal, summing cost of contained nodes
ContainedNodeEnum nodes = new ContainedNodeEnum(rec.fNode);
while (nodes.hasMoreElements()) {
Record child = calculateTreeCosts(nodes.nextNode());
rec.adjustCreateCost(child.getParentCreateCost());
}
adjustRecord(rec);
return rec;
}
/**
* Convert a child to being build in another method, adjust the size of
* the parent.
*/
private void moveChildIntoMethod(Record parent,
Record child) {
parent.adjustCreateCost(-child.getParentCreateCost());
child.makeMethodRoot();
parent.adjustCreateCost(child.getParentCreateCost());
}
/**
* Convert a child to being build in another method, if this will reduce
* the cost of building the parent. Adjust the size of the parent.
*/
private void minimizeChildCreateCost(Record parent,
Record child) {
if (!child.isMethodRoot() && child.canBeMethodRoot()
&& (child.getParentCreateCost() > BUILD_METHOD_CALL_CREATE_COST)) {
moveChildIntoMethod(parent, child);
}
}
/**
* Build a descending sorted list of children to potentially move to their
* own build methods, based to their parent create cost.
*/
private ArrayList buildSortedBuildMethodCandidates(Record rec) {
ArrayList list = new ArrayList();
ContainedNodeEnum children = new ContainedNodeEnum(rec.fNode);
while (children.hasMoreElements()) {
Record child = getRecord(children.nextNode());
if (child.canBeMethodRoot()) {
list.add(child);
}
}
Comparator cmp
= new Comparator() {
public int compare(Object o1,
Object o2) {
int cost1 = ((Record)o1).getParentCreateCost();
int cost2 = ((Record)o2).getParentCreateCost();
return ((cost1 > cost2) ? -1 : ((cost1 < cost2) ? 1 : 0));
}
};
Collections.sort(list, cmp);
return list;
}
/**
* Move children into other build methods until the cost of this method
* is below the maximum. Sorting to move largest first results in fewer
* methods being created.
*/
private void adjustBuildMethod(Record rec) {
ArrayList list = buildSortedBuildMethodCandidates(rec);
// Move largest-first into their own build methods until this method
// is no longer full.
int numChildren = list.size();
for (int idx = 0;
(idx < numChildren)
&& (rec.getCreateCost() >= fMaxCreateCostPerBuildMethod);
idx++) {
minimizeChildCreateCost(rec, (Record)list.get(idx));
}
}
/**
* Adjust an node record, if nececssary, to keep it from overflowing
* the maximum build method create-cost. This must be called *before* the
* parent record is created.
*/
private void adjustRecord(Record rec) {
if (rec.getCreateCost() >= fMaxCreateCostPerBuildMethod) {
// Split children into other build methods until this one fits
adjustBuildMethod(rec);
}
if (rec.getCreateCost() > fMaxCreateCostPerBuildMethod) {
// Not enough, create a chained method.
rec.makeChainedChildrenMethods();
}
}
/**
* Should a new method be created at the root of this node.
*/
public boolean isMethodRoot(Node node) {
return getRecord(node).isMethodRoot();
}
/**
* Should the children be created in a chained method?
*/
public boolean useChainedChildrenMethods(Node node) {
return getRecord(node).useChainedChildrenMethods();
}
/**
* Get the create cost of a node in it's method.
*/
public int getCreateCost(Node node) {
return getRecord(node).getCreateCost();
}
/**
* Get a string repersentation of an entry for debugging.
*/
public String toString(Node node) {
return getRecord(node).toString();
}
}