Software Development

You are currently browsing the archive for the Software Development category.

CS331 was a fun class. Data structures are right up my ally. They take a simple concept and expand it to apply to scenarios in neat and interesting ways.  It was eight years ago when I took (and passed) that class…Yikes!

Most of the assignment, actually, 4 out of 5 assignments, were coded and turned in on time.  This was not a small feat.  In college, it was common for me to work a 30 hour week.  Getting assignments done was usually a balancing act of putting one thing on hold while moving on to another (such is life).  Four out of five is not all that bad.  So why did that one problem give me such an issue.  Why has there been a sour note in the melody that was a great collegiate career?  Because I said I would finish it, that’s why.

Java coding has been taking up a lot of my nights lately.  It’s fun again to write code; just like in college.  When thinking up of things to code, the memory of this assignment came to the front of my mind.  It was time.

After reviewing the C++ I had written 8 years ago, it was obvious that it was close to working.  There was just a piece or two missing.  Perhaps that is another reason why this had been stuck in my mind.   The other thing that was obvious is that I name my variables much better now.  Reading my old code was nothing less than a chore.  Writing code that someone else, not myself, can maintain is something that I took for granted then.  Of course, in college, it was cheating to work with someone else on a project :).

The rules for the application

  • The application will read input from a text file
  • The application will use Dikjstra’s algorithm to find the shortest path between all reachable nodes in the graph

Here’s a short refresher on Dijkstra’s Algorithm from Wikipedia:

Let the node at which we are starting be called the initial node. Let the distance of node Y be the distance from the initial node to Y. Dijkstra’s algorithm will assign some initial distance values and will try to improve them step by step.

  1. Assign to every node a distance value: set it to zero for our initial node and to infinity for all other nodes.
  2. Mark all nodes as unvisited. Set initial node as current.
  3. For current node, consider all its unvisited neighbors and calculate their tentative distance. For example, if current node (A) has distance of 6, and an edge connecting it with another node (B) is 2, the distance to B through A will be 6+2=8. If this distance is less than the previously recorded distance, overwrite the distance. All unvisited neighbors are added to an unvisited set.
  4. When we are done considering all neighbors of the current node, mark it as visited. A visited node will not be checked ever again; its distance recorded now is final and minimal.
  5. The next current node will be the node with the lowest distance in the unvisited set.
  6. If all nodes have been visited, finish. Otherwise, set the unvisited node with the smallest distance (from the initial node, considering all nodes in graph) as the next “current node” and continue from step 3.

Design:

  • There are two main objects in play, an Edge and a Node.  A Node is a destination and an Edge describes how to get there.
  • To accomplish #5 above, a PriorityQueue is used.  It is a sorted Queue.
  • There is another requirement that was part of the problem.  There are two types of graphs, one that has bi-directional nodes and one that has directional nodes.  Instead of creating two Edge objects with a source and a destination, I decided to make the Edge object less directional sounding.  It has a leftNode and a rightNode.  That way, in a directional graph, left is before right.  In a bi-directional graph the direction can be considered ambiguous.

Changes if this Were Production Code:

  • The nodeId is used all over the place.  In order to make this code apply to more situations, the Id should be a generic type
  • Better comments

What I Learned:

  • HashMaps are very intuitive.  Give a key, get a value.
  • If at first you don’t succeed…..but 8 years later… really?

The Code (Save all files to the same directory):
Save as Main.java

import java.io.BufferedReader;
import java.io.Console;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Scanner;


public class Main {

	//These would be in a Value Object if Object orientation was a concern
	Queue<Node> unsettledNodes = new PriorityQueue<Node>();
	Map<Integer,ArrayList<Edge>> edgeMapKeyedOnNodeId = new HashMap<Integer,ArrayList<Edge>>();
	Map<Integer,Node> nodeMap = new HashMap<Integer,Node>();
	String uOrD;

	Integer INFINITE_DISTANCE = Integer.MAX_VALUE;

	public static void main(String... args)
	{
		Integer choice = -1;
		while (choice != 0)
		{
			Console console = System.console();
			String input;
			if (console != null)
			{
				console.printf("Enter in the file number to open 1-3 or 0 to exit\n");
				input = console.readLine();
			}
			else
			{
				input = "3";
			}
			choice = Integer.parseInt(input);
			
			if(!choice.equals(0))
			{
				Main main = new Main();
				Integer numberOfNodes = main.readFileInput(choice);
				Integer startNodeId =  main.getStartNode(numberOfNodes);
				main.runDijkstra(startNodeId);
				main.printPaths();
			}

			if (console == null)
			{
				choice = 0;
			}
		}
	}

