/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.core.command.arguments; import java.util.Map; import org.eclipse.core.runtime.Assert; /** * This class provides a mechanism to normalize indexed objects and their * properties into key-value pairs. * * <p> * The keys look like <code>"prefix-index"</code> or * <code>"prefix-index-subkey"</code>, where <code>prefix</code> is an * identifier to protect all objects under a same group, <code>index</code> is * the index of the object, <code>subkey</code> is for storing properties of the * object. * </p> * * <p> * The <code>"prefix-index"</code> key is used to map an object alone, while the * <code>"prefix-index-subkey"</code> key is used to map a property of an * object. * </p> * * <p> * To write objects into a map, iterate over the collection of objects and set * each object/property into the <code>ArrayMapper</code>. To read objects from * a map, iterate over the <code>ArrayMapper</code> and get each object/property * from it. * </p> * * <p> * <b>Note</b> that the method <code>next()</code> should be called at the * beginning of each iteration step to make the index cursor point to the next * object. * </p> * * <p> * <b>Also note</b> that the method <code>setSize()</code> should be called * after writing objects into the map is finished, otherwise the map will be * unable to read objects from. * </p> * * <p> * Samples: * * <pre> * Map writeElementsIntoMap(Collection elements) { * Map data = new HashMap(); * ArrayMapper writer = new ArrayMapper(data, "elements"); * Iterator it = elements.iterator(); * while (it.hasNext()) { * Element element = (Element) it.next(); * writer.next(); * writer.set("content", element.getContent()); * writer.set("time", element.getTime()); * } * writer.setSize(); //DO NOT FORGET THIS LINE! * return data; * } * * Collection readElementsFromMap(Map data) { * Collection elements = new ArrayList(); * ArrayMapper reader = new ArrayMapper(data, "elements"); * while (reader.hasNext()) { * reader.next(); * Element element = new Element(); * element.setContent(reader.get("content")); * element.setTime(reader.get("time")); * elements.add(element); * } * return elements; * } * </pre> * * </p> * * @author Frank Shaka * */ public class ArrayMapper { @SuppressWarnings("rawtypes") private Map map; private String prefix; private int index = -1; /** * Constructs an <code>ArrayMapper</code> instance using the map and prefix. * * @param map * the map to store/load objects * @param prefix * the key prefix of all objects to map, while <code>null</code> * or an empty string indicates no prefix */ @SuppressWarnings("rawtypes") public ArrayMapper(Map map, String prefix) { Assert.isNotNull(map); this.map = map; this.prefix = prefix; } /** * Checks if there are more objects to read. * * @return <code>true</code> if there are more objects to read, or * <code>false</code> otherwise */ public boolean hasNext() { return index + 1 < getSize(); } /** * Moves the index cursor forward to next object. * <p> * <b>NOTE:</b> This method should be called at the beginning of each * iteration step. * </p> */ public void next() { index++; } /** * Manually sets the index cursor to a specific position. This method is not * recommended unless you really want to roll back or skip forward. To move * the index cursor one step forward, use <code>next()</code> instead. * * <p> * Note that no <code>IndexOutOfBoundsException</code> will be raised if the * index is set to an illegal value, so it's the client's responsibility to * set a proper value to prevent reading/writing failure. * </p> * * @param index * the new position of the index cursor */ public void setIndex(int index) { this.index = index; } /** * Puts the object at the current index into the map with the * <code>"prefix-index"</code> key. * * @param value * the object to put */ @SuppressWarnings("unchecked") public void set(Object value) { map.put(getKey(null), value); } /** * Puts the property of the object at the current index into the map with * the <code>"prefix-index-subkey"</code> key. * * @param subkey * the sub-key of this property * @param value * the property to put */ @SuppressWarnings("unchecked") public void set(String subkey, Object value) { map.put(getKey(subkey), value); } /** * Retrieves the object at the current index from the map specified by the * <code>"prefix-index"</code> key. * * @return the object at the current index */ public Object get() { return map.get(getKey(null)); } /** * Retrieves the property of the object at the current index from map * specified by the <code>"prefix-index-subkey"</code> key. * * @param subkey * the sub-key of this property * @return the disired property of the object at the current index */ public Object get(String subkey) { return map.get(getKey(subkey)); } /** * Generates a key using the prefix, the current index and the sub-key. * * @param subkey * the sub-key to use, or <code>null</code> to indicate no * sub-key * @return <code>"prefix-index-subkey"</code> if prefix and subkey are * non-empty, <code>"prefix-index"</code> if prefix is non-empty and * subkey is empty, <code>"index-subkey"</code> if prefix is empty * and subkey is non-empty, or <code>"index"</code> is prefix and * subkey are empty */ private String getKey(String subkey) { if (prefix == null || "".equals(prefix)) //$NON-NLS-1$ return subkey == null || "".equals(subkey) //$NON-NLS-1$ ? String.valueOf(index) : String.valueOf(index) + "-" + subkey; //$NON-NLS-1$ return subkey == null || "".equals(subkey) //$NON-NLS-1$ ? prefix + "-" + index //$NON-NLS-1$ : prefix + "-" + index + "-" + subkey; //$NON-NLS-1$ //$NON-NLS-2$ } /** * Generates the key for the <code>size</code> value. * * @return <code>"prefix-size"</code> if prefix is non-empty, or * <code>"size"</code> otherwise */ private String getSizeKey() { if (prefix == null || "".equals(prefix)) { //$NON-NLS-1$ return "size"; //$NON-NLS-1$ } else { return prefix + "-size"; //$NON-NLS-1$ } } /** * Stores the current size into the map. * <p> * <b>NOTE:</b> This method should be called after all objects are written * into the map. Failing to do so will lead to unability to read objects * from the map. * </p> */ public void setSize() { setSize(index + 1); } /** * Manually stores the size to a specific value. This method is not * recommended unless you have manually changed the index cursor using * <code>setIndex(int)</code>, otherwise use <code>setSize()</code> instead. * * @param size * the new size value */ @SuppressWarnings("unchecked") public void setSize(int size) { map.put(getSizeKey(), String.valueOf(size)); } /** * Gets the number of the objects stored in the map. * * @return the number of all objects, or <code>0</code> if retrieving is * failed. */ public int getSize() { Object size = map.get(getSizeKey()); if (size == null || !(size instanceof String)) return 0; try { return Integer.parseInt((String) size, 10); } catch (NumberFormatException e) { return 0; } } }