All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bibliothek.gui.dock.layout.DockSituation Maven / Gradle / Ivy

The newest version!
/*
 * 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) 2007 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
 * [email protected]
 * CH - Switzerland
 */

package bibliothek.gui.dock.layout;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DefaultDockable;
import bibliothek.gui.dock.DockElement;
import bibliothek.gui.dock.DockFactory;
import bibliothek.gui.dock.FlapDockStation;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.dockable.DefaultDockableFactory;
import bibliothek.gui.dock.perspective.Perspective;
import bibliothek.gui.dock.perspective.PerspectiveElement;
import bibliothek.gui.dock.station.flap.FlapDockStationFactory;
import bibliothek.gui.dock.station.screen.ScreenDockStationFactory;
import bibliothek.gui.dock.station.split.SplitDockStationFactory;
import bibliothek.gui.dock.station.stack.StackDockStationFactory;
import bibliothek.gui.dock.station.support.PlaceholderStrategy;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.gui.dock.util.extension.ExtensionManager;
import bibliothek.gui.dock.util.extension.ExtensionName;
import bibliothek.util.Path;
import bibliothek.util.Todo;
import bibliothek.util.Version;
import bibliothek.util.Todo.Compatibility;
import bibliothek.util.Todo.Priority;
import bibliothek.util.xml.XAttribute;
import bibliothek.util.xml.XElement;
import bibliothek.util.xml.XException;