	/**
	 * Reads in the file and populates the graph variables
	 * @param fileNumber the number of the file to load from
	 * @return the number of nodes in the graph
	 */
	private Integer readFileInput(Integer fileNumber)
	{
		Integer numberOfNodes = 0;
		try 
		{
			FileReader fr = new FileReader("edge_weights" + fileNumber);
			BufferedReader bfr = new BufferedReader(fr);

			numberOfNodes = Integer.parseInt(bfr.readLine());
			numberOfNodes --; // 0 base it for ease of use
			uOrD = bfr.readLine(); //Directed or Undirected
			String nodeLine;
			ArrayList<Edge> edgeList = null;

			while ((nodeLine = bfr.readLine()) != null)
			{
				Scanner scanner = new Scanner(nodeLine);
				Edge edge = new Edge();
				edge.setVertexLeft(scanner.nextInt());
				edgeList = edgeMapKeyedOnNodeId.get(edge.getVertexLeft());
				if (edgeList == null)
				{
					edgeList = new ArrayList<Edge>();
				}
				edge.setVertexRight(scanner.nextInt());
				edge.setWeight(scanner.nextInt());
				edgeList.add(edge);
				edgeMapKeyedOnNodeId.put(edge.getVertexLeft(), edgeList);
				if (uOrD.equals("U"))
				{
					//Add the same edge to the possible edges from the right vertex
					//The same edge object is put in the left and right maps when in unordered mode
					edgeList = edgeMapKeyedOnNodeId.get(edge.getVertexRight());
					if (edgeList == null)
					{
						edgeList = new ArrayList<Edge>();
					}
					edgeList.add(edge);
					edgeMapKeyedOnNodeId.put(edge.getVertexRight(), edgeList);					
				}
			}
			bfr.close();
		}
		catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return numberOfNodes;
	}

	/**
	 * Prompt the user for a start node
	 * @param numberOfNodes the number of nodes in the graph
	 * @return the start node the user entered
	 */
	public Integer getStartNode(Integer numberOfNodes)
	{
		Integer startNode = -1;
		Console console = System.console();

		while (startNode < 0 || startNode > numberOfNodes)
		{
			if (console != null)
			{
				console.printf("Enter a start node 0-" + numberOfNodes + "\n");
				startNode = Integer.parseInt(console.readLine());
			}
			else
			{
				startNode = 0;
			}
		}
		return startNode;
	}

	public void runDijkstra(Integer startNodeId)
	{
		Node node = new Node();
		node.setNodeId(startNodeId);
		node.setWeight(0);
		nodeMap.put(node.getNodeId(),node);	

		Integer totalComparisons = 0;
		while (node != null)
		{
			if (edgeMapKeyedOnNodeId.get(node.getNodeId()) != null)
			{
				for(Edge edge : edgeMapKeyedOnNodeId.get(node.getNodeId()))
				{
					Node nextDestination;
					Integer nextDestinationId = null;
					//If unordered the id of the next destination may be the right or the left vertex
					if (uOrD.equals("U"))
					{
						nextDestinationId = edge.getOppositeVertex(node.getNodeId());
					}
					else
					{
						//If ordered, the destination is always the right vertex
						nextDestinationId = edge.getVertexRight();
					}


					if (nodeMap.get(nextDestinationId) == null)
					{
						nextDestination = new Node();
						nextDestination.setNodeId(nextDestinationId);
						nextDestination.setWeight(INFINITE_DISTANCE);
					}
					else
					{
						nextDestination = nodeMap.get(nextDestinationId);
					}


					System.out.println("Calculating distance From " + node.getNodeId() + " To: " + nextDestination.getNodeId() + " Traversed: " + nextDestination.getTraversed());
					System.out.println("Current Node Distance: " + node.getWeight() + " Edge Weight: " + edge.getWeight() + " Against " + nextDestination.getWeight());

					if(!nextDestination.traversed)
					{	
						totalComparisons ++;
						if(nextDestination.getWeight() > node.getWeight() + edge.weight)
						{
							System.out.println("Found a shorter path to " + nextDestination.getNodeId());

							nextDestination.setWeight(edge.weight + node.getWeight());
							nextDestination.setPredecessor(node);
							nodeMap.put(nextDestination.getNodeId(),nextDestination);
							unsettledNodes.offer(nextDestination);
						}
					}
				}
			}
			node.traversed = true;
			node = unsettledNodes.poll();
		}

		System.out.println("Total distance comparisons: " + totalComparisons);
	}
	
