package org.dbxml.xml;

/*
 * dbXML License, Version 1.0
 *
 *
 * Copyright (c) 1999-2001 The dbXML Group, L.L.C.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by
 *        The dbXML Group (http://www.dbxml.com/)."
 *    Alternately, this acknowledgment may appear in the software
 *    itself, if and wherever such third-party acknowledgments normally
 *    appear.
 *
 * 4. The names "dbXML" and "The dbXML Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    info@dbxml.com.
 *
 * 5. Products derived from this software may not be called "dbXML",
 *    nor may "dbXML" appear in their name, without prior written
 *    permission of The dbXML Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 DBXML GROUP OR ITS 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.
 *
 * $Id: SymbolTable.java,v 1.13 2001/08/06 03:10:06 tom Exp $
 */

import org.w3c.dom.*;
import java.io.*;
import java.util.*;

/**
 * SymbolTable is a class that allows the dbXML Compression system to
 * resolve symbol IDs in compression/decompression passes.
 */

public class SymbolTable implements XMLSerializable {
   static final String SYMBOLS = "symbols";
   static final String SYMBOL = "symbol";
   static final String NAME = "name";
   static final String NSURI = "nsuri";
   static final String ID = "id";
      
   private transient boolean dirty = false;
   private transient long lastModified = System.currentTimeMillis();

   private Map symbols = new HashMap(); // String to SymbolInfo
   private Map names = new HashMap();   // Short to SymbolInfo
   private short maxSymbol = -1;

   public SymbolTable() {
   }

   public SymbolTable(Element elem) {
      streamFromXML(elem);
   }

   public static final String getLookupName(String qname, String namespaceURI) {
      StringBuffer sb = new StringBuffer(128);
      sb.append('[');
      sb.append(namespaceURI);
      sb.append(']');
      sb.append(qname);
      return sb.toString();
   }
   
   public static final short getNormalizedSymbol(SymbolTable symbols, String lookup, NamespaceMap nsMap, boolean create) {
      if ( lookup.startsWith("[") ) {
         int idx = lookup.indexOf(']');
         String nsURI = lookup.substring(1, idx);
         int cidx = lookup.indexOf(idx+1, ':');
         String name = cidx != -1 ? lookup.substring(cidx+1)
                                  : lookup.substring(idx+1);
         return symbols.getSymbol(getNormalizedLocalName(name, nsURI), nsURI, create);
      }
      else {
         int idx = lookup.indexOf(':');
         if ( idx != -1 ) {
            String pfx = lookup.substring(0, idx);
            String nsURI = (String)nsMap.get(pfx);
            if ( nsURI != null ) {
               String name = lookup.substring(idx+1);
               return symbols.getSymbol(getNormalizedLocalName(name, nsURI), nsURI, create);
            }
            else
               return symbols.getSymbol(lookup);
         }
         else
            return symbols.getSymbol(lookup, create);
      }
   }
   
   public static final String getNormalizedLocalName(String localName, String namespaceURI) {
      StringBuffer sb = new StringBuffer(32);
      sb.append("ns");
      sb.append(Integer.toString(namespaceURI.hashCode()));
      sb.append(':');
      sb.append(localName);
      return sb.toString();
   }
   
   public static final String getNormalizedQName(String qName, String namespaceURI) {
      StringBuffer sb = new StringBuffer(32);
      sb.append("ns");
      sb.append(Integer.toString(namespaceURI.hashCode()));
      sb.append(':');
      int idx = qName.indexOf(':');
      if ( idx != -1 )
         sb.append(qName.substring(idx+1));
      else
         sb.append(qName);
      return sb.toString();
   }
   
   public final boolean isDirty() {
      return dirty;
   }

   public final void setDirty(boolean dirty) {
      this.dirty = dirty;
      lastModified = System.currentTimeMillis();
   }

   public final SymbolInfo getSymbolInfo(String qname, String namespaceURI) {
      String lookupName = getLookupName(qname, namespaceURI);
      return (SymbolInfo)symbols.get(lookupName);
   }
   
