public class Dataset
{
    private final String description;
    private final GraphDatabaseService db;
    private final Log log;
    private final long runStartTime;

    Dataset( String description, GraphDatabaseService db, Log log )
    {
        this.description = description;
        this.db = db;
        this.log = log;
        runStartTime = System.nanoTime();
        log.write( String.format( "Begin [%s]\n", description ) );
    }

    <T> T execute( BatchCommand<T> command )
    {
        long startTime = System.nanoTime();

        log.write( String.format( "Begin [%s Iterations: %s, BatchSize: %s]",
                command.description(), command.numberOfIterations(), command.batchSize() ) );
        command.onBegin( log );
        for ( int iteration = 0; iteration < command.numberOfIterations(); iteration += command.batchSize() )
        {
            doExecute( iteration, command, startTime );
        }
        command.onEnd( log );
        log.write( String.format( "End   [%s] %s\n", command.description(), elapsedTime( startTime ) ) );

        return command.results();
    }

    public void end()
    {
        log.write( String.format( "Store [%s]\n\nEnd   [%s] %s\n",
                ((GraphDatabaseAPI) db).getStoreDir(), description, elapsedTime( runStartTime ) ) );
    }

    GraphDatabaseService db()
    {
        return db;
    }

    private void doExecute( int startIteration, BatchCommand command, long startTime )
    {
        log.write( String.format( "      [Beginning " + startIteration + " of " + command.numberOfIterations() +
                ", '%s'] %s", command.shortDescription(), elapsedTime( startTime ) ) );

        try (Transaction tx = db.beginTx()) {
            for (int iteration = startIteration; iterationIsInRange(startIteration, command,
                    iteration); iteration++) {
                command.execute(iteration);
                tx.success();
            }
        }

    }

    private static boolean iterationIsInRange( int startIteration, BatchCommand command, int iteration )
    {
        return iteration < (startIteration + command.batchSize()) && iteration < command.numberOfIterations();
    }

