/******************************************************************************* * Copyright (c) 2004, 2007, 2011 Phil Muldoon <pkmuldoon@picobot.org>. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Phil Muldoon <pmuldoon@redhat.com> - initial API and implementation *******************************************************************************/ package org.eclipse.linuxtools.internal.changelog.core.formatters; import java.io.File; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.regex.Pattern; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.FindReplaceDocumentAdapter; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.linuxtools.changelog.core.IFormatterChangeLogContrib; import org.eclipse.linuxtools.internal.changelog.core.ChangelogPlugin; import org.eclipse.linuxtools.internal.changelog.core.editors.ChangeLogEditor; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.texteditor.AbstractTextEditor; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.ITextEditor; /** * @author pmuldoon (Phil Muldoon) */ public class GNUFormat implements IFormatterChangeLogContrib { final String line_sep = System.getProperty("line.separator"); //$NON-NLS-1$ final static String TAB = "\t"; // $NON-NLS-1$ @Override public String formatDateLine(String authorName, String authorEmail) { String detail = returnDate() + " " + //$NON-NLS-1$ authorName + " " + "<" + authorEmail + ">" + line_sep + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ line_sep; return detail; } @Override public String mergeChangelog(String dateLine, String functionGuess,String defaultContent, IEditorPart changelog, String changeLogLocation, String fileLocation) { String fileDetail = formatFileDetail(changeLogLocation, fileLocation); IDocument changelog_doc = getDocument(changelog); String function = formatFunction(functionGuess); boolean multipleEntrySuccess = false; boolean forceNewEntry = false; String functionSpacer = " "; // $NON-NLS-1$ if (function.equals(": ")) // $NON-NLS-1$ functionSpacer = ""; // $NON-NLS-1$ /* Fix Bz #366854. Make sure that forceNewEntry is used only * once and then cleared even when the ChangeLog is empty to start with. */ if(changelog instanceof ChangeLogEditor) { ChangeLogEditor editor = (ChangeLogEditor)changelog; forceNewEntry = editor.isForceNewLogEntry(); editor.setForceNewLogEntry(false); } if (changelog_doc.getLength() > 0) { int offset_start = findChangeLogEntry(changelog_doc, dateLine); int offset_end = dateLine.length(); boolean foundFunction = false; //if the prepare change action determines it requires a new entry, we force //a new entry by changing the offset_start and change the corresponding field //of the editor back to false to prevent subsequent function change log being //written to a new entry again. if (forceNewEntry) { offset_start = -1; } if (offset_start != -1) { int nextChangeEntry = findChangeLogPattern(changelog_doc, offset_start + dateLine.length()); int functLogEntry = offset_start + dateLine.length(); final int numLines = changelog_doc.getNumberOfLines(); while (functLogEntry < nextChangeEntry) { int lineNum = 0; String entry = ""; // $NON-NLS-1$ try { lineNum = changelog_doc.getLineOfOffset(functLogEntry); entry = changelog_doc.get(functLogEntry, changelog_doc.getLineLength(lineNum)); } catch (BadLocationException e) { // Should never get here } // Look to see if entry already exists for file (will be preceded by "*") final int entryStart = entry.indexOf("* " + fileDetail); if (entryStart >= 0) { foundFunction = true; } else if (foundFunction && isFileLine(entry)) { functLogEntry--; break; } if (foundFunction) { foundFunction = true; // Check for the case where the default content (e.g. new or removed file) // is being caught again because user has prepared the ChangeLog more than once. // In such a case, just return. We don't need to repeat ourselves. if (defaultContent.length() > 0 && entry.lastIndexOf(defaultContent) > 0) { return ""; // $NON-NLS-1$ } final int nextFunctLoc; if (entryStart > 0) { nextFunctLoc = functLogEntry + entryStart + fileDetail.length() + 2; } else { nextFunctLoc = functLogEntry; } String nextFunc = ""; // $NON-NLS-1$ try { final int lineEnd; if (lineNum < numLines - 1) { lineEnd = changelog_doc.getLineOffset(lineNum+1)-1; } else { lineEnd = changelog_doc.getLength(); } nextFunc = changelog_doc.get(nextFunctLoc, lineEnd - nextFunctLoc); } catch (BadLocationException e1) { // Should never get here } if (nextFunc.trim().startsWith(function)) { return ""; // $NON-NLS-1$ } } try { functLogEntry += changelog_doc.getLineLength(lineNum); } catch (BadLocationException e1) { // Should never get here } } if (functLogEntry >= nextChangeEntry) { functLogEntry = nextChangeEntry - 1; try { // Get rid of some potential lines containing whitespace only. functLogEntry = removeWhitespaceOnlyLines(changelog_doc, functLogEntry); while (changelog_doc.get(functLogEntry, 1).equals("\n")) // $NON-NLS-1$ functLogEntry--; } catch (BadLocationException e) { // TODO Auto-generated catch block e.printStackTrace(); } functLogEntry++; } if (offset_start != -1) { if (foundFunction) { try { if (!function.equals(": ")) // $NON-NLS-1$ changelog_doc.replace(functLogEntry, 0, "\n" + TAB // $NON-NLS-1$ + function + " "); // $NON-NLS-1$ else changelog_doc.replace(functLogEntry, 0, "\n" + TAB // $NON-NLS-1$ ); } catch (BadLocationException e) { // TODO Auto-generated catch block e.printStackTrace(); } ITextEditor edit = (ITextEditor) changelog; if (!function.equals(": ")) // $NON-NLS-1$ edit.selectAndReveal(functLogEntry + function.length() + 3, 0); else edit.selectAndReveal(functLogEntry + function.length() , 0); multipleEntrySuccess = true; } else { try { changelog_doc.replace(offset_end, 0, TAB + "* " + fileDetail + functionSpacer // $NON-NLS-1$ + function + functionSpacer + defaultContent + "\n"); //$NON-NLS-1$ } catch (BadLocationException e) { // TODO Auto-generated catch block e.printStackTrace(); } ITextEditor edit = (ITextEditor) changelog; edit.selectAndReveal(offset_end + fileDetail.length() + function.length() +functionSpacer.length()*2 + 3 + defaultContent.length(), 0); multipleEntrySuccess = true; } } } } if (!multipleEntrySuccess) { try { if (changelog_doc.getLength() > 0) { changelog_doc.replace(0, 0, "\n\n"); //$NON-NLS-1$ } changelog_doc.replace(0, 0, dateLine + TAB + "* " + fileDetail // $NON-NLS-1$ + functionSpacer+function+functionSpacer+defaultContent); ITextEditor edit = (ITextEditor) changelog; edit.selectAndReveal(dateLine.length() + fileDetail.length() + function.length() + functionSpacer.length()*2 + 3 + defaultContent.length(), 0); } catch (BadLocationException e) { e.printStackTrace(); } } return ""; // $NON-NLS-1$ } private boolean isFileLine(String entry) { return Pattern.matches("\\s*\\* \\S+:.*", entry.trim()); } /** * Remove any empty lines (i.e. lines only containing whitespace) between * <code>offset</code> and index backed-up until a '\n' preceded by some non-whitespace * character is reached. Whitespace will be merged to '\n\n'. For example * consider the following string "(main): Removed.\n\t\ \n\n\t\n" and * <code>offset</code> pointing to the last '\n'. This string would be * changed to: "(main): Removed.\n\n". * * @param changelogDoc * @param offset * @return The new offset. */ private int removeWhitespaceOnlyLines(IDocument changelogDoc, int offset) { int initialOffset = offset; int backedUpOffset = offset; char charAtOffset; try { charAtOffset = changelogDoc.get(offset, 1).charAt(0); } catch (BadLocationException e) { e.printStackTrace(); return offset; } while( backedUpOffset > 0 && (charAtOffset == '\n' || charAtOffset == '\t' || charAtOffset == ' ') ) { backedUpOffset--; try { charAtOffset = changelogDoc.get(backedUpOffset, 1).charAt(0); } catch (BadLocationException e) { e.printStackTrace(); break; } } if ( (initialOffset - backedUpOffset) > 2 ) { try { int replaceLength = (initialOffset - backedUpOffset - 2); changelogDoc.replace(backedUpOffset + 2, replaceLength, ""); // change offset accordingly offset -= replaceLength; } catch (BadLocationException e) { // exception should have been thrown earlier if that's // really a bad location... } } return offset; } private IWorkspaceRoot getWorkspaceRoot() { return ResourcesPlugin.getWorkspace().getRoot(); } private String formatFileDetail(String changeLogLocation, String editorFileLocation) { // Format Path. Is a full path specified, or just file name? IWorkspaceRoot myWorkspaceRoot = getWorkspaceRoot(); String WorkspaceRoot = myWorkspaceRoot.getLocation().toOSString(); String changeLogLocNoRoot = ""; // $NON-NLS-1$ String editorFileLocNoRoot = ""; // $NON-NLS-1$ if (changeLogLocation.lastIndexOf(WorkspaceRoot) >= 0) { changeLogLocNoRoot = changeLogLocation.substring(changeLogLocation .lastIndexOf(WorkspaceRoot) + WorkspaceRoot.length(), changeLogLocation.length()); } else changeLogLocNoRoot = changeLogLocation; if (editorFileLocation.lastIndexOf(WorkspaceRoot) >= 0) { editorFileLocNoRoot = editorFileLocation.substring( editorFileLocation.lastIndexOf(WorkspaceRoot), editorFileLocation.lastIndexOf(WorkspaceRoot) + WorkspaceRoot.length()); } else editorFileLocNoRoot = editorFileLocation; File changelogLocation = new File(changeLogLocNoRoot); File fileLocation = new File(editorFileLocNoRoot); File reversePath = fileLocation.getParentFile(); String reversePathb = ""; // $NON-NLS-1$ while (reversePath.getParentFile() != null) { if (reversePath.compareTo(changelogLocation.getParentFile()) == 0) { break; } reversePath = reversePath.getParentFile(); } if (reversePath != null) { reversePathb = fileLocation.toString().substring( reversePath.toString().length() + 1, fileLocation.toString().length()); } return reversePathb; } private int findChangeLogPattern(IDocument changelogDoc, int startOffset) { // find the "pattern" of a changelog entry. Not a specific one, // but one that "looks" like an entry int nextEntry = startOffset; int lineNum = 0; String entry = ""; // $NON-NLS-1$ while (nextEntry < changelogDoc.getLength()) { try { // Get the line of interest in the changelog document lineNum = changelogDoc.getLineOfOffset(nextEntry); entry = changelogDoc.get(nextEntry, changelogDoc .getLineLength(lineNum)); // Attempt to find date pattern on line if (matchDatePattern(entry)) { //nextDate -= entry.length()+1; break; } // If no date matches, move to the next line nextEntry += changelogDoc.getLineLength(lineNum); } catch (BadLocationException e) { ChangelogPlugin.getDefault().getLog().log( new Status(IStatus.ERROR, ChangelogPlugin.PLUGIN_ID, IStatus.ERROR, e .getMessage(), e )); } } return nextEntry; } private boolean matchDatePattern(String text) { // Set up patterns for looking for the next date in the changelog SimpleDateFormat isoDate = new SimpleDateFormat("yyyy-MM-dd"); // $NON-NLS-1$ // Try to find next Date bounded changelog entry by parsing date patterns // First start with an ISO date try { Date ad = isoDate.parse(text); if (ad != null) { return true; } } catch (ParseException e) { // We don't really care on exception; it just means it could not parse a date on that line } return false; } private int findChangeLogEntry(IDocument changelogDoc, String entry) { FindReplaceDocumentAdapter findDocumentAptd = new FindReplaceDocumentAdapter( changelogDoc); IRegion region = null; try { region = findDocumentAptd.find(0, entry, true, false,/*whole world */ false, true); } catch (BadLocationException e) { ChangelogPlugin.getDefault().getLog().log( new Status(IStatus.ERROR, ChangelogPlugin.PLUGIN_ID, IStatus.ERROR, e .getMessage(), e )); return -1; } if (region != null) { // If the user's entry is not at the beginning of the file, // make a new entry. return region.getOffset() > 0 ? -1 : 0; } else return -1; } private String formatFunction(String function) { // If Function Guess is true, and Function Guess has found something if (function.length() > 0) { return "(" + function + "):"; // $NON-NLS-1$ // $NON-NLS-2$ } else { return ": "; //$NON-NLS-1$ } } public IDocument getDocument(IEditorPart currentEditor) { AbstractTextEditor castEditor = (AbstractTextEditor) currentEditor; IDocumentProvider provider = castEditor.getDocumentProvider(); return provider.getDocument(castEditor.getEditorInput()); } private String returnDate() { SimpleDateFormat date_Format = new SimpleDateFormat("yyyy-MM-dd"); //$NON-NLS-1$ return date_Format.format(new Date()); } }