1 /******************************************************************************* 2 * Copyright (c) 2000, 2017 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.corext.util; 15 16 import java.util.Map; 17 18 import org.eclipse.core.runtime.Assert; 19 20 import org.eclipse.text.edits.TextEdit; 21 22 import org.eclipse.jface.text.BadLocationException; 23 import org.eclipse.jface.text.Document; 24 import org.eclipse.jface.text.IRegion; 25 26 import org.eclipse.jdt.core.IJavaProject; 27 import org.eclipse.jdt.core.JavaCore; 28 import org.eclipse.jdt.core.ToolFactory; 29 import org.eclipse.jdt.core.dom.ASTNode; 30 import org.eclipse.jdt.core.dom.BodyDeclaration; 31 import org.eclipse.jdt.core.dom.Expression; 32 import org.eclipse.jdt.core.dom.Statement; 33 import org.eclipse.jdt.core.formatter.CodeFormatter; 34 import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants; 35 36 import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin; 37 38 public class CodeFormatterUtil { 39 40 /** 41 * Creates a string that represents the given number of indentation units. 42 * The returned string can contain tabs and/or spaces depending on the core formatter preferences. 43 * 44 * @param indentationUnits 45 * the number of indentation units to generate 46 * @param project 47 * the project from which to get the formatter settings, 48 * <code>null</code> if the workspace default should be used 49 * @return the indent string 50 */ createIndentString(int indentationUnits, IJavaProject project)51 public static String createIndentString(int indentationUnits, IJavaProject project) { 52 Map<String, String> options= project != null ? project.getOptions(true) : JavaCore.getOptions(); 53 return ToolFactory.createCodeFormatter(options).createIndentationString(indentationUnits); 54 } 55 56 /** 57 * Gets the current tab width. 58 * 59 * @param project 60 * The project where the source is used, used for project specific options or 61 * <code>null</code> if the project is unknown and the workspace default should be used 62 * @return The tab width 63 */ getTabWidth(IJavaProject project)64 public static int getTabWidth(IJavaProject project) { 65 /* 66 * If the tab-char is SPACE, FORMATTER_INDENTATION_SIZE is not used 67 * by the core formatter. 68 * We piggy back the visual tab length setting in that preference in 69 * that case. 70 */ 71 String key; 72 if (JavaCore.SPACE.equals(getCoreOption(project, DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) 73 key= DefaultCodeFormatterConstants.FORMATTER_INDENTATION_SIZE; 74 else 75 key= DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE; 76 77 return getCoreOption(project, key, 4); 78 } 79 80 /** 81 * Returns the current indent width. 82 * 83 * @param project 84 * the project where the source is used or, 85 * <code>null</code> if the project is unknown and the workspace default should be used 86 * @return the indent width 87 * @since 3.1 88 */ getIndentWidth(IJavaProject project)89 public static int getIndentWidth(IJavaProject project) { 90 String key; 91 if (DefaultCodeFormatterConstants.MIXED.equals(getCoreOption(project, DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) 92 key= DefaultCodeFormatterConstants.FORMATTER_INDENTATION_SIZE; 93 else 94 key= DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE; 95 96 return getCoreOption(project, key, 4); 97 } 98 99 /** 100 * Returns the possibly <code>project</code>-specific core preference defined under <code>key</code>. 101 * 102 * @param project 103 * the project to get the preference from, 104 * or <code>null</code> to get the global preference 105 * @param key 106 * the key of the preference 107 * @return the value of the preference 108 * @since 3.1 109 */ getCoreOption(IJavaProject project, String key)110 private static String getCoreOption(IJavaProject project, String key) { 111 if (project == null) 112 return JavaCore.getOption(key); 113 return project.getOption(key, true); 114 } 115 116 /** 117 * Returns the possibly <code>project</code>-specific core preference defined under <code>key</code>, 118 * or <code>def</code> if the value is not a integer. 119 * 120 * @param project 121 * the project to get the preference from, 122 * or <code>null</code> to get the global preference 123 * @param key 124 * the key of the preference 125 * @param def 126 * the default value 127 * @return the value of the preference 128 * @since 3.1 129 */ getCoreOption(IJavaProject project, String key, int def)130 private static int getCoreOption(IJavaProject project, String key, int def) { 131 try { 132 return Integer.parseInt(getCoreOption(project, key)); 133 } catch (NumberFormatException e) { 134 return def; 135 } 136 } 137 138 // transition code 139 140 /** 141 * Old API. Consider to use format2 (TextEdit) 142 * 143 * @param kind 144 * Use to specify the kind of the code snippet to format. 145 * It can be any of the kind constants defined in {@link CodeFormatter} 146 * @param source 147 * The source to format 148 * @param indentationLevel 149 * The initial indentation level, used to shift left/right the entire source fragment. 150 * An initial indentation level of zero or below has no effect. 151 * @param lineSeparator 152 * The line separator to use in formatted source, 153 * if set to <code>null</code>, then the platform default one will be used. 154 * @param project 155 * The project from which to retrieve the formatter options from 156 * If set to <code>null</code>, then use the current settings from {@link JavaCore#getOptions()}. 157 * @return the formatted source string 158 */ format(int kind, String source, int indentationLevel, String lineSeparator, IJavaProject project)159 public static String format(int kind, String source, int indentationLevel, String lineSeparator, IJavaProject project) { 160 Map<String, String> options= project != null ? project.getOptions(true) : null; 161 return format(kind, source, indentationLevel, lineSeparator, options); 162 } 163 164 /** 165 * Old API. Consider to use format2 (TextEdit) 166 * 167 * @param kind 168 * Use to specify the kind of the code snippet to format. 169 * It can be any of the kind constants defined in {@link CodeFormatter} 170 * @param source 171 * The source to format 172 * @param indentationLevel 173 * The initial indentation level, used to shift left/right the entire source fragment. 174 * An initial indentation level of zero or below has no effect. 175 * @param lineSeparator 176 * The line separator to use in formatted source, 177 * if set to <code>null</code>, then the platform default one will be used. 178 * @param options 179 * The options map to use for formatting with the default code formatter. 180 * Recognized options are documented on {@link JavaCore#getDefaultOptions()}. 181 * If set to <code>null</code>, then use the current settings from {@link JavaCore#getOptions()}. 182 * @return the formatted source string 183 */ format(int kind, String source, int indentationLevel, String lineSeparator, Map<String, String> options)184 public static String format(int kind, String source, int indentationLevel, String lineSeparator, Map<String, String> options) { 185 TextEdit edit= format2(kind, source, indentationLevel, lineSeparator, options); 186 if (edit == null) { 187 return source; 188 } else { 189 Document document= new Document(source); 190 try { 191 edit.apply(document, TextEdit.NONE); 192 } catch (BadLocationException e) { 193 JavaManipulationPlugin.log(e); // bug in the formatter 194 Assert.isTrue(false, "Formatter created edits with wrong positions: " + e.getMessage()); //$NON-NLS-1$ 195 } 196 return document.get(); 197 } 198 } 199 200 /** 201 * Creates edits that describe how to format the given string. 202 * Returns <code>null</code> if the code could not be formatted for the given kind. 203 * 204 * @param kind 205 * Use to specify the kind of the code snippet to format. 206 * It can be any of the kind constants defined in {@link CodeFormatter} 207 * @param source 208 * The source to format 209 * @param offset 210 * The given offset to start recording the edits (inclusive). 211 * @param length the given length to stop recording the edits (exclusive). 212 * @param indentationLevel 213 * The initial indentation level, used to shift left/right the entire source fragment. 214 * An initial indentation level of zero or below has no effect. 215 * @param lineSeparator 216 * The line separator to use in formatted source, 217 * if set to <code>null</code>, then the platform default one will be used. 218 * @param options 219 * The options map to use for formatting with the default code formatter. 220 * Recognized options are documented on {@link JavaCore#getDefaultOptions()}. 221 * If set to <code>null</code>, then use the current settings from {@link JavaCore#getOptions()}. 222 * @return an TextEdit describing the changes required to format source 223 * @throws IllegalArgumentException 224 * If the offset and length are not inside the string, a IllegalArgumentException is thrown. 225 */ format2(int kind, String source, int offset, int length, int indentationLevel, String lineSeparator, Map<String, String> options)226 public static TextEdit format2(int kind, String source, int offset, int length, int indentationLevel, String lineSeparator, Map<String, String> options) { 227 if (offset < 0 || length < 0 || offset + length > source.length()) { 228 throw new IllegalArgumentException("offset or length outside of string. offset: " + offset + ", length: " + length + ", string size: " + source.length()); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ 229 } 230 return ToolFactory.createCodeFormatter(options).format(kind, source, offset, length, indentationLevel, lineSeparator); 231 } 232 233 /** 234 * Creates edits that describe how to format the given string. 235 * Returns <code>null</code> if the code could not be formatted for the given kind. 236 * 237 * @param kind 238 * Use to specify the kind of the code snippet to format. 239 * It can be any of the kind constants defined in {@link CodeFormatter} 240 * @param source 241 * The source to format 242 * @param indentationLevel 243 * The initial indentation level, used to shift left/right the entire source fragment. 244 * An initial indentation level of zero or below has no effect. 245 * @param lineSeparator 246 * The line separator to use in formatted source, 247 * if set to <code>null</code>, then the platform default one will be used. 248 * @param options 249 * The options map to use for formatting with the default code formatter. 250 * Recognized options are documented on {@link JavaCore#getDefaultOptions()}. 251 * If set to <code>null</code>, then use the current settings from {@link JavaCore#getOptions()}. 252 * @return an TextEdit describing the changes required to format source 253 * @throws IllegalArgumentException 254 * If the offset and length are not inside the string, a IllegalArgumentException is thrown. 255 */ format2(int kind, String source, int indentationLevel, String lineSeparator, Map<String, String> options)256 public static TextEdit format2(int kind, String source, int indentationLevel, String lineSeparator, Map<String, String> options) { 257 return format2(kind, source, 0, source.length(), indentationLevel, lineSeparator, options); 258 } 259 260 /** 261 * Creates edits that describe how to re-format the given string. 262 * This method should be used for formatting existing code. 263 * Returns <code>null</code> if the code could not be formatted for the given kind. 264 * 265 * @param kind 266 * Use to specify the kind of the code snippet to format. 267 * It can be any of the kind constants defined in {@link CodeFormatter} 268 * @param source 269 * The source to format 270 * @param offset 271 * The given offset to start recording the edits (inclusive). 272 * @param length the given length to stop recording the edits (exclusive). 273 * @param indentationLevel 274 * The initial indentation level, used to shift left/right the entire source fragment. 275 * An initial indentation level of zero or below has no effect. 276 * @param lineSeparator 277 * The line separator to use in formatted source, 278 * if set to <code>null</code>, then the platform default one will be used. 279 * @param options 280 * The options map to use for formatting with the default code formatter. 281 * Recognized options are documented on {@link JavaCore#getDefaultOptions()}. 282 * If set to <code>null</code>, then use the current settings from {@link JavaCore#getOptions()}. 283 * @return an TextEdit describing the changes required to format source 284 * @throws IllegalArgumentException 285 * If the offset and length are not inside the string, a IllegalArgumentException is thrown. 286 */ reformat(int kind, String source, int offset, int length, int indentationLevel, String lineSeparator, Map<String, String> options)287 public static TextEdit reformat(int kind, String source, int offset, int length, int indentationLevel, String lineSeparator, Map<String, String> options) { 288 if (offset < 0 || length < 0 || offset + length > source.length()) { 289 throw new IllegalArgumentException("offset or length outside of string. offset: " + offset + ", length: " + length + ", string size: " + source.length()); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ 290 } 291 return ToolFactory.createCodeFormatter(options, ToolFactory.M_FORMAT_EXISTING).format(kind, source, offset, length, indentationLevel, lineSeparator); 292 } 293 294 /** 295 * Creates edits that describe how to re-format the regions in the given string. 296 * This method should be used for formatting existing code. 297 * Returns <code>null</code> if the code could not be formatted for the given kind. 298 * 299 * <p>No region in <code>regions</code> must overlap with any other region in <code>regions</code>. 300 * Each region must be within source. There must be at least one region. Regions must be sorted 301 * by their offsets, smaller offset first.</p> 302 * 303 * @param kind 304 * Use to specify the kind of the code snippet to format. 305 * It can be any of K_EXPRESSION, K_STATEMENTS, K_CLASS_BODY_DECLARATIONS, K_COMPILATION_UNIT, K_UNKNOWN 306 * @param source 307 * The source to format 308 * @param regions 309 * a set of regions in the string to format 310 * @param indentationLevel 311 * The initial indentation level, used to shift left/right the entire source fragment. 312 * An initial indentation level of zero or below has no effect. 313 * @param lineSeparator 314 * The line separator to use in formatted source, 315 * if set to <code>null</code>, then the platform default one will be used. 316 * @param options 317 * The options map to use for formatting with the default code formatter. 318 * Recognized options are documented on {@link JavaCore#getDefaultOptions()}. 319 * If set to <code>null</code>, then use the current settings from {@link JavaCore#getOptions()}. 320 * @return an TextEdit describing the changes required to format source 321 * @throws IllegalArgumentException if there is no region, a region overlaps with another region, or the regions are not 322 * sorted in the ascending order. 323 * @since 3.4 324 */ reformat(int kind, String source, IRegion[] regions, int indentationLevel, String lineSeparator, Map<String, String> options)325 public static TextEdit reformat(int kind, String source, IRegion[] regions, int indentationLevel, String lineSeparator, Map<String, String> options) { 326 return ToolFactory.createCodeFormatter(options, ToolFactory.M_FORMAT_EXISTING).format(kind, source, regions, indentationLevel, lineSeparator); 327 } 328 329 /** 330 * Creates edits that describe how to re-format the given string. 331 * This method should be used for formatting existing code. 332 * Returns <code>null</code> if the code could not be formatted for the given kind. 333 * 334 * @param kind 335 * Use to specify the kind of the code snippet to format. 336 * It can be any of the kind constants defined in {@link CodeFormatter} 337 * @param source 338 * The source to format 339 * @param indentationLevel 340 * The initial indentation level, used to shift left/right the entire source fragment. 341 * An initial indentation level of zero or below has no effect. 342 * @param lineSeparator 343 * The line separator to use in formatted source, 344 * if set to <code>null</code>, then the platform default one will be used. 345 * @param options 346 * The options map to use for formatting with the default code formatter. 347 * Recognized options are documented on {@link JavaCore#getDefaultOptions()}. 348 * If set to <code>null</code>, then use the current settings from {@link JavaCore#getOptions()}. 349 * @return an TextEdit describing the changes required to format source 350 * @throws IllegalArgumentException 351 * If the offset and length are not inside the string, a IllegalArgumentException is thrown. 352 */ reformat(int kind, String source, int indentationLevel, String lineSeparator, Map<String, String> options)353 public static TextEdit reformat(int kind, String source, int indentationLevel, String lineSeparator, Map<String, String> options) { 354 return reformat(kind, source, 0, source.length(), indentationLevel, lineSeparator, options); 355 } 356 357 /** 358 * Creates edits that describe how to format the given string. 359 * The given node is used to infer the kind to use to format the string. 360 * Consider to use {@link #format2(int, String, int, String, Map)} if the kind is already known. 361 * Returns <code>null</code> if the code could not be formatted for the given kind. 362 * 363 * @param node 364 * Use to infer the kind of the code snippet to format. 365 * @param source 366 * The source to format 367 * @param indentationLevel 368 * The initial indentation level, used to shift left/right the entire source fragment. 369 * An initial indentation level of zero or below has no effect. 370 * @param lineSeparator 371 * The line separator to use in formatted source, 372 * if set to <code>null</code>, then the platform default one will be used. 373 * @param options 374 * The options map to use for formatting with the default code formatter. 375 * Recognized options are documented on {@link JavaCore#getDefaultOptions()}. 376 * If set to <code>null</code>, then use the current settings from {@link JavaCore#getOptions()}. 377 * @return an TextEdit describing the changes required to format source 378 * @throws IllegalArgumentException 379 * If the offset and length are not inside the string, a IllegalArgumentException is thrown. 380 */ format2(ASTNode node, String source, int indentationLevel, String lineSeparator, Map<String, String> options)381 public static TextEdit format2(ASTNode node, String source, int indentationLevel, String lineSeparator, Map<String, String> options) { 382 int code; 383 String prefix= ""; //$NON-NLS-1$ 384 String suffix= ""; //$NON-NLS-1$ 385 if (node instanceof Statement) { 386 code= CodeFormatter.K_STATEMENTS; 387 if (node.getNodeType() == ASTNode.SWITCH_CASE) { 388 prefix= "switch(1) {"; //$NON-NLS-1$ 389 suffix= "}"; //$NON-NLS-1$ 390 code= CodeFormatter.K_STATEMENTS; 391 } 392 } else if (node instanceof Expression && node.getNodeType() != ASTNode.VARIABLE_DECLARATION_EXPRESSION) { 393 code= CodeFormatter.K_EXPRESSION; 394 } else if (node instanceof BodyDeclaration) { 395 code= CodeFormatter.K_CLASS_BODY_DECLARATIONS; 396 } else { 397 switch (node.getNodeType()) { 398 case ASTNode.ARRAY_TYPE: 399 case ASTNode.PARAMETERIZED_TYPE: 400 case ASTNode.PRIMITIVE_TYPE: 401 case ASTNode.QUALIFIED_TYPE: 402 case ASTNode.SIMPLE_TYPE: 403 suffix= " x;"; //$NON-NLS-1$ 404 code= CodeFormatter.K_CLASS_BODY_DECLARATIONS; 405 break; 406 case ASTNode.WILDCARD_TYPE: 407 prefix= "A<"; //$NON-NLS-1$ 408 suffix= "> x;"; //$NON-NLS-1$ 409 code= CodeFormatter.K_CLASS_BODY_DECLARATIONS; 410 break; 411 case ASTNode.COMPILATION_UNIT: 412 code= CodeFormatter.K_COMPILATION_UNIT; 413 break; 414 case ASTNode.MODULE_DECLARATION: 415 code= CodeFormatter.K_MODULE_INFO; 416 break; 417 case ASTNode.VARIABLE_DECLARATION_EXPRESSION: 418 case ASTNode.SINGLE_VARIABLE_DECLARATION: 419 suffix= ";"; //$NON-NLS-1$ 420 code= CodeFormatter.K_STATEMENTS; 421 break; 422 case ASTNode.VARIABLE_DECLARATION_FRAGMENT: 423 prefix= "A "; //$NON-NLS-1$ 424 suffix= ";"; //$NON-NLS-1$ 425 code= CodeFormatter.K_STATEMENTS; 426 break; 427 case ASTNode.PACKAGE_DECLARATION: 428 case ASTNode.IMPORT_DECLARATION: 429 suffix= "\nclass A {}"; //$NON-NLS-1$ 430 code= CodeFormatter.K_COMPILATION_UNIT; 431 break; 432 case ASTNode.JAVADOC: 433 suffix= "void foo();"; //$NON-NLS-1$ 434 code= CodeFormatter.K_CLASS_BODY_DECLARATIONS; 435 break; 436 case ASTNode.CATCH_CLAUSE: 437 prefix= "try {}"; //$NON-NLS-1$ 438 code= CodeFormatter.K_STATEMENTS; 439 break; 440 case ASTNode.ANONYMOUS_CLASS_DECLARATION: 441 prefix= "new A()"; //$NON-NLS-1$ 442 suffix= ";"; //$NON-NLS-1$ 443 code= CodeFormatter.K_STATEMENTS; 444 break; 445 case ASTNode.MEMBER_VALUE_PAIR: 446 prefix= "@Author("; //$NON-NLS-1$ 447 suffix= ") class x {}"; //$NON-NLS-1$ 448 code= CodeFormatter.K_COMPILATION_UNIT; 449 break; 450 case ASTNode.MODIFIER: 451 suffix= " class x {}"; //$NON-NLS-1$ 452 code= CodeFormatter.K_COMPILATION_UNIT; 453 break; 454 case ASTNode.TYPE_PARAMETER: 455 prefix= "class X<"; //$NON-NLS-1$ 456 suffix= "> {}"; //$NON-NLS-1$ 457 code= CodeFormatter.K_COMPILATION_UNIT; 458 break; 459 case ASTNode.MEMBER_REF: 460 case ASTNode.METHOD_REF: 461 case ASTNode.METHOD_REF_PARAMETER: 462 case ASTNode.TAG_ELEMENT: 463 case ASTNode.TEXT_ELEMENT: 464 // Javadoc formatting not yet supported: 465 return null; 466 default: 467 //Assert.isTrue(false, "Node type not covered: " + node.getClass().getName()); //$NON-NLS-1$ 468 return null; 469 } 470 } 471 472 String concatStr= prefix + source + suffix; 473 TextEdit edit= format2(code, concatStr, prefix.length(), source.length(), indentationLevel, lineSeparator, options); 474 if (edit != null && prefix.length() > 0) { 475 edit.moveTree(-prefix.length()); 476 } 477 return edit; 478 } 479 CodeFormatterUtil()480 private CodeFormatterUtil() { 481 } 482 483 } 484