View Javadoc

1   /*
2    * Copyright (c) 2003-2008 by Cosylab d. d.
3    *
4    * This file is part of Java-Common.
5    *
6    * Java-Common is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU General Public License as published by
8    * the Free Software Foundation, either version 3 of the License, or
9    * (at your option) any later version.
10   *
11   * Java-Common is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU General Public License for more details.
15   *
16   * You should have received a copy of the GNU General Public License
17   * along with Java-Common.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  
20  package com.cosylab.application.state.impl;
21  
22  import com.cosylab.application.state.*;
23  
24  import com.cosylab.util.StringUtilities;
25  
26  import java.awt.Color;
27  import java.awt.Dimension;
28  import java.awt.Font;
29  import java.awt.Rectangle;
30  
31  import java.io.IOException;
32  import java.io.Serializable;
33  import java.io.Writer;
34  
35  import java.util.Iterator;
36  import java.util.LinkedHashMap;
37  import java.util.Set;
38  
39  
40  /**
41   * This is the default implementation of the <code>State</code> interface. It
42   * is basically extended properties. It uses the <code>LinkedHashMap</code>
43   * for the intermidiate storage. This implementation should be sufficient for
44   * the majority application and it is not likely that a component need to
45   * implement its own state object to exchange with the
46   * <code>StateKeeper</code>
47   *
48   * @author dvitas
49   */
50  public class DefaultState implements State, Serializable
51  {
52  	private static final String keyID = "ID";
53  	private static boolean typed = false;
54  	private static final String typeMark = ".type";
55  	protected LinkedHashMap map = new LinkedHashMap();
56  
57  	/**
58  	 * Turn on/off storing type information about stored objects. Default is
59  	 * off - objects are stored without information about its type. If it is
60  	 * turned on, all objects are written with its type information so the
61  	 * function <code>getType()</code> can be used.
62  	 *
63  	 * @param typed on/off
64  	 */
65  	public static void setTyped(boolean typed)
66  	{
67  		DefaultState.typed = typed;
68  	}
69  
70  	/**
71  					 *
72  					 */
73  	public DefaultState()
74  	{
75  		super();
76  	}
77  	
78  	private void checkKeyName(String name) {
79  		if (name==null) {
80  			throw new IllegalArgumentException("Key name can not be 'null'.");
81  		}
82  		int i= name.indexOf(' ');
83  		if (i>=0) {
84  			throw new IllegalArgumentException("Key name '"+name+"' can not contain whitespace character(s).");
85  		}
86  		i= name.indexOf('*');
87  		if (i>=0) {
88  			throw new IllegalArgumentException("Key name '"+name+"' can not contain '*' character(s).");
89  		}
90  	}
91  
92  	/* (non-Javadoc)
93  	 * @see com.cosylab.application.state.State#putState(java.lang.String, com.cosylab.application.state.State)
94  	 */
95  	public void putState(String key, State state)
96  	{
97  		if (state != null) {
98  			checkKeyName(key);
99  			map.put(key, state);
100 		}
101 	}
102 
103 	/* (non-Javadoc)
104 	 * @see com.cosylab.application.state.State#createChild(java.lang.String)
105 	 */
106 	public State createState(String key)
107 	{
108 		checkKeyName(key);
109 		State s = new DefaultState();
110 		map.put(key, s);
111 
112 		return s;
113 	}
114 
115 	/* (non-Javadoc)
116 	 * @see com.cosylab.application.state.State#getBoolean(java.lang.String)
117 	 */
118 	public boolean getBoolean(String key, boolean defaultValue)
119 	{
120 		String val = getString(key, null);
121 
122 		if (val == null) {
123 			return defaultValue;
124 		}
125 
126 		return Boolean.valueOf(val).booleanValue();
127 	}
128 
129 	/* (non-Javadoc)
130 	 * @see com.cosylab.application.state.State#getChild(java.lang.String)
131 	 */
132 	public State getState(String key)
133 	{
134 		Object obj = map.get(key);
135 
136 		if (obj == null) {
137 			return null;
138 		}
139 
140 		if (!(obj instanceof State)) {
141 			return null;
142 		}
143 
144 		return (State)obj;
145 	}
146 
147 	/* (non-Javadoc)
148 	 * @see com.cosylab.application.state.State#getClas(java.lang.String)
149 	 */
150 	public Class getClass(String key)
151 	{
152 		String value = getString(key, null);
153 
154 		if (value == null) {
155 			return null;
156 		}
157 
158 		try {
159 			return Class.forName(value);
160 		} catch (ClassNotFoundException e) {
161 			return null;
162 		}
163 	}
164 
165 	/* (non-Javadoc)
166 	 * @see com.cosylab.application.state.State#getColor(java.lang.String)
167 	 */
168 	public Color getColor(String key)
169 	{
170 		String value = getString(key, null);
171 
172 		if (value == null) {
173 			return null;
174 		}
175 
176 		try {
177 			int rgb = (int)Long.parseLong(value, 16);
178 			return new Color(rgb, true);
179 		} catch (NumberFormatException e) {
180 			return null;
181 		}
182 	}
183 
184 	/* (non-Javadoc)
185 	 * @see com.cosylab.application.state.State#getDimension(java.lang.String)
186 	 */
187 	public Dimension getDimension(String key)
188 	{
189 		// test if exists
190 		Object obj = map.get(key + ".width");
191 
192 		if (obj == null) {
193 			return null;
194 		}
195 
196 		Dimension dim = new Dimension();
197 		dim.width = getInt(key + ".width", 0);
198 		dim.height = getInt(key + ".height", 0);
199 
200 		return dim;
201 	}
202 
203 	/* (non-Javadoc)
204 	 * @see com.cosylab.application.state.State#getDouble(java.lang.String)
205 	 */
206 	public double getDouble(String key, double defaultValue)
207 	{
208 		Object obj = map.get(key);
209 
210 		if (obj == null) {
211 			return defaultValue;
212 		}
213 
214 		if (obj instanceof Number) {
215 			return ((Number)obj).doubleValue();
216 		}
217 
218 		return Double.parseDouble(obj.toString());
219 	}
220 
221 	/* (non-Javadoc)
222 	 * @see com.cosylab.application.state.State#getDoubleSeq(java.lang.String)
223 	 */
224 	public double[] getDoubleSeq(String key)
225 	{
226 		int length = getInt(key + ".length", -1);
227 
228 		if (length == -1) {
229 			return null;
230 		}
231 
232 		double[] retVal = new double[length];
233 
234 		for (int i = 0; i < retVal.length; i++) {
235 			retVal[i] = getDouble(key + '_' + i + '_', Double.NaN);
236 		}
237 
238 		return retVal;
239 	}
240 	
241 	public Color[] getColorSeq(String key)
242 	{
243 		int length = getInt(key + ".length", -1);
244 
245 		if (length < 0) {
246 			return null;
247 		}
248 
249 		Color[] retVal = new Color[length];
250 
251 		for (int i = 0; i < retVal.length; i++) {
252 			retVal[i] = getColor(key + '_' + i + '_');
253 		}
254 
255 		return retVal;
256 	}
257 
258 	/* (non-Javadoc)
259 	 * @see com.cosylab.application.state.State#getFont(java.lang.String)
260 	 */
261 	public Font getFont(String key)
262 	{
263 		String value = getString(key, null);
264 
265 		if (value == null) {
266 			return null;
267 		}
268 
269 		Font f;
270 
271 		try {
272 			f = Font.decode(value);
273 		} catch (NumberFormatException e) {
274 			return null;
275 		}
276 
277 		return f;
278 	}
279 
280 	public Font[] getFontSeq(String key)
281 	{
282 		int length = getInt(key + ".length", -1);
283 
284 		if (length < 0) {
285 			return null;
286 		}
287 
288 		Font[] retVal = new Font[length];
289 
290 		for (int i = 0; i < retVal.length; i++) {
291 			retVal[i] = getFont(key + '_' + i + '_');
292 		}
293 
294 		return retVal;
295 	}
296 	
297 	/* (non-Javadoc)
298 	 * @see com.cosylab.application.state.State#getID()
299 	 */
300 	public String getID()
301 	{
302 		return getString(keyID, null);
303 	}
304 
305 	/* (non-Javadoc)
306 	 * @see com.cosylab.application.state.State#getInt(java.lang.String)
307 	 */
308 	public int getInt(String key, int defaultValue)
309 	{
310 		Object obj = map.get(key);
311 
312 		if (obj == null) {
313 			return defaultValue;
314 		}
315 
316 		if (obj instanceof Number) {
317 			return ((Number)obj).intValue();
318 		}
319 
320 		return Integer.parseInt(obj.toString());
321 	}
322 
323 	/* (non-Javadoc)
324 	 * @see com.cosylab.application.state.State#getIntSeq(java.lang.String)
325 	 */
326 	public int[] getIntSeq(String key)
327 	{
328 		int length = getInt(key + ".length", -1);
329 
330 		if (length == -1) {
331 			return null;
332 		}
333 
334 		int[] retVal = new int[length];
335 
336 		for (int i = 0; i < retVal.length; i++) {
337 			retVal[i] = getInt(key + '_' + i + '_', Integer.MAX_VALUE);
338 		}
339 
340 		return retVal;
341 	}
342 
343 	/* (non-Javadoc)
344 	 * @see com.cosylab.application.state.State#getObject()
345 	 */
346 	public Object getObject(String key)
347 	{
348 		return map.get(key);
349 	}
350 
351 	/* (non-Javadoc)
352 	 * @see com.cosylab.application.state.State#getType(java.lang.String)
353 	 */
354 	public Class getType(String key)
355 	{
356 		String className = getString(key + typeMark, null);
357 
358 		if (className == null) {
359 			return null;
360 		}
361 
362 		try {
363 			return StringUtilities.classFromString(className);
364 		} catch (Exception e) {
365 			// no op
366 		}
367 
368 		return null;
369 	}
370 
371 	/* (non-Javadoc)
372 	 * @see com.cosylab.application.state.State#getRectangle(java.lang.String)
373 	 */
374 	public Rectangle getRectangle(String key)
375 	{
376 		// test if exists
377 		Object obj = map.get(key + ".x");
378 
379 		if (obj == null) {
380 			return null;
381 		}
382 
383 		Rectangle r = new Rectangle();
384 		r.x = getInt(key + ".x", 0);
385 		r.y = getInt(key + ".y", 0);
386 		r.width = getInt(key + ".width", 0);
387 		r.height = getInt(key + ".height", 0);
388 
389 		return r;
390 	}
391 
392 	/* (non-Javadoc)
393 	 * @see com.cosylab.application.state.State#getString(java.lang.String)
394 	 */
395 	public String getString(String key, String defaultValue)
396 	{
397 		Object obj = map.get(key);
398 
399 		if (obj == null) {
400 			return defaultValue;
401 		}
402 
403 		return obj.toString();
404 	}
405 
406 	/* (non-Javadoc)
407 	 * @see com.cosylab.application.state.State#getStringSeq(java.lang.String)
408 	 */
409 	public String[] getStringSeq(String key)
410 	{
411 		int length = getInt(key + ".length", -1);
412 
413 		if (length == -1) {
414 			return null;
415 		}
416 
417 		String[] retVal = new String[length];
418 
419 		for (int i = 0; i < retVal.length; i++) {
420 			retVal[i] = getString(key + '_' + i + '_', null);
421 		}
422 
423 		return retVal;
424 	}
425 
426 	/* (non-Javadoc)
427 	 * @see com.cosylab.application.state.State#keySet()
428 	 */
429 	public Set keySet()
430 	{
431 		return map.keySet();
432 	}
433 
434 	/* (non-Javadoc)
435 	 * @see com.cosylab.application.state.State#putBoolean(java.lang.String, boolean)
436 	 */
437 	public void putBoolean(String key, boolean value)
438 	{
439 		putString(key, Boolean.toString(value));
440 
441 		if (DefaultState.typed) {
442 			map.put(key + typeMark, Boolean.TYPE);
443 		}
444 	}
445 
446 	/* (non-Javadoc)
447 	 * @see com.cosylab.application.state.State#putClas(java.lang.String, java.lang.Class)
448 	 */
449 	public void putClass(String key, Class value)
450 	{
451 		if (value == null) {
452 			return;
453 		}
454 
455 		putString(key, value.getName());
456 
457 		if (DefaultState.typed) {
458 			map.put(key + typeMark, value.getClass().getName());
459 		}
460 	}
461 
462 	/* (non-Javadoc)
463 	 * @see com.cosylab.application.state.State#putColor(java.lang.String, java.awt.Color)
464 	 */
465 	public void putColor(String key, Color value)
466 	{
467 		if (value == null) {
468 			return;
469 		}
470 
471 		putString(key, Integer.toHexString(value.getRGB()));
472 
473 		if (DefaultState.typed) {
474 			map.put(key + typeMark, value.getClass().getName());
475 		}
476 	}
477 
478 	/* (non-Javadoc)
479 	 * @see com.cosylab.application.state.State#putDimension(java.lang.String, java.awt.Dimension)
480 	 */
481 	public void putDimension(String key, Dimension value)
482 	{
483 		if (value == null) {
484 			return;
485 		}
486 
487 		putInt(key + ".width", value.width);
488 		putInt(key + ".height", value.height);
489 
490 		if (DefaultState.typed) {
491 			map.put(key + typeMark, value.getClass().getName());
492 		}
493 	}
494 
495 	/* (non-Javadoc)
496 	 * @see com.cosylab.application.state.State#putDouble(java.lang.String, double)
497 	 */
498 	public void putDouble(String key, double value)
499 	{
500 		checkKeyName(key);
501 		map.put(key, new Double(value));
502 
503 		if (DefaultState.typed) {
504 			map.put(key + typeMark, Double.TYPE.getName());
505 		}
506 	}
507 
508 	/* (non-Javadoc)
509 	 * @see com.cosylab.application.state.State#putDoubleSeq(java.lang.String, double[])
510 	 */
511 	public void putDoubleSeq(String key, double[] value)
512 	{
513 		if (value == null) {
514 			return;
515 		}
516 
517 		putInt(key + ".length", value.length);
518 
519 		for (int i = 0; i < value.length; i++) {
520 			putDouble(key + '_' + i + '_', value[i]);
521 		}
522 
523 		if (DefaultState.typed) {
524 			map.put(key + typeMark, value.getClass().getName());
525 		}
526 	}
527 	
528 	/* (non-Javadoc)
529 	 * @see com.cosylab.application.state.State#putDoubleSeq(java.lang.String, double[])
530 	 */
531 	public void putColorSeq(String key, Color[] value)
532 	{
533 		if (value == null) {
534 			return;
535 		}
536 
537 		putInt(key + ".length", value.length);
538 
539 		for (int i = 0; i < value.length; i++) {
540 			putColor(key + '_' + i + '_', value[i]);
541 		}
542 
543 		if (DefaultState.typed) {
544 			map.put(key + typeMark, value.getClass().getName());
545 		}
546 	}
547 
548 	/* (non-Javadoc)
549 	 * @see com.cosylab.application.state.State#putFont(java.lang.String, java.awt.Font)
550 	 */
551 	public void putFont(String key, Font value)
552 	{
553 		if (value == null) {
554 			return;
555 		}
556 
557 		String strStyle;
558 
559 		if (value.isBold()) {
560 			strStyle = value.isItalic() ? "bolditalic" : "bold";
561 		} else {
562 			strStyle = value.isItalic() ? "italic" : "plain";
563 		}
564 
565 		putString(key, value.getName() + "-" + strStyle + "-" + value.getSize());
566 
567 		if (DefaultState.typed) {
568 			map.put(key + typeMark, value.getClass().getName());
569 		}
570 	}
571 	
572 	public void putFontSeq(String key, Font[] value)
573 	{
574 		if (value == null) {
575 			return;
576 		}
577 
578 		putInt(key + ".length", value.length);
579 
580 		for (int i = 0; i < value.length; i++) {
581 			putFont(key + '_' + i + '_', value[i]);
582 		}
583 
584 		if (DefaultState.typed) {
585 			map.put(key + typeMark, value.getClass().getName());
586 		}
587 	}
588 
589 	/* (non-Javadoc)
590 	 * @see com.cosylab.application.state.State#putInt(java.lang.String, int)
591 	 */
592 	public void putInt(String key, int value)
593 	{
594 		checkKeyName(key);
595 		map.put(key, new Integer(value));
596 
597 		if (DefaultState.typed) {
598 			map.put(key + typeMark, Integer.TYPE.getName());
599 		}
600 	}
601 
602 	/* (non-Javadoc)
603 	 * @see com.cosylab.application.state.State#putIntSeq(java.lang.String, int[])
604 	 */
605 	public void putIntSeq(String key, int[] value)
606 	{
607 		if (value == null) {
608 			return;
609 		}
610 
611 		putInt(key + ".length", value.length);
612 
613 		for (int i = 0; i < value.length; i++) {
614 			putInt(key + '_' + i + '_', value[i]);
615 		}
616 
617 		if (DefaultState.typed) {
618 			map.put(key + typeMark, value.getClass().getName());
619 		}
620 	}
621 
622 	/* (non-Javadoc)
623 	 * @see com.cosylab.application.state.State#putRectangle(java.lang.String, java.awt.Rectangle)
624 	 */
625 	public void putRectangle(String key, Rectangle value)
626 	{
627 		if (value == null) {
628 			return;
629 		}
630 
631 		putInt(key + ".x", value.x);
632 		putInt(key + ".y", value.y);
633 		putInt(key + ".width", value.width);
634 		putInt(key + ".height", value.height);
635 
636 		if (DefaultState.typed) {
637 			map.put(key + typeMark, value.getClass().getName());
638 		}
639 	}
640 
641 	/* (non-Javadoc)
642 	 * @see com.cosylab.application.state.State#putString(java.lang.String, java.lang.String)
643 	 */
644 	public void putString(String key, String value)
645 	{
646 		if (value == null) {
647 			return;
648 		}
649 
650 		checkKeyName(key);
651 		map.put(key, value);
652 
653 		if (DefaultState.typed) {
654 			map.put(key + typeMark, value.getClass().getName());
655 		}
656 	}
657 
658 	/* (non-Javadoc)
659 	 * @see com.cosylab.application.state.State#putStringSeq(java.lang.String, java.lang.String[])
660 	 */
661 	public void putStringSeq(String key, String[] value)
662 	{
663 		if (value == null) {
664 			return;
665 		}
666 
667 		putInt(key + ".length", value.length);
668 
669 		for (int i = 0; i < value.length; i++) {
670 			putString(key + '_' + i + '_', value[i]);
671 		}
672 
673 		if (DefaultState.typed) {
674 			map.put(key + typeMark, value.getClass().getName());
675 		}
676 	}
677 
678 	/* (non-Javadoc)
679 	 * @see com.cosylab.application.state.State#setID()
680 	 */
681 	public void setID(String ID)
682 	{
683 		putString(keyID, ID);
684 	}
685 
686     private String processValueString(String inString) {
687         if (inString==null) return "null";
688         int len=inString.length();
689         StringBuffer retBuf=new StringBuffer();
690         for (int i = 0; i < len; i++) {
691             char c=inString.charAt(i);
692             if (c>127 || c=='&' || c=='"' || c=='<' || c=='>') {
693                 retBuf.append("&#"+((int)c)+";");
694             } else {
695                 retBuf.append(c);
696             }
697         }
698         return retBuf.toString();
699     }
700     
701 	private void writeState(Writer writer, String name, State state, String linePrefix)
702 		throws IOException
703 	{
704 		boolean hasChilds = false;
705 		writer.write(linePrefix+"<" + name);
706 
707 		Iterator iter = state.keySet().iterator();
708 
709 		while (iter.hasNext()) {
710 			String key = (String)iter.next();
711 			Object value = state.getObject(key);
712 
713 			if (value instanceof State) {
714 				hasChilds = true;
715 
716 				continue;
717 			}
718 
719 			String aa = processValueString(value.toString());
720 			writer.write(" " + key + "=\"" + aa + "\" ");
721 		}
722 
723 		if (hasChilds) {
724 			writer.write(">\n");
725 		} else {
726 			writer.write("/>\n");
727 		}
728 
729 		iter = state.keySet().iterator();
730 
731 		while (iter.hasNext()) {
732 			String key = (String)iter.next();
733 			Object value = state.getObject(key);
734 
735 			if (value instanceof State) {
736 				writeState(writer, key, (State)value, linePrefix+"  ");
737 
738 				continue;
739 			}
740 		}
741 
742 		if (hasChilds) {
743 			writer.write(linePrefix+"</" + name + ">\n");
744 		}
745 	}
746 
747 	/* (non-Javadoc)
748 	 * @see com.cosylab.application.state.State#write(java.io.Writer)
749 	 */
750 	public void writeXML(Writer writer) throws IOException
751 	{
752 		writer.write("<?xml version=\"1.0\" standalone=\"yes\"?>\n<states>\n");
753 		writeState(writer, "state", this, "");
754 		writer.write("</states>\n");
755 		writer.close();
756 	}
757 
758 	/* (non-Javadoc)
759 	 * @see java.lang.Object#toString()
760 	 */
761 	public String toString()
762 	{
763 		StringBuffer sb = new StringBuffer();
764 		Iterator iter = map.keySet().iterator();
765 		sb.append("ID=" + getID());
766 		sb.append('\n');
767 
768 		while (iter.hasNext()) {
769 			Object key = iter.next();
770 			Object value = map.get(key);
771 			sb.append(key.toString());
772 			sb.append('=');
773 			sb.append(value.toString());
774 			sb.append('\n');
775 		}
776 
777 		sb.append('\n');
778 
779 		return new String(sb);
780 	}
781 }
782 
783 /* __oOo__ */