/* * Bibliothek - DockingFrames * Library built on Java/Swing, allows the user to "drag and drop" * panels containing any Swing-Component the developer likes to add. * * Copyright (C) 2010 Benjamin Sigg * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Benjamin Sigg * benjamin_sigg@gmx.ch * CH - Switzerland */ package bibliothek.gui.dock.station.support; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import bibliothek.util.Path; import bibliothek.util.Version; import bibliothek.util.xml.XElement; import bibliothek.util.xml.XException; /** * A data structure designed to store and retrieve placeholder information * persistently. This data structure basically is a map with a restricted set of types * that are allowed to be stored. This map uses arrays of {@link Path}s as {@link Key}s, there are * two modes how to use the keys: * <ul> * <li>If using shared keys, this map will use the <code>equals</code> method to compare keys</li> * <li>If using non-shared keys, this map will use the <code>==</code> operator to compare keys</li> * </ul> * This data structure can work together with a {@link PlaceholderStrategy} to automatically delete * entries that are no longer valid. * @author Benjamin Sigg */ public class PlaceholderMap { /** version of the format */ private int version; /** what kind of data is stored in this map */ private Path format; /** all the data that is stored in this map */ private Map<Key, Map<String, Object>> data = new LinkedHashMap<Key, Map<String,Object>>(); /** strategy observed for automatically removal of invalid placeholders */ private PlaceholderStrategy strategy; /** listener to {@link #strategy} */ private PlaceholderStrategyListener listener = new PlaceholderStrategyListener() { public void placeholderInvalidated( Set<Path> placeholders ){ removeAll( placeholders, false ); } }; /** * Creates a new map. * @param format the kind of data stored in this map, the exact meaning depends on the client * that is using this map * @param version the version of the format, the exact meaning depends on the client * that is using this map */ public PlaceholderMap( Path format, int version ){ if( format == null ){ throw new IllegalArgumentException( "format must not be null" ); } this.format = format; this.version = version; } /** * Creates a new map reading the content of the map directly from <code>in</code>. * @param in the content * @param strategy guard to identify the placeholders which are allowed to be stored, can be <code>null</code> * @throws IOException in case of an I/O error */ public PlaceholderMap( DataInputStream in, PlaceholderStrategy strategy ) throws IOException{ setPlaceholderStrategy( strategy ); Version version = Version.read( in ); if( Version.VERSION_1_1_1a.compareTo( version ) < 0 ){ throw new IOException( "unknown version: " + version ); } this.version = in.readInt(); format = new Path( in.readUTF() ); int size = in.readInt(); for( int i = 0; i < size; i++ ){ PlaceholderKey key = new PlaceholderKey( in, version ); key = key.shrink( strategy ); if( key != null ){ add(key); Map<String, Object> map = data.get( key ); int length = in.readInt(); for( int j = 0; j < length; j++ ){ String subkey = in.readUTF(); Object value = read( in, strategy ); map.put( subkey, value ); } } else{ int length = in.readInt(); for( int j = 0; j < length; j++ ){ in.readUTF(); read( in, strategy ); } } } } /** * Creates a new map reading the content of the map directly from <code>in</code>. * @param in the content to read * @param strategy guard to identify the placeholders which are allowed to be stored, can be <code>null</code> */ public PlaceholderMap( XElement in, PlaceholderStrategy strategy ){ setPlaceholderStrategy( strategy ); XElement xversion = in.getElement( "version" ); if( xversion == null ){ throw new XException( "missing element 'version'" ); } version = xversion.getInt(); XElement xformat = in.getElement( "format" ); if( xformat == null ){ throw new XException( "missing element 'format'" ); } format = new Path( xformat.getString() ); for( int i = 0, n = in.getElementCount(); i<n; i++ ){ XElement xentry = in.getElement( i ); if( xentry.getName().equals( "entry" )){ PlaceholderKey placeholder = new PlaceholderKey( xentry.getElement( "key" ) ); placeholder = placeholder.shrink( strategy ); if( placeholder != null ){ add( placeholder ); Map<String,Object> map = data.get( placeholder ); for( int j = 0, m = xentry.getElementCount(); j<m; j++ ){ XElement xitem = xentry.getElement( j ); if( xitem.getName().equals( "item" )){ String key = xitem.getString( "key" ); Object value = read( xitem, strategy ); map.put( key, value ); } } } } } } /** * Writes the contents of this map into <code>out</code>. * @param out the stream to write into * @throws IOException in case of an I/O error */ public void write( DataOutputStream out ) throws IOException{ Version.write( out, Version.VERSION_1_1_1a ); out.writeInt( version ); out.writeUTF( format.toString() ); out.writeInt( data.size() ); for( Map.Entry<Key, Map<String, Object>> entry : data.entrySet() ){ ((PlaceholderKey)entry.getKey()).write( out ); Map<String, Object> map = entry.getValue(); out.writeInt( map.size() ); for( Map.Entry<String, Object> mapEntry : map.entrySet() ){ out.writeUTF( mapEntry.getKey() ); write( mapEntry.getValue(), out ); } } } private void write( Object value, DataOutputStream out ) throws IOException{ if( value instanceof String ){ out.writeByte( 0 ); out.writeUTF( (String)value ); } else if( value instanceof Integer ){ out.writeByte( 1 ); out.writeInt( (Integer)value ); } else if( value instanceof Long ){ out.writeByte( 2 ); out.writeLong( (Long)value ); } else if( value instanceof Double ){ out.writeByte( 3 ); out.writeDouble( (Double)value ); } else if( value instanceof Boolean ){ out.writeByte( 4 ); out.writeBoolean( (Boolean)value ); } else if( value instanceof PlaceholderMap ){ out.writeByte( 5 ); ((PlaceholderMap)value).write( out ); } else if( value instanceof Object[] ){ out.writeByte( 6 ); Object[] array = (Object[])value; out.writeInt( array.length ); for( Object item : array ){ write( item, out ); } } else if( value instanceof Path ){ out.writeByte( 7 ); out.writeUTF( ((Path)value).toString() ); } else{ throw new IOException( "unknown type: " + value.getClass() ); } } private Object read( DataInputStream in, PlaceholderStrategy strategy ) throws IOException{ byte kind = in.readByte(); switch( kind ){ case 0: return in.readUTF(); case 1: return in.readInt(); case 2: return in.readLong(); case 3: return in.readDouble(); case 4: return in.readBoolean(); case 5: return new PlaceholderMap( in, strategy ); case 6: int length = in.readInt(); Object[] result = new Object[length]; for( int i = 0; i < length; i++ ){ result[i] = read( in, strategy ); } return result; case 7: return new Path( in.readUTF() ); } throw new IOException( "illegal format" ); } /** * Writes the contents of this map into <code>out</code>. * @param out the element to fill, its attributes will not be modified */ public void write( XElement out ){ out.addElement( "version" ).setInt( version ); out.addElement( "format" ).setString( format.toString() ); for( Map.Entry<Key, Map<String, Object>> entry : data.entrySet() ){ XElement xplaceholder = out.addElement( "entry" ); ((PlaceholderKey)entry.getKey()).write( xplaceholder.addElement( "key" ) ); Map<String, Object> map = entry.getValue(); for( Map.Entry<String, Object> mapEntry : map.entrySet() ){ XElement xitem = xplaceholder.addElement( "item" ); xitem.addString( "key", mapEntry.getKey() ); write( mapEntry.getValue(), xitem ); } } } private void write( Object value, XElement out ){ if( value instanceof String ){ out.addString( "type", "s" ); out.setString( (String)value ); } else if( value instanceof Integer ){ out.addString( "type", "i" ); out.setInt( (Integer)value ); } else if( value instanceof Long ){ out.addString( "type", "l" ); out.setLong( (Long)value ); } else if( value instanceof Double ){ out.addString( "type", "d" ); out.setDouble( (Double)value ); } else if( value instanceof Boolean ){ out.addString( "type", "b" ); out.setBoolean( (Boolean)value ); } else if( value instanceof PlaceholderMap ){ out.addString( "type", "p" ); ((PlaceholderMap)value).write( out ); } else if( value instanceof Object[] ){ out.addString( "type", "a" ); Object[] array = (Object[])value; for( Object item : array ){ write( item, out.addElement( "item" ) ); } } else if( value instanceof Path ){ out.addString( "type", "t" ); out.setString( ((Path)value).toString() ); } else{ throw new XException( "unknown type: " + value.getClass() ); } } private Object read( XElement in, PlaceholderStrategy strategy ){ String type = in.getString( "type" ); if( "s".equals( type )){ return in.getString(); } if( "i".equals( type )){ return in.getInt(); } if( "l".equals( type )){ return in.getLong(); } if( "d".equals( type )){ return in.getDouble(); } if( "b".equals( type )){ return in.getBoolean(); } if( "p".equals( type )){ return new PlaceholderMap( in, strategy ); } if( "a".equals( type )){ XElement[] xitems = in.getElements( "item" ); Object[] result = new Object[xitems.length]; for( int i = 0; i < xitems.length; i++ ){ result[i] = read( xitems[i], strategy ); } return result; } if( "t".equals( type )){ return new Path( in.getString() ); } else{ throw new XException( "unknown type: " + type ); } } /** * Creates a deep copy of this map. * @return the copy, not <code>null</code> */ public PlaceholderMap copy(){ PlaceholderMap result = new PlaceholderMap( format, version ); for( Map.Entry<Key, Map<String, Object>> entry : data.entrySet() ){ Key newKey = result.copyKey( entry.getKey() ); result.add( newKey ); Map<String, Object> map = result.data.get( newKey ); for( Map.Entry<String, Object> valueEntry : entry.getValue().entrySet() ){ map.put( valueEntry.getKey(), copy( valueEntry.getValue() ) ); } } return result; } /** * May return this or a copy of this {@link PlaceholderMap} to which <code>strategy</code> was applied. The * strategy of the result may or may not be set. This map is not modified by this method. * @param strategy the strategy to apply * @return either this or a copy of this map */ public PlaceholderMap filter( PlaceholderStrategy strategy ){ if( strategy == null || this.strategy == strategy ){ return this; } PlaceholderMap copy = copy(); copy.setPlaceholderStrategy( strategy ); copy.setPlaceholderStrategy( null ); return copy; } private Object copy( Object value ){ if( value instanceof String ){ return value; } else if( value instanceof Integer ){ return value; } else if( value instanceof Long ){ return value; } else if( value instanceof Double ){ return value; } else if( value instanceof Boolean ){ return value; } else if( value instanceof PlaceholderMap ){ return ((PlaceholderMap)value).copy(); } else if( value instanceof Object[] ){ Object[] array = (Object[])value; Object[] copy = new Object[ array.length ]; for( int i = 0; i < copy.length; i++ ){ copy[i] = copy( array[i] ); } return copy; } else{ throw new IllegalArgumentException( "unknown type: " + value.getClass() ); } } /** * Creates a new shared key for any set of placeholders. The new key will be * equal to any key that is generated by this method using the same arguments. * @param placeholders the placeholders of the key * @return the new key */ public Key newKey( Path... placeholders ){ return newKey( null, placeholders ); } /** * Creates a new shared key for any set of placeholders. The new key will be * equal to any key that is generated by this method using the same arguments. * @param anchor if the anchor is set, then this key cannot be automatically deleted. It is the clients responsibility * to ensure that no two keys have the same anchor. If two keys have the same anchor, then one of them might override * the other one leading to data loss. Can be <code>null</code>. * @param placeholders the placeholders of the key * @return the new key */ public Key newKey( String anchor, Path... placeholders ){ return new PlaceholderKey( anchor, placeholders, true ); } /** * Creates a new non-shared key for any set of placeholders. The new key will * not be equal to any other key but itself. * @param placeholders the placeholders of the key * @return the new key */ public Key newUniqueKey( Path... placeholders ){ return newUniqueKey( null, placeholders ); } /** * Creates a new non-shared key for any set of placeholders. The new key will * not be equal to any other key but itself. * @param placeholders the placeholders of the key * @param anchor if the anchor is set, then this key cannot be automatically deleted. It is the clients responsibility * to ensure that no two keys have the same anchor. If two keys have the same anchor, then one of them might override * the other one leading to data loss. Can be <code>null</code>. * @return the new key */ public Key newUniqueKey( String anchor, Path... placeholders ){ return new PlaceholderKey( anchor, placeholders, false ); } /** * Creates a copy of <code>key</code>. * @param key the key to copy * @return the new key */ public Key copyKey( Key key ){ return new PlaceholderKey( key.getAnchor(), key.getPlaceholders(), key.isShared() ); } /** * Sets the strategy that is used to automatically remove invalid placeholders. This * strategy is recursively applied to all other {@link PlaceholderMap}s that are * stored within this map. * @param strategy the new strategy, can be <code>null</code> */ public void setPlaceholderStrategy( PlaceholderStrategy strategy ){ if( this.strategy != null ){ this.strategy.removeListener( listener ); } this.strategy = strategy; for( Map<?, Object> map : data.values() ){ for( Object value : map.values() ){ setPlaceholderStrategy( value, strategy ); } } if( this.strategy != null ){ validate( this.strategy, false ); this.strategy.addListener( listener ); } } private void setPlaceholderStrategy( Object value, PlaceholderStrategy strategy ){ if( value instanceof PlaceholderMap ){ ((PlaceholderMap)value).setPlaceholderStrategy( strategy ); } else if( value instanceof Object[] ){ for( Object child : (Object[])value ){ setPlaceholderStrategy( child, strategy ); } } } /** * Gets the strategy that is observed for removing invalid placeholders. * @return the strategy, can be <code>null</code> */ public PlaceholderStrategy getPlaceholderStrategy(){ return strategy; } /** * Using <code>strategy</code> removes all placeholders that are invalid. * @param strategy the strategy for checking the placeholders, a value or <code>null</code> * means that all placeholders are valid * @param recursive if <code>true</code> then {@link #validate(PlaceholderStrategy,boolean)} is also * called on all sub-maps of this map */ public void validate( PlaceholderStrategy strategy, boolean recursive ){ if( strategy == null ){ return; } if( recursive ){ for( Map<?, Object> map : data.values() ){ for( Object value : map.values() ){ validate( value, strategy ); } } } Key[] keys = data.keySet().toArray( new Key[ data.size() ] ); for( Key key : keys ){ Key replacement = ((PlaceholderKey)key).shrink( strategy ); if( replacement != key ){ Map<String, Object> map = data.remove( key ); if( replacement != null ){ data.put( replacement, map ); } } } } private void validate( Object value, PlaceholderStrategy strategy ){ if( value instanceof PlaceholderMap ){ ((PlaceholderMap)value).validate( strategy, true ); } else if( value instanceof Object[] ){ for( Object child : (Object[])value ){ validate( child, strategy ); } } } /** * Removes all occurrences of <code>placeholders</code>. * @param placeholder the placeholder to remove * @param recursive if <code>true</code>, this method is called recursively on * every sub-map in this map */ public void removeAll( Path placeholder, boolean recursive ){ Set<Path> placeholders = new HashSet<Path>(); placeholders.add( placeholder ); removeAll( placeholders, recursive ); } /** * Removes all occurrences of all elements of <code>placeholders</code>. * @param placeholders the placeholders to remove * @param recursive if <code>true</code>, this method is called recursively on * every sub-map in this map */ public void removeAll( Set<Path> placeholders, boolean recursive ){ if( placeholders.isEmpty() ){ return; } if( recursive ){ for( Map<?, Object> map : data.values() ){ for( Object value : map.values() ){ removeAll( value, placeholders ); } } } Key[] keys = data.keySet().toArray( new Key[ data.size() ] ); for( Key key : keys ){ Key replacement = ((PlaceholderKey)key).shrink( placeholders ); if( replacement != key ){ Map<String, Object> map = data.remove( key ); if( replacement != null ){ data.put( replacement, map ); } } } } private void removeAll( Object value, Set<Path> invalidated ){ if( value instanceof PlaceholderMap ){ ((PlaceholderMap)value).removeAll( invalidated, true ); } else if( value instanceof Object[] ){ for( Object child : (Object[])value ){ removeAll( child, invalidated ); } } } /** * Gets the version of the format used in this map. * @return the version, its meaning depends on {@link #getFormat() the format} */ public int getVersion(){ return version; } /** * Gets the format of this map, the meaning of the format depends on the client. * @return the format, not <code>null</code> */ public Path getFormat(){ return format; } /** * Adds the placeholder <code>placeholder</code> to this map. Nothing happens * if <code>placeholder</code> is already in this map. * @param placeholder the new placeholder, not <code>null</code> */ public void add( Key placeholder ){ if( placeholder == null ){ throw new IllegalArgumentException( "placeholder must not be null" ); } Map<String, Object> map = data.get( placeholder ); if( map == null ){ map = new LinkedHashMap<String, Object>(); data.put( placeholder, map ); } } /** * Removes <code>placeholder</code> and all information that is associated with * <code>placeholder</code> from this map. * @param placeholder the placeholder to clear */ public void remove( Key placeholder ){ data.remove( placeholder ); } /** * Gets all placeholders that are known to this map in the order * they were {@link #add(Key) added} to this map. * @return all placeholders */ public Key[] getPlaceholders(){ Set<Key> set = data.keySet(); return set.toArray( new Key[ set.size() ] ); } /** * Gets all keys that are in use for <code>placeholder</code> in the order * they were first used to put an object into this map. * @param placeholder some placeholder * @return the associated keys, <code>null</code> if <code>placeholder</code> is not * known to this map */ public String[] getKeys( Key placeholder ){ Map<String,Object> map = data.get( placeholder ); if( map == null ){ return null; } Set<String> set = map.keySet(); return set.toArray( new String[ set.size() ] ); } /** * Tells whether this map is completely empty. * @return <code>true</code> if there are no data stored in this map */ public boolean isEmpty(){ return data.isEmpty(); } /** * Stores the value <code>value</code> in this map, {@link #add(Key) adds} * <code>placeholder</code> if necessary, overrides the value stored at * <code>key</code> if existent. * @param placeholder the placeholder for which <code>value</code> is stored * @param key the unique identifier of the value * @param value the new value, not <code>null</code> */ public void putString( Key placeholder, String key, String value ){ put( placeholder, key, value ); } /** * Stores the value <code>value</code> in this map, {@link #add(Key) adds} * <code>placeholder</code> if necessary, overrides the value stored at * <code>key</code> if existent. * @param placeholder the placeholder for which <code>value</code> is stored * @param key the unique identifier of the value * @param value the new value, not <code>null</code> */ public void putPath( Key placeholder, String key, Path value ){ put( placeholder, key, value ); } /** * Stores the value <code>value</code> in this map, {@link #add(Key) adds} * <code>placeholder</code> if necessary, overrides the value stored at * <code>key</code> if existent. * @param placeholder the placeholder for which <code>value</code> is stored * @param key the unique identifier of the value * @param value the new value */ public void putInt( Key placeholder, String key, int value ){ put( placeholder, key, Integer.valueOf( value ) ); } /** * Stores the value <code>value</code> in this map, {@link #add(Key) adds} * <code>placeholder</code> if necessary, overrides the value stored at * <code>key</code> if existent. * @param placeholder the placeholder for which <code>value</code> is stored * @param key the unique identifier of the value * @param value the new value */ public void putLong( Key placeholder, String key, long value ){ put( placeholder, key, Long.valueOf( value ) ); } /** * Stores the value <code>value</code> in this map, {@link #add(Key) adds} * <code>placeholder</code> if necessary, overrides the value stored at * <code>key</code> if existent. * @param placeholder the placeholder for which <code>value</code> is stored * @param key the unique identifier of the value * @param value the new value */ public void putBoolean( Key placeholder, String key, boolean value ){ put( placeholder, key, Boolean.valueOf( value ) ); } /** * Stores the value <code>value</code> in this map, {@link #add(Key) adds} * <code>placeholder</code> if necessary, overrides the value stored at * <code>key</code> if existent. * @param placeholder the placeholder for which <code>value</code> is stored * @param key the unique identifier of the value * @param value the new value */ public void putDouble( Key placeholder, String key, double value ){ put( placeholder, key, Double.valueOf( value ) ); } /** * Stores the value <code>value</code> in this map, {@link #add(Key) adds} * <code>placeholder</code> if necessary, overrides the value stored at * <code>key</code> if existent. * @param placeholder the placeholder for which <code>value</code> is stored * @param key the unique identifier of the value * @param value the new value, not <code>null</code> */ public void putMap( Key placeholder, String key, PlaceholderMap value ){ put( placeholder, key, value ); } /** * Stores the value <code>value</code> in this map, {@link #add(Key) adds} * <code>placeholder</code> if necessary, overrides the value stored at * <code>key</code> if existent. * @param placeholder the placeholder for which <code>value</code> is stored * @param key the unique identifier of the value * @param value the new value, not <code>null</code>. May contain any data type * for which this map offers a put-method. */ public void putArray( Key placeholder, String key, Object[] value ){ put( placeholder, key, value ); } /** * Stores the value <code>value</code> in this map, {@link #add(Key) adds} * <code>placeholder</code> if necessary, overrides the value stored at * <code>key</code> if existent.<br> * It is the clients responsibility not to introduce cycles of object references. * @param placeholder the placeholder for which <code>value</code> is stored * @param key the unique identifier of the value * @param value the new value, not <code>null</code> * @throws IllegalArgumentException if <code>value</code> is neither * {@link String}, {@link Integer}, {@link Long}, {@link Double}, {@link Boolean} * {@link PlaceholderMap} nor an array of {@link Object}s containing the mentioned data types */ public void put( Key placeholder, String key, Object value ){ Object invalid = invalidType( value ); if( invalid == null ){ add( placeholder ); data.get( placeholder ).put( key, value ); } else{ throw new IllegalArgumentException( "value of illegal type: " + (invalid instanceof String ? invalid : invalid.getClass() )); } } private Object invalidType( Object value ){ if( value instanceof String || value instanceof Integer || value instanceof Long || value instanceof Double || value instanceof Boolean || value instanceof Path || value instanceof PlaceholderMap ){ return null; } if( value instanceof Object[] ){ for( Object item : (Object[])value ){ Object result = invalidType( item ); if( result != null ){ return result; } } } if( value == null ){ return "null"; } return null; } /** * Removes the value that is stored at <code>key</code>. Note that this * method will not remove <code>placeholder</code> from this map even * if there is no data left associated with <code>placeholder</code>. * @param placeholder the placeholder in whose realm the data is deleted * @param key the unique identifier of the removed data * @return the data that was removed, may be <code>null</code> */ public Object remove( Key placeholder, String key ){ Map<String, Object> map = data.get( placeholder ); if( map == null ){ return null; } return map.remove( key ); } /** * Tells whether <code>key</code> exists for <code>placeholder</code>. * @param placeholder the placeholder in whose realm <code>key</code> should exist * @param key the key to search * @return <code>true</code> if there is some data stored */ public boolean contains( Key placeholder, String key ){ return get( placeholder, key ) != null; } /** * Gets the string that is stored under <code>key</code> * @param placeholder the placeholder in whose realm to search * @param key the unique identifier of the searched data * @return the data, not <code>null</code> * @throws IllegalArgumentException if either the value is of the wrong type or missing */ public String getString( Key placeholder, String key ){ Object data = get( placeholder, key ); if( data instanceof String ){ return (String)data; } else{ throw new IllegalArgumentException( "\"" + key + "\" is not a string" ); } } /** * Gets the {@link Path} that is stored under <code>key</code> * @param placeholder the placeholder in whose realm to search * @param key the unique identifier of the searched data * @return the data, not <code>null</code> * @throws IllegalArgumentException if either the value is of the wrong type or missing */ public Path getPath( Key placeholder, String key ){ Object data = get( placeholder, key ); if( data instanceof Path ){ return (Path)data; } else{ throw new IllegalArgumentException(); } } /** * Gets the integer that is stored under <code>key</code> * @param placeholder the placeholder in whose realm to search * @param key the unique identifier of the searched data * @return the data, not <code>null</code> * @throws IllegalArgumentException if either the value is of the wrong type or missing */ public int getInt( Key placeholder, String key ){ Object data = get( placeholder, key ); if( data instanceof Integer ){ return (Integer)data; } else{ throw new IllegalArgumentException(); } } /** * Gets the long that is stored under <code>key</code> * @param placeholder the placeholder in whose realm to search * @param key the unique identifier of the searched data * @return the data, not <code>null</code> * @throws IllegalArgumentException if either the value is of the wrong type or missing */ public long getLong( Key placeholder, String key ){ Object data = get( placeholder, key ); if( data instanceof Long ){ return (Long)data; } else{ throw new IllegalArgumentException(); } } /** * Gets the boolean that is stored under <code>key</code> * @param placeholder the placeholder in whose realm to search * @param key the unique identifier of the searched data * @return the data, not <code>null</code> * @throws IllegalArgumentException if either the value is of the wrong type or missing */ public boolean getBoolean( Key placeholder, String key ){ Object data = get( placeholder, key ); if( data instanceof Boolean ){ return (Boolean)data; } else{ throw new IllegalArgumentException(); } } /** * Gets the double that is stored under <code>key</code> * @param placeholder the placeholder in whose realm to search * @param key the unique identifier of the searched data * @return the data, not <code>null</code> * @throws IllegalArgumentException if either the value is of the wrong type or missing */ public double getDouble( Key placeholder, String key ){ Object data = get( placeholder, key ); if( data instanceof Double ){ return (Double)data; } else{ throw new IllegalArgumentException(); } } /** * Gets the map that is stored under <code>key</code> * @param placeholder the placeholder in whose realm to search * @param key the unique identifier of the searched data * @return the data, not <code>null</code> * @throws IllegalArgumentException if either the value is of the wrong type or missing */ public PlaceholderMap getMap( Key placeholder, String key ){ Object data = get( placeholder, key ); if( data instanceof PlaceholderMap ){ return (PlaceholderMap)data; } else{ throw new IllegalArgumentException(); } } /** * Gets the map that is stored under <code>key</code> * @param placeholder the placeholder in whose realm to search * @param key the unique identifier of the searched data * @return the data, not <code>null</code> * @throws IllegalArgumentException if either the value is of the wrong type or missing */ public Object[] getArray( Key placeholder, String key ){ Object data = get( placeholder, key ); if( data instanceof Object[] ){ return (Object[])data; } else{ throw new IllegalArgumentException(); } } /** * Gets the data that is stored under <code>key</code>. * @param placeholder the realm in which to search * @param key the key of the data * @return the data, may be <code>null</code> */ public Object get( Key placeholder, String key ){ Map<String, Object> map = data.get( placeholder ); if( map == null ){ return null; } return map.get( key ); } @Override public String toString(){ return data.toString(); } /** * A key is a set of {@link Path}s, it is used to identify * entries in a {@link PlaceholderMap}. * @author Benjamin Sigg */ public static interface Key{ /** * Gets the placeholders which make up this key. * @return the placeholders, this array may be empty but not <code>null</code> */ public Path[] getPlaceholders(); /** * Tells whether this key knows <code>placeholder</code>. * @param placeholder the placeholder to search * @return whether <code>placeholder</code> was found */ public boolean contains( Path placeholder ); /** * Gets the anchor. If the anchor is set, then this key cannot be deleted even if all * {@link #getPlaceholders() placeholders} are no longer valid. No two keys may have the * same anchor. * @return the anchor, can be <code>null</code> */ public String getAnchor(); /** * Tells whether this key is shared. Two shared keys are equal if they * have the same array of {@link #getPlaceholders() placeholders}, two non-shared * keys are equal only if they are the same object, a shared and a non-shared key * are never equal. * @return whether this is a shared key */ public boolean isShared(); } /** * Standard implementation of {@link PlaceholderMap.Key}. * @author Benjamin Sigg * */ private class PlaceholderKey implements Key{ private Path[] placeholders; private String anchor; private boolean shared; /** * Creates a new key. * @param anchor the anchor, makes this key accessible even if all placeholders are deleted, can be <code>null</code> * @param placeholders the placeholders which make up this key, must not contain a <code>null</code> value * @param shared how the <code>equals</code> method behaves */ public PlaceholderKey( String anchor, Path[] placeholders, boolean shared ){ if( placeholders == null ){ throw new IllegalArgumentException( "placeholders must not be null" ); } for( Path placeholder : placeholders ){ if( placeholder == null ){ throw new IllegalArgumentException( "placeholders does contain a null value" ); } } this.anchor = anchor; this.placeholders = placeholders; this.shared = shared; } public PlaceholderKey( DataInputStream in, Version version ) throws IOException{ shared = in.readBoolean(); if( Version.VERSION_1_1_1.compareTo( version ) <= 0 ){ if( in.readBoolean() ){ anchor = in.readUTF(); } } placeholders = new Path[ in.readInt() ]; for( int i = 0; i < placeholders.length; i++ ){ placeholders[i] = new Path( in.readUTF() ); } } public PlaceholderKey( XElement in ){ shared = in.getBoolean( "shared" ); XElement xanchor = in.getElement( "anchor" ); if( xanchor != null ){ anchor = xanchor.getString(); } XElement[] xplaceholders = in.getElements( "placeholder" ); placeholders = new Path[ xplaceholders.length ]; for( int i = 0; i < xplaceholders.length; i++ ){ placeholders[i] = new Path( xplaceholders[i].getString() ); } } public void write( DataOutputStream out ) throws IOException{ out.writeBoolean( shared ); if( anchor != null ){ out.writeBoolean( true ); out.writeUTF( anchor ); } else{ out.writeBoolean( false ); } out.writeInt( placeholders.length ); for( Path path : placeholders ){ out.writeUTF( path.toString() ); } } public void write( XElement out ){ out.addBoolean( "shared", shared ); if( anchor != null ){ out.addElement( "anchor" ).setString( anchor ); } for( Path placeholder : placeholders ){ out.addElement( "placeholder" ).setString( placeholder.toString() ); } } /** * Creates a new key by removing any invalid placeholder. * @param strategy the strategy to apply, can be <code>null</code> * @return the new key or <code>null</code> if there is nothing left from this key */ public PlaceholderKey shrink( PlaceholderStrategy strategy ){ if( strategy == null ){ return this; } boolean[] remain = new boolean[placeholders.length]; int count = 0; for( int i = 0; i < remain.length; i++ ){ remain[i] = strategy.isValidPlaceholder( placeholders[i] ); if( remain[i] ){ count++; } } if( count == placeholders.length ){ return this; } if( count == 0 && anchor == null ){ return null; } Path[] copy = new Path[ count ]; int index = 0; for( int i = 0; i < placeholders.length; i++ ){ if( remain[i] ){ copy[ index++ ] = placeholders[i]; } } return new PlaceholderKey( anchor, copy, shared ); } /** * Creates a new key removing all placeholders in <code>invalidated</code> * from this key. * @param invalidated the placeholders that are no longer valid * @return the new key, may be <code>this</code> or <code>null</code> to indicate * that this key is no longer valid */ public PlaceholderKey shrink( Set<Path> invalidated ){ boolean[] remain = new boolean[placeholders.length]; int count = 0; for( int i = 0; i < remain.length; i++ ){ remain[i] = !invalidated.contains( placeholders[i] ); if( remain[i] ){ count++; } } if( count == placeholders.length ){ return this; } if( count == 0 && anchor == null ){ return null; } Path[] copy = new Path[ count ]; int index = 0; for( int i = 0; i < placeholders.length; i++ ){ if( remain[i] ){ copy[ index++ ] = placeholders[i]; } } return new PlaceholderKey( anchor, copy, shared ); } public Path[] getPlaceholders(){ return placeholders; } public boolean contains( Path placeholder ){ for( Path item : placeholders ){ if( item.equals( placeholder )){ return true; } } return false; } public boolean isShared(){ return shared; } public String getAnchor(){ return anchor; } @Override public String toString(){ StringBuilder builder = new StringBuilder(); for( Path placeholder : placeholders ){ builder.append( placeholder ); builder.append( ", " ); } if( anchor != null ){ builder.append( ", anchor=" ).append( anchor ).append( ", " ); } builder.append( "shared=" ); builder.append( shared ); return builder.toString(); } @Override public int hashCode(){ int result = Arrays.hashCode( placeholders ); if( shared ){ return result; } else{ return -result; } } @Override public boolean equals( Object obj ){ if( obj == this ){ return true; } if( !isShared() ){ return false; } if( obj.getClass() != this.getClass() ){ return false; } PlaceholderKey that = (PlaceholderKey)obj; if( !that.isShared() ){ return false; } if( anchor == null && that.getAnchor() != null ){ return false; } if( anchor != null && !anchor.equals( that.getAnchor() )){ return false; } return Arrays.equals( placeholders, that.placeholders ); } } }