package net.sf.jasperreports.engine;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

public class JRPropertiesMap implements Serializable, Cloneable {
  private static final long serialVersionUID = 10200L;
  
  private Map propertiesMap;
  
  private List propertiesList;
  
  private JRPropertiesMap base;
  
  public JRPropertiesMap() {}
  
  public JRPropertiesMap(JRPropertiesMap propertiesMap) {
    this();
    this.base = propertiesMap.base;
    String[] propertyNames = propertiesMap.getPropertyNames();
    if (propertyNames != null && propertyNames.length > 0)
      for (int i = 0; i < propertyNames.length; i++)
        setProperty(propertyNames[i], propertiesMap.getProperty(propertyNames[i]));  
  }
  
  protected synchronized void ensureInit() {
    if (this.propertiesMap == null)
      init(); 
  }
  
  private void init() {
    this.propertiesMap = new HashMap();
    this.propertiesList = new ArrayList();
  }
  
  public String[] getPropertyNames() {
    String[] names;
    if (hasOwnProperties()) {
      if (this.base == null) {
        names = (String[])this.propertiesList.toArray((Object[])new String[this.propertiesList.size()]);
      } else {
        LinkedHashSet namesSet = new LinkedHashSet();
        collectPropertyNames(namesSet);
        names = (String[])namesSet.toArray((Object[])new String[namesSet.size()]);
      } 
    } else if (this.base != null) {
      names = this.base.getPropertyNames();
    } else {
      names = new String[0];
    } 
    return names;
  }
  
  protected void collectPropertyNames(Collection names) {
    if (this.base != null)
      this.base.collectPropertyNames(names); 
    if (this.propertiesList != null)
      names.addAll(this.propertiesList); 
  }
  
  public String getProperty(String propName) {
    String val;
    if (hasOwnProperty(propName)) {
      val = getOwnProperty(propName);
    } else if (this.base != null) {
      val = this.base.getProperty(propName);
    } else {
      val = null;
    } 
    return val;
  }
  
  public boolean containsProperty(String propName) {
    return (hasOwnProperty(propName) || (this.base != null && this.base.containsProperty(propName)));
  }
  
  protected boolean hasOwnProperty(String propName) {
    return (this.propertiesMap != null && this.propertiesMap.containsKey(propName));
  }
  
  protected String getOwnProperty(String propName) {
    return (this.propertiesMap != null) ? (String)this.propertiesMap.get(propName) : null;
  }
  
  public void setProperty(String propName, String value) {
    ensureInit();
    if (!hasOwnProperty(propName))
      this.propertiesList.add(propName); 
    this.propertiesMap.put(propName, value);
  }
  
  public void removeProperty(String propName) {
    if (hasOwnProperty(propName)) {
      this.propertiesList.remove(propName);
      this.propertiesMap.remove(propName);
    } 
  }
  
  public JRPropertiesMap cloneProperties() {
    return new JRPropertiesMap(this);
  }
  
  public Object clone() {
    return cloneProperties();
  }
  
  public String toString() {
    return (this.propertiesMap == null) ? "" : this.propertiesMap.toString();
  }
  
  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    if (this.propertiesList == null && this.propertiesMap != null) {
      this.propertiesList = new ArrayList(this.propertiesMap.keySet());
      this.propertiesMap = new HashMap(this.propertiesMap);
    } 
  }
  
  public boolean hasProperties() {
    return (hasOwnProperties() || (this.base != null && this.base.hasProperties()));
  }
  
  public boolean hasOwnProperties() {
    return (this.propertiesList != null && !this.propertiesList.isEmpty());
  }
  
  public static JRPropertiesMap getPropertiesClone(JRPropertiesHolder propertiesHolder) {
    JRPropertiesMap clone;
    if (propertiesHolder.hasProperties()) {
      clone = propertiesHolder.getPropertiesMap().cloneProperties();
    } else {
      clone = null;
    } 
    return clone;
  }
  
  public JRPropertiesMap getBaseProperties() {
    return this.base;
  }
  
  public void setBaseProperties(JRPropertiesMap base) {
    this.base = base;
  }
}
