package org.dbxml.core.indexer;

/*
 * 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: IndexPattern.java,v 1.11 2001/08/05 07:22:48 tom Exp $
 */

import org.dbxml.core.filer.*;
import org.dbxml.xml.*;

import java.io.*;
import java.util.*;

/**
  * IndexPattern is the internal representation of a pattern for index
  * matching purposes.
  */

public final class IndexPattern implements Streamable {
   public static final int PATTERN_NONE = -1;
   public static final int PATTERN_WILDCARD = -2;
   public static final int PATTERN_NAME = -3;

   public static final int SCORE_NONE = 0;
   public static final int SCORE_WILDCARD = 1;
   public static final int SCORE_NAME = 2;
   public static final int SCORE_NATURAL = 3;

   private SymbolTable symbols = null;
   private String elemName = null;
   private short elemID = PATTERN_NONE;
   private String attrName = null;
   private short attrID = PATTERN_NONE;

   public IndexPattern(SymbolTable symbols, String pattern, NamespaceMap nsMap) {
      this.symbols = symbols;
      StringTokenizer st = new StringTokenizer(pattern.trim(), "@");
      elemName = st.nextToken();
      if ( elemName.equals("*") )
         elemID = PATTERN_WILDCARD;
      else
         elemID = SymbolTable.getNormalizedSymbol(symbols, elemName, nsMap, true);
      if ( st.hasMoreTokens() ) {
         attrName = st.nextToken();
         if ( attrName.equals("*") )
            attrID = PATTERN_WILDCARD;
         else
            attrID = elemID == PATTERN_WILDCARD ? PATTERN_NAME
                                                : SymbolTable.getNormalizedSymbol(symbols, attrName, nsMap, true);
      }
   }

   public IndexPattern(SymbolTable symbols, short elemID) {
      this.symbols = symbols;
      this.elemID = elemID;
   }

   public IndexPattern(SymbolTable symbols, short elemID, short attrID) {
      this.symbols = symbols;
      this.elemID = elemID;
      this.attrID = attrID;
   }

   public IndexPattern(SymbolTable symbols, short elemID, String attrName) {
      this.symbols = symbols;
      this.elemID = elemID;
      this.attrID = PATTERN_NAME;
      this.attrName = attrName;
   }

   /**
    * getMatchLevel compares this IndexPattern to p and returns the strength of
    * the match betwen the two patterns from 0 to 15.  (0 being no match at
    * all, 15 being perfect).  This method should be called by XPath and
    * SAX patterns to be matched against Indexer patterns.
    *
    * @param p The pattern to compare
    * @return The resulting IndexPattern strength
    */
   public int getMatchLevel(IndexPattern p) {
      int result = 0;

      switch ( p.elemID ) {
         case PATTERN_WILDCARD:
            result = (SCORE_WILDCARD << 2);
            break;
         case PATTERN_NAME:
            if ( elemName.equals(p.elemName) )
               result = (SCORE_NAME << 2);
            break;
         default:
            if ( elemID == p.elemID )
               result = (SCORE_NATURAL << 2);
      }

      if ( result != 0 ) {
         switch ( p.attrID ) {
            case PATTERN_WILDCARD:
               return result + SCORE_WILDCARD;
            case PATTERN_NAME:
               if ( attrName.equals(p.attrName) )
                  return result + SCORE_NAME;
               break;
            default:
               if ( attrID == p.attrID )
                  return result + SCORE_NATURAL;
         }
      }

      return 0;
   }

   /**
    * getElementID returns the Element Symbol ID for this pattern if
    * there is one, otherwise it returns a negative value.
    *
    * @return The Element Symbol ID
    */
   public short getElementID() {
      return elemID;
   }

   /**
    * getAttributeID returns the Attribute Symbol ID for this pattern
    * if there is one, otherwise it returns a negative value.
    *
    * @return The Attribute Symbol ID
    */
   public short getAttributeID() {
      return attrID;
   }

   /**
    * getElementName returns the Element Name for this pattern if
    * there is one, otherwise it returns null.
    *
    * @return The Element Name
    */
   public String getElementName() {
      return elemName;
   }

   /**
    * getAttributeName returns the Attribute Name for this pattern if
    * there is one, otherwise it returns null.
    *
    * @return The Attribute Name
    */
   public String getAttributeName() {
      return attrName;
   }

   public void read(DataInputStream dis) throws IOException {
      elemID = dis.readShort();
      if ( elemID == PATTERN_NAME ) {
         short len = dis.readShort();
         byte[] name = new byte[len];
         dis.read(name);
         elemName = new String(name);
      }
      attrID = dis.readShort();
      if ( attrID == PATTERN_NAME ) {
         short len = dis.readShort();
         byte[] name = new byte[len];
         dis.read(name);
         attrName = new String(name);
      }
   }

   public void write(DataOutputStream dos) throws IOException {
      dos.writeShort(elemID);
      if ( elemID == PATTERN_NAME ) {
         byte[] name = elemName.getBytes();
         dos.writeShort(name.length);
         dos.write(name);
      }
      dos.writeShort(attrID);
      if ( attrID == PATTERN_NAME ) {
         byte[] name = attrName.getBytes();
         dos.writeShort(name.length);
         dos.write(name);
      }
   }

   public int hashCode() {
      return new Integer((elemID << 16) + attrID).hashCode();
   }

   public boolean equals(Object obj) {
      if ( obj instanceof IndexPattern ) {
         IndexPattern p = (IndexPattern)obj;
         boolean eq = elemID == p.elemID && attrID == p.attrID;
         if ( eq && (elemName != null || p.elemName != null) )
            eq = elemName != null && p.elemName != null && elemName.equals(p.elemName);
         if ( eq && (attrName != null || p.attrName != null) )
            eq = attrName != null && p.attrName != null && attrName.equals(p.attrName);
         return eq;
      }
      else
         return false;
   }
}