	/**
	 * Print out all of the paths to all of the possible destinations
	 */
	public void printPaths()
	{
		for(int i = 0; i<= nodeMap.size(); i++)
		{
			if (nodeMap.get(i) != null)
			{
				List<Node> shortestPath = getShortestPathTo(nodeMap.get(i));
				System.out.println("Shortest path to node index " + i);
				for (Node point : shortestPath)
				{
					System.out.println(point);
				}
			}
		}
	}


	/**
	 * Returns a list containing the nodes that are in the shortest path to the destination
	 * @param destinationNode
	 * @return
	 */
	public List<Node> getShortestPathTo(Node destinationNode)
	{
		List<Node> path = new ArrayList<Node>();
		for (Node lastNode = destinationNode; lastNode.getPredecessor() != null; lastNode = lastNode.getPredecessor())
		{
			path.add(lastNode);
		}

		Collections.reverse(path);
		return path;
	}

	/**
	 * This is the representation of an edge.  Note that is does not imply direction.
	 *
	 */
	class Edge
	{
		Integer vertexLeft;
		Integer vertexRight;    	
		Integer weight;
		public Integer getVertexLeft() {
			return vertexLeft;
		}
		public void setVertexLeft(Integer vertexLeft) {
			this.vertexLeft = vertexLeft;
		}
		public Integer getVertexRight() {
			return vertexRight;
		}
		public void setVertexRight(Integer vertexRight) {
			this.vertexRight = vertexRight;
		}
		public Integer getWeight() {
			return weight;
		}
		public void setWeight(Integer weight) {
			this.weight = weight;
		}
		@Override
		public String toString()
		{
			String stringed = "Left: " + vertexLeft + " Right: " + vertexRight + " Weight: " + weight;
			return stringed;
		}

		public Integer getOppositeVertex (Integer vertex)
		{
			Integer oppositeVertex = null;
			if (vertex.equals(vertexLeft))
			{
				oppositeVertex = vertexRight;
			}
			else
			{
				oppositeVertex = vertexLeft;
			}
			return oppositeVertex;
		}
	}


	/**
	 * This is a representation of a node on the graph
	 */
	class Node implements Comparable<Node>
	{
		Integer nodeId;
		Boolean traversed = false;
		Integer distanceFromStartNode = Integer.MAX_VALUE;
		Node predecessor;

		public Node getPredecessor() {
			return predecessor;
		}
		public void setPredecessor(Node predecessor) {
			this.predecessor = predecessor;
		}
		public Integer getNodeId() {
			return nodeId;
		}
		public void setNodeId(Integer vertexStart) {
			this.nodeId = vertexStart;
		}
		public Boolean getTraversed() {
			return traversed;
		}
		public void setTraversed(Boolean traversed) {
			this.traversed = traversed;
		}
		public Integer getWeight() {
			return distanceFromStartNode;
		}
		public void setWeight(Integer weight) {
			this.distanceFromStartNode = weight;
		}
		@Override
		public int compareTo(Node o) {
			return this.getWeight().compareTo(o.distanceFromStartNode);
		}
		@Override
		public String toString()
		{
			return ("Node Id:" + nodeId + " Weight: " + distanceFromStartNode);
		}
	}
}

Save as edge_weights1

6
U
0 1 800
0 2 2985
0 3 310
0 4 200
1 2 410
1 3 612
2 3 1421
3 4 400
1 5 100
5 4 100

Save as edge_weights2

5
D
1 0 800
2 0 2985
0 2 2985
0 3 310
4 0 200
1 2 410
2 1 410
3 1 612
1 3 612
2 3 1421
3 2 1421
3 4 400

Save as edge_weights3

10
U
1 0 3
2 1 17
2 3 2
8 9 1
0 7 2
8 0 13
7 8 10
7 6 5
6 8 4
5 6 3
4 5 15
4 9 12
3 9 9
2 9 6
4 3 3

Run (in the same directory as all of the files):

javac Main.java
java -cp . Main

Tags:

This is example code to do a SFTP file copy using the JSCH Java library.  There are a few libraries in this arena, and all of them seem more complex than they are.  JSCH doesn’t have the best documentation along with it, but it is maintained and in the Maven repositories.

The key file just needs to be somewhere in the classpath for this to work.    The code is actually quite short.  In the actual version of this code, there is an object that has all of the values that is needed for this method and a verification routine to make sure that they are valid.

Note that the SFTP session is always connected regardless is the file to be copied doesn’t exists.  This is by design so that if there is an issue with the SSH server, an error will be received even if there is no file to copy.

