/*
* 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) 2011 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.common.intern.station;
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.Map;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DockElement;
import bibliothek.gui.dock.DockFactory;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.SingleCDockable;
import bibliothek.gui.dock.common.SingleCDockableFactory;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CommonSingleDockableFactory;
import bibliothek.gui.dock.common.perspective.CElementPerspective;
import bibliothek.gui.dock.common.perspective.CStationPerspective;
import bibliothek.gui.dock.common.perspective.CommonDockStationPerspective;
import bibliothek.gui.dock.frontend.FrontendPerspectiveCache;
import bibliothek.gui.dock.layout.DockLayout;
import bibliothek.gui.dock.layout.LocationEstimationMap;
import bibliothek.gui.dock.perspective.PerspectiveDockable;
import bibliothek.gui.dock.perspective.PerspectiveElement;
import bibliothek.gui.dock.station.support.PlaceholderStrategy;
import bibliothek.util.Version;
import bibliothek.util.xml.XElement;
import bibliothek.util.xml.XException;
/**
* A factory that is responsible for storing and loading the layout of the
* {@link CommonDockStation}s. This factory actually forwards the work to
* a real {@link DockFactory} and only stores meta-data (like "was the
* {@link CommonDockStation} as {@link SingleCDockable}?").
* @author Benjamin Sigg
*/
public class CommonDockStationFactory implements DockFactory<CommonDockStation<?, ?>, CommonDockStationPerspective, CommonDockStationLayout>{
/** The unique identifier of this factory */
public static final String FACTORY_ID = "CommonDockStationFactory";
/** The control in whose realm this factory is used */
private CControl control;
/** The factory used to create new {@link CommonDockStation}s that are also {@link SingleCDockable}s */
private CommonSingleDockableFactory singleDockableFactory;
/** Factory used to access missing {@link PerspectiveElement}s */
private FrontendPerspectiveCache cache;
/**
* Creates a new factory
* @param control the {@link CControl} in whose realm this factory works, not <code>null</code>
* @param cache used to create missing {@link PerspectiveElement}s, can be <code>null</code>
* @param singleDockableFactory the factory used to create new {@link CommonDockStation}s that are also {@link SingleCDockable}s, not <code>null</code>
*/
public CommonDockStationFactory( CControl control, FrontendPerspectiveCache cache, CommonSingleDockableFactory singleDockableFactory ){
if( control == null ){
throw new IllegalArgumentException( "control must not be null" );
}
if( singleDockableFactory == null ){
throw new IllegalArgumentException( "singleDockableFactory must not be null" );
}
this.control = control;
this.cache = cache;
this.singleDockableFactory = singleDockableFactory;
}
/**
* Creates a new {@link CommonDockStation} whose {@link CStation} is also a {@link SingleCDockable} with
* unique identifier <code>id</code>.
* @param id the unique identifier of a {@link SingleCDockable}
* @return the new station or <code>null</code> if <code>id</code> is unknown
*/
protected CommonDockStation<?, ?> createStation( String id ){
SingleCDockableFactory factory = singleDockableFactory.getFactory( id );
if( factory == null ){
return null;
}
SingleCDockable dockable = factory.createBackup( id );
if( dockable == null ){
return null;
}
String factoryId = dockable.intern().getFactoryID();
if( !factoryId.equals( getID() )){
throw new IllegalArgumentException( "Wrong type of dockable for unique id '" + id + "': The backup factory created a dockable which expects a factory of type '" + factoryId +
"', but the call was done from a factory of type '" + getID() + "'" );
}
CStation<?> station = dockable.asStation();
if( station == null ){
System.err.println( "unique identifier '" + id + "' was supposed to be a CStation, but factory created a dockable" );
return null;
}
return (CommonDockStation<?, ?>)station.getStation();
}
/**
* Register <code>station</code> at the {@link CControl} in whose realm this factory works.
* @param station the station to register
* @param root whether to set the root flag or not
*/
protected void registerStation( CStation<?> station, boolean root ){
if( control.getStation( station.getUniqueId() ) != station ){
control.addStation( station, root );
}
CDockable dockable = station.asDockable();
if( dockable != null ){
if( dockable instanceof SingleCDockable ){
SingleCDockable single = (SingleCDockable)dockable;
if( control.getSingleDockable( single.getUniqueId() ) != single ){
control.addDockable( single );
}
}
}
}
public String getID(){
return FACTORY_ID;
}
@SuppressWarnings("unchecked")
public CommonDockStationLayout getLayout( CommonDockStation<?, ?> element, Map<Dockable, Integer> children ){
String factoryId = element.getConverterID();
DockFactory<DockElement, ?, ?> factory = (DockFactory<DockElement, ?, ?>)control.intern().getDockFactory( factoryId );
if( factory == null ){
return null;
}
Object layout = factory.getLayout( element, children );
if( layout == null ){
return null;
}
CDockable dockable = element.getStation().asDockable();
String id = null;
if( dockable instanceof SingleCDockable ){
id = ((SingleCDockable)dockable).getUniqueId();
}
boolean root = control.isRootStation( element.getStation() );
return new CommonDockStationLayout( id, root, factoryId, new DockLayout<Object>( factoryId, layout ) );
}
public CommonDockStation<?, ?> layout( CommonDockStationLayout layout, Map<Integer, Dockable> children, PlaceholderStrategy placeholders ){
CommonDockStation<?, ?> station = createStation( layout.getId() );
if( station == null ){
return null;
}
registerStation( station.getStation(), layout.isRoot() );
setLayout( station, layout, children, placeholders );
return station;
}
public CommonDockStation<?, ?> layout( CommonDockStationLayout layout, PlaceholderStrategy placeholders ){
return layout( layout, null, placeholders );
}
@SuppressWarnings("unchecked")
public void setLayout( CommonDockStation<?, ?> element, CommonDockStationLayout layout, Map<Integer, Dockable> children, PlaceholderStrategy placeholders ){
String factoryId = element.getConverterID();
DockFactory<DockElement, ?, Object> factory = (DockFactory<DockElement, ?, Object>)control.intern().getDockFactory( factoryId );
if( factory == null ){
return;
}
layout.updateLayout( factory, placeholders );
DockLayout<?> data = layout.getLayout();
if( data == null ){
return;
}
if( children == null ){
factory.setLayout( element, data.getData(), placeholders );
}
else{
factory.setLayout( element, data.getData(), children, placeholders );
}
}
public void setLayout( CommonDockStation<?, ?> element, CommonDockStationLayout layout, PlaceholderStrategy placeholders ){
setLayout( element, layout, null, placeholders );
}
@SuppressWarnings("unchecked")
public void estimateLocations( CommonDockStationLayout layout, LocationEstimationMap children ){
String factoryId = null;
if( layout != null ){
factoryId = layout.getFactoryId();
}
if( factoryId == null ){
return;
}
DockFactory<DockElement, ?, Object> factory = (DockFactory<DockElement, ?, Object>)control.intern().getDockFactory( factoryId );
if( factory == null ){
return;
}
layout.updateLayout( factory, null );
DockLayout<?> data = layout.getLayout();
if( data == null ){
return;
}
factory.estimateLocations( data.getData(), children );
}
public CommonDockStationPerspective layoutPerspective( CommonDockStationLayout layout, Map<Integer, PerspectiveDockable> children ){
CommonDockStationPerspective element = null;
if( cache != null ){
element = (CommonDockStationPerspective)cache.get( layout.getId(), layout.isRoot() );
}
if( element == null ){
return null;
}
layoutPerspective( element, layout, children );
return element;
}
@SuppressWarnings("unchecked")
public void layoutPerspective( CommonDockStationPerspective element, CommonDockStationLayout layout, Map<Integer, PerspectiveDockable> children ){
CStationPerspective station = element.getElement().asStation();
station.setRoot( layout.isRoot() );
DockFactory<?, PerspectiveElement, Object> factory = (DockFactory<?, PerspectiveElement, Object>)control.intern().getDockFactory( layout.getFactoryId() );
if( factory == null ){
return;
}
layout.updateLayout( factory, null );
DockLayout<?> data = layout.getLayout();
if( data == null ){
return;
}
factory.layoutPerspective( element, data.getData(), children );
}
@SuppressWarnings("unchecked")
public CommonDockStationLayout getPerspectiveLayout( CommonDockStationPerspective element, Map<PerspectiveDockable, Integer> children ){
String factoryId = element.getConverterID();
DockFactory<?, PerspectiveElement, Object> dockFactory = (DockFactory<?, PerspectiveElement, Object>)control.intern().getDockFactory( factoryId );
DockFactory<?, PerspectiveElement, Object> factory = dockFactory;
if( factory == null ){
return null;
}
Object data = factory.getPerspectiveLayout( element, children );
if( data == null ){
return null;
}
CElementPerspective celement = element.getElement();
CStationPerspective station = celement.asStation();
String id = station.getUniqueId();
return new CommonDockStationLayout( id, station.isRoot(), factoryId, new DockLayout<Object>( factoryId, data ) );
}
@SuppressWarnings("unchecked")
public void write( CommonDockStationLayout layout, XElement element ){
String factoryId = layout.getFactoryId();
DockFactory<DockElement, ?, Object> factory = (DockFactory<DockElement, ?, Object>)control.intern().getDockFactory( factoryId );
XElement content = layout.getLayoutXML();
if( content == null ){
layout.updateLayout( factory, null );
DockLayout<?> data = layout.getLayout();
if( data == null ){
throw new XException( "data are null, but data were just updated" );
}
content = new XElement("content");
factory.write( data.getData(), content );
}
String id = layout.getId();
if( id != null ){
element.addElement( "id" ).setString( id );
}
element.addElement( "root" ).setBoolean( layout.isRoot() );
content.addString( "delegate", factoryId );
element.addElement( content );
}
@SuppressWarnings("unchecked")
public CommonDockStationLayout read( XElement element, PlaceholderStrategy placeholders ){
String id = null;
XElement xid = element.getElement( "id" );
if( xid != null ){
id = xid.getString();
}
boolean root = element.getElement( "root" ).getBoolean();
XElement xcontent = element.getElement( "content" );
if( xcontent == null ){
throw new XException( "missing content element" );
}
String factoryId = xcontent.getString( "delegate" );
DockFactory<DockElement, ?, Object> factory = (DockFactory<DockElement, ?, Object>)control.intern().getDockFactory( factoryId );
if( factory == null ){
return new CommonDockStationLayout( id, root, factoryId, xcontent );
}
else{
Object data = factory.read( xcontent, placeholders );
if( data == null ){
return null;
}
return new CommonDockStationLayout( id, root, factoryId, new DockLayout<Object>( factoryId, data ) );
}
}
@SuppressWarnings("unchecked")
public void write( CommonDockStationLayout layout, DataOutputStream out ) throws IOException{
String factoryId = layout.getFactoryId();
DockFactory<DockElement, ?, Object> factory = (DockFactory<DockElement, ?, Object>)control.intern().getDockFactory( factoryId );
byte[] content = layout.getLayoutBytes();
if( content == null ){
layout.updateLayout( factory, null );
DockLayout<?> data = layout.getLayout();
if( data == null ){
throw new IOException( "data are null, but data were just updated" );
}
ByteArrayOutputStream bout = new ByteArrayOutputStream();
factory.write( data.getData(), new DataOutputStream( bout ) );
content = bout.toByteArray();
}
if( content == null ){
throw new IOException( "unable to write layout, it could not be converted into byte-array format" );
}
Version.write( out, Version.VERSION_1_1_1 );
String id = layout.getId();
if( id == null ){
out.writeBoolean( false );
}
else{
out.writeBoolean( true );
out.writeUTF( id );
}
out.writeBoolean( layout.isRoot() );
out.writeUTF( factoryId );
out.writeInt( content.length );
out.write( content );
}
@SuppressWarnings("unchecked")
public CommonDockStationLayout read( DataInputStream in, PlaceholderStrategy placeholders ) throws IOException{
Version.read( in ).checkCurrent();
String id;
if( in.readBoolean() ){
id = in.readUTF();
}
else{
id = null;
}
boolean root = in.readBoolean();
String factoryId = in.readUTF();
int length = in.readInt();
byte[] content = new byte[length];
int offset = 0;
while( offset < length ){
int delta = in.read( content, offset, length - offset );
if( delta == -1 ){
throw new EOFException();
}
offset += delta;
}
DockFactory<DockElement, ?, Object> factory = (DockFactory<DockElement, ?, Object>)control.intern().getDockFactory( factoryId );
if( factory == null ){
return new CommonDockStationLayout( id, root, factoryId, content );
}
else{
Object data = factory.read( new DataInputStream( new ByteArrayInputStream( content ) ), placeholders );
if( data == null ){
return null;
}
return new CommonDockStationLayout( id, root, factoryId, new DockLayout<Object>( factoryId, data ) );
}
}
}