001: /*
002: * This file is part of the Echo Web Application Framework (hereinafter "Echo").
003: * Copyright (C) 2002-2005 NextApp, Inc.
004: *
005: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
006: *
007: * The contents of this file are subject to the Mozilla Public License Version
008: * 1.1 (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: * http://www.mozilla.org/MPL/
011: *
012: * Software distributed under the License is distributed on an "AS IS" basis,
013: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
014: * for the specific language governing rights and limitations under the
015: * License.
016: *
017: * Alternatively, the contents of this file may be used under the terms of
018: * either the GNU General Public License Version 2 or later (the "GPL"), or
019: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
020: * in which case the provisions of the GPL or the LGPL are applicable instead
021: * of those above. If you wish to allow use of your version of this file only
022: * under the terms of either the GPL or the LGPL, and not to allow others to
023: * use your version of this file under the terms of the MPL, indicate your
024: * decision by deleting the provisions above and replace them with the notice
025: * and other provisions required by the GPL or the LGPL. If you do not delete
026: * the provisions above, a recipient may use your version of this file under
027: * the terms of any one of the MPL, the GPL or the LGPL.
028: */
029:
030: package nextapp.echo2.app;
031:
032: import java.io.Serializable;
033: import java.util.Iterator;
034: import java.util.SortedMap;
035: import java.util.TreeMap;
036:
037: /**
038: * A <code>Style</code> implementation which may be modified.
039: * Note that modifications to the <code>Style</code> will not necessarily be
040: * reflected in <code>Component</code>s that use the <code>Style</code>
041: * unless the <code>Component</code>s are specifically informed of the changes,
042: * i.e., by resetting the shared style of a <code>Component</code>.
043: * As such, shared <code>Style</code>s should not be updated once they are
044: * in use by <code>Component</code>s, as it will result in undefined behavior.
045: */
046: public class MutableStyle implements Style {
047:
048: private static final int GROW_RATE = 5 * 2; // Must be a multiple of 2.
049:
050: private static final Object[] EMPTY = new Object[0];
051:
052: /**
053: * An <code>Iterator</code> which returns the names of properties which
054: * are set in the style.
055: */
056: private class PropertyNameIterator implements Iterator {
057:
058: private int index = 0;
059:
060: /**
061: * @see java.util.Iterator#hasNext()
062: */
063: public boolean hasNext() {
064: return index < length;
065: }
066:
067: /**
068: * @see java.util.Iterator#next()
069: */
070: public Object next() {
071: Object value = data[index];
072: index += 2;
073: return value;
074: }
075:
076: /**
077: * @see java.util.Iterator#remove()
078: */
079: public void remove() {
080: throw new UnsupportedOperationException();
081: }
082: }
083:
084: /**
085: * A value object which stores the indexed values of a property.
086: */
087: public class IndexedPropertyValue implements Serializable {
088:
089: private SortedMap indicesToValues;
090:
091: /**
092: * Returns the value at the specified index.
093: *
094: * @param index the index
095: * @return the value
096: */
097: public Object getValue(int index) {
098: if (indicesToValues == null) {
099: return null;
100: } else {
101: return indicesToValues.get(new Integer(index));
102: }
103: }
104:
105: /**
106: * Returns the set property indices as an
107: * <code>Integer</code>-returning <code>Iterator</code>.
108: *
109: * @return an iterator over the indices
110: */
111: public Iterator getIndices() {
112: return indicesToValues.keySet().iterator();
113: }
114:
115: /**
116: * Determines if a value is set at the specified index.
117: *
118: * @param index the index
119: * @return true if a value is set
120: */
121: public boolean hasValue(int index) {
122: return indicesToValues != null
123: && indicesToValues.containsKey(new Integer(index));
124: }
125:
126: /**
127: * Removes the value at the specified index.
128: *
129: * @param index the index
130: */
131: private void removeValue(int index) {
132: if (indicesToValues != null) {
133: indicesToValues.remove(new Integer(index));
134: if (indicesToValues.size() == 0) {
135: indicesToValues = null;
136: }
137: }
138: }
139:
140: /**
141: * Sets the value at the specified index.
142: *
143: * @param index the index
144: * @param value the new property value
145: */
146: private void setValue(int index, Object value) {
147: if (indicesToValues == null) {
148: indicesToValues = new TreeMap();
149: }
150: indicesToValues.put(new Integer(index), value);
151: }
152: }
153:
154: private Object[] data = EMPTY;
155: int length = 0; // Number of items * 2;
156:
157: /**
158: * Default constructor.
159: */
160: public MutableStyle() {
161: super ();
162: }
163:
164: /**
165: * Adds the content of the specified style to this style.
166: *
167: * @param style the style to add
168: */
169: public void addStyleContent(Style style) {
170: Iterator nameIt = style.getPropertyNames();
171: while (nameIt.hasNext()) {
172: String name = (String) nameIt.next();
173: Object value = style.getProperty(name);
174: if (value instanceof IndexedPropertyValue) {
175: IndexedPropertyValue indexedPropertyValue = (IndexedPropertyValue) value;
176: Iterator indexIt = indexedPropertyValue.getIndices();
177: while (indexIt.hasNext()) {
178: int index = ((Integer) indexIt.next()).intValue();
179: setIndexedProperty(name, index,
180: indexedPropertyValue.getValue(index));
181: }
182: } else {
183: setProperty(name, value);
184: }
185: }
186: }
187:
188: /**
189: * @see nextapp.echo2.app.Style#getIndexedProperty(java.lang.String, int)
190: */
191: public Object getIndexedProperty(String propertyName,
192: int propertyIndex) {
193: Object value = retrieveProperty(propertyName);
194: if (!(value instanceof IndexedPropertyValue)) {
195: return null;
196: }
197: return ((IndexedPropertyValue) value).getValue(propertyIndex);
198: }
199:
200: /**
201: * @see nextapp.echo2.app.Style#getProperty(java.lang.String)
202: */
203: public Object getProperty(String propertyName) {
204: return retrieveProperty(propertyName);
205: }
206:
207: /**
208: * @see nextapp.echo2.app.Style#getPropertyIndices(java.lang.String)
209: */
210: public Iterator getPropertyIndices(String propertyName) {
211: Object value = getProperty(propertyName);
212: if (!(value instanceof IndexedPropertyValue)) {
213: return null;
214: }
215: return ((IndexedPropertyValue) value).getIndices();
216: }
217:
218: /**
219: * @see nextapp.echo2.app.Style#getPropertyNames()
220: */
221: public Iterator getPropertyNames() {
222: return new PropertyNameIterator();
223: }
224:
225: /**
226: * @see nextapp.echo2.app.Style#isIndexedPropertySet(java.lang.String, int)
227: */
228: public boolean isIndexedPropertySet(String propertyName, int index) {
229: Object value = retrieveProperty(propertyName);
230: if (!(value instanceof IndexedPropertyValue)) {
231: return false;
232: }
233: return ((IndexedPropertyValue) value).hasValue(index);
234: }
235:
236: /**
237: * @see nextapp.echo2.app.Style#isPropertySet(java.lang.String)
238: */
239: public boolean isPropertySet(String propertyName) {
240: int propertyNameHashCode = propertyName.hashCode();
241: for (int i = 0; i < length; i += 2) {
242: if (propertyNameHashCode == data[i].hashCode()
243: && propertyName.equals(data[i])) {
244: return true;
245: }
246: }
247: return false;
248: }
249:
250: /**
251: * Removes a value of an indexed property from the <code>Style</code>.
252: *
253: * @param propertyName the name of the property
254: * @param propertyIndex the index of the property to remove
255: */
256: public void removeIndexedProperty(String propertyName,
257: int propertyIndex) {
258: Object value = retrieveProperty(propertyName);
259: if (!(value instanceof IndexedPropertyValue)) {
260: return;
261: }
262: ((IndexedPropertyValue) value).removeValue(propertyIndex);
263: }
264:
265: /**
266: * Removes a property from the <code>Style</code>.
267: *
268: * @param propertyName the name of the property to remove
269: */
270: public void removeProperty(String propertyName) {
271: int propertyNameHashCode = propertyName.hashCode();
272: for (int i = 0; i < length; i += 2) {
273: if (propertyNameHashCode == data[i].hashCode()
274: && propertyName.equals(data[i])) {
275: data[i] = data[length - 2];
276: data[i + 1] = data[length - 1];
277: data[length - 2] = null;
278: data[length - 1] = null;
279: length -= 2;
280: break;
281: }
282: }
283:
284: if (length == 0) {
285: data = EMPTY;
286: }
287: }
288:
289: /**
290: * Retrieves locally stored value of property.
291: *
292: * @param propertyName the name of the property
293: * @return the value of the property
294: */
295: private Object retrieveProperty(String propertyName) {
296: int propertyNameHashCode = propertyName.hashCode();
297: for (int i = 0; i < length; i += 2) {
298: if (propertyNameHashCode == data[i].hashCode()
299: && propertyName.equals(data[i])) {
300: return data[i + 1];
301: }
302: }
303: return null;
304: }
305:
306: /**
307: * Sets an indexed property of the <code>Style</code>
308: *
309: * @param propertyName the name of the property
310: * @param propertyIndex the index of the property
311: * @param propertyValue the value of the property
312: */
313: public void setIndexedProperty(String propertyName,
314: int propertyIndex, Object propertyValue) {
315: Object value = retrieveProperty(propertyName);
316: if (!(value instanceof IndexedPropertyValue)) {
317: value = new IndexedPropertyValue();
318: setProperty(propertyName, value);
319: }
320: ((IndexedPropertyValue) value).setValue(propertyIndex,
321: propertyValue);
322: }
323:
324: /**
325: * Sets a property of the <code>Style</code>.
326: * If <code>propertyValue</code> is null, the property will be
327: * removed.
328: *
329: * @param propertyName the name of the property
330: * @param propertyValue the value of the property
331: */
332: public void setProperty(String propertyName, Object propertyValue) {
333: if (propertyValue == null) {
334: removeProperty(propertyName);
335: return;
336: }
337:
338: if (data == EMPTY) {
339: data = new Object[GROW_RATE];
340: }
341:
342: int propertyNameHashCode = propertyName.hashCode();
343: for (int i = 0; i < data.length; i += 2) {
344: if (data[i] == null) {
345: // Property is not set, space remains to set property.
346: // Add property at end.
347: data[i] = propertyName;
348: data[i + 1] = propertyValue;
349: length += 2;
350: return;
351: }
352: if (propertyNameHashCode == data[i].hashCode()
353: && propertyName.equals(data[i])) {
354: // Found property, overwrite.
355: data[i + 1] = propertyValue;
356: return;
357: }
358: }
359:
360: // Array is full: grow array.
361: Object[] newData = new Object[data.length + GROW_RATE];
362: System.arraycopy(data, 0, newData, 0, data.length);
363:
364: newData[data.length] = propertyName;
365: newData[data.length + 1] = propertyValue;
366: length += 2;
367: data = newData;
368: }
369:
370: /**
371: * Returns the number of properties set.
372: *
373: * @return the number of properties set
374: */
375: public int size() {
376: return length / 2;
377: }
378:
379: /**
380: * Returns a debug representation.
381: *
382: * @see java.lang.Object#toString()
383: */
384: public String toString() {
385: StringBuffer out = new StringBuffer("MutableStyle {");
386: for (int i = 0; i < length; i += 2) {
387: out.append(data[i]);
388: out.append("=");
389: out.append(data[i + 1]);
390: if (i < length - 2) {
391: out.append(", ");
392: }
393: }
394: out.append("}");
395: return out.toString();
396: }
397: }
|