/plugins/JDiffPlugin/tags/jdiffplugin-2_1_0/jdiff/util/patch/normal/Patch.java

#
Java | 395 lines | 230 code | 15 blank | 150 comment | 64 complexity | a0740365f4381aaa50f3cad292fcba7e MD5 | raw file

✨ Summary
  1. package jdiff.util.patch.normal;
  2. import java.util.ArrayList;
  3. import java.util.StringTokenizer;
  4. /**
  5. * This Library implements a simple patch algorithm which is able to process
  6. * the output of diff in normal format.<br>
  7. * <br>
  8. * This class implements the algorithm.<br>
  9. * <br>
  10. * The Method you're probably looking for is PatchUtils.patch(diff, target).<br>
  11. * <br>
  12. * Example usage in comparison to GNU patch:<br>
  13. * GNU patch: "patch target < diff"<br>
  14. * jPatchLib: "PatchUtils.patch(diff, target"<br>
  15. *
  16. *
  17. * see <a href="http://www.gnu.org/software/diffutils/manual/html_mono/diff.html#Normal">http://www.gnu.org/software/diffutils/manual/html_mono/diff.html#Normal</a>
  18. *
  19. * <pre>
  20. * Copyright (c) 2007 Dominik Schulz
  21. *
  22. * This file is part of jPatchLib.
  23. *
  24. * jPatchLib is free software; you can redistribute it and/or modify
  25. * it under the terms of the GNU General Public License as published by
  26. * the Free Software Foundation; either version 2 of the License, or
  27. * (at your option) any later version.
  28. *
  29. * jPatchLib is distributed in the hope that it will be useful,
  30. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  31. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  32. * GNU General Public License for more details.
  33. *
  34. * You should have received a copy of the GNU General Public License
  35. * along with jPatchLib; if not, write to the Free Software
  36. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  37. * </pre>
  38. *
  39. * @author Dominik
  40. */
  41. /*
  42. This comment is covered under the GNU Free Documentation License.
  43. Detailed Description of Normal Format
  44. The normal output format consists of one or more hunks of differences; each hunk
  45. shows one area where the files differ. Normal format hunks look like this:
  46. change-command
  47. < from-file-line
  48. < from-file-line...
  49. ---
  50. > to-file-line
  51. > to-file-line...
  52. There are three types of change commands. Each consists of a line number or
  53. comma-separated range of lines in the first file, a single character indicating
  54. the kind of change to make, and a line number or comma-separated range of lines
  55. in the second file. All line numbers are the original line numbers in each file.
  56. The types of change commands are:
  57. lar
  58. Add the lines in range r of the second file after line l of the first file.
  59. For example, 8a12,15 means append lines 12-15 of file 2 after line 8 of file
  60. 1; or, if changing file 2 into file 1, delete lines 12-15 of file 2.
  61. fct
  62. Replace the lines in range f of the first file with lines in range t of the
  63. second file. This is like a combined add and delete, but more compact. For
  64. example, 5,7c8,10 means change lines 5-7 of file 1 to read as lines 8-10 of
  65. file 2; or, if changing file 2 into file 1, change lines 8-10 of file 2 to
  66. read as lines 5-7 of file 1.
  67. rdl
  68. Delete the lines in range r from the first file; line l is where they would
  69. have appeared in the second file had they not been deleted. For example,
  70. 5,7d3 means delete lines 5-7 of file 1; or, if changing file 2 into file 1,
  71. append lines 5-7 of file 1 after line 3 of file 2.
  72. */
  73. public class Patch {
  74. /**
  75. * Checks if the string starts with a number.
  76. * Use to check if the string (line) is the beginning of a new chunk.
  77. * @param str the string to be checked
  78. * @return true if the string starts with a number.
  79. */
  80. public static boolean startWithNumber( String str ) {
  81. char c = str.charAt( 0 );
  82. return ( '\u0030' <= c && c <= '\u0039' );
  83. }
  84. /**
  85. * Get the single chunks from the patch.
  86. * @param patchSrc the patch as a string array
  87. * @return the array of chunks from the patch file.
  88. */
  89. public static Chunk[] getChunks( String[] patchSrc ) {
  90. // count the chunks
  91. int numChunks = 0;
  92. for ( int i = 0; i < patchSrc.length; i++ ) {
  93. if ( startWithNumber( patchSrc[ i ] ) ) {
  94. numChunks++;
  95. }
  96. }
  97. // split into chunks (start at (some number) to (some number)).
  98. Chunk[] chunks = new Chunk[ numChunks ];
  99. numChunks = -1; // must be -1 because in the next step its incremented
  100. ArrayList<String> fromBuf = new ArrayList<String>();
  101. ArrayList<String> toBuf = new ArrayList<String>();
  102. char opBuf = ' ';
  103. int from1buf = -1;
  104. int from2buf = -1;
  105. int to1buf = -1;
  106. int to2buf = -1;
  107. for ( int i = 0; i < patchSrc.length; i++ ) {
  108. if ( startWithNumber( patchSrc[ i ] ) ) {
  109. // save the old from buffers
  110. if ( numChunks > -1 ) {
  111. // char op, int from1, int from2, int to1, int to2, String[] patch, String[] target
  112. chunks[ numChunks ] = new Chunk( opBuf, from1buf, from2buf, to1buf, to2buf, fromBuf
  113. .toArray( new String[ 1 ] ), toBuf.toArray( new String[ 1 ] ) );
  114. }
  115. numChunks++; // increment to the next chunk
  116. fromBuf = new ArrayList<String>(); // reset the arrayLists
  117. toBuf = new ArrayList<String>();
  118. // parse the change-command line
  119. StringTokenizer tok1;
  120. if ( patchSrc[ i ].contains( "a" ) ) {
  121. tok1 = new StringTokenizer( patchSrc[ i ], "a" );
  122. opBuf = 'a';
  123. }
  124. else if ( patchSrc[ i ].contains( "c" ) ) {
  125. tok1 = new StringTokenizer( patchSrc[ i ], "c" );
  126. opBuf = 'c';
  127. }
  128. else if ( patchSrc[ i ].contains( "d" ) ) {
  129. tok1 = new StringTokenizer( patchSrc[ i ], "d" );
  130. opBuf = 'd';
  131. }
  132. else
  133. tok1 = new StringTokenizer( patchSrc[ i ], "a" );
  134. String fromRange = tok1.nextToken();
  135. String toRange = tok1.nextToken();
  136. log( "fromRange: " + fromRange );
  137. log( "toRange: " + toRange );
  138. StringTokenizer tokFrom = new StringTokenizer( fromRange, "," );
  139. String from1 = tokFrom.nextToken();
  140. log( "From1: " + from1 );
  141. String from2 = "0";
  142. if ( tokFrom.hasMoreTokens() ) {
  143. from2 = tokFrom.nextToken();
  144. log( "From2: " + from2 );
  145. }
  146. StringTokenizer tokTo = new StringTokenizer( toRange, "," );
  147. String to1 = tokTo.nextToken();
  148. log( "To1: " + to1 );
  149. String to2 = "0";
  150. if ( tokTo.hasMoreTokens() ) {
  151. to2 = tokTo.nextToken();
  152. log( "To2: " + to2 );
  153. }
  154. from1buf = Integer.parseInt( from1 );
  155. from2buf = Integer.parseInt( from2 );
  156. to1buf = Integer.parseInt( to1 );
  157. to2buf = Integer.parseInt( to2 );
  158. if ( chunks[ numChunks ] != null )
  159. log( "--- Chunk Name: " + chunks[ numChunks ].getName() );
  160. }
  161. else if ( patchSrc[ i ].startsWith( "<" ) ) {
  162. String line = "";
  163. if ( patchSrc[ i ].length() > 2 )
  164. line = patchSrc[ i ].substring( 2 ); // cut off the <
  165. log( "< " + line );
  166. fromBuf.add( line );
  167. }
  168. else if ( patchSrc[ i ].startsWith( ">" ) ) {
  169. String line = "";
  170. if ( patchSrc[ i ].length() > 2 )
  171. line = patchSrc[ i ].substring( 2 ); // cut off the >
  172. log( "> " + line );
  173. toBuf.add( line );
  174. }
  175. else if ( patchSrc[ i ].startsWith( "---" ) ) {
  176. log( "Got separation line: " + patchSrc[ i ] );
  177. }
  178. else {
  179. log( "Ignoring: " + patchSrc[ i ] );
  180. }
  181. }
  182. // finalize
  183. if ( numChunks > -1 ) {
  184. chunks[ numChunks ] = new Chunk( opBuf, from1buf, from2buf, to1buf, to2buf, fromBuf.toArray( new String[ 1 ] ),
  185. toBuf.toArray( new String[ 1 ] ) );
  186. }
  187. return chunks;
  188. }
  189. /**
  190. * Apply the chunks from the chunks array to the target.
  191. * @param targetSrc the target to be patched
  192. * @param chunks the chunks to apply
  193. * @return The patched targetSrc array.
  194. */
  195. public static String[] applyChunks( String[] targetSrc, Chunk[] chunks ) {
  196. // apply each chunk
  197. log( "--- Now applying the Chunks: ---" );
  198. log( "--- Target Text before: ---" );
  199. log( StringTools.arrayToString( targetSrc ) );
  200. /*
  201. * Some words about the offset:
  202. * This will adjust the following chunk operations so that they match the right lines.
  203. * Without this there could be errors after the first chunk was applied, if this changed
  204. * the number of lines. So we keep track of how many lines were added and/or removed and
  205. * adjust the operations by this number of lines.
  206. */
  207. int offset = 0;
  208. for ( int i = 0; i < chunks.length; i++ ) {
  209. // apply this chunk
  210. Chunk c = chunks[ i ];
  211. if ( c.getOp() == 'a' ) {
  212. // This will handle an "add" chunk
  213. offset -= c.getTarget().length;
  214. targetSrc = add( c.getFrom1(), c.getTarget(), targetSrc, offset );
  215. log( "--- Applied an ADD Chunk: ---" );
  216. log( c.getName() );
  217. log( StringTools.arrayToString( targetSrc ) );
  218. }
  219. else if ( c.getOp() == 'c' ) {
  220. // This will handle an "change" chunk
  221. targetSrc = change( c.getFrom1(), c.getFrom2(), c.getTo1(), c.getTo2(), c.getTarget(), targetSrc, offset );
  222. log( "--- Applied an CHANGE Chunk: ---" );
  223. log( c.getName() );
  224. log( StringTools.arrayToString( targetSrc ) );
  225. }
  226. else if ( c.getOp() == 'd' ) {
  227. // This will handle an "delete" chunk
  228. targetSrc = delete( c.getFrom1(), c.getFrom2(), targetSrc, offset );
  229. offset += c.getFrom2() - c.getFrom1() + 1;
  230. log( "--- Applied an DELETE Chunk: ---" );
  231. log( c.getName() );
  232. log( StringTools.arrayToString( targetSrc ) );
  233. }
  234. }
  235. log( "--- Done applying the Chunks: ---" );
  236. log( "--- Target Text after: ---" );
  237. log( StringTools.arrayToString( targetSrc ) );
  238. return targetSrc;
  239. }
  240. /**
  241. * This is like the patch utility from GNU diff/patch.
  242. * @param patchText The output from diff
  243. * @param targetText The text on which the diff will be applied
  244. * @return the result of the patching
  245. */
  246. public static String patchNormal( String patchText, String targetText ) {
  247. String[] patchSrc = StringTools.stringToArray( patchText );
  248. String[] targetSrc = StringTools.stringToArray( targetText );
  249. Chunk[] chunks = getChunks( patchSrc );
  250. return StringTools.arrayToString( applyChunks( targetSrc, chunks ) );
  251. }
  252. /**
  253. * This applys a chunk of the type delete
  254. * @param fromLine the first line to delete
  255. * @param toLine the last line to delete
  256. * @param baseText the text to be patched
  257. * @param offset the offset. see ... uhm, somewhere else
  258. * @return the modified baseText
  259. */
  260. private static String[] delete( int fromLine, int toLine, String[] baseText, int offset ) {
  261. // check input
  262. if ( fromLine < 0 || toLine < 0 || baseText == null ) {
  263. log( "### delete() - Argument ERROR" );
  264. return baseText;
  265. }
  266. // adjust from and to
  267. fromLine = fromLine - offset;
  268. toLine = toLine - offset;
  269. log( "--- Offset: " + offset );
  270. log( "--- Base Text before: ---" );
  271. log( StringTools.arrayToString( baseText ) );
  272. log( "--- fromLine: " + fromLine + ", toLine: " + toLine );
  273. ArrayList<String> buff = new ArrayList<String>();
  274. for ( int i = 0; i < ( fromLine - 1 ) && i < baseText.length; i++ ) {
  275. buff.add( baseText[ i ] );
  276. }
  277. // just "drop" the deleted lines
  278. for ( int j = toLine; j < baseText.length; j++ ) {
  279. buff.add( baseText[ j ] );
  280. }
  281. return buff.toArray( new String[ 1 ] );
  282. }
  283. /**
  284. * This applys a chunk of the type change.
  285. * @param fromLine
  286. * @param toLine
  287. * @param to1
  288. * @param to2
  289. * @param changeTo
  290. * @param baseText
  291. * @param offset
  292. * @return the modified baseText
  293. */
  294. private static String[] change( int fromLine, int toLine, int to1, int to2, String[] changeTo, String[] baseText, int offset ) {
  295. log( "######### CHANGE #########" );
  296. // check input
  297. if ( fromLine < 0 || toLine < 0 || baseText == null || changeTo == null ) {
  298. log( "### change() - Argument ERROR" );
  299. return baseText;
  300. }
  301. // adjust from and to
  302. fromLine = fromLine - offset;
  303. toLine = toLine - offset;
  304. log( "--- Offset: " + offset );
  305. log( "--- Base Text before: ---" );
  306. log( StringTools.arrayToString( baseText ) );
  307. log( "--- ChangeTo Text before: ---" );
  308. log( StringTools.arrayToString( changeTo ) );
  309. log( "--- fromLine: " + fromLine + ", toLine: " + toLine + ", changeTo.length: " + changeTo.length );
  310. ArrayList<String> buff = new ArrayList<String>();
  311. int i = 0;
  312. for ( ; i < ( fromLine - 1 ) && i < baseText.length; i++ ) { // stop _before_ the line(s) that has(have) to be changed
  313. log( "--- change() - Adding(1): " + baseText[ i ] );
  314. buff.add( baseText[ i ] );
  315. }
  316. for ( int j = 0; j < changeTo.length; j++ ) {
  317. log( "--- change() - Adding(2): " + changeTo[ j ] );
  318. buff.add( changeTo[ j ] );
  319. i++;
  320. }
  321. // fix the current line, this may have change due to insertion or removal of lines
  322. int linesRemoved = fromLine;
  323. if ( fromLine > 1 )
  324. linesRemoved = fromLine - 1;
  325. if ( toLine > 0 )
  326. linesRemoved = toLine - ( fromLine - 1 );
  327. int linesInserted = 1; // = 1?
  328. if ( to2 > 0 ) {
  329. linesInserted = to2 - to1;
  330. }
  331. log( "--- change() - Ajusting i by: " + ( linesRemoved - linesInserted ) );
  332. i = i + ( linesRemoved - linesInserted );
  333. for ( int j = i; j < baseText.length; j++ ) {
  334. log( "--- change() - Adding(3): " + baseText[ j ] );
  335. buff.add( baseText[ j ] );
  336. }
  337. return buff.toArray( new String[ 1 ] );
  338. }
  339. /**
  340. * Apply an add chunk.
  341. * @param line
  342. * @param insertion
  343. * @param baseText
  344. * @param offset
  345. * @return the modified baseText
  346. */
  347. private static String[] add( int line, String[] insertion, String[] baseText, int offset ) {
  348. // check input
  349. if ( line < 0 || baseText == null || insertion == null ) {
  350. log( "### add(line: " + line + ", insertion: " + insertion + ", baseText: " + baseText + ") - Argument ERROR" );
  351. return baseText;
  352. }
  353. // adjust from and to
  354. line = line - offset;
  355. log( "--- Offset: " + offset );
  356. ArrayList<String> buff = new ArrayList<String>();
  357. int i = 0;
  358. for ( ; i < line && i < baseText.length; i++ ) {
  359. buff.add( baseText[ i ] );
  360. }
  361. for ( int j = 0; j < insertion.length; j++ ) {
  362. buff.add( insertion[ j ] );
  363. }
  364. for ( int j = i; j < baseText.length; j++ ) {
  365. buff.add( baseText[ j ] );
  366. }
  367. return buff.toArray( new String[ 1 ] );
  368. }
  369. private static final boolean DEBUG = false;
  370. private static void log( String logLine ) {
  371. if ( DEBUG ) {
  372. System.out.println( logLine );
  373. }
  374. }
  375. }