package bibliothek.help.model;
import java.util.*;
import bibliothek.help.view.text.HelpDocument;
/**
* An <code>Entry</code> is some content that will be presented to the user.
* Every <code>Entry</code> has fields for identification, a small
* description (the title) and a set of links to other <code>Entries</code>
* which contain more detailed or additional information to the content
* of this <code>Entry</code>.
* @author Benjamin Sigg
*
*/
public class Entry {
/** an array of length 0 */
private static final String[] EMPTY = new String[0];
/**
* The general type, tells who and how this {@link Entry} can be shown.
* The exact meaning depends on the client.
*/
private String type;
/** an identifier, unique for all <code>Entries</code> with the same {@link #type} */
private String id;
/** a small description */
private String title;
/** the data that will be processed and presented to the user */
private String content;
/** links to other <code>Entries</code> */
private String[] details;
/**
* Creates a new entry
* @param type the type of the entry
* @param id the id of the entry
* @param title the title of this entry
* @param content the text for the entry
* @param details Links to other entries which should be shown when
* this entry is shown. Each link should have the form <code>type:id</code>.
*/
public Entry( String type, String id, String title, String content, String...details ) {
super();
this.type = type;
this.id = id;
this.title = title;
this.content = content;
if( details == null || details.length == 0 )
details = EMPTY;
this.details = details;
}
/**
* Tells who and how this <code>Entry</code> will be presented to the
* user. The exact meaning of this property depends on the client.
* @return the general type
*/
public String getType() {
return type;
}
/**
* Gets an identifier which is unique for all <code>Entries</code>
* with the same {@link #getType() type}.
* @return the unique id
*/
public String getId() {
return id;
}
/**
* Gets a small description of this <code>Entry</code>.
* @return a small text
*/
public String getTitle(){
return title;
}
/**
* Gets the data that will be presented to the user. Some content
* is encoded and must be processed further before presenting.
* @return the content
*/
public String getContent() {
return content;
}
/**
* Gets a set of links to other <code>Entries</code>. The other
* <code>Entries</code> contain more information related to the
* content of this <code>Entry</code>.
* @return links of the form <code>type:id</code>
*/
public String[] getDetails() {
return details;
}
/**
* Decodes the content of this <code>Entry</code> and produces a tree.
* @return the tree
*/
public HierarchyNode toSubHierarchy(){
Token t;
Reader reader = new Reader();
LinkedList<Intermediate> classes = new LinkedList<Intermediate>();
LinkedList<Intermediate> interfaceStack = new LinkedList<Intermediate>();
Intermediate current = null;
while( (t = reader.next()) != null ){
if( t.mode ){
if( t.content[0].equals( "class" )){
current = new Intermediate();
current.type = t.content[1];
current.name = t.content[2];
if( !classes.isEmpty() )
current.children.add( classes.getLast() );
classes.add( current );
}
else if( t.content[0].equals( "interface" )){
Intermediate next = new Intermediate();
next.type = "i";
next.name = t.content[1];
if( interfaceStack.isEmpty() )
current.children.add( next );
else
interfaceStack.getLast().children.add( next );
}
else if( t.content[0].equals( "tree" )){
if( t.content[1].equals( "+" )){
if( interfaceStack.isEmpty() )
interfaceStack.addLast( current.children.get( current.children.size()-1 ) );
else
interfaceStack.addLast( interfaceStack.getLast().children.get( interfaceStack.getLast().children.size()-1 ) );
}
else if( t.content[1].equals( "-" )){
interfaceStack.removeLast();
}
}
}
}
if( classes.isEmpty() )
return null;
return classes.getLast().toNode();
}
/**
* A class used to create the resulting tree of {@link Entry#toSubHierarchy()}.
* @author Benjamin Sigg
*/
private static class Intermediate{
/** children of this node */
public List<Intermediate> children = new ArrayList<Intermediate>();
/** the nodes type (interface, class, ...) */
public String type;
/** the text of the node */
public String name;
/**
* Transforms this <code>Intermediate</code> in an {@link HierarchyNode},
* transforms also the children of this node.
* @return a tree with the content of this <code>Intermediate</code> as root
*/
public HierarchyNode toNode(){
HierarchyNode[] subs = new HierarchyNode[ children.size() ];
for( int i = 0; i < subs.length; i++ )
subs[i] = children.get( i ).toNode();
return new HierarchyNode( name, type, subs );
}
}
/**
* Tries to read the content of this Entry as document.
* @param destination the document to write into, can be <code>null</code>
* @return the document that was written
*/
public HelpDocument toDocument( HelpDocument destination ){
if( destination == null )
destination = new HelpDocument();
Set<String> modes = new HashSet<String>();
Reader reader = new Reader();
Token token;
while( (token = reader.next()) != null ){
if( token.mode ){
if( token.content[0].equals( "link" )){
destination.appendLink( token.content[2], token.content[1], modes );
}
else if( token.content[0].equals( "mode" )){
String input = token.content[1];
if( input.startsWith( "+" ))
modes.add( input.substring( 1 ));
else
modes.remove( input.substring( 1 ));
}
}
else
destination.appendText( token.content[0], modes );
}
return destination;
}
/**
* A token is some text or tag that is read from the encoded
* {@link Entry#content} of the enclosing {@link Entry}.
* @author Benjamin Sigg
*/
private class Token{
/** whether this <code>Token</code> is a tag or just simple text */
public boolean mode;
/** the entries of a tag or if {@link #mode} is <code>false</code> the text between tags */
public String[] content;
}
/**
* A <code>Reader</code> decodes the {@link Entry#content} of an {@link Entry}
* and produces a stream of {@link Entry.Token}s.
* @author Benjamin Sigg
*
*/
private class Reader{
/** the next char to read from the {@link Entry#content} */
private int offset = 0;
/** buffer used to collect single chars */
private StringBuilder builder = new StringBuilder();
/** whether the next {@link Entry.Token} is expected to be a tag or not */
private boolean mode = false;
/**
* Reads the next {@link Token} from the {@link Entry#content}. The
* <code>Token</code> is either a tag or a text.
* @return the token or <code>null</code> if the end of the stream
* is reached
*/
public Token next(){
while( offset < content.length() ){
Token t;
if( mode )
t = nextMode();
else
t = nextText();
mode = !mode;
if( t != null )
return t;
}
return null;
}
/**
* Decodes the text between two tags.
* @return the text or <code>null</code> if there is nothing to read
*/
private Token nextText(){
offset = next( offset );
if( builder.length() == 0 )
return null;
Token t = new Token();
t.mode = false;
t.content = new String[]{ builder.toString() };
return t;
}
/**
* Decodes the next tag.
* @return the tag
*/
private Token nextMode(){
List<String> list = new ArrayList<String>();
while( offset < content.length() &&
( offset == 0 || content.charAt( offset-1 ) != ']' )){
offset = next( offset );
list.add( builder.toString() );
}
Token t = new Token();
t.mode = true;
t.content = list.toArray( new String[ list.size() ] );
return t;
}
/**
* Reads char from the {@link Entry#content} until the end
* is reached, or a single character '[', '|' or ']' is found.<br>
* The result is stored in the {@link #builder}.
* @param offset the first char to read
* @return <code>offset + buffer.getLength()</code>
*/
private int next( int offset ){
builder.setLength( 0 );
int n = content.length();
boolean armed = false;
char last = 0;
while( offset < n ){
char c = content.charAt( offset );
offset++;
if( c == '|' || c == '[' || c == ']'){
if( armed && c != last )
return offset-1;
if( armed )
builder.append( c );
armed = !armed;
}
else if( armed ){
return offset-1;
}
else
builder.append( c );
last = c;
}
return offset;
}
}
}