Académique Documents
Professionnel Documents
Culture Documents
3.
4.
5.
You should divide and conquer so that no single piece is too big. When you set out to write a book, you start with an outline not a specific paragraph or figure. An outline amounts to a chapter list which you then expand for each chapter. You then expand each chapter into a section list and so on. A key point: as you break down the outline into finer-grained chunks, you will need to alter the overall structure of the chapter or even the whole book because you know more about the problem. This is called refactoring in the programming world and we will discuss in a future lecture. The book outlining process is an apt analogy to the functional decomposition design strategy of iterative refinement. The overall structure/outline/design is very nonspecific and references ill defined concepts that can only be concretely defined by further refinement. Information hiding is an old old concept that got incorporated into object-oriented design. The idea is simply to reduce what you have to think about at one time per design goal mentioned above. In terms of objects, it means that you do not expose your implementation, just some well-behaved methods that provide a service to other objects. In terms of functional decomposition, it means that each method should be just a few references to other methods, which in turn reference a few methods until you get to an atomic operation. Object-oriented programming languages call information hiding encapsulation and specifically provide language constructs to support it (in C you had to group functions by file whereas OO languages provide classes). Use encapsulation to 1. group similar functionality into a single "module" (a class in Java). These are your managers or services such as EmailManager, UserManager, ... group data and behavior associated with a real world entity into an object. These are your "actors" and entities such as User, EmailMessage, PopAccount, ...
2.
1.
hard to understand long sequences, especially if there are IF conditionals 2. hard to reuse big chunks whereas each smaller chunk could be reusable; factoring your code into smaller methods usually makes the whole program shorter due to code reuse. Why is good to factor big methods into smaller? 1. 2. the top level function that calls the subfunctions is like an index each subfunction has a nice name that clearly identifies its function
The leaves of the tree are self-contained methods that do not need to invoke other methods (except perhaps System.out.println() and the like) such as task3. In any given node, you should assume that all other methods work or will become available--focus solely on the sequence of steps taken by that method. This diagram doesn't show implementation details; it just shows the breakdown of tasks into subtasks for a particular operation (the root). Some subtasks are implemented later as inline code while other subtasks are implemented as references to other methods that you then must further break down into another branch. If you want, you can label the information flow to and from the
methods by their name. Sometimes a branch maps to a module for which we use a class like a DatabaseManager singleton object. Most often a decomposition diagram path will weave through several objects. I prefer doing these things sideways so my nodes can have lots of text
Another method is to imply a diagram by simply referencing another method that lives on another sheet of paper or wherever.
Start: task1 task2 task3 task1: blah blah task2: blort blort
2.
3.
4.
"atomic" operation that is just a few simple programming instructions. Coding: Implement the methods top-down. Contrary to some advocates of top-down design, I recommend strongly that you implement your methods starting at the highest level and working downwards. Changes at level n force changes at level n+1 and below like a ripple effect. Testing: Test your methods bottom-up by writing little test harnesses; this is called unit testing. At each level, you will have confidence in the level(s) below you. In summary, you will design your overall program using a object interaction diagram and design each object using functional decomposition.
From this, I conclude that I need the following objects: 1. Server.java 2. Dispatcher.java 3. ClientHandler.java But this does not tell me how they implement these inter -object messages. For that, I will provide a top-down design for the major methods.
Functional Decomposition
My text only functional decomposition ( across objects) looks like:
Server.main: check args (server root, log dir root, ...) create and launch Dispatch.accept() Dispatch.accept:
forever do { wait for a socket connection create a new ClientHandler attached to socket create a thread on the handler and launch } ClientHandler.run: (invoked from thread.start()) Get the first line of input If not empty, process shutdown input/output streams close socket ClientHandler.process If input line does not start with GET, return parse HTTP GET command if filename starts with "/java", execJava else dumpFile ClientHandler.dumpFile open file to dump read it all in and write to output stream to client flush output stream close file stream ClientHandler.execJava obtain Class object for class name get a new instance of that class if instance of ServerSideJava then exec its service method else send note to browser saying invalid Note that I don't worry about details like object constructors at this point. That's an implementation detail.
I tend not to worry about arguments to methods until implementation. I also don't worry much about error checking at this "algorithm" stage. You'll see something like this on the final!
package http; import java.net.ServerSocket; import java.net.Socket; import java.io.*; public class Server { public static final int DEFAULT_PORT = 8080; public static String rootDir = "."; public static String logDir = "/tmp"; public static void main(String[] args) { int port = DEFAULT_PORT; if (args.length != 2) { System.err.println("Usage: java Server root-dir log-dir"); System.exit(0); } rootDir = args[0]; logDir = args[1]; Dispatcher d = new Dispatcher(port); d.accept(); } } Dispatcher
package http; import java.net.*; import java.io.*; class Dispatcher { protected ServerSocket server; public Dispatcher(int port) { // create a server listening at port, max 15 pending connections try { server = new ServerSocket(port, 15); } catch (IOException e) { System.err.println("cannot listen at "+port+" ("+e.getMessage()+")"); } } public void accept() { Socket socket = null; while (true) { // For each connection, start a new client handler
// in its own thread and keep on listening try { socket = server.accept(); // Create a new Thread for the client Thread clientThread = null; clientThread = new Thread(new ClientHandler(socket)); clientThread.start(); } catch (IOException e) { System.err.println("Error creating socket connection"); System.exit(1); } } } } ClientHandler
package http; import java.net.*; import java.io.*; import java.util.Observable; import java.util.Observer; import java.util.*; public class ClientHandler implements Runnable { protected DataInputStream in; protected PrintStream out; protected Socket socket; public ClientHandler(Socket socket) throws IOException { this.socket = socket; in = new DataInputStream(socket.getInputStream()); out = new PrintStream(socket.getOutputStream()); } public void run() { try { String line = in.readLine(); if ( line!=null ) { Logger.log(String.valueOf(socket.getInetAddress()), line); process(out, line); } socket.shutdownInput(); socket.shutdownOutput(); out.close();
in.close(); socket.close(); } catch (IOException e) { // do nothing; IO error means end of file } } protected void process(PrintStream out, String line) { if ( line.startsWith("GET") ) { StringTokenizer st = new StringTokenizer(line); st.nextToken(); // skip GET String fileName = st.nextToken(); // get file // System.out.println("getting "+fileName); if ( fileName.startsWith("/java/") ) { String className = fileName.substring("/java/".length(), fileName.length()); execJava(out, className, fileName); } else { dumpFile(out, Server.rootDir+fileName); } } } protected void execJava(PrintStream out, String className, String fileName) { try { try { Class c = Class.forName(className); Object o = c.newInstance(); if ( o instanceof ServerSideJava ) { System.out.println("exec "+className); ((ServerSideJava)o).service(null,fileName, out); } else { PrintStream p = new PrintStream(out); p.println("Java class "+className+" not ServerSideJava"); } } catch (IllegalAccessException ia) { PrintStream p = new PrintStream(out); p.println("Cannot execute Java class "+className); } catch (ClassNotFoundException cnf) { PrintStream p = new PrintStream(out);
p.println("Cannot execute Java class "+className); } catch (InstantiationException i) { PrintStream p = new PrintStream(out); p.println("Cannot execute Java class "+className); } } catch (IOException e) { PrintStream p = new PrintStream(out); p.println("IO problems executing Java class "+className); } } protected void dumpFile(PrintStream out, String fileName) { File f = new File(fileName); if ( !f.exists() ) { // doesn't exist, send error message out.println("<html><body>Missing file "+fileName+"</body></html>"); return; } FileInputStream inFile = null; try { inFile = new FileInputStream(fileName); // System.out.println("reading "+fileName); byte[] buffer = new byte[1024]; int readCount; while( (readCount = inFile.read(buffer)) > 0) { out.write(buffer, 0, readCount); } out.flush(); } // If something went wrong, report it! catch(IOException e) { System.err.println("Couldn't send "+fileName); e.printStackTrace(System.err); } finally { try { inFile.close(); } catch(IOException e) { System.err.println("Couldn't close "+fileName); } } }
Student Examples
Good example A student had the same breakdown (minus one intermediate method that I had, process()):
public void run() { try { String line = in.readLine(); if (line != null) { if (line.startsWith("GET")) { int n = line.indexOf(' ') + 1; int m = line.indexOf("GET"); String filename = line.substring(n, line.indexOf(' ', n + 1)); if(filename.startsWith("/java/")) { callClass(filename.substring(6, filename.length())); } else { sendFile(filename); } } channel.shutdownInput(); channel.shutdownOutput(); out.close(); in.close(); channel.close(); } ... } Note the same reference to real world abstractions: callClass and sendFile .
Less than optimal example Here is another student example (names, style mangled to protect identity) where everything is in a big method with a name indicating they don't really have a clear relationship to real world concept or operation. The operations are also inline. Harder to write this way, harder to read, can't resuse anything.
void doTheJob() { int i = 0, j = 0; String line; String str = new String(); line = br.readLine(); if (line.indexOf("GET") >= 0) { i = line.indexOf("/java");
if (i >= 0) { j = line.substring(i+6).indexOf(" "); str = line.substring(i+6, i+6+j); try{ Class class_name = Class.forName(str); ServerSideJava java = (ServerSideJava)class_name .newInstance(); java.service(ps); } catch(ClassNotFoundException e) { ... } catch(Exception e) { ... } } else { i = line.indexOf("/"); j = line.substring(4).indexOf(" "); str = line.substring(i+1, j + 4); while((i = str.indexOf("%20"))>=0) { str = str.substring(0, i) + " " + str.substring(i+3, str.length()); } File fileName = new File(Server.document_root.getAbsoluteFile(),str); if (fileName.exists()) { byte[] buffer = new byte[Server.length]; int len; BufferedInputStream fb = new BufferedInputStream(new FileInputStream(fileName)); do { len = fb.read(buffer, 0, Server.length); bos.write(buffer, 0, Server.length); } while(len > 0); fb.close(); bos.close(); fileName = null; } else { // file not found ... } } ... } How about this one?
public class ClientHandler implements Runnable{ Socket socket; String docRoot; String fileLocation; OutputStream out = null; PrintStream p = null; InputStream in = null; BufferedReader buf; public ClientHandler(Socket socket, String docRoot) { this.socket = socket; this.docRoot = docRoot; } public void run() { try { // Get In and Out streams sorted out = socket.getOutputStream(); p = new PrintStream(out); in = socket.getInputStream(); //Using bufferedReader in instead because readline is not deprecated buf = new BufferedReader(new InputStreamReader(i n)); // Split the header string: requestPart[0] = GET, requestPart[1]= /filepath String headerLine = buf.readLine(); String[] requestPart; requestPart = headerLine.split(" "); String get = requestPart[0]; String filePath = requestPart[1]; if (!get.startsWith("GET")) { p.print(ErrorManager("Server only accepts GET command.")); closeAll(); } else { fileLocation = docRoot + filePath; if (filePath.startsWith("/java")) { execJava(filePath); closeAll(); } else { if (filePath.equals("/")) { filePath = indexFileHandler(); fileLocation = docRoot + filePath; } downloadFile(fileLocation); } } }
catch (IOException e) { e.printStackTrace(); } } private void downloadFile(String fileLocation) throws IOException { File f = new File(fileLocation); if (f.exists() || f.isDirectory()) { FileInputStream inFile = new FileInputStream(f); // Sort out header String httpHeader = "HTTP/1.0 200 OK \n"; //Determine Content-Type String contentType; if ((fileLocation.endsWith(".html") == true) | (fileLocation.endsWith(".htm") == true)) { contentType = "Content-Type: text/html\n\n"; } else if (fileLocation.endsWith(".gif") == true) { contentType = "Content-Type: image/gif\n\n"; } else if (fileLocation.endsWith(".jpg") == true) { contentType = "Content-Type: image/jpg\n\n"; } else { contentType = "Content-Type: \n\n"; } // Write headers out.write(httpHeader.getBytes()); out.write(contentType.getBytes()); // Handle binary file byte[] buffer = new byte[1024]; int readCount = 0; while ((readCount = inFile.read(buffer)) >0) { out.write(buffer, 0, readCount); } } else { p.print(ErrorManager("File doesn't exist.")); } closeAll(); } private Object execJava(String path) throws IOException { String[] pathPart = path.split("/"); // check for no path
if (pathPart.length < 3) { p.println(ErrorManager("Enter fully -qualified-classname after /java.")); p.flush(); closeAll(); return null; } String className = pathPart[2]; if (className.equals("http.Directory")) { path = docRoot + "/" + path; } try { Class c = Class.forName(className); Object o = c.newInstance(); if (o instanceof ServerSideJava) { ((ServerSideJava) o).service(path, out); } else { p.println(ErrorManager("Java class " + className + " not ServerSideJava.")); p.flush(); } } catch (ClassNotFoundExceptio n c) { c.printStackTrace(System.err); p.println(ErrorManager("<p>Cannot execute!!! </p>" + c.toString())); closeAll(); return null; } catch (InstantiationException i) { i.printStackTrace(System.err); p.println(ErrorManager("<p>Cannot e xecute!!! </p>" + i.toString())); closeAll(); return null; } catch (IllegalAccessException il) { il.printStackTrace(System.err); p.println(ErrorManager("<p>Cannot execute!!! </p>" + il.toString())); closeAll(); return null; } closeAll(); return null; } private String indexFileHandler() { File indexFile = new File(docRoot + "/index.htm"); if (indexFile.exists() == true) { return "/index.htm"; }
else { return "/index.html"; } } private void closeAll() throws IOException { out.close(); p.close(); in.close(); buf.close(); socket.close(); } static public String ErrorManager(String contentIn){ String htmlPage = "<html><head><title>Error!!!</title>" + "<body text=#33FFFF bgcolor=#333333>"+ "<h1>Error!!!</h1>" + "<p>" + conte ntIn + "</p>"+ "</body></html>"; return htmlPage; } static public String htmlPage(String contentIn){ String htmlPage = "<html><head><title>Display</title></head>" + "<body text=#33FFFF bgcolor=#333333>" + "<p>" + contentIn + "</p>" + "</body></html>"; return htmlPage; } }