/**
 * A DockSituation is a converter: the relationship of {@link DockStation}s and {@link Dockable}s,
 * the position of Dockables and other information are converted into a 
 * stream of bytes. The other direction, read a stream and create Dockables and DockStations, is also possible.
* @author Benjamin Sigg */ public class DockSituation { /** Name for an {@link ExtensionName} to load additional {@link DockFactory}s */ public static final Path DOCK_FACTORY_EXTENSION = new Path("dock.DockSituation.DockFactory"); /** Name for an {@link ExtensionName} to load additional {@link AdjacentDockFactory}s */ public static final Path ADJACENT_DOCK_FACTORY_EXTENSION = new Path("dock.DockSituation.AdjacentDockFactory"); /** Name of a parameter of an {@link ExtensionName} pointing to this */ public static final String EXTENSION_PARAM = "situation"; /** the factories used to create new {@link DockElement elements}*/ private Map> factories = new HashMap>(); /** the factory used when no {@link DockFactory} is available */ private MissingDockFactory missingFactory; /** a set of additional factories for the {@link DockElement}s */ private Map> adjacent = new HashMap>(); /** the factory used when no {@link AdjacentDockFactory} is available */ private MissingDockFactory missingAdjacent; /** a filter for elements which should be ignored */ private DockSituationIgnore ignore; /** strategy used to filter placeholders in the intermediate format */ private PlaceholderStrategy intermediatePlaceholders; /** strategy used to filter placeholders when converting the intermediate format to real {@link DockElement}s */ private PlaceholderStrategy placeholders; /** * Constructs a new DockSituation and sets some factories which are * used to create new {@link DockElement DockElements}. Please note that this * constructor does not add the default factories, hence it should be used with care. * @param factories the factories */ public DockSituation( DockFactory...factories ){ for( DockFactory factory : factories ) this.factories.put( getID( factory ), factory ); } /** * Constructs a new DockSituation. Factories for {@link DefaultDockable}, * {@link SplitDockStation}, {@link StackDockStation} and * {@link FlapDockStation} will be preinstalled. * @param controller {@link DockController} in whose realm this {@link DockSituation} will be used, the * controller is used to access the {@link ExtensionManager} and load additional factories */ public DockSituation( DockController controller ){ this( new DefaultDockableFactory(), new SplitDockStationFactory(), new StackDockStationFactory(), new FlapDockStationFactory()); @SuppressWarnings("rawtypes") List factories = controller.getExtensions().load( new ExtensionName( DOCK_FACTORY_EXTENSION, DockFactory.class, EXTENSION_PARAM, this ) ); for( DockFactory factory : factories ){ add( factory ); } @SuppressWarnings("rawtypes") List adjacent = controller.getExtensions().load( new ExtensionName( ADJACENT_DOCK_FACTORY_EXTENSION, AdjacentDockFactory.class, EXTENSION_PARAM, this ) ); for( AdjacentDockFactory factory : adjacent ){ addAdjacent( factory ); } } /** * Creates a new {@link Perspective} that uses the settings made on this {@link DockSituation}. Changes on the * properties of this {@link DockSituation} will be noticed and used by the created {@link Perspective}. However * changes on the properties of the {@link Perspective} will not influence this {@link DockSituation}.
* Note that subclasses may create {@link Perspective}s that need the client to make additional settings before * it can be used. * @return the new perspective */ public Perspective createPerspective(){ return new Perspective( this ){ @Override protected String getID( PerspectiveElement element ){ return DockSituation.this.getID( element ); } @Override protected DockFactory getFactory( String id ){ return DockSituation.this.getFactory( id ); } }; } /** * Sets a filter which decides, which elements (stations and dockables) * are stored. * @param ignore the filter or null */ public void setIgnore( DockSituationIgnore ignore ) { this.ignore = ignore; } /** * Gets the filter which decides, which elements are stored. * @return the filter or null */ public DockSituationIgnore getIgnore() { return ignore; } /** * Sets a strategy for deleting invalid placeholders. * @param placeholders the strategy, null for keeping all placeholders */ public void setPlaceholderStrategy( PlaceholderStrategy placeholders ){ this.placeholders = placeholders; } /** * Gets the current strategy for removing invalid placeholders. * @return the strategy, may be null */ public PlaceholderStrategy getPlaceholderStrategy(){ return placeholders; } /** * Sets the strategy for deleting invalid placeholders in the intermediate format * @param intermediatePlaceholders the strategy, can be null */ public void setIntermediatePlaceholders( PlaceholderStrategy intermediatePlaceholders ){ this.intermediatePlaceholders = intermediatePlaceholders; } /** * Gets the strategy for deleting invalid placeholders in the intermediate format. * @return the intermediate strategy, can be null */ public PlaceholderStrategy getIntermediatePlaceholders(){ return intermediatePlaceholders; } /** * Gets a placeholder for element using the current {@link PlaceholderStrategy}. * @param element some element, not null * @return the placeholder, can be null */ protected Path getPlaceholder( DockElement element ){ if( placeholders == null ){ return null; } Dockable dockable = element.asDockable(); if( dockable == null ){ return null; } return placeholders.getPlaceholderFor( dockable ); } /** * Adds a factory * @param factory the additional factory */ public void add( DockFactory factory ){ factories.put( getID( factory ), factory ); } /** * Adds an adjacent factory * @param factory the new factory */ public void addAdjacent( AdjacentDockFactory factory ){ adjacent.put( getAdjacentID( factory ), factory ); } /** * Sets a factory which is used whenever no ordinary {@link DockFactory} * can be found to read something. The missingFactory can convert * the input to any {@link Object} it likes, but if a missing factory * is later added to this situation, then that object needs to be casted * into the object used by the original factory. So when working with * a {@link MissingDockFactory} handling different types of layout-data * needs to be done very carefully. Note that this factory is only used to convert * byte-stream or xml data to the intermediate format. It cannot create any {@link Dockable} or * {@link DockElement} nor store any data. * @param missingFactory the factory, can be null */ public void setMissingFactory( MissingDockFactory missingFactory ) { this.missingFactory = missingFactory; } /** * Gets the factory which is used when another factory is missing. * @return the factory replacing missing factories, can be null * @see #setMissingFactory(MissingDockFactory) */ public MissingDockFactory getMissingFactory() { return missingFactory; } /** * Sets a factory which is used when a {@link AdjacentDockFactory} is missing. * There are the same issues with this factory than with the one used * by {@link #setMissingFactory(MissingDockFactory)}. * @param missingAdjacent the new factory, can be null * @see #setMissingFactory(MissingDockFactory) */ public void setMissingAdjacentFactory( MissingDockFactory missingAdjacent ) { this.missingAdjacent = missingAdjacent; } /** * Gets the factory which is used when another {@link AdjacentDockFactory} * is missing. * @return the factory, can be null * @see #setMissingAdjacentFactory(MissingDockFactory) */ public MissingDockFactory getMissingAdjacentFactory() { return missingAdjacent; } /** * Converts the layout of element and all its children into a * {@link DockLayoutComposition}. * @param element the element to convert * @return the composition or null if the element is ignored * @throws IllegalArgumentException if one element has an unknown id of * a {@link DockFactory}. * @throws ClassCastException if an element does not specify the correct * {@link DockFactory}. */ @SuppressWarnings("unchecked") public DockLayoutComposition convert( DockElement element ){ if( ignoreElement( element )) return null; String id = getID( element ); DockFactory factory = (DockFactory)getFactory( id ); if( factory == null ) throw new IllegalArgumentException( "Unknown factory-id: " + element.getFactoryID() ); DockStation station = element.asDockStation(); Map ids = new HashMap(); List children = new ArrayList(); boolean ignore = false; if( station != null ){ ignore = ignoreChildren( station ); if( !ignore ){ int index = 0; for( int i = 0, n = station.getDockableCount(); i layout = new DockLayout( id, data ); List> adjacent = null; for( AdjacentDockFactory adjacentFactory : this.adjacent.values() ){ if( adjacentFactory.interested( element )){ Object adjacentData = adjacentFactory.getLayout( element, ids ); if( adjacent == null ){ adjacent = new ArrayList>(); } adjacent.add( new DockLayout( getAdjacentID( adjacentFactory ), adjacentData ) ); } } DockLayoutInfo info = new DockLayoutInfo( layout ); info.setPlaceholder( getPlaceholder( element ) ); return new DockLayoutComposition( info, adjacent, children, ignore ); } /** * Reads the contents of composition and tries to create a * {@link DockElement} that matches the composition. * @param composition the composition to analyze * @return the new element, can be something else then a {@link DockElement} * if the factory for composition was not found */ @SuppressWarnings("unchecked") public DockElement convert( DockLayoutComposition composition ){ DockLayoutInfo info = composition.getLayout(); if( info == null ) return null; DockLayout layout = info.getDataLayout(); if( layout == null ) return null; DockFactory factory = (DockFactory)getFactory( layout.getFactoryID() ); if( factory == null ) return null; DockElement result = null; Map children = null; if( composition.isIgnoreChildren() ){ for( DockLayoutComposition childComposition : composition.getChildren() ){ convert( childComposition ); } result = factory.layout( layout.getData(), placeholders ); } else{ children = new HashMap(); int index = 0; for( DockLayoutComposition childComposition : composition.getChildren() ){ DockElement child = convert( childComposition ); if( child != null ){ Dockable dockable = child.asDockable(); if( dockable != null ){ children.put( index, dockable ); if( dockable.getDockParent() != null ){ dockable.getDockParent().drag( dockable ); } } } index++; } result = factory.layout( layout.getData(), children, placeholders ); } if( result != null ){ List> adjacent = composition.getAdjacent(); if( adjacent != null ){ for( DockLayout adjacentLayout : adjacent ){ AdjacentDockFactory adjacentFactory = (AdjacentDockFactory)getAdjacentFactory( adjacentLayout.getFactoryID() ); if( adjacentFactory != null ){ if( children == null ){ adjacentFactory.setLayout( result, adjacentLayout.getData(), placeholders ); } else{ adjacentFactory.setLayout( result, adjacentLayout.getData(), children, placeholders ); } } } } } return result; } /** * Writes the contents of composition and all its children * to out. * @param composition the composition to write, should be created by * this {@link DockSituation} or a DockSituation with * similar properties. * @param out the stream to write into * @throws IOException if an I/O-error occurs */ public void writeComposition( DockLayoutComposition composition, DataOutputStream out ) throws IOException{ Version.write( out, Version.VERSION_1_0_8 ); writeCompositionStream( composition, out ); } /** * Writes the contents of composition and all its children * to out. * @param composition the composition to write, should be created by * this {@link DockSituation} or a DockSituation with * similar properties. * @param out the stream to write into * @throws IOException if an I/O-error occurs */ @SuppressWarnings("unchecked") private void writeCompositionStream( DockLayoutComposition composition, DataOutputStream out ) throws IOException{ DockLayoutInfo info = composition.getLayout(); // placeholder Path placeholder = info.getPlaceholder(); out.writeBoolean( placeholder != null ); if( placeholder != null ){ out.writeUTF( placeholder.toString() ); } if( info.getKind() == DockLayoutInfo.Data.BYTE ){ // data out.write( info.getDataByte() ); } else if( info.getKind() == DockLayoutInfo.Data.DOCK_LAYOUT ){ DockLayout layout = info.getDataLayout(); DockFactory factory = (DockFactory)getFactory( layout.getFactoryID() ); if( factory == null ) throw new IOException( "Missing factory: " + layout.getFactoryID() ); // factory out.writeUTF( getID( factory ) ); // contents ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream( bout ); factory.write( layout.getData(), dout ); dout.close(); out.writeInt( bout.size() ); bout.writeTo( out ); } else{ // there is nothing to write... throw new IllegalArgumentException( "Cannot store layout in stream: it was never translated and its raw format is not a byte stream" ); } // adjacent List> adjacentLayouts = composition.getAdjacent(); if( adjacentLayouts == null ){ out.writeInt( 0 ); } else{ out.writeInt( adjacentLayouts.size() ); for( DockLayout adjacentLayout : adjacentLayouts ){ AdjacentDockFactory adjacentFactory = (AdjacentDockFactory)getAdjacentFactory( adjacentLayout.getFactoryID() ); if( adjacentFactory == null ) throw new IOException( "Missing adjacent factory: " + adjacentLayout.getFactoryID() ); ByteArrayOutputStream adjacentBOut = new ByteArrayOutputStream(); DataOutputStream adjacentOut = new DataOutputStream( adjacentBOut ); adjacentFactory.write( adjacentLayout.getData(), adjacentOut ); adjacentOut.close(); out.writeUTF( getAdjacentID( adjacentFactory ) ); out.writeInt( adjacentBOut.size() ); adjacentBOut.writeTo( out ); } } // ignore out.writeBoolean( composition.isIgnoreChildren() ); // children List children = composition.getChildren(); out.writeInt( children.size() ); for( DockLayoutComposition child : children ){ writeCompositionStream( child, out ); } } /** * Reads one {@link DockLayoutComposition} and all its children. * @param in the stream to read from * @return the new composition or null if the factory was missing * @throws IOException if an I/O-error occurs */ public DockLayoutComposition readComposition( DataInputStream in ) throws IOException{ Version version = Version.read( in ); version.checkCurrent(); return readCompositionStream( in, version ); } /** * Reads one {@link DockLayoutComposition} and all its children. * @param in the stream to read from * @param version the format of in * @return the new composition or null if the factory was missing * @throws IOException if an I/O-error occurs */ @SuppressWarnings("unchecked") private DockLayoutComposition readCompositionStream( DataInputStream in, Version version ) throws IOException{ // factory Path entryPlaceholder = null; if( Version.VERSION_1_0_8.compareTo( version ) <= 0 ){ if( in.readBoolean() ){ entryPlaceholder = new Path( in.readUTF() ); } } byte[] entry = readBuffer( in ); DockLayoutInfo info = readEntry( entry, entryPlaceholder ); List> adjacentLayouts = null; if( Version.VERSION_1_0_7.compareTo( version ) <= 0 ){ // adjacent int layoutCount = in.readInt(); if( layoutCount > 0 ){ adjacentLayouts = new ArrayList>( layoutCount ); for( int i = 0; i < layoutCount; i++ ){ String adjacentFactoryId = in.readUTF(); int adjacentCount = in.readInt(); AdjacentDockFactory adjacentFactory = (AdjacentDockFactory)getAdjacentFactory( adjacentFactoryId ); if( adjacentFactory == null ){ if( missingAdjacent == null ){ // skip while( adjacentCount > 0 ){ int skipped = (int)in.skip( adjacentCount ); if( skipped <= 0 ) throw new EOFException(); adjacentCount -= skipped; } } else{ DataInputStream din = readBuffer( in, adjacentCount ); Object data = missingAdjacent.read( getAdjacentFactoryID( adjacentFactoryId ), din, adjacentCount ); if( data != null ){ adjacentLayouts.add( new DockLayout( adjacentFactoryId, data ) ); } din.close(); } } else{ DataInputStream din = readBuffer( in, adjacentCount ); Object data = adjacentFactory.read( din, intermediatePlaceholders ); if( data != null ){ adjacentLayouts.add( new DockLayout( adjacentFactoryId, data ) ); } din.close(); } } } } // ignore boolean ignore = in.readBoolean(); // children List children = new ArrayList(); int count = in.readInt(); for( int i = 0; i < count; i++ ){ children.add( readCompositionStream( in, version ) ); } // result return new DockLayoutComposition( info, adjacentLayouts, children, ignore ); } /** * Tries to read entry. Entry will be read by a {@link DataInputStream}, * it must start with an utf-String used as id for a {@link DockFactory}, * then followed by an int telling how many bytes are in the remaining * array. The rest of the array will be given to a {@link DockFactory}. * @param entry the entry to read * @param placeholder the placeholder which is associated with this element * @return the information that was obtained, may be null * @throws IOException if entry has not the correct format */ @SuppressWarnings("unchecked") private DockLayoutInfo readEntry( byte[] entry, Path placeholder ) throws IOException{ DataInputStream entryIn = new DataInputStream( new ByteArrayInputStream( entry )); String factoryId = entryIn.readUTF(); DockFactory factory = (DockFactory)getFactory( factoryId ); // contents DockLayoutInfo info; int count = entryIn.readInt(); if( factory == null ){ // try read info = null; if( missingFactory != null ){ Object data = missingFactory.read( getFactoryID( factoryId ), entryIn, count ); entryIn.close(); if( data != null ){ info = new DockLayoutInfo( new DockLayout( factoryId, data )); info.setPlaceholder( placeholder ); } } if( info == null ){ info = new DockLayoutInfo( entry ); info.setPlaceholder( placeholder ); } } else{ Object data = factory.read( entryIn, intermediatePlaceholders ); if( data == null ){ info = null; } else{ info = new DockLayoutInfo( new DockLayout( factoryId, data ) ); info.setPlaceholder( placeholder ); } entryIn.close(); } return info; } /** * Reads count bytes from in and returns them * in a new stream. * @param in the stream to read from * @param count the number of bytes to read * @return a new stream with count bytes * @throws IOException if in cannot be read or does not * have count elements */ private DataInputStream readBuffer( DataInputStream in, int count ) throws IOException{ byte[] buffer = new byte[ count ]; int read = 0; while( read < count ){ int input = in.read( buffer, read, count-read ); if( input < 0 ) throw new EOFException(); read += input; } ByteArrayInputStream bin = new ByteArrayInputStream( buffer ); DataInputStream din = new DataInputStream( bin ); return din; } /** * Reads one entry of the stream and returns the whole entry. * An entry starts with an UTF-String, then an int telling how many bytes * follow, then an array of bytes. * @param in the stream to read from * @return the whole entry * @throws IOException if the entry cannot be read from in */ private byte[] readBuffer( DataInputStream in ) throws IOException{ String factory = in.readUTF(); int count = in.readInt(); ByteArrayOutputStream out = new ByteArrayOutputStream( factory.length()*4 + 4 + count ); DataOutputStream dout = new DataOutputStream( out ); dout.writeUTF( factory ); dout.writeInt( count ); for( int i = 0; i < count; i++ ){ int read = in.read(); if( read == -1 ) throw new EOFException( "unexpectetly reached end of file" ); dout.write( read ); } dout.close(); return out.toByteArray(); } /** * Writes all locations and relationships of the {@link DockStation DockStations} * stations and their children into an array of bytes. * @param stations The stations to store, a call to {@link #read(byte[])} * would return the same map. Only the roots are needed. * @return the information as an array of bytes * @throws IOException if the information can't be written */ public byte[] write( Map stations ) throws IOException{ ByteArrayOutputStream bytes = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream( bytes ); write( stations, out ); out.close(); return bytes.toByteArray(); } /** * Writes all locations and relationships of the {@link DockStation DockStations} * stations and their children into the stream out. * @param stations The stations to store, only the roots are needed. * @param out the stream to write in * @throws IOException if the stream throws an exception */ public void write( Map stations, DataOutputStream out ) throws IOException{ Map map = new HashMap(); for( Map.Entry entry : stations.entrySet() ){ DockLayoutComposition composition = convert( entry.getValue() ); if( composition != null ){ map.put( entry.getKey(), composition ); } } writeCompositions( map, out ); } /** * Writes all information stored in stations to out. * @param stations Representations of the root-stations. * @param out the stream to write in * @throws IOException if the stream throws an exception */ public void writeCompositions( Map stations, DataOutputStream out ) throws IOException{ Version.write( out, Version.VERSION_1_0_4 ); out.writeInt( stations.size() ); for( Map.Entry entry : stations.entrySet() ){ out.writeUTF( entry.getKey() ); writeComposition( entry.getValue(), out ); } } /** * Reads data as stream and returns the roots of the * {@link DockElement DockElements} which were found. * @param data the array to read * @return the root stations which were found * @throws IOException if data can't be read */ public Map read( byte[] data ) throws IOException{ DataInputStream in = new DataInputStream( new ByteArrayInputStream( data )); Map result = read( in ); in.close(); return result; } /** * Reads in and returns the roots of the * {@link DockElement DockElements} which were found. * @param in the stream to read * @return the roots of all elements that were found * @throws IOException if the stream can't be read */ public Map read( DataInputStream in ) throws IOException{ Version version = Version.read( in ); version.checkCurrent(); int count = in.readInt(); Map result = new HashMap(); for( int i = 0; i < count; i++ ){ String key = in.readUTF(); DockLayoutComposition composition = readComposition( in ); DockElement element = composition == null ? null : convert( composition ); DockStation station = element == null ? null : element.asDockStation(); if( station != null ){ result.put( key, station ); } } return result; } /** * Reads in and returns the map of {@link DockLayoutComposition}s that was * stored. * @param in the stream to read from * @return the roots of the layout * @throws IOException if an I/O-error occurs */ public Map readCompositions( DataInputStream in ) throws IOException{ Version version = Version.read( in ); version.checkCurrent(); int count = in.readInt(); Map result = new HashMap(); for( int i = 0; i < count; i++ ){ String key = in.readUTF(); DockLayoutComposition composition = readComposition( in ); if( composition != null ){ result.put( key, composition ); } } return result; } /** * Writes the contents of composition into element without * changing the attributes of element. * @param composition the composition to write * @param element the element to write into * @throws IllegalArgumentException if a factory is missing */ @SuppressWarnings("unchecked") public void writeCompositionXML( DockLayoutComposition composition, XElement element ){ DockLayoutInfo info = composition.getLayout(); if( info.getKind() == DockLayoutInfo.Data.XML ){ element.addElement( info.getDataXML() ); } else if( info.getKind() == DockLayoutInfo.Data.DOCK_LAYOUT ){ DockLayout layout = info.getDataLayout(); DockFactory factory = (DockFactory)getFactory( layout.getFactoryID() ); if( factory == null ) throw new IllegalArgumentException( "Missing factory: " + layout.getFactoryID() ); XElement xfactory = element.addElement( "layout" ); xfactory.addString( "factory", getID( factory ) ); Path placeholder = info.getPlaceholder(); if( placeholder != null ){ xfactory.addString( "placeholder", placeholder.toString() ); } factory.write( layout.getData(), xfactory ); } else{ // there is nothing to write... throw new IllegalArgumentException( "Cannot store layout as XML: it was never translated and its raw format is not XML" ); } List> adjacentLayouts = composition.getAdjacent(); if( adjacentLayouts != null ){ XElement xadjacent = element.addElement( "adjacent" ); for( DockLayout adjacentLayout : adjacentLayouts ){ AdjacentDockFactory adjacentFactory = (AdjacentDockFactory)getAdjacentFactory( adjacentLayout.getFactoryID() ); if( adjacentFactory == null ) throw new IllegalArgumentException( "Missing adjacent factory: " + adjacentLayout.getFactoryID() ); XElement xlayout = xadjacent.addElement( "layout" ); xlayout.addString( "factory", getAdjacentID( adjacentFactory ) ); adjacentFactory.write( adjacentLayout.getData(), xlayout ); } } XElement xchildren = element.addElement( "children" ); xchildren.addBoolean( "ignore", composition.isIgnoreChildren() ); for( DockLayoutComposition child : composition.getChildren() ){ XElement xchild = xchildren.addElement( "child" ); writeCompositionXML( child, xchild ); } } /** * Reads a {@link DockLayoutComposition} from an xml element. * @param element the element to read * @return the composition that was read * @throws XException if something is missing or malformed in element */ @SuppressWarnings("unchecked") public DockLayoutComposition readCompositionXML( XElement element ){ XElement xfactory = element.getElement( "layout" ); DockLayoutInfo layout = readEntry( xfactory ); XElement xadjacent = element.getElement( "adjacent" ); List> adjacentLayouts = null; if( xadjacent != null ){ adjacentLayouts = new ArrayList>(); for( XElement xlayout : xadjacent.getElements( "layout" )){ String factoryId = xlayout.getString( "factory" ); AdjacentDockFactory adjacentFactory = (AdjacentDockFactory)getAdjacentFactory( factoryId ); if( adjacentFactory != null ){ Object data = adjacentFactory.read( xlayout, intermediatePlaceholders ); if( data != null ){ adjacentLayouts.add( new DockLayout( factoryId, data )); } } else if( missingAdjacent != null ){ Object data = missingAdjacent.readXML( getAdjacentFactoryID( factoryId ), xlayout ); if( data != null ){ adjacentLayouts.add( new DockLayout( factoryId, data ) ); } } } } XElement xchildren = element.getElement( "children" ); boolean ignore = true; List children = new ArrayList(); if( xchildren != null ){ ignore = xchildren.getBoolean( "ignore" ); for( XElement xchild : xchildren.getElements( "child" )){ children.add( readCompositionXML( xchild )); } } return new DockLayoutComposition( layout, adjacentLayouts, children, ignore ); } /** * Reads an entry that was stored in xml format. The entry should have * one attribute factory. * @param element the element to read, can be null * @return the information of element, may be null */ @SuppressWarnings("unchecked") private DockLayoutInfo readEntry( XElement element ){ DockLayoutInfo layout = null; if( element != null ){ String factoryId = element.getString( "factory" ); Path placeholder = null; XAttribute xplaceholder = element.getAttribute( "placeholder" ); if( xplaceholder != null ){ placeholder = new Path( xplaceholder.getString() ); } DockFactory factory = (DockFactory)getFactory( factoryId ); if( factory != null ){ Object data = factory.read( element, intermediatePlaceholders ); if( data != null ){ layout = new DockLayoutInfo( new DockLayout( factoryId, data ) ); layout.setPlaceholder( placeholder ); } } else{ layout = null; if( missingFactory != null ){ Object data = missingFactory.readXML( getFactoryID( factoryId ), element ); if( data != null ){ layout = new DockLayoutInfo( new DockLayout( factoryId, data ) ); layout.setPlaceholder( placeholder ); } } if( layout == null ){ layout = new DockLayoutInfo( element ); layout.setPlaceholder( placeholder ); } } } return layout; } /** * Writes all locations and relationships of the {@link DockStation}s * stations and their children as xml. * @param stations The stations to store, only the roots are needed. * @param element the element to write into, attributes of element will * not be changed */ public void writeXML( Map stations, XElement element ) { Map map = new HashMap(); for( Map.Entry entry : stations.entrySet() ){ DockLayoutComposition composition = convert( entry.getValue() ); if( composition != null ){ map.put( entry.getKey(), composition ); } } writeCompositionsXML( map, element ); } /** * Writes the contents of station into element. * @param stations the items to write * @param element the element to write into, the attributes of element * will not be changed */ public void writeCompositionsXML( Map stations, XElement element ) { for( Map.Entry entry : stations.entrySet() ){ XElement xchild = element.addElement( "element" ); xchild.addString( "name", entry.getKey() ); writeCompositionXML( entry.getValue(), xchild ); } } /** * Reads a set of {@link DockStation}s that were stored earlier. * @param root the xml element from which to read * @return the set of station */ public Map readXML( XElement root ){ Map result = new HashMap(); for( XElement xelement : root.getElements( "element" )){ String name = xelement.getString( "name" ); DockLayoutComposition composition = readCompositionXML( xelement ); DockElement element = composition == null ? null : convert( composition ); DockStation station = element == null ? null : element.asDockStation(); if( station != null ) result.put( name, station ); } return result; } /** * Reads a set of {@link DockLayoutComposition}s that were stored earlier. * @param root the xml element from which to read * @return the set of compositions */ public Map readCompositionsXML( XElement root ){ Map result = new HashMap(); for( XElement xelement : root.getElements( "element" )){ String name = xelement.getString( "name" ); DockLayoutComposition composition = readCompositionXML( xelement ); if( composition != null ){ result.put( name, composition ); } } return result; } /** * Using the factories currently known to this {@link DockSituation}, this * method tries to fill gaps in composition. It checks * all the {@link DockLayoutInfo}s, if an info contains a byte array or * an {@link XElement}, then this method tries to use a factory to * read the element. If a {@link #setMissingFactory(MissingDockFactory) missing factory} * is present, then this factory is used as well. * @param composition the composition to read * @return either composition or a new composition if this * method changed something * @throws IOException if some stream was opened but cannot be read * @throws XException if some xml element was found but cannot be read */ public DockLayoutComposition fillMissing( DockLayoutComposition composition ) throws IOException, XException{ DockLayoutInfo info = composition.getLayout(); DockLayoutInfo original = info; if( info.getKind() == DockLayoutInfo.Data.BYTE ){ info = readEntry( info.getDataByte(), info.getPlaceholder() ); if( info != null && info.getKind() == DockLayoutInfo.Data.BYTE ){ info = original; } } else if( info.getKind() == DockLayoutInfo.Data.XML ){ info = readEntry( info.getDataXML() ); if( info != null && info.getKind() == DockLayoutInfo.Data.XML ){ info = original; } } if( info.getKind() == DockLayoutInfo.Data.DOCK_LAYOUT ){ info = fillMissing( info ); } boolean createNew = info != original; List children = composition.getChildren(); List newChildren; if( children != null ){ newChildren = new ArrayList( children.size() ); for( DockLayoutComposition child : children ){ DockLayoutComposition filled = fillMissing( child ); newChildren.add( filled ); if( child != filled ){ createNew = true; } } } else{ newChildren = null; } if( info != null && info != original ){ info.setLocation( original.getLocation() ); } if( createNew ){ return new DockLayoutComposition( info, composition.getAdjacent(), newChildren, composition.isIgnoreChildren() ); } return composition; } /** * Called by {@link #fillMissing(DockLayoutComposition)} only for * {@link DockLayoutInfo}s which contain a {@link DockLayout}. This method * can apply further updates to info if necessary. The default * implementation just returns info.
* This method is intended for subclasses which wrap {@link DockFactory}s * and use a {@link DockLayout} even for incomplete data. * @param info the info to update * @return either info if nothing changed or a new * {@link DockLayoutInfo} if additional information could be retrieved */ protected DockLayoutInfo fillMissing( DockLayoutInfo info ){ return info; } /** * Tries to guess the location of the elements stored in the tree * below composition. Invoking this method is the same as calling * guessLocation( composition, composition.getLayout().getLocation() );. * @param composition the composition whose location will be determined */ public void estimateLocations( DockLayoutComposition composition ){ estimateLocations( composition, composition.getLayout().getLocation() ); } /** * Tries to guess the location of the elements stored in the tree below * composition, assuming that composition itself * is at location location. This method reads out the * {@link DockLayoutInfo} through {@link DockLayoutComposition#getLayout()} * and then calls {@link DockLayoutInfo#setLocation(DockableProperty)}, so * composition gets modified by this method. This method stops * its recursion if the location of a child of composition * was not found.
* This method returns immediately if one of: *
    *
  • composition does not have children
  • *
  • composition does not carry a {@link DockLayout}
  • *
  • There is no {@link DockFactory} registered for the factory-id found * in composition
  • *

* Note: if the number of factories changed, then it might be a good idea * to call {@link #fillMissing(DockLayoutComposition)} before invoking this method. * @param composition the composition whose children should be analyzed * @param location the location of composition, can be null */ public void estimateLocations( DockLayoutComposition composition, DockableProperty location ){ DefaultLocationEstimationMap map = new DefaultLocationEstimationMap( composition ); estimateLocations( map ); if( location != null ){ appendFirstOnEstimate( composition, location ); } } private void appendFirstOnEstimate( DockLayoutComposition composition, DockableProperty location ){ DockLayoutInfo info = composition.getLayout(); DockableProperty property = info.getLocation(); if( property != null ){ info.setLocation( DockUtilities.append( property, location ) ); } for( DockLayoutComposition child : composition.getChildren() ){ appendFirstOnEstimate( child, location ); } } /** * Recursively tries to estimate the locations of all {@link DockLayoutInfo}s that can * be found in map.
* Note: this method does not call {@link DefaultLocationEstimationMap#finish()}. * @param map the root of the tree for which locations need to be estimated */ @SuppressWarnings("unchecked") protected void estimateLocations( DefaultLocationEstimationMap map ){ DockLayoutComposition composition = map.getRoot(); List children = composition.getChildren(); if( children == null || children.size() == 0 ){ return; } DockLayout layout = (DockLayout)composition.getLayout().getDataLayout(); if( layout == null ){ return; } DockFactory factory = (DockFactory)getFactory( layout.getFactoryID() ); if( factory == null ){ return; } for( int i = 0, n = map.getChildCount(); itrue if the element should not be saved */ protected boolean ignoreElement( DockElement element ){ if( ignore == null ) return false; return ignore.ignoreElement( element ); } /** * Tells whether to ignore the children of the station when saving or not. If the children * are ignored, no factories are needed for them. This implementation forwards * the call to the {@link DockSituationIgnore} of this situation. * @param station the station whose children might be ignored * @return true if the station is saved as having no children */ protected boolean ignoreChildren( DockStation station ){ if( ignore == null ) return false; return ignore.ignoreChildren( station ); } /** * Gets the id of the factory which is needed to write (and later * read) element * @param element the element to read * @return the id of the factory * @see #getID(DockFactory) * @see #getFactory(String) */ protected String getID( PerspectiveElement element ){ return element.getFactoryID(); } /** * Gets the id of the factory which is needed to write (and later * read) element * @param element the element to read * @return the id of the factory * @see #getID(DockFactory) * @see #getFactory(String) */ protected String getID( DockElement element ){ return element.getFactoryID(); } /** * Gets the id of factory. The default behavior is just to * return {@link DockFactory#getID()}. Note that this method should be * a bijection to {@link #getFactory(String)}. * @param factory the factory whose id is needed * @return the id of the factory */ protected String getID( DockFactory factory ){ return factory.getID(); } /** * Reads the id of the factory which was used to create info. * This returns the id of the factory as it is used internally by this * {@link DockSituation}. * @param info the info to check * @return the id of a factory or null if info does not contain * data * @throws IllegalArgumentException if the data of info is in the wrong format * @throws XException if the data of info is in the wrong format */ protected String getFactoryID( DockLayoutInfo info ){ if( info.getKind() == DockLayoutInfo.Data.BYTE ){ try{ DataInputStream in = new DataInputStream( new ByteArrayInputStream( info.getDataByte() )); String result = in.readUTF(); in.close(); return result; } catch( IOException ex ){ throw new IllegalArgumentException( "byte entry not in the correct format", ex ); } } if( info.getKind() == DockLayoutInfo.Data.XML ){ return info.getDataXML().getString( "factory" ); } if( info.getKind() == DockLayoutInfo.Data.DOCK_LAYOUT ){ return info.getDataLayout().getFactoryID(); } return null; } /** * Tells what identifier is used for factory in the * {@link DockLayoutComposition}.
* This method just calls {@link #getID(DockFactory)}, but * {@link #getID(DockFactory)} is intended for internal use while this * method is intended to be used by clients which read out a {@link DockLayoutComposition}. * @param factory the factory which might be used * @return the identifier * @see #getID(DockFactory) */ public String convertFactoryId( DockFactory factory ){ return getID( factory ); } /** * Tells what identifier the {@link DockFactory} has, for which the * identifier id is used within a {@link DockLayoutComposition}.
* This method just calls {@link #getFactoryID(String)}, but while * {@link #getFactoryID(String)} is intended for internal use, this method * is intended for clients. * @param id an identifier found in a {@link DockLayoutComposition} * @return the identifer of a {@link DockFactory} */ public String convertFactoryId( String id ){ return getFactoryID( id ); } /** * Transforms an id read from a stream to the id of the factory which * would be used. This method must fulfill one contract: * DockFactory factory = ... * factory.getID().equals( getFactoryID( getID( factory ))); * @param id the id read from a stream * @return the id of the original factory */ protected String getFactoryID( String id ){ return id; } /** * Gets the id of factory. The default behavior is just to * return {@link DockFactory#getID()}. Note that this method should be * a bijection to {@link #getAdjacentFactory(String)}. * @param factory the factory whose id is needed * @return the id of the factory */ protected String getAdjacentID( AdjacentDockFactory factory ){ return factory.getID(); } /** * Transforms an id read from a stream to the id of the adjacent factory which * would be used. This method must fulfill one contract: * AdjacentDockFactory factory = ... * factory.getID().equals( getFactoryID( getAdjacentID( factory ))); * @param id the id read from a stream * @return the id of the original factory */ protected String getAdjacentFactoryID( String id ){ return id; } /** * Gets the factory which has the given id. Note that this * method should be a bijection to {@link #getID(DockFactory)}. The * default behavior compares id with the * {@link #getID(DockFactory)}. * @param id the name of the factory * @return the factory or null if no factory has this id */ @Todo( compatibility=Compatibility.BREAK_MINOR, priority=Priority.MAJOR, target=Todo.Version.VERSION_1_1_3, description="remove the legacy code that filters out identifiers that look like 'secure ...'. Instead 'result' can be returned directly." ) public DockFactory getFactory( String id ){ DockFactory result = factories.get( id ); if( result == null ){ String base = null; if( id.startsWith( "delegate_secure " )){ base = id.substring( "delegate_secure ".length() ); id = "delegate_" + base; } else if( id.startsWith( "secure " )){ base = id.substring( "secure ".length() ); id = base; } if( FlapDockStationFactory.ID.equals( base) || ScreenDockStationFactory.ID.equals( base ) || StackDockStationFactory.ID.equals( base ) || SplitDockStationFactory.ID.equals( base )){ result = factories.get( id ); } } return result; } /** * Gets the adjacent factory which has the given id. Note that this * method should be a bijection to {@link #getID(DockFactory)}. The * default behavior compares id with the * {@link #getID(DockFactory)}. * @param id the name of the factory * @return the factory or null if no factory has this id */ public AdjacentDockFactory getAdjacentFactory( String id ){ return adjacent.get( id ); } /** * Gets all the adjacent factories that are currently registered at this {@link DockSituation}, * the returned {@link Map} is unmodifiable. * @return an unmodifiable map containing all {@link AdjacentDockFactory}s. */ public Map> getAdjacentFactorys(){ return Collections.unmodifiableMap( adjacent ); } /** * Tells what identifier was associated with composition when it was * stored by a {@link DockSituation} of this type. The default implementation always * returns null. * @param composition some element that was created by this or a similar {@link DockSituation} * @return the identifier that was associated with composition */ public String getIdentifier( DockLayoutComposition composition ){ return null; } }