/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/urm/org/apache/slide/urm/utils/resourceutilities/message/MessageCode.java,v 1.4 2005/03/02 10:53:36 eckehard Exp $
 * $Revision: 1.4 $
 * $Date: 2005/03/02 10:53:36 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.slide.urm.utils.resourceutilities.message;

// import list


/**
 ** This class represents the Code of a Message. The MessageCode provides
 ** information about the product and the component that caused the message,
 ** and the severity and the (identifying) number.
 **
 ** @version $Revision: 1.4 $
 **
 ** @author rsk@softwareag.com
 **/
public class MessageCode {
    
    /**
     ** The message of the IllegalArgumentException that is thrown by the constructor
     ** if the code does not have the expected format.
     **/
    public static final String FORMAT_MISMATCH = "Code string has not expected format 'PPPCCSxxxx': ";
    
    /**
     ** The message of the IllegalArgumentException that is thrown by
     ** {@link #checkProductIdentifier checkProductIdentifier()}
     ** if the product identifier does not have the expected format.
     **/
    public static final String PRODUCT_IDENTIFIER_MISMATCH = "Product identifier string must be a three letter String: ";
    
    /**
     ** The message of the IllegalArgumentException that is thrown by
     ** {@link #checkComponentIdentifier checkComponentIdentifier()}
     ** if the component identifier does not have the expected format.
     **/
    public static final String COMPONENT_IDENTIFIER_MISMATCH = "Component identifier string must be a two letter String: ";
    
    /**
     ** The message of the IllegalArgumentException that is thrown by
     ** {@link #checkSeverity checkSeverity()}
     ** if the severity is <code>null</code>
     **/
    public static final String SEVERITY_MISMATCH = "Severity must not be null";
    
    /**
     ** The message of the IllegalArgumentException that is thrown by
     ** {@link #checkNumber checkNumber()}
     ** if the number does not have the expected format.
     **/
    public static final String NUMBER_MISMATCH = "Number string must be a four digit String: ";
    
    
    
    
    /**
     ** The product identifier of the MessageCode.
     **/
    protected String productIdentifier = null;
    
    /**
     ** The component identifier of the MessageCode.
     **/
    protected String componentIdentifier = null;
    
    /**
     ** The Severity of the MessageCode.
     **/
    protected Severity severity = null;
    
    /**
     ** The number of the MessageCode.
     **/
    protected String number = null;
    
    /**
     ** The String representation of the MessageCode as returned by
     ** {@link #toString() toString()}.
     **/
    protected String stringRepresentation = null;
    
    
    
    
    /**
     ** Creates a MessageCode from the given parameters.
     ** The parameters of the MessageCode must match the specified format, where
     ** the <code>productIdentifier</code> must be a three letter upper case String,
     ** the <code>componentIdentifier</code> must be a two letter upper case String,
     ** and the <code>number</code> a four digit String.
     ** All parameters must not be <code>null</code>.
     ** If one of these rules is violated, an <code>IllegalArgumentException</code>
     ** is thrown.
     **
     ** @pre        productIdentifier != null
     ** @pre        componentIdentifier != null
     ** @pre        severity != null
     ** @pre        number != null
     ** @post       true
     **
     ** @param      productIdentifier    the product identifier of the MessageCode.
     ** @param      componentIdentifier  the component identifier of the MessageCode.
     ** @param      severity             the Severity of the MessageCode.
     ** @param      number               the number of the MessageCode.
     **
     ** @throws     IllegalArgumentException if one of the parameters is either <code>null</code>
     **                                      or is not in the expected format.
     **/
    public MessageCode(String productIdentifier, String componentIdentifier, Severity severity, String number) throws IllegalArgumentException {
        
        checkProductIdentifier(productIdentifier);
        checkComponentIdentifier(componentIdentifier);
        checkSeverity(severity);
        checkNumber(number);
        
        this.productIdentifier = productIdentifier;
        this.componentIdentifier = componentIdentifier;
        this.severity = severity;
        this.number = number;
    }
    
