Java tutorial
/******************************************************************************* * Copyright (c) 2008 xored software, Inc. * * 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: * xored software, Inc. - initial API and Implementation (Alex Panchenko) *******************************************************************************/ package com.aptana.formatter; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.compare.contentmergeviewer.TokenComparator; import org.eclipse.compare.rangedifferencer.IRangeComparator; import org.eclipse.compare.rangedifferencer.RangeDifference; import org.eclipse.compare.rangedifferencer.RangeDifferencer; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.ui.internal.editors.text.EditorsPlugin; import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; import org.eclipse.ui.texteditor.ChainedPreferenceStore; import com.aptana.core.logging.IdeLog; import com.aptana.formatter.epl.FormatterPlugin; import com.aptana.formatter.nodes.IFormatterNode; import com.aptana.formatter.nodes.IFormatterTextNode; @SuppressWarnings("restriction") public class FormatterUtils { public static boolean isSpace(char c) { return c == '\t' || c == ' '; } public static boolean isLineSeparator(char c) { return c == '\r' || c == '\n'; } public static boolean isNewLine(IFormatterNode node) { if (node instanceof IFormatterTextNode) { final IFormatterTextNode textNode = (IFormatterTextNode) node; final IFormatterDocument document = node.getDocument(); int start = textNode.getStartOffset(); if (start < textNode.getEndOffset()) { if (document.charAt(start) == '\n') { ++start; } else if (document.charAt(start) == '\r') { ++start; if (start < textNode.getEndOffset() && document.charAt(start) == '\n') { ++start; } } else { return false; } } while (start < textNode.getEndOffset()) { if (!isSpace(document.charAt(start))) { return false; } ++start; } return true; } return false; } /** * @param node * @return */ public static boolean isEmptyText(IFormatterNode node) { if (node instanceof IFormatterTextNode) { final String text = ((IFormatterTextNode) node).getText(); for (int i = 0; i < text.length(); ++i) { char c = text.charAt(i); if (!Character.isWhitespace(c)) { return false; } } } return true; } /** * @since 2.0 */ public static IFormatterNode[] toTextNodeArray(List<IFormatterNode> list) { if (list != null) { return list.toArray(new IFormatterNode[list.size()]); } else { return null; } } /** * Returns the editor's tab width as set in the given editor's-specific preferences. In case a value is not found, * or the preferences are null, the workspace settings for the editor's tab-width will be returned. * * @param preferenceStore * The editor's preferences store; Null, in case a workspace setting is needed. * @return The editor's tab-width * @deprecated Use EditorUtil.getSpaceIndentSize(preferencesQualifier) */ public static int getEditorTabWidth(IPreferenceStore preferenceStore) { IPreferenceStore prefs = getChainedPreferences(preferenceStore, EditorsPlugin.getDefault().getPreferenceStore()); return prefs.getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH); } /** * Returns the editor's setting for 'Insert Spaces for Tabs'. This setting exists in the general 'Text Editors' * preference page. * * @param preferenceStore * The editor's preferences store; Null, in case a workspace setting is needed. * @return The value for the generic editor's 'Insert Spaces for Tabs' */ public static boolean isInsertSpacesForTabs(IPreferenceStore preferenceStore) { IPreferenceStore prefs = getChainedPreferences(preferenceStore, EditorsPlugin.getDefault().getPreferenceStore()); return prefs.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SPACES_FOR_TABS); } /** * Generate OFF-ON regions for the given map of comments, using the on-pattern and the off-pattern.<br> * The method will traverse the comments map and return a {@link java.util.Set} of valid regions that should be * skipped in case the Off/On was enabled. * * @param commentsMap * A {@link LinkedHashMap} of comments, ordered by their appearance in the document. The map key is the * start offset of the comment-string that it's mapping to. * @param onPattern * The 'ON' pattern (as defined in the formatter preferences). * @param offPattern * The 'OFF' pattern (as defined in the formatter preferences). * @param documentEndOffset * The end offset of the document. * @return A {@link List} of {@link IRegion} instances. */ public static List<IRegion> resolveOnOffRegions(LinkedHashMap<Integer, String> commentsMap, Pattern onPattern, Pattern offPattern, int documentEndOffset) { List<IRegion> regions = new ArrayList<IRegion>(); boolean isOn = true; int start = -1; int end = -1; int startCommentBeginOffset = -1; for (Integer offset : commentsMap.keySet()) { String comment = commentsMap.get(offset); if (isOn) { // Look for an 'OFF' pattern Matcher matcher = offPattern.matcher(comment); if (matcher.find()) { start = matcher.start(); startCommentBeginOffset = offset; isOn = false; } } else { // Look for an 'ON' pattern Matcher matcher = onPattern.matcher(comment); if (matcher.find()) { end = matcher.end(); int regionStart = startCommentBeginOffset + start; regions.add(new Region(regionStart, offset + end - regionStart)); // reset the values isOn = true; start = -1; end = -1; startCommentBeginOffset = -1; } } } if (start > -1 && end < 0) { // We need to add a region that will go all the way to the end of the document start = startCommentBeginOffset + start; regions.add(new Region(start, documentEndOffset - start)); } return regions; } /** * Replace the output OFF/ON formatting regions with the original content from the input. * * @param input * @param output * @param inputOffOnRegions * A non null list of OFF/ON regions that were found on the input content. * @param outputOffOnRegions * A list of OFF/ON regions that were found on the output content. This list may be null in case of an * error. * @return A new output string that contains the original regions content from the input string between the Off and * On formatter tags. * @throws CoreException * In case the given outputOnOffRegions was null, or in case the size of the output-regions does not * match the size of the input-regions. */ public static String applyOffOnRegions(String input, String output, List<IRegion> inputOffOnRegions, List<IRegion> outputOffOnRegions) throws CoreException { // Validate the inputs... if (outputOffOnRegions == null || inputOffOnRegions.size() != outputOffOnRegions.size()) { IdeLog.logError(FormatterPlugin.getDefault(), outputOffOnRegions == null ? "Output OFF/ON regions was null" //$NON-NLS-1$ : "Output OFF/ON regions do not match in size to the input regions", //$NON-NLS-1$ IDebugScopes.DEBUG); throw new CoreException(new Status(IStatus.ERROR, FormatterPlugin.PLUGIN_ID, "Error applying the formatter ON-OFF regions")); //$NON-NLS-1$ } StringBuilder outputBuffer = new StringBuilder(output); for (int i = inputOffOnRegions.size() - 1; i >= 0; i--) { IRegion inputRegion = inputOffOnRegions.get(i); IRegion outputRegion = outputOffOnRegions.get(i); String originalString = input.substring(inputRegion.getOffset(), inputRegion.getOffset() + inputRegion.getLength()); outputBuffer.replace(outputRegion.getOffset(), outputRegion.getOffset() + outputRegion.getLength(), originalString); } return outputBuffer.toString(); } /** * Compute and return an array of {@link RangeDifference}s for two given strings. * * @param left * @param right * @return An array of {@link RangeDifference}s. */ public static RangeDifference[] getDiff(String left, String right) { IRangeComparator leftRangeComparator = new TokenComparator(left); IRangeComparator rightRangeComparator = new TokenComparator(right); return RangeDifferencer.findRanges(leftRangeComparator, rightRangeComparator); } /** * A utility function to log the first difference area that two strings have.<br> * The function scans char-by-char, and on the first difference it try to log 10 chars before and 40 chars after the * diff. * * @param input * - Expected content * @param output * - Matched content */ public static void logDiff(String input, String output) { // find the first offset that has a change and log it. int length = Math.min(input.length(), output.length()); int offset = 0; for (; offset < length; offset++) { if (input.charAt(offset) != output.charAt(offset)) { // Found a change break; } } // log 10 characters back and 40 ahead StringBuilder message = new StringBuilder(100); message.append("Formatted content differ around position "); //$NON-NLS-1$ message.append(offset); message.append("\nINPUT:\n"); //$NON-NLS-1$ int start = Math.max(0, offset - 10); int end = Math.min(offset + 40, input.length()); message.append(input.substring(start, end)); message.append("\nOUTPUT:\n"); //$NON-NLS-1$ start = Math.max(0, offset - 10); end = Math.min(offset + 40, output.length()); message.append(output.substring(start, end)); IdeLog.logError(FormatterPlugin.getDefault(), message.toString()); } /** * @param optionalStore * - An optional preference store that may contain a searched key (can be null) * @param defaultStore * - A non-null preference store that will be used in case the optional store is null or does not contain * the searched key * @return A chained preference store for the given preferences stores. In case the optional store was null, the * given default store is returned. */ private static IPreferenceStore getChainedPreferences(IPreferenceStore optionalStore, IPreferenceStore defaultStore) { IPreferenceStore prefs; if (optionalStore == null) { prefs = defaultStore; } else { IPreferenceStore[] preferenceStores = new IPreferenceStore[] { optionalStore, defaultStore }; prefs = new ChainedPreferenceStore(preferenceStores); } return prefs; } }