/*
* Copyright (c) 2008, Dennis M. Sosnoski All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following
* disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of
* JiBX nor the names of its contributors may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jibx.binding.model;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.jibx.runtime.IBindingFactory;
import org.jibx.runtime.IMarshallable;
import org.jibx.runtime.IMarshallingContext;
import org.jibx.runtime.JiBXException;
import org.jibx.util.InsertionOrderedMap;
/**
* Organizer for a set of bindings under construction. This tracks the individual bindings by default namespace, and
* manages the relationships between the different bindings.
*
* @author Dennis M. Sosnoski
*/
public class BindingDirectory
{
//
// Flags to initialize constructed bindings
private final boolean m_forceClasses;
private final boolean m_trackSource;
private final boolean m_addConstructors;
private final boolean m_inBinding;
private final boolean m_outBinding;
/** Map from namespace URI to binding holder. */
private final InsertionOrderedMap m_uriBindingMap;
/**
* Constructor taking flags used with constructed bindings.
*
* @param force force classes flag
* @param track track source flag
* @param addcon add constructors flag
* @param in input binding flag
* @param out output binding flag
*/
public BindingDirectory(boolean force, boolean track, boolean addcon, boolean in, boolean out) {
m_forceClasses = force;
m_trackSource = track;
m_addConstructors = addcon;
m_inBinding = in;
m_outBinding = out;
m_uriBindingMap = new InsertionOrderedMap();
}
/**
* Find the binding to be used for a particular namespace. If this is the first time a particular namespace was
* requested, a new binding will be created for that namespace and returned.
*
* @param uri namespace URI (null
if no namespace)
* @return binding holder
*/
public BindingHolder findBinding(String uri) {
BindingHolder hold = (BindingHolder)m_uriBindingMap.get(uri);
if (hold == null) {
hold = new BindingHolder(uri, m_uriBindingMap.size() + 1, this);
m_uriBindingMap.put(uri, hold);
BindingElement binding = hold.getBinding();
binding.setForceClasses(m_forceClasses);
binding.setTrackSource(m_trackSource);
binding.setAddConstructors(m_addConstructors);
binding.setInBinding(m_inBinding);
binding.setOutBinding(m_outBinding);
}
return hold;
}
/**
* General object comparison method. Don't know why Sun hasn't seen fit to include this somewhere, but at least it's
* easy to write (over and over again).
*
* @param a first object to be compared
* @param b second object to be compared
* @return true
if both objects are null
, or if a.equals(b)
;
* false
otherwise
*/
public static boolean isEqual(Object a, Object b) {
return (a == null) ? b == null : a.equals(b);
}
/**
* Add dependency on another binding.
*
* @param uri namespace for binding of referenced component
* @param hold binding holder
*/
public void addDependency(String uri, BindingHolder hold) {
if (!isEqual(uri, hold.getNamespace())) {
BindingHolder tohold = findBinding(uri);
hold.addReference(tohold);
}
}
/**
* Fix all references between bindings. This needs to be called after the generation of binding components is
* completed, in order to make sure that required namespace definitions are included in the output bindings.
*/
public void finish() {
ArrayList uris = getNamespaces();
for (int i = 0; i < uris.size(); i++) {
String uri = (String)uris.get(i);
BindingHolder holder = (BindingHolder)m_uriBindingMap.get(uri);
holder.finish();
}
}
/**
* Get the existing binding definition for a namespace.
*
* @param uri
* @return binding holder, or null
if none
*/
public BindingHolder getBinding(String uri) {
return (BindingHolder)m_uriBindingMap.get(uri);
}
/**
* Get the list of binding namespace URIs.
*
* @return namespaces
*/
public ArrayList getNamespaces() {
return m_uriBindingMap.keyList();
}
/**
* Add namespace declarations to binding. This is used to define any namespaces which are not directly used by the
* binding(s) but need to be present in the binding definition.
*
* @param adduris
* @param binding
*/
private void addNamespaceDeclarations(String[] adduris, BindingElement binding) {
int offset = -1;
int nsnum = m_uriBindingMap.size();
for (int i = 0; i < adduris.length; i++) {
String adduri = adduris[i];
if (!m_uriBindingMap.containsKey(adduri)) {
ArrayList childs = binding.topChildren();
if (offset < 0) {
while (++offset < childs.size() && (childs.get(offset) instanceof NamespaceElement));
}
NamespaceElement ns = new NamespaceElement();
ns.setDefaultName("none");
ns.setUri(adduri);
ns.setPrefix("ns" + ++nsnum);
childs.add(offset++, ns);
}
}
}
/**
* Check if a character is an ASCII alpha character.
*
* @param chr
* @return alpha character flag
*/
private static boolean isAsciiAlpha(char chr) {
return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z');
}
/**
* Check if a character is an ASCII numeric character.
*
* @param chr
* @return numeric character flag
*/
private static boolean isAsciiNum(char chr) {
return chr >= '0' && chr <= '9';
}
/**
* Check if a character is an ASCII alpha or numeric character.
*
* @param chr
* @return alpha or numeric character flag
*/
private static boolean isAsciiAlphaNum(char chr) {
return isAsciiAlpha(chr) || isAsciiNum(chr);
}
/**
* Configure the names to be used for writing bindings to files. If only one binding has been defined, it just gets
* the supplied name. If multiple bindings have been defined, a single root binding is constructed which includes
* all the other bindings, and that root binding is given the supplied name while the other bindings are given
* unique names within the same directory.
*
* @param name file name for root or singleton binding definition
* @param adduris list of namespaces to be added to bindings, if not already defined
* @param pack target package for binding
* @return root or singleton binding holder
*/
public BindingHolder configureFiles(String name, String[] adduris, String pack) {
BindingHolder rhold;
BindingElement root;
ArrayList uris = getNamespaces();
if (uris.size() == 1) {
// single binding, just write it using supplied name and added namespaces
rhold = (BindingHolder)m_uriBindingMap.get(uris.get(0));
root = rhold.getBinding();
} else {
// get or create no namespace binding
rhold = findBinding(null);
root = rhold.getBinding();
// set file names and add to root binding
Set nameset = new HashSet();
for (int i = 0; i < uris.size(); i++) {
String uri = (String)uris.get(i);
BindingHolder holder = (BindingHolder)m_uriBindingMap.get(uri);
if (holder != rhold) {
// get last part of namespace URI as file name candidate
String bindname;
String raw = holder.getNamespace();
if (raw == null) {
bindname = "nonamespaceBinding";
} else {
// strip off protocol and any trailing slash
raw = raw.replace('\\', '/');
int split = raw.indexOf("://");
if (split >= 0) {
raw = raw.substring(split + 3);
}
while (raw.endsWith("/")) {
raw = raw.substring(0, raw.length()-1);
}
// strip off host portion if present and followed by path
split = raw.indexOf('/');
if (split > 0 && raw.substring(0, split).indexOf('.') > 0) {
raw = raw.substring(split+1);
}
// eliminate any invalid characters in name
StringBuffer buff = new StringBuffer();
int index = 0;
char chr = raw.charAt(0);
if (isAsciiAlpha(chr)) {
buff.append(chr);
index = 1;
} else {
buff.append('_');
}
boolean toupper = false;
while (index < raw.length()) {
chr = raw.charAt(index++);
if (isAsciiAlphaNum(chr)) {
if (toupper) {
chr = Character.toUpperCase(chr);
toupper = false;
}
buff.append(chr);
} else if (chr == '.') {
toupper = true;
}
}
buff.append("Binding");
bindname = buff.toString();
}
// ensure uniqueness of the name
String uname = bindname.toLowerCase();
int pass = 0;
while (nameset.contains(uname)) {
bindname = bindname + pass;
uname = bindname.toLowerCase();
}
nameset.add(uname);
holder.setFileName(bindname + ".xml");
// include within the root binding
IncludeElement include = new IncludeElement();
include.setIncludePath(holder.getFileName());
rhold.addInclude(include);
}
}
}
// add any necessary namespace declarations to binding root
addNamespaceDeclarations(adduris, root);
// set the file name on the singleton or root binding
rhold.setFileName(name);
// set the binding name based on the file name
int split = name.lastIndexOf('.');
if (split > 0) {
root.setName(name.substring(0, split));
} else {
root.setName(name);
}
// set the target package for code generation
root.setTargetPackage(pack);
return rhold;
}
/**
* Write the bindings to supplied destination path. This first finalizes the binding defintions with a call to
* {@link #finish()}, then writes the completed bindings. The binding file names must be set before calling this
* method, generally by a call to {@link #configureFiles(String, String[], String)}.
*
* @param dir target directory for writing binding definitions
* @throws JiBXException
* @throws IOException
*/
public void writeBindings(File dir) throws JiBXException, IOException {
finish();
IBindingFactory fact = org.jibx.runtime.BindingDirectory.getFactory(BindingElement.class);
IMarshallingContext ictx = fact.createMarshallingContext();
ictx.setIndent(2);
ArrayList uris = getNamespaces();
for (int i = 0; i < uris.size(); i++) {
String uri = (String)uris.get(i);
BindingHolder holder = (BindingHolder)m_uriBindingMap.get(uri);
File file = new File(dir, holder.getFileName());
ictx.setOutput(new FileOutputStream(file), null);
((IMarshallable)holder.getBinding()).marshal(ictx);
ictx.getXmlWriter().flush();
}
}
}