    /**
     ** Creates a MessageCode from the given String. The String must
     ** be of format <code>PPPCCSxxxx</code>, where PPP is the product identifier
     ** as a three letter upper case String, CC the component identifier as a
     ** two letter upper case String, S the Severity and xxxx the 4 digit
     ** number of the code. Allowed values for severity are I(NFORMATION),
     ** Q(UESTION), W(ARNING), E(RROR) and C(RITICAL). If parsing of the
     ** String fails, an IllegalArgumentException is thrown.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      code  the String to parse the MessageCode from.
     **
     ** @throws     IllegalArgumentException if parsing the String fails.
     **/
    public MessageCode(String code) throws IllegalArgumentException {
        
        if ( (code == null) || (code.length() != 10) ) {
            throw new IllegalArgumentException(FORMAT_MISMATCH + code);
        }
        
        Exception exception = null;
        try {
            
            productIdentifier = code.substring(0, 3);
            componentIdentifier = code.substring(3, 5);
            severity = Severity.getSeverityForCodeChar(code.charAt(5));
            number = code.substring(6, 10);
        }
        catch (NullPointerException e) {
            exception = e;
        }
        catch (NumberFormatException e) {
            exception = e;
        }
        catch (IndexOutOfBoundsException e) {
            exception = e;
        }
        
        if (exception != null) {
            exception.printStackTrace();
            throw new IllegalArgumentException(FORMAT_MISMATCH + code);
        }
        
        checkProductIdentifier(productIdentifier);
        checkComponentIdentifier(componentIdentifier);
        checkSeverity(severity);
        checkNumber(number);
    }
    
    /**
     ** Checks if the <code>productIdentifier</code> is a three letter upper case String.
     ** If not, an IllegalArgumentException is thrown.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      productIdentifier  the product identifier to check.
     **
     ** @throws     IllegalArgumentException if the parameter is not in the expected format.
     **/
    public static void checkProductIdentifier(String productIdentifier) throws IllegalArgumentException {
        
        if ( (productIdentifier == null) || (productIdentifier.length() != 3) ) {
            throw new IllegalArgumentException(PRODUCT_IDENTIFIER_MISMATCH + productIdentifier);
        }
        
        for (int i=0; i<3; i++) {
            if ( ! (Character.isLetter(productIdentifier.charAt(i)) &&
                        Character.isUpperCase(productIdentifier.charAt(i))) ) {
                throw new IllegalArgumentException(PRODUCT_IDENTIFIER_MISMATCH + productIdentifier);
            }
        }
    }
    
    /**
     ** Checks if the <code>componentIdentifier</code> is a two letter upper case String.
     ** If not, an IllegalArgumentException is thrown.
     **
     ** <note>
     ** Currently these restrictions has been softed due to the fact that a component
     ** did not match this restriction.
     ** It has to be decided if the component must change its IDs or not.
     ** </note>
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      componentIdentifier  the component identifier to check.
     **
     ** @throws     IllegalArgumentException if the parameter is not in the expected format.
     **/
    public static void checkComponentIdentifier(String componentIdentifier) throws IllegalArgumentException {
        
        if ( (componentIdentifier == null) || (componentIdentifier.length() != 2) ) {
            throw new IllegalArgumentException(COMPONENT_IDENTIFIER_MISMATCH + componentIdentifier);
        }
        
        for (int i=0; i<2; i++) {
            if ( ( ! (Character.isLetter(componentIdentifier.charAt(i)) &&
                        Character.isUpperCase(componentIdentifier.charAt(i))) ) &&
                    ( ! Character.isDigit(componentIdentifier.charAt(i))) ) {
                throw new IllegalArgumentException(COMPONENT_IDENTIFIER_MISMATCH + componentIdentifier);
            }
        }
    }
    
    /**
     ** Checks if the <code>severity</code> is not <code>null</code>.
     ** If it is <code>null</code>, an IllegalArgumentException is thrown.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      severity  the severity to check.
     **
     ** @throws     IllegalArgumentException if the parameter is not in the expected format.
     **/
    public static void checkSeverity(Severity severity) throws IllegalArgumentException {
        if (severity == null) {
            throw new IllegalArgumentException(SEVERITY_MISMATCH);
        }
    }
    
    /**
     ** Checks if the <code>number</code> is a four digit String.
     ** If not, an IllegalArgumentException is thrown.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      number  the number to check.
     **
     ** @throws     IllegalArgumentException if the parameter is not in the expected format.
     **/
    public static void checkNumber(String number) throws IllegalArgumentException {
        
        if ( (number == null) || (number.length() != 4) ) {
            throw new IllegalArgumentException(NUMBER_MISMATCH + number);
        }
        
        for (int i=0; i<4; i++) {
            if ( ! Character.isDigit(number.charAt(i)) ) {
                throw new IllegalArgumentException(NUMBER_MISMATCH + number);
            }
        }
    }
    
