/* * 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.support.mode; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import bibliothek.gui.Dockable; import bibliothek.util.Path; import bibliothek.util.Version; import bibliothek.util.xml.XAttribute; import bibliothek.util.xml.XElement; import bibliothek.util.xml.XException; /** * A set of properties extracted from a {@link ModeManager} and its {@link Mode}s. The properties * can be stored persistently and do not depend on any existing object (like for example the {@link Mode}s). * * @author Benjamin Sigg * @param <A> the objects used by {@link ModeManager} to store information * @param <B> the independent objects used by this {@link ModeSettings} to store information */ public class ModeSettings<A,B> { /** the list of known {@link Dockable}s */ private List<DockableEntry> dockables = new ArrayList<DockableEntry>(); /** the list of mode information to store */ private Map<Path, ModeSetting<A>> modes = new HashMap<Path, ModeSetting<A>>(); /** a converter that converts properties from outside to inside */ private ModeSettingsConverter<A, B> converter; /** factories for creating new {@link ModeSetting}s */ private Map<Path, ModeSettingFactory<A>> factories = new HashMap<Path, ModeSettingFactory<A>>(); /** * Creates a new setting * @param converter the converter to read and write properties */ public ModeSettings( ModeSettingsConverter<A, B> converter ){ if( converter == null ) throw new IllegalArgumentException( "converter must not be null" ); this.converter = converter; } /** * Gets the converter that is used to transform internal and external * properties. * @return the converter, never <code>null</code> */ public ModeSettingsConverter<A, B> getConverter(){ return converter; } /** * Adds a factory to this setting. The factory will be used to create * new {@link ModeSetting}s. * @param factory the new factory, not <code>null</code> */ public void addFactory( ModeSettingFactory<A> factory ){ factories.put( factory.getModeId(), factory ); } /** * Adds a new set of properties to this setting. * @param id the unique identifier of this set of properties * @param current the current mode of the set, can be <code>null</code> * @param properties the properties, will be copied by this method * @param history older modes of the setting, will be copied by this method */ public void add( String id, Path current, Map<Path, A> properties, Collection<Path> history ){ if( id == null ) throw new NullPointerException( "id" ); if( properties == null ) throw new NullPointerException( "properties" ); if( history == null ) throw new NullPointerException( "history" ); DockableEntry entry = new DockableEntry(); entry.id = id; entry.current = current; entry.history = history.toArray( new Path[ history.size() ] ); entry.properties = new HashMap<Path, B>(); for( Map.Entry<Path, A> next : properties.entrySet() ){ entry.properties.put( next.getKey(), converter.convertToSetting( next.getValue() ) ); } dockables.add( entry ); } /** * Adds the settings of <code>mode</code> to this. * @param mode the mode whose settings are to be stored */ public void add( Mode<A> mode ){ ModeSettingFactory<A> factory = factories.get( mode.getUniqueIdentifier() ); if( factory == null ) throw new IllegalArgumentException( "no factory present for '" + mode.getUniqueIdentifier() + "'" ); ModeSetting<A> setting = factory.create(); if( setting != null ){ mode.writeSetting( setting ); modes.put( setting.getModeId(), setting ); } } /** * Adds the settings <code>mode</code> to this. * @param mode the properties to store */ public void add( ModeSetting<A> mode ){ modes.put( mode.getModeId(), mode ); } /** * Gets the number of sets this setting stores. * @return the number of sets */ public int size(){ return dockables.size(); } /** * Gets the index of the entry with identifier <code>id</code>. * @param id the identifier of a dockable * @return the entry that represents that dockable, can be -1 */ public int indexOf( String id ){ int index = 0; for( DockableEntry entry : dockables ){ if( entry.id.equals( id )){ return index; } index++; } return -1; } /** * Gets the unique id of the index'th set. * @param index the index of the set * @return the unique id */ public String getId( int index ){ return dockables.get( index ).id; } /** * Gets the current mode of the index'th set. * @param index the index of the set * @return the current mode */ public Path getCurrent( int index ){ return dockables.get( index ).current; } /** * Gets the history of the index'th set. * @param index the index of the set * @return the history */ public Path[] getHistory( int index ){ return dockables.get( index ).history; } /** * Gets the converted properties of the index'th set. * @param index the index of the set * @return a new map of freshly converted properties */ public Map<Path, A> getProperties( int index ){ Map<Path, A> result = new HashMap<Path, A>(); for( Map.Entry<Path, B> entry : dockables.get( index ).properties.entrySet() ){ result.put( entry.getKey(), converter.convertToWorld( entry.getValue() ) ); } return result; } /** * Gets the settings which belong to a {@link Mode} with unique * identifier <code>modeId</code>. * @param modeId the unique identifier of some mode * @return the settings or <code>null</code> if not found */ public ModeSetting<A> getSettings( Path modeId ){ return modes.get( modeId ); } /** * Writes all properties of this setting into <code>out</code>. * @param out the stream to write into * @throws IOException if an I/O-error occurs */ public void write( DataOutputStream out ) throws IOException{ Version.write( out, Version.VERSION_1_0_8 ); out.writeInt( dockables.size() ); for( DockableEntry entry : dockables ){ out.writeUTF( entry.id ); if( entry.current == null ){ out.writeBoolean( false ); } else{ out.writeBoolean( true ); out.writeUTF( entry.current.toString() ); } out.writeInt( entry.history.length ); for( Path history : entry.history ) out.writeUTF( history.toString() ); out.writeInt( entry.properties.size() ); for( Map.Entry<Path, B> next : entry.properties.entrySet() ){ out.writeUTF( next.getKey().toString() ); converter.writeProperty( next.getValue(), out ); } } out.writeInt( modes.size() ); for( ModeSetting<A> mode : modes.values() ){ // storing id - byte count - bytes out.writeUTF( mode.getModeId().toString() ); ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream( bout ); mode.write( dout, converter ); out.writeInt( bout.size() ); bout.writeTo( out ); dout.close(); } } /** * Called if some setting with version < 1.0.8 is found. Subclasses * may override this method to read and interpret the old settings. The * default implementation does nothing. * @param in the stream to read from * @param version the version that was found * @throws IOException in case of an error */ protected void rescueSettings( DataInputStream in, Version version ) throws IOException{ // nothing } /** * Called if some setting does not have the "modes" entry, the assumption is that * only old settings are missing this entry. * @param element the entry that has to be rescued * @throws XException in case of an error related to a wrongly read attribute */ protected void rescueSettings( XElement element ){ // nothing } /** * Called if a single identifier of a mode is found as was used in * version 1.0.7 and below. * @param identifier the single identifier * @return the identifier of the matching mode, can be <code>null</code> */ protected Path resuceMode( String identifier ){ return null; } /** * Clears all properties of this setting and then reads new properties * from <code>in</code>. * @param in the stream to read from * @throws IOException if an I/O-error occurs */ public void read( DataInputStream in ) throws IOException{ Version version = Version.read( in ); version.checkCurrent(); boolean version7 = Version.VERSION_1_0_7.compareTo( version ) >= 0; if( version7 ){ Version.read( in ); } dockables.clear(); for( int i = 0, n = in.readInt(); i<n; i++ ){ DockableEntry entry = new DockableEntry(); dockables.add( entry ); entry.id = in.readUTF(); if( in.readBoolean() ){ String key = in.readUTF(); entry.current = version7 ? resuceMode( key ) : new Path( key ); } entry.history = new Path[ in.readInt() ]; for( int j = 0; j < entry.history.length; j++ ){ String key = in.readUTF(); entry.history[j] = version7 ? resuceMode( key ) : new Path( key ); } if( version7 ){ int count = 0; for( int j = 0; j < entry.history.length; j++ ){ if( entry.history[j] != null ){ count++; } } if( count != entry.history.length ){ Path[] temp = entry.history; entry.history = new Path[ count ]; int index = 0; for( int j = 0; j < temp.length; j++ ){ if( temp[j] != null ){ entry.history[ index++ ] = temp[j]; } } } } entry.properties = new HashMap<Path, B>(); for( int j = 0, m = in.readInt(); j<m; j++ ){ String key = in.readUTF(); Path mode = version7 ? resuceMode( key ) : new Path( key ); B property = converter.readProperty( in ); if( mode != null ){ entry.properties.put( mode, property ); } } } // new since 1.0.8 modes.clear(); if( version7 ){ rescueSettings( in, version ); } else{ for( int i = 0, n = in.readInt(); i<n; i++ ){ Path id = new Path( in.readUTF() ); int count = in.readInt(); byte[] content = new byte[ count ]; int offset = 0; int length = count; int read; while( (length > 0) && ((read = in.read( content, offset, length )) > 0) ){ offset += read; length -= read; } ByteArrayInputStream bin = new ByteArrayInputStream( content ); DataInputStream din = new DataInputStream( bin ); ModeSettingFactory<A> factory = factories.get( id ); if( factory != null ){ ModeSetting<A> setting = factory.create(); setting.read( din, converter ); din.close(); modes.put( setting.getModeId(), setting ); } } } } /** * Writes the contents of this setting in xml format. * @param element the element to write into, the attributes of * element will not be changed. * @see #readXML(XElement) */ public void writeXML( XElement element ){ XElement delement = element.addElement( "dockables" ); for( DockableEntry entry : dockables ){ XElement xentry = delement.addElement( "entry" ); xentry.addString( "id", entry.id ); if( entry.current != null ) xentry.addString( "current", entry.current.toString() ); XElement xhistory = xentry.addElement( "history" ); for( Path history : entry.history ){ xhistory.addElement( "mode" ).setString( history.toString() ); } XElement xproperties = xentry.addElement( "properties" ); for( Map.Entry<Path, B> next : entry.properties.entrySet() ){ XElement xproperty = xproperties.addElement( "property" ); xproperty.addString( "id", next.getKey().toString() ); converter.writePropertyXML( next.getValue(), xproperty ); } } XElement melement = element.addElement( "modes" ); for( ModeSetting<A> mode : modes.values() ){ XElement xmode = melement.addElement( "entry" ); xmode.addString( "id", mode.getModeId().toString() ); mode.write( xmode, converter ); } } /** * Clears all properties of this setting and then reads new properties * from <code>element</code>. * @param element the element from which the properties should be read * @see #writeXML(XElement) */ public void readXML( XElement element ){ dockables.clear(); XElement delement = element.getElement( "dockables" ); if( delement != null ){ for( XElement xentry : delement.getElements( "entry" )){ DockableEntry entry = new DockableEntry(); dockables.add( entry ); entry.id = xentry.getString( "id" ); XAttribute current = xentry.getAttribute( "current" ); if( current != null ) entry.current = new Path( current.getString() ); XElement xhistory = xentry.getElement( "history" ); if( xhistory == null ) entry.history = new Path[]{}; else{ XElement[] xmodes = xhistory.getElements( "mode" ); entry.history = new Path[ xmodes.length ]; for( int i = 0; i < xmodes.length; i++ ) entry.history[i] = new Path( xmodes[i].getString() ); } XElement xproperties = xentry.getElement( "properties" ); entry.properties = new HashMap<Path, B>(); if( xproperties != null ){ for( XElement xproperty : xproperties.getElements( "property" )){ entry.properties.put( new Path( xproperty.getString( "id" )), converter.readPropertyXML( xproperty ) ); } } } } modes.clear(); XElement melement = element.getElement( "modes" ); if( melement != null ){ for( XElement xmode : melement.getElements( "entry" )){ Path id = new Path( xmode.getString( "id" )); ModeSettingFactory<A> factory = factories.get( id ); if( factory != null ){ ModeSetting<A> setting = factory.create(); setting.read( xmode, converter ); modes.put( setting.getModeId(), setting ); } } } else{ rescueSettings( element ); } } /** * The properties of one {@link Dockable} * @author Benjamin Sigg */ private class DockableEntry{ /** the unique id of this entry */ public String id; /** the current mode of this entry */ public Path current; /** a set of properties that has been built by the client */ public Map<Path, B> properties; /** The modes this entry already visited. No mode is more than once in this list. */ public Path[] history; } }