JSch.setLogger(new JSCHLogger()); // More on this below
JSch jsch = new JSch();
Session session = null;
ChannelSftp channel = null;
FileInputStream localFileStream = null;

try
{
//Use key authentication if it is set, else use password auth
if (YOUR_KEY_FILE_NAME != null && YOUR_KEY_FILE_NAME != "")
{
URL keyFileURL = this.getClass().getClassLoader().getResource(YOUR_KEY_FILE_NAME);
if (keyFileURL == null)
{
throw new RuntimeException("Key file " + YOUR_KEY_FILE_NAME  + "not found in classpath");
}
URI keyFileURI = keyFileURL.toURI();

jsch.addIdentity(new File(keyFileURI).getAbsolutePath());
session = jsch.getSession(YOUR_SSH_SERVER_USER_NAME, YOUR_SSH_SERVER_NAME , YOUR_SSH_SERVER_PORT);
}
else if (YOUR_SSH_PASSWORD != null && YOUR_SSH_PASSWORD != "")
{
session = jsch.getSession(YOUR_SSH_SERVER_USER_NAME, YOUR_SSH_SERVER_NAME , YOUR_SSH_SERVER_PORT);
session.setPassword(YOUR_SSH_SERVER_PASSWORD);
}

//Make it so we do not do host key checking.  Enabling this would require some extra code and maintenance, but would increase security.
session.setConfig("StrictHostKeyChecking", "no");
session.setTimeout(15000);

session.connect();

channel = (ChannelSftp)session.openChannel("sftp");
channel.connect();

if (YOUR_SSH_SERVER_FILE_DIRECTORY != null && YOUR_SSH_SERVER_FILE_DIRECTORY != "")
{
channel.cd(YOUR_SSH_SERVER_FILE_DIRECTORY);
}

File localFile = new File (YOUR_LOCAL_FILE_TO_COPY + YOUR_LOCAL_FILE_TO_COPY);
if (localFile.exists())
{
localFileStream = new FileInputStream(localFile);

channel.put(localFileStream, YOUR_REMOTE_FILE_NAME));
}
else
{
log.warn("Local file not found " + localFile.getAbsolutePath());
}
}

There is one more thing that needs to be done.  JSCH has its own internal logging that isn’t automatically connected to the program’s logger.  This code ties them together.

public static class JSCHLogger implements com.jcraft.jsch.Logger
{

static java.util.Hashtable<Integer,String> name = new java.util.Hashtable <Integer,String> ();

static
{
name.put(new Integer(DEBUG), "DEBUG: ");
name.put(new Integer(INFO), "INFO: ");
name.put(new Integer(WARN), "WARN: ");
name.put(new Integer(ERROR), "ERROR: ");
name.put(new Integer(FATAL), "FATAL: ");
}

public boolean isEnabled(int level)
{
return true;
}

public void log(int level, String message)
{
if(level == DEBUG)
{
log.debug(message);
}
else if(level == INFO)
{
log.info(message);
}
else if(level == WARN)
{
log.warn(message);
}
else if(level == ERROR)
{
log.error(message);
}
else if(level == FATAL)
{
log.fatal(message);
}
}
} 

Tags: , ,

So I’m mixing it up a little bit.  Today’s example prints from a StringBuffer and not a File.  Printing from a File should be pretty similar.  This is useful whenever something needs to be printed out for auditing purposes.

There is some code in here to get around the limitations of a page.  The number of lines on a page and the font size are part of this.  In practice, if the line in the StringBuffer is bigger than the page, it will just disappear.    In this case, the lines were already formatted to be a length that did not skip off of the page.

The code treats the page as a canvas and writes to specific part of that canvas.  If you use this code, be sure to pay attention to all of the settings that inpact the way the text appears on the page.  Some changes will impact other settings.  For example, if the font size is increased, the spacing between lines should increase.

This isn’t particularly object oriented as it is an example.

PrintService printService = PrintHelper.getPrintService(YOUR_PRINTER_NAME);
PrintRequestAttributeSet printRequestAttributeSet = new HashPrintRequestAttributeSet();
printRequestAttributeSet.add(new JobName(YOUR_JOB_NAME,Locale.getDefault()));
PrinterJob pjob = PrinterJob.getPrinterJob();
pjob.setPrintService(printService);

