/**
 *
 * Copyright 2005 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.geronimo.gbean;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;


/**
 * Class that represents the name for a GBean.
 * A name is comprised of a domain combined with one or more properties.
 * The domain is a fixed base name, properties qualify that as necessary.
 * Two names are equal if they have the same name and the same properties.
 * The String representation of a name can be written as domain:key1=value1,key2=value2,...
 * Values are case sensitive, spaces are significant and there is no escaping mechanism;
 * this is intended to be fast rather than allow lax syntax.
 *
 * @version $Rev$ $Date$
 */
public final class GBeanName implements Serializable {
    private static final long serialVersionUID = 8571821054715922993L;

    /**
     * Original name preserved; used for toString and Serialized form
     */
    private final String name;
    private final transient String domain;
    private final transient Properties props;
    private final transient int hashCode;

    /**
     * Construct a GBeanName by combining a domain with explicit properties.
     * The string representation of this name is generated by combining the properties in sorted order.
     *
     * @param domain the domain
     * @param props the properties used to qualify this name
     */
    public GBeanName(String domain, Properties props) {
        if (domain == null) {
            throw new IllegalArgumentException("domain is null");
        } else if (props == null) {
            throw new IllegalArgumentException("props is null");
        } else if (props.isEmpty()) {
            throw new IllegalArgumentException("props is empty");
        }
        this.domain = domain;
        this.props = new Properties(props);
        this.name = sortName(domain, props);
        this.hashCode = domain.hashCode() + 37 * props.hashCode();
    }

    /**
     * Construct a GBeanName by parsing a string.
     * @param name the name to parse
     */
    public GBeanName(String name) {
        int idx = name.indexOf(':');
        if (idx == -1) {
            throw new IllegalArgumentException("Missing ':' for domain: " + name);
        }
        this.name = name;
        this.domain = name.substring(0, idx);
        this.props = parseName(name.substring(idx + 1));
        this.hashCode = domain.hashCode() + 37 * props.hashCode();
    }

    private static Properties parseName(String name) {
        if (name.endsWith(",")) {
            throw new IllegalArgumentException("Missing last property pair");
        }
        Properties props = new Properties();
        String[] pairs = name.split(",");
        for (int i = 0; i < pairs.length; i++) {
            String pair = pairs[i];
            int idx = pair.indexOf('=');
            if (idx == -1) {
                throw new IllegalArgumentException("Invalid property pair: " + pair);
            }
            String key = pair.substring(0, idx);
            String value = pair.substring(idx + 1);
            if (props.put(key, value) != null) {
                throw new IllegalArgumentException("Duplicate property: " + key);
            }
        }
        return props;
    }

    private static String sortName(String domain, Map props) {
        String[] names = (String[]) props.keySet().toArray(new String[props.size()]);
        Arrays.sort(names);
        StringBuffer buf = new StringBuffer(128);
        buf.append(domain).append(':');
        buf.append(names[0]).append('=').append(props.get(names[0]));
        for (int i = 1; i < names.length; i++) {
            String name = names[i];
            buf.append(',').append(name).append('=').append(props.get(name));
        }
        return buf.toString();
    }

    /**
     * Determine if this name matches the supplied pattern.
     * This performs a fast but simplistic pattern match which is true if:
     * <ul>
     * <li>The domains are equal</li>
     * <li>If this instance has all the supplied properties with equal values</li>
     * <ul>
     * A null domain and a null or empty properties object are considered wildcards
     * and always match; in other words GBeanName.match(null, new Properties()) will
     * always evaluate to true.
     *
     * @param domain the domain to match
     * @param pattern the set properties to match
     * @return true if this instance matches the pattern
     */
    public boolean matches(String domain, Properties pattern) {
        if (domain != null) {
            if (!this.domain.equals(domain)) {
                return false;
            }
        }
        if (pattern != null && !pattern.isEmpty()) {
            for (Iterator i = pattern.entrySet().iterator(); i.hasNext();) {
                Map.Entry entry = (Map.Entry) i.next();
                String key = (String) entry.getKey();
                String ourValue = (String) props.get(key);
                if (ourValue == null || !ourValue.equals(entry.getValue())) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Test for equality.
     * This instance will be equal if the supplied object is a GBeanName with
     * equal domain and properties.
     * @param obj
     * @return
     */
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (obj instanceof GBeanName == false) return false;
        final GBeanName other = (GBeanName) obj;
        return this.domain.equals(other.domain) && this.props.equals(other.props);
    }

    public int hashCode() {
        return hashCode;
    }

    /**
     * Return a human readable version of this GBeanName. If the instance was created
     * by parsing a String, this will be the supplied value; it it was created by
     * supplying properties, the name will contain properties in sorted order.
     *
     * @return a readable name
     */
    public String toString() {
        return name;
    }

    private Object readResolve() {
        return new GBeanName(name);
    }
}
@@ -729,16 +729,43 @@
 rrays;%0D%0A
+import java.util.HashMap;%0D%0A
 import j
@@ -811,38 +811,8 @@
 p;%0D%0A
-import java.util.Properties;%0D%0A
 %0D%0A%0D%0A
@@ -1692,26 +1692,23 @@
 ansient 
-Properties
+HashMap
  props;%0D
@@ -2042,24 +2042,46 @@
 fy this name
+; a Map%3CString,String%3E
 %0D%0A     */%0D%0A 
@@ -2115,26 +2115,19 @@
 domain, 
-Properties
+Map
  props) 
@@ -2503,26 +2503,23 @@
 s = new 
-Properties
+HashMap
 (props);
@@ -3174,26 +3174,23 @@
  static 
-Properties
+HashMap
  parseNa
@@ -3341,26 +3341,23 @@
         
-Properties
+HashMap
  props =
@@ -3361,26 +3361,23 @@
 s = new 
-Properties
+HashMap
 ();%0D%0A   
@@ -5122,32 +5122,54 @@
 perties to match
+; a Map%3CString,String%3E
 %0D%0A     * @return
@@ -5263,26 +5263,19 @@
 domain, 
-Properties
+Map
  pattern

Use Map to avoid synchronization