/* * 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(); } } }