    private static String elapsedTime( long startTime )
    {
        String ms = String.valueOf( (System.nanoTime() - startTime) / 1000000 );
        return String.format( "(elapsed: %s ms)", ms );
    }
} public class DatasetManager
{
    private final GraphDatabaseService db;
    private final Log log;

    public DatasetManager( GraphDatabaseService db, Log log )
    {
        this.db = db;
        this.log = log;
    }

    public NodeSpecification nodeSpecification( String label, Property... properties )
    {
        return new NodeSpecification( label, asList( properties ), db );
    }

    public RelationshipSpecification relationshipSpecification( String label, Property... properties )
    {
        return relationshipSpecification( withName( label ), properties );
    }

    public RelationshipSpecification relationshipSpecification( RelationshipType relationshipType,
                                                                Property... properties )
    {
        return new RelationshipSpecification( relationshipType, asList( properties ), db );
    }

    public Dataset newDataset( String description )
    {
        return new Dataset( description, db, log );
    }
} public class NodeCollection implements Iterable<Node>
{
    public static final NodeCollection NULL = new NodeCollection( null, (String)null, new NullSet<Long>() );

    private final GraphDatabaseService db;
    private final Label label;
    private final Set<Long> nodeIds;
    private final List<Long> nodeIdList;

    NodeCollection( GraphDatabaseService db, String label, Set<Long> nodeIds )
    {
        this( db, DynamicLabel.label( label ), nodeIds );
    }
    NodeCollection( GraphDatabaseService db, Label label, Set<Long> nodeIds )
    {
        this.db = db;
        this.label = label;
        this.nodeIds = nodeIds;
        nodeIdList = new ArrayList<>( nodeIds );
    }

    void add( Node node )
    {
        long id = node.getId();
        if ( nodeIds.add( id ) )
        {
            nodeIdList.add( id );
        }
    }

    Node getNodeByPosition( int position )
    {
        try (Transaction tx = db.beginTx()) {
            Node node = db.getNodeById(nodeIdList.get(position));
            tx.success();
            return node;
        }
    }

    public String labelName()
    {
        return label.name();
    }

    public Label label()
    {
        return label;
    }

    public int size()
    {
        return nodeIds.size();
    }

    @Override
    public Iterator<Node> iterator()
    {
        final Iterator<Long> idIterator = nodeIds.iterator();

        return new Iterator<Node>()
        {
            @Override
            public boolean hasNext()
            {
                return idIterator.hasNext();
            }

            @Override
            public Node next()
            {
                Long id = idIterator.next();
                return id != null ? db.getNodeById( id ) : null;
            }

            @Override
            public void remove()
            {
                idIterator.remove();
            }
        };
    }

    NodeCollection subset( List<Integer> positions )
    {
        Set<Long> newNodeIds = new HashSet<Long>( positions.size() );
        for ( Integer position : positions )
        {
            newNodeIds.add( nodeIdList.get( position ) );
        }

        return new NodeCollection( db, label, newNodeIds );
    }

    public NodeCollection approxPercentage( int percentage )
    {
        if ( percentage < 1 || percentage > 100 )
        {
            throw new IllegalArgumentException( "Percent must be between 1 and 100" );
        }

        Random random = new Random();

        int arraySize = nodeIds.size() * ((percentage + 10) / 100);
        Set<Long> newNodeIds = new HashSet<Long>( arraySize );
        for ( Long nodeId : nodeIds )
        {
            int score = random.nextInt( 100 ) + 1;
            if ( score <= percentage )
            {
                newNodeIds.add( nodeId );
            }
        }
        return new NodeCollection( db, label, newNodeIds );
    }

    public NodeCollection combine( NodeCollection other )
    {
        Set<Long> newNodeIds = new HashSet<Long>( nodeIds.size() + other.nodeIds.size() );
        newNodeIds.addAll( nodeIds );
        newNodeIds.addAll( other.nodeIds );
        return new NodeCollection( db, label, newNodeIds );
    }

    public UpdateDataset<NodeCollection> createRelationshipsTo( TargetNodesStrategy targetNodesStrategy )
    {
        return new RelateNodesBatchCommandBuilder( this, targetNodesStrategy );
    }

    public UpdateDataset<List<NodeCollection>> createRelationshipsTo(
            ChoiceOfTargetNodesStrategy choiceOfTargetNodesStrategy )
    {
        return new RelateToChoiceOfNodesBatchCommandBuilder( this, choiceOfTargetNodesStrategy );
    }

} public class NodeSpecification
{
    private final Label label;
    private final List<Property> properties;
    private final GraphDatabaseService db;

    NodeSpecification( String label, List<Property> properties, GraphDatabaseService db )
    {
        this( DynamicLabel.label(label), properties, db );
    }

    NodeSpecification( Label label, List<Property> properties, GraphDatabaseService db )
    {
        this.label = label;
        this.properties = properties;
        this.db = db;
    }

    public UpdateDataset<NodeCollection> create( int quantity )
    {
        return new CreateNodesBatchCommandBuilder( this, quantity );
    }

    public NodeCollection getAll()
    {
        Iterable<Node> allNodes = GlobalGraphOperations.at( db ).getAllNodesWithLabel( label );
        Set<Long> nodeIds = new HashSet<>();
        for ( Node node : allNodes )
        {
            nodeIds.add( node.getId() );
        }
        return newNodeCollection( nodeIds );
    }


    Node build( int iteration )
    {
        Node node = db.createNode( label );
        for ( Property property : properties )
        {
            property.setProperty( node, label.name(), iteration );
        }
        return node;
    }

    NodeCollection emptyNodeCollection( int capacity )
    {
        return new NodeCollection( db, label, new HashSet<Long>( capacity ) );
    }

    NodeCollection newNodeCollection( Set<Long> nodeIds )
    {
        return new NodeCollection( db, label, nodeIds );
    }

    String labelName()
    {
        return label.name();
    }

    Label label()
    {
        return label;
    }
} public class RelationshipSpecification
{
    private final RelationshipType relationshipType;
    private final List<Property> properties;
    private final GraphDatabaseService db;

    RelationshipSpecification( RelationshipType relationshipType, List<Property> properties, GraphDatabaseService db )
    {
        this.relationshipType = relationshipType;
        this.properties = properties;
        this.db = db;
    }

    Relationship createRelationship( Node startNode, Node endNode, int iteration )
    {
        Relationship rel = startNode.createRelationshipTo( endNode, relationshipType );

        for ( Property property : properties )
        {
            property.setProperty( rel, relationshipType.name(), iteration );
        }

        return rel;
    }

    String label()
    {
        return relationshipType.name();
    }

    Expander expander( Direction direction )
    {
        return expanderForTypes( relationshipType, direction );
    }
} public final class SysOutLog implements Log
{
    public static final Log INSTANCE = new SysOutLog();

    private SysOutLog()
    {
    }

    @Override
    public void write( String value )
    {
        System.out.println( value );
    }
} public class AsciiDocFormatter implements GraphStatisticsFormatter
{
    private final Log log;

    public AsciiDocFormatter( Log log )
    {
        this.log = log;
    }

    public void describe( GraphStatistics graphStatistics )
    {
        int maxLengthNodeLabel = 14;
        int maxLengthNumber = 7;

        for ( NodeStatistic ns : graphStatistics.nodeStatistics() )
        {
            maxLengthNodeLabel = ns.label().length() > maxLengthNodeLabel ?
                    ns.label().length() :
                    maxLengthNodeLabel;

            for ( RelationshipStatistic rs : ns.relationshipStatistics() )
            {
                maxLengthNodeLabel = rs.label().length() > maxLengthNodeLabel ?
                        rs.label().length() :
                        maxLengthNodeLabel;

                maxLengthNumber = updateMaxLength( maxLengthNumber, lenInt( rs.incoming().average() ) );
                maxLengthNumber = updateMaxLength( maxLengthNumber, lenInt( rs.outgoing().average() ) );
                maxLengthNumber = updateMaxLength( maxLengthNumber, lenInt( rs.outgoing().high() ) );
                maxLengthNumber = updateMaxLength( maxLengthNumber, lenInt( rs.outgoing().high() ) );
            }
        }

        int lineLength = maxLengthNodeLabel + (3 * maxLengthNumber) + 13;

        log.write( String.format( "Graph statistics for '%s':\n", graphStatistics.description() ) );
        for ( NodeStatistic nodeStatistic : graphStatistics.nodeStatistics() )
        {
            log.write( horizontalLine( lineLength ) );
            log.write( String.format( "| %s | %s |",
                    padRight( nodeStatistic.label(), maxLengthNodeLabel ),
                    padRight( nodeStatistic.count(), 6 + (3 * maxLengthNumber) ) ) );
            log.write( horizontalLine( lineLength ) );
            log.write( String.format( "| %s | %s | %s | %s |",
                    repeat( " ", maxLengthNodeLabel ),
                    padLeft( "Average", maxLengthNumber ),
                    padLeft( "High", maxLengthNumber ),
                    padLeft( "Low", maxLengthNumber ) ) );
            log.write( horizontalLine( lineLength ) );
            for ( RelationshipStatistic relationshipStatistic : nodeStatistic.relationshipStatistics() )
            {
                log.write( String.format( "| %s|", padRight( relationshipStatistic.label(), lineLength - 3 ) ) );
                log.write( horizontalLine( lineLength ) );
                log.write( String.format( "| %s | %s | %s | %s |",
                        padLeft( "OUTGOING", maxLengthNodeLabel ),
                        padLeft( relationshipStatistic.outgoing().average(), maxLengthNumber ),
                        padLeft( relationshipStatistic.outgoing().high(), maxLengthNumber ),
                        padLeft( relationshipStatistic.outgoing().low(), maxLengthNumber ) ) );

                log.write( String.format( "| %s | %s | %s | %s |",
                        padLeft( "incoming", maxLengthNodeLabel ),
                        padLeft( relationshipStatistic.incoming().average(), maxLengthNumber ),
                        padLeft( relationshipStatistic.incoming().high(), maxLengthNumber ),
                        padLeft( relationshipStatistic.incoming().low(), maxLengthNumber ) ) );

                log.write( horizontalLine( lineLength ) );
            }
            log.write( "" );
        }
        log.write( horizontalLine( lineLength ) );
        log.write( String.format( "| %s |", padRight( "Totals", lineLength - 4 ) ) );
        log.write( horizontalLine( lineLength ) );
        log.write( String.format( "| %s | %s |", padRight( "Nodes", maxLengthNodeLabel ),
                padRight( graphStatistics.totalNodes(), 6 + (3 * maxLengthNumber) ) ) );
        log.write( String.format( "| %s | %s |", padRight( "Relationships", maxLengthNodeLabel ),
                padRight( graphStatistics.totalRelationships(), 6 + (3 * maxLengthNumber) ) ) );
        log.write( horizontalLine( lineLength ) );

        Map<String, Integer> relationshipTotals = graphStatistics.totalsPerRelationship();
        for ( String label : relationshipTotals.keySet() )
        {
            log.write( String.format( "| %s | %s |", padRight( label, maxLengthNodeLabel ),
                    padRight( relationshipTotals.get( label ), 6 + (3 * maxLengthNumber) ) ) );
        }
        log.write( horizontalLine( lineLength ) );

    }

    private String horizontalLine( int lineLength )
    {
        return "+" + repeat( "-", lineLength - 2 ) + "+";
    }

    private static int updateMaxLength( int maxLength, int value )
    {
        return value > maxLength ? value : maxLength;
    }

    private static int lenInt( int i )
    {
        String value = String.valueOf( i );
        return value.length();
    }

    private static String padRight( Object s, int n )
    {
        return String.format( "%1$-" + n + "s", String.valueOf( s ) );
    }

    private static String padLeft( Object s, int n )
    {
        return String.format( "%1$" + n + "s", String.valueOf( s ) );
    }

    private static String repeat( String s, int i )
    {
        return String.format( String.format( "%%0%dd", i ), 0 ).replace( "0", s );
    }

} public class GraphStatistics
{

    public static GraphStatistics create( GraphDatabaseService db, String description,
                                          NodeLabelResolver nodeLabelResolver )
    {
        try ( Transaction tx = db.beginTx() )
        {
            GraphStatistics graphStatistics = new GraphStatistics( description, nodeLabelResolver );
            for ( Node node : GlobalGraphOperations.at( db ).getAllNodes() )
            {
                graphStatistics.add( node );
            }
            tx.success();
            return graphStatistics;
        }
    }

    public static GraphStatistics create( GraphDatabaseService db, String description )
    {
        return create( db, description, new NeodeNodeLabelResolver() );
    }

    private final String description;
    private final NodeLabelResolver nodeLabelResolver;
    private final Map<String, NodeStatistic> nodeStatistics;

    private GraphStatistics( String description, NodeLabelResolver nodeLabelResolver )
    {
        this.description = description;
        this.nodeLabelResolver = nodeLabelResolver;
        nodeStatistics = new HashMap<>();
    }

    private void add( Node node )
    {
        String label = nodeLabelResolver.labelFor( node );
        if ( label == null )
        {
            label = "_UNKNOWN";
        }

        NodeStatistic nodeStatistic = nodeStatistics.get( label );
        if ( nodeStatistic == null )
        {
            nodeStatistics.put( label, nodeStatistic = new NodeStatistic( label ) );
        }

        nodeStatistic.update( node );
    }

    public Iterable<NodeStatistic> nodeStatistics()
    {
        return nodeStatistics.values();
    }

    public NodeStatistic getNodeStatistic( String label )
    {
        return nodeStatistics.get( label );
    }

    public int totalNodes()
    {
        int total = 0;
        for ( NodeStatistic nodeStatistic : nodeStatistics.values() )
        {
            total += nodeStatistic.count();
        }
        return total;
    }

    public int totalRelationships()
    {
        int total = 0;
        for ( NodeStatistic nodeStatistic : nodeStatistics.values() )
        {
            for ( RelationshipStatistic relationshipStatistic : nodeStatistic.relationshipStatistics() )
            {
                total += relationshipStatistic.outgoing().total();
            }
        }
        return total;
    }

    public Map<String, Integer> totalsPerRelationship()
    {
        Map<String, Integer> totals = new HashMap<String, Integer>();

        for ( NodeStatistic nodeStatistic : nodeStatistics.values() )
        {
            for ( RelationshipStatistic relationshipStatistic : nodeStatistic.relationshipStatistics() )
            {
                String label = relationshipStatistic.label();
                Integer total = totals.get( label );
                if ( total == null )
                {
                    totals.put( label, total = 0 );
                }
                totals.put( label, total + relationshipStatistic.outgoing().total() );
            }
        }

        return totals;
    }

    public String description()
    {
        return description;
    }

    public void describeTo( GraphStatisticsFormatter formatter )
    {
        formatter.describe( this );
    }
} public static ChoiceOfTargetNodesStrategy randomChoice( TargetNodesStrategy... targetNodeStrategies )
{
    return new RandomTargetNodesStrategy( asList( targetNodeStrategies ) );
} public static Range minMax( int min, int max )
{
    return new Range( min, max );
} public static SetNumberOfNodes getOrCreate( NodeSpecification nodeSpecification, int maxNumberOfEntities,
                                            ProbabilityDistribution probabilityDistribution )
{
    GetOrCreateUniqueNodes targetNodesSource = new GetOrCreateUniqueNodes( nodeSpecification,
            maxNumberOfEntities, probabilityDistribution );
    return new TargetNodesStrategyBuilder( targetNodesSource );
} public static Property indexableProperty( GraphDatabaseService db, String label, String name, String... labelNames )
{
    return indexableProperty( db,label, name, new CounterBasedStringPropertyValueGenerator(), labelNames );
} public static Property property( String name )
{
    return new NonIndexableProperty( name, new CounterBasedStringPropertyValueGenerator() );
} public static PropertyValueGenerator integerRange( int min, int max,
                                                   ProbabilityDistribution probabilityDistribution )
{
    return new RangeBasedIntegerPropertyValueGenerator( Range.minMax( min, max ), probabilityDistribution );
}
import java.util.List;
import org.junit.Test;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.neode.Dataset;
import org.neo4j.neode.DatasetManager;
import org.neo4j.neode.NodeCollection;
import org.neo4j.neode.NodeSpecification;
import org.neo4j.neode.RelationshipSpecification;
import org.neo4j.neode.logging.SysOutLog;
import org.neo4j.neode.statistics.AsciiDocFormatter;
import org.neo4j.neode.statistics.GraphStatistics;
import static org.neo4j.neode.ChoiceOfTargetNodesStrategy.randomChoice;
import static org.neo4j.neode.Range.minMax;
import static org.neo4j.neode.TargetNodesStrategy.getOrCreate;
import static org.neo4j.neode.properties.Property.indexableProperty;
import static org.neo4j.neode.properties.Property.property;
import static org.neo4j.neode.properties.PropertyValueGenerator.integerRange;
package org.neo4j.neode.test;





public class PricingStructureExample
{
    @Test
    public void buildPricingStructure()
    {
        GraphDatabaseService db = Db.tempDb();
        DatasetManager dsm = new DatasetManager( db, SysOutLog.INSTANCE );