/***************************************************************************
* Copyright (C) 2011 by Balint Maschio <bmaschio@italianasoftware.com> *
* Copyright (C) 2011 by Claudio Guidi <cguidi@italianasoftware.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License as *
* published by the Free Software Foundation; either version 2 of the *
* License, or (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
* For details about the authors of this software, see the AUTHORS file. *
***************************************************************************/
package jolie.doc.impl.html;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.Vector;
import jolie.lang.Constants;
import jolie.lang.NativeType;
import jolie.lang.parse.ast.InputPortInfo;
import jolie.lang.parse.ast.InterfaceDefinition;
import jolie.lang.parse.ast.OneWayOperationDeclaration;
import jolie.lang.parse.ast.OperationDeclaration;
import jolie.lang.parse.ast.OutputPortInfo;
import jolie.lang.parse.ast.PortInfo;
import jolie.lang.parse.ast.RequestResponseOperationDeclaration;
import jolie.lang.parse.ast.types.TypeDefinition;
import jolie.lang.parse.ast.types.TypeDefinitionLink;
/**
*
* @author balint maschio, claudio guidi
*/
public class JolieDocWriter
{
private PortInfo port;
private Vector<String> typeDefintionNameVector;
private Vector<TypeDefinition> typeDefinitionVector;
private Vector<TypeDefinitionLink> typeDefinitionLinkVector;
private Vector<String> typeDefintionLinkNameVector;
private Vector<InterfaceDefinition> interfaceDefintionVector;
private Writer writer;
private final String originalFilename;
public JolieDocWriter( Writer writer, String originalFilename )
{
this.writer = writer;
typeDefinitionLinkVector = new Vector<TypeDefinitionLink>();
typeDefintionLinkNameVector = new Vector<String>();
typeDefinitionVector = new Vector<TypeDefinition>();
typeDefintionNameVector = new Vector<String>();
interfaceDefintionVector = new Vector<InterfaceDefinition>();
this.originalFilename = originalFilename;
}
public void addPort( PortInfo port )
{
this.port = port;
}
public void addInterface( InterfaceDefinition interfaceDefinition )
{
interfaceDefintionVector.add( interfaceDefinition );
}
public void addType( TypeDefinition typeDefinition )
{
if ( (!NativeType.isNativeTypeKeyword( typeDefinition.id() )) && (!typeDefinition.id().equals( "undefined" )) ) {
if ( !(typeDefintionLinkNameVector.contains( typeDefinition.id() )) && !(typeDefintionNameVector.contains( typeDefinition.id() )) ) {
typeDefinitionVector.add( typeDefinition );
typeDefintionNameVector.add( typeDefinition.id() );
}
}
}
public void addLinkedType( TypeDefinitionLink typeDefinitionLink )
{
if ( !(typeDefintionNameVector.contains( typeDefinitionLink.linkedTypeName() )) ) {
typeDefinitionLinkVector.add( typeDefinitionLink );
typeDefintionNameVector.add( typeDefinitionLink.linkedTypeName() );
}
}
private void writeHead()
throws IOException
{
writer.write( "<head><style>"
+ "body { font-size:14px; font-family:Courier; }"
+ "a { color:#000099;}"
+ "table {"
+ "font-size:14px; font-family:Sans-serif;"
+ "border-collapse:collapse;"
+ "text-align:left;"
+ "}"
+ "table, th, td {"
+ "border:1px solid #AAAAAA;"
+ "padding:7px;"
+ "}"
+ "li {font-family:Sans-serif;}"
+ " h1 { font-size:26px; font-family:Sans-serif;}"
+ " h2 { font-size:20px; font-family:Sans-serif;}"
+ " h3 { font-size:16px;font-family:Sans-serif; color:#003300; }"
+ " th { font-size:16px;font-family:Sans-serif; color:black; }"
+ ".native { font-weight:bold; color:#990000; }"
+ ".opdoc { font-family: Sans-serif; }"
+ ".operation-title { background-color: #EEEEEE; width:600px; height:20px; }"
+ "</style></head>" );
}
public void write()
throws IOException
{
// document init
writer.write( "<html>" );
writeHead();
writer.write( "<body>" );
// port
String location;
String protocol;
if ( port instanceof OutputPortInfo ) {
OutputPortInfo htmlPort = (OutputPortInfo) port;
location = htmlPort.location() == null ? "" : htmlPort.location().toString();
protocol = htmlPort.protocolId() == null ? "" : htmlPort.protocolId();
} else {
InputPortInfo htmlPort = (InputPortInfo) port;
location = htmlPort.location() == null ? "" : htmlPort.location().toString();
protocol = htmlPort.protocolId();
}
writer.write( "<h1>" + "JolieDoc for Port " + port.id() + "</h1>" );
writer.write( "<h2>From file <code>" + originalFilename + "</code></h2>" );
if ( !(port.getDocumentation() == null) ) {
writer.write( port.getDocumentation().trim().replace( "\n", "<br/>" ) );
writer.write( "<br/><br/>" );
}
writer.write( "<table>" );
writer.write( "<tr>" );
writer.write( "<th>Port Name</th>" );
writer.write( "<th>Location</th>" );
writer.write( "<th>Protocol</th>" );
//JolieDocWriter.write( "<th>Code</th>" );
writer.write( "</tr>" );
writer.write( "<tr>" );
writer.write( "<td>" + port.id() + "</td>" );
writer.write( "<td>" + location + "</td>" );
writer.write( "<td>" + protocol + "</td>" );
//JolieDocWriter.write( "<td>" + "<a href=\"#Code\"> CodePort </a><br />" + "</td>" + "<BR>" );
writer.write( "</tr>" );
writer.write( "</table>" );
// generating interface list
//writer.write( "<br>" );
writer.write( "<h2>" + "List of the available interfaces</h2>" );
writer.write( "<ul>" );
for( InterfaceDefinition interfaceDefinition : interfaceDefintionVector ) {
writer.write( "<li><a href=\"#" + interfaceDefinition.name() + "\">" + interfaceDefinition.name() + " </a>" );
}
writer.write( "</ul>" );
writer.write( "<hr>" );
// interface tables
for( InterfaceDefinition interfaceDefinition : interfaceDefintionVector ) {
writer.write( "<h2>" + "Interface " + interfaceDefinition.name() + "</h2>" );
writer.write( "<a name=\"" + interfaceDefinition.name() + "\"></a>" );
if ( !(interfaceDefinition.getDocumentation() == null) ) {
writer.write( interfaceDefinition.getDocumentation().trim().replace( "\n", "<br>" ) );
//writer.write( "<BR><BR>" );
}
OperationDeclaration operation;
writer.write( "<table border=\"1\">" );
writer.write( "<tr>" );
writer.write( "<th>Heading</th>" );
writer.write( "<th>Input type</th>" );
writer.write( "<th>Output type</th>" );
writer.write( "<th>Faults</th>" );
writer.write( "</tr>" );
// scanning operations into the interface
ArrayList<String> keylist = new ArrayList<String>();
for( String key : interfaceDefinition.operationsMap().keySet() ) {
keylist.add( key );
}
Collections.sort( keylist );
Iterator keyiterator = keylist.iterator();
while( keyiterator.hasNext() ) {
String key = (String) keyiterator.next();
operation = interfaceDefinition.operationsMap().get( key );
writer.write( "<tr>" );
writer.write( "<td><a href=\"#" + operation.id() + "\">" + operation.id() + "</a></td>" );
if ( operation instanceof RequestResponseOperationDeclaration ) {
if ( ((RequestResponseOperationDeclaration) operation).requestType().hasSubTypes() ) {
writer.write( "<td>" + "<a href=\"#" + ((RequestResponseOperationDeclaration) operation).requestType().id() + "\">" + ((RequestResponseOperationDeclaration) operation).requestType().id() + "</a><br />" + "</td>" );
} else {
writer.write( "<td>" + ((RequestResponseOperationDeclaration) operation).requestType().id() + "<br />" + "</td>" );
}
if ( ((RequestResponseOperationDeclaration) operation).responseType().hasSubTypes() ) {
writer.write( "<td>" + "<a href=\"#" + ((RequestResponseOperationDeclaration) operation).responseType().id() + "\">" + ((RequestResponseOperationDeclaration) operation).responseType().id() + "</a><br />" + "</td>" );
} else {
writer.write( "<td>" + ((RequestResponseOperationDeclaration) operation).responseType().id() + "<br />" + "</td>" );
}
writer.write( "<td>" );
for( Entry<String, TypeDefinition> fault : ((RequestResponseOperationDeclaration) operation).faults().entrySet() ) {
if ( !fault.getValue().id().equals( "undefined" ) ) {
writer.write( fault.getKey() + "( <a href=\"#" + fault.getValue().id() + "\">" + fault.getValue().id() + "</a> ) <br>" );
} else {
writer.write( fault.getKey() + ", <br>" );
}
}
writer.write( "</td>" );
writer.write( "</tr>" );
}
if ( operation instanceof OneWayOperationDeclaration ) {
writer.write( "<td>" + "<a href=\"#" + ((OneWayOperationDeclaration) operation).requestType().id()
+ "\">" + ((OneWayOperationDeclaration) operation).requestType().id() + "</a><br /></td><td> </td><td> </td>" );
writer.write( "</tr>" );
writer.write( "</tr>" );
}
}
writer.write( "</table>" );
}
writer.write( "<hr>" );
// Operation details
writer.write( "<h2>Operation list</h2>" );
for( InterfaceDefinition interfaceDefinition : interfaceDefintionVector ) {
for( Entry<String, OperationDeclaration> entry : interfaceDefinition.operationsMap().entrySet() ) {
OperationDeclaration operation = entry.getValue();
writer.write( "<div class=\"operation-title\"><a name=\"" + operation.id() + "\"></a><h3>" + operation.id() + "</h3></div>" );
if ( operation instanceof RequestResponseOperationDeclaration ) {
RequestResponseOperationDeclaration rrOperation = (RequestResponseOperationDeclaration) operation;
writer.write( "<pre>" + operation.id() + "( <a href=\"#" + rrOperation.requestType().id() + "\">" + rrOperation.requestType().id()
+ "</a> )( <a href=\"#" + rrOperation.responseType().id() + "\">" + rrOperation.responseType().id() + "</a> )" );
boolean faultExist = false;
for( Entry<String, TypeDefinition> fault : ((RequestResponseOperationDeclaration) operation).faults().entrySet() ) {
if ( !faultExist ) {
writer.write( " throws" );
faultExist = true;
}
writer.write( "\n" + indent(4) );
if ( !fault.getValue().id().equals( "undefined" ) ) {
writer.write( fault.getKey() + "( <a href=\"#" + fault.getValue().id() + "\">" + fault.getValue().id() + "</a> )" );
} else {
writer.write( fault.getKey() );
}
}
writer.write( "</pre>" );
} else {
OneWayOperationDeclaration owOperation = (OneWayOperationDeclaration) operation;
writer.write( "<p><pre>" + operation.id() + "( <a href=\"#" + owOperation.requestType().id() + "\">" + owOperation.requestType().id() + "</a> )</pre></p>" );
}
if ( operation.getDocumentation() != null ) {
writer.write( "<span class=\"opdoc\"><p>" + operation.getDocumentation().trim().replace( "\n", "<br>" ) + "</p></span>" );
}
}
}
writer.write( "<hr>" );
writer.write( "<h2>Message type list</h2>" );
// scanning type list
for( TypeDefinition typesDefinition : typeDefinitionVector ) {
writer.write( "<a name=\"" + typesDefinition.id() + "\"></a><h3>" + typesDefinition.id() + "</h3>" );
writer.write( "<div class='code' lang='jolie'>" + writeType( typesDefinition, false, 0 ) + "</div>");
}
writer.write( "<hr>" );
writer.write( "<h2>Type list</h2>" );
for( TypeDefinitionLink typesDefinitionLink : typeDefinitionLinkVector ) {
writer.write( "<h3 id=\"" + typesDefinitionLink.linkedTypeName() + "\">" + typesDefinitionLink.linkedTypeName() + "</h3>" );
writer.write( "<a name=\"" + typesDefinitionLink.linkedTypeName() + "\"></a>" );
writer.write( "<div class='code' lang='jolie'>" + writeType( typesDefinitionLink.linkedType(), false, 0 ) + "</div>" );
}
// document ending
writer.write( "</body>" );
writer.write( "</html>" );
// writing and closing
writer.flush();
writer.close();
}
private String writeType( TypeDefinition type, boolean subType, int indetationLevel )
throws IOException
{
StringBuilder builder = new StringBuilder();
if ( subType ) {
for( int indexIndetation = 0; indexIndetation < indetationLevel; indexIndetation++ ) {
builder.append( " " );
}
builder.append( "." + type.id() + getCardinalityString( type ) );
} else {
builder.append( "type " + type.id() );
}
builder.append( ':' );
if ( type instanceof TypeDefinitionLink ) {
TypeDefinitionLink link = (TypeDefinitionLink) type;
builder.append( /* "<a href=\"#" + */ link.linkedTypeName() /* + "\">" + link.linkedTypeName() + "</a>" */ );
} else if ( type.untypedSubTypes() ) {
builder.append( "undefined" );
} else {
builder.append( /*"<span class=\"native\">" + */ nativeTypeToString( type.nativeType() ) /* + "</span>" */ );
if ( type.hasSubTypes() ) {
builder.append( " { \n" );
for( Entry<String, TypeDefinition> entry : type.subTypes() ) {
builder.append( writeType( entry.getValue(), true, indetationLevel + 4 ) + "\n" );
}
for( int indexIndetation = 0; indexIndetation < indetationLevel; indexIndetation++ ) {
builder.append( " " );
}
;
builder.append( "}" );
}
}
return builder.toString();
}
private static String nativeTypeToString( NativeType nativeType )
{
return (nativeType == null) ? "" : nativeType.id();
}
private String getCardinalityString( TypeDefinition type )
{
if ( type.cardinality().equals( Constants.RANGE_ONE_TO_ONE ) ) {
return "";
} else if ( type.cardinality().min() == 0 && type.cardinality().max() == 1 ) {
return "?";
} else if ( type.cardinality().min() == 0 && type.cardinality().max() == Integer.MAX_VALUE ) {
return "*";
} else {
return new StringBuilder().append( '[' ).append( type.cardinality().min() ).append( ',' ).append( type.cardinality().max() ).append( ']' ).toString();
}
}
private String indent( int n )
{
String indentation = "";
for( int x = 0; x < n; x++ ) {
indentation = indentation + "\t";
}
return indentation;
}
}