    /**
     ** Returns the product identifier of the MessageCode.
     **
     ** @pre        true
     ** @post       true
     **
     ** @return     the product identifier of the MessageCode.
     **/
    public String getProductIdentifier() {
        return productIdentifier;
    }
    
    /**
     ** Returns the component identifier of the MessageCode.
     **
     ** @pre        true
     ** @post       true
     **
     ** @return     the component identifier of the MessageCode.
     **/
    public String getComponentIdentifier() {
        return componentIdentifier;
    }
    
    /**
     ** Returns the Severity of the message.
     **
     ** @pre        true
     ** @post       return != null
     **
     ** @return     Returns the Severity of the message.
     **/
    public Severity getSeverity() {
        return severity;
    }
    
    /**
     ** Returns the number of the MessageCode.
     **
     ** @pre        true
     ** @post       true
     **
     ** @return     the number of the MessageCode.
     **/
    public String getNumber() {
        return number;
    }
    
    
    /**
     ** Returns a String representation of the MessageCode in the
     ** format "PPPCCSxxxx", where PPP is the product identifier,
     ** CC the component identifier, S the Severity abbreviation
     ** and xxxx the 4 digit number of the code.
     **
     ** @pre        true
     ** @post       true
     **
     ** @return     a String representation of the MessageCode.
     **/
    public String toString() {
        
        if (stringRepresentation == null) {
            StringBuffer buffer = new StringBuffer(getProductIdentifier());
            buffer.append(getComponentIdentifier());
            buffer.append(getSeverity().getCodeChar());
            buffer.append(getNumber());
            stringRepresentation = buffer.toString();
        }
        
        return stringRepresentation;
    }
    
    
    /**
     ** Returns <code>true</code>, if <b>other</b> is a MessageCode and is
     ** equal to this one, where equality means that the product identifier,
     ** component identifier, severity and number are equal.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      other  the other (MessageCode) to test for equality.
     **
     ** @return     <code>true</code>, if <b>other</b> is a MessageCode and is
     **             equal to this one, where equality means that the product identifier,
     **             component identifier, severity and number are equal.
     **/
    public boolean equals(Object other) {
        
        boolean result = false;
        if (other instanceof MessageCode) {
            
            MessageCode otherCode = (MessageCode)other;
            result = (this.getProductIdentifier().equals(otherCode.getProductIdentifier()));
            result &= (this.getComponentIdentifier().equals(otherCode.getComponentIdentifier()));
            result &= (this.getSeverity().equals(otherCode.getSeverity()));
            result &= (this.getNumber().equals(otherCode.getNumber()));
        }
        
        return result;
    }
    
    /**
     ** Overwritten due to the rule that {@link #equals(Object) equal} objects must have the
     ** same hash code.
     **
     ** @pre        true
     ** @post       true
     **
     ** @return     the hash code of this MessageCode.
     **/
    public int hashCode() {
        return getProductIdentifier().hashCode() +
            7*getComponentIdentifier().hashCode() +
            13*getSeverity().hashCode() +
            17*getNumber().hashCode();
    }
    
    
    /**
     ** This class provides a typesafe enumeration of
     ** available Severity types. The constructor is private
     ** since this class should not be used directly but
     ** only the provided Severity constants.
     **
     ** @version $Revision: 1.4 $
     **
     ** @author rsk@softwareag.com
     **/
    public static class Severity implements Comparable {
        
        
        /**
         ** Constant for severity of level "INFORMATION".
         **/
        public final static Severity INFORMATION = new Severity(0, "Information");
        
        /**
         ** Constant for severity of level "QUESTION".
         **/
        public final static Severity QUESTION = new Severity(1, "Question");
        
        /**
         ** Constant for severity of level "WARNING".
         **/
        public final static Severity WARNING = new Severity(2, "Warning");
        
        /**
         ** Constant for severity of level "ERROR".
         **/
        public final static Severity ERROR = new Severity(3, "Error");
        
        /**
         ** Constant for severity of level "CRITICAL".
         **/
        public final static Severity CRITICAL = new Severity(4, "Critical");
        
        
        /**
         ** An array of all Severities.
         **/
        protected static Severity[] severities = new Severity[] { INFORMATION, QUESTION, WARNING, ERROR, CRITICAL };
        
        
        /**
         ** The severity level.
         **/
        protected int level = 0;
        
