001/* SimpleAttributeSet.java --
002   Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.text;
040
041import java.io.Serializable;
042import java.util.Enumeration;
043import java.util.Hashtable;
044
045/**
046 * A set of attributes.
047 */
048public class SimpleAttributeSet
049  implements MutableAttributeSet, Serializable, Cloneable
050{
051  /** The serialization UID (compatible with JDK1.5). */
052  private static final long serialVersionUID = 8267656273837665219L;
053
054  /**
055   * An empty attribute set.
056   */
057  public static final AttributeSet EMPTY = new EmptyAttributeSet();
058
059  /** Storage for the attributes. */
060  Hashtable tab;
061
062  /**
063   * Creates a new attribute set that is initially empty.
064   */
065  public SimpleAttributeSet()
066  {
067    tab = new Hashtable();
068  }
069  
070  /**
071   * Creates a new <code>SimpleAttributeSet</code> with the same attributes
072   * and resolve parent as the specified set.
073   * 
074   * @param a  the attributes (<code>null</code> not permitted).
075   * 
076   * @throws NullPointerException if <code>a</code> is <code>null</code>.
077   */
078  public SimpleAttributeSet(AttributeSet a)
079  {
080    tab = new Hashtable();
081    addAttributes(a);
082  }
083
084  /**
085   * Adds an attribute with the given <code>name</code> and <code>value</code>
086   * to the set.  If the set already contains an attribute with the given
087   * <code>name</code>, the attribute value is updated.
088   * 
089   * @param name  the attribute name (<code>null</code> not permitted).
090   * @param value  the value (<code>null</code> not permitted).
091   * 
092   * @throws NullPointerException if either argument is <code>null</code>.
093   */
094  public void addAttribute(Object name, Object value)
095  {
096    tab.put(name, value);
097  }
098
099  /**
100   * Adds all the attributes from <code>attributes</code> to this set.
101   * 
102   * @param attributes  the set of attributes to add (<code>null</code> not
103   *                    permitted).
104   *                    
105   * @throws NullPointerException if <code>attributes</code> is 
106   *         <code>null</code>.
107   */
108  public void addAttributes(AttributeSet attributes)
109  {
110    Enumeration e = attributes.getAttributeNames();
111    while (e.hasMoreElements())
112      {
113        Object name = e.nextElement();
114        Object val = attributes.getAttribute(name);
115        tab.put(name, val);
116      }
117  }
118
119  /**
120   * Returns a clone of the attribute set.
121   * 
122   * @return A clone of the attribute set.
123   */
124  public Object clone()
125  {
126    SimpleAttributeSet attr = null;
127    try
128      {
129        attr = (SimpleAttributeSet) super.clone();
130        attr.tab = (Hashtable) tab.clone();
131      }
132    catch (CloneNotSupportedException ex)
133      {
134        assert false;
135      }
136    return attr;
137  }
138
139  /**
140   * Returns true if the given name and value represent an attribute
141   * found either in this AttributeSet or in its resolve parent hierarchy.
142   * @param name the key for the attribute
143   * @param value the value for the attribute
144   * @return true if the attribute is found here or in this set's resolve
145   * parent hierarchy
146   */
147  public boolean containsAttribute(Object name, Object value)
148  {
149    if (value == null)
150      throw new NullPointerException("Null 'value' argument.");
151    if (tab.containsKey(name))
152      return tab.get(name).equals(value);
153    else
154      {
155        AttributeSet p = getResolveParent();
156        if (p != null)
157          return p.containsAttribute(name, value);
158        else
159          return false;
160      }
161  }
162  
163  /**
164   * Returns true if the given name and value are found in this AttributeSet.
165   * Does not check the resolve parent.
166   * @param name the key for the attribute
167   * @param value the value for the attribute
168   * @return true if the attribute is found in this AttributeSet
169   */
170  boolean containsAttributeLocally(Object name, Object value)
171  {
172    return tab.containsKey(name) 
173      && tab.get(name).equals(value);
174  }
175
176  /**
177   * Returns <code>true</code> of this <code>AttributeSet</code> contains all
178   * of the specified <code>attributes</code>.
179   *
180   * @param attributes the requested attributes
181   *
182   * @return <code>true</code> of this <code>AttributeSet</code> contains all
183   *         of the specified <code>attributes</code>
184   */
185  public boolean containsAttributes(AttributeSet attributes)
186  {
187    Enumeration e = attributes.getAttributeNames();
188    while (e.hasMoreElements())
189      {
190        Object name = e.nextElement();
191        Object val = attributes.getAttribute(name);
192        if (! containsAttribute(name, val))
193          return false;         
194      }
195    return true;
196  }
197
198  /**
199   * Creates and returns a copy of this <code>AttributeSet</code>.
200   *
201   * @return a copy of this <code>AttributeSet</code>
202   */
203  public AttributeSet copyAttributes()
204  {
205    return (AttributeSet) clone();
206  }
207
208  /**
209   * Checks this set for equality with an arbitrary object.
210   * 
211   * @param obj  the object (<code>null</code> permitted).
212   * 
213   * @return <code>true</code> if this set is equal to <code>obj</code>, and
214   *         <code>false</code> otherwise. 
215   */
216  public boolean equals(Object obj)
217  {
218    return 
219      (obj instanceof AttributeSet)
220      && this.isEqual((AttributeSet) obj);
221  }
222
223  /**
224   * Returns the value of the specified attribute, or <code>null</code> if 
225   * there is no attribute with that name.  If the attribute is not defined
226   * directly in this set, the parent hierarchy (if there is one) will be
227   * used.
228   * 
229   * @param name  the attribute (<code>null</code> not permitted).
230   * 
231   * @throws NullPointerException if <code>name</code> is <code>null</code>.
232   */
233  public Object getAttribute(Object name)
234  {
235    Object val = tab.get(name);
236    if (val != null) 
237      return val;
238
239    AttributeSet p = getResolveParent();
240    if (p != null)
241      return p.getAttribute(name);
242
243    return null;
244  }
245
246  /**
247   * Returns the number of attributes stored in this set, plus 1 if a parent
248   * has been specified (the reference to the parent is stored as a special 
249   * attribute).  The attributes stored in the parent do NOT contribute
250   * to the count.
251   * 
252   * @return The attribute count.
253   */
254  public int getAttributeCount()
255  {
256    return tab.size();
257  }
258
259  /**
260   * Returns an enumeration of the attribute names.
261   * 
262   * @return An enumeration of the attribute names.
263   */
264  public Enumeration<?> getAttributeNames()
265  {
266    return tab.keys();
267  }
268
269  /**
270   * Returns the resolving parent.
271   * 
272   * @return The resolving parent (possibly <code>null</code>).
273   * 
274   * @see #setResolveParent(AttributeSet)
275   */
276  public AttributeSet getResolveParent()
277  {
278    return (AttributeSet) tab.get(ResolveAttribute);
279  }
280
281  /**
282   * Returns a hash code for this instance.
283   * 
284   * @return A hash code.
285   */
286  public int hashCode()
287  {
288    return tab.hashCode();
289  }
290
291  /**
292   * Returns <code>true</code> if the given attribute is defined in this set,
293   * and <code>false</code> otherwise.  The parent attribute set is not
294   * checked.
295   * 
296   * @param attrName  the attribute name (<code>null</code> not permitted).
297   */
298  public boolean isDefined(Object attrName)
299  {
300    return tab.containsKey(attrName);
301  }
302
303  /**
304   * Returns <code>true</code> if the set contains no attributes, and 
305   * <code>false</code> otherwise.  Note that the resolving parent is 
306   * stored as an attribute, so this method will return <code>false</code> if 
307   * a resolving parent is set.
308   * 
309   * @return <code>true</code> if the set contains no attributes, and 
310   * <code>false</code> otherwise.
311   */
312  public boolean isEmpty()
313  {
314    return tab.isEmpty();       
315  }
316
317  /**
318   * Returns true if the given set has the same number of attributes
319   * as this set and <code>containsAttributes(attr)</code> returns
320   * <code>true</code>.
321   * 
322   * @param attr  the attribute set (<code>null</code> not permitted).
323   * 
324   * @return A boolean.
325   * 
326   * @throws NullPointerException if <code>attr</code> is <code>null</code>.
327   */
328  public boolean isEqual(AttributeSet attr)
329  {
330    return getAttributeCount() == attr.getAttributeCount()
331      && this.containsAttributes(attr);
332  }
333    
334  /**
335   * Removes the attribute with the specified <code>name</code>, if this 
336   * attribute is defined.  This method will only remove an attribute from
337   * this set, not from the resolving parent.
338   * 
339   * @param name  the attribute name (<code>null</code> not permitted).
340   * 
341   * @throws NullPointerException if <code>name</code> is <code>null</code>.
342   */
343  public void removeAttribute(Object name)
344  {
345    tab.remove(name);
346  }
347
348  /**
349   * Removes attributes from this set if they are found in the 
350   * given set.  Only attributes whose key AND value are removed.
351   * Removes attributes only from this set, not from the resolving parent.  
352   * Since the resolving parent is stored as an attribute, if 
353   * <code>attributes</code> has the same resolving parent as this set, the
354   * parent will be removed from this set.
355   * 
356   * @param attributes  the attributes (<code>null</code> not permitted).
357   */
358  public void removeAttributes(AttributeSet attributes)
359  {
360    Enumeration e = attributes.getAttributeNames();
361    while (e.hasMoreElements())
362      {
363        Object name = e.nextElement();
364        Object val = attributes.getAttribute(name);
365        if (containsAttributeLocally(name, val))
366          removeAttribute(name);     
367      }
368  }
369
370  /**
371   * Removes the attributes listed in <code>names</code>.
372   * 
373   * @param names  the attribute names (<code>null</code> not permitted).
374   * 
375   * @throws NullPointerException if <code>names</code> is <code>null</code> 
376   *         or contains any <code>null</code> values.
377   */
378  public void removeAttributes(Enumeration<?> names)
379  {
380    while (names.hasMoreElements())
381      {
382        removeAttribute(names.nextElement());
383      } 
384  }
385
386  /**
387   * Sets the reolving parent for this set.  When looking up an attribute, if
388   * it is not found in this set, then the resolving parent is also used for
389   * the lookup.
390   * <p>
391   * Note that the parent is stored as an attribute, and will contribute 1 to 
392   * the count returned by {@link #getAttributeCount()}. 
393   * 
394   * @param parent  the parent attribute set (<code>null</code> not permitted).
395   * 
396   * @throws NullPointerException if <code>parent</code> is <code>null</code>.
397   * 
398   * @see #setResolveParent(AttributeSet)
399   */
400  public void setResolveParent(AttributeSet parent)
401  {
402    addAttribute(ResolveAttribute, parent);
403  }
404  
405  /**
406   * Returns a string representation of this instance, typically used for
407   * debugging purposes.
408   * 
409   * @return A string representation of this instance.
410   */
411  public String toString()
412  {
413    return tab.toString();
414  }    
415}