<commit_before>/* Copyright 2014 MITRE Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.mitre.provenance.plusobject.builder;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.UUID;

import org.mitre.provenance.Metadata;
import org.mitre.provenance.PLUSException;
import org.mitre.provenance.contenthash.ContentHasher;
import org.mitre.provenance.contenthash.SHA256ContentHasher;
import org.mitre.provenance.db.neo4j.Neo4JPLUSObjectFactory;
import org.mitre.provenance.npe.NonProvenanceEdge;
import org.mitre.provenance.plusobject.PLUSActor;
import org.mitre.provenance.plusobject.PLUSEdge;
import org.mitre.provenance.plusobject.PLUSInvocation;
import org.mitre.provenance.plusobject.PLUSObject;
import org.mitre.provenance.plusobject.PLUSString;
import org.mitre.provenance.plusobject.PLUSWorkflow;
import org.mitre.provenance.plusobject.ProvenanceCollection;
import org.mitre.provenance.user.User;

/**
 * A tool to help build provenance graphs more easily, and to make code to do so more readable.
 * For example, to create a simple two-node graph that shows a data item input to a process, this code can be used:
 * <p><pre>new DeclarativeBuilder().link(db.dataNamed("Data input"), db.invocationNamed("Process Foo"))</pre>
 * <p>Most types return a DeclarativeBuilder for easy method chaining.   Almost all methods do <strong>not</strong> modify the state
 * of the current object, they return a new object.
 * 
 *  <p>Below is some sample code that illustrates building a simple workflow.   Let's say that we want to model 
 *  two pieces of data ("X" and "Y" with values "3" and "4" respectively) being fed into an "addition" process, resulting in 
 *  a piece of data "Z" (value 7).  That code might look like this:
 *  <pre>
 *  ProvBuilder pb = new ProvBuilder(new PLUSWorkflow("Simple Addition"), null);
 *  
 *  ProvBuilder inputs = new ProvBuilder(
 *     pb.newNodeNamed("X", "value", "3"),
 *     pb.newNodeNamed("Y", "value", "4"));
 *     
 *  ProvBuilder process = pb.newInvocationNamed("Addition");
 *  ProvBuilder output = pb.newNodeNamed("Z", "value", "7");
 *  return pb.merge(pb.link(inputs, process), pb.link(process, output));
 * </pre>
 *
 *  A number of methods take an arbitrary number of Strings called "metadataKeyValuePairs".   Java doesn't permit
 *  static initialization of maps, so this is how some basic metadata can be specified in creating a new object.  For example,
 *  if you call newDataNamed("Foo", "X", "hello", "Y", "world") then you'll get an object named Foo, with two metadata pairs:
 *  X=hello, and Y=world.
 * @author moxious
 */
public class ProvBuilder extends ProvenanceCollection {
	protected PLUSWorkflow wf = PLUSWorkflow.DEFAULT_WORKFLOW;
	protected User viewer = User.PUBLIC;
	protected ContentHasher hasher = null;

	/** Create a new builder object with the default workflow, viewed by User PUBLIC. */
	public ProvBuilder() {
		this(PLUSWorkflow.DEFAULT_WORKFLOW, User.PUBLIC);		
	}
		
	/**
	 * Create a new DeclarativeBuilder object connected to a specified workflow and user "viewer".
	 * @param wf 
	 * @param viewer
	 * @throws PLUSException
	 */
	public ProvBuilder(PLUSWorkflow wf, User viewer) {
		super();
		
		if(wf == null) wf = PLUSWorkflow.DEFAULT_WORKFLOW;
		if(viewer == null) viewer = User.PUBLIC;
		
		this.wf = wf;
		this.viewer = viewer;
		
		if(wf != PLUSWorkflow.DEFAULT_WORKFLOW) addNode(wf);
		
		try { hasher = new SHA256ContentHasher(); }
		catch(Exception exc) { ; } 
	} // End DeclarativeBuilder
	
	/**
	 * Create a new builder as the union of all of the arguments.
	 * @param builders
	 */
	public ProvBuilder(ProvBuilder ... builders) { 
		this();		
		merge(builders);
	}
	
	/**
	 * Create a new builder containing all of the arguments.
	 * @param items
	 */
	public ProvBuilder(PLUSObject ... items) {
		this();
		for(PLUSObject o : items) addNode(o);
	}
	
	/**
	 * Utility method to generate a new random SHA-256 hash 
	 * @return a String representing a hex-encoded, random SHA-256 hash.
	 * @throws IOException
	 */
	protected String generateHash() throws IOException { 
		String uuid = UUID.randomUUID().toString();		
		return ContentHasher.formatAsHexString(hasher.hash(new ByteArrayInputStream(uuid.getBytes())));
	}
		
	public User getUser() { return viewer; } 
	public ProvBuilder setUser(User viewer) { this.viewer = viewer; return this; } 
	
