001    /*
002     *  jDTAUS Core API
003     *  Copyright (c) 2005 Christian Schulte
004     *
005     *  Christian Schulte, Haldener Strasse 72, 58095 Hagen, Germany
006     *  <schulte2005@users.sourceforge.net> (+49 2331 3543887)
007     *
008     *  This library is free software; you can redistribute it and/or
009     *  modify it under the terms of the GNU Lesser General Public
010     *  License as published by the Free Software Foundation; either
011     *  version 2.1 of the License, or any later version.
012     *
013     *  This library is distributed in the hope that it will be useful,
014     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
015     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
016     *  Lesser General Public License for more details.
017     *
018     *  You should have received a copy of the GNU Lesser General Public
019     *  License along with this library; if not, write to the Free Software
020     *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
021     *
022     */
023    package org.jdtaus.core.container;
024    
025    import java.io.Serializable;
026    import java.util.Arrays;
027    import java.util.Collection;
028    import java.util.HashMap;
029    import java.util.Map;
030    
031    /**
032     * Collection of properties.
033     *
034     * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
035     * @version $Id: Properties.java 8044 2009-07-02 01:29:05Z schulte2005 $
036     */
037    public class Properties extends ModelObject implements Cloneable, Serializable
038    {
039        //--Constants---------------------------------------------------------------
040    
041        /** Serial version UID for backwards compatibility with 1.0.x classes. */
042        private static final long serialVersionUID = 3703581195509652826L;
043    
044        //---------------------------------------------------------------Constants--
045        //--Properties--------------------------------------------------------------
046    
047        /**
048         * The properties held by the instance.
049         * @serial
050         */
051        private Property[] properties;
052    
053        /**
054         * Maps property names to properties.
055         * @serial
056         */
057        private final Map names = new HashMap();
058    
059        /**
060         * Hash code.
061         * @serial
062         */
063        private int hashCode;
064    
065        /**
066         * Gets the properties of the collection.
067         *
068         * @return the properties of the collection.
069         */
070        public Property[] getProperties()
071        {
072            if ( this.properties == null )
073            {
074                this.properties = new Property[ 0 ];
075                this.hashCode = 0;
076            }
077    
078            return this.properties;
079        }
080    
081        /**
082         * Setter for property {@code properties}.
083         *
084         * @param value the new collection of properties.
085         *
086         * @throws DuplicatePropertyException if {@code value} contains duplicate
087         * properties.
088         */
089        public void setProperties( final Property[] value )
090        {
091            this.names.clear();
092            this.hashCode = 0;
093            this.properties = null;
094    
095            if ( value != null )
096            {
097                for ( int i = value.length - 1; i >= 0; i-- )
098                {
099                    this.hashCode += value[i].hashCode();
100                    if ( this.names.put( value[i].getName(), value[i] ) != null )
101                    {
102                        this.names.clear();
103                        this.hashCode = 0;
104    
105                        throw new DuplicatePropertyException( value[i].getName() );
106                    }
107                }
108    
109                this.properties = value;
110            }
111        }
112    
113        /**
114         * Gets a property for a name.
115         *
116         * @param name the name of the property to return.
117         *
118         * @return a reference to the property named {@code name}.
119         *
120         * @throws NullPointerException if {@code name} is {@code null}.
121         * @throws MissingPropertyException if no property matching {@code name}
122         * exists in the collection.
123         */
124        public Property getProperty( final String name )
125        {
126            if ( name == null )
127            {
128                throw new NullPointerException( "name" );
129            }
130    
131            final Property ret = (Property) this.names.get( name );
132    
133            if ( ret == null )
134            {
135                throw new MissingPropertyException( name );
136            }
137    
138            return ret;
139        }
140    
141        /**
142         * Gets a property for an index.
143         *
144         * @param index the index of the property to return.
145         *
146         * @return a reference to the property at {@code index}.
147         *
148         * @throws IndexOutOfBoundsException if {@code index} is negativ,
149         * greater than or equal to {@code size()}.
150         */
151        public final Property getProperty( final int index )
152        {
153            if ( index < 0 || index >= this.size() )
154            {
155                throw new ArrayIndexOutOfBoundsException( index );
156            }
157    
158            return this.getProperties()[index];
159        }
160    
161        /**
162         * Setter for a named property from property {@code properties}.
163         *
164         * @param property the property to update or to add to property
165         * {@code properties}.
166         *
167         * @return previous value of the property named {@code property.getName()}
168         * or {@code null} if no property with name {@code property.getName()}
169         * existed before.
170         *
171         * @throws NullPointerException if {@code property} is {@code null}.
172         * @throws IllegalArgumentException if a property with the same name but
173         * different type exists.
174         */
175        public Property setProperty( final Property property )
176        {
177            if ( property == null )
178            {
179                throw new NullPointerException( "property" );
180            }
181    
182            Property ret = null;
183    
184            try
185            {
186                final Property p = this.getProperty( property.getName() );
187                if ( !p.getType().equals( property.getType() ) )
188                {
189                    throw new IllegalArgumentException( p.getType().getName() );
190                }
191                else
192                {
193                    ret = (Property) p.clone();
194                    this.hashCode -= p.hashCode();
195                    p.setValue( property.getValue() );
196                    this.hashCode += p.hashCode();
197                }
198    
199            }
200            catch ( MissingPropertyException e )
201            {
202                final Collection props = Arrays.asList( this.getProperties() );
203                props.add( property );
204                this.setProperties( (Property[]) props.toArray(
205                                    new Property[ props.size() ] ) );
206    
207            }
208    
209            return ret;
210        }
211    
212        /**
213         * Gets the number of properties held by the instance.
214         *
215         * @return the number of properties held by the instance.
216         */
217        public final int size()
218        {
219            return this.getProperties().length;
220        }
221    
222        /**
223         * Creates a string representing the properties of the instance.
224         *
225         * @return a string representing the properties of the instance.
226         */
227        private String internalString()
228        {
229            final StringBuffer buf = new StringBuffer( 200 ).append( '{' );
230            buf.append( this.internalString( this ) );
231    
232            final Property[] props = this.getProperties();
233            for ( int i = props.length - 1; i >= 0; i-- )
234            {
235                buf.append( ", [" ).append( i ).append( "]=" ).append( props[i] );
236            }
237    
238            buf.append( '}' );
239            return buf.toString();
240        }
241    
242        //--------------------------------------------------------------Properties--
243        //--Object------------------------------------------------------------------
244    
245        /**
246         * Indicates whether some other object is equal to this one by comparing
247         * the values of all properties.
248         *
249         * @param o the reference object with which to compare.
250         *
251         * @return {@code true} if this object is the same as {@code o};
252         * {@code false} otherwise.
253         */
254        public boolean equals( final Object o )
255        {
256            boolean equal = this == o;
257    
258            if ( !equal && o instanceof Properties )
259            {
260                final Properties that = (Properties) o;
261                final Collection these = Arrays.asList( this.getProperties() );
262                final Collection those = Arrays.asList( that.getProperties() );
263    
264                equal = this.size() == that.size() && these.containsAll( those );
265            }
266    
267            return equal;
268        }
269    
270        /**
271         * Returns a hash code value for this object.
272         *
273         * @return a hash code value for this object.
274         */
275        public int hashCode()
276        {
277            return this.hashCode;
278        }
279    
280        /**
281         * Returns a string representation of the object.
282         *
283         * @return a string representation of the object.
284         */
285        public String toString()
286        {
287            return super.toString() + this.internalString();
288        }
289    
290        /**
291         * Creates and returns a deep copy of this object.
292         *
293         * @return a clone of this instance.
294         */
295        public Object clone()
296        {
297            try
298            {
299                final Properties ret = (Properties) super.clone();
300                final Property[] props = this.getProperties();
301                final Property[] cloned = new Property[ props.length ];
302    
303                for ( int i = props.length - 1; i >= 0; i-- )
304                {
305                    cloned[i] = (Property) props[i].clone();
306                }
307    
308                ret.setProperties( cloned );
309                return ret;
310            }
311            catch ( CloneNotSupportedException e )
312            {
313                throw new AssertionError( e );
314            }
315        }
316    
317        //------------------------------------------------------------------Object--
318    }