PDDocument document = new PDDocument();
//To load a file do this:   pd = PDDocument.load(input);
PDPage page = new PDPage();
document.addPage( page );
PDFont font = PDType1Font.HELVETICA_BOLD;
PDPageContentStream contentStream = new PDPageContentStream(document, page);
contentStream.beginText();
contentStream.setFont( font, 12 );
//The first number in moveTextPositionByAmount is to provide a left margin.
//The second number in moveTextPositionByAmount is to miss the header
contentStream.moveTextPositionByAmount( 100, 550 );
StringReader strReader = new StringReader(printText.toString());
BufferedReader strBufRdr = new BufferedReader(strReader);
String line = "";
Integer linesPrinted = 0;
while((line=strBufRdr.readLine()) != null)
{
contentStream.drawString(line);
linesPrinted++;
//The second parameter will change if the font changes.
//It's something that has to be guessed and checked to get right.
contentStream.moveTextPositionByAmount( 0, SPACE BETWEEN LINES -15 SHOULD BE GOOD);
if (linesPrinted % INTEGER_FOR_LINES ON A PAGE 30 SHOULD BE GOOD == 0)
{
contentStream.endText();
contentStream.close();
page = new PDPage();
document.addPage( page );
contentStream = new PDPageContentStream(document, page);
contentStream.beginText();
contentStream.setFont( font, 12 );
contentStream.moveTextPositionByAmount( 100, 550 );
}
}
contentStream.endText();
contentStream.close();
//For testing, write this to a file
//document.save("outpdf.pdf");
document.silentPrint(pjob);
document.close();
}

catch (PrinterException e)
{
//Handle Error
}
catch (IOException e)
{
//Handle Error
}
}

Tags: , ,

Printing is not an easy task.  It is easy to get tied into intricate details of environment specific configuration settings such as margins and paper size. Therefore, printing a PDF without a library is not recommended unless you have a lot of time or are really interested in that sort of thing.  There are two libraries that can perform this task already.   One will be exampled today, and another tomorrow.

Today’s library is PDFRenderer https://pdf-renderer.dev.java.net/.   This library was released by Sun a couple of years ago.   Note that this example does not map a FileChannel object as some examples do.  Using a FileChannel made the file not able to be deleted until garbage collection was run.  See the bug in the code comments for more details.

try
{
File f = null;
RandomAccessFile fis = null;
FileChannel fc = null;
ByteBuffer bb = null;
String printer = YOUR_PRINTER_NAME;
PrintService printService = PrintHelper.getPrintService(printer);

f = YOUR_PDF_FILE;
//Read only access would work too
fis = new RandomAccessFile(f, "rw");
fc = fis.getChannel();
bb = ByteBuffer.allocate((int)fc.size());
fc.read(bb);


//Do not map the file to a ByteBuffer as the examples show.
// There is a reason why in java bug #474038
// http://bugs.sun.com/view_bug.do?bug_id=4724038
//fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size());
//bb = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size());

PDFFile pdfFile = new PDFFile(bb); // Create PDF Print Page
PDFPrintPage pages = new PDFPrintPage(pdfFile);
// Create Print Job
PrinterJob pjob = PrinterJob.getPrinterJob();
pjob.setPrintService(printService);

PageFormat pf = PrinterJob.getPrinterJob().defaultPage();

pf.setOrientation(PageFormat.PORTRAIT);

Paper paper = new Paper();

//This is to fix an error in PDF-Renderer
//View http://juixe.com/techknow/index.php/2008/01/17/print-a-pdf-document-in-java/ for details
//Printing a PDF is also possible by sending the bytes directly to the printer, but
//  the printer would have to support it.

paper.setImageableArea(0,0,paper.getWidth() * 2,paper.getHeight());

pf.setPaper(paper);

pjob.setJobName(f.getName());

Book book = new Book();
book.append(pages, pf, pdfFile.getNumPages());
pjob.setPageable(book);
pjob.print();

}
catch (FileNotFoundException e)
{
//do your error action
}
catch (IOException e)
{
//do your error action
}
catch (PrinterException e)
{
//do your error action
}
finally
{
try
{
if (fc != null)
{
fc.close();
fc = null;
}
}
catch (IOException e)
{
log.error(e);
//handle error here
}
try
{
if (fis != null)
{
fis.close();
fis = null;
}
}
catch (IOException e)
{
//handle error here
}
if (bb != null)
{
bb.clear();
}
}

Because this library has the bug that makes it not want to print in half size when printing a portrait-oriented PDF,  I cannot recommend using this library unless you find it is the only thing that will do the job.    Such obvious bugs that have been around for over a year point to unmaintained code or some other upstream issues.  Tune in tomorrow for another library that is maintained that will get the job done as well.

Tags: , ,