	/**
	 * Combine all the provided builders into the current one, modifying the current one.
	 * @param builders
	 * @return
	 */
	public ProvBuilder merge(ProvBuilder ... builders) {
		for(ProvBuilder db : builders) addAll(db);
		return this;
	}
	
	/**
	 * Link the first object found under headName with the first object found under tailName
	 * @param headName
	 * @param tailName
	 * @return a new DeclarativeBuilder containing the linkage
	 * @throws PLUSException
	 */
	public ProvBuilder link(String headName, String tailName) throws PLUSException { 
		ProvBuilder head = nodeNamed(headName);
		ProvBuilder tail = nodeNamed(tailName);
		
		return link(head, tail); 
	}
		
	/**
	 * Same as excise(items, false)
	 * @see ProvBuilder#excise(ProvBuilder, boolean)
	 * @param items the items you want removed
	 * @return a new builder
	 */
	public ProvBuilder excise(ProvBuilder items) { 
		return excise(items, false);
	}
	
	/**
	 * Remove all items found in one builder from this, and returns a new builder.  Removes all edges incident to nodes
	 * being removed.
	 * @param items the list of items you want removed
	 * @param removeWorkflows if true, workflow nodes will be removed as well.  If false, they won't be.
	 * @return new declarative builder
	 */
	public ProvBuilder excise(ProvBuilder items, boolean removeWorkflows) {
		ProvBuilder db = new ProvBuilder(this);
			
		for(PLUSEdge e : items.getEdges()) {
			db.removeEdge(e);
		}
		
		for(PLUSObject o : items.getNodes()) {
			if(o.isWorkflow() && !removeWorkflows) continue;
			
			System.out.println("Removing " + o); 
			db.removeNode(o, true);  // Remove incident edges too.
		}
	
		for(PLUSActor a : items.getActors()) {
			db.removeActor(a);
		}
		
		for(NonProvenanceEdge npe : items.getNonProvenanceEdges()) {
			db.removeNonProvenanceEdge(npe);
		} 
		
		return db;
	} // End excise
	
	/**
	 * Returns a new builder containing only those edges that go between nodes named headName, and nodes named tailName.
	 * @param headName
	 * @param tailName
	 * @return<commit_msg>convenience functions<commit_after>/* Copyright 2014 MITRE Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.mitre.provenance.plusobject.builder;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.UUID;

import org.mitre.provenance.Metadata;
import org.mitre.provenance.PLUSException;
import org.mitre.provenance.contenthash.ContentHasher;
import org.mitre.provenance.contenthash.SHA256ContentHasher;
import org.mitre.provenance.db.neo4j.Neo4JPLUSObjectFactory;
import org.mitre.provenance.npe.NonProvenanceEdge;
import org.mitre.provenance.plusobject.PLUSActor;
import org.mitre.provenance.plusobject.PLUSEdge;
import org.mitre.provenance.plusobject.PLUSInvocation;
import org.mitre.provenance.plusobject.PLUSObject;
import org.mitre.provenance.plusobject.PLUSString;
import org.mitre.provenance.plusobject.PLUSWorkflow;
import org.mitre.provenance.plusobject.ProvenanceCollection;
import org.mitre.provenance.user.User;

/**
 * A tool to help build provenance graphs more easily, and to make code to do so more readable.
 * For example, to create a simple two-node graph that shows a data item input to a process, this code can be used:
 * <p><pre>new DeclarativeBuilder().link(db.dataNamed("Data input"), db.invocationNamed("Process Foo"))</pre>
 * <p>Most types return a DeclarativeBuilder for easy method chaining.   Almost all methods do <strong>not</strong> modify the state
 * of the current object, they return a new object.
 * 
 *  <p>Below is some sample code that illustrates building a simple workflow.   Let's say that we want to model 
 *  two pieces of data ("X" and "Y" with values "3" and "4" respectively) being fed into an "addition" process, resulting in 
 *  a piece of data "Z" (value 7).  That code might look like this:
 *  <pre>
 *  ProvBuilder pb = new ProvBuilder(new PLUSWorkflow("Simple Addition"), null);
 *  
 *  ProvBuilder inputs = new ProvBuilder(
 *     pb.newNodeNamed("X", "value", "3"),
 *     pb.newNodeNamed("Y", "value", "4"));
 *     
 *  ProvBuilder process = pb.newInvocationNamed("Addition");
 *  ProvBuilder output = pb.newNodeNamed("Z", "value", "7");
 *  return pb.merge(pb.link(inputs, process), pb.link(process, output));
 * </pre>
 *
 *  A number of methods take an arbitrary number of Strings called "metadataKeyValuePairs".   Java doesn't permit
 *  static initialization of maps, so this is how some basic metadata can be specified in creating a new object.  For example,
 *  if you call newDataNamed("Foo", "X", "hello", "Y", "world") then you'll get an object named Foo, with two metadata pairs:
 *  X=hello, and Y=world.
 * @author moxious
 */
public class ProvBuilder extends ProvenanceCollection {
	protected PLUSWorkflow wf = PLUSWorkflow.DEFAULT_WORKFLOW;
	protected User viewer = User.PUBLIC;
	protected ContentHasher hasher = null;

