001: /*
002: * UmlGraph class diagram testing framework
003: *
004: * Contibuted by Andrea Aime
005: * (C) Copyright 2005 Diomidis Spinellis
006: *
007: * Permission to use, copy, and distribute this software and its
008: * documentation for any purpose and without fee is hereby granted,
009: * provided that the above copyright notice appear in all copies and that
010: * both that copyright notice and this permission notice appear in
011: * supporting documentation.
012: *
013: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
014: * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
015: * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
016: *
017: * $Id: DotDiff.java,v 1.4 2007/11/27 09:04:22 dds Exp $
018: *
019: */
020:
021: package org.umlgraph.test;
022:
023: import java.io.BufferedReader;
024: import java.io.File;
025: import java.io.FileReader;
026: import java.io.IOException;
027: import java.io.StreamTokenizer;
028: import java.io.StringReader;
029: import java.util.ArrayList;
030: import java.util.HashMap;
031: import java.util.Iterator;
032: import java.util.List;
033: import java.util.Map;
034:
035: public class DotDiff {
036:
037: private List<DotNode> nodes1 = new ArrayList<DotNode>();
038:
039: private List<DotNode> nodes2 = new ArrayList<DotNode>();
040:
041: private List<DotArc> arcs1 = new ArrayList<DotArc>();
042:
043: private List<DotArc> arcs2 = new ArrayList<DotArc>();
044:
045: private List<String> extraLines1 = new ArrayList<String>();
046:
047: private List<String> extraLines2 = new ArrayList<String>();
048:
049: /**
050: * Builds a dot differ on the two files
051: *
052: * @param dotFirst
053: * @param dotSecond
054: * @throws IOException
055: */
056: public DotDiff(File dotFirst, File dotSecond) throws IOException {
057: // gather the lines
058: List<String> lines1 = readGraphLines(dotFirst);
059: List<String> lines2 = readGraphLines(dotSecond);
060:
061: // parse the lines
062: extraLines1 = parseLines(lines1, nodes1, arcs1);
063: extraLines2 = parseLines(lines2, nodes2, arcs2);
064:
065: // diff extra lines
066: for (Iterator<String> it = extraLines1.iterator(); it.hasNext();) {
067: if (extraLines2.remove(it.next()))
068: it.remove();
069: }
070:
071: // diff nodes
072: for (Iterator<DotNode> it = nodes1.iterator(); it.hasNext();) {
073: if (nodes2.remove(it.next()))
074: it.remove();
075: }
076:
077: // diff arcs
078: for (Iterator<DotArc> it = arcs1.iterator(); it.hasNext();) {
079: if (arcs2.remove(it.next()))
080: it.remove();
081: }
082: }
083:
084: /**
085: * Returns true if the dot files are structurally equal, that is, if every
086: * non comment and non header line of the first file appears in the second,
087: * and otherwise.
088: *
089: * @return
090: */
091: public boolean graphEquals() {
092: return (extraLines1.size() + extraLines2.size() + nodes1.size()
093: + nodes2.size() + arcs1.size() + arcs2.size()) == 0;
094: }
095:
096: public List<DotArc> getArcs1() {
097: return arcs1;
098: }
099:
100: public List<DotArc> getArcs2() {
101: return arcs2;
102: }
103:
104: public List<String> getExtraLines1() {
105: return extraLines1;
106: }
107:
108: public List<String> getExtraLines2() {
109: return extraLines2;
110: }
111:
112: public List<DotNode> getNodes1() {
113: return nodes1;
114: }
115:
116: public List<DotNode> getNodes2() {
117: return nodes2;
118: }
119:
120: /**
121: * Reads all relevant lines from the dot file
122: *
123: * @param dotFile
124: * @return
125: * @throws IOException
126: */
127: private List<String> readGraphLines(File dotFile)
128: throws IOException {
129: List<String> lines = new ArrayList<String>();
130: BufferedReader br = null;
131: try {
132: br = new BufferedReader(new FileReader(dotFile));
133:
134: String line;
135: while ((line = br.readLine()) != null) {
136: line = line.trim();
137: if (graphDefinitionLine(line))
138: lines.add(line);
139: }
140: } finally {
141: if (br != null)
142: br.close();
143: }
144:
145: return lines;
146: }
147:
148: /**
149: * Tells if a line is relevant or not (unrelevant lines are headers,
150: * comments, "digraf G {" and the closing "}" (to simplify matters we assume
151: * the file is properly structured).
152: *
153: * @param line
154: * @return
155: */
156: private boolean graphDefinitionLine(String line) {
157: return !(line.startsWith("#") || line.startsWith("//")
158: || line.equals("digraph G {") || line.equals("}"));
159: }
160:
161: private List<String> parseLines(List<String> lines,
162: List<DotNode> nodeList, List<DotArc> arcs)
163: throws IOException {
164: List<String> extraLines = new ArrayList<String>();
165: List<String> arcLines = new ArrayList<String>();
166: Map<String, DotNode> nodes = new HashMap<String, DotNode>();
167: for (String line : lines) {
168: int openBrackedIdx = line.indexOf('[');
169: int closedBracketIdx = line.lastIndexOf(']');
170: int arrowIdx = line.indexOf("->");
171: if (openBrackedIdx < 0 && closedBracketIdx < 0
172: || line.startsWith("edge")
173: || line.startsWith("node"))
174: extraLines.add(line);
175: else if (arrowIdx > 0 && arrowIdx < openBrackedIdx) { // that's an arc
176: arcLines.add(line);
177: } else { // that's a node
178: String attributes = line.substring(openBrackedIdx + 1,
179: closedBracketIdx);
180: Map<String, String> attMap = parseAttributes(attributes);
181: String name = line.substring(0, openBrackedIdx - 1)
182: .trim();
183: String label = attMap.get("label");
184: DotNode node = new DotNode(name, label, attMap, line);
185: nodes.put(name, node);
186: nodeList.add(node);
187: }
188: }
189: for (String line : arcLines) {
190: int openBrackedIdx = line.indexOf('[');
191: int closedBracketIdx = line.lastIndexOf(']');
192: String attributes = line.substring(openBrackedIdx + 1,
193: closedBracketIdx);
194: String[] names = line.substring(0, openBrackedIdx).split(
195: "->");
196: DotNode from = nodes.get(names[0].trim());
197: DotNode to = nodes.get(names[1].trim());
198: if (from == null) {
199: from = new DotNode(names[0], "",
200: new HashMap<String, String>(), "");
201: }
202: if (to == null) {
203: to = new DotNode(names[1], "",
204: new HashMap<String, String>(), "");
205: }
206: arcs.add(new DotArc(from, to, parseAttributes(attributes),
207: line));
208: }
209: return extraLines;
210: }
211:
212: private Map<String, String> parseAttributes(String attributes)
213: throws IOException {
214: Map<String, String> map = new HashMap<String, String>();
215:
216: StreamTokenizer st = new StreamTokenizer(new StringReader(
217: attributes));
218: st.wordChars('_', '_');
219: st.wordChars('\\', '\\');
220: st.wordChars('\'', '\'');
221: int tokenType;
222: boolean isValue = false;
223: String attName = null;
224: String token = null;
225: while ((tokenType = st.nextToken()) != StreamTokenizer.TT_EOF) {
226: switch (tokenType) {
227: case StreamTokenizer.TT_NUMBER:
228: token = "" + st.nval;
229: break;
230: case StreamTokenizer.TT_WORD:
231: case '"':
232: case '\'':
233: token = st.sval;
234: break;
235: }
236: tokenType = st.nextToken();
237:
238: if (isValue) {
239: map.put(attName, token);
240: isValue = false;
241: } else {
242: attName = token;
243: isValue = true;
244: }
245: }
246:
247: return map;
248: }
249:
250: private static class DotNode {
251: String name;
252:
253: String label;
254:
255: Map<String, String> attributes;
256:
257: String line;
258:
259: public DotNode(String name, String label,
260: Map<String, String> attributes, String line) {
261: this .name = name;
262: this .label = label.replace("\n", "\\n");
263: this .attributes = attributes;
264: this .line = line.replace("\n", "\\n");
265: }
266:
267: public boolean equals(Object other) {
268: if (!(other instanceof DotNode))
269: return false;
270:
271: DotNode on = (DotNode) other;
272: if (label == null) // anonymous node
273: return on.label == null && on.name.equals(name)
274: && on.attributes.equals(attributes);
275: else
276: return on.label.equals(label)
277: && on.attributes.equals(attributes);
278: }
279:
280: public int hashCode() {
281: return name.hashCode() + 17 * attributes.hashCode();
282: }
283:
284: public String toString() {
285: return "Node: " + label + "; " + line;
286: }
287:
288: }
289:
290: private static class DotArc {
291: DotNode from;
292:
293: DotNode to;
294:
295: Map<String, String> attributes;
296:
297: String line;
298:
299: public DotArc(DotNode from, DotNode to,
300: Map<String, String> attributes, String line) {
301: this .from = from;
302: this .to = to;
303: this .attributes = attributes;
304: this .line = line.replace("\n", "\\n");
305: }
306:
307: public boolean equals(Object other) {
308: if (!(other instanceof DotArc))
309: return false;
310:
311: DotArc oa = (DotArc) other;
312: return oa.from.equals(from) && oa.to.equals(to)
313: && oa.attributes.equals(attributes);
314: }
315:
316: public int hashCode() {
317: return from.hashCode() + 17
318: * (to.hashCode() + 17 * attributes.hashCode());
319: }
320:
321: public String toString() {
322: return "Arc: " + from.label + " -> " + to.label + "; "
323: + line;
324: }
325: }
326:
327: }
|