   public final short getSymbol(String qname, String namespaceURI, boolean create) {
      String lookupName = getLookupName(qname, namespaceURI);
      SymbolInfo info = (SymbolInfo)symbols.get(lookupName);
      if ( info != null )
         return info.symbol;
      else if ( create ) {
         synchronized(symbols) {
            short id = ++maxSymbol;
            info = new SymbolInfo(qname, namespaceURI, id);
            symbols.put(lookupName, info);
            names.put(new Short(id), info);
            setDirty(true);
            return id;
         }
      }
      return -1;
   }
   
   public final short getSymbol(String qname, boolean create) {
      SymbolInfo info = (SymbolInfo)symbols.get(qname);
      if ( info != null )
         return info.symbol;
      else if ( create ) {
         synchronized(symbols) {
            short id = ++maxSymbol;
            info = new SymbolInfo(qname, id);
            symbols.put(qname, info);
            names.put(new Short(id), info);
            setDirty(true);
            return id;
         }
      }
      return -1;
   }

   public final short getSymbol(String qname, String namespaceURI) {
      return getSymbol(qname, namespaceURI, false);
   }
   
   public final short getSymbol(String qname) {
      return getSymbol(qname, false);
   }

   public final SymbolInfo getSymbolInfo(short symbol) {
      return (SymbolInfo)names.get(new Short(symbol));
   }
   
   public final String getNamespaceURI(short symbol) {
      SymbolInfo info = getSymbolInfo(symbol);
      return info != null ? info.namespaceURI
                          : null;
   }
   
   public final String getName(short symbol) {
      SymbolInfo info = getSymbolInfo(symbol);
      return info != null ? info.qname
                          : null;
   }

   public final long getLastModified() {
      return lastModified;
   }
   
   public final Element streamToXML(Document doc) throws DOMException {
      Element root = doc.createElement(SYMBOLS);
      doc.appendChild(root);

      Iterator list = symbols.values().iterator();
      while ( list.hasNext() ) {
         SymbolInfo info = (SymbolInfo)list.next();
         Element e = doc.createElement(SYMBOL);
         e.setAttribute(NAME, info.qname);
         if ( info.namespaceURI != null && info.namespaceURI.length() > 0 )
            e.setAttribute(NSURI, info.namespaceURI);
         e.setAttribute(ID, Short.toString(info.symbol));
         root.appendChild(e);
      }
      return root;
   }

   public final void streamFromXML(Element element) throws DOMException {
      symbols.clear();
      names.clear();
      maxSymbol = -1;

      NodeList list = element.getElementsByTagName(SYMBOL);
      int size = list.getLength();
      for ( int i = 0; i < size; i++ ) {
         Element elem = (Element)list.item(i);

         String qname = elem.getAttribute(NAME);
         String namespaceURI = elem.getAttribute(NSURI);
         if ( namespaceURI != null && namespaceURI.length() == 0 )
            namespaceURI = null;
         
         Short id = new Short(elem.getAttribute(ID));
         if ( id.shortValue() > maxSymbol )
            maxSymbol = id.shortValue();

         SymbolInfo info = new SymbolInfo(qname, namespaceURI, id.shortValue());
         if ( namespaceURI != null ) {
            String lookupName = getLookupName(qname, namespaceURI);
            symbols.put(lookupName, info);
         }
         else
            symbols.put(qname, info);
         names.put(id, info);
      }
   }

   public final class SymbolInfo implements Serializable {
      public String namespaceURI;
      public String qname;
      public short symbol;

      public SymbolInfo(String qname, String namespaceURI, short symbol) {
         this.qname = qname;
         this.namespaceURI = namespaceURI;
         this.symbol = symbol;
      }
      
      public SymbolInfo(String qname, short symbol) {
         this.qname = qname;
         this.symbol = symbol;
      }
      
      public String getNamespaceURI() {
         return namespaceURI;
      }
      
      public String getQName() {
         return qname;
      }
      
      public short getSymbolID() {
         return symbol;
      }
   }
}