	/** Create a new builder object with the default workflow, viewed by User PUBLIC. */
	public ProvBuilder() {
		this(PLUSWorkflow.DEFAULT_WORKFLOW, User.PUBLIC);		
	}
		
	/**
	 * Create a new DeclarativeBuilder object connected to a specified workflow and user "viewer".
	 * @param wf 
	 * @param viewer
	 * @throws PLUSException
	 */
	public ProvBuilder(PLUSWorkflow wf, User viewer) {
		super();
		
		if(wf == null) wf = PLUSWorkflow.DEFAULT_WORKFLOW;
		if(viewer == null) viewer = User.PUBLIC;
		
		this.wf = wf;
		this.viewer = viewer;
		
		if(wf != PLUSWorkflow.DEFAULT_WORKFLOW) addNode(wf);
		
		try { hasher = new SHA256ContentHasher(); }
		catch(Exception exc) { ; } 
	} // End DeclarativeBuilder
	
	/**
	 * Create a new builder as the union of all of the arguments.
	 * @param builders
	 */
	public ProvBuilder(ProvBuilder ... builders) { 
		this();		
		merge(builders);
	}
	
	/**
	 * Create a new builder containing all of the arguments.
	 * @param items
	 */
	public ProvBuilder(PLUSObject ... items) {
		this();
		for(PLUSObject o : items) addNode(o);
	}
	
	/**
	 * Utility method to generate a new random SHA-256 hash 
	 * @return a String representing a hex-encoded, random SHA-256 hash.
	 * @throws IOException
	 */
	protected String generateHash() throws IOException { 
		String uuid = UUID.randomUUID().toString();		
		return ContentHasher.formatAsHexString(hasher.hash(new ByteArrayInputStream(uuid.getBytes())));
	}
		
	public User getUser() { return viewer; } 
	public ProvBuilder setUser(User viewer) { this.viewer = viewer; return this; } 
	
	/**
	 * Combine all the provided builders into the current one, modifying the current one.
	 * @param builders
	 * @return
	 */
	public ProvBuilder merge(ProvBuilder ... builders) {
		for(ProvBuilder db : builders) addAll(db);
		return this;
	}
	
	/**
	 * Link the first object found under headName with the first object found under tailName
	 * @param headName
	 * @param tailName
	 * @return a new DeclarativeBuilder containing the linkage
	 * @throws PLUSException
	 */
	public ProvBuilder link(String headName, String tailName) throws PLUSException { 
		ProvBuilder head = nodeNamed(headName);
		ProvBuilder tail = nodeNamed(tailName);
		
		return link(head, tail); 
	}
		
	public PLUSObject singleNode() {
		if(countNodes() > 1) log.warning("Collection has more than one node!");
		return getNodes().iterator().next();
	}
	
	/**
	 * Same as excise(items, false)
	 * @see ProvBuilder#excise(ProvBuilder, boolean)
	 * @param items the items you want removed
	 * @return a new builder
	 */
	public ProvBuilder excise(ProvBuilder items) { 
		return excise(items, false);
	}
	
	/**
	 * Remove all items found in one builder from this, and returns a new builder.  Removes all edges incident to nodes
	 * being removed.
	 * @param items the list of items you want removed
	 * @param removeWorkflows if true, workflow nodes will be removed as well.  If false, they won't be.
	 * @return new declarative builder
	 */
	public ProvBuilder excise(ProvBuilder items, boolean removeWorkflows) {
		ProvBuilder db = new ProvBuilder(this);
			
		for(PLUSEdge e : items.getEdges()) {
			db.removeEdge(e);
		}
		
		for(PLUSObject o : items.getNodes()) {
			if(o.isWorkflow() && !removeWorkflows) continue;
			
			// System.out.println("Removing " + o); 
			db.removeNode(o, true);  // Remove incident edges too.
		}
	
		for(PLUSActor a : items.getActors()) {
			db.removeActor(a);
		}
		
		for(NonProvenanceEdge npe : items.getNonProvenanceEdges()) {
			db.removeNonProvenanceEdge(npe);
		} 
		
		return db;
	} // End excise
	
	/**
	 * Returns a new builder containing only those edges that go between nodes named headName, and nodes named tailName.
	 * @param headName
	 * @param tailName
	 * @return
{'fraction_non_alphanumeric': 0.10296312086975058, 'fraction_numerical': 0.004050309102536773, 'mean_word_length': 3.952146375791696, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 22, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6542405', 'n_tokens_mistral': 4574, 'n_tokens_neox': 4168, 'n_words': 1427}