        /**
         ** The name of the Severity.
         **/
        protected String name = null;
        
        /**
         ** The String representation of the Severity as returned by
         ** {@link #toString() toString()}.
         **/
        protected String stringRepresentation = null;
        
        
        /**
         ** Private Constructor in order to make only the defined
         ** constants available to the public.
         **
         ** @pre        true
         ** @post       true
         **
         ** @param      level  the level of severity.
         ** @param      name   the name of the Severity.
         **/
        private Severity(int level, String name) {
            
            this.level = level;
            this.name = name;
        }
        
        /**
         ** Returns the level of severity.
         **
         ** @pre        true
         ** @post       return &gt;= 0
         **
         ** @return     the level of severity.
         **/
        public int getLevel() {
            return level;
        }
        
        /**
         ** Returns the name of the Severity, e.g. "Information" for
         ** Severity.INFORMATION.
         **
         ** @pre        true
         ** @post       return != null
         **
         ** @return     the name of the Severity, e.g. "Information" for
         **             Severity.INFORMATION.
         **/
        public String getName() {
            return name;
        }
        
        /**
         ** Returns a the character that represents this severity in a
         ** MessageCode's String representation. This is always the first
         ** character of the name.
         **
         ** @pre        true
         ** @post       true
         **
         ** @return     a the character that represents this severity in a
         **             MessageCode's String representation.
         **
         ** @see #getName()
         **/
        public char getCodeChar() {
            return getName().charAt(0);
        }
        
        
        /**
         ** Compares this object with the specified object for order.  Returns a
         ** negative integer, zero, or a positive integer as this object is less
         ** than, equal to, or greater than the specified object.
         **
         ** @param   other  the Object to be compared.
         **
         ** @return  a negative integer, zero, or a positive integer as this object
         **          is less than, equal to, or greater than the specified object.
         **
         ** @throws ClassCastException if the specified object's type prevents it
         **        from being compared to this Object.
         **/
        public int compareTo(Object other) throws ClassCastException {
            return (this.getLevel() - ((Severity)other).getLevel());
        }
        
        /**
         ** Returns <code>true</code> if the given Object is a Severity and
         ** its level is equal to this Severity's level.
         **
         ** @pre        true
         ** @post       true
         **
         ** @param      other  the Object to compare to.
         **
         ** @return     <code>true</code> if the given Object is a Severity and
         **             its level is equal to this Severity's level.
         **/
        public boolean equals(Object other) {
            
            boolean isEqual = false;
            if (other instanceof Severity) {
                isEqual = (this.getLevel() == ((Severity)other).getLevel());
            }
            
            return isEqual;
        }
        
        
        /**
         ** Returns the hash code for the Severity.
         **
         ** @pre        true
         ** @post       true
         **
         ** @return     the hash code for the Severity.
         **/
        public int hashCode() {
            return level;
        }
        
        /**
         ** Returns a String representation of this Object.
         **
         ** @pre        true
         ** @post       true
         **
         ** @return     a String representation of this Object.
         **/
        public String toString() {
            
            if (stringRepresentation == null) {
                StringBuffer buffer = new StringBuffer("Severity[");
                buffer.append(getName());
                buffer.append("]");
                stringRepresentation = buffer.toString();
            }
            
            return stringRepresentation;
        }
        
        
        /**
         ** Returns an array of all Severities.
         **
         ** @pre        true
         ** @post       true
         **
         ** @return     an array of all Severities.
         **/
        public static Severity[] getSeverities() {
            return severities;
        }
        
        
        /**
         ** Returns the Severity that is represented in a MessageCode String
         ** by the given character.
         **
         ** @pre        true
         ** @post       true
         **
         ** @param      codeChar  the character for which to return the associated
         **                       Severity.
         **
         ** @return     the Severity that is represented in a MessageCode String
         **             by the given character.
         **
         ** @see  #getCodeChar()
         **/
        public static Severity getSeverityForCodeChar(char codeChar) {
            
            Severity severity = null;
            Severity[] severities = getSeverities();
            int i = 0;
            while ( (severity == null) & (i < severities.length) ) {
                
                if (severities[i].getCodeChar() == codeChar) {
                    severity = severities[i];
                }
                ++i;
            }
            return severity;
        